mixdog 0.7.18 → 0.8.1
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/README.md +37 -331
- package/package.json +67 -99
- package/scripts/boot-smoke.mjs +94 -0
- package/scripts/build-tui.mjs +52 -0
- package/scripts/compact-smoke.mjs +199 -0
- package/scripts/lead-workflow-smoke.mjs +598 -0
- package/scripts/live-worker-smoke.mjs +239 -0
- package/scripts/output-style-smoke.mjs +101 -0
- package/scripts/session-context-bench.mjs +172 -0
- package/scripts/smoke-loop-report.mjs +221 -0
- package/scripts/smoke-loop.mjs +201 -0
- package/scripts/smoke.mjs +113 -0
- package/scripts/tool-failures.mjs +143 -0
- package/scripts/tool-smoke.mjs +452 -0
- package/src/agents/debugger/AGENT.md +3 -0
- package/src/agents/debugger/agent.json +6 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/agents/heavy-worker/AGENT.md +3 -0
- package/src/agents/heavy-worker/agent.json +6 -0
- package/src/agents/maintainer/AGENT.md +3 -0
- package/src/agents/maintainer/agent.json +6 -0
- package/src/agents/reviewer/AGENT.md +3 -0
- package/src/agents/reviewer/agent.json +6 -0
- package/src/agents/scheduler-task.md +3 -0
- package/src/agents/web-researcher/AGENT.md +3 -0
- package/src/agents/web-researcher/agent.json +6 -0
- package/src/agents/webhook-handler.md +3 -0
- package/src/agents/worker/AGENT.md +3 -0
- package/src/agents/worker/agent.json +6 -0
- package/src/app.mjs +90 -0
- package/src/cli.mjs +11 -0
- package/src/defaults/hidden-roles.json +72 -0
- package/src/defaults/mixdog-config.template.json +15 -0
- package/src/hooks/lib/permission-evaluator.cjs +488 -0
- package/src/hooks/lib/settings-loader.cjs +112 -0
- package/src/lib/keychain-cjs.cjs +332 -0
- package/src/lib/plugin-paths.cjs +28 -0
- package/src/lib/rules-builder.cjs +315 -0
- package/src/mixdog-session-runtime.mjs +3813 -0
- package/src/output-styles/default.md +38 -0
- package/src/output-styles/extreme-simple.md +17 -0
- package/src/output-styles/simple.md +17 -0
- package/src/repl.mjs +330 -0
- package/src/rules/bridge/00-common.md +5 -0
- package/src/rules/bridge/20-skip-protocol.md +11 -0
- package/src/rules/bridge/30-explorer.md +4 -0
- package/src/rules/bridge/40-cycle1-agent.md +28 -0
- package/src/rules/bridge/41-cycle2-agent.md +59 -0
- package/src/rules/lead/00-tool-lead.md +5 -0
- package/src/rules/lead/01-general.md +5 -0
- package/src/rules/lead/02-channels.md +3 -0
- package/src/rules/lead/04-workflow.md +12 -0
- package/src/rules/shared/00-language.md +3 -0
- package/src/rules/shared/01-tool.md +3 -0
- package/src/runtime/agent/orchestrator/bridge-trace.mjs +814 -0
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +60 -0
- package/src/runtime/agent/orchestrator/config.mjs +446 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +796 -0
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +417 -0
- package/src/runtime/agent/orchestrator/internal-roles.mjs +188 -0
- package/src/runtime/agent/orchestrator/internal-tools.mjs +88 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +345 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2104 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +784 -0
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +341 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +1679 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +959 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +213 -0
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +38 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +471 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +615 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +808 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1719 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2587 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1953 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +136 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +317 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +109 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +247 -0
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +332 -0
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/trace-utils.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +318 -0
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +367 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +882 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +233 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +2269 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +2972 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +870 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +166 -0
- package/src/runtime/agent/orchestrator/smart-bridge/bridge-llm.mjs +339 -0
- package/src/runtime/agent/orchestrator/smart-bridge/cache-strategy.mjs +419 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +227 -0
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +235 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +723 -0
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +389 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +637 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +165 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +104 -0
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +194 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +596 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +110 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +153 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +189 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +731 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +168 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +465 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +160 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +982 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1087 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +231 -0
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +223 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +478 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4102 -0
- package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +323 -0
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +154 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +26 -0
- package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +18 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +2772 -0
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +114 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +880 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +312 -0
- package/src/runtime/channels/backends/discord.mjs +781 -0
- package/src/runtime/channels/data/voice-runtime-manifest.json +138 -0
- package/src/runtime/channels/index.mjs +3309 -0
- package/src/runtime/channels/lib/config.mjs +285 -0
- package/src/runtime/channels/lib/drop-trace.mjs +71 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +81 -0
- package/src/runtime/channels/lib/holidays.mjs +138 -0
- package/src/runtime/channels/lib/hook-pipe-server.mjs +671 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +765 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +497 -0
- package/src/runtime/channels/lib/scheduler.mjs +710 -0
- package/src/runtime/channels/lib/session-discovery.mjs +102 -0
- package/src/runtime/channels/lib/state-file.mjs +68 -0
- package/src/runtime/channels/lib/status-snapshot.mjs +224 -0
- package/src/runtime/channels/lib/tool-format.mjs +122 -0
- package/src/runtime/channels/lib/transcript-discovery.mjs +195 -0
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +734 -0
- package/src/runtime/channels/lib/webhook.mjs +1288 -0
- package/src/runtime/channels/tool-defs.mjs +177 -0
- package/src/runtime/lib/keychain-cjs.cjs +289 -0
- package/src/runtime/memory/data/runtime-manifest.json +40 -0
- package/src/runtime/memory/index.mjs +3706 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +336 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +275 -0
- package/src/runtime/memory/lib/embedding-worker.mjs +331 -0
- package/src/runtime/memory/lib/memory-cycle-requests.mjs +276 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +783 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +1389 -0
- package/src/runtime/memory/lib/memory-cycle3.mjs +646 -0
- package/src/runtime/memory/lib/memory-embed.mjs +300 -0
- package/src/runtime/memory/lib/memory-ops-policy.mjs +149 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +644 -0
- package/src/runtime/memory/lib/memory.mjs +418 -0
- package/src/runtime/memory/lib/pg/adapter.mjs +328 -0
- package/src/runtime/memory/lib/pg/process.mjs +366 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +495 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +464 -0
- package/src/runtime/memory/lib/trace-store.mjs +734 -0
- package/src/runtime/memory/tool-defs.mjs +79 -0
- package/src/runtime/search/index.mjs +925 -0
- package/src/runtime/search/lib/config.mjs +61 -0
- package/src/runtime/search/lib/web-tools.mjs +1278 -0
- package/src/runtime/search/tool-defs.mjs +64 -0
- package/src/runtime/shared/atomic-file.mjs +435 -0
- package/src/runtime/shared/background-tasks.mjs +376 -0
- package/src/runtime/shared/child-guardian.mjs +98 -0
- package/src/runtime/shared/config.mjs +393 -0
- package/src/runtime/shared/err-text.mjs +121 -0
- package/src/runtime/shared/launcher-control.mjs +259 -0
- package/src/runtime/shared/llm/http-agent.mjs +129 -0
- package/src/runtime/shared/open-url.mjs +37 -0
- package/src/runtime/shared/plugin-paths.mjs +25 -0
- package/src/runtime/shared/process-shutdown.mjs +147 -0
- package/src/runtime/shared/schedules-store.mjs +70 -0
- package/src/runtime/shared/tool-execution-contract.mjs +104 -0
- package/src/runtime/shared/tool-surface.mjs +947 -0
- package/src/runtime/shared/user-cwd.mjs +221 -0
- package/src/runtime/shared/user-data-guard.mjs +232 -0
- package/src/runtime/shared/workspace-router.mjs +259 -0
- package/src/standalone/bridge-tool.mjs +1414 -0
- package/src/standalone/channel-admin.mjs +366 -0
- package/src/standalone/channel-worker-preload.cjs +3 -0
- package/src/standalone/channel-worker.mjs +353 -0
- package/src/standalone/explore-tool.mjs +233 -0
- package/src/standalone/hook-bus.mjs +246 -0
- package/src/standalone/plugin-admin.mjs +247 -0
- package/src/standalone/provider-admin.mjs +338 -0
- package/src/standalone/seeds.mjs +94 -0
- package/src/standalone/usage-dashboard.mjs +510 -0
- package/src/tui/App.jsx +5446 -0
- package/src/tui/components/AnsiText.jsx +199 -0
- package/src/tui/components/ContextPanel.jsx +265 -0
- package/src/tui/components/Markdown.jsx +205 -0
- package/src/tui/components/MarkdownTable.jsx +204 -0
- package/src/tui/components/Message.jsx +103 -0
- package/src/tui/components/Picker.jsx +317 -0
- package/src/tui/components/PromptInput.jsx +584 -0
- package/src/tui/components/QueuedCommands.jsx +47 -0
- package/src/tui/components/SlashCommandPalette.jsx +114 -0
- package/src/tui/components/Spinner.jsx +317 -0
- package/src/tui/components/StatusLine.jsx +87 -0
- package/src/tui/components/TextEntryPanel.jsx +323 -0
- package/src/tui/components/ToolExecution.jsx +791 -0
- package/src/tui/components/TurnDone.jsx +78 -0
- package/src/tui/components/UsagePanel.jsx +331 -0
- package/src/tui/dist/index.mjs +12420 -0
- package/src/tui/engine.mjs +2410 -0
- package/src/tui/figures.mjs +50 -0
- package/src/tui/hooks/useEngine.mjs +16 -0
- package/src/tui/index.jsx +254 -0
- package/src/tui/input-editing.mjs +242 -0
- package/src/tui/markdown/format-token.mjs +194 -0
- package/src/tui/paste-attachments.mjs +198 -0
- package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
- package/src/tui/spinner-verbs.mjs +45 -0
- package/src/tui/theme.mjs +67 -0
- package/src/tui/time-format.mjs +53 -0
- package/src/ui/ansi.mjs +115 -0
- package/src/ui/markdown.mjs +195 -0
- package/src/ui/statusline.mjs +730 -0
- package/src/ui/tool-card.mjs +99 -0
- package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
- package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
- package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
- package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
- package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
- package/src/workflows/default/WORKFLOW.md +7 -0
- package/src/workflows/default/workflow.json +14 -0
- package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
- package/vendor/ink/build/ansi-tokenizer.js +316 -0
- package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
- package/vendor/ink/build/colorize.d.ts +3 -0
- package/vendor/ink/build/colorize.js +48 -0
- package/vendor/ink/build/colorize.js.map +1 -0
- package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
- package/vendor/ink/build/components/AccessibilityContext.js +5 -0
- package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
- package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
- package/vendor/ink/build/components/AnimationContext.js +13 -0
- package/vendor/ink/build/components/AnimationContext.js.map +1 -0
- package/vendor/ink/build/components/App.d.ts +24 -0
- package/vendor/ink/build/components/App.js +554 -0
- package/vendor/ink/build/components/App.js.map +1 -0
- package/vendor/ink/build/components/AppContext.d.ts +80 -0
- package/vendor/ink/build/components/AppContext.js +25 -0
- package/vendor/ink/build/components/AppContext.js.map +1 -0
- package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
- package/vendor/ink/build/components/BackgroundContext.js +3 -0
- package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
- package/vendor/ink/build/components/Box.d.ts +130 -0
- package/vendor/ink/build/components/Box.js +34 -0
- package/vendor/ink/build/components/Box.js.map +1 -0
- package/vendor/ink/build/components/CursorContext.d.ts +11 -0
- package/vendor/ink/build/components/CursorContext.js +8 -0
- package/vendor/ink/build/components/CursorContext.js.map +1 -0
- package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
- package/vendor/ink/build/components/ErrorBoundary.js +23 -0
- package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
- package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
- package/vendor/ink/build/components/ErrorOverview.js +90 -0
- package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
- package/vendor/ink/build/components/FocusContext.d.ts +16 -0
- package/vendor/ink/build/components/FocusContext.js +17 -0
- package/vendor/ink/build/components/FocusContext.js.map +1 -0
- package/vendor/ink/build/components/Newline.d.ts +13 -0
- package/vendor/ink/build/components/Newline.js +8 -0
- package/vendor/ink/build/components/Newline.js.map +1 -0
- package/vendor/ink/build/components/Spacer.d.ts +7 -0
- package/vendor/ink/build/components/Spacer.js +11 -0
- package/vendor/ink/build/components/Spacer.js.map +1 -0
- package/vendor/ink/build/components/Static.d.ts +24 -0
- package/vendor/ink/build/components/Static.js +28 -0
- package/vendor/ink/build/components/Static.js.map +1 -0
- package/vendor/ink/build/components/StderrContext.d.ts +15 -0
- package/vendor/ink/build/components/StderrContext.js +13 -0
- package/vendor/ink/build/components/StderrContext.js.map +1 -0
- package/vendor/ink/build/components/StdinContext.d.ts +28 -0
- package/vendor/ink/build/components/StdinContext.js +20 -0
- package/vendor/ink/build/components/StdinContext.js.map +1 -0
- package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
- package/vendor/ink/build/components/StdoutContext.js +13 -0
- package/vendor/ink/build/components/StdoutContext.js.map +1 -0
- package/vendor/ink/build/components/Text.d.ts +55 -0
- package/vendor/ink/build/components/Text.js +50 -0
- package/vendor/ink/build/components/Text.js.map +1 -0
- package/vendor/ink/build/components/Transform.d.ts +16 -0
- package/vendor/ink/build/components/Transform.js +15 -0
- package/vendor/ink/build/components/Transform.js.map +1 -0
- package/vendor/ink/build/cursor-helpers.d.ts +39 -0
- package/vendor/ink/build/cursor-helpers.js +62 -0
- package/vendor/ink/build/cursor-helpers.js.map +1 -0
- package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
- package/vendor/ink/build/devtools-window-polyfill.js +68 -0
- package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
- package/vendor/ink/build/devtools.d.ts +1 -0
- package/vendor/ink/build/devtools.js +36 -0
- package/vendor/ink/build/devtools.js.map +1 -0
- package/vendor/ink/build/dom.d.ts +62 -0
- package/vendor/ink/build/dom.js +143 -0
- package/vendor/ink/build/dom.js.map +1 -0
- package/vendor/ink/build/get-max-width.d.ts +3 -0
- package/vendor/ink/build/get-max-width.js +10 -0
- package/vendor/ink/build/get-max-width.js.map +1 -0
- package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
- package/vendor/ink/build/hooks/use-animation.js +87 -0
- package/vendor/ink/build/hooks/use-animation.js.map +1 -0
- package/vendor/ink/build/hooks/use-app.d.ts +5 -0
- package/vendor/ink/build/hooks/use-app.js +8 -0
- package/vendor/ink/build/hooks/use-app.js.map +1 -0
- package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
- package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
- package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
- package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
- package/vendor/ink/build/hooks/use-cursor.js +29 -0
- package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
- package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
- package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
- package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
- package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
- package/vendor/ink/build/hooks/use-focus.js +43 -0
- package/vendor/ink/build/hooks/use-focus.js.map +1 -0
- package/vendor/ink/build/hooks/use-input.d.ts +132 -0
- package/vendor/ink/build/hooks/use-input.js +126 -0
- package/vendor/ink/build/hooks/use-input.js.map +1 -0
- package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
- package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
- package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
- package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
- package/vendor/ink/build/hooks/use-paste.js +62 -0
- package/vendor/ink/build/hooks/use-paste.js.map +1 -0
- package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
- package/vendor/ink/build/hooks/use-stderr.js +8 -0
- package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
- package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
- package/vendor/ink/build/hooks/use-stdin.js +9 -0
- package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
- package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
- package/vendor/ink/build/hooks/use-stdout.js +8 -0
- package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
- package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
- package/vendor/ink/build/hooks/use-window-size.js +22 -0
- package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
- package/vendor/ink/build/index.d.ts +42 -0
- package/vendor/ink/build/index.js +24 -0
- package/vendor/ink/build/index.js.map +1 -0
- package/vendor/ink/build/ink.d.ts +146 -0
- package/vendor/ink/build/ink.js +1022 -0
- package/vendor/ink/build/ink.js.map +1 -0
- package/vendor/ink/build/input-parser.d.ts +10 -0
- package/vendor/ink/build/input-parser.js +194 -0
- package/vendor/ink/build/input-parser.js.map +1 -0
- package/vendor/ink/build/instances.d.ts +3 -0
- package/vendor/ink/build/instances.js +8 -0
- package/vendor/ink/build/instances.js.map +1 -0
- package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
- package/vendor/ink/build/kitty-keyboard.js +32 -0
- package/vendor/ink/build/kitty-keyboard.js.map +1 -0
- package/vendor/ink/build/log-update.d.ts +20 -0
- package/vendor/ink/build/log-update.js +261 -0
- package/vendor/ink/build/log-update.js.map +1 -0
- package/vendor/ink/build/measure-element.d.ts +20 -0
- package/vendor/ink/build/measure-element.js +13 -0
- package/vendor/ink/build/measure-element.js.map +1 -0
- package/vendor/ink/build/measure-text.d.ts +6 -0
- package/vendor/ink/build/measure-text.js +21 -0
- package/vendor/ink/build/measure-text.js.map +1 -0
- package/vendor/ink/build/output.d.ts +35 -0
- package/vendor/ink/build/output.js +328 -0
- package/vendor/ink/build/output.js.map +1 -0
- package/vendor/ink/build/parse-keypress.d.ts +20 -0
- package/vendor/ink/build/parse-keypress.js +495 -0
- package/vendor/ink/build/parse-keypress.js.map +1 -0
- package/vendor/ink/build/reconciler.d.ts +4 -0
- package/vendor/ink/build/reconciler.js +306 -0
- package/vendor/ink/build/reconciler.js.map +1 -0
- package/vendor/ink/build/render-background.d.ts +4 -0
- package/vendor/ink/build/render-background.js +25 -0
- package/vendor/ink/build/render-background.js.map +1 -0
- package/vendor/ink/build/render-border.d.ts +4 -0
- package/vendor/ink/build/render-border.js +84 -0
- package/vendor/ink/build/render-border.js.map +1 -0
- package/vendor/ink/build/render-node-to-output.d.ts +14 -0
- package/vendor/ink/build/render-node-to-output.js +162 -0
- package/vendor/ink/build/render-node-to-output.js.map +1 -0
- package/vendor/ink/build/render-to-string.d.ts +38 -0
- package/vendor/ink/build/render-to-string.js +116 -0
- package/vendor/ink/build/render-to-string.js.map +1 -0
- package/vendor/ink/build/render.d.ts +176 -0
- package/vendor/ink/build/render.js +71 -0
- package/vendor/ink/build/render.js.map +1 -0
- package/vendor/ink/build/renderer.d.ts +8 -0
- package/vendor/ink/build/renderer.js +64 -0
- package/vendor/ink/build/renderer.js.map +1 -0
- package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
- package/vendor/ink/build/sanitize-ansi.js +27 -0
- package/vendor/ink/build/sanitize-ansi.js.map +1 -0
- package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
- package/vendor/ink/build/squash-text-nodes.js +36 -0
- package/vendor/ink/build/squash-text-nodes.js.map +1 -0
- package/vendor/ink/build/styles.d.ts +302 -0
- package/vendor/ink/build/styles.js +303 -0
- package/vendor/ink/build/styles.js.map +1 -0
- package/vendor/ink/build/utils.d.ts +9 -0
- package/vendor/ink/build/utils.js +19 -0
- package/vendor/ink/build/utils.js.map +1 -0
- package/vendor/ink/build/wrap-text.d.ts +3 -0
- package/vendor/ink/build/wrap-text.js +38 -0
- package/vendor/ink/build/wrap-text.js.map +1 -0
- package/vendor/ink/build/write-synchronized.d.ts +4 -0
- package/vendor/ink/build/write-synchronized.js +9 -0
- package/vendor/ink/build/write-synchronized.js.map +1 -0
- package/vendor/ink/license +10 -0
- package/vendor/ink/package.json +137 -0
- package/.claude-plugin/marketplace.json +0 -34
- package/.claude-plugin/plugin.json +0 -20
- package/.gitattributes +0 -34
- package/.mcp.json +0 -14
- package/ARCHITECTURE.md +0 -77
- package/CHANGELOG.md +0 -30
- package/CONTRIBUTING.md +0 -45
- package/DATA-FLOW.md +0 -79
- package/LICENSE +0 -21
- package/SECURITY.md +0 -138
- package/UNINSTALL.md +0 -115
- package/agents/maintenance.md +0 -5
- package/agents/memory-classification.md +0 -30
- package/agents/scheduler-task.md +0 -18
- package/agents/webhook-handler.md +0 -27
- package/agents/worker.md +0 -24
- package/bin/bridge +0 -133
- package/bin/statusline-launcher.mjs +0 -82
- package/bin/statusline-lib.mjs +0 -581
- package/bin/statusline-route.mjs +0 -273
- package/bin/statusline.mjs +0 -638
- package/bun.lock +0 -927
- package/commands/config.md +0 -16
- package/commands/doctor.md +0 -13
- package/commands/model.md +0 -61
- package/commands/setup.md +0 -17
- package/defaults/hidden-roles.json +0 -68
- package/defaults/memory-chunk-prompt.md +0 -63
- package/defaults/mixdog-config.template.json +0 -27
- package/defaults/user-workflow.json +0 -8
- package/defaults/user-workflow.md +0 -17
- package/hooks/hooks.json +0 -73
- package/hooks/lib/active-instance.cjs +0 -77
- package/hooks/lib/permission-evaluator.cjs +0 -411
- package/hooks/lib/permission-route.cjs +0 -63
- package/hooks/lib/settings-loader.cjs +0 -117
- package/hooks/post-tool-use.cjs +0 -84
- package/hooks/pre-mcp-sandbox.cjs +0 -158
- package/hooks/pre-tool-subagent.cjs +0 -258
- package/hooks/session-start.cjs +0 -1493
- package/hooks/shim-launcher.cjs +0 -65
- package/hooks/turn-timer.cjs +0 -82
- package/lib/claude-md-writer.cjs +0 -386
- package/lib/keychain-cjs.cjs +0 -290
- package/lib/plugin-paths.cjs +0 -69
- package/lib/rules-builder.cjs +0 -241
- package/native/README.md +0 -117
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/prompts/code-review.txt +0 -16
- package/prompts/security-audit.txt +0 -17
- package/rules/bridge/00-common.md +0 -39
- package/rules/bridge/20-skip-protocol.md +0 -18
- package/rules/bridge/30-explorer.md +0 -33
- package/rules/bridge/40-cycle1-agent.md +0 -52
- package/rules/bridge/41-cycle2-agent.md +0 -62
- package/rules/lead/00-tool-lead.md +0 -61
- package/rules/lead/01-general.md +0 -26
- package/rules/lead/02-channels.md +0 -49
- package/rules/lead/03-team.md +0 -27
- package/rules/lead/04-workflow.md +0 -20
- package/rules/shared/00-language.md +0 -14
- package/rules/shared/01-tool.md +0 -138
- package/scripts/bootstrap.mjs +0 -130
- package/scripts/bridge-unify-smoke.mjs +0 -308
- package/scripts/build-runtime-linux.sh +0 -348
- package/scripts/build-runtime-macos.sh +0 -217
- package/scripts/build-runtime-windows.ps1 +0 -242
- package/scripts/builtin-utils-smoke.mjs +0 -398
- package/scripts/bump.mjs +0 -80
- package/scripts/check-json.mjs +0 -45
- package/scripts/check-syntax-changed.mjs +0 -102
- package/scripts/check-syntax.mjs +0 -58
- package/scripts/code-graph-batch.test.mjs +0 -33
- package/scripts/config-preserve-smoke.mjs +0 -180
- package/scripts/doctor.mjs +0 -489
- package/scripts/edit-normalize-fuzz.mjs +0 -130
- package/scripts/edit-normalize-smoke.mjs +0 -401
- package/scripts/edit-operation-smoke.mjs +0 -369
- package/scripts/edit2-smoke.mjs +0 -63
- package/scripts/ensure-deps.mjs +0 -259
- package/scripts/fuzzy-e2e.mjs +0 -28
- package/scripts/fuzzy-smoke.mjs +0 -26
- package/scripts/gateway-model.mjs +0 -596
- package/scripts/generate-runtime-manifest.mjs +0 -166
- package/scripts/guard-smoke.mjs +0 -66
- package/scripts/hidden-role-schema-smoke.mjs +0 -162
- package/scripts/hook-routing-smoke.mjs +0 -29
- package/scripts/inject-input.ps1 +0 -204
- package/scripts/io-complex-smoke.mjs +0 -667
- package/scripts/io-explore-bench.mjs +0 -424
- package/scripts/io-guardrails-smoke.mjs +0 -205
- package/scripts/io-mini-bench-baseline.json +0 -11
- package/scripts/io-mini-bench.mjs +0 -216
- package/scripts/io-route-harness.mjs +0 -933
- package/scripts/io-telemetry-report.mjs +0 -691
- package/scripts/lib/gateway-inventory.mjs +0 -178
- package/scripts/lib/gateway-settings.mjs +0 -78
- package/scripts/mutation-bench.mjs +0 -564
- package/scripts/mutation-io-smoke.mjs +0 -1097
- package/scripts/native-patch-bridge-smoke.mjs +0 -288
- package/scripts/native-patch-smoke.mjs +0 -304
- package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
- package/scripts/patch-interior-context-smoke.mjs +0 -49
- package/scripts/patch-newline-utf8-smoke.mjs +0 -157
- package/scripts/perf-hook-smoke.mjs +0 -71
- package/scripts/permission-eval-smoke.mjs +0 -443
- package/scripts/prep-patch.mjs +0 -53
- package/scripts/prep-shim.mjs +0 -96
- package/scripts/provider-cache-smoke.mjs +0 -687
- package/scripts/report-runtime-health.mjs +0 -132
- package/scripts/resolve-bun.mjs +0 -60
- package/scripts/run-mcp.mjs +0 -1473
- package/scripts/salvage-v4a-shatter.test.mjs +0 -58
- package/scripts/scoped-cache-io-smoke.mjs +0 -103
- package/scripts/shell-policy-round3-smoke.mjs +0 -46
- package/scripts/smoke-runtime-negative.ps1 +0 -100
- package/scripts/smoke-runtime-negative.sh +0 -95
- package/scripts/stall-policy-smoke.mjs +0 -50
- package/scripts/start-memory-worker.mjs +0 -23
- package/scripts/statusline-launcher-smoke.mjs +0 -235
- package/scripts/stress-atomic-write.mjs +0 -1028
- package/scripts/test-fault-inject.mjs +0 -164
- package/scripts/test-large-file.mjs +0 -174
- package/scripts/tool-edge-smoke.mjs +0 -209
- package/scripts/uninstall.mjs +0 -238
- package/scripts/webhook-selfheal-smoke.mjs +0 -27
- package/scripts/write-overwrite-guard-smoke.mjs +0 -56
- package/server-main.mjs +0 -3350
- package/server.mjs +0 -468
- package/setup/config-merge.mjs +0 -246
- package/setup/install.mjs +0 -574
- package/setup/launch-core.mjs +0 -617
- package/setup/launch.mjs +0 -101
- package/setup/locate-claude.mjs +0 -56
- package/setup/mixdog-cli.mjs +0 -122
- package/setup/setup-server.mjs +0 -3305
- package/setup/setup.html +0 -3740
- package/setup/tui.mjs +0 -325
- package/skills/retro-skill-proposer/SKILL.md +0 -92
- package/skills/schedule-add/SKILL.md +0 -77
- package/skills/setup/SKILL.md +0 -356
- package/skills/webhook-add/SKILL.md +0 -81
- package/src/agent/bridge-stall-watchdog.mjs +0 -337
- package/src/agent/index.mjs +0 -2229
- package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
- package/src/agent/orchestrator/bridge-retry.mjs +0 -220
- package/src/agent/orchestrator/bridge-trace.mjs +0 -601
- package/src/agent/orchestrator/cache-mtime.mjs +0 -58
- package/src/agent/orchestrator/config.mjs +0 -405
- package/src/agent/orchestrator/context/collect.mjs +0 -651
- package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
- package/src/agent/orchestrator/drain-registry.mjs +0 -50
- package/src/agent/orchestrator/explore-validator.mjs +0 -8
- package/src/agent/orchestrator/internal-roles.mjs +0 -118
- package/src/agent/orchestrator/internal-tools.mjs +0 -88
- package/src/agent/orchestrator/jobs.mjs +0 -116
- package/src/agent/orchestrator/mcp/client.mjs +0 -364
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1884
- package/src/agent/orchestrator/providers/anthropic.mjs +0 -598
- package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
- package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
- package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
- package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
- package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
- package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
- package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
- package/src/agent/orchestrator/providers/registry.mjs +0 -192
- package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
- package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +0 -42
- package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
- package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
- package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
- package/src/agent/orchestrator/session/loop.mjs +0 -1619
- package/src/agent/orchestrator/session/manager.mjs +0 -1991
- package/src/agent/orchestrator/session/result-classification.mjs +0 -65
- package/src/agent/orchestrator/session/store.mjs +0 -632
- package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
- package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
- package/src/agent/orchestrator/session/trim.mjs +0 -491
- package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
- package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
- package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
- package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
- package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
- package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
- package/src/agent/orchestrator/stall-policy.mjs +0 -201
- package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
- package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
- package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +0 -171
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -511
- package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
- package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
- package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
- package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
- package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
- package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
- package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
- package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
- package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
- package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
- package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
- package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
- package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
- package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
- package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
- package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
- package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
- package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
- package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
- package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
- package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
- package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
- package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
- package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
- package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
- package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
- package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
- package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
- package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
- package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
- package/src/agent/orchestrator/tools/builtin.mjs +0 -503
- package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
- package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
- package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
- package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
- package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
- package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
- package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
- package/src/agent/orchestrator/tools/host-input.mjs +0 -204
- package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
- package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
- package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
- package/src/agent/orchestrator/tools/patch.mjs +0 -2754
- package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
- package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
- package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
- package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
- package/src/agent/orchestrator/workflow-store.mjs +0 -93
- package/src/agent/tool-defs.mjs +0 -110
- package/src/channels/backends/discord.mjs +0 -784
- package/src/channels/data/voice-runtime-manifest.json +0 -138
- package/src/channels/index.mjs +0 -3268
- package/src/channels/lib/config.mjs +0 -292
- package/src/channels/lib/drop-trace.mjs +0 -71
- package/src/channels/lib/event-pipeline.mjs +0 -81
- package/src/channels/lib/holidays.mjs +0 -138
- package/src/channels/lib/hook-pipe-server.mjs +0 -822
- package/src/channels/lib/output-forwarder.mjs +0 -765
- package/src/channels/lib/runtime-paths.mjs +0 -552
- package/src/channels/lib/scheduler.mjs +0 -723
- package/src/channels/lib/session-discovery.mjs +0 -103
- package/src/channels/lib/state-file.mjs +0 -68
- package/src/channels/lib/status-snapshot.mjs +0 -219
- package/src/channels/lib/tool-format.mjs +0 -140
- package/src/channels/lib/transcript-discovery.mjs +0 -195
- package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
- package/src/channels/lib/webhook.mjs +0 -1318
- package/src/channels/tool-defs.mjs +0 -170
- package/src/daemon/host.mjs +0 -118
- package/src/daemon/mcp-transport.mjs +0 -47
- package/src/daemon/session.mjs +0 -100
- package/src/daemon/thin-client.mjs +0 -71
- package/src/daemon/transport.mjs +0 -163
- package/src/gateway/claude-current.mjs +0 -255
- package/src/gateway/oauth-usage.mjs +0 -598
- package/src/gateway/route-meta.mjs +0 -629
- package/src/gateway/server.mjs +0 -713
- package/src/memory/data/runtime-manifest.json +0 -40
- package/src/memory/index.mjs +0 -3332
- package/src/memory/lib/core-memory-store.mjs +0 -330
- package/src/memory/lib/embedding-provider.mjs +0 -269
- package/src/memory/lib/embedding-worker.mjs +0 -323
- package/src/memory/lib/memory-cycle1.mjs +0 -645
- package/src/memory/lib/memory-cycle2.mjs +0 -1284
- package/src/memory/lib/memory-cycle3.mjs +0 -540
- package/src/memory/lib/memory-embed.mjs +0 -299
- package/src/memory/lib/memory-ops-policy.mjs +0 -190
- package/src/memory/lib/memory-recall-store.mjs +0 -638
- package/src/memory/lib/memory.mjs +0 -412
- package/src/memory/lib/pg/adapter.mjs +0 -308
- package/src/memory/lib/pg/process.mjs +0 -360
- package/src/memory/lib/pg/supervisor.mjs +0 -396
- package/src/memory/lib/runtime-fetcher.mjs +0 -458
- package/src/memory/lib/trace-store.mjs +0 -728
- package/src/memory/tool-defs.mjs +0 -79
- package/src/search/index.mjs +0 -1173
- package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
- package/src/search/lib/backends/exa.mjs +0 -50
- package/src/search/lib/backends/firecrawl.mjs +0 -61
- package/src/search/lib/backends/gemini-api.mjs +0 -83
- package/src/search/lib/backends/grok-oauth.mjs +0 -86
- package/src/search/lib/backends/index.mjs +0 -150
- package/src/search/lib/backends/openai-api.mjs +0 -144
- package/src/search/lib/backends/openai-oauth.mjs +0 -102
- package/src/search/lib/backends/openai-web-search.mjs +0 -76
- package/src/search/lib/backends/tavily.mjs +0 -55
- package/src/search/lib/backends/xai-api.mjs +0 -113
- package/src/search/lib/config.mjs +0 -192
- package/src/search/lib/provider-usage.mjs +0 -67
- package/src/search/lib/providers.mjs +0 -47
- package/src/search/lib/search-intent.mjs +0 -109
- package/src/search/lib/setup-handler.mjs +0 -261
- package/src/search/lib/web-tools.mjs +0 -1219
- package/src/search/tool-defs.mjs +0 -83
- package/src/setup/defender-exclusion.mjs +0 -183
- package/src/shared/atomic-file.mjs +0 -436
- package/src/shared/config.mjs +0 -372
- package/src/shared/daemon-recycle.mjs +0 -108
- package/src/shared/disable-claude-builtins.mjs +0 -91
- package/src/shared/err-text.mjs +0 -12
- package/src/shared/llm/http-agent.mjs +0 -123
- package/src/shared/open-url.mjs +0 -62
- package/src/shared/plugin-paths.mjs +0 -58
- package/src/shared/schedules-store.mjs +0 -70
- package/src/shared/seed.mjs +0 -161
- package/src/shared/user-cwd.mjs +0 -225
- package/src/shared/user-data-guard.mjs +0 -244
- package/src/status/aggregator.mjs +0 -584
- package/src/status/server.mjs +0 -413
- package/tools.json +0 -1671
- /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
- /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
- /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
- /package/{lib → src/lib}/config-cjs.cjs +0 -0
- /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
- /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
- /package/{lib → src/lib}/text-utils.cjs +0 -0
- /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
- /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
- /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
- /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
- /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
- /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
- /package/src/{search → runtime/search}/lib/state.mjs +0 -0
- /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
- /package/src/{shared → runtime/shared}/llm/cost.mjs +0 -0
- /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
- /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
- /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
- /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
|
@@ -0,0 +1,3813 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { performance } from 'node:perf_hooks';
|
|
6
|
+
import { ensureProjectMixdogMd, ensureStandaloneEnvironment } from './standalone/seeds.mjs';
|
|
7
|
+
import { createStandaloneBridge } from './standalone/bridge-tool.mjs';
|
|
8
|
+
import { EXPLORE_TOOL, runExplore } from './standalone/explore-tool.mjs';
|
|
9
|
+
import { createStandaloneChannelWorker } from './standalone/channel-worker.mjs';
|
|
10
|
+
import { createStandaloneHookBus } from './standalone/hook-bus.mjs';
|
|
11
|
+
import { writeLastSessionCwd } from './runtime/shared/user-cwd.mjs';
|
|
12
|
+
import { cancelBackgroundTasks } from './runtime/shared/background-tasks.mjs';
|
|
13
|
+
import { createWorkspaceRouter, formatWorkspaceSessionContext } from './runtime/shared/workspace-router.mjs';
|
|
14
|
+
import { setConfiguredShell } from './runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs';
|
|
15
|
+
import {
|
|
16
|
+
PROVIDER_STATUS_TOOL,
|
|
17
|
+
beginOAuthProviderLogin,
|
|
18
|
+
forgetProviderAuth,
|
|
19
|
+
loginOAuthProvider,
|
|
20
|
+
providerSetup,
|
|
21
|
+
renderProviderStatus,
|
|
22
|
+
saveOpenAIUsageSessionKey,
|
|
23
|
+
saveOpenCodeGoUsageAuth,
|
|
24
|
+
saveProviderApiKey,
|
|
25
|
+
setLocalProvider,
|
|
26
|
+
} from './standalone/provider-admin.mjs';
|
|
27
|
+
import { createUsageDashboard } from './standalone/usage-dashboard.mjs';
|
|
28
|
+
import {
|
|
29
|
+
channelSetup,
|
|
30
|
+
deleteChannel,
|
|
31
|
+
deleteSchedule,
|
|
32
|
+
deleteWebhook,
|
|
33
|
+
forgetDiscordToken,
|
|
34
|
+
forgetWebhookAuthtoken,
|
|
35
|
+
renderChannelStatus,
|
|
36
|
+
saveChannel,
|
|
37
|
+
saveDiscordToken,
|
|
38
|
+
saveSchedule,
|
|
39
|
+
saveWebhook,
|
|
40
|
+
saveWebhookAuthtoken,
|
|
41
|
+
setScheduleEnabled,
|
|
42
|
+
setWebhookEnabled,
|
|
43
|
+
setWebhookConfig,
|
|
44
|
+
} from './standalone/channel-admin.mjs';
|
|
45
|
+
import {
|
|
46
|
+
addPlugin as registryAddPlugin,
|
|
47
|
+
listRegisteredPlugins,
|
|
48
|
+
pluginAdminStatus,
|
|
49
|
+
removePlugin as registryRemovePlugin,
|
|
50
|
+
updatePlugin as registryUpdatePlugin,
|
|
51
|
+
} from './standalone/plugin-admin.mjs';
|
|
52
|
+
import {
|
|
53
|
+
estimateMessagesTokens,
|
|
54
|
+
estimateRequestReserveTokens,
|
|
55
|
+
estimateToolSchemaTokens,
|
|
56
|
+
} from './runtime/agent/orchestrator/session/context-utils.mjs';
|
|
57
|
+
|
|
58
|
+
function sessionMessageText(content) {
|
|
59
|
+
if (content == null) return '';
|
|
60
|
+
if (typeof content === 'string') return content;
|
|
61
|
+
const parts = Array.isArray(content)
|
|
62
|
+
? content
|
|
63
|
+
: (content && typeof content === 'object' && Array.isArray(content.content) ? content.content : null);
|
|
64
|
+
if (parts) {
|
|
65
|
+
return parts.map((part) => {
|
|
66
|
+
if (typeof part === 'string') return part;
|
|
67
|
+
return part?.text ?? '';
|
|
68
|
+
}).filter(Boolean).join('\n');
|
|
69
|
+
}
|
|
70
|
+
if (typeof content === 'object' && typeof content.text === 'string') return content.text;
|
|
71
|
+
try { return JSON.stringify(content); } catch { return String(content); }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function roughTokenCount(text) {
|
|
75
|
+
return Math.ceil(String(text ?? '').length / 4);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function messageContextText(message) {
|
|
79
|
+
if (!message || typeof message !== 'object') return '';
|
|
80
|
+
let text = sessionMessageText(message.content);
|
|
81
|
+
if (message.role === 'assistant' && Array.isArray(message.toolCalls) && message.toolCalls.length) {
|
|
82
|
+
try { text += `\n${JSON.stringify(message.toolCalls)}`; }
|
|
83
|
+
catch { text += `\n[${message.toolCalls.length} tool calls]`; }
|
|
84
|
+
}
|
|
85
|
+
if (message.role === 'tool' && message.toolCallId) text += `\n${message.toolCallId}`;
|
|
86
|
+
return text;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stripSystemReminder(text) {
|
|
90
|
+
return String(text || '')
|
|
91
|
+
.replace(/^\s*<system-reminder>\s*/i, '')
|
|
92
|
+
.replace(/\s*<\/system-reminder>\s*$/i, '')
|
|
93
|
+
.trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function splitMarkdownSections(text) {
|
|
97
|
+
const sections = [];
|
|
98
|
+
let current = [];
|
|
99
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
100
|
+
if (/^#\s+/.test(line) && current.length) {
|
|
101
|
+
const body = current.join('\n').trim();
|
|
102
|
+
if (body) sections.push(body);
|
|
103
|
+
current = [line];
|
|
104
|
+
} else {
|
|
105
|
+
current.push(line);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const tail = current.join('\n').trim();
|
|
109
|
+
if (tail) sections.push(tail);
|
|
110
|
+
return sections;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function reminderSectionBucket(section) {
|
|
114
|
+
const heading = String(section.match(/^#\s+([^\n]+)/)?.[1] || '').trim().toLowerCase();
|
|
115
|
+
if (heading.includes('core memory')) return 'memory';
|
|
116
|
+
if (heading.includes('mixdog-project-context') || heading.includes('project-context')) return 'project';
|
|
117
|
+
if (heading.includes('active workflow') || heading.includes('available agents') || heading.includes('workflow')) return 'workflow';
|
|
118
|
+
if (heading.includes('workspace') || heading === 'role' || heading.includes('role-identity') || heading.includes('task-brief')) return 'workspace';
|
|
119
|
+
if (heading.includes('environment')) return 'environment';
|
|
120
|
+
return 'other';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function summarizeContextMessages(messages) {
|
|
124
|
+
const rows = {
|
|
125
|
+
system: { count: 0, tokens: 0 },
|
|
126
|
+
user: { count: 0, tokens: 0 },
|
|
127
|
+
assistant: { count: 0, tokens: 0 },
|
|
128
|
+
tool: { count: 0, tokens: 0 },
|
|
129
|
+
other: { count: 0, tokens: 0 },
|
|
130
|
+
};
|
|
131
|
+
const semantic = {
|
|
132
|
+
system: { count: 0, tokens: 0 },
|
|
133
|
+
chat: { count: 0, tokens: 0 },
|
|
134
|
+
assistant: { count: 0, tokens: 0 },
|
|
135
|
+
toolResults: { count: 0, tokens: 0 },
|
|
136
|
+
reminders: { count: 0, tokens: 0, otherTokens: 0 },
|
|
137
|
+
project: { tokens: 0 },
|
|
138
|
+
workflow: { tokens: 0 },
|
|
139
|
+
memory: { tokens: 0 },
|
|
140
|
+
workspace: { tokens: 0 },
|
|
141
|
+
environment: { tokens: 0 },
|
|
142
|
+
other: { tokens: 0 },
|
|
143
|
+
};
|
|
144
|
+
let toolCallCount = 0;
|
|
145
|
+
let toolCallTokens = 0;
|
|
146
|
+
let toolResultCount = 0;
|
|
147
|
+
let toolResultTokens = 0;
|
|
148
|
+
for (const message of messages || []) {
|
|
149
|
+
const role = rows[message?.role] ? message.role : 'other';
|
|
150
|
+
const text = messageContextText(message);
|
|
151
|
+
const tokens = roughTokenCount(text) + 4;
|
|
152
|
+
rows[role].count += 1;
|
|
153
|
+
rows[role].tokens += tokens;
|
|
154
|
+
if (role === 'system') {
|
|
155
|
+
semantic.system.count += 1;
|
|
156
|
+
semantic.system.tokens += tokens;
|
|
157
|
+
} else if (role === 'user') {
|
|
158
|
+
if (String(text || '').trim().startsWith('<system-reminder>')) {
|
|
159
|
+
semantic.reminders.count += 1;
|
|
160
|
+
semantic.reminders.tokens += tokens;
|
|
161
|
+
let sectionTokens = 0;
|
|
162
|
+
for (const section of splitMarkdownSections(stripSystemReminder(text))) {
|
|
163
|
+
const bucket = reminderSectionBucket(section);
|
|
164
|
+
const sectionTokenCount = roughTokenCount(section);
|
|
165
|
+
semantic[bucket].tokens += sectionTokenCount;
|
|
166
|
+
sectionTokens += sectionTokenCount;
|
|
167
|
+
}
|
|
168
|
+
semantic.reminders.otherTokens += Math.max(0, tokens - sectionTokens);
|
|
169
|
+
} else {
|
|
170
|
+
semantic.chat.count += 1;
|
|
171
|
+
semantic.chat.tokens += tokens;
|
|
172
|
+
}
|
|
173
|
+
} else if (role === 'assistant') {
|
|
174
|
+
semantic.assistant.count += 1;
|
|
175
|
+
semantic.assistant.tokens += tokens;
|
|
176
|
+
} else if (role === 'tool') {
|
|
177
|
+
semantic.toolResults.count += 1;
|
|
178
|
+
semantic.toolResults.tokens += tokens;
|
|
179
|
+
}
|
|
180
|
+
if (message?.role === 'assistant' && Array.isArray(message.toolCalls) && message.toolCalls.length) {
|
|
181
|
+
toolCallCount += message.toolCalls.length;
|
|
182
|
+
try { toolCallTokens += roughTokenCount(JSON.stringify(message.toolCalls)); }
|
|
183
|
+
catch { toolCallTokens += roughTokenCount(`[${message.toolCalls.length} tool calls]`); }
|
|
184
|
+
}
|
|
185
|
+
if (message?.role === 'tool') {
|
|
186
|
+
toolResultCount += 1;
|
|
187
|
+
toolResultTokens += tokens;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
count: Array.isArray(messages) ? messages.length : 0,
|
|
192
|
+
estimatedTokens: Array.isArray(messages) ? estimateMessagesTokens(messages) : 0,
|
|
193
|
+
roles: rows,
|
|
194
|
+
semantic,
|
|
195
|
+
toolCallCount,
|
|
196
|
+
toolCallTokens,
|
|
197
|
+
toolResultCount,
|
|
198
|
+
toolResultTokens,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function isSessionPreviewNoise(text) {
|
|
203
|
+
const value = String(text || '').trim();
|
|
204
|
+
return !value
|
|
205
|
+
|| value.startsWith('<system-reminder>')
|
|
206
|
+
|| value.startsWith('</system-reminder>')
|
|
207
|
+
|| /^#\s*permission\b/i.test(value)
|
|
208
|
+
|| /^permission:\s*/i.test(value)
|
|
209
|
+
|| /^cwd:\s*/i.test(value);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function cleanSessionPreview(text) {
|
|
213
|
+
return String(text || '')
|
|
214
|
+
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, ' ')
|
|
215
|
+
.replace(/\s+/g, ' ')
|
|
216
|
+
.trim()
|
|
217
|
+
.slice(0, 160);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const RUNTIME = './runtime/agent/orchestrator';
|
|
221
|
+
const SEARCH_RUNTIME = './runtime/search/index.mjs';
|
|
222
|
+
const SEARCH_TOOL_DEFS = './runtime/search/tool-defs.mjs';
|
|
223
|
+
const MEMORY_TOOL_DEFS = './runtime/memory/tool-defs.mjs';
|
|
224
|
+
const MEMORY_RUNTIME = './runtime/memory/index.mjs';
|
|
225
|
+
const CHANNEL_TOOL_DEFS = './runtime/channels/tool-defs.mjs';
|
|
226
|
+
const CHANNEL_WORKER_ENTRY = './runtime/channels/index.mjs';
|
|
227
|
+
const CODE_GRAPH_TOOL_DEFS = './runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs';
|
|
228
|
+
const CODE_GRAPH_RUNTIME = './runtime/agent/orchestrator/tools/code-graph.mjs';
|
|
229
|
+
const STATUSLINE_SESSION_ROUTES = './vendor/statusline/src/gateway/session-routes.mjs';
|
|
230
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
231
|
+
const STANDALONE_SOURCE_ROOT = __dirname;
|
|
232
|
+
// Resource root stays at src/ because defaults/, rules/, runtime/, vendor/ live
|
|
233
|
+
// there. User-owned standalone state lives under MIXDOG_HOME (~/.mixdog).
|
|
234
|
+
const STANDALONE_ROOT = STANDALONE_SOURCE_ROOT;
|
|
235
|
+
const MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
|
|
236
|
+
const STANDALONE_DATA_DIR = process.env.MIXDOG_DATA_DIR || join(MIXDOG_HOME, 'data');
|
|
237
|
+
|
|
238
|
+
const DEFAULT_PROVIDER = 'anthropic-oauth';
|
|
239
|
+
const DEFAULT_MODEL = '';
|
|
240
|
+
const TOOL_MODES = new Set(['full', 'readonly', 'lead']);
|
|
241
|
+
const ALL_EFFORT_LEVELS = new Set(['none', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
242
|
+
const EFFORT_LABELS = {
|
|
243
|
+
none: 'None',
|
|
244
|
+
low: 'Low',
|
|
245
|
+
medium: 'Medium',
|
|
246
|
+
high: 'High',
|
|
247
|
+
xhigh: 'Extra High',
|
|
248
|
+
max: 'Max',
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
function envFlag(name) {
|
|
252
|
+
return /^(1|true|yes|on)$/i.test(String(process.env[name] || ''));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function envDelayMs(name, fallback, { min = 0, max = 60_000 } = {}) {
|
|
256
|
+
const raw = process.env[name];
|
|
257
|
+
if (raw === undefined || raw === '') return fallback;
|
|
258
|
+
const n = Number(raw);
|
|
259
|
+
if (!Number.isFinite(n)) return fallback;
|
|
260
|
+
return Math.min(max, Math.max(min, Math.floor(n)));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const BOOT_PROFILE_ENABLED = envFlag('MIXDOG_BOOT_PROFILE');
|
|
264
|
+
const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
|
|
265
|
+
|
|
266
|
+
function bootProfile(event, fields = {}) {
|
|
267
|
+
if (!BOOT_PROFILE_ENABLED) return;
|
|
268
|
+
const elapsedMs = performance.now() - BOOT_PROFILE_START;
|
|
269
|
+
const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, event];
|
|
270
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
271
|
+
if (value === undefined || value === null || value === '') continue;
|
|
272
|
+
parts.push(`${key}=${String(value).replace(/\s+/g, '_')}`);
|
|
273
|
+
}
|
|
274
|
+
try { process.stderr.write(`${parts.join(' ')}\n`); } catch {}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function profiledImport(label, spec, { optional = false } = {}) {
|
|
278
|
+
const startedAt = performance.now();
|
|
279
|
+
try {
|
|
280
|
+
const mod = await import(spec);
|
|
281
|
+
bootProfile(`import:${label}`, { ms: (performance.now() - startedAt).toFixed(1) });
|
|
282
|
+
return mod;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
bootProfile(`import:${label}:failed`, {
|
|
285
|
+
ms: (performance.now() - startedAt).toFixed(1),
|
|
286
|
+
error: error?.message || String(error),
|
|
287
|
+
});
|
|
288
|
+
if (optional) return null;
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const EFFORT_OPTIONS_BY_PROVIDER = {
|
|
293
|
+
openai: ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
294
|
+
'openai-oauth': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
295
|
+
anthropic: ['low', 'medium', 'high', 'max'],
|
|
296
|
+
'anthropic-oauth': ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
297
|
+
xai: ['none', 'low', 'medium', 'high'],
|
|
298
|
+
'grok-oauth': ['none', 'low', 'medium', 'high'],
|
|
299
|
+
'opencode-go': ['high', 'max'],
|
|
300
|
+
};
|
|
301
|
+
const EFFORT_BY_FAMILY = {
|
|
302
|
+
opus: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
303
|
+
sonnet: ['low', 'medium', 'high'],
|
|
304
|
+
haiku: [],
|
|
305
|
+
'gpt-5.5': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
306
|
+
'gpt-5.4': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
307
|
+
'gpt-5.2': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
308
|
+
'gpt-5': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
309
|
+
'gpt-mini': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
310
|
+
'gpt-nano': ['none', 'low', 'medium', 'high'],
|
|
311
|
+
'gpt-codex': ['none', 'low', 'medium', 'high'],
|
|
312
|
+
grok: ['none', 'low', 'medium', 'high'],
|
|
313
|
+
};
|
|
314
|
+
const EFFORT_FALLBACKS = {
|
|
315
|
+
max: ['max', 'xhigh', 'high', 'medium', 'low'],
|
|
316
|
+
xhigh: ['xhigh', 'high', 'medium', 'low'],
|
|
317
|
+
high: ['high', 'medium', 'low'],
|
|
318
|
+
medium: ['medium', 'low'],
|
|
319
|
+
low: ['low'],
|
|
320
|
+
none: ['none'],
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const TOOL_SEARCH_TOOL = {
|
|
324
|
+
name: 'tool_search',
|
|
325
|
+
title: 'Tool Search',
|
|
326
|
+
annotations: {
|
|
327
|
+
title: 'Tool Search',
|
|
328
|
+
readOnlyHint: true,
|
|
329
|
+
destructiveHint: false,
|
|
330
|
+
idempotentHint: true,
|
|
331
|
+
openWorldHint: false,
|
|
332
|
+
bridgeHidden: true,
|
|
333
|
+
},
|
|
334
|
+
description: 'Search the current standalone tool surface and select deferred tools/skills for the task. Use before unfamiliar or currently inactive tools.',
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
query: { type: 'string', description: 'Optional search text, e.g. shell, bridge, memory, skill, mcp.' },
|
|
339
|
+
select: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Tool/skill names to activate, as comma-separated text or an array.' },
|
|
340
|
+
limit: { type: 'number', description: 'Maximum matches to return.' },
|
|
341
|
+
},
|
|
342
|
+
additionalProperties: false,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const CHANNEL_STATUS_TOOL = {
|
|
347
|
+
name: 'channel_status',
|
|
348
|
+
title: 'Channel Status',
|
|
349
|
+
annotations: {
|
|
350
|
+
title: 'Channel Status',
|
|
351
|
+
readOnlyHint: true,
|
|
352
|
+
destructiveHint: false,
|
|
353
|
+
idempotentHint: true,
|
|
354
|
+
openWorldHint: false,
|
|
355
|
+
bridgeHidden: true,
|
|
356
|
+
},
|
|
357
|
+
description: 'List standalone Discord/channel/schedule/webhook configuration status. Read-only and never returns secrets.',
|
|
358
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const CWD_TOOL = {
|
|
362
|
+
name: 'cwd',
|
|
363
|
+
title: 'Current Working Directory',
|
|
364
|
+
annotations: {
|
|
365
|
+
title: 'Current Working Directory',
|
|
366
|
+
readOnlyHint: false,
|
|
367
|
+
destructiveHint: false,
|
|
368
|
+
idempotentHint: false,
|
|
369
|
+
openWorldHint: false,
|
|
370
|
+
bridgeHidden: true,
|
|
371
|
+
},
|
|
372
|
+
description: 'Show or set the standalone session working directory. Default get; action=set requires path.',
|
|
373
|
+
inputSchema: {
|
|
374
|
+
type: 'object',
|
|
375
|
+
properties: {
|
|
376
|
+
action: { type: 'string', enum: ['get', 'set'], description: 'Default get, or set when path is provided.' },
|
|
377
|
+
path: { type: 'string', description: 'Directory path for action=set. Relative paths resolve from the current cwd.' },
|
|
378
|
+
},
|
|
379
|
+
additionalProperties: false,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const MEASURED_TOOL_USAGE = Object.freeze({
|
|
384
|
+
read: 710,
|
|
385
|
+
code_graph: 520,
|
|
386
|
+
grep: 500,
|
|
387
|
+
glob: 460,
|
|
388
|
+
list: 430,
|
|
389
|
+
apply_patch: 400,
|
|
390
|
+
explore: 360,
|
|
391
|
+
bridge: 330,
|
|
392
|
+
shell: 81,
|
|
393
|
+
cwd: 2,
|
|
394
|
+
diagnostics: 2,
|
|
395
|
+
recall: 2,
|
|
396
|
+
search: 2,
|
|
397
|
+
web_fetch: 2,
|
|
398
|
+
provider_status: 2,
|
|
399
|
+
channel_status: 2,
|
|
400
|
+
});
|
|
401
|
+
const MEASURED_TOOL_ORDER = Object.freeze(Object.keys(MEASURED_TOOL_USAGE));
|
|
402
|
+
const DEFERRED_ALWAYS_ACTIVE_TOOLS = new Set([
|
|
403
|
+
'tool_search',
|
|
404
|
+
'recall',
|
|
405
|
+
'search',
|
|
406
|
+
'web_fetch',
|
|
407
|
+
]);
|
|
408
|
+
const LEAD_DISALLOWED_TOOLS = Object.freeze(['diagnostics', 'open_config']);
|
|
409
|
+
const DEFERRED_DEFAULT_FULL_LIMIT = 9;
|
|
410
|
+
const DEFERRED_DEFAULT_READONLY_TOOLS = Object.freeze([
|
|
411
|
+
'read',
|
|
412
|
+
'code_graph',
|
|
413
|
+
'grep',
|
|
414
|
+
'glob',
|
|
415
|
+
'list',
|
|
416
|
+
'explore',
|
|
417
|
+
'tool_search',
|
|
418
|
+
]);
|
|
419
|
+
const DEFERRED_DEFAULT_LEAD_TOOLS = Object.freeze([
|
|
420
|
+
'read',
|
|
421
|
+
'code_graph',
|
|
422
|
+
'grep',
|
|
423
|
+
'glob',
|
|
424
|
+
'list',
|
|
425
|
+
'shell',
|
|
426
|
+
'task',
|
|
427
|
+
'explore',
|
|
428
|
+
'apply_patch',
|
|
429
|
+
'bridge',
|
|
430
|
+
'recall',
|
|
431
|
+
'search',
|
|
432
|
+
'web_fetch',
|
|
433
|
+
'cwd',
|
|
434
|
+
'tool_search',
|
|
435
|
+
]);
|
|
436
|
+
const READONLY_TOOL_NAMES = new Set([
|
|
437
|
+
'read',
|
|
438
|
+
'list',
|
|
439
|
+
'grep',
|
|
440
|
+
'glob',
|
|
441
|
+
'code_graph',
|
|
442
|
+
'search',
|
|
443
|
+
'web_fetch',
|
|
444
|
+
'recall',
|
|
445
|
+
'memory',
|
|
446
|
+
'provider_status',
|
|
447
|
+
'channel_status',
|
|
448
|
+
'schedule_status',
|
|
449
|
+
'fetch',
|
|
450
|
+
]);
|
|
451
|
+
const BRIDGE_HIDDEN_WRAPPER_TOOLS = new Set(['explore', 'search']);
|
|
452
|
+
|
|
453
|
+
function applyStandaloneToolDefaults(tool) {
|
|
454
|
+
if (!tool || !BRIDGE_HIDDEN_WRAPPER_TOOLS.has(tool.name)) return tool;
|
|
455
|
+
return {
|
|
456
|
+
...tool,
|
|
457
|
+
annotations: {
|
|
458
|
+
...(tool.annotations || {}),
|
|
459
|
+
bridgeHidden: true,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
const DEFERRED_SELECT_ALIASES = {
|
|
464
|
+
filesystem: ['read', 'list', 'grep', 'glob'],
|
|
465
|
+
search: ['search', 'web_fetch'],
|
|
466
|
+
web: ['web_fetch', 'search'],
|
|
467
|
+
memory: ['memory', 'recall'],
|
|
468
|
+
channels: ['reply', 'fetch', 'react', 'edit_message', 'download_attachment', 'schedule_status', 'trigger_schedule', 'schedule_control', 'reload_config'],
|
|
469
|
+
discord: ['reply', 'fetch', 'react', 'edit_message', 'download_attachment'],
|
|
470
|
+
providers: ['provider_status'],
|
|
471
|
+
provider: ['provider_status'],
|
|
472
|
+
status: ['provider_status', 'channel_status', 'schedule_status'],
|
|
473
|
+
schedule: ['schedule_status', 'trigger_schedule', 'schedule_control'],
|
|
474
|
+
channel: ['channel_status'],
|
|
475
|
+
explore: ['explore'],
|
|
476
|
+
discovery: ['explore'],
|
|
477
|
+
bridge: ['bridge'],
|
|
478
|
+
graph: ['code_graph'],
|
|
479
|
+
code: ['code_graph'],
|
|
480
|
+
shell: ['shell', 'task'],
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
function normalizeToolMode(mode) {
|
|
484
|
+
const value = String(mode || '').trim().toLowerCase();
|
|
485
|
+
return TOOL_MODES.has(value) ? value : 'full';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function normalizeEffortInput(value) {
|
|
489
|
+
const v = clean(value).toLowerCase();
|
|
490
|
+
if (!v || v === 'auto') return null;
|
|
491
|
+
if (!ALL_EFFORT_LEVELS.has(v)) {
|
|
492
|
+
throw new Error(`effort must be one of auto, ${[...ALL_EFFORT_LEVELS].join(', ')}`);
|
|
493
|
+
}
|
|
494
|
+
return v;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function effortOptionsFor(provider, model) {
|
|
498
|
+
const providerAllowed = EFFORT_OPTIONS_BY_PROVIDER[provider] || null;
|
|
499
|
+
const filterProvider = (values) => {
|
|
500
|
+
const unique = [...new Set((values || []).map(clean).filter(Boolean))];
|
|
501
|
+
return providerAllowed ? unique.filter((v) => providerAllowed.includes(v)) : unique;
|
|
502
|
+
};
|
|
503
|
+
const declared = Array.isArray(model?.reasoningLevels)
|
|
504
|
+
? model.reasoningLevels.map(clean).filter(Boolean)
|
|
505
|
+
: [];
|
|
506
|
+
if (Array.isArray(model?.reasoningLevels)) return filterProvider(declared);
|
|
507
|
+
const family = clean(model?.family).toLowerCase();
|
|
508
|
+
if (Object.prototype.hasOwnProperty.call(EFFORT_BY_FAMILY, family)) {
|
|
509
|
+
return filterProvider(EFFORT_BY_FAMILY[family]);
|
|
510
|
+
}
|
|
511
|
+
return providerAllowed || [];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function coerceEffortFor(provider, model, effort) {
|
|
515
|
+
if (!effort) return null;
|
|
516
|
+
const allowed = effortOptionsFor(provider, model);
|
|
517
|
+
if (!allowed || allowed.length === 0) return null;
|
|
518
|
+
if (allowed.includes(effort)) return effort;
|
|
519
|
+
for (const candidate of EFFORT_FALLBACKS[effort] || []) {
|
|
520
|
+
if (allowed.includes(candidate)) return candidate;
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function hasOwn(obj, key) {
|
|
526
|
+
return Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function modelSettingsFor(config, provider, model) {
|
|
530
|
+
const key = routeFastKey(provider, model);
|
|
531
|
+
const value = key ? config?.modelSettings?.[key] : null;
|
|
532
|
+
return value && typeof value === 'object' ? value : {};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function normalizeSavedEffort(value) {
|
|
536
|
+
try {
|
|
537
|
+
return normalizeEffortInput(value);
|
|
538
|
+
} catch {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function effortItemsFor(provider, model, activeEffort) {
|
|
544
|
+
const allowed = effortOptionsFor(provider, model);
|
|
545
|
+
const items = [];
|
|
546
|
+
for (const value of allowed || []) {
|
|
547
|
+
items.push({
|
|
548
|
+
value,
|
|
549
|
+
label: EFFORT_LABELS[value] || value,
|
|
550
|
+
description: value === activeEffort ? 'current' : '',
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
return items;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function toolSpecForMode(mode) {
|
|
557
|
+
return mode === 'readonly' ? ['tools:readonly'] : 'full';
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function clean(value) {
|
|
561
|
+
return String(value ?? '').trim();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const OUTPUT_STYLE_ORDER = ['default', 'simple', 'extreme-simple'];
|
|
565
|
+
const OUTPUT_STYLE_ALIASES = new Map([
|
|
566
|
+
['compact', 'default'],
|
|
567
|
+
['normal', 'default'],
|
|
568
|
+
['extreme', 'extreme-simple'],
|
|
569
|
+
['extremesimple', 'extreme-simple'],
|
|
570
|
+
['extreme-simple', 'extreme-simple'],
|
|
571
|
+
['extreme_simple', 'extreme-simple'],
|
|
572
|
+
]);
|
|
573
|
+
|
|
574
|
+
function normalizeOutputStyleId(value) {
|
|
575
|
+
const raw = clean(value).toLowerCase();
|
|
576
|
+
if (!raw) return '';
|
|
577
|
+
const slug = raw.replace(/[_\s]+/g, '-').replace(/^-+|-+$/g, '');
|
|
578
|
+
const compact = slug.replace(/[_.-]+/g, '');
|
|
579
|
+
if (OUTPUT_STYLE_ALIASES.has(slug)) return OUTPUT_STYLE_ALIASES.get(slug);
|
|
580
|
+
if (OUTPUT_STYLE_ALIASES.has(compact)) return OUTPUT_STYLE_ALIASES.get(compact);
|
|
581
|
+
return /^[a-z0-9.-]+$/.test(slug) ? slug : '';
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function outputStyleCompactKey(value) {
|
|
585
|
+
return normalizeOutputStyleId(value).replace(/[_.-]+/g, '');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function titleCaseOutputStyle(id) {
|
|
589
|
+
return clean(id)
|
|
590
|
+
.split(/[_.-]+/)
|
|
591
|
+
.filter(Boolean)
|
|
592
|
+
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
593
|
+
.join(' ') || 'Default';
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function parseOutputStyleFrontmatter(markdown) {
|
|
597
|
+
const match = String(markdown || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
598
|
+
const meta = {};
|
|
599
|
+
if (!match) return meta;
|
|
600
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
601
|
+
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
602
|
+
if (!kv) continue;
|
|
603
|
+
meta[kv[1]] = kv[2].replace(/^['"]|['"]$/g, '').trim();
|
|
604
|
+
}
|
|
605
|
+
return meta;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function readOutputStyleMetadata(filePath, source) {
|
|
609
|
+
let raw = '';
|
|
610
|
+
try { raw = readFileSync(filePath, 'utf8'); } catch { return null; }
|
|
611
|
+
const meta = parseOutputStyleFrontmatter(raw);
|
|
612
|
+
const fileId = normalizeOutputStyleId(basename(filePath).replace(/\.md$/i, ''));
|
|
613
|
+
const id = normalizeOutputStyleId(meta.name) || fileId;
|
|
614
|
+
if (!id) return null;
|
|
615
|
+
const aliases = clean(meta.aliases)
|
|
616
|
+
.split(',')
|
|
617
|
+
.map((value) => normalizeOutputStyleId(value))
|
|
618
|
+
.filter(Boolean);
|
|
619
|
+
const label = clean(meta.title || meta.label) || titleCaseOutputStyle(id);
|
|
620
|
+
return {
|
|
621
|
+
id,
|
|
622
|
+
label,
|
|
623
|
+
description: clean(meta.description),
|
|
624
|
+
aliases,
|
|
625
|
+
source,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function listOutputStyleCatalog(dataDir = STANDALONE_DATA_DIR) {
|
|
630
|
+
const byId = new Map();
|
|
631
|
+
const dirs = [
|
|
632
|
+
{ dir: join(STANDALONE_ROOT, 'output-styles'), source: 'builtin' },
|
|
633
|
+
{ dir: join(dataDir || STANDALONE_DATA_DIR, 'output-styles'), source: 'user' },
|
|
634
|
+
];
|
|
635
|
+
for (const { dir, source } of dirs) {
|
|
636
|
+
let entries = [];
|
|
637
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
638
|
+
for (const entry of entries) {
|
|
639
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.md')) continue;
|
|
640
|
+
const style = readOutputStyleMetadata(join(dir, entry.name), source);
|
|
641
|
+
if (style) byId.set(style.id, style);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return [...byId.values()].sort((a, b) => {
|
|
645
|
+
const ai = OUTPUT_STYLE_ORDER.indexOf(a.id);
|
|
646
|
+
const bi = OUTPUT_STYLE_ORDER.indexOf(b.id);
|
|
647
|
+
if (ai !== bi) return (ai < 0 ? 999 : ai) - (bi < 0 ? 999 : bi);
|
|
648
|
+
return a.label.localeCompare(b.label, 'en', { sensitivity: 'base' });
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function findOutputStyle(value, styles) {
|
|
653
|
+
const id = normalizeOutputStyleId(value);
|
|
654
|
+
const compact = outputStyleCompactKey(value);
|
|
655
|
+
if (!id && !compact) return null;
|
|
656
|
+
return (styles || []).find((style) => {
|
|
657
|
+
if (style.id === id || outputStyleCompactKey(style.id) === compact) return true;
|
|
658
|
+
if (outputStyleCompactKey(style.label) === compact) return true;
|
|
659
|
+
return (style.aliases || []).some((alias) => alias === id || outputStyleCompactKey(alias) === compact);
|
|
660
|
+
}) || null;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function configuredOutputStyleValue(dataDir = STANDALONE_DATA_DIR) {
|
|
664
|
+
const unified = readJsonSafe(join(dataDir || STANDALONE_DATA_DIR, 'mixdog-config.json')) || {};
|
|
665
|
+
return clean(unified.outputStyle || (unified.agent && unified.agent.outputStyle) || 'default') || 'default';
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function outputStyleStatus(dataDir = STANDALONE_DATA_DIR) {
|
|
669
|
+
const styles = listOutputStyleCatalog(dataDir);
|
|
670
|
+
const configured = configuredOutputStyleValue(dataDir);
|
|
671
|
+
const current = findOutputStyle(configured, styles)
|
|
672
|
+
|| findOutputStyle('default', styles)
|
|
673
|
+
|| styles[0]
|
|
674
|
+
|| { id: 'default', label: 'Default', description: '', aliases: [], source: 'builtin' };
|
|
675
|
+
return { configured, current, styles };
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function sessionHasConversationMessages(activeSession) {
|
|
679
|
+
const messages = Array.isArray(activeSession?.messages) ? activeSession.messages : [];
|
|
680
|
+
return messages.some((message) => {
|
|
681
|
+
const role = message?.role;
|
|
682
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'tool') return false;
|
|
683
|
+
const text = sessionMessageText(message.content).trim();
|
|
684
|
+
if (!text && role !== 'assistant') return false;
|
|
685
|
+
if (role === 'user' && isSessionPreviewNoise(text)) return false;
|
|
686
|
+
return true;
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function readJsonSafe(path) {
|
|
691
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function countSkillFiles(root) {
|
|
695
|
+
const skillsDir = join(root, 'skills');
|
|
696
|
+
if (!existsSync(skillsDir)) return 0;
|
|
697
|
+
let count = 0;
|
|
698
|
+
const walk = (dir) => {
|
|
699
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
700
|
+
const full = join(dir, entry.name);
|
|
701
|
+
if (entry.isDirectory()) walk(full);
|
|
702
|
+
else if (/^(SKILL|skill)\.md$/i.test(entry.name) || entry.name.toLowerCase().endsWith('.md')) count += 1;
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
try { walk(skillsDir); } catch { return count; }
|
|
706
|
+
return count;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function mcpScriptForPlugin(root) {
|
|
710
|
+
const candidates = [
|
|
711
|
+
'scripts/run-mcp.mjs',
|
|
712
|
+
'mcp/server.mjs',
|
|
713
|
+
'server.mjs',
|
|
714
|
+
];
|
|
715
|
+
return candidates.find((rel) => existsSync(join(root, rel))) || null;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function pluginManifest(root) {
|
|
719
|
+
return readJsonSafe(join(root, '.codex-plugin', 'plugin.json'))
|
|
720
|
+
|| readJsonSafe(join(root, 'plugin.json'))
|
|
721
|
+
|| {};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function pluginMcpServerName(plugin = {}) {
|
|
725
|
+
const base = clean(plugin.name || plugin.title || 'plugin')
|
|
726
|
+
.toLowerCase()
|
|
727
|
+
.replace(/[^a-z0-9_.-]+/g, '-')
|
|
728
|
+
.replace(/^-+|-+$/g, '');
|
|
729
|
+
return base ? `plugin-${base}` : 'plugin-mcp';
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function findPreset(config, key) {
|
|
733
|
+
const wanted = clean(key).toLowerCase();
|
|
734
|
+
if (!wanted) return null;
|
|
735
|
+
const presets = Array.isArray(config?.presets) ? config.presets : [];
|
|
736
|
+
return presets.find((p) => {
|
|
737
|
+
const id = clean(p?.id).toLowerCase();
|
|
738
|
+
const name = clean(p?.name).toLowerCase();
|
|
739
|
+
return id === wanted || name === wanted;
|
|
740
|
+
}) || null;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function resolveRoute(config, { provider, model, effort, fast } = {}) {
|
|
744
|
+
const explicitProvider = clean(provider);
|
|
745
|
+
const explicitModel = clean(model);
|
|
746
|
+
const hasExplicitEffort = effort !== undefined;
|
|
747
|
+
const explicitEffort = hasExplicitEffort ? normalizeEffortInput(effort) : undefined;
|
|
748
|
+
const hasExplicitFast = fast !== undefined;
|
|
749
|
+
const explicitFast = fast === true;
|
|
750
|
+
|
|
751
|
+
if (explicitModel && !explicitProvider) {
|
|
752
|
+
const preset = findPreset(config, explicitModel);
|
|
753
|
+
if (preset) {
|
|
754
|
+
const p = clean(preset.provider) || DEFAULT_PROVIDER;
|
|
755
|
+
const m = clean(preset.model) || DEFAULT_MODEL;
|
|
756
|
+
const saved = modelSettingsFor(config, p, m);
|
|
757
|
+
return {
|
|
758
|
+
provider: p,
|
|
759
|
+
model: m,
|
|
760
|
+
preset,
|
|
761
|
+
effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort ?? preset.effort),
|
|
762
|
+
fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : (preset.fast === true || fastPreferenceFor(config, p, m))),
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (!explicitProvider && !explicitModel) {
|
|
768
|
+
const defaultKey = config?.default;
|
|
769
|
+
const preset = findPreset(config, defaultKey);
|
|
770
|
+
if (preset) {
|
|
771
|
+
const p = clean(preset.provider) || DEFAULT_PROVIDER;
|
|
772
|
+
const m = clean(preset.model) || DEFAULT_MODEL;
|
|
773
|
+
const saved = modelSettingsFor(config, p, m);
|
|
774
|
+
return {
|
|
775
|
+
provider: p,
|
|
776
|
+
model: m,
|
|
777
|
+
preset,
|
|
778
|
+
effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort ?? preset.effort),
|
|
779
|
+
fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : (preset.fast === true || fastPreferenceFor(config, p, m))),
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const p = explicitProvider || DEFAULT_PROVIDER;
|
|
785
|
+
const m = explicitModel || DEFAULT_MODEL;
|
|
786
|
+
const saved = modelSettingsFor(config, p, m);
|
|
787
|
+
return {
|
|
788
|
+
provider: p,
|
|
789
|
+
model: m,
|
|
790
|
+
preset: null,
|
|
791
|
+
effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort),
|
|
792
|
+
fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : fastPreferenceFor(config, p, m)),
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function ensureProviderEnabled(config, provider) {
|
|
797
|
+
const providers = { ...(config?.providers || {}) };
|
|
798
|
+
providers[provider] = { ...(providers[provider] || {}), enabled: true };
|
|
799
|
+
return providers;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const AUTO_CLEAR_DEFAULT_IDLE_MS = 60 * 60 * 1000;
|
|
803
|
+
|
|
804
|
+
function normalizeSystemShellConfig(value = {}) {
|
|
805
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
806
|
+
const command = clean(raw.command ?? raw.path ?? raw.executable ?? raw.shell);
|
|
807
|
+
const envCommand = clean(process.env.MIXDOG_SHELL);
|
|
808
|
+
return {
|
|
809
|
+
command,
|
|
810
|
+
effective: command || envCommand || '',
|
|
811
|
+
source: command ? 'config' : (envCommand ? 'env' : 'auto'),
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function normalizeSystemShellCommand(value) {
|
|
816
|
+
const command = clean(value).replace(/^auto$/i, '').replace(/^['"](.+)['"]$/, '$1').trim();
|
|
817
|
+
if (!command) return '';
|
|
818
|
+
if (process.platform === 'win32') {
|
|
819
|
+
const stem = command.split(/[\\/]/).pop().toLowerCase().replace(/\.exe$/, '');
|
|
820
|
+
if (stem !== 'powershell' && stem !== 'pwsh') {
|
|
821
|
+
throw new Error('system shell command must be powershell.exe or pwsh on Windows');
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return command;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function normalizeAutoClearConfig(value = {}) {
|
|
828
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
829
|
+
const idleMs = Number(raw.idleMs ?? raw.thresholdMs ?? raw.idleMillis);
|
|
830
|
+
return {
|
|
831
|
+
enabled: raw.enabled !== false,
|
|
832
|
+
idleMs: Number.isFinite(idleMs) && idleMs > 0 ? Math.max(60_000, Math.round(idleMs)) : AUTO_CLEAR_DEFAULT_IDLE_MS,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function formatDurationMs(ms) {
|
|
837
|
+
const value = Math.max(0, Number(ms) || 0);
|
|
838
|
+
if (value % 3_600_000 === 0) return `${value / 3_600_000}h`;
|
|
839
|
+
if (value % 60_000 === 0) return `${value / 60_000}m`;
|
|
840
|
+
return `${Math.round(value / 1000)}s`;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function parseDurationMs(input) {
|
|
844
|
+
const text = clean(input).toLowerCase();
|
|
845
|
+
if (!text) return null;
|
|
846
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(text);
|
|
847
|
+
if (!match) return null;
|
|
848
|
+
const n = Number(match[1]);
|
|
849
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
850
|
+
const unit = match[2] || 'm';
|
|
851
|
+
const mult = unit === 'h' ? 3_600_000 : unit === 'm' ? 60_000 : unit === 's' ? 1000 : 1;
|
|
852
|
+
return Math.max(60_000, Math.round(n * mult));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const FAST_CAPABLE_PROVIDERS = new Set(['anthropic', 'anthropic-oauth', 'openai', 'openai-oauth']);
|
|
856
|
+
const LAZY_SECRET_PROVIDERS = new Set(['openai-oauth', 'anthropic-oauth', 'grok-oauth', 'ollama', 'lmstudio']);
|
|
857
|
+
|
|
858
|
+
function routeFastKey(provider, model) {
|
|
859
|
+
const p = clean(provider);
|
|
860
|
+
const m = clean(model);
|
|
861
|
+
return p && m ? `${p}/${m}` : '';
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function openAiModelMetaSupportsFast(model) {
|
|
865
|
+
const tiers = Array.isArray(model?.serviceTiers) ? model.serviceTiers : [];
|
|
866
|
+
const speedTiers = Array.isArray(model?.additionalSpeedTiers) ? model.additionalSpeedTiers : [];
|
|
867
|
+
if (tiers.length || speedTiers.length || model?.defaultServiceTier) {
|
|
868
|
+
return tiers.some((tier) => tier?.id === 'priority')
|
|
869
|
+
|| speedTiers.includes('priority')
|
|
870
|
+
|| model?.defaultServiceTier === 'priority';
|
|
871
|
+
}
|
|
872
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
873
|
+
if (id.includes('mini') || id.includes('nano') || id.includes('codex')) return false;
|
|
874
|
+
return /^gpt-5(\.|-|$)/.test(id);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function openAiDirectModelSupportsFast(model) {
|
|
878
|
+
const id = clean(model?.id || model);
|
|
879
|
+
return /^gpt-5\.5(?:-\d{4}|$)/.test(id)
|
|
880
|
+
|| /^gpt-5\.4(?:-\d{4}|$)/.test(id)
|
|
881
|
+
|| /^gpt-5\.4-mini(?:-\d{4}|$)/.test(id);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function anthropicModelMetaSupportsFast(model) {
|
|
885
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
886
|
+
return /^claude-(opus|sonnet)/.test(id);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function fastCapableFor(provider, model) {
|
|
890
|
+
const p = clean(provider);
|
|
891
|
+
if (!FAST_CAPABLE_PROVIDERS.has(p)) return false;
|
|
892
|
+
if (p === 'openai') return openAiDirectModelSupportsFast(model);
|
|
893
|
+
if (p === 'openai-oauth') return openAiModelMetaSupportsFast(model);
|
|
894
|
+
if (p === 'anthropic' || p === 'anthropic-oauth') return anthropicModelMetaSupportsFast(model);
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function fastPreferenceFor(config, provider, model) {
|
|
899
|
+
const key = routeFastKey(provider, model);
|
|
900
|
+
if (!key) return false;
|
|
901
|
+
const saved = config?.modelSettings?.[key];
|
|
902
|
+
if (saved && typeof saved === 'object' && hasOwn(saved, 'fast')) return saved.fast === true;
|
|
903
|
+
return config?.fastModels?.[key] === true;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function saveModelSettings(cfgMod, route, { fastCapable = true } = {}) {
|
|
907
|
+
const key = routeFastKey(route?.provider, route?.model);
|
|
908
|
+
if (!key) return cfgMod.loadConfig();
|
|
909
|
+
const nextConfig = cfgMod.loadConfig();
|
|
910
|
+
const modelSettings = { ...(nextConfig.modelSettings || {}) };
|
|
911
|
+
const nextSetting = { ...(modelSettings[key] || {}) };
|
|
912
|
+
if (hasOwn(route, 'effort') && route.effort) nextSetting.effort = route.effort;
|
|
913
|
+
else delete nextSetting.effort;
|
|
914
|
+
if (fastCapable) nextSetting.fast = route.fast === true;
|
|
915
|
+
else nextSetting.fast = false;
|
|
916
|
+
modelSettings[key] = nextSetting;
|
|
917
|
+
|
|
918
|
+
// Legacy compatibility: keep fastModels true entries for old readers, but
|
|
919
|
+
// let modelSettings.fast=false override them in new readers.
|
|
920
|
+
const fastModels = { ...(nextConfig.fastModels || {}) };
|
|
921
|
+
if (nextSetting.fast === true) fastModels[key] = true;
|
|
922
|
+
else delete fastModels[key];
|
|
923
|
+
|
|
924
|
+
const savedConfig = { ...nextConfig, modelSettings, fastModels };
|
|
925
|
+
cfgMod.saveConfig(savedConfig);
|
|
926
|
+
return savedConfig;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function routeForStatusline(route) {
|
|
930
|
+
const out = {
|
|
931
|
+
mode: 'fixed',
|
|
932
|
+
defaultProvider: route.provider,
|
|
933
|
+
defaultModel: route.model,
|
|
934
|
+
};
|
|
935
|
+
const preset = route.preset || {};
|
|
936
|
+
if (preset.id) out.presetId = preset.id;
|
|
937
|
+
if (preset.name) out.presetName = preset.name;
|
|
938
|
+
if (preset.modelDisplay) out.modelDisplay = preset.modelDisplay;
|
|
939
|
+
if (route.fast === true || route.fast === false) out.fast = route.fast;
|
|
940
|
+
else if (preset.fast === true || preset.fast === false) out.fast = preset.fast;
|
|
941
|
+
if (route.effectiveEffort) {
|
|
942
|
+
out.effort = route.effectiveEffort;
|
|
943
|
+
out.displayEffort = route.effectiveEffort;
|
|
944
|
+
} else if (hasOwn(route, 'effort')) {
|
|
945
|
+
delete out.effort;
|
|
946
|
+
delete out.displayEffort;
|
|
947
|
+
}
|
|
948
|
+
return out;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const ONBOARDING_VERSION = 1;
|
|
952
|
+
const WORKFLOW_ROUTE_SLOTS = ['lead', 'bridge', 'explorer', 'memory'];
|
|
953
|
+
const FIXED_AGENT_SLOTS = Object.freeze([
|
|
954
|
+
{ id: 'explore', label: 'Explore', description: 'Broad repository exploration', workflowSlot: 'explorer' },
|
|
955
|
+
{ id: 'web-researcher', label: 'Web Researcher', description: 'External current-info research' },
|
|
956
|
+
{ id: 'maintainer', label: 'Maintainer', description: 'Background memory and upkeep', workflowSlot: 'memory' },
|
|
957
|
+
{ id: 'worker', label: 'Worker', description: 'Scoped implementation' },
|
|
958
|
+
{ id: 'heavy-worker', label: 'Heavy Worker', description: 'Broad or multi-file implementation' },
|
|
959
|
+
{ id: 'reviewer', label: 'Reviewer', description: 'Diff review and risk checks' },
|
|
960
|
+
{ id: 'debugger', label: 'Debugger', description: 'Root-cause analysis and failure tracing' },
|
|
961
|
+
]);
|
|
962
|
+
const AGENT_ROLE_IDS = new Set(FIXED_AGENT_SLOTS.map((agent) => agent.id));
|
|
963
|
+
const agentDefinitionCache = new Map();
|
|
964
|
+
const DEFAULT_WORKFLOW_ID = 'default';
|
|
965
|
+
|
|
966
|
+
function workflowPresetId(slot) {
|
|
967
|
+
return `workflow-${slot}`;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function workflowPresetName(slot) {
|
|
971
|
+
return `WORKFLOW ${String(slot || '').toUpperCase()}`;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function agentPresetSlot(agentId) {
|
|
975
|
+
return `agent-${String(agentId || '').replace(/[^a-z0-9_.-]+/gi, '-').toLowerCase()}`;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function normalizeAgentId(value) {
|
|
979
|
+
const id = clean(value).toLowerCase().replace(/[\s_]+/g, '-');
|
|
980
|
+
if (id === 'explorer') return 'explore';
|
|
981
|
+
if (id === 'maint' || id === 'maintenance' || id === 'memory') return 'maintainer';
|
|
982
|
+
if (id === 'heavy' || id === 'heavyworker') return 'heavy-worker';
|
|
983
|
+
if (id === 'review') return 'reviewer';
|
|
984
|
+
if (id === 'debug') return 'debugger';
|
|
985
|
+
return AGENT_ROLE_IDS.has(id) ? id : '';
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function normalizeWorkflowId(value, fallback = '') {
|
|
989
|
+
const id = clean(value).toLowerCase().replace(/[\s_]+/g, '-');
|
|
990
|
+
return /^[a-z0-9][a-z0-9_.-]*$/.test(id) ? id : fallback;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function readTextSafe(path) {
|
|
994
|
+
try { return readFileSync(path, 'utf8').trim(); } catch { return ''; }
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function workflowSourceDirs(dataDir) {
|
|
998
|
+
return [
|
|
999
|
+
{ root: join(STANDALONE_ROOT, 'workflows'), source: 'built-in' },
|
|
1000
|
+
{ root: join(dataDir || STANDALONE_DATA_DIR, 'workflows'), source: 'user' },
|
|
1001
|
+
];
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function agentSourceDirs(dataDir, id) {
|
|
1005
|
+
return [
|
|
1006
|
+
join(dataDir || STANDALONE_DATA_DIR, 'agents', id),
|
|
1007
|
+
join(STANDALONE_ROOT, 'agents', id),
|
|
1008
|
+
];
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function readWorkflowPackFromDir(dir, source = 'built-in') {
|
|
1012
|
+
const manifest = readJsonSafe(join(dir, 'workflow.json'));
|
|
1013
|
+
if (!manifest || typeof manifest !== 'object') return null;
|
|
1014
|
+
const id = normalizeWorkflowId(manifest.id || manifest.name);
|
|
1015
|
+
if (!id) return null;
|
|
1016
|
+
const entry = clean(manifest.entry) || 'WORKFLOW.md';
|
|
1017
|
+
const body = readTextSafe(join(dir, entry));
|
|
1018
|
+
if (!body) return null;
|
|
1019
|
+
return {
|
|
1020
|
+
id,
|
|
1021
|
+
name: clean(manifest.name) || id,
|
|
1022
|
+
description: clean(manifest.description),
|
|
1023
|
+
entry,
|
|
1024
|
+
agents: Array.isArray(manifest.agents) ? manifest.agents.map((agent) => normalizeAgentId(agent) || normalizeWorkflowId(agent)).filter(Boolean) : [],
|
|
1025
|
+
body,
|
|
1026
|
+
source,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function listWorkflowPacks(dataDir) {
|
|
1031
|
+
const byId = new Map();
|
|
1032
|
+
for (const { root, source } of workflowSourceDirs(dataDir)) {
|
|
1033
|
+
if (!existsSync(root)) continue;
|
|
1034
|
+
let entries = [];
|
|
1035
|
+
try { entries = readdirSync(root, { withFileTypes: true }); } catch { entries = []; }
|
|
1036
|
+
for (const entry of entries) {
|
|
1037
|
+
if (!entry.isDirectory()) continue;
|
|
1038
|
+
const pack = readWorkflowPackFromDir(join(root, entry.name), source);
|
|
1039
|
+
if (pack) byId.set(pack.id, pack);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return [...byId.values()].sort((a, b) => {
|
|
1043
|
+
if (a.id === DEFAULT_WORKFLOW_ID) return -1;
|
|
1044
|
+
if (b.id === DEFAULT_WORKFLOW_ID) return 1;
|
|
1045
|
+
return a.name.localeCompare(b.name);
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function activeWorkflowId(config) {
|
|
1050
|
+
return normalizeWorkflowId(config?.workflow?.active, DEFAULT_WORKFLOW_ID);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function loadWorkflowPack(dataDir, id) {
|
|
1054
|
+
const wanted = normalizeWorkflowId(id, DEFAULT_WORKFLOW_ID);
|
|
1055
|
+
for (const { root, source } of workflowSourceDirs(dataDir).reverse()) {
|
|
1056
|
+
const pack = readWorkflowPackFromDir(join(root, wanted), source);
|
|
1057
|
+
if (pack) return pack;
|
|
1058
|
+
}
|
|
1059
|
+
return readWorkflowPackFromDir(join(STANDALONE_ROOT, 'workflows', DEFAULT_WORKFLOW_ID), 'built-in');
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function loadAgentDefinition(dataDir, id) {
|
|
1063
|
+
const agentId = normalizeAgentId(id) || normalizeWorkflowId(id);
|
|
1064
|
+
if (!agentId) return null;
|
|
1065
|
+
const cacheKey = `${dataDir || STANDALONE_DATA_DIR}\n${agentId}`;
|
|
1066
|
+
if (agentDefinitionCache.has(cacheKey)) return agentDefinitionCache.get(cacheKey);
|
|
1067
|
+
for (const dir of agentSourceDirs(dataDir, agentId)) {
|
|
1068
|
+
const manifest = readJsonSafe(join(dir, 'agent.json')) || {};
|
|
1069
|
+
const entry = clean(manifest.entry) || 'AGENT.md';
|
|
1070
|
+
const body = readTextSafe(join(dir, entry));
|
|
1071
|
+
if (!body) continue;
|
|
1072
|
+
const definition = {
|
|
1073
|
+
id: agentId,
|
|
1074
|
+
name: clean(manifest.name) || FIXED_AGENT_SLOTS.find((agent) => agent.id === agentId)?.label || agentId,
|
|
1075
|
+
description: clean(manifest.description) || FIXED_AGENT_SLOTS.find((agent) => agent.id === agentId)?.description || '',
|
|
1076
|
+
body,
|
|
1077
|
+
};
|
|
1078
|
+
agentDefinitionCache.set(cacheKey, definition);
|
|
1079
|
+
return definition;
|
|
1080
|
+
}
|
|
1081
|
+
const legacyBody = readTextSafe(join(dataDir || STANDALONE_DATA_DIR, 'roles', `${agentId}.md`))
|
|
1082
|
+
|| readTextSafe(join(STANDALONE_ROOT, 'agents', `${agentId}.md`));
|
|
1083
|
+
if (!legacyBody) {
|
|
1084
|
+
agentDefinitionCache.set(cacheKey, null);
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
const definition = {
|
|
1088
|
+
id: agentId,
|
|
1089
|
+
name: FIXED_AGENT_SLOTS.find((agent) => agent.id === agentId)?.label || agentId,
|
|
1090
|
+
description: '',
|
|
1091
|
+
body: legacyBody,
|
|
1092
|
+
};
|
|
1093
|
+
agentDefinitionCache.set(cacheKey, definition);
|
|
1094
|
+
return definition;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function workflowContextBlock(config, dataDir) {
|
|
1098
|
+
const pack = loadWorkflowPack(dataDir, activeWorkflowId(config));
|
|
1099
|
+
if (!pack) return '';
|
|
1100
|
+
const lines = [
|
|
1101
|
+
`# Active Workflow: ${pack.name}`,
|
|
1102
|
+
];
|
|
1103
|
+
if (pack.description) lines.push(pack.description);
|
|
1104
|
+
lines.push(pack.body);
|
|
1105
|
+
|
|
1106
|
+
const agentIds = pack.agents.length ? pack.agents : FIXED_AGENT_SLOTS.map((agent) => agent.id);
|
|
1107
|
+
const agentBlocks = agentIds
|
|
1108
|
+
.map((id) => loadAgentDefinition(dataDir, id))
|
|
1109
|
+
.filter(Boolean);
|
|
1110
|
+
if (agentBlocks.length) {
|
|
1111
|
+
lines.push('# Available Agents');
|
|
1112
|
+
for (const agent of agentBlocks) {
|
|
1113
|
+
lines.push(`## ${agent.name} (${agent.id})`);
|
|
1114
|
+
if (agent.description) lines.push(agent.description);
|
|
1115
|
+
lines.push(agent.body);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return lines.join('\n\n');
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function normalizeWorkflowRoute(routeLike, fallback = {}) {
|
|
1122
|
+
const provider = clean(routeLike?.provider) || clean(fallback.provider);
|
|
1123
|
+
const model = clean(routeLike?.model) || clean(fallback.model);
|
|
1124
|
+
if (!provider || !model) return null;
|
|
1125
|
+
const effort = normalizeEffortInput(routeLike?.effort ?? fallback.effort);
|
|
1126
|
+
const fast = routeLike?.fast ?? fallback.fast;
|
|
1127
|
+
return {
|
|
1128
|
+
provider,
|
|
1129
|
+
model,
|
|
1130
|
+
...(effort ? { effort } : {}),
|
|
1131
|
+
...(fast === true ? { fast: true } : {}),
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function upsertWorkflowPreset(presets, slot, routeLike) {
|
|
1136
|
+
const route = normalizeWorkflowRoute(routeLike);
|
|
1137
|
+
if (!route) return presets;
|
|
1138
|
+
const id = workflowPresetId(slot);
|
|
1139
|
+
const preset = {
|
|
1140
|
+
id,
|
|
1141
|
+
name: workflowPresetName(slot),
|
|
1142
|
+
type: 'bridge',
|
|
1143
|
+
provider: route.provider,
|
|
1144
|
+
model: route.model,
|
|
1145
|
+
...(route.effort ? { effort: route.effort } : {}),
|
|
1146
|
+
...(route.fast === true ? { fast: true } : {}),
|
|
1147
|
+
tools: 'full',
|
|
1148
|
+
};
|
|
1149
|
+
const next = (Array.isArray(presets) ? presets : []).filter((p) => clean(p?.id) !== id && clean(p?.name) !== preset.name);
|
|
1150
|
+
next.push(preset);
|
|
1151
|
+
return next;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function summarizeWorkflowRoutes(config) {
|
|
1155
|
+
const routes = config?.workflowRoutes && typeof config.workflowRoutes === 'object' ? config.workflowRoutes : {};
|
|
1156
|
+
const out = {};
|
|
1157
|
+
for (const slot of WORKFLOW_ROUTE_SLOTS) {
|
|
1158
|
+
const route = routes[slot];
|
|
1159
|
+
if (route?.provider && route?.model) out[slot] = normalizeWorkflowRoute(route);
|
|
1160
|
+
}
|
|
1161
|
+
return out;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function legacyWorkflowRolePreset(dataDir, role) {
|
|
1165
|
+
try {
|
|
1166
|
+
const file = join(dataDir, 'user-workflow.json');
|
|
1167
|
+
if (!existsSync(file)) return '';
|
|
1168
|
+
const raw = JSON.parse(readFileSync(file, 'utf8'));
|
|
1169
|
+
const found = (raw?.roles || []).find((item) => clean(item?.name) === role);
|
|
1170
|
+
return clean(found?.preset);
|
|
1171
|
+
} catch {
|
|
1172
|
+
return '';
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function routeFromPreset(config, presetName) {
|
|
1177
|
+
const preset = findPreset(config, presetName);
|
|
1178
|
+
return preset ? normalizeWorkflowRoute(preset) : null;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function agentRouteFromConfig(config, agentId, dataDir) {
|
|
1182
|
+
const id = normalizeAgentId(agentId);
|
|
1183
|
+
if (!id) return null;
|
|
1184
|
+
const explicit = normalizeWorkflowRoute(config?.agents?.[id])
|
|
1185
|
+
|| (id === 'maintainer' ? normalizeWorkflowRoute(config?.agents?.maintenance) : null);
|
|
1186
|
+
if (explicit) return explicit;
|
|
1187
|
+
|
|
1188
|
+
const agent = FIXED_AGENT_SLOTS.find((item) => item.id === id);
|
|
1189
|
+
if (agent?.workflowSlot) {
|
|
1190
|
+
const workflowRoute = normalizeWorkflowRoute(config?.workflowRoutes?.[agent.workflowSlot]);
|
|
1191
|
+
if (workflowRoute) return workflowRoute;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (id === 'explore') return routeFromPreset(config, config?.maintenance?.explore);
|
|
1195
|
+
if (id === 'maintainer') return routeFromPreset(config, config?.maintenance?.memory);
|
|
1196
|
+
|
|
1197
|
+
return routeFromPreset(config, legacyWorkflowRolePreset(dataDir, id));
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function toolResponseText(result) {
|
|
1201
|
+
if (result && typeof result === 'object' && Array.isArray(result.content)) {
|
|
1202
|
+
return result.content
|
|
1203
|
+
.map((part) => (part?.type === 'text' ? part.text || '' : JSON.stringify(part)))
|
|
1204
|
+
.join('\n');
|
|
1205
|
+
}
|
|
1206
|
+
if (typeof result === 'string') return result;
|
|
1207
|
+
return JSON.stringify(result, null, 2);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function isEmptyRecallText(value) {
|
|
1211
|
+
const text = String(value || '').trim();
|
|
1212
|
+
return !text || /^\(?no results\)?$/i.test(text) || /^\(?empty memory result\)?$/i.test(text);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function currentSessionRecallRows(session, query, { limit = 10 } = {}) {
|
|
1216
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
1217
|
+
if (!messages.length) return '(no results)';
|
|
1218
|
+
const terms = [...new Set(String(query || '').toLowerCase().match(/[\p{L}\p{N}_./:-]{2,}/gu) || [])]
|
|
1219
|
+
.filter(Boolean)
|
|
1220
|
+
.slice(0, 16);
|
|
1221
|
+
const max = Math.max(1, Math.min(100, Number(limit) || 10));
|
|
1222
|
+
const rows = [];
|
|
1223
|
+
for (let i = messages.length - 1; i >= 0 && rows.length < max; i -= 1) {
|
|
1224
|
+
const m = messages[i];
|
|
1225
|
+
if (!m || (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'tool')) continue;
|
|
1226
|
+
const text = messageContextText(m).replace(/\s+/g, ' ').trim();
|
|
1227
|
+
if (!text) continue;
|
|
1228
|
+
if (terms.length && !terms.some((term) => text.toLowerCase().includes(term))) continue;
|
|
1229
|
+
rows.push(`[session:${i + 1}] ${m.role}: ${text.slice(0, 1000)}`);
|
|
1230
|
+
}
|
|
1231
|
+
return rows.length ? rows.join('\n') : '(no results)';
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function parseToolSelection(value) {
|
|
1235
|
+
if (Array.isArray(value)) return value.map(clean).filter(Boolean);
|
|
1236
|
+
return String(value || '')
|
|
1237
|
+
.split(/[,\s]+/)
|
|
1238
|
+
.map(clean)
|
|
1239
|
+
.filter(Boolean);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function toolKind(tool) {
|
|
1243
|
+
const name = clean(tool?.name);
|
|
1244
|
+
if (name.startsWith('mcp__')) return 'mcp';
|
|
1245
|
+
if (name.startsWith('skill_') || name === 'skills_list') return 'skill';
|
|
1246
|
+
if (tool?.annotations?.bridgeHidden) return 'control';
|
|
1247
|
+
if (['apply_patch', 'shell'].includes(name)) return 'mutation';
|
|
1248
|
+
return 'tool';
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function toolSchemaBucket(tool) {
|
|
1252
|
+
const name = clean(tool?.name);
|
|
1253
|
+
const kind = toolKind(tool);
|
|
1254
|
+
if (kind === 'mcp') return 'mcp';
|
|
1255
|
+
if (kind === 'skill') return 'skills';
|
|
1256
|
+
if (name === 'memory' || name === 'recall' || name.includes('memory')) return 'memory';
|
|
1257
|
+
if (name === 'search' || name === 'web_fetch') return 'web';
|
|
1258
|
+
if (['read', 'grep', 'glob', 'list', 'code_graph', 'explore'].includes(name)) return 'code';
|
|
1259
|
+
if (['shell', 'apply_patch'].includes(name)) return 'mutation';
|
|
1260
|
+
if (name === 'bridge' || name === 'delegate' || name.includes('bridge')) return 'agents';
|
|
1261
|
+
if (name.includes('channel') || name.includes('discord') || name.includes('webhook')) return 'channels';
|
|
1262
|
+
if (name.includes('provider') || name === 'tool_search' || name === 'cwd') return 'setup';
|
|
1263
|
+
return 'other';
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function estimateToolSchemaBreakdown(tools) {
|
|
1267
|
+
const out = {};
|
|
1268
|
+
for (const tool of Array.isArray(tools) ? tools : []) {
|
|
1269
|
+
const bucket = toolSchemaBucket(tool);
|
|
1270
|
+
const row = out[bucket] || { count: 0, tokens: 0 };
|
|
1271
|
+
row.count += 1;
|
|
1272
|
+
row.tokens += estimateToolSchemaTokens([tool]);
|
|
1273
|
+
out[bucket] = row;
|
|
1274
|
+
}
|
|
1275
|
+
return out;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
function measuredToolUsage(name) {
|
|
1279
|
+
return MEASURED_TOOL_USAGE[clean(name)] || 0;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function measuredToolRank(name) {
|
|
1283
|
+
const index = MEASURED_TOOL_ORDER.indexOf(clean(name));
|
|
1284
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function sortedCatalogByMeasuredUsage(catalog) {
|
|
1288
|
+
return (catalog || [])
|
|
1289
|
+
.map((tool, index) => ({ tool, index }))
|
|
1290
|
+
.sort((a, b) => {
|
|
1291
|
+
const au = measuredToolUsage(a.tool?.name);
|
|
1292
|
+
const bu = measuredToolUsage(b.tool?.name);
|
|
1293
|
+
if (bu !== au) return bu - au;
|
|
1294
|
+
const ar = measuredToolRank(a.tool?.name);
|
|
1295
|
+
const br = measuredToolRank(b.tool?.name);
|
|
1296
|
+
if (ar !== br) return ar - br;
|
|
1297
|
+
return a.index - b.index;
|
|
1298
|
+
})
|
|
1299
|
+
.map((entry) => entry.tool);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function activeToolForSurface(tool) {
|
|
1303
|
+
if (!tool || typeof tool !== 'object') return tool;
|
|
1304
|
+
return JSON.parse(JSON.stringify(tool));
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function filterDisallowedTools(tools, disallowed = []) {
|
|
1308
|
+
if (!Array.isArray(disallowed) || disallowed.length === 0) return tools;
|
|
1309
|
+
const deny = new Set(disallowed.map((name) => clean(name)).filter(Boolean));
|
|
1310
|
+
if (deny.size === 0) return tools;
|
|
1311
|
+
return (tools || []).filter((tool) => !deny.has(clean(tool?.name)));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function sortedNamesByMeasuredUsage(names) {
|
|
1315
|
+
return [...(names || [])].sort((a, b) => {
|
|
1316
|
+
const au = measuredToolUsage(a);
|
|
1317
|
+
const bu = measuredToolUsage(b);
|
|
1318
|
+
if (bu !== au) return bu - au;
|
|
1319
|
+
const ar = measuredToolRank(a);
|
|
1320
|
+
const br = measuredToolRank(b);
|
|
1321
|
+
if (ar !== br) return ar - br;
|
|
1322
|
+
return String(a).localeCompare(String(b));
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
export function defaultDeferredToolNames(catalog, mode) {
|
|
1327
|
+
if (mode === 'lead') {
|
|
1328
|
+
const available = new Set((catalog || []).map((tool) => clean(tool?.name)).filter(Boolean));
|
|
1329
|
+
return new Set(DEFERRED_DEFAULT_LEAD_TOOLS.filter((name) => available.has(name)));
|
|
1330
|
+
}
|
|
1331
|
+
if (mode === 'readonly') {
|
|
1332
|
+
const available = new Set((catalog || []).map((tool) => clean(tool?.name)).filter(Boolean));
|
|
1333
|
+
return new Set(DEFERRED_DEFAULT_READONLY_TOOLS.filter((name) => available.has(name)));
|
|
1334
|
+
}
|
|
1335
|
+
const names = new Set(DEFERRED_ALWAYS_ACTIVE_TOOLS);
|
|
1336
|
+
const limit = DEFERRED_DEFAULT_FULL_LIMIT;
|
|
1337
|
+
for (const tool of sortedCatalogByMeasuredUsage(catalog)) {
|
|
1338
|
+
const name = clean(tool?.name);
|
|
1339
|
+
if (!name || names.has(name)) continue;
|
|
1340
|
+
if (mode === 'readonly' && !isReadonlySelectable(tool)) continue;
|
|
1341
|
+
names.add(name);
|
|
1342
|
+
if (names.size >= DEFERRED_ALWAYS_ACTIVE_TOOLS.size + limit) break;
|
|
1343
|
+
}
|
|
1344
|
+
return names;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
export function compactToolSearchDescription(value, max = 220) {
|
|
1348
|
+
const text = clean(value).replace(/\s+/g, ' ');
|
|
1349
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function toolRow(tool, activeNames = new Set()) {
|
|
1353
|
+
const name = clean(tool?.name);
|
|
1354
|
+
return {
|
|
1355
|
+
name,
|
|
1356
|
+
kind: toolKind(tool),
|
|
1357
|
+
usage: measuredToolUsage(name),
|
|
1358
|
+
active: activeNames.has(name),
|
|
1359
|
+
description: compactToolSearchDescription(tool?.description),
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function toolSearchTokens(value) {
|
|
1364
|
+
return (clean(value).toLowerCase().match(/[a-z0-9_.-]+/g) || [])
|
|
1365
|
+
.map((token) => token.replace(/[-.]+/g, '_'))
|
|
1366
|
+
.filter(Boolean);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function toolSearchText(row) {
|
|
1370
|
+
const text = `${row.name} ${String(row.name || '').replace(/_/g, ' ')} ${row.kind} ${row.description} ${row.active ? 'active' : 'deferred'}`;
|
|
1371
|
+
return `${text} ${text.replace(/[-.]+/g, '_')}`.toLowerCase();
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function toolSearchMatches(row, query) {
|
|
1375
|
+
const raw = clean(query).toLowerCase();
|
|
1376
|
+
if (!raw) return true;
|
|
1377
|
+
const haystack = toolSearchText(row);
|
|
1378
|
+
if (haystack.includes(raw)) return true;
|
|
1379
|
+
const tokens = toolSearchTokens(raw);
|
|
1380
|
+
if (tokens.length === 0) return haystack.includes(raw);
|
|
1381
|
+
return tokens.some((token) => haystack.includes(token));
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function expandSelectionNames(names) {
|
|
1385
|
+
const out = [];
|
|
1386
|
+
for (const raw of names || []) {
|
|
1387
|
+
const key = clean(raw);
|
|
1388
|
+
if (!key) continue;
|
|
1389
|
+
const alias = DEFERRED_SELECT_ALIASES[key.toLowerCase()];
|
|
1390
|
+
if (alias) out.push(...alias);
|
|
1391
|
+
else out.push(key);
|
|
1392
|
+
}
|
|
1393
|
+
return [...new Set(out)];
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function isReadonlySelectable(tool) {
|
|
1397
|
+
const name = clean(tool?.name);
|
|
1398
|
+
if (READONLY_TOOL_NAMES.has(name)) return true;
|
|
1399
|
+
const annotations = tool?.annotations || {};
|
|
1400
|
+
if (annotations.destructiveHint === true) return false;
|
|
1401
|
+
if (annotations.readOnlyHint === true) return true;
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function applyDeferredToolSurface(session, mode, extraTools = []) {
|
|
1406
|
+
if (!session || !Array.isArray(session.tools)) return session;
|
|
1407
|
+
const byName = new Map();
|
|
1408
|
+
for (const tool of [...session.tools, ...(extraTools || [])]) {
|
|
1409
|
+
const name = clean(tool?.name);
|
|
1410
|
+
if (!name || byName.has(name)) continue;
|
|
1411
|
+
byName.set(name, activeToolForSurface(tool));
|
|
1412
|
+
}
|
|
1413
|
+
const catalog = sortedCatalogByMeasuredUsage([...byName.values()]);
|
|
1414
|
+
const defaultNames = defaultDeferredToolNames(catalog, mode);
|
|
1415
|
+
session.deferredToolCatalog = catalog;
|
|
1416
|
+
session.deferredToolUsage = MEASURED_TOOL_USAGE;
|
|
1417
|
+
session.deferredSelectedTools = sortedNamesByMeasuredUsage(defaultNames);
|
|
1418
|
+
session.tools.length = 0;
|
|
1419
|
+
for (const tool of catalog) {
|
|
1420
|
+
if (!defaultNames.has(clean(tool?.name))) continue;
|
|
1421
|
+
if (mode === 'readonly' && !isReadonlySelectable(tool)) continue;
|
|
1422
|
+
session.tools.push(tool);
|
|
1423
|
+
}
|
|
1424
|
+
return session;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
function selectDeferredTools(session, names, mode) {
|
|
1428
|
+
const catalog = Array.isArray(session?.deferredToolCatalog)
|
|
1429
|
+
? session.deferredToolCatalog
|
|
1430
|
+
: (Array.isArray(session?.tools) ? session.tools : []);
|
|
1431
|
+
const active = new Set((session?.tools || []).map((tool) => tool?.name).filter(Boolean));
|
|
1432
|
+
const byName = new Map(catalog.map((tool) => [tool?.name, tool]).filter(([name]) => name));
|
|
1433
|
+
const added = [];
|
|
1434
|
+
const already = [];
|
|
1435
|
+
const blocked = [];
|
|
1436
|
+
const missing = [];
|
|
1437
|
+
for (const name of expandSelectionNames(names)) {
|
|
1438
|
+
const tool = byName.get(name);
|
|
1439
|
+
if (!tool) {
|
|
1440
|
+
missing.push(name);
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (mode === 'readonly' && !isReadonlySelectable(tool)) {
|
|
1444
|
+
blocked.push({ name, reason: 'readonly mode' });
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (active.has(name)) {
|
|
1448
|
+
already.push(name);
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
session.tools.push(tool);
|
|
1452
|
+
active.add(name);
|
|
1453
|
+
added.push(name);
|
|
1454
|
+
}
|
|
1455
|
+
session.deferredSelectedTools = sortedNamesByMeasuredUsage(active);
|
|
1456
|
+
return { added, already, blocked, missing };
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function renderToolSearch(args = {}, session, mode = 'full') {
|
|
1460
|
+
const catalog = Array.isArray(session?.deferredToolCatalog)
|
|
1461
|
+
? session.deferredToolCatalog
|
|
1462
|
+
: (Array.isArray(session?.tools) ? session.tools : []);
|
|
1463
|
+
const activeNames = new Set((session?.tools || []).map((tool) => tool?.name).filter(Boolean));
|
|
1464
|
+
const query = clean(args.query).toLowerCase();
|
|
1465
|
+
const selectedNames = parseToolSelection(args.select);
|
|
1466
|
+
const limit = Math.max(1, Math.min(50, Number(args.limit) || 20));
|
|
1467
|
+
const selection = selectedNames.length ? selectDeferredTools(session, selectedNames, mode) : null;
|
|
1468
|
+
const nextActiveNames = new Set((session?.tools || []).map((tool) => tool?.name).filter(Boolean));
|
|
1469
|
+
const rows = catalog.map((tool) => toolRow(tool, nextActiveNames)).filter((row) => row.name);
|
|
1470
|
+
const matches = query
|
|
1471
|
+
? rows.filter((row) => toolSearchMatches(row, query))
|
|
1472
|
+
: rows;
|
|
1473
|
+
return JSON.stringify({
|
|
1474
|
+
selected: selection,
|
|
1475
|
+
totalMatches: matches.length,
|
|
1476
|
+
matches: matches.slice(0, limit),
|
|
1477
|
+
activeTools: sortedNamesByMeasuredUsage(nextActiveNames),
|
|
1478
|
+
note: 'standalone: tool_search adds deferred tools to the current session schema for the next model iteration.',
|
|
1479
|
+
}, null, 2);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function resolveCwdPath(currentCwd, value) {
|
|
1483
|
+
const raw = clean(value);
|
|
1484
|
+
if (!raw) throw new Error('cwd: path is required for action=set');
|
|
1485
|
+
const next = resolve(currentCwd || process.cwd(), raw);
|
|
1486
|
+
const stat = statSync(next);
|
|
1487
|
+
if (!stat.isDirectory()) throw new Error(`cwd: not a directory: ${next}`);
|
|
1488
|
+
return next;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
export async function createMixdogSessionRuntime({
|
|
1492
|
+
provider,
|
|
1493
|
+
model,
|
|
1494
|
+
cwd = process.cwd(),
|
|
1495
|
+
toolMode = 'full',
|
|
1496
|
+
} = {}) {
|
|
1497
|
+
bootProfile('session-runtime:start', { provider, model, toolMode, cwd });
|
|
1498
|
+
process.env.MIXDOG_QUIET_SESSION_LOG ??= '1';
|
|
1499
|
+
const standaloneStartedAt = performance.now();
|
|
1500
|
+
ensureStandaloneEnvironment({
|
|
1501
|
+
rootDir: STANDALONE_ROOT,
|
|
1502
|
+
dataDir: STANDALONE_DATA_DIR,
|
|
1503
|
+
});
|
|
1504
|
+
ensureProjectMixdogMd({ cwd });
|
|
1505
|
+
bootProfile('standalone-env:ready', { ms: (performance.now() - standaloneStartedAt).toFixed(1) });
|
|
1506
|
+
|
|
1507
|
+
const importsStartedAt = performance.now();
|
|
1508
|
+
const [
|
|
1509
|
+
cfgMod,
|
|
1510
|
+
sharedCfgMod,
|
|
1511
|
+
reg,
|
|
1512
|
+
mcpClient,
|
|
1513
|
+
mgr,
|
|
1514
|
+
contextMod,
|
|
1515
|
+
internalTools,
|
|
1516
|
+
statusRoutes,
|
|
1517
|
+
searchToolDefs,
|
|
1518
|
+
memoryToolDefs,
|
|
1519
|
+
channelToolDefs,
|
|
1520
|
+
codeGraphToolDefs,
|
|
1521
|
+
] = await Promise.all([
|
|
1522
|
+
profiledImport('config', `${RUNTIME}/config.mjs`),
|
|
1523
|
+
profiledImport('shared-config', `${RUNTIME}/../../shared/config.mjs`),
|
|
1524
|
+
profiledImport('providers-registry', `${RUNTIME}/providers/registry.mjs`),
|
|
1525
|
+
profiledImport('mcp-client', `${RUNTIME}/mcp/client.mjs`),
|
|
1526
|
+
profiledImport('session-manager', `${RUNTIME}/session/manager.mjs`),
|
|
1527
|
+
profiledImport('context-collect', `${RUNTIME}/context/collect.mjs`),
|
|
1528
|
+
profiledImport('internal-tools', `${RUNTIME}/internal-tools.mjs`),
|
|
1529
|
+
profiledImport('status-routes', STATUSLINE_SESSION_ROUTES, { optional: true }),
|
|
1530
|
+
profiledImport('search-tool-defs', SEARCH_TOOL_DEFS, { optional: true }),
|
|
1531
|
+
profiledImport('memory-tool-defs', MEMORY_TOOL_DEFS, { optional: true }),
|
|
1532
|
+
profiledImport('channel-tool-defs', CHANNEL_TOOL_DEFS, { optional: true }),
|
|
1533
|
+
profiledImport('code-graph-tool-defs', CODE_GRAPH_TOOL_DEFS, { optional: true }),
|
|
1534
|
+
]);
|
|
1535
|
+
bootProfile('imports:ready', { ms: (performance.now() - importsStartedAt).toFixed(1) });
|
|
1536
|
+
let memoryModPromise = null;
|
|
1537
|
+
let memoryInitPromise = null;
|
|
1538
|
+
let searchModPromise = null;
|
|
1539
|
+
let codeGraphModPromise = null;
|
|
1540
|
+
|
|
1541
|
+
async function getMemoryModule() {
|
|
1542
|
+
const startedAt = performance.now();
|
|
1543
|
+
memoryModPromise ??= import(MEMORY_RUNTIME);
|
|
1544
|
+
const mod = await memoryModPromise;
|
|
1545
|
+
if (typeof mod?.init === 'function') {
|
|
1546
|
+
memoryInitPromise ??= mod.init();
|
|
1547
|
+
await memoryInitPromise;
|
|
1548
|
+
}
|
|
1549
|
+
bootProfile('memory-runtime:ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1550
|
+
return mod;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
async function getSearchModule() {
|
|
1554
|
+
const startedAt = performance.now();
|
|
1555
|
+
searchModPromise ??= import(SEARCH_RUNTIME);
|
|
1556
|
+
const mod = await searchModPromise;
|
|
1557
|
+
bootProfile('search-runtime:ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1558
|
+
return mod;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
async function getCodeGraphModule() {
|
|
1562
|
+
const startedAt = performance.now();
|
|
1563
|
+
codeGraphModPromise ??= import(CODE_GRAPH_RUNTIME);
|
|
1564
|
+
const mod = await codeGraphModPromise;
|
|
1565
|
+
bootProfile('code-graph-runtime:ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1566
|
+
return mod;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function persistLeadRoute(routeLike) {
|
|
1570
|
+
const leadRoute = normalizeWorkflowRoute(routeLike);
|
|
1571
|
+
if (!leadRoute) return null;
|
|
1572
|
+
|
|
1573
|
+
const nextConfig = { ...(config || {}) };
|
|
1574
|
+
nextConfig.presets = upsertWorkflowPreset(nextConfig.presets, 'lead', leadRoute);
|
|
1575
|
+
nextConfig.workflowRoutes = {
|
|
1576
|
+
...(nextConfig.workflowRoutes || {}),
|
|
1577
|
+
lead: leadRoute,
|
|
1578
|
+
};
|
|
1579
|
+
nextConfig.default = workflowPresetId('lead');
|
|
1580
|
+
|
|
1581
|
+
cfgMod.saveConfig(nextConfig);
|
|
1582
|
+
config = nextConfig;
|
|
1583
|
+
return leadRoute;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
async function closePatchRuntimeIfLoaded(options = {}) {
|
|
1587
|
+
const closer = globalThis.__mixdogCloseNativePatchServers;
|
|
1588
|
+
if (typeof closer !== 'function' || globalThis.__mixdogNativePatchRuntimeTouched !== true) return;
|
|
1589
|
+
bootProfile('patch-runtime:close:start');
|
|
1590
|
+
const startedAt = performance.now();
|
|
1591
|
+
try {
|
|
1592
|
+
await closer(options);
|
|
1593
|
+
} catch {
|
|
1594
|
+
// Best-effort shutdown only; terminal restore must continue.
|
|
1595
|
+
} finally {
|
|
1596
|
+
bootProfile('patch-runtime:close:done', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
function formatCoreMemoryLines(payload = {}) {
|
|
1601
|
+
const seen = new Set();
|
|
1602
|
+
const lines = [];
|
|
1603
|
+
for (const value of [
|
|
1604
|
+
...(Array.isArray(payload.userLines) ? payload.userLines : []),
|
|
1605
|
+
...(Array.isArray(payload.dbLines) ? payload.dbLines : []),
|
|
1606
|
+
]) {
|
|
1607
|
+
const text = clean(value).replace(/\s+/g, ' ');
|
|
1608
|
+
if (!text) continue;
|
|
1609
|
+
const key = text.toLowerCase();
|
|
1610
|
+
if (seen.has(key)) continue;
|
|
1611
|
+
seen.add(key);
|
|
1612
|
+
lines.push(`- ${text}`);
|
|
1613
|
+
if (lines.length >= 40) break;
|
|
1614
|
+
}
|
|
1615
|
+
const out = lines.join('\n');
|
|
1616
|
+
const maxChars = 6000;
|
|
1617
|
+
return out.length > maxChars ? `${out.slice(0, maxChars).replace(/\s+\S*$/, '')}\n- ...` : out;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
async function loadCoreMemoryContext() {
|
|
1621
|
+
// Boot should not pay for memory/PG startup unless explicitly requested.
|
|
1622
|
+
// Recall and memory tools still initialize the memory service on first use.
|
|
1623
|
+
if (process.env.MIXDOG_BOOT_CORE_MEMORY !== '1') {
|
|
1624
|
+
bootProfile('core-memory:skipped');
|
|
1625
|
+
return '';
|
|
1626
|
+
}
|
|
1627
|
+
const startedAt = performance.now();
|
|
1628
|
+
let timer = null;
|
|
1629
|
+
const timeout = new Promise((resolve) => {
|
|
1630
|
+
timer = setTimeout(() => resolve(''), 2000);
|
|
1631
|
+
timer.unref?.();
|
|
1632
|
+
});
|
|
1633
|
+
try {
|
|
1634
|
+
return await Promise.race([
|
|
1635
|
+
(async () => {
|
|
1636
|
+
const memoryMod = await getMemoryModule();
|
|
1637
|
+
if (typeof memoryMod?.buildSessionCoreMemoryPayload !== 'function') return '';
|
|
1638
|
+
return formatCoreMemoryLines(await memoryMod.buildSessionCoreMemoryPayload(currentCwd));
|
|
1639
|
+
})(),
|
|
1640
|
+
timeout,
|
|
1641
|
+
]);
|
|
1642
|
+
} catch {
|
|
1643
|
+
return '';
|
|
1644
|
+
} finally {
|
|
1645
|
+
if (timer) clearTimeout(timer);
|
|
1646
|
+
bootProfile('core-memory:done', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const configStartedAt = performance.now();
|
|
1651
|
+
let config = cfgMod.loadConfig({ secrets: false });
|
|
1652
|
+
setConfiguredShell(normalizeSystemShellConfig(config.shell).command);
|
|
1653
|
+
let configHasSecrets = false;
|
|
1654
|
+
let route = resolveRoute(config, { provider, model });
|
|
1655
|
+
bootProfile('config:ready', { ms: (performance.now() - configStartedAt).toFixed(1) });
|
|
1656
|
+
let mode = normalizeToolMode(toolMode);
|
|
1657
|
+
let session = null;
|
|
1658
|
+
let sessionCreatePromise = null;
|
|
1659
|
+
let currentCwd = cwd;
|
|
1660
|
+
let sessionNeedsCwdRefresh = false;
|
|
1661
|
+
const workspaceRouter = createWorkspaceRouter({ entryCwd: cwd });
|
|
1662
|
+
let closeRequested = false;
|
|
1663
|
+
let channelStartTimer = null;
|
|
1664
|
+
let providerWarmupTimer = null;
|
|
1665
|
+
let providerModelWarmupTimer = null;
|
|
1666
|
+
let activeTurnCount = 0;
|
|
1667
|
+
let firstTurnCompleted = false;
|
|
1668
|
+
const sessionPrewarmDelayMs = envDelayMs('MIXDOG_SESSION_PREWARM_DELAY_MS', 50, { min: 0, max: 10_000 });
|
|
1669
|
+
const providerWarmupDelayMs = envDelayMs('MIXDOG_PROVIDER_WARMUP_DELAY_MS', 1_500, { min: 0, max: 60_000 });
|
|
1670
|
+
const providerModelWarmupDelayMs = envDelayMs('MIXDOG_PROVIDER_MODEL_WARMUP_DELAY_MS', 15_000, { min: 0, max: 120_000 });
|
|
1671
|
+
const channelStartDelayMs = envDelayMs('MIXDOG_CHANNEL_START_DELAY_MS', 10_000, { min: 0, max: 120_000 });
|
|
1672
|
+
const backgroundBusyRetryMs = envDelayMs('MIXDOG_BACKGROUND_BUSY_RETRY_MS', 1_000, { min: 50, max: 10_000 });
|
|
1673
|
+
const modelMetaByRoute = new Map();
|
|
1674
|
+
const notificationListeners = new Set();
|
|
1675
|
+
let providerModelsCache = { models: null, at: 0 };
|
|
1676
|
+
let providerModelsPromise = null;
|
|
1677
|
+
let providerSetupCache = { setup: null, at: 0 };
|
|
1678
|
+
let providerSetupPromise = null;
|
|
1679
|
+
let providerInitPromise = null;
|
|
1680
|
+
const PROVIDER_SETUP_CACHE_TTL_MS = 10_000;
|
|
1681
|
+
let mcpFailures = [];
|
|
1682
|
+
let preSessionToolSurface = null;
|
|
1683
|
+
let contextStatusCacheKey = null;
|
|
1684
|
+
let contextStatusCacheValue = null;
|
|
1685
|
+
const hooksStartedAt = performance.now();
|
|
1686
|
+
const hooks = createStandaloneHookBus({ dataDir: cfgMod.getPluginData() });
|
|
1687
|
+
hooks.emit('runtime:start', { cwd: currentCwd, provider: route.provider, model: route.model, toolMode: mode });
|
|
1688
|
+
bootProfile('hooks:ready', { ms: (performance.now() - hooksStartedAt).toFixed(1) });
|
|
1689
|
+
|
|
1690
|
+
function contextContentLength(content) {
|
|
1691
|
+
if (typeof content === 'string') return content.length;
|
|
1692
|
+
if (!Array.isArray(content)) {
|
|
1693
|
+
try { return JSON.stringify(content ?? '').length; } catch { return String(content ?? '').length; }
|
|
1694
|
+
}
|
|
1695
|
+
let length = 0;
|
|
1696
|
+
for (const part of content) {
|
|
1697
|
+
if (typeof part === 'string') length += part.length;
|
|
1698
|
+
else if (typeof part?.text === 'string') length += part.text.length;
|
|
1699
|
+
else {
|
|
1700
|
+
try { length += JSON.stringify(part ?? '').length; } catch { length += String(part ?? '').length; }
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return length;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function sameContextStatusKey(a, b) {
|
|
1707
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
1708
|
+
for (let i = 0; i < a.length; i++) {
|
|
1709
|
+
if (!Object.is(a[i], b[i])) return false;
|
|
1710
|
+
}
|
|
1711
|
+
return true;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function buildContextStatusCacheKey(messages, tools, bridgeMode) {
|
|
1715
|
+
const lastMessage = messages[messages.length - 1] || null;
|
|
1716
|
+
const compaction = session?.compaction || null;
|
|
1717
|
+
return [
|
|
1718
|
+
session?.id || null,
|
|
1719
|
+
route.provider,
|
|
1720
|
+
route.model,
|
|
1721
|
+
currentCwd,
|
|
1722
|
+
mode,
|
|
1723
|
+
bridgeMode,
|
|
1724
|
+
messages,
|
|
1725
|
+
messages.length,
|
|
1726
|
+
lastMessage,
|
|
1727
|
+
lastMessage?.role || null,
|
|
1728
|
+
lastMessage?.content || null,
|
|
1729
|
+
contextContentLength(lastMessage?.content),
|
|
1730
|
+
Array.isArray(lastMessage?.toolCalls) ? lastMessage.toolCalls.length : 0,
|
|
1731
|
+
tools,
|
|
1732
|
+
tools.length,
|
|
1733
|
+
session?.contextWindow || null,
|
|
1734
|
+
session?.rawContextWindow || null,
|
|
1735
|
+
session?.effectiveContextWindowPercent || null,
|
|
1736
|
+
session?.lastContextTokens || 0,
|
|
1737
|
+
session?.lastContextTokensUpdatedAt || 0,
|
|
1738
|
+
session?.lastContextTokensStaleAfterCompact === true,
|
|
1739
|
+
session?.lastInputTokens || 0,
|
|
1740
|
+
session?.lastOutputTokens || 0,
|
|
1741
|
+
session?.lastCachedReadTokens || 0,
|
|
1742
|
+
session?.lastCacheWriteTokens || 0,
|
|
1743
|
+
session?.totalInputTokens || 0,
|
|
1744
|
+
session?.totalOutputTokens || 0,
|
|
1745
|
+
session?.totalCachedReadTokens || 0,
|
|
1746
|
+
session?.totalCacheWriteTokens || 0,
|
|
1747
|
+
session?.compactBoundaryTokens || 0,
|
|
1748
|
+
compaction,
|
|
1749
|
+
compaction?.lastChangedAt || 0,
|
|
1750
|
+
compaction?.lastCompactAt || 0,
|
|
1751
|
+
compaction?.boundaryTokens || 0,
|
|
1752
|
+
compaction?.triggerTokens || 0,
|
|
1753
|
+
];
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function mcpTransportLabel(cfg = {}) {
|
|
1757
|
+
if (cfg.autoDetect) return `autoDetect:${cfg.autoDetect}`;
|
|
1758
|
+
if (cfg.transport === 'http' || cfg.url) return 'http';
|
|
1759
|
+
if (cfg.command) return 'stdio';
|
|
1760
|
+
return 'unknown';
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
function emitRuntimeNotification(content, meta = {}) {
|
|
1764
|
+
const text = String(content || '').trim();
|
|
1765
|
+
if (!text) return;
|
|
1766
|
+
const event = { content: text, meta: meta && typeof meta === 'object' ? meta : {} };
|
|
1767
|
+
for (const listener of [...notificationListeners]) {
|
|
1768
|
+
try { listener(event); } catch {}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function notifyFnForSession(callerSessionId) {
|
|
1773
|
+
return (text, meta = {}) => {
|
|
1774
|
+
const hadRuntimeListener = notificationListeners.size > 0;
|
|
1775
|
+
emitRuntimeNotification(text, meta);
|
|
1776
|
+
// TUI sessions keep their own Claude-Code-style command queue via
|
|
1777
|
+
// onNotification. Headless/model-tool callers have no listener, so fall
|
|
1778
|
+
// back to the session manager pending-message queue.
|
|
1779
|
+
if (!hadRuntimeListener && callerSessionId && typeof mgr.enqueuePendingMessage === 'function') {
|
|
1780
|
+
try { mgr.enqueuePendingMessage(callerSessionId, String(text || '')); } catch {}
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
function mcpStatus() {
|
|
1786
|
+
const configured = config?.mcpServers && typeof config.mcpServers === 'object'
|
|
1787
|
+
? config.mcpServers
|
|
1788
|
+
: {};
|
|
1789
|
+
const connected = new Map((mcpClient.getMcpServerStatus?.() || []).map((row) => [row.name, row]));
|
|
1790
|
+
const failures = new Map((mcpFailures || []).map((row) => [row.name, row]));
|
|
1791
|
+
const servers = [];
|
|
1792
|
+
for (const [name, cfg] of Object.entries(configured)) {
|
|
1793
|
+
const live = connected.get(name);
|
|
1794
|
+
const fail = failures.get(name);
|
|
1795
|
+
servers.push({
|
|
1796
|
+
name,
|
|
1797
|
+
configured: true,
|
|
1798
|
+
enabled: cfg?.enabled !== false,
|
|
1799
|
+
connected: Boolean(live),
|
|
1800
|
+
status: cfg?.enabled === false ? 'disabled' : live ? 'connected' : fail ? 'failed' : 'disconnected',
|
|
1801
|
+
transport: mcpTransportLabel(cfg),
|
|
1802
|
+
toolCount: live?.toolCount || 0,
|
|
1803
|
+
tools: live?.tools || [],
|
|
1804
|
+
error: fail?.msg || null,
|
|
1805
|
+
});
|
|
1806
|
+
connected.delete(name);
|
|
1807
|
+
}
|
|
1808
|
+
for (const live of connected.values()) {
|
|
1809
|
+
servers.push({ ...live, configured: false, status: 'connected' });
|
|
1810
|
+
}
|
|
1811
|
+
servers.sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
1812
|
+
return {
|
|
1813
|
+
servers,
|
|
1814
|
+
configuredCount: Object.keys(configured).length,
|
|
1815
|
+
connectedCount: servers.filter((row) => row.connected).length,
|
|
1816
|
+
failedCount: servers.filter((row) => row.status === 'failed').length,
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
function skillsStatus() {
|
|
1821
|
+
const skills = typeof contextMod.collectSkillsCached === 'function'
|
|
1822
|
+
? contextMod.collectSkillsCached(currentCwd)
|
|
1823
|
+
: [];
|
|
1824
|
+
const norm = (value) => String(value || '').replace(/\\/g, '/').toLowerCase();
|
|
1825
|
+
const cwdNorm = norm(currentCwd);
|
|
1826
|
+
const sourceForSkill = (filePath) => {
|
|
1827
|
+
const path = norm(filePath);
|
|
1828
|
+
if (cwdNorm && path.startsWith(`${cwdNorm}/.mixdog/skills/`)) return 'project';
|
|
1829
|
+
return 'skill';
|
|
1830
|
+
};
|
|
1831
|
+
return {
|
|
1832
|
+
cwd: currentCwd,
|
|
1833
|
+
count: skills.length,
|
|
1834
|
+
skills: skills.map((skill) => ({
|
|
1835
|
+
name: skill.name,
|
|
1836
|
+
description: skill.description || '',
|
|
1837
|
+
filePath: skill.filePath || null,
|
|
1838
|
+
source: sourceForSkill(skill.filePath),
|
|
1839
|
+
})),
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
function applyResolvedCwd(nextCwd, { markRefresh = true } = {}) {
|
|
1844
|
+
const resolved = resolve(nextCwd);
|
|
1845
|
+
const stat = statSync(resolved);
|
|
1846
|
+
if (!stat.isDirectory()) throw new Error(`cwd: not a directory: ${resolved}`);
|
|
1847
|
+
const changed = resolve(currentCwd) !== resolved;
|
|
1848
|
+
currentCwd = resolved;
|
|
1849
|
+
ensureProjectMixdogMd({ cwd: currentCwd });
|
|
1850
|
+
process.env.MIXDOG_SESSION_CWD = currentCwd;
|
|
1851
|
+
writeLastSessionCwd(currentCwd);
|
|
1852
|
+
if (session) session.cwd = currentCwd;
|
|
1853
|
+
if (changed && markRefresh && session?.id) sessionNeedsCwdRefresh = true;
|
|
1854
|
+
return currentCwd;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
async function refreshSessionForCwdIfNeeded(reason = 'cwd-change') {
|
|
1858
|
+
if (!session?.id || !sessionNeedsCwdRefresh) return session;
|
|
1859
|
+
const previousId = session.id;
|
|
1860
|
+
statusRoutes?.clearGatewaySessionRoute?.(previousId);
|
|
1861
|
+
mgr.closeSession(previousId, reason);
|
|
1862
|
+
session = null;
|
|
1863
|
+
sessionNeedsCwdRefresh = false;
|
|
1864
|
+
return await createCurrentSession();
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function buildWorkspaceContext() {
|
|
1868
|
+
try {
|
|
1869
|
+
return formatWorkspaceSessionContext(workspaceRouter.snapshot(currentCwd));
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
return [
|
|
1872
|
+
'# Workspace',
|
|
1873
|
+
`current cwd: ${currentCwd}`,
|
|
1874
|
+
`project candidates: unavailable (${error?.message || String(error)})`,
|
|
1875
|
+
].join('\n');
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function skillContent(name) {
|
|
1880
|
+
const content = typeof contextMod.loadSkillContent === 'function'
|
|
1881
|
+
? contextMod.loadSkillContent(name, currentCwd)
|
|
1882
|
+
: null;
|
|
1883
|
+
if (!content) throw new Error(`skill not found: ${name}`);
|
|
1884
|
+
return { name, content };
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
function addProjectSkill(input = {}) {
|
|
1888
|
+
const name = clean(input.name).replace(/[^a-zA-Z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
1889
|
+
if (!name) throw new Error('skill name is required');
|
|
1890
|
+
const dir = join(currentCwd, '.mixdog', 'skills', name);
|
|
1891
|
+
const filePath = join(dir, 'SKILL.md');
|
|
1892
|
+
if (existsSync(filePath)) throw new Error(`skill already exists: ${name}`);
|
|
1893
|
+
const description = clean(input.description) || 'Project skill.';
|
|
1894
|
+
mkdirSync(dir, { recursive: true });
|
|
1895
|
+
writeFileSync(filePath, [
|
|
1896
|
+
'---',
|
|
1897
|
+
`name: ${name}`,
|
|
1898
|
+
`description: ${description}`,
|
|
1899
|
+
'---',
|
|
1900
|
+
'',
|
|
1901
|
+
'# Instructions',
|
|
1902
|
+
'',
|
|
1903
|
+
'Describe when and how to use this skill.',
|
|
1904
|
+
'',
|
|
1905
|
+
].join('\n'), 'utf8');
|
|
1906
|
+
return { name, filePath };
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
function pluginsStatus() {
|
|
1910
|
+
const dataDir = cfgMod.getPluginData?.();
|
|
1911
|
+
const configuredMcp = config?.mcpServers && typeof config.mcpServers === 'object'
|
|
1912
|
+
? config.mcpServers
|
|
1913
|
+
: {};
|
|
1914
|
+
const plugins = [];
|
|
1915
|
+
const addRegisteredPlugin = (entry) => {
|
|
1916
|
+
const root = clean(entry.root);
|
|
1917
|
+
if (!root || !existsSync(root)) return;
|
|
1918
|
+
const manifest = pluginManifest(root);
|
|
1919
|
+
const name = clean(manifest.name) || clean(manifest.id) || clean(entry.name) || root.split(/[\\/]/).pop() || root;
|
|
1920
|
+
const plugin = {
|
|
1921
|
+
id: clean(entry.id) || name,
|
|
1922
|
+
name,
|
|
1923
|
+
title: clean(manifest.title) || clean(manifest.displayName) || clean(entry.title) || name,
|
|
1924
|
+
version: clean(manifest.version) || clean(entry.version) || null,
|
|
1925
|
+
description: clean(manifest.description) || clean(entry.description),
|
|
1926
|
+
marketplace: null,
|
|
1927
|
+
source: clean(entry.sourceType) === 'local' ? 'local' : 'registry',
|
|
1928
|
+
sourceUrl: clean(entry.source),
|
|
1929
|
+
sourceType: clean(entry.sourceType) || 'git',
|
|
1930
|
+
managed: entry.managed !== false,
|
|
1931
|
+
root,
|
|
1932
|
+
installedAt: entry.installedAt || null,
|
|
1933
|
+
updatedAt: entry.updatedAt || null,
|
|
1934
|
+
skillCount: countSkillFiles(root),
|
|
1935
|
+
mcpScript: mcpScriptForPlugin(root),
|
|
1936
|
+
};
|
|
1937
|
+
plugin.mcpServerName = pluginMcpServerName(plugin);
|
|
1938
|
+
plugin.mcpEnabled = Object.prototype.hasOwnProperty.call(configuredMcp, plugin.mcpServerName);
|
|
1939
|
+
plugins.push(plugin);
|
|
1940
|
+
};
|
|
1941
|
+
|
|
1942
|
+
for (const entry of listRegisteredPlugins({ dataDir })) addRegisteredPlugin(entry);
|
|
1943
|
+
|
|
1944
|
+
plugins.sort((a, b) => {
|
|
1945
|
+
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
1946
|
+
return a.name.localeCompare(b.name);
|
|
1947
|
+
});
|
|
1948
|
+
const admin = pluginAdminStatus({ dataDir });
|
|
1949
|
+
return {
|
|
1950
|
+
count: plugins.length,
|
|
1951
|
+
plugins,
|
|
1952
|
+
roots: {
|
|
1953
|
+
registry: admin.registryPath,
|
|
1954
|
+
installed: admin.installRoot,
|
|
1955
|
+
},
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
async function connectConfiguredMcp({ reset = false } = {}) {
|
|
1960
|
+
if (reset) await mcpClient.disconnectAll?.();
|
|
1961
|
+
mcpFailures = [];
|
|
1962
|
+
const servers = config?.mcpServers && typeof config.mcpServers === 'object'
|
|
1963
|
+
? config.mcpServers
|
|
1964
|
+
: {};
|
|
1965
|
+
if (Object.keys(servers).length === 0) return mcpStatus();
|
|
1966
|
+
try {
|
|
1967
|
+
await mcpClient.connectMcpServers(servers);
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
mcpFailures = Array.isArray(error?.failures)
|
|
1970
|
+
? error.failures
|
|
1971
|
+
: [{ name: 'mcp', msg: error?.message || String(error) }];
|
|
1972
|
+
}
|
|
1973
|
+
return mcpStatus();
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
function normalizeMcpServerInput(input = {}) {
|
|
1977
|
+
const name = clean(input.name).toLowerCase().replace(/[^a-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
1978
|
+
if (!name) throw new Error('MCP server name is required');
|
|
1979
|
+
const url = clean(input.url);
|
|
1980
|
+
if (url) {
|
|
1981
|
+
if (!/^https?:\/\//i.test(url)) throw new Error('MCP URL must start with http:// or https://');
|
|
1982
|
+
return { name, config: { transport: 'http', url } };
|
|
1983
|
+
}
|
|
1984
|
+
const command = clean(input.command);
|
|
1985
|
+
if (!command) throw new Error('MCP server command or URL is required');
|
|
1986
|
+
const args = Array.isArray(input.args)
|
|
1987
|
+
? input.args.map((v) => String(v)).filter(Boolean)
|
|
1988
|
+
: clean(input.args).split(/\s+/).filter(Boolean);
|
|
1989
|
+
const requestedCwd = clean(input.cwd);
|
|
1990
|
+
const cwdForServer = requestedCwd ? resolve(currentCwd, requestedCwd) : currentCwd;
|
|
1991
|
+
const root = resolve(currentCwd);
|
|
1992
|
+
const resolvedCwd = resolve(cwdForServer);
|
|
1993
|
+
if (resolvedCwd !== root && !resolvedCwd.startsWith(`${root}\\`) && !resolvedCwd.startsWith(`${root}/`)) {
|
|
1994
|
+
throw new Error('MCP server cwd must stay under the current project');
|
|
1995
|
+
}
|
|
1996
|
+
return { name, config: { command, args, cwd: resolvedCwd } };
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
const persistedBridgeMode = (() => {
|
|
2000
|
+
try { return (sharedCfgMod.readSection('agent') || {}).bridgeMode; } catch { return undefined; }
|
|
2001
|
+
})();
|
|
2002
|
+
const bridgeStartedAt = performance.now();
|
|
2003
|
+
const bridge = createStandaloneBridge({
|
|
2004
|
+
cfgMod,
|
|
2005
|
+
reg,
|
|
2006
|
+
mgr,
|
|
2007
|
+
dataDir: cfgMod.getPluginData(),
|
|
2008
|
+
cwd,
|
|
2009
|
+
defaultMode: persistedBridgeMode ?? 'async',
|
|
2010
|
+
});
|
|
2011
|
+
bootProfile('bridge:ready', { ms: (performance.now() - bridgeStartedAt).toFixed(1) });
|
|
2012
|
+
const bridgeStatusState = () => {
|
|
2013
|
+
try {
|
|
2014
|
+
const status = bridge.getStatus?.({ clientHostPid: session?.clientHostPid || process.pid }) || {};
|
|
2015
|
+
return {
|
|
2016
|
+
bridgeMode: bridge.getDefaultMode?.() || status.bridgeMode || 'async',
|
|
2017
|
+
bridgeWorkers: Array.isArray(status.workers) ? status.workers : [],
|
|
2018
|
+
bridgeJobs: Array.isArray(status.jobs) ? status.jobs : [],
|
|
2019
|
+
bridgeScope: status.scope || null,
|
|
2020
|
+
};
|
|
2021
|
+
} catch {
|
|
2022
|
+
return { bridgeMode: bridge.getDefaultMode?.() || 'async', bridgeWorkers: [], bridgeJobs: [], bridgeScope: null };
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
const channelsStartedAt = performance.now();
|
|
2026
|
+
const channels = createStandaloneChannelWorker({
|
|
2027
|
+
entry: join(STANDALONE_ROOT, CHANNEL_WORKER_ENTRY.replace(/^\.\//, '')),
|
|
2028
|
+
rootDir: STANDALONE_ROOT,
|
|
2029
|
+
dataDir: cfgMod.getPluginData(),
|
|
2030
|
+
cwd,
|
|
2031
|
+
});
|
|
2032
|
+
bootProfile('channels:worker-ready', { ms: (performance.now() - channelsStartedAt).toFixed(1) });
|
|
2033
|
+
const toolsStartedAt = performance.now();
|
|
2034
|
+
const standaloneTools = [
|
|
2035
|
+
TOOL_SEARCH_TOOL,
|
|
2036
|
+
CWD_TOOL,
|
|
2037
|
+
EXPLORE_TOOL,
|
|
2038
|
+
...(searchToolDefs?.TOOL_DEFS || []).filter((tool) => tool?.name === 'search' || tool?.name === 'web_fetch'),
|
|
2039
|
+
...(memoryToolDefs?.TOOL_DEFS || []).filter((tool) => tool?.name === 'recall' || tool?.name === 'memory'),
|
|
2040
|
+
...(channelToolDefs?.TOOL_DEFS || []).filter((tool) => channels.isChannelTool(tool?.name)),
|
|
2041
|
+
...(codeGraphToolDefs?.CODE_GRAPH_TOOL_DEFS || []).filter((tool) => tool?.name === 'code_graph'),
|
|
2042
|
+
...bridge.tools,
|
|
2043
|
+
PROVIDER_STATUS_TOOL,
|
|
2044
|
+
CHANNEL_STATUS_TOOL,
|
|
2045
|
+
].map(applyStandaloneToolDefaults);
|
|
2046
|
+
bootProfile('tools:ready', { ms: (performance.now() - toolsStartedAt).toFixed(1), count: standaloneTools.length });
|
|
2047
|
+
|
|
2048
|
+
function invalidatePreSessionToolSurface() {
|
|
2049
|
+
preSessionToolSurface = null;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function invalidateContextStatusCache() {
|
|
2053
|
+
contextStatusCacheKey = null;
|
|
2054
|
+
contextStatusCacheValue = null;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function buildPreSessionToolSurface() {
|
|
2058
|
+
const previewTools = typeof mgr.previewSessionTools === 'function'
|
|
2059
|
+
? mgr.previewSessionTools(toolSpecForMode(mode), [])
|
|
2060
|
+
: [];
|
|
2061
|
+
const tools = filterDisallowedTools(previewTools, LEAD_DISALLOWED_TOOLS);
|
|
2062
|
+
const surface = { tools: Array.isArray(tools) ? tools.slice() : [] };
|
|
2063
|
+
applyDeferredToolSurface(surface, mode, standaloneTools);
|
|
2064
|
+
return surface;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
function activeToolSurface() {
|
|
2068
|
+
if (session) return session;
|
|
2069
|
+
preSessionToolSurface ??= buildPreSessionToolSurface();
|
|
2070
|
+
return preSessionToolSurface;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
function applyPreSessionToolSelection() {
|
|
2074
|
+
if (!session || !preSessionToolSurface) return;
|
|
2075
|
+
const selected = Array.isArray(preSessionToolSurface.deferredSelectedTools)
|
|
2076
|
+
? preSessionToolSurface.deferredSelectedTools
|
|
2077
|
+
: [];
|
|
2078
|
+
if (selected.length) selectDeferredTools(session, selected, mode);
|
|
2079
|
+
}
|
|
2080
|
+
internalTools.setInternalToolsProvider({
|
|
2081
|
+
tools: standaloneTools,
|
|
2082
|
+
executor: async (name, args, callerCtx = {}) => {
|
|
2083
|
+
const callerCwd = callerCtx?.callerCwd || currentCwd;
|
|
2084
|
+
if (name === 'search' || name === 'web_fetch') {
|
|
2085
|
+
const callerSessionId = callerCtx?.callerSessionId || session?.id || null;
|
|
2086
|
+
const searchMod = await getSearchModule();
|
|
2087
|
+
if (!searchMod?.handleToolCall) throw new Error('search runtime is not available');
|
|
2088
|
+
return await searchMod.handleToolCall(name, args || {}, {
|
|
2089
|
+
callerCwd,
|
|
2090
|
+
callerSessionId,
|
|
2091
|
+
routingSessionId: callerSessionId,
|
|
2092
|
+
clientHostPid: callerCtx?.clientHostPid || session?.clientHostPid || process.pid,
|
|
2093
|
+
notifyFn: notifyFnForSession(callerSessionId),
|
|
2094
|
+
agentSearch: name === 'search'
|
|
2095
|
+
? async (searchArgs) => {
|
|
2096
|
+
const query = Array.isArray(searchArgs.keywords)
|
|
2097
|
+
? searchArgs.keywords.join('\n')
|
|
2098
|
+
: String(searchArgs.keywords || searchArgs.query || '');
|
|
2099
|
+
const prompt = searchArgs.prompt || [
|
|
2100
|
+
'Perform a concise web research task for Mixdog search.',
|
|
2101
|
+
'',
|
|
2102
|
+
`Query: ${query}`,
|
|
2103
|
+
searchArgs.site ? `Site/domain restriction: ${searchArgs.site}` : null,
|
|
2104
|
+
searchArgs.type ? `Search type: ${searchArgs.type}` : null,
|
|
2105
|
+
searchArgs.locale ? `Locale: ${typeof searchArgs.locale === 'string' ? searchArgs.locale : JSON.stringify(searchArgs.locale)}` : null,
|
|
2106
|
+
`Max results: ${Math.max(1, Math.min(20, Number(searchArgs.maxResults) || 10))}`,
|
|
2107
|
+
'',
|
|
2108
|
+
'Return a short answer first, then cite useful results as title + URL + one-line snippet.',
|
|
2109
|
+
'Do not edit files.',
|
|
2110
|
+
].filter(Boolean).join('\n');
|
|
2111
|
+
const rendered = await bridge.execute({
|
|
2112
|
+
type: 'spawn',
|
|
2113
|
+
agent: 'web-researcher',
|
|
2114
|
+
tag: `search_${Date.now().toString(36)}`,
|
|
2115
|
+
prompt,
|
|
2116
|
+
cwd: callerCwd,
|
|
2117
|
+
wait: true,
|
|
2118
|
+
firstResponseTimeoutMs: Number.isFinite(Number(searchArgs.firstResponseTimeoutMs)) ? Number(searchArgs.firstResponseTimeoutMs) : 120_000,
|
|
2119
|
+
idleTimeoutMs: Number.isFinite(Number(searchArgs.idleTimeoutMs)) ? Number(searchArgs.idleTimeoutMs) : 30 * 60_000,
|
|
2120
|
+
}, { invocationSource: 'user-command', callerCwd });
|
|
2121
|
+
return String(rendered || '').replace(/^bridge result[^\n]*\n/i, '').trim();
|
|
2122
|
+
}
|
|
2123
|
+
: undefined,
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
if (name === 'recall' || name === 'memory' || name === 'search_memories') {
|
|
2127
|
+
const memoryMod = await getMemoryModule();
|
|
2128
|
+
if (!memoryMod?.handleToolCall) throw new Error('memory runtime is not available');
|
|
2129
|
+
return await memoryMod.handleToolCall(name, args || {});
|
|
2130
|
+
}
|
|
2131
|
+
if (name === 'code_graph') {
|
|
2132
|
+
const codeGraphMod = await getCodeGraphModule();
|
|
2133
|
+
if (!codeGraphMod?.executeCodeGraphTool) throw new Error('code_graph runtime is not available');
|
|
2134
|
+
return await codeGraphMod.executeCodeGraphTool(name, args || {}, args?.cwd || callerCwd);
|
|
2135
|
+
}
|
|
2136
|
+
if (name === 'tool_search') return renderToolSearch(args, session, mode);
|
|
2137
|
+
if (name === 'explore') {
|
|
2138
|
+
const callerSessionId = callerCtx?.callerSessionId || session?.id || null;
|
|
2139
|
+
return await runExplore(args || {}, {
|
|
2140
|
+
callerCwd: args?.cwd ? resolveCwdPath(currentCwd, args.cwd) : callerCwd,
|
|
2141
|
+
callerSessionId,
|
|
2142
|
+
routingSessionId: callerSessionId,
|
|
2143
|
+
clientHostPid: callerCtx?.clientHostPid || session?.clientHostPid || process.pid,
|
|
2144
|
+
notifyFn: notifyFnForSession(callerSessionId),
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
if (name === 'cwd') {
|
|
2148
|
+
const action = clean(args?.action || (args?.path ? 'set' : 'get')).toLowerCase();
|
|
2149
|
+
if (action === 'set') {
|
|
2150
|
+
applyResolvedCwd(resolveCwdPath(currentCwd, args?.path));
|
|
2151
|
+
} else if (action !== 'get') {
|
|
2152
|
+
throw new Error(`cwd: unknown action "${action}"`);
|
|
2153
|
+
}
|
|
2154
|
+
return JSON.stringify({ cwd: currentCwd, sessionId: session?.id || null }, null, 2);
|
|
2155
|
+
}
|
|
2156
|
+
if (name === 'bridge') {
|
|
2157
|
+
const callerSessionId = callerCtx?.callerSessionId || session?.id || null;
|
|
2158
|
+
return await bridge.execute(args, {
|
|
2159
|
+
callerCwd,
|
|
2160
|
+
invocationSource: 'model-tool',
|
|
2161
|
+
callerSessionId,
|
|
2162
|
+
clientHostPid: callerCtx?.clientHostPid || session?.clientHostPid || process.pid,
|
|
2163
|
+
signal: callerCtx?.signal,
|
|
2164
|
+
notifyFn: notifyFnForSession(callerSessionId),
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
if (name === 'provider_status') return renderProviderStatus(cfgMod.loadConfig());
|
|
2168
|
+
if (name === 'channel_status') return renderChannelStatus();
|
|
2169
|
+
if (channels.isChannelTool(name)) return await channels.execute(name, args || {});
|
|
2170
|
+
throw new Error(`unknown standalone internal tool: ${name}`);
|
|
2171
|
+
},
|
|
2172
|
+
});
|
|
2173
|
+
internalTools.markBootReady?.();
|
|
2174
|
+
void connectConfiguredMcp()
|
|
2175
|
+
.then((status) => bootProfile('mcp:ready', {
|
|
2176
|
+
connected: Number(status?.connectedCount || 0),
|
|
2177
|
+
failed: Number(status?.failedCount || 0),
|
|
2178
|
+
}))
|
|
2179
|
+
.catch((error) => bootProfile('mcp:failed', { error: error?.message || String(error) }));
|
|
2180
|
+
|
|
2181
|
+
function reloadChannelsSoon() {
|
|
2182
|
+
channels.execute('reload_config', {}).catch(() => {});
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function invalidateProviderCaches() {
|
|
2186
|
+
providerModelsCache = { models: null, at: 0 };
|
|
2187
|
+
providerModelsPromise = null;
|
|
2188
|
+
providerSetupCache = { setup: null, at: 0 };
|
|
2189
|
+
providerSetupPromise = null;
|
|
2190
|
+
providerInitPromise = null;
|
|
2191
|
+
modelMetaByRoute.clear();
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
function ensureFullConfig() {
|
|
2195
|
+
if (configHasSecrets) return config;
|
|
2196
|
+
config = cfgMod.loadConfig();
|
|
2197
|
+
configHasSecrets = true;
|
|
2198
|
+
return config;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
function ensureConfigForRouteProvider() {
|
|
2202
|
+
const providerId = clean(route.provider);
|
|
2203
|
+
const providerCfg = config?.providers?.[providerId];
|
|
2204
|
+
if (configHasSecrets || LAZY_SECRET_PROVIDERS.has(providerId) || providerCfg?.apiKey) {
|
|
2205
|
+
return config;
|
|
2206
|
+
}
|
|
2207
|
+
return ensureFullConfig();
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
async function ensureProvidersReady(providerConfig = config.providers || {}) {
|
|
2211
|
+
if (providerInitPromise) return await providerInitPromise;
|
|
2212
|
+
providerInitPromise = reg.initProviders(providerConfig)
|
|
2213
|
+
.finally(() => {
|
|
2214
|
+
providerInitPromise = null;
|
|
2215
|
+
});
|
|
2216
|
+
return await providerInitPromise;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
async function cachedProviderSetup({ force = false } = {}) {
|
|
2220
|
+
const now = Date.now();
|
|
2221
|
+
if (!force && providerSetupCache.setup && now - providerSetupCache.at < PROVIDER_SETUP_CACHE_TTL_MS) {
|
|
2222
|
+
return providerSetupCache.setup;
|
|
2223
|
+
}
|
|
2224
|
+
if (!force && providerSetupPromise) return await providerSetupPromise;
|
|
2225
|
+
providerSetupPromise = providerSetup(cfgMod.loadConfig())
|
|
2226
|
+
.then((setup) => {
|
|
2227
|
+
providerSetupCache = { setup, at: Date.now() };
|
|
2228
|
+
return setup;
|
|
2229
|
+
})
|
|
2230
|
+
.finally(() => {
|
|
2231
|
+
providerSetupPromise = null;
|
|
2232
|
+
});
|
|
2233
|
+
return await providerSetupPromise;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
function modelMetaKey(providerId, modelId) {
|
|
2237
|
+
return `${clean(providerId)}\n${clean(modelId)}`;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
async function lookupModelMeta(providerId, modelId, { allowFetch = false } = {}) {
|
|
2241
|
+
const key = modelMetaKey(providerId, modelId);
|
|
2242
|
+
if (modelMetaByRoute.has(key)) return modelMetaByRoute.get(key);
|
|
2243
|
+
const providerImpl = reg.getProvider(providerId);
|
|
2244
|
+
if (!providerImpl || typeof providerImpl.listModels !== 'function') {
|
|
2245
|
+
const fallback = { id: modelId, provider: providerId };
|
|
2246
|
+
modelMetaByRoute.set(key, fallback);
|
|
2247
|
+
return fallback;
|
|
2248
|
+
}
|
|
2249
|
+
if (typeof providerImpl.getCachedModelInfo === 'function') {
|
|
2250
|
+
const cached = providerImpl.getCachedModelInfo(modelId);
|
|
2251
|
+
if (cached) {
|
|
2252
|
+
const meta = { ...cached, id: cached.id || modelId, provider: providerId };
|
|
2253
|
+
modelMetaByRoute.set(key, meta);
|
|
2254
|
+
return meta;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
if (!allowFetch) {
|
|
2258
|
+
const fallback = { id: modelId, provider: providerId };
|
|
2259
|
+
modelMetaByRoute.set(key, fallback);
|
|
2260
|
+
scheduleProviderModelWarmup();
|
|
2261
|
+
return fallback;
|
|
2262
|
+
}
|
|
2263
|
+
try {
|
|
2264
|
+
const models = await providerImpl.listModels();
|
|
2265
|
+
const found = Array.isArray(models) ? models.find((m) => m?.id === modelId) : null;
|
|
2266
|
+
const meta = found || { id: modelId, provider: providerId };
|
|
2267
|
+
modelMetaByRoute.set(key, meta);
|
|
2268
|
+
return meta;
|
|
2269
|
+
} catch {
|
|
2270
|
+
const fallback = { id: modelId, provider: providerId };
|
|
2271
|
+
modelMetaByRoute.set(key, fallback);
|
|
2272
|
+
return fallback;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
function parsedProviderModelVersion(id) {
|
|
2277
|
+
const text = clean(id).toLowerCase();
|
|
2278
|
+
const claude = text.match(/^claude-[a-z]+-(\d+)(?:[-.](\d+))?/);
|
|
2279
|
+
if (claude) return [Number(claude[1]) || 0, Number(claude[2]) || 0];
|
|
2280
|
+
const compact = text.match(/(?:^|[-_])(?:o|gpt|grok|qwen|llama|mistral|gemma|phi|glm)(\d+)(?:\.(\d+))?(?:\.(\d{1,3}))?/);
|
|
2281
|
+
if (compact) return compact.slice(1).filter((v) => v != null).map((v) => Number(v) || 0);
|
|
2282
|
+
const generic = text.match(/(?:^|[-_v])(\d+)(?:\.(\d+))?(?:\.(\d{1,3}))?/);
|
|
2283
|
+
return generic ? generic.slice(1).filter((v) => v != null).map((v) => Number(v) || 0) : [];
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
function compareProviderModelVersion(a, b) {
|
|
2287
|
+
const va = parsedProviderModelVersion(a.id || a.display || a.name);
|
|
2288
|
+
const vb = parsedProviderModelVersion(b.id || b.display || b.name);
|
|
2289
|
+
if (va.length === 0 && vb.length === 0) return 0;
|
|
2290
|
+
if (va.length === 0) return 1;
|
|
2291
|
+
if (vb.length === 0) return -1;
|
|
2292
|
+
for (let i = 0; i < Math.max(va.length, vb.length); i += 1) {
|
|
2293
|
+
const delta = (vb[i] || 0) - (va[i] || 0);
|
|
2294
|
+
if (delta) return delta;
|
|
2295
|
+
}
|
|
2296
|
+
return 0;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function providerModelReleaseTime(model) {
|
|
2300
|
+
if (model?.releaseDate) {
|
|
2301
|
+
const t = Date.parse(model.releaseDate);
|
|
2302
|
+
if (Number.isFinite(t)) return t;
|
|
2303
|
+
}
|
|
2304
|
+
const created = Number(model?.created);
|
|
2305
|
+
if (Number.isFinite(created) && created > 0) {
|
|
2306
|
+
return created < 1_000_000_000_000 ? created * 1000 : created;
|
|
2307
|
+
}
|
|
2308
|
+
const dated = clean(model?.id).match(/(?:^|-)(\d{4})(\d{2})(\d{2})(?:$|-)/);
|
|
2309
|
+
return dated ? (Date.parse(`${dated[1]}-${dated[2]}-${dated[3]}`) || 0) : 0;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
function isClaudeProviderModel(model) {
|
|
2313
|
+
return clean(model?.provider).toLowerCase().includes('anthropic')
|
|
2314
|
+
&& /^claude-[a-z]+-/.test(clean(model?.id).toLowerCase());
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function compareProviderModelRecency(a, b) {
|
|
2318
|
+
if (isClaudeProviderModel(a) && isClaudeProviderModel(b)) {
|
|
2319
|
+
if (a.latest !== b.latest) return a.latest ? -1 : 1;
|
|
2320
|
+
const versionDelta = compareProviderModelVersion(a, b);
|
|
2321
|
+
if (versionDelta) return versionDelta;
|
|
2322
|
+
const ta = providerModelReleaseTime(a);
|
|
2323
|
+
const tb = providerModelReleaseTime(b);
|
|
2324
|
+
if (ta !== tb) return tb - ta;
|
|
2325
|
+
return clean(a.display || a.id).localeCompare(clean(b.display || b.id));
|
|
2326
|
+
}
|
|
2327
|
+
const ta = providerModelReleaseTime(a);
|
|
2328
|
+
const tb = providerModelReleaseTime(b);
|
|
2329
|
+
if (ta !== tb) return tb - ta;
|
|
2330
|
+
if (a.latest !== b.latest) return a.latest ? -1 : 1;
|
|
2331
|
+
const versionDelta = compareProviderModelVersion(a, b);
|
|
2332
|
+
if (versionDelta) return versionDelta;
|
|
2333
|
+
return clean(a.display || a.id).localeCompare(clean(b.display || b.id));
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
function sortProviderModels(models) {
|
|
2337
|
+
return (models || []).sort((a, b) => {
|
|
2338
|
+
const ar = a.provider === route.provider ? 0 : 1;
|
|
2339
|
+
const br = b.provider === route.provider ? 0 : 1;
|
|
2340
|
+
if (ar !== br) return ar - br;
|
|
2341
|
+
if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);
|
|
2342
|
+
return compareProviderModelRecency(a, b);
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
function isSelectableLlmModel(model) {
|
|
2347
|
+
const id = clean(model?.id).toLowerCase();
|
|
2348
|
+
const display = clean(model?.display || model?.name).toLowerCase();
|
|
2349
|
+
const mode = clean(model?.mode).toLowerCase();
|
|
2350
|
+
const text = `${id} ${display}`;
|
|
2351
|
+
if (!id) return false;
|
|
2352
|
+
if (mode && !['chat', 'completion', 'responses', 'messages'].includes(mode)) return false;
|
|
2353
|
+
if (/(^|[-_\s])(image|images|video|videos|audio|tts|stt|speech|embedding|embeddings|rerank|moderation|imagine)([-_\s]|$)/i.test(text)) return false;
|
|
2354
|
+
if (/(^|[-_\s])(dall[-_\s]?e|sora|imagen)([-_\s]|$)/i.test(text)) return false;
|
|
2355
|
+
return true;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
function providerModelCacheRow(name, m) {
|
|
2359
|
+
return {
|
|
2360
|
+
id: m.id,
|
|
2361
|
+
provider: name,
|
|
2362
|
+
display: m.display || m.name || m.id,
|
|
2363
|
+
created: typeof m.created === 'number' ? m.created : null,
|
|
2364
|
+
releaseDate: m.releaseDate || null,
|
|
2365
|
+
contextWindow: m.contextWindow,
|
|
2366
|
+
outputTokens: m.outputTokens || null,
|
|
2367
|
+
family: m.family || null,
|
|
2368
|
+
tier: m.tier || null,
|
|
2369
|
+
latest: m.latest === true,
|
|
2370
|
+
description: m.description || '',
|
|
2371
|
+
supportsVision: m.supportsVision === true,
|
|
2372
|
+
supportsFunctionCalling: m.supportsFunctionCalling === true,
|
|
2373
|
+
supportsPromptCaching: m.supportsPromptCaching === true,
|
|
2374
|
+
supportsReasoning: m.supportsReasoning === true,
|
|
2375
|
+
reasoningLevels: Array.isArray(m.reasoningLevels) ? m.reasoningLevels : [],
|
|
2376
|
+
reasoningOptions: Array.isArray(m.reasoningOptions) ? m.reasoningOptions : [],
|
|
2377
|
+
reasoningContentField: m.reasoningContentField || null,
|
|
2378
|
+
mode: m.mode || null,
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
function hydrateProviderModelRow(row) {
|
|
2383
|
+
return {
|
|
2384
|
+
...row,
|
|
2385
|
+
effortOptions: effortItemsFor(row.provider, row, null),
|
|
2386
|
+
fastCapable: fastCapableFor(row.provider, row),
|
|
2387
|
+
fastPreferred: fastPreferenceFor(config, row.provider, row.id),
|
|
2388
|
+
savedEffort: modelSettingsFor(config, row.provider, row.id).effort || null,
|
|
2389
|
+
savedFast: modelSettingsFor(config, row.provider, row.id).fast === true,
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
function providerModelsFromCacheRows(rows) {
|
|
2394
|
+
return sortProviderModels((rows || []).map(hydrateProviderModelRow));
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
async function loadProviderModelsFresh() {
|
|
2398
|
+
ensureFullConfig();
|
|
2399
|
+
await ensureProvidersReady(config.providers || {});
|
|
2400
|
+
const allProviders = reg.getAllProviders();
|
|
2401
|
+
const results = [];
|
|
2402
|
+
const seen = new Set();
|
|
2403
|
+
for (const [name, provider] of allProviders) {
|
|
2404
|
+
if (typeof provider?.listModels !== 'function') continue;
|
|
2405
|
+
try {
|
|
2406
|
+
const models = await provider.listModels();
|
|
2407
|
+
if (Array.isArray(models)) {
|
|
2408
|
+
for (const m of models) {
|
|
2409
|
+
if (!m?.id) continue;
|
|
2410
|
+
if (!isSelectableLlmModel(m)) continue;
|
|
2411
|
+
const key = `${name}:${m.id}`;
|
|
2412
|
+
if (seen.has(key)) continue;
|
|
2413
|
+
seen.add(key);
|
|
2414
|
+
const row = providerModelCacheRow(name, m);
|
|
2415
|
+
results.push(row);
|
|
2416
|
+
modelMetaByRoute.set(modelMetaKey(name, m.id), row);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
} catch {
|
|
2420
|
+
// Ignore per-provider catalog failures so one bad credential or
|
|
2421
|
+
// transient /models error does not hide other authenticated models.
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return results;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
async function collectProviderModels({ force = false } = {}) {
|
|
2428
|
+
if (!force && Array.isArray(providerModelsCache.models)) {
|
|
2429
|
+
return providerModelsFromCacheRows(providerModelsCache.models);
|
|
2430
|
+
}
|
|
2431
|
+
if (!providerModelsPromise) {
|
|
2432
|
+
providerModelsPromise = loadProviderModelsFresh()
|
|
2433
|
+
.then((models) => {
|
|
2434
|
+
providerModelsCache = { models, at: Date.now() };
|
|
2435
|
+
return models;
|
|
2436
|
+
})
|
|
2437
|
+
.finally(() => {
|
|
2438
|
+
providerModelsPromise = null;
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
return providerModelsFromCacheRows(await providerModelsPromise);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
function warmProviderModelCache() {
|
|
2445
|
+
if (Array.isArray(providerModelsCache.models) || providerModelsPromise) return providerModelsPromise;
|
|
2446
|
+
providerModelsPromise = loadProviderModelsFresh()
|
|
2447
|
+
.then((models) => {
|
|
2448
|
+
providerModelsCache = { models, at: Date.now() };
|
|
2449
|
+
bootProfile('provider-models:warm-ready', { count: models.length });
|
|
2450
|
+
return models;
|
|
2451
|
+
})
|
|
2452
|
+
.catch((err) => {
|
|
2453
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2454
|
+
bootProfile('provider-models:warm-failed', { error: msg });
|
|
2455
|
+
return [];
|
|
2456
|
+
})
|
|
2457
|
+
.finally(() => {
|
|
2458
|
+
providerModelsPromise = null;
|
|
2459
|
+
});
|
|
2460
|
+
return providerModelsPromise;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
async function resolveMissingRouteModelForFirstTurn() {
|
|
2464
|
+
if (routeHasModel()) return route;
|
|
2465
|
+
const models = await collectProviderModels();
|
|
2466
|
+
const picked = models[0] || null;
|
|
2467
|
+
if (!picked) {
|
|
2468
|
+
throw new Error('No provider models available. Run /providers to authenticate, then /model to choose a model.');
|
|
2469
|
+
}
|
|
2470
|
+
route = {
|
|
2471
|
+
...route,
|
|
2472
|
+
provider: picked.provider,
|
|
2473
|
+
model: picked.id,
|
|
2474
|
+
preset: null,
|
|
2475
|
+
};
|
|
2476
|
+
return route;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
async function refreshRouteEffort(modelMetaOverride = null) {
|
|
2480
|
+
await ensureProvidersReady(ensureProviderEnabled(config, route.provider));
|
|
2481
|
+
const modelMeta = modelMetaOverride || await lookupModelMeta(route.provider, route.model);
|
|
2482
|
+
const requested = hasOwn(route, 'effort') ? route.effort : (route.preset?.effort || null);
|
|
2483
|
+
const effectiveEffort = coerceEffortFor(route.provider, modelMeta, requested);
|
|
2484
|
+
const fastCapable = fastCapableFor(route.provider, modelMeta);
|
|
2485
|
+
route = {
|
|
2486
|
+
...route,
|
|
2487
|
+
fast: fastCapable ? route.fast === true : false,
|
|
2488
|
+
fastCapable,
|
|
2489
|
+
effectiveEffort,
|
|
2490
|
+
effortOptions: effortItemsFor(route.provider, modelMeta, effectiveEffort),
|
|
2491
|
+
};
|
|
2492
|
+
return route;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
function routeHasModel() {
|
|
2496
|
+
return !!clean(route?.model);
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
function requireModelRoute() {
|
|
2500
|
+
if (routeHasModel()) return;
|
|
2501
|
+
throw new Error('No model configured. Run /providers to authenticate, then /model to choose a model.');
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
async function recreateCurrentSessionIfReady() {
|
|
2505
|
+
if (!routeHasModel()) {
|
|
2506
|
+
session = null;
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2509
|
+
return await createCurrentSession();
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
async function createCurrentSession(reason = 'demand') {
|
|
2513
|
+
if (sessionCreatePromise) return await sessionCreatePromise;
|
|
2514
|
+
if (session?.id && !sessionNeedsCwdRefresh) {
|
|
2515
|
+
const liveSession = mgr.getSession(session.id);
|
|
2516
|
+
if (liveSession && liveSession.closed !== true && liveSession.status !== 'closed') {
|
|
2517
|
+
session = liveSession;
|
|
2518
|
+
return session;
|
|
2519
|
+
}
|
|
2520
|
+
session = null;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
const startedAt = performance.now();
|
|
2524
|
+
bootProfile('session:create:start', { mode, reason });
|
|
2525
|
+
const promise = (async () => {
|
|
2526
|
+
ensureConfigForRouteProvider();
|
|
2527
|
+
await resolveMissingRouteModelForFirstTurn();
|
|
2528
|
+
requireModelRoute();
|
|
2529
|
+
bootProfile('session:create:route-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
2530
|
+
await refreshRouteEffort();
|
|
2531
|
+
bootProfile('session:create:effort-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
2532
|
+
const providerImpl = reg.getProvider(route.provider);
|
|
2533
|
+
if (!providerImpl) {
|
|
2534
|
+
throw new Error(`Provider "${route.provider}" is not configured.`);
|
|
2535
|
+
}
|
|
2536
|
+
bootProfile('session:create:provider-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
2537
|
+
const coreMemoryContext = await loadCoreMemoryContext();
|
|
2538
|
+
if (closeRequested) throw new Error('runtime is closing');
|
|
2539
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
2540
|
+
const workflowContext = workflowContextBlock(config, dataDir);
|
|
2541
|
+
const workspaceContext = buildWorkspaceContext();
|
|
2542
|
+
const sessionOpts = {
|
|
2543
|
+
provider: route.provider,
|
|
2544
|
+
model: route.model,
|
|
2545
|
+
preset: route.preset || undefined,
|
|
2546
|
+
tools: toolSpecForMode(mode),
|
|
2547
|
+
owner: 'cli',
|
|
2548
|
+
role: 'lead',
|
|
2549
|
+
lane: 'cli',
|
|
2550
|
+
sourceType: 'lead',
|
|
2551
|
+
sourceName: 'main',
|
|
2552
|
+
clientHostPid: process.pid,
|
|
2553
|
+
disallowedTools: LEAD_DISALLOWED_TOOLS,
|
|
2554
|
+
cwd: currentCwd,
|
|
2555
|
+
coreMemoryContext,
|
|
2556
|
+
workflowContext,
|
|
2557
|
+
workspaceContext,
|
|
2558
|
+
fast: route.fast === true,
|
|
2559
|
+
};
|
|
2560
|
+
if (hasOwn(route, 'effort') || route.effectiveEffort) {
|
|
2561
|
+
sessionOpts.effort = route.effectiveEffort || null;
|
|
2562
|
+
}
|
|
2563
|
+
session = mgr.createSession(sessionOpts);
|
|
2564
|
+
sessionNeedsCwdRefresh = false;
|
|
2565
|
+
Object.defineProperty(session, 'beforeToolHook', {
|
|
2566
|
+
value: (input) => hooks.beforeTool(input),
|
|
2567
|
+
enumerable: false,
|
|
2568
|
+
configurable: true,
|
|
2569
|
+
writable: true,
|
|
2570
|
+
});
|
|
2571
|
+
applyDeferredToolSurface(session, mode, standaloneTools);
|
|
2572
|
+
applyPreSessionToolSelection();
|
|
2573
|
+
statusRoutes?.writeGatewaySessionRoute?.(session.id, routeForStatusline(route));
|
|
2574
|
+
hooks.emit('session:create', { sessionId: session.id, provider: route.provider, model: route.model, toolMode: mode, cwd: currentCwd });
|
|
2575
|
+
bootProfile('session:create:ready', {
|
|
2576
|
+
ms: (performance.now() - startedAt).toFixed(1),
|
|
2577
|
+
reason,
|
|
2578
|
+
tools: Array.isArray(session.tools) ? session.tools.length : 0,
|
|
2579
|
+
catalog: Array.isArray(session.deferredToolCatalog) ? session.deferredToolCatalog.length : 0,
|
|
2580
|
+
});
|
|
2581
|
+
return session;
|
|
2582
|
+
})();
|
|
2583
|
+
|
|
2584
|
+
sessionCreatePromise = promise;
|
|
2585
|
+
try {
|
|
2586
|
+
return await promise;
|
|
2587
|
+
} finally {
|
|
2588
|
+
if (sessionCreatePromise === promise) sessionCreatePromise = null;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
function scheduleLeadSessionPrewarm() {
|
|
2593
|
+
if (envFlag('MIXDOG_DISABLE_SESSION_PREWARM')) return;
|
|
2594
|
+
const timer = setTimeout(() => {
|
|
2595
|
+
if (closeRequested || session?.id || sessionCreatePromise || activeTurnCount > 0) return;
|
|
2596
|
+
void createCurrentSession('prewarm')
|
|
2597
|
+
.then(() => bootProfile('session:prewarm:ready'))
|
|
2598
|
+
.catch((error) => bootProfile('session:prewarm:failed', { error: error?.message || String(error) }));
|
|
2599
|
+
}, sessionPrewarmDelayMs);
|
|
2600
|
+
timer.unref?.();
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
function scheduleProviderWarmup(delayMs = providerWarmupDelayMs) {
|
|
2604
|
+
if (envFlag('MIXDOG_DISABLE_PROVIDER_WARMUP')) {
|
|
2605
|
+
bootProfile('providers:warm-skipped');
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
if (providerWarmupTimer || closeRequested) return;
|
|
2609
|
+
providerWarmupTimer = setTimeout(() => {
|
|
2610
|
+
providerWarmupTimer = null;
|
|
2611
|
+
if (closeRequested) return;
|
|
2612
|
+
if (activeTurnCount > 0 || sessionCreatePromise) {
|
|
2613
|
+
bootProfile('providers:warm-deferred', { reason: activeTurnCount > 0 ? 'turn-active' : 'session-create' });
|
|
2614
|
+
scheduleProviderWarmup(backgroundBusyRetryMs);
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const providersStartedAt = performance.now();
|
|
2618
|
+
try {
|
|
2619
|
+
config = cfgMod.loadConfig();
|
|
2620
|
+
configHasSecrets = true;
|
|
2621
|
+
} catch (error) {
|
|
2622
|
+
bootProfile('config:full-failed', { error: error?.message || String(error) });
|
|
2623
|
+
}
|
|
2624
|
+
void ensureProvidersReady(config.providers || {})
|
|
2625
|
+
.then(() => {
|
|
2626
|
+
bootProfile('providers:init:ready', { ms: (performance.now() - providersStartedAt).toFixed(1) });
|
|
2627
|
+
if (closeRequested) return null;
|
|
2628
|
+
if (activeTurnCount > 0 || sessionCreatePromise) {
|
|
2629
|
+
bootProfile('providers:optional-warm-deferred', { reason: activeTurnCount > 0 ? 'turn-active' : 'session-create' });
|
|
2630
|
+
scheduleProviderWarmup(backgroundBusyRetryMs);
|
|
2631
|
+
return null;
|
|
2632
|
+
}
|
|
2633
|
+
return cachedProviderSetup();
|
|
2634
|
+
})
|
|
2635
|
+
.then((setup) => {
|
|
2636
|
+
if (!setup) return;
|
|
2637
|
+
bootProfile('provider-setup:warm-ready', {
|
|
2638
|
+
api: Array.isArray(setup?.api) ? setup.api.length : 0,
|
|
2639
|
+
oauth: Array.isArray(setup?.oauth) ? setup.oauth.length : 0,
|
|
2640
|
+
local: Array.isArray(setup?.local) ? setup.local.length : 0,
|
|
2641
|
+
});
|
|
2642
|
+
})
|
|
2643
|
+
.catch((error) => bootProfile('providers:warm-failed', { error: error?.message || String(error) }));
|
|
2644
|
+
}, delayMs);
|
|
2645
|
+
providerWarmupTimer.unref?.();
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
function scheduleProviderModelWarmup(delayMs = providerModelWarmupDelayMs) {
|
|
2649
|
+
if (envFlag('MIXDOG_DISABLE_PROVIDER_WARMUP')) return;
|
|
2650
|
+
if (providerModelWarmupTimer || closeRequested) return;
|
|
2651
|
+
providerModelWarmupTimer = setTimeout(() => {
|
|
2652
|
+
providerModelWarmupTimer = null;
|
|
2653
|
+
if (closeRequested || Array.isArray(providerModelsCache.models) || providerModelsPromise) return;
|
|
2654
|
+
if (activeTurnCount > 0 || sessionCreatePromise) {
|
|
2655
|
+
bootProfile('provider-models:warm-deferred', { reason: activeTurnCount > 0 ? 'turn-active' : 'session-create' });
|
|
2656
|
+
scheduleProviderModelWarmup(backgroundBusyRetryMs);
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
warmProviderModelCache();
|
|
2660
|
+
}, delayMs);
|
|
2661
|
+
providerModelWarmupTimer.unref?.();
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
function scheduleChannelStart(delayMs = channelStartDelayMs) {
|
|
2665
|
+
if (envFlag('MIXDOG_DISABLE_CHANNEL_START')) {
|
|
2666
|
+
bootProfile('channels:start-skipped');
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
if (channelStartTimer || closeRequested) return;
|
|
2670
|
+
bootProfile('channels:start-scheduled', { delayMs });
|
|
2671
|
+
channelStartTimer = setTimeout(() => {
|
|
2672
|
+
channelStartTimer = null;
|
|
2673
|
+
if (closeRequested) return;
|
|
2674
|
+
if (activeTurnCount > 0 || sessionCreatePromise) {
|
|
2675
|
+
bootProfile('channels:start-deferred', { reason: activeTurnCount > 0 ? 'turn-active' : 'session-create' });
|
|
2676
|
+
scheduleChannelStart(backgroundBusyRetryMs);
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
const startedAt = performance.now();
|
|
2680
|
+
bootProfile('channels:start:begin');
|
|
2681
|
+
channels.start()
|
|
2682
|
+
.then(() => bootProfile('channels:start:ready', { ms: (performance.now() - startedAt).toFixed(1) }))
|
|
2683
|
+
.catch((error) => bootProfile('channels:start:failed', {
|
|
2684
|
+
ms: (performance.now() - startedAt).toFixed(1),
|
|
2685
|
+
error: error?.message || String(error),
|
|
2686
|
+
}));
|
|
2687
|
+
}, delayMs);
|
|
2688
|
+
channelStartTimer.unref?.();
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
function withTeardownDeadline(promise, ms, fallback = false) {
|
|
2692
|
+
let timer = null;
|
|
2693
|
+
return Promise.race([
|
|
2694
|
+
Promise.resolve(promise),
|
|
2695
|
+
new Promise((resolve) => {
|
|
2696
|
+
timer = setTimeout(() => resolve(fallback), ms);
|
|
2697
|
+
}),
|
|
2698
|
+
]).finally(() => {
|
|
2699
|
+
if (timer) clearTimeout(timer);
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
bootProfile('session-runtime:ready', { lazySession: true, prewarmSession: true });
|
|
2704
|
+
scheduleLeadSessionPrewarm();
|
|
2705
|
+
scheduleProviderWarmup();
|
|
2706
|
+
scheduleChannelStart();
|
|
2707
|
+
|
|
2708
|
+
function contextStatusCacheKeyFor({ messages, tools, bridgeMode }) {
|
|
2709
|
+
const compaction = session?.compaction || {};
|
|
2710
|
+
const lastMessage = messages[messages.length - 1] || null;
|
|
2711
|
+
return {
|
|
2712
|
+
session,
|
|
2713
|
+
sessionId: session?.id || null,
|
|
2714
|
+
provider: route.provider,
|
|
2715
|
+
model: route.model,
|
|
2716
|
+
cwd: currentCwd,
|
|
2717
|
+
mode,
|
|
2718
|
+
bridgeMode,
|
|
2719
|
+
messages,
|
|
2720
|
+
messageCount: messages.length,
|
|
2721
|
+
lastMessage,
|
|
2722
|
+
lastMessageRole: lastMessage?.role || null,
|
|
2723
|
+
lastMessageContent: lastMessage?.content || null,
|
|
2724
|
+
tools,
|
|
2725
|
+
toolCount: tools.length,
|
|
2726
|
+
contextWindow: session?.contextWindow || null,
|
|
2727
|
+
rawContextWindow: session?.rawContextWindow || null,
|
|
2728
|
+
effectiveContextWindowPercent: session?.effectiveContextWindowPercent || null,
|
|
2729
|
+
autoCompactTokenLimit: Number(session?.autoCompactTokenLimit || 0),
|
|
2730
|
+
lastContextTokens: Number(session?.lastContextTokens || 0),
|
|
2731
|
+
lastContextTokensUpdatedAt: Number(session?.lastContextTokensUpdatedAt || 0),
|
|
2732
|
+
lastContextTokensStaleAfterCompact: session?.lastContextTokensStaleAfterCompact === true,
|
|
2733
|
+
lastInputTokens: Number(session?.lastInputTokens || 0),
|
|
2734
|
+
lastOutputTokens: Number(session?.lastOutputTokens || 0),
|
|
2735
|
+
lastCachedReadTokens: Number(session?.lastCachedReadTokens || 0),
|
|
2736
|
+
lastCacheWriteTokens: Number(session?.lastCacheWriteTokens || 0),
|
|
2737
|
+
totalInputTokens: Number(session?.totalInputTokens || 0),
|
|
2738
|
+
totalOutputTokens: Number(session?.totalOutputTokens || 0),
|
|
2739
|
+
totalCachedReadTokens: Number(session?.totalCachedReadTokens || 0),
|
|
2740
|
+
totalCacheWriteTokens: Number(session?.totalCacheWriteTokens || 0),
|
|
2741
|
+
compactBoundaryTokens: Number(session?.compactBoundaryTokens || 0),
|
|
2742
|
+
compactionBoundaryTokens: Number(compaction.boundaryTokens || 0),
|
|
2743
|
+
compactionTriggerTokens: Number(compaction.triggerTokens || 0),
|
|
2744
|
+
compactionLastChangedAt: Number(compaction.lastChangedAt || 0),
|
|
2745
|
+
compactionLastCompactAt: Number(compaction.lastCompactAt || 0),
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
function sameContextStatusCacheKey(a, b) {
|
|
2750
|
+
if (!a || !b) return false;
|
|
2751
|
+
for (const key of Object.keys(a)) {
|
|
2752
|
+
if (!Object.is(a[key], b[key])) return false;
|
|
2753
|
+
}
|
|
2754
|
+
return true;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
return {
|
|
2758
|
+
get id() {
|
|
2759
|
+
return session?.id || null;
|
|
2760
|
+
},
|
|
2761
|
+
get provider() {
|
|
2762
|
+
return route.provider;
|
|
2763
|
+
},
|
|
2764
|
+
get model() {
|
|
2765
|
+
return route.model;
|
|
2766
|
+
},
|
|
2767
|
+
get effort() {
|
|
2768
|
+
return route.effectiveEffort || route.effort || route.preset?.effort || null;
|
|
2769
|
+
},
|
|
2770
|
+
get fast() {
|
|
2771
|
+
return route.fast === true;
|
|
2772
|
+
},
|
|
2773
|
+
get fastCapable() {
|
|
2774
|
+
return route.fastCapable === true;
|
|
2775
|
+
},
|
|
2776
|
+
get effortOptions() {
|
|
2777
|
+
return route.effortOptions || [];
|
|
2778
|
+
},
|
|
2779
|
+
get contextWindow() {
|
|
2780
|
+
return session?.contextWindow || null;
|
|
2781
|
+
},
|
|
2782
|
+
get rawContextWindow() {
|
|
2783
|
+
return session?.rawContextWindow || session?.contextWindow || null;
|
|
2784
|
+
},
|
|
2785
|
+
get effectiveContextWindowPercent() {
|
|
2786
|
+
return session?.effectiveContextWindowPercent || null;
|
|
2787
|
+
},
|
|
2788
|
+
get toolMode() {
|
|
2789
|
+
return mode;
|
|
2790
|
+
},
|
|
2791
|
+
get bridgeMode() {
|
|
2792
|
+
return bridge.getDefaultMode();
|
|
2793
|
+
},
|
|
2794
|
+
get autoClear() {
|
|
2795
|
+
return normalizeAutoClearConfig(config.autoClear);
|
|
2796
|
+
},
|
|
2797
|
+
get systemShell() {
|
|
2798
|
+
return normalizeSystemShellConfig(config.shell);
|
|
2799
|
+
},
|
|
2800
|
+
get workflow() {
|
|
2801
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
2802
|
+
const pack = loadWorkflowPack(dataDir, activeWorkflowId(config));
|
|
2803
|
+
return pack ? { id: pack.id, name: pack.name, description: pack.description, source: pack.source } : { id: DEFAULT_WORKFLOW_ID, name: 'Default' };
|
|
2804
|
+
},
|
|
2805
|
+
get outputStyle() {
|
|
2806
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
2807
|
+
return outputStyleStatus(dataDir).current;
|
|
2808
|
+
},
|
|
2809
|
+
get cwd() {
|
|
2810
|
+
return currentCwd;
|
|
2811
|
+
},
|
|
2812
|
+
get session() {
|
|
2813
|
+
return session;
|
|
2814
|
+
},
|
|
2815
|
+
contextStatus() {
|
|
2816
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
2817
|
+
const tools = Array.isArray(session?.tools) ? session.tools : [];
|
|
2818
|
+
const bridgeMode = bridge.getDefaultMode();
|
|
2819
|
+
const cacheKey = contextStatusCacheKeyFor({ messages, tools, bridgeMode });
|
|
2820
|
+
if (contextStatusCacheValue && sameContextStatusCacheKey(cacheKey, contextStatusCacheKey)) {
|
|
2821
|
+
return contextStatusCacheValue;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
const messageSummary = summarizeContextMessages(messages);
|
|
2825
|
+
const toolSchemaTokens = estimateToolSchemaTokens(tools);
|
|
2826
|
+
const toolSchemaBreakdown = estimateToolSchemaBreakdown(tools);
|
|
2827
|
+
const requestReserveTokens = estimateRequestReserveTokens(tools);
|
|
2828
|
+
const requestOverheadTokens = Math.max(0, requestReserveTokens - toolSchemaTokens);
|
|
2829
|
+
const rawWindow = Number(session?.rawContextWindow || session?.contextWindow || 0);
|
|
2830
|
+
const effectiveWindow = Number(session?.contextWindow || rawWindow || 0);
|
|
2831
|
+
const lastContextTokens = Number(session?.lastContextTokens || 0);
|
|
2832
|
+
const estimatedContextTokens = messageSummary.estimatedTokens + requestReserveTokens;
|
|
2833
|
+
const compactAt = Number(session?.compaction?.lastChangedAt || session?.compaction?.lastCompactAt || 0);
|
|
2834
|
+
const usageAt = Number(session?.lastContextTokensUpdatedAt || 0);
|
|
2835
|
+
const lastUsageStale = !!lastContextTokens && (
|
|
2836
|
+
session?.lastContextTokensStaleAfterCompact === true
|
|
2837
|
+
|| (compactAt > 0 && usageAt > 0 && usageAt <= compactAt)
|
|
2838
|
+
|| (compactAt > 0 && usageAt <= 0)
|
|
2839
|
+
);
|
|
2840
|
+
const compactBoundaryTokens = Number(session?.compactBoundaryTokens || session?.compaction?.boundaryTokens || 0);
|
|
2841
|
+
const displayWindow = compactBoundaryTokens || effectiveWindow;
|
|
2842
|
+
const usedTokens = lastUsageStale
|
|
2843
|
+
? estimatedContextTokens
|
|
2844
|
+
: Math.max(estimatedContextTokens, lastContextTokens || 0);
|
|
2845
|
+
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
2846
|
+
const autoCompactTokenLimit = Number(session?.autoCompactTokenLimit || 0);
|
|
2847
|
+
const defaultCompactTriggerTokens = compactBoundaryTokens ? Math.max(1, compactBoundaryTokens) : 0;
|
|
2848
|
+
const compactTriggerTokens = autoCompactTokenLimit && compactBoundaryTokens && autoCompactTokenLimit <= compactBoundaryTokens
|
|
2849
|
+
? autoCompactTokenLimit
|
|
2850
|
+
: Number(session?.compaction?.triggerTokens || defaultCompactTriggerTokens || 0);
|
|
2851
|
+
const compactBufferTokens = Number(session?.compaction?.bufferTokens || (compactBoundaryTokens && compactTriggerTokens ? Math.max(0, compactBoundaryTokens - compactTriggerTokens) : 0));
|
|
2852
|
+
const value = {
|
|
2853
|
+
sessionId: session?.id || null,
|
|
2854
|
+
provider: route.provider,
|
|
2855
|
+
model: route.model,
|
|
2856
|
+
cwd: currentCwd,
|
|
2857
|
+
toolMode: mode,
|
|
2858
|
+
bridgeMode,
|
|
2859
|
+
contextWindow: displayWindow || effectiveWindow || null,
|
|
2860
|
+
effectiveContextWindow: effectiveWindow || null,
|
|
2861
|
+
rawContextWindow: rawWindow || null,
|
|
2862
|
+
effectiveContextWindowPercent: session?.effectiveContextWindowPercent || null,
|
|
2863
|
+
usedTokens,
|
|
2864
|
+
usedSource: lastContextTokens && !lastUsageStale && lastContextTokens >= estimatedContextTokens
|
|
2865
|
+
? 'last_api_request'
|
|
2866
|
+
: 'estimated',
|
|
2867
|
+
currentEstimatedTokens: estimatedContextTokens,
|
|
2868
|
+
lastApiRequestTokens: lastContextTokens || 0,
|
|
2869
|
+
lastApiRequestStale: lastUsageStale,
|
|
2870
|
+
freeTokens,
|
|
2871
|
+
compaction: {
|
|
2872
|
+
...(session?.compaction || {}),
|
|
2873
|
+
boundaryTokens: compactBoundaryTokens || null,
|
|
2874
|
+
triggerTokens: compactTriggerTokens || null,
|
|
2875
|
+
bufferTokens: compactBufferTokens || null,
|
|
2876
|
+
currentEstimatedTokens: estimatedContextTokens,
|
|
2877
|
+
lastApiRequestTokens: lastContextTokens || 0,
|
|
2878
|
+
lastApiRequestStale: lastUsageStale,
|
|
2879
|
+
},
|
|
2880
|
+
messages: messageSummary,
|
|
2881
|
+
request: {
|
|
2882
|
+
toolSchemaTokens,
|
|
2883
|
+
toolSchemaBreakdown,
|
|
2884
|
+
requestOverheadTokens,
|
|
2885
|
+
reserveTokens: requestReserveTokens,
|
|
2886
|
+
},
|
|
2887
|
+
usage: {
|
|
2888
|
+
lastInputTokens: Number(session?.lastInputTokens || 0),
|
|
2889
|
+
lastOutputTokens: Number(session?.lastOutputTokens || 0),
|
|
2890
|
+
lastCachedReadTokens: Number(session?.lastCachedReadTokens || 0),
|
|
2891
|
+
lastCacheWriteTokens: Number(session?.lastCacheWriteTokens || 0),
|
|
2892
|
+
lastContextTokens,
|
|
2893
|
+
totalInputTokens: Number(session?.totalInputTokens || 0),
|
|
2894
|
+
totalOutputTokens: Number(session?.totalOutputTokens || 0),
|
|
2895
|
+
totalCachedReadTokens: Number(session?.totalCachedReadTokens || 0),
|
|
2896
|
+
totalCacheWriteTokens: Number(session?.totalCacheWriteTokens || 0),
|
|
2897
|
+
},
|
|
2898
|
+
};
|
|
2899
|
+
contextStatusCacheKey = cacheKey;
|
|
2900
|
+
contextStatusCacheValue = value;
|
|
2901
|
+
return value;
|
|
2902
|
+
},
|
|
2903
|
+
listProviders() {
|
|
2904
|
+
return renderProviderStatus(cfgMod.loadConfig());
|
|
2905
|
+
},
|
|
2906
|
+
async getProviderSetup() {
|
|
2907
|
+
return await cachedProviderSetup();
|
|
2908
|
+
},
|
|
2909
|
+
async getUsageDashboard(options = {}) {
|
|
2910
|
+
const nextConfig = cfgMod.loadConfig();
|
|
2911
|
+
return await createUsageDashboard(nextConfig, {
|
|
2912
|
+
...(options || {}),
|
|
2913
|
+
setup: await cachedProviderSetup(),
|
|
2914
|
+
getProvider: (providerId) => reg.getProvider(providerId),
|
|
2915
|
+
log: (message) => {
|
|
2916
|
+
if (process.env.MIXDOG_USAGE_TRACE) {
|
|
2917
|
+
try { process.stderr.write(`[usage] ${message}\n`); } catch {}
|
|
2918
|
+
}
|
|
2919
|
+
},
|
|
2920
|
+
});
|
|
2921
|
+
},
|
|
2922
|
+
getOnboardingStatus() {
|
|
2923
|
+
const nextConfig = cfgMod.loadConfig();
|
|
2924
|
+
return {
|
|
2925
|
+
completed: nextConfig?.onboarding?.completed === true,
|
|
2926
|
+
version: nextConfig?.onboarding?.version || 0,
|
|
2927
|
+
default: nextConfig?.default || null,
|
|
2928
|
+
workflowRoutes: summarizeWorkflowRoutes(nextConfig),
|
|
2929
|
+
};
|
|
2930
|
+
},
|
|
2931
|
+
getAutoClear() {
|
|
2932
|
+
return normalizeAutoClearConfig(config.autoClear);
|
|
2933
|
+
},
|
|
2934
|
+
getSystemShell() {
|
|
2935
|
+
return normalizeSystemShellConfig(config.shell);
|
|
2936
|
+
},
|
|
2937
|
+
setSystemShell(input = {}) {
|
|
2938
|
+
const command = normalizeSystemShellCommand(typeof input === 'string' ? input : input?.command);
|
|
2939
|
+
const nextConfig = cfgMod.loadConfig();
|
|
2940
|
+
cfgMod.saveConfig({
|
|
2941
|
+
...nextConfig,
|
|
2942
|
+
shell: command ? { ...(nextConfig.shell || {}), command } : {},
|
|
2943
|
+
});
|
|
2944
|
+
config = cfgMod.loadConfig();
|
|
2945
|
+
setConfiguredShell(command);
|
|
2946
|
+
return normalizeSystemShellConfig(config.shell);
|
|
2947
|
+
},
|
|
2948
|
+
setAutoClear(input = {}) {
|
|
2949
|
+
const current = normalizeAutoClearConfig(config.autoClear);
|
|
2950
|
+
const next = { ...current };
|
|
2951
|
+
if (hasOwn(input, 'enabled')) next.enabled = input.enabled !== false;
|
|
2952
|
+
if (hasOwn(input, 'idleMs')) {
|
|
2953
|
+
const idleMs = Number(input.idleMs);
|
|
2954
|
+
if (!Number.isFinite(idleMs) || idleMs <= 0) throw new Error('autoclear idleMs must be a positive number');
|
|
2955
|
+
next.idleMs = Math.max(60_000, Math.round(idleMs));
|
|
2956
|
+
}
|
|
2957
|
+
if (hasOwn(input, 'duration')) {
|
|
2958
|
+
const idleMs = parseDurationMs(input.duration);
|
|
2959
|
+
if (!idleMs) throw new Error('usage: /autoclear [on|off|status|<minutes|1h|90m>]');
|
|
2960
|
+
next.idleMs = idleMs;
|
|
2961
|
+
if (!hasOwn(input, 'enabled')) next.enabled = true;
|
|
2962
|
+
}
|
|
2963
|
+
const nextConfig = cfgMod.loadConfig();
|
|
2964
|
+
cfgMod.saveConfig({ ...nextConfig, autoClear: next });
|
|
2965
|
+
config = cfgMod.loadConfig();
|
|
2966
|
+
return { ...normalizeAutoClearConfig(config.autoClear), label: formatDurationMs(normalizeAutoClearConfig(config.autoClear).idleMs) };
|
|
2967
|
+
},
|
|
2968
|
+
async completeOnboarding(payload = {}) {
|
|
2969
|
+
const defaultRoute = normalizeWorkflowRoute(payload.defaultRoute, route);
|
|
2970
|
+
const workflowInput = payload.workflowRoutes && typeof payload.workflowRoutes === 'object'
|
|
2971
|
+
? payload.workflowRoutes
|
|
2972
|
+
: {};
|
|
2973
|
+
const nextConfig = cfgMod.loadConfig();
|
|
2974
|
+
let presets = Array.isArray(nextConfig.presets) ? nextConfig.presets.slice() : [];
|
|
2975
|
+
const workflowRoutes = { ...(nextConfig.workflowRoutes || {}) };
|
|
2976
|
+
|
|
2977
|
+
if (defaultRoute) {
|
|
2978
|
+
presets = upsertWorkflowPreset(presets, 'lead', defaultRoute);
|
|
2979
|
+
workflowRoutes.lead = defaultRoute;
|
|
2980
|
+
nextConfig.default = workflowPresetId('lead');
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
for (const slot of WORKFLOW_ROUTE_SLOTS) {
|
|
2984
|
+
const normalized = normalizeWorkflowRoute(workflowInput[slot]);
|
|
2985
|
+
if (!normalized) continue;
|
|
2986
|
+
workflowRoutes[slot] = normalized;
|
|
2987
|
+
presets = upsertWorkflowPreset(presets, slot, normalized);
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
nextConfig.presets = presets;
|
|
2991
|
+
nextConfig.workflowRoutes = workflowRoutes;
|
|
2992
|
+
nextConfig.maintenance = {
|
|
2993
|
+
...(nextConfig.maintenance || {}),
|
|
2994
|
+
explore: workflowRoutes.explorer ? workflowPresetId('explorer') : (nextConfig.maintenance?.explore || 'haiku'),
|
|
2995
|
+
memory: workflowRoutes.memory ? workflowPresetId('memory') : (nextConfig.maintenance?.memory || 'haiku'),
|
|
2996
|
+
};
|
|
2997
|
+
nextConfig.onboarding = {
|
|
2998
|
+
...(nextConfig.onboarding || {}),
|
|
2999
|
+
completed: true,
|
|
3000
|
+
version: ONBOARDING_VERSION,
|
|
3001
|
+
completedAt: new Date().toISOString(),
|
|
3002
|
+
};
|
|
3003
|
+
|
|
3004
|
+
cfgMod.saveConfig(nextConfig);
|
|
3005
|
+
config = cfgMod.loadConfig();
|
|
3006
|
+
if (defaultRoute) {
|
|
3007
|
+
route = resolveRoute(config, { provider: defaultRoute.provider, model: defaultRoute.model, effort: defaultRoute.effort });
|
|
3008
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-onboarding-complete');
|
|
3009
|
+
await recreateCurrentSessionIfReady();
|
|
3010
|
+
}
|
|
3011
|
+
return this.getOnboardingStatus();
|
|
3012
|
+
},
|
|
3013
|
+
getChannelSetup() {
|
|
3014
|
+
return channelSetup();
|
|
3015
|
+
},
|
|
3016
|
+
getChannelWorkerStatus() {
|
|
3017
|
+
return channels.status();
|
|
3018
|
+
},
|
|
3019
|
+
saveDiscordToken(token) {
|
|
3020
|
+
const result = saveDiscordToken(token);
|
|
3021
|
+
reloadChannelsSoon();
|
|
3022
|
+
return result;
|
|
3023
|
+
},
|
|
3024
|
+
forgetDiscordToken() {
|
|
3025
|
+
const result = forgetDiscordToken();
|
|
3026
|
+
reloadChannelsSoon();
|
|
3027
|
+
return result;
|
|
3028
|
+
},
|
|
3029
|
+
saveWebhookAuthtoken(token) {
|
|
3030
|
+
const result = saveWebhookAuthtoken(token);
|
|
3031
|
+
reloadChannelsSoon();
|
|
3032
|
+
return result;
|
|
3033
|
+
},
|
|
3034
|
+
forgetWebhookAuthtoken() {
|
|
3035
|
+
const result = forgetWebhookAuthtoken();
|
|
3036
|
+
reloadChannelsSoon();
|
|
3037
|
+
return result;
|
|
3038
|
+
},
|
|
3039
|
+
saveChannel(entry) {
|
|
3040
|
+
const result = saveChannel(entry);
|
|
3041
|
+
reloadChannelsSoon();
|
|
3042
|
+
return result;
|
|
3043
|
+
},
|
|
3044
|
+
deleteChannel(name) {
|
|
3045
|
+
const result = deleteChannel(name);
|
|
3046
|
+
reloadChannelsSoon();
|
|
3047
|
+
return result;
|
|
3048
|
+
},
|
|
3049
|
+
setWebhookConfig(patch) {
|
|
3050
|
+
const result = setWebhookConfig(patch);
|
|
3051
|
+
reloadChannelsSoon();
|
|
3052
|
+
return result;
|
|
3053
|
+
},
|
|
3054
|
+
saveSchedule(entry) {
|
|
3055
|
+
const result = saveSchedule(entry);
|
|
3056
|
+
reloadChannelsSoon();
|
|
3057
|
+
return result;
|
|
3058
|
+
},
|
|
3059
|
+
deleteSchedule(name) {
|
|
3060
|
+
const result = deleteSchedule(name);
|
|
3061
|
+
reloadChannelsSoon();
|
|
3062
|
+
return result;
|
|
3063
|
+
},
|
|
3064
|
+
setScheduleEnabled(name, enabled) {
|
|
3065
|
+
const result = setScheduleEnabled(name, enabled);
|
|
3066
|
+
reloadChannelsSoon();
|
|
3067
|
+
return result;
|
|
3068
|
+
},
|
|
3069
|
+
saveWebhook(entry) {
|
|
3070
|
+
const result = saveWebhook(entry);
|
|
3071
|
+
reloadChannelsSoon();
|
|
3072
|
+
return result;
|
|
3073
|
+
},
|
|
3074
|
+
deleteWebhook(name) {
|
|
3075
|
+
const result = deleteWebhook(name);
|
|
3076
|
+
reloadChannelsSoon();
|
|
3077
|
+
return result;
|
|
3078
|
+
},
|
|
3079
|
+
setWebhookEnabled(name, enabled) {
|
|
3080
|
+
const result = setWebhookEnabled(name, enabled);
|
|
3081
|
+
reloadChannelsSoon();
|
|
3082
|
+
return result;
|
|
3083
|
+
},
|
|
3084
|
+
async authenticateProvider(providerId, secret) {
|
|
3085
|
+
const result = String(secret || '').trim()
|
|
3086
|
+
? saveProviderApiKey(cfgMod, providerId, secret)
|
|
3087
|
+
: await loginOAuthProvider(cfgMod, providerId);
|
|
3088
|
+
config = cfgMod.loadConfig();
|
|
3089
|
+
invalidateProviderCaches();
|
|
3090
|
+
warmProviderModelCache();
|
|
3091
|
+
return result;
|
|
3092
|
+
},
|
|
3093
|
+
async loginOAuthProvider(providerId) {
|
|
3094
|
+
const result = await loginOAuthProvider(cfgMod, providerId);
|
|
3095
|
+
config = cfgMod.loadConfig();
|
|
3096
|
+
invalidateProviderCaches();
|
|
3097
|
+
warmProviderModelCache();
|
|
3098
|
+
return result;
|
|
3099
|
+
},
|
|
3100
|
+
async beginOAuthProviderLogin(providerId) {
|
|
3101
|
+
const result = await beginOAuthProviderLogin(cfgMod, providerId);
|
|
3102
|
+
config = cfgMod.loadConfig();
|
|
3103
|
+
return {
|
|
3104
|
+
...result,
|
|
3105
|
+
completeCode: async (code) => {
|
|
3106
|
+
const completed = await result.completeCode(code);
|
|
3107
|
+
config = cfgMod.loadConfig();
|
|
3108
|
+
invalidateProviderCaches();
|
|
3109
|
+
warmProviderModelCache();
|
|
3110
|
+
return completed;
|
|
3111
|
+
},
|
|
3112
|
+
};
|
|
3113
|
+
},
|
|
3114
|
+
saveProviderApiKey(providerId, secret) {
|
|
3115
|
+
const result = saveProviderApiKey(cfgMod, providerId, secret);
|
|
3116
|
+
config = cfgMod.loadConfig();
|
|
3117
|
+
invalidateProviderCaches();
|
|
3118
|
+
warmProviderModelCache();
|
|
3119
|
+
return result;
|
|
3120
|
+
},
|
|
3121
|
+
saveOpenAIUsageSessionKey(secret) {
|
|
3122
|
+
const result = saveOpenAIUsageSessionKey(cfgMod, secret);
|
|
3123
|
+
config = cfgMod.loadConfig();
|
|
3124
|
+
invalidateProviderCaches();
|
|
3125
|
+
return result;
|
|
3126
|
+
},
|
|
3127
|
+
saveOpenCodeGoUsageAuth(opts) {
|
|
3128
|
+
const result = saveOpenCodeGoUsageAuth(cfgMod, opts);
|
|
3129
|
+
config = cfgMod.loadConfig();
|
|
3130
|
+
invalidateProviderCaches();
|
|
3131
|
+
return result;
|
|
3132
|
+
},
|
|
3133
|
+
setLocalProvider(providerId, opts) {
|
|
3134
|
+
const result = setLocalProvider(cfgMod, providerId, opts);
|
|
3135
|
+
config = cfgMod.loadConfig();
|
|
3136
|
+
invalidateProviderCaches();
|
|
3137
|
+
warmProviderModelCache();
|
|
3138
|
+
return result;
|
|
3139
|
+
},
|
|
3140
|
+
forgetProviderAuth(providerId) {
|
|
3141
|
+
const result = forgetProviderAuth(providerId);
|
|
3142
|
+
config = cfgMod.loadConfig();
|
|
3143
|
+
invalidateProviderCaches();
|
|
3144
|
+
warmProviderModelCache();
|
|
3145
|
+
return result;
|
|
3146
|
+
},
|
|
3147
|
+
listPresets() {
|
|
3148
|
+
return cfgMod.listPresets(cfgMod.loadConfig());
|
|
3149
|
+
},
|
|
3150
|
+
async listProviderModels(options = {}) {
|
|
3151
|
+
return await collectProviderModels({ force: options.force === true || options.refresh === true });
|
|
3152
|
+
},
|
|
3153
|
+
listAgents() {
|
|
3154
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
3155
|
+
return FIXED_AGENT_SLOTS.map((agent) => ({
|
|
3156
|
+
...agent,
|
|
3157
|
+
locked: true,
|
|
3158
|
+
route: agentRouteFromConfig(config, agent.id, dataDir),
|
|
3159
|
+
definition: loadAgentDefinition(dataDir, agent.id),
|
|
3160
|
+
}));
|
|
3161
|
+
},
|
|
3162
|
+
listWorkflows() {
|
|
3163
|
+
const currentConfig = cfgMod.loadConfig();
|
|
3164
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
3165
|
+
const active = activeWorkflowId(currentConfig);
|
|
3166
|
+
return listWorkflowPacks(dataDir).map((workflow) => ({
|
|
3167
|
+
id: workflow.id,
|
|
3168
|
+
name: workflow.name,
|
|
3169
|
+
description: workflow.description,
|
|
3170
|
+
source: workflow.source,
|
|
3171
|
+
active: workflow.id === active,
|
|
3172
|
+
agents: workflow.agents,
|
|
3173
|
+
}));
|
|
3174
|
+
},
|
|
3175
|
+
getOutputStyle() {
|
|
3176
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
3177
|
+
return outputStyleStatus(dataDir);
|
|
3178
|
+
},
|
|
3179
|
+
listOutputStyles() {
|
|
3180
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
3181
|
+
return outputStyleStatus(dataDir);
|
|
3182
|
+
},
|
|
3183
|
+
async setOutputStyle(value) {
|
|
3184
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
3185
|
+
const before = outputStyleStatus(dataDir);
|
|
3186
|
+
const selected = findOutputStyle(value, before.styles);
|
|
3187
|
+
if (!selected) {
|
|
3188
|
+
const names = before.styles.map((style) => style.label || style.id).join(', ') || 'Default';
|
|
3189
|
+
throw new Error(`output style must be one of ${names}`);
|
|
3190
|
+
}
|
|
3191
|
+
if (typeof sharedCfgMod.updateConfig !== 'function') throw new Error('output style config writer unavailable');
|
|
3192
|
+
sharedCfgMod.updateConfig((root) => {
|
|
3193
|
+
const next = { ...(root || {}), outputStyle: selected.id };
|
|
3194
|
+
if (next.agent && typeof next.agent === 'object' && !Array.isArray(next.agent)) {
|
|
3195
|
+
const agent = { ...next.agent };
|
|
3196
|
+
delete agent.outputStyle;
|
|
3197
|
+
next.agent = agent;
|
|
3198
|
+
}
|
|
3199
|
+
return next;
|
|
3200
|
+
});
|
|
3201
|
+
const hasConversation = sessionHasConversationMessages(session);
|
|
3202
|
+
let appliedToCurrentSession = !hasConversation;
|
|
3203
|
+
if (session?.id && !hasConversation) {
|
|
3204
|
+
mgr.closeSession(session.id, 'cli-output-style-switch');
|
|
3205
|
+
session = null;
|
|
3206
|
+
await recreateCurrentSessionIfReady();
|
|
3207
|
+
}
|
|
3208
|
+
invalidateContextStatusCache();
|
|
3209
|
+
return { ...outputStyleStatus(dataDir), appliedToCurrentSession };
|
|
3210
|
+
},
|
|
3211
|
+
async setWorkflow(workflowId) {
|
|
3212
|
+
const id = normalizeWorkflowId(workflowId, DEFAULT_WORKFLOW_ID);
|
|
3213
|
+
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
3214
|
+
const pack = loadWorkflowPack(dataDir, id);
|
|
3215
|
+
if (!pack || pack.id !== id) throw new Error(`workflow "${workflowId}" not found`);
|
|
3216
|
+
const nextConfig = cfgMod.loadConfig();
|
|
3217
|
+
nextConfig.workflow = { ...(nextConfig.workflow || {}), active: id };
|
|
3218
|
+
cfgMod.saveConfig(nextConfig);
|
|
3219
|
+
config = cfgMod.loadConfig();
|
|
3220
|
+
if (session?.id) {
|
|
3221
|
+
mgr.closeSession(session.id, 'cli-workflow-switch');
|
|
3222
|
+
session = null;
|
|
3223
|
+
}
|
|
3224
|
+
await recreateCurrentSessionIfReady();
|
|
3225
|
+
return { id: pack.id, name: pack.name, description: pack.description, source: pack.source };
|
|
3226
|
+
},
|
|
3227
|
+
async setAgentRoute(agentId, next) {
|
|
3228
|
+
const id = normalizeAgentId(agentId);
|
|
3229
|
+
if (!id) throw new Error(`unknown agent "${agentId}"`);
|
|
3230
|
+
let selectedRoute = resolveRoute(config, { ...(next || {}) });
|
|
3231
|
+
await reg.initProviders(ensureProviderEnabled(config, selectedRoute.provider));
|
|
3232
|
+
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model);
|
|
3233
|
+
const fastCapable = fastCapableFor(selectedRoute.provider, modelMeta);
|
|
3234
|
+
selectedRoute = { ...selectedRoute, fast: fastCapable ? selectedRoute.fast === true : false };
|
|
3235
|
+
config = saveModelSettings(cfgMod, selectedRoute, { fastCapable });
|
|
3236
|
+
|
|
3237
|
+
const routeToSave = normalizeWorkflowRoute(selectedRoute);
|
|
3238
|
+
if (!routeToSave) throw new Error('agent route requires provider and model');
|
|
3239
|
+
const agent = FIXED_AGENT_SLOTS.find((item) => item.id === id);
|
|
3240
|
+
const nextConfig = cfgMod.loadConfig();
|
|
3241
|
+
nextConfig.agents = {
|
|
3242
|
+
...(nextConfig.agents || {}),
|
|
3243
|
+
[id]: routeToSave,
|
|
3244
|
+
};
|
|
3245
|
+
nextConfig.presets = upsertWorkflowPreset(nextConfig.presets, agentPresetSlot(id), routeToSave);
|
|
3246
|
+
if (agent?.workflowSlot) {
|
|
3247
|
+
nextConfig.workflowRoutes = {
|
|
3248
|
+
...(nextConfig.workflowRoutes || {}),
|
|
3249
|
+
[agent.workflowSlot]: routeToSave,
|
|
3250
|
+
};
|
|
3251
|
+
nextConfig.presets = upsertWorkflowPreset(nextConfig.presets, agent.workflowSlot, routeToSave);
|
|
3252
|
+
nextConfig.maintenance = {
|
|
3253
|
+
...(nextConfig.maintenance || {}),
|
|
3254
|
+
...(id === 'explore' ? { explore: workflowPresetId('explorer') } : {}),
|
|
3255
|
+
...(id === 'maintainer' ? { memory: workflowPresetId('memory') } : {}),
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
cfgMod.saveConfig(nextConfig);
|
|
3259
|
+
config = cfgMod.loadConfig();
|
|
3260
|
+
return routeToSave;
|
|
3261
|
+
},
|
|
3262
|
+
async ask(prompt, options = {}) {
|
|
3263
|
+
activeTurnCount += 1;
|
|
3264
|
+
const startedAt = Date.now();
|
|
3265
|
+
try {
|
|
3266
|
+
await refreshSessionForCwdIfNeeded('cwd-change');
|
|
3267
|
+
if (!session?.id) await createCurrentSession('turn');
|
|
3268
|
+
hooks.emit('turn:start', { sessionId: session.id, prompt, cwd: currentCwd });
|
|
3269
|
+
const turnContext = [options.context || ''].map((part) => String(part || '').trim()).filter(Boolean).join('\n\n');
|
|
3270
|
+
const result = await mgr.askSession(
|
|
3271
|
+
session.id,
|
|
3272
|
+
prompt,
|
|
3273
|
+
turnContext || null,
|
|
3274
|
+
async (iter, calls) => {
|
|
3275
|
+
for (const call of calls || []) {
|
|
3276
|
+
hooks.emit('tool:planned', {
|
|
3277
|
+
sessionId: session.id,
|
|
3278
|
+
name: call?.name || 'tool',
|
|
3279
|
+
callId: call?.id || null,
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
if (typeof options.onToolCall === 'function') {
|
|
3283
|
+
return await options.onToolCall(iter, calls);
|
|
3284
|
+
}
|
|
3285
|
+
return undefined;
|
|
3286
|
+
},
|
|
3287
|
+
currentCwd,
|
|
3288
|
+
options.prefetch || null,
|
|
3289
|
+
{
|
|
3290
|
+
onTextDelta: options.onTextDelta,
|
|
3291
|
+
onReasoningDelta: options.onReasoningDelta,
|
|
3292
|
+
onUsageDelta: options.onUsageDelta,
|
|
3293
|
+
onToolResult: options.onToolResult,
|
|
3294
|
+
onStageChange: options.onStageChange,
|
|
3295
|
+
onStreamDelta: options.onStreamDelta,
|
|
3296
|
+
drainSteering: options.drainSteering,
|
|
3297
|
+
onSteerMessage: options.onSteerMessage,
|
|
3298
|
+
},
|
|
3299
|
+
);
|
|
3300
|
+
session = mgr.getSession(session.id) || session;
|
|
3301
|
+
hooks.emit('turn:end', { sessionId: session.id, elapsedMs: Date.now() - startedAt });
|
|
3302
|
+
return { result, session };
|
|
3303
|
+
} catch (error) {
|
|
3304
|
+
hooks.emit('turn:error', { sessionId: session?.id || null, elapsedMs: Date.now() - startedAt, error: error?.message || String(error) });
|
|
3305
|
+
throw error;
|
|
3306
|
+
} finally {
|
|
3307
|
+
activeTurnCount = Math.max(0, activeTurnCount - 1);
|
|
3308
|
+
if (!firstTurnCompleted) {
|
|
3309
|
+
firstTurnCompleted = true;
|
|
3310
|
+
scheduleProviderModelWarmup();
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
},
|
|
3314
|
+
async clear(options = {}) {
|
|
3315
|
+
if (!session?.id) return false;
|
|
3316
|
+
const cleared = await mgr.clearSessionMessages(session.id);
|
|
3317
|
+
if (!cleared) return false;
|
|
3318
|
+
session = typeof cleared === 'object' ? cleared : (mgr.getSession(session.id) || session);
|
|
3319
|
+
if (options.recoverBridge === true) {
|
|
3320
|
+
try { bridge.recoverWorkers?.({ clientHostPid: session?.clientHostPid || process.pid }); } catch {}
|
|
3321
|
+
}
|
|
3322
|
+
invalidateContextStatusCache();
|
|
3323
|
+
return true;
|
|
3324
|
+
},
|
|
3325
|
+
async compact(options = {}) {
|
|
3326
|
+
if (!session?.id) return null;
|
|
3327
|
+
const result = await mgr.compactSessionMessages(session.id);
|
|
3328
|
+
session = mgr.getSession(session.id) || session;
|
|
3329
|
+
if (options.recoverBridge === true) {
|
|
3330
|
+
try { bridge.recoverWorkers?.({ clientHostPid: session?.clientHostPid || process.pid }); } catch {}
|
|
3331
|
+
}
|
|
3332
|
+
invalidateContextStatusCache();
|
|
3333
|
+
return result;
|
|
3334
|
+
},
|
|
3335
|
+
async setToolMode(nextMode) {
|
|
3336
|
+
mode = normalizeToolMode(nextMode);
|
|
3337
|
+
invalidatePreSessionToolSurface();
|
|
3338
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-mode-switch');
|
|
3339
|
+
await recreateCurrentSessionIfReady();
|
|
3340
|
+
return mode;
|
|
3341
|
+
},
|
|
3342
|
+
setBridgeMode(nextMode) {
|
|
3343
|
+
const applied = bridge.setDefaultMode(nextMode);
|
|
3344
|
+
try { sharedCfgMod.updateSection('agent', (s) => ({ ...(s || {}), bridgeMode: applied })); } catch {}
|
|
3345
|
+
return applied;
|
|
3346
|
+
},
|
|
3347
|
+
toggleBridgeMode() {
|
|
3348
|
+
const applied = bridge.toggleDefaultMode();
|
|
3349
|
+
try { sharedCfgMod.updateSection('agent', (s) => ({ ...(s || {}), bridgeMode: applied })); } catch {}
|
|
3350
|
+
return applied;
|
|
3351
|
+
},
|
|
3352
|
+
bridgeStatus() {
|
|
3353
|
+
return bridgeStatusState();
|
|
3354
|
+
},
|
|
3355
|
+
get clientHostPid() {
|
|
3356
|
+
return session?.clientHostPid || process.pid;
|
|
3357
|
+
},
|
|
3358
|
+
bridgeControl(args = {}) {
|
|
3359
|
+
const callerSessionId = session?.id || null;
|
|
3360
|
+
return bridge.execute(args, {
|
|
3361
|
+
callerCwd: currentCwd,
|
|
3362
|
+
invocationSource: 'user-command',
|
|
3363
|
+
callerSessionId,
|
|
3364
|
+
clientHostPid: session?.clientHostPid || process.pid,
|
|
3365
|
+
notifyFn: notifyFnForSession(callerSessionId),
|
|
3366
|
+
});
|
|
3367
|
+
},
|
|
3368
|
+
onNotification(listener) {
|
|
3369
|
+
if (typeof listener !== 'function') return () => {};
|
|
3370
|
+
notificationListeners.add(listener);
|
|
3371
|
+
return () => notificationListeners.delete(listener);
|
|
3372
|
+
},
|
|
3373
|
+
toolsStatus(query = '') {
|
|
3374
|
+
const surface = activeToolSurface();
|
|
3375
|
+
const catalog = Array.isArray(surface?.deferredToolCatalog)
|
|
3376
|
+
? surface.deferredToolCatalog
|
|
3377
|
+
: (Array.isArray(surface?.tools) ? surface.tools : []);
|
|
3378
|
+
const activeNames = new Set((surface?.tools || []).map((tool) => tool?.name).filter(Boolean));
|
|
3379
|
+
const needle = clean(query).toLowerCase();
|
|
3380
|
+
const rows = catalog.map((tool) => toolRow(tool, activeNames)).filter((row) => row.name);
|
|
3381
|
+
const tools = needle
|
|
3382
|
+
? rows.filter((row) => toolSearchMatches(row, needle))
|
|
3383
|
+
: rows;
|
|
3384
|
+
return {
|
|
3385
|
+
mode,
|
|
3386
|
+
count: rows.length,
|
|
3387
|
+
activeCount: rows.filter((row) => row.active).length,
|
|
3388
|
+
tools,
|
|
3389
|
+
activeTools: sortedNamesByMeasuredUsage(activeNames),
|
|
3390
|
+
};
|
|
3391
|
+
},
|
|
3392
|
+
selectTools(names) {
|
|
3393
|
+
const list = Array.isArray(names) ? names : String(names || '').split(/[,\s]+/);
|
|
3394
|
+
const result = selectDeferredTools(activeToolSurface(), list, mode);
|
|
3395
|
+
return { ...result, status: this.toolsStatus() };
|
|
3396
|
+
},
|
|
3397
|
+
setCwd(path) {
|
|
3398
|
+
applyResolvedCwd(resolveCwdPath(currentCwd, path));
|
|
3399
|
+
return currentCwd;
|
|
3400
|
+
},
|
|
3401
|
+
mcpStatus() {
|
|
3402
|
+
return mcpStatus();
|
|
3403
|
+
},
|
|
3404
|
+
async reconnectMcp() {
|
|
3405
|
+
config = cfgMod.loadConfig();
|
|
3406
|
+
const status = await connectConfiguredMcp({ reset: true });
|
|
3407
|
+
invalidatePreSessionToolSurface();
|
|
3408
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-reconnect');
|
|
3409
|
+
await recreateCurrentSessionIfReady();
|
|
3410
|
+
return status;
|
|
3411
|
+
},
|
|
3412
|
+
async addMcpServer(input = {}) {
|
|
3413
|
+
const { name, config: serverConfig } = normalizeMcpServerInput(input);
|
|
3414
|
+
const nextConfig = cfgMod.loadConfig();
|
|
3415
|
+
nextConfig.mcpServers = {
|
|
3416
|
+
...(nextConfig.mcpServers || {}),
|
|
3417
|
+
[name]: serverConfig,
|
|
3418
|
+
};
|
|
3419
|
+
cfgMod.saveConfig(nextConfig);
|
|
3420
|
+
config = cfgMod.loadConfig();
|
|
3421
|
+
const status = await connectConfiguredMcp({ reset: true });
|
|
3422
|
+
invalidatePreSessionToolSurface();
|
|
3423
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-add');
|
|
3424
|
+
await recreateCurrentSessionIfReady();
|
|
3425
|
+
return { name, status };
|
|
3426
|
+
},
|
|
3427
|
+
async removeMcpServer(name) {
|
|
3428
|
+
const serverName = clean(name);
|
|
3429
|
+
if (!serverName) throw new Error('MCP server name is required');
|
|
3430
|
+
const nextConfig = cfgMod.loadConfig();
|
|
3431
|
+
const current = nextConfig.mcpServers && typeof nextConfig.mcpServers === 'object'
|
|
3432
|
+
? { ...nextConfig.mcpServers }
|
|
3433
|
+
: {};
|
|
3434
|
+
if (!Object.prototype.hasOwnProperty.call(current, serverName)) {
|
|
3435
|
+
throw new Error(`MCP server not configured: ${serverName}`);
|
|
3436
|
+
}
|
|
3437
|
+
delete current[serverName];
|
|
3438
|
+
cfgMod.saveConfig({ ...nextConfig, mcpServers: current });
|
|
3439
|
+
config = cfgMod.loadConfig();
|
|
3440
|
+
const status = await connectConfiguredMcp({ reset: true });
|
|
3441
|
+
invalidatePreSessionToolSurface();
|
|
3442
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-remove');
|
|
3443
|
+
await recreateCurrentSessionIfReady();
|
|
3444
|
+
return status;
|
|
3445
|
+
},
|
|
3446
|
+
async setMcpServerEnabled(name, enabled) {
|
|
3447
|
+
const serverName = clean(name);
|
|
3448
|
+
if (!serverName) throw new Error('MCP server name is required');
|
|
3449
|
+
const nextConfig = cfgMod.loadConfig();
|
|
3450
|
+
const current = nextConfig.mcpServers && typeof nextConfig.mcpServers === 'object'
|
|
3451
|
+
? { ...nextConfig.mcpServers }
|
|
3452
|
+
: {};
|
|
3453
|
+
if (!Object.prototype.hasOwnProperty.call(current, serverName)) {
|
|
3454
|
+
throw new Error(`MCP server not configured: ${serverName}`);
|
|
3455
|
+
}
|
|
3456
|
+
current[serverName] = { ...(current[serverName] || {}), enabled: enabled !== false };
|
|
3457
|
+
cfgMod.saveConfig({ ...nextConfig, mcpServers: current });
|
|
3458
|
+
config = cfgMod.loadConfig();
|
|
3459
|
+
const status = await connectConfiguredMcp({ reset: true });
|
|
3460
|
+
invalidatePreSessionToolSurface();
|
|
3461
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-toggle');
|
|
3462
|
+
await recreateCurrentSessionIfReady();
|
|
3463
|
+
return status;
|
|
3464
|
+
},
|
|
3465
|
+
skillsStatus() {
|
|
3466
|
+
return skillsStatus();
|
|
3467
|
+
},
|
|
3468
|
+
skillContent(name) {
|
|
3469
|
+
return skillContent(name);
|
|
3470
|
+
},
|
|
3471
|
+
async addSkill(input = {}) {
|
|
3472
|
+
const skill = addProjectSkill(input);
|
|
3473
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-skill-add');
|
|
3474
|
+
await recreateCurrentSessionIfReady();
|
|
3475
|
+
return { skill, status: skillsStatus() };
|
|
3476
|
+
},
|
|
3477
|
+
async reloadSkills() {
|
|
3478
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-skills-reload');
|
|
3479
|
+
await recreateCurrentSessionIfReady();
|
|
3480
|
+
return skillsStatus();
|
|
3481
|
+
},
|
|
3482
|
+
pluginsStatus() {
|
|
3483
|
+
return pluginsStatus();
|
|
3484
|
+
},
|
|
3485
|
+
async reloadPlugins() {
|
|
3486
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-plugins-reload');
|
|
3487
|
+
await recreateCurrentSessionIfReady();
|
|
3488
|
+
return pluginsStatus();
|
|
3489
|
+
},
|
|
3490
|
+
async addPlugin(source) {
|
|
3491
|
+
const dataDir = cfgMod.getPluginData?.();
|
|
3492
|
+
const plugin = registryAddPlugin(source, { dataDir });
|
|
3493
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-add');
|
|
3494
|
+
await recreateCurrentSessionIfReady();
|
|
3495
|
+
return { plugin, status: pluginsStatus() };
|
|
3496
|
+
},
|
|
3497
|
+
async updatePlugin(plugin = {}) {
|
|
3498
|
+
const key = clean(plugin.id || plugin.name || plugin);
|
|
3499
|
+
const dataDir = cfgMod.getPluginData?.();
|
|
3500
|
+
const updated = registryUpdatePlugin(key, { dataDir });
|
|
3501
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-update');
|
|
3502
|
+
await recreateCurrentSessionIfReady();
|
|
3503
|
+
return { plugin: updated, status: pluginsStatus() };
|
|
3504
|
+
},
|
|
3505
|
+
async removePlugin(plugin = {}) {
|
|
3506
|
+
const key = clean(plugin.id || plugin.name || plugin);
|
|
3507
|
+
const dataDir = cfgMod.getPluginData?.();
|
|
3508
|
+
const removed = registryRemovePlugin(key, { dataDir });
|
|
3509
|
+
const nextConfig = cfgMod.loadConfig();
|
|
3510
|
+
const serverName = pluginMcpServerName(plugin);
|
|
3511
|
+
if (nextConfig.mcpServers && Object.prototype.hasOwnProperty.call(nextConfig.mcpServers, serverName)) {
|
|
3512
|
+
const current = { ...nextConfig.mcpServers };
|
|
3513
|
+
delete current[serverName];
|
|
3514
|
+
cfgMod.saveConfig({ ...nextConfig, mcpServers: current });
|
|
3515
|
+
config = cfgMod.loadConfig();
|
|
3516
|
+
await connectConfiguredMcp({ reset: true });
|
|
3517
|
+
invalidatePreSessionToolSurface();
|
|
3518
|
+
}
|
|
3519
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-remove');
|
|
3520
|
+
await recreateCurrentSessionIfReady();
|
|
3521
|
+
return { plugin: removed, status: pluginsStatus() };
|
|
3522
|
+
},
|
|
3523
|
+
async enablePluginMcp(plugin = {}) {
|
|
3524
|
+
const root = clean(plugin.root);
|
|
3525
|
+
const script = clean(plugin.mcpScript);
|
|
3526
|
+
if (!root || !script) throw new Error('plugin has no MCP script');
|
|
3527
|
+
const scriptPath = join(root, script);
|
|
3528
|
+
if (!existsSync(scriptPath)) throw new Error(`plugin MCP script not found: ${scriptPath}`);
|
|
3529
|
+
const serverName = pluginMcpServerName(plugin);
|
|
3530
|
+
const nextConfig = cfgMod.loadConfig();
|
|
3531
|
+
nextConfig.mcpServers = {
|
|
3532
|
+
...(nextConfig.mcpServers || {}),
|
|
3533
|
+
[serverName]: {
|
|
3534
|
+
command: 'node',
|
|
3535
|
+
args: [scriptPath],
|
|
3536
|
+
cwd: root,
|
|
3537
|
+
env: {
|
|
3538
|
+
MIXDOG_PLUGIN_ROOT: root,
|
|
3539
|
+
MIXDOG_PLUGIN_DATA: join(cfgMod.getPluginData?.() || STANDALONE_DATA_DIR, 'plugins', 'data', clean(plugin.id || plugin.name || serverName)),
|
|
3540
|
+
},
|
|
3541
|
+
},
|
|
3542
|
+
};
|
|
3543
|
+
cfgMod.saveConfig(nextConfig);
|
|
3544
|
+
config = cfgMod.loadConfig();
|
|
3545
|
+
const status = await connectConfiguredMcp({ reset: true });
|
|
3546
|
+
invalidatePreSessionToolSurface();
|
|
3547
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-mcp-enable');
|
|
3548
|
+
await recreateCurrentSessionIfReady();
|
|
3549
|
+
return { serverName, status };
|
|
3550
|
+
},
|
|
3551
|
+
hooksStatus() {
|
|
3552
|
+
return hooks.status();
|
|
3553
|
+
},
|
|
3554
|
+
addHookRule(rule) {
|
|
3555
|
+
return hooks.addRule(rule);
|
|
3556
|
+
},
|
|
3557
|
+
setHookRuleEnabled(index, enabled) {
|
|
3558
|
+
return hooks.setRuleEnabled(index, enabled);
|
|
3559
|
+
},
|
|
3560
|
+
deleteHookRule(index) {
|
|
3561
|
+
return hooks.deleteRule(index);
|
|
3562
|
+
},
|
|
3563
|
+
async memoryControl(args = {}) {
|
|
3564
|
+
const memoryMod = await getMemoryModule();
|
|
3565
|
+
if (!memoryMod?.handleToolCall) throw new Error('memory runtime is not available');
|
|
3566
|
+
return toolResponseText(await memoryMod.handleToolCall('memory', args || {}));
|
|
3567
|
+
},
|
|
3568
|
+
async recall(query, args = {}) {
|
|
3569
|
+
const baseQuery = query || args?.query || '';
|
|
3570
|
+
if (args?.currentSession !== false && session?.id) {
|
|
3571
|
+
const currentText = currentSessionRecallRows(session, baseQuery, { limit: args?.limit });
|
|
3572
|
+
if (!isEmptyRecallText(currentText)) return currentText;
|
|
3573
|
+
}
|
|
3574
|
+
const memoryMod = await getMemoryModule();
|
|
3575
|
+
if (!memoryMod?.handleToolCall) throw new Error('memory runtime is not available');
|
|
3576
|
+
const baseArgs = {
|
|
3577
|
+
...(args || {}),
|
|
3578
|
+
query: baseQuery,
|
|
3579
|
+
cwd: args?.cwd || currentCwd,
|
|
3580
|
+
};
|
|
3581
|
+
let result = '(no results)';
|
|
3582
|
+
if (session?.id && args?.currentSession !== false && args?.forceCycleOnEmpty !== false) {
|
|
3583
|
+
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
3584
|
+
if (messages.length > 0) {
|
|
3585
|
+
await memoryMod.handleToolCall('memory', {
|
|
3586
|
+
action: 'ingest_session',
|
|
3587
|
+
sessionId: session.id,
|
|
3588
|
+
cwd: currentCwd,
|
|
3589
|
+
messages,
|
|
3590
|
+
});
|
|
3591
|
+
await memoryMod.handleToolCall('memory', {
|
|
3592
|
+
action: 'cycle1',
|
|
3593
|
+
min_batch: 1,
|
|
3594
|
+
session_cap: 1,
|
|
3595
|
+
batch_size: Math.max(1, Math.min(100, messages.length)),
|
|
3596
|
+
});
|
|
3597
|
+
result = toolResponseText(await memoryMod.handleToolCall('recall', {
|
|
3598
|
+
...baseArgs,
|
|
3599
|
+
sessionId: session.id,
|
|
3600
|
+
currentSession: true,
|
|
3601
|
+
projectScope: baseArgs.projectScope || 'all',
|
|
3602
|
+
includeRaw: baseArgs.includeRaw !== false,
|
|
3603
|
+
includeArchived: baseArgs.includeArchived !== false,
|
|
3604
|
+
}));
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
if (isEmptyRecallText(result)) {
|
|
3608
|
+
result = toolResponseText(await memoryMod.handleToolCall('recall', baseArgs));
|
|
3609
|
+
}
|
|
3610
|
+
return result;
|
|
3611
|
+
},
|
|
3612
|
+
async setRoute(next) {
|
|
3613
|
+
const requested = { ...(next || {}) };
|
|
3614
|
+
if (requested.effort === undefined && !requested.provider && !requested.model && hasOwn(route, 'effort')) {
|
|
3615
|
+
requested.effort = route.effort;
|
|
3616
|
+
}
|
|
3617
|
+
if (!requested.provider && requested.model && !findPreset(config, requested.model)) {
|
|
3618
|
+
requested.provider = route.provider;
|
|
3619
|
+
}
|
|
3620
|
+
let selectedRoute = resolveRoute(config, requested);
|
|
3621
|
+
await reg.initProviders(ensureProviderEnabled(config, selectedRoute.provider));
|
|
3622
|
+
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model);
|
|
3623
|
+
const fastCapable = fastCapableFor(selectedRoute.provider, modelMeta);
|
|
3624
|
+
selectedRoute = { ...selectedRoute, fast: fastCapable ? selectedRoute.fast === true : false };
|
|
3625
|
+
config = saveModelSettings(cfgMod, selectedRoute, { fastCapable });
|
|
3626
|
+
const leadRoute = persistLeadRoute(selectedRoute);
|
|
3627
|
+
route = resolveRoute(config, leadRoute
|
|
3628
|
+
? { model: workflowPresetId('lead') }
|
|
3629
|
+
: selectedRoute);
|
|
3630
|
+
await refreshRouteEffort(modelMeta);
|
|
3631
|
+
if (session) {
|
|
3632
|
+
const updated = mgr.updateSessionRoute?.(session.id, {
|
|
3633
|
+
provider: route.provider,
|
|
3634
|
+
model: route.model,
|
|
3635
|
+
fast: route.fast === true,
|
|
3636
|
+
effort: route.effectiveEffort || null,
|
|
3637
|
+
});
|
|
3638
|
+
if (updated) session = updated;
|
|
3639
|
+
else {
|
|
3640
|
+
session.provider = route.provider;
|
|
3641
|
+
session.model = route.model;
|
|
3642
|
+
session.fast = route.fast === true;
|
|
3643
|
+
session.effort = route.effectiveEffort || null;
|
|
3644
|
+
}
|
|
3645
|
+
statusRoutes?.writeGatewaySessionRoute?.(session.id, routeForStatusline(route));
|
|
3646
|
+
invalidateContextStatusCache();
|
|
3647
|
+
}
|
|
3648
|
+
return route;
|
|
3649
|
+
},
|
|
3650
|
+
|
|
3651
|
+
async setFast(value) {
|
|
3652
|
+
const enabled = value === true;
|
|
3653
|
+
const modelMeta = await lookupModelMeta(route.provider, route.model);
|
|
3654
|
+
const fastCapable = fastCapableFor(route.provider, modelMeta);
|
|
3655
|
+
if (enabled && !fastCapable) {
|
|
3656
|
+
throw new Error(`fast mode is not available for ${route.provider}/${route.model}`);
|
|
3657
|
+
}
|
|
3658
|
+
route = resolveRoute(config, { provider: route.provider, model: route.model, effort: route.effort, fast: fastCapable ? enabled : false });
|
|
3659
|
+
config = saveModelSettings(cfgMod, route, { fastCapable });
|
|
3660
|
+
const leadRoute = persistLeadRoute(route);
|
|
3661
|
+
if (leadRoute) route = resolveRoute(config, { model: workflowPresetId('lead') });
|
|
3662
|
+
await refreshRouteEffort(modelMeta);
|
|
3663
|
+
if (session) {
|
|
3664
|
+
session.fast = route.fast === true;
|
|
3665
|
+
session.effort = route.effectiveEffort || null;
|
|
3666
|
+
statusRoutes?.writeGatewaySessionRoute?.(session.id, routeForStatusline(route));
|
|
3667
|
+
invalidateContextStatusCache();
|
|
3668
|
+
}
|
|
3669
|
+
return route.fast === true;
|
|
3670
|
+
},
|
|
3671
|
+
|
|
3672
|
+
async toggleFast() {
|
|
3673
|
+
return await this.setFast(!(route.fast === true));
|
|
3674
|
+
},
|
|
3675
|
+
|
|
3676
|
+
async setEffort(value) {
|
|
3677
|
+
const normalized = normalizeEffortInput(value);
|
|
3678
|
+
route = { ...route, effort: normalized };
|
|
3679
|
+
config = saveModelSettings(cfgMod, route, { fastCapable: route.fastCapable !== false });
|
|
3680
|
+
const leadRoute = persistLeadRoute(route);
|
|
3681
|
+
if (leadRoute) {
|
|
3682
|
+
route = resolveRoute(config, { model: workflowPresetId('lead') });
|
|
3683
|
+
}
|
|
3684
|
+
await refreshRouteEffort();
|
|
3685
|
+
if (session) {
|
|
3686
|
+
session.effort = route.effectiveEffort || null;
|
|
3687
|
+
statusRoutes?.writeGatewaySessionRoute?.(session.id, routeForStatusline(route));
|
|
3688
|
+
invalidateContextStatusCache();
|
|
3689
|
+
}
|
|
3690
|
+
return route;
|
|
3691
|
+
},
|
|
3692
|
+
async close(reason = 'cli-exit', options = {}) {
|
|
3693
|
+
const detach = options?.detach === true || options?.wait === false || options?.waitForExit === false;
|
|
3694
|
+
closeRequested = true;
|
|
3695
|
+
if (channelStartTimer) {
|
|
3696
|
+
clearTimeout(channelStartTimer);
|
|
3697
|
+
channelStartTimer = null;
|
|
3698
|
+
}
|
|
3699
|
+
if (providerWarmupTimer) {
|
|
3700
|
+
clearTimeout(providerWarmupTimer);
|
|
3701
|
+
providerWarmupTimer = null;
|
|
3702
|
+
}
|
|
3703
|
+
if (providerModelWarmupTimer) {
|
|
3704
|
+
clearTimeout(providerModelWarmupTimer);
|
|
3705
|
+
providerModelWarmupTimer = null;
|
|
3706
|
+
}
|
|
3707
|
+
try { cancelBackgroundTasks({ reason, notify: false }); } catch {}
|
|
3708
|
+
const channelStop = channels.stop(reason, detach ? { waitForExit: false } : undefined);
|
|
3709
|
+
try { bridge.closeAll(reason); } catch {}
|
|
3710
|
+
let mcpStop = null;
|
|
3711
|
+
try { mcpStop = mcpClient.disconnectAll?.(); } catch {}
|
|
3712
|
+
const openaiWsStop = globalThis.__mixdogOpenaiWsRuntimeLoaded === true
|
|
3713
|
+
? import('./runtime/agent/orchestrator/providers/openai-oauth-ws.mjs')
|
|
3714
|
+
.then((mod) => mod?.drainOpenaiWsPool?.(reason))
|
|
3715
|
+
.catch(() => {})
|
|
3716
|
+
: null;
|
|
3717
|
+
const patchStop = closePatchRuntimeIfLoaded(detach ? { waitForExit: false } : undefined);
|
|
3718
|
+
let ok = false;
|
|
3719
|
+
if (session?.id) {
|
|
3720
|
+
statusRoutes?.clearGatewaySessionRoute?.(session.id);
|
|
3721
|
+
ok = mgr.closeSession(session.id, reason);
|
|
3722
|
+
session = null;
|
|
3723
|
+
}
|
|
3724
|
+
if (detach) {
|
|
3725
|
+
try { await withTeardownDeadline(channelStop, 300, false); } catch {}
|
|
3726
|
+
for (const stop of [mcpStop, openaiWsStop, patchStop]) {
|
|
3727
|
+
Promise.resolve(stop).catch(() => {});
|
|
3728
|
+
}
|
|
3729
|
+
return ok;
|
|
3730
|
+
}
|
|
3731
|
+
await Promise.allSettled([
|
|
3732
|
+
withTeardownDeadline(channelStop, 5500, false),
|
|
3733
|
+
withTeardownDeadline(mcpStop, 1500, false),
|
|
3734
|
+
withTeardownDeadline(openaiWsStop, 1500, false),
|
|
3735
|
+
withTeardownDeadline(patchStop, 1500, false),
|
|
3736
|
+
]);
|
|
3737
|
+
return ok;
|
|
3738
|
+
},
|
|
3739
|
+
abort(reason = 'cli-abort') {
|
|
3740
|
+
if (!session?.id) return false;
|
|
3741
|
+
return mgr.abortSessionTurn(session.id, reason);
|
|
3742
|
+
},
|
|
3743
|
+
listSessions() {
|
|
3744
|
+
return mgr.listSessions({}).map(s => {
|
|
3745
|
+
const owner = clean(s.owner || 'user').toLowerCase();
|
|
3746
|
+
if (owner && !['cli', 'user', 'mixdog', 'legacy'].includes(owner)) return null;
|
|
3747
|
+
const sourceType = clean(s.sourceType || '').toLowerCase();
|
|
3748
|
+
const sourceName = clean(s.sourceName || '').toLowerCase();
|
|
3749
|
+
const role = clean(s.role || '').toLowerCase();
|
|
3750
|
+
const leadish = role === 'lead'
|
|
3751
|
+
|| sourceType === 'lead'
|
|
3752
|
+
|| (sourceType === 'cli' && (!sourceName || sourceName === 'main'))
|
|
3753
|
+
|| (!sourceType && !sourceName && owner !== 'bridge');
|
|
3754
|
+
if (!leadish) return null;
|
|
3755
|
+
let preview = cleanSessionPreview(s.preview || '');
|
|
3756
|
+
let messageCount = Math.max(0, Number(s.messageCount) || 0);
|
|
3757
|
+
if (!preview && Array.isArray(s.messages)) {
|
|
3758
|
+
const msgs = s.messages || [];
|
|
3759
|
+
const userPreviews = msgs
|
|
3760
|
+
.filter(m => m && m.role === 'user')
|
|
3761
|
+
.map(m => cleanSessionPreview(sessionMessageText(m.content)))
|
|
3762
|
+
.filter(text => !isSessionPreviewNoise(text));
|
|
3763
|
+
preview = userPreviews[userPreviews.length - 1] || userPreviews[0] || '';
|
|
3764
|
+
messageCount = msgs.filter(m => m && (m.role === 'user' || m.role === 'assistant')).length;
|
|
3765
|
+
}
|
|
3766
|
+
if (!preview) return null;
|
|
3767
|
+
return {
|
|
3768
|
+
id: s.id,
|
|
3769
|
+
updatedAt: s.updatedAt,
|
|
3770
|
+
cwd: s.cwd || '',
|
|
3771
|
+
model: s.model,
|
|
3772
|
+
provider: s.provider,
|
|
3773
|
+
messageCount,
|
|
3774
|
+
preview,
|
|
3775
|
+
};
|
|
3776
|
+
}).filter(Boolean);
|
|
3777
|
+
},
|
|
3778
|
+
async newSession() {
|
|
3779
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-new');
|
|
3780
|
+
await createCurrentSession();
|
|
3781
|
+
return session.id;
|
|
3782
|
+
},
|
|
3783
|
+
async resume(id) {
|
|
3784
|
+
const previousId = session?.id || null;
|
|
3785
|
+
const resumed = await mgr.resumeSession(id, toolSpecForMode(mode));
|
|
3786
|
+
if (!resumed) return null;
|
|
3787
|
+
if (previousId && previousId !== resumed.id) {
|
|
3788
|
+
statusRoutes?.clearGatewaySessionRoute?.(previousId);
|
|
3789
|
+
mgr.closeSession(previousId, 'cli-resume');
|
|
3790
|
+
}
|
|
3791
|
+
session = resumed;
|
|
3792
|
+
currentCwd = resumed.cwd || currentCwd;
|
|
3793
|
+
applyResolvedCwd(currentCwd, { markRefresh: false });
|
|
3794
|
+
const resumeEffort = hasOwn(route, 'effort') ? route.effort : resumed.effort;
|
|
3795
|
+
route = resolveRoute(config, { provider: resumed.provider, model: resumed.model, effort: resumeEffort });
|
|
3796
|
+
await refreshRouteEffort();
|
|
3797
|
+
session.effort = route.effectiveEffort || null;
|
|
3798
|
+
session.cwd = currentCwd;
|
|
3799
|
+
applyDeferredToolSurface(session, mode, standaloneTools);
|
|
3800
|
+
invalidatePreSessionToolSurface();
|
|
3801
|
+
invalidateContextStatusCache();
|
|
3802
|
+
sessionNeedsCwdRefresh = false;
|
|
3803
|
+
statusRoutes?.writeGatewaySessionRoute?.(session.id, routeForStatusline(route));
|
|
3804
|
+
return {
|
|
3805
|
+
id: resumed.id,
|
|
3806
|
+
messages: resumed.messages || [],
|
|
3807
|
+
cwd: currentCwd,
|
|
3808
|
+
provider: resumed.provider,
|
|
3809
|
+
model: resumed.model,
|
|
3810
|
+
};
|
|
3811
|
+
},
|
|
3812
|
+
};
|
|
3813
|
+
}
|