mixdog 0.7.18 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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 +456 -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 +3704 -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 +322 -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/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 +2320 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +2960 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +663 -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/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 +124 -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 +3600 -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 +314 -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 +950 -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 +5438 -0
- package/src/tui/components/AnsiText.jsx +199 -0
- package/src/tui/components/ContextPanel.jsx +217 -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 +772 -0
- package/src/tui/components/TurnDone.jsx +78 -0
- package/src/tui/components/UsagePanel.jsx +331 -0
- package/src/tui/dist/index.mjs +12359 -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 +101 -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/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/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/post-edit-marks.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/advisory-lock.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,2410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine.mjs - the engine<->React bridge (React-free).
|
|
3
|
+
*
|
|
4
|
+
* Runs mixdog's session manager outside React and exposes a tiny subscribable
|
|
5
|
+
* store. The React/ink layer consumes it via useSyncExternalStore
|
|
6
|
+
* (see hooks/useEngine.mjs).
|
|
7
|
+
*/
|
|
8
|
+
import { performance } from 'node:perf_hooks';
|
|
9
|
+
import { SPINNER_VERBS } from './spinner-verbs.mjs';
|
|
10
|
+
import {
|
|
11
|
+
classifyToolCategory,
|
|
12
|
+
formatAggregateDetail,
|
|
13
|
+
summarizeToolResult,
|
|
14
|
+
} from '../runtime/shared/tool-surface.mjs';
|
|
15
|
+
import { presentErrorText } from '../runtime/shared/err-text.mjs';
|
|
16
|
+
|
|
17
|
+
const BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ''));
|
|
18
|
+
const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
|
|
19
|
+
|
|
20
|
+
function bootProfile(event, fields = {}) {
|
|
21
|
+
if (!BOOT_PROFILE_ENABLED) return;
|
|
22
|
+
const elapsedMs = performance.now() - BOOT_PROFILE_START;
|
|
23
|
+
const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
|
|
24
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
25
|
+
if (value === undefined || value === null || value === '') continue;
|
|
26
|
+
parts.push(`${key}=${String(value).replace(/\s+/g, '_')}`);
|
|
27
|
+
}
|
|
28
|
+
try { process.stderr.write(`${parts.join(' ')}\n`); } catch {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Session-usage accumulator - inlined (not imported from ui/statusline.mjs) so
|
|
32
|
+
// engine.mjs has no static dependency on the vendored statusline closure.
|
|
33
|
+
function createSessionStats() {
|
|
34
|
+
return {
|
|
35
|
+
inputTokens: 0,
|
|
36
|
+
outputTokens: 0,
|
|
37
|
+
cachedTokens: 0,
|
|
38
|
+
cacheWriteTokens: 0,
|
|
39
|
+
promptTokens: 0,
|
|
40
|
+
latestInputTokens: 0,
|
|
41
|
+
latestOutputTokens: 0,
|
|
42
|
+
latestCachedTokens: 0,
|
|
43
|
+
latestCacheWriteTokens: 0,
|
|
44
|
+
latestPromptTokens: 0,
|
|
45
|
+
currentContextTokens: 0,
|
|
46
|
+
costUsd: 0,
|
|
47
|
+
turns: 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function num(v) { const n = Number(v); return Number.isFinite(n) ? n : 0; }
|
|
51
|
+
function applyUsageDelta(stats, delta = {}) {
|
|
52
|
+
if (!stats || !delta) return stats;
|
|
53
|
+
const inputTokens = num(delta.deltaInput);
|
|
54
|
+
const outputTokens = num(delta.deltaOutput);
|
|
55
|
+
const cachedTokens = num(delta.deltaCachedRead);
|
|
56
|
+
const cacheWriteTokens = num(delta.deltaCacheWrite);
|
|
57
|
+
const promptTokens = num(delta.deltaPrompt);
|
|
58
|
+
stats.inputTokens += inputTokens;
|
|
59
|
+
stats.outputTokens += outputTokens;
|
|
60
|
+
stats.cachedTokens += cachedTokens;
|
|
61
|
+
stats.cacheWriteTokens += cacheWriteTokens;
|
|
62
|
+
stats.promptTokens += promptTokens;
|
|
63
|
+
stats.latestInputTokens = inputTokens;
|
|
64
|
+
stats.latestOutputTokens = outputTokens;
|
|
65
|
+
stats.latestCachedTokens = cachedTokens;
|
|
66
|
+
stats.latestCacheWriteTokens = cacheWriteTokens;
|
|
67
|
+
stats.latestPromptTokens = promptTokens;
|
|
68
|
+
stats.costUsd += num(delta.costUsd);
|
|
69
|
+
return stats;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Source tests resolve from src/tui/engine.mjs; the built bundle resolves from
|
|
73
|
+
// src/tui/dist/index.mjs.
|
|
74
|
+
const SESSION_RUNTIME_MODULE = import.meta.url.replace(/\\/g, '/').includes('/tui/dist/')
|
|
75
|
+
? '../../mixdog-session-runtime.mjs'
|
|
76
|
+
: '../mixdog-session-runtime.mjs';
|
|
77
|
+
|
|
78
|
+
let _idSeq = 0;
|
|
79
|
+
const nextId = () => `it_${++_idSeq}`;
|
|
80
|
+
|
|
81
|
+
function pickVerb(turn) {
|
|
82
|
+
return SPINNER_VERBS[(turn * 7 + 3) % SPINNER_VERBS.length];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const TURN_DONE_VERBS = [
|
|
86
|
+
'Thought',
|
|
87
|
+
'Reasoned',
|
|
88
|
+
'Mapped',
|
|
89
|
+
'Checked',
|
|
90
|
+
'Solved',
|
|
91
|
+
'Composed',
|
|
92
|
+
'Synthesized',
|
|
93
|
+
'Wrapped',
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
function pickDoneVerb(turn) {
|
|
97
|
+
return TURN_DONE_VERBS[(turn * 5 + 2) % TURN_DONE_VERBS.length];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatIdleDuration(ms) {
|
|
101
|
+
const value = Math.max(0, Number(ms) || 0);
|
|
102
|
+
if (value >= 3_600_000 && value % 3_600_000 === 0) return `${value / 3_600_000}h`;
|
|
103
|
+
if (value >= 60_000) return `${Math.round(value / 60_000)}m`;
|
|
104
|
+
if (value < 1_000) return '';
|
|
105
|
+
return `${Math.floor(value / 1000)}s`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function formatTokenCount(value) {
|
|
109
|
+
const n = Number(value || 0);
|
|
110
|
+
if (!Number.isFinite(n) || n <= 0) return '0';
|
|
111
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}m`;
|
|
112
|
+
if (n >= 10_000) return `${Math.round(n / 1000)}k`;
|
|
113
|
+
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
|
|
114
|
+
return `${Math.round(n)}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const FAILED_NOTICE_ACTIONS = new Map([
|
|
118
|
+
['api key save', 'save API key'],
|
|
119
|
+
['auth-forget', 'forget auth'],
|
|
120
|
+
['auto-clear', 'update auto-clear'],
|
|
121
|
+
['autoclear', 'update auto-clear'],
|
|
122
|
+
['bridge', 'run bridge command'],
|
|
123
|
+
['channels', 'load channels'],
|
|
124
|
+
['channels update', 'update channels'],
|
|
125
|
+
['clear', 'clear chat'],
|
|
126
|
+
['compact', 'compact context'],
|
|
127
|
+
['copy', 'copy'],
|
|
128
|
+
['core memory', 'load core memory'],
|
|
129
|
+
['cwd', 'update working directory'],
|
|
130
|
+
['effort switch', 'switch effort'],
|
|
131
|
+
['fast', 'update fast mode'],
|
|
132
|
+
['hook rule update', 'update hook rule'],
|
|
133
|
+
['hook toggle', 'toggle hook'],
|
|
134
|
+
['hook update', 'update hook'],
|
|
135
|
+
['hooks status', 'load hooks'],
|
|
136
|
+
['local provider update', 'update local provider'],
|
|
137
|
+
['mcp add', 'add MCP server'],
|
|
138
|
+
['mcp reconnect', 'reconnect MCP server'],
|
|
139
|
+
['mcp status', 'load MCP status'],
|
|
140
|
+
['mcp toggle', 'toggle MCP server'],
|
|
141
|
+
['memory', 'run memory command'],
|
|
142
|
+
['memory status', 'load memory status'],
|
|
143
|
+
['model save', 'save model'],
|
|
144
|
+
['model switch', 'switch model'],
|
|
145
|
+
['oauth code', 'finish OAuth login'],
|
|
146
|
+
['oauth login', 'start OAuth login'],
|
|
147
|
+
['output style switch', 'switch output style'],
|
|
148
|
+
['OpenAI usage auth save', 'save OpenAI usage auth'],
|
|
149
|
+
['OpenCode Go usage auth save', 'save OpenCode Go usage auth'],
|
|
150
|
+
['plugin add', 'add plugin'],
|
|
151
|
+
['plugin MCP enable', 'enable plugin MCP'],
|
|
152
|
+
['plugin uninstall', 'uninstall plugin'],
|
|
153
|
+
['plugin update', 'update plugin'],
|
|
154
|
+
['plugins status', 'load plugins'],
|
|
155
|
+
['providers', 'load providers'],
|
|
156
|
+
['recall', 'run recall'],
|
|
157
|
+
['resume', 'resume chat'],
|
|
158
|
+
['schedule toggle', 'toggle schedule'],
|
|
159
|
+
['setup save', 'save setup'],
|
|
160
|
+
['settings update', 'update settings'],
|
|
161
|
+
['skill add', 'add skill'],
|
|
162
|
+
['skills status', 'load skills'],
|
|
163
|
+
['tools status', 'load tool status'],
|
|
164
|
+
['usage', 'load usage'],
|
|
165
|
+
['webhook toggle', 'toggle webhook'],
|
|
166
|
+
['workflow switch', 'switch workflow'],
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
function polishNoticeAction(action) {
|
|
170
|
+
const value = String(action || '').trim();
|
|
171
|
+
if (!value) return 'finish';
|
|
172
|
+
const key = value.toLowerCase();
|
|
173
|
+
for (const [candidate, replacement] of FAILED_NOTICE_ACTIONS.entries()) {
|
|
174
|
+
if (candidate.toLowerCase() === key) return replacement;
|
|
175
|
+
}
|
|
176
|
+
const suffixes = [
|
|
177
|
+
[' save', 'save'],
|
|
178
|
+
[' switch', 'switch'],
|
|
179
|
+
[' update', 'update'],
|
|
180
|
+
[' toggle', 'toggle'],
|
|
181
|
+
[' reconnect', 'reconnect'],
|
|
182
|
+
[' enable', 'enable'],
|
|
183
|
+
[' uninstall', 'uninstall'],
|
|
184
|
+
[' add', 'add'],
|
|
185
|
+
];
|
|
186
|
+
for (const [suffix, verb] of suffixes) {
|
|
187
|
+
if (!key.endsWith(suffix)) continue;
|
|
188
|
+
const subject = value.slice(0, -suffix.length).trim();
|
|
189
|
+
return subject ? `${verb} ${subject}` : verb;
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function sentenceStart(text) {
|
|
195
|
+
const value = String(text || '').trim();
|
|
196
|
+
return value ? `${value[0].toUpperCase()}${value.slice(1)}` : value;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function polishNoticeText(text) {
|
|
200
|
+
let value = String(text ?? '').trim().replace(/^✓\s*/, '');
|
|
201
|
+
if (!value) return '';
|
|
202
|
+
const error = /^error\s*:\s*(.+)$/i.exec(value);
|
|
203
|
+
if (error?.[1]) value = error[1].trim();
|
|
204
|
+
const couldNot = /^could not\s+(.+?)(?::\s*(.+))?$/i.exec(value);
|
|
205
|
+
if (couldNot) {
|
|
206
|
+
return couldNot[2]
|
|
207
|
+
? `Couldn’t ${couldNot[1]}: ${couldNot[2]}`
|
|
208
|
+
: `Couldn’t ${couldNot[1]}.`;
|
|
209
|
+
}
|
|
210
|
+
const failed = /^(.+?)\s+failed(?::\s*(.+))?$/i.exec(value);
|
|
211
|
+
if (failed) {
|
|
212
|
+
const action = polishNoticeAction(failed[1]);
|
|
213
|
+
return failed[2] ? `Couldn’t ${action}: ${failed[2]}` : `Couldn’t ${action}.`;
|
|
214
|
+
}
|
|
215
|
+
const busy = /^(.+?)\s+already in progress\.?$/i.exec(value);
|
|
216
|
+
if (busy) return `${sentenceStart(polishNoticeAction(busy[1]))} is already running.`;
|
|
217
|
+
const required = /^(.+?)\s+is required(?:\s+for\s+(.+))?\.?$/i.exec(value);
|
|
218
|
+
if (required) {
|
|
219
|
+
const subject = required[1].trim();
|
|
220
|
+
const target = required[2]?.trim();
|
|
221
|
+
return `${subject}${target ? ` required for ${target}` : ' required'}.`;
|
|
222
|
+
}
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function toolResultText(content) {
|
|
227
|
+
if (content == null) return '';
|
|
228
|
+
if (typeof content === 'string') return content;
|
|
229
|
+
const parts = Array.isArray(content)
|
|
230
|
+
? content
|
|
231
|
+
: (content && typeof content === 'object' && Array.isArray(content.content) ? content.content : null);
|
|
232
|
+
if (parts) {
|
|
233
|
+
return parts.map((c) => {
|
|
234
|
+
if (typeof c === 'string') return c;
|
|
235
|
+
if (c?.type === 'image') return `[image: ${c.mimeType || c.mediaType || c.source?.media_type || 'image'}]`;
|
|
236
|
+
return c?.text ?? '';
|
|
237
|
+
}).filter(Boolean).join('\n');
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(content)) {
|
|
240
|
+
return content.map((c) => (typeof c === 'string' ? c : c?.text ?? '')).filter(Boolean).join('\n');
|
|
241
|
+
}
|
|
242
|
+
if (typeof content === 'object' && typeof content.text === 'string') return content.text;
|
|
243
|
+
try { return JSON.stringify(content); } catch { return String(content); }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function toolErrorDisplay(value, surface = 'tool') {
|
|
247
|
+
const text = presentErrorText(value, { surface });
|
|
248
|
+
if (/^(?:Search failed|Fetch failed|No first response|The .+ went stale|(?:Web search agent|Agent|Tool) (?:stopped|was cancelled))/i.test(text)) {
|
|
249
|
+
return text;
|
|
250
|
+
}
|
|
251
|
+
return /^error\s*:/i.test(text) ? text : `Error: ${text}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function toolCallId(call) {
|
|
255
|
+
return call?.id ?? call?.toolCallId ?? call?.tool_call_id ?? call?.call_id;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function toolResultCallId(message) {
|
|
259
|
+
return message?.toolCallId
|
|
260
|
+
?? message?.tool_call_id
|
|
261
|
+
?? message?.tool_use_id
|
|
262
|
+
?? message?.call_id
|
|
263
|
+
?? message?.id;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function toolCallName(call) {
|
|
267
|
+
return call?.name ?? call?.function?.name ?? call?.toolName ?? call?.tool_name ?? 'tool';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function toolCallArgs(call) {
|
|
271
|
+
return call?.arguments ?? call?.args ?? call?.input ?? call?.function?.arguments;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function textBetweenTag(text, tag) {
|
|
275
|
+
const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
|
|
276
|
+
const match = re.exec(String(text ?? ''));
|
|
277
|
+
return match ? match[1].trim() : '';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function stripSyntheticAgentTags(text) {
|
|
281
|
+
const value = String(text ?? '').trim();
|
|
282
|
+
const finalAnswer = textBetweenTag(value, 'final-answer');
|
|
283
|
+
if (finalAnswer) return finalAnswer;
|
|
284
|
+
const taskResult = textBetweenTag(value, 'result');
|
|
285
|
+
if (taskResult) return taskResult;
|
|
286
|
+
return value
|
|
287
|
+
.replace(/^bridge result[^\n]*(?:\n|$)/i, '')
|
|
288
|
+
.replace(/<\/?(?:final-answer|task-notification|task-id|tool-use-id|output-file|result|status|summary|usage|total_tokens|tool_uses|duration_ms|worktree|worktreePath|worktreeBranch)[^>]*>/gi, '')
|
|
289
|
+
.trim();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function splitBridgeEnvelope(text) {
|
|
293
|
+
const value = String(text ?? '').trim();
|
|
294
|
+
if (!value) return { head: '', body: '' };
|
|
295
|
+
const match = /\n\s*\n/.exec(value);
|
|
296
|
+
if (!match) return { head: value, body: '' };
|
|
297
|
+
return {
|
|
298
|
+
head: value.slice(0, match.index).trim(),
|
|
299
|
+
body: value.slice(match.index + match[0].length).trim(),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function bridgeJobStatusText(parsed) {
|
|
304
|
+
if (!parsed) return '';
|
|
305
|
+
const parts = [];
|
|
306
|
+
if (parsed.status) parts.push(`status: ${parsed.status}`);
|
|
307
|
+
if (parsed.taskId) parts.push(`task_id: ${parsed.taskId}`);
|
|
308
|
+
return parts.join(' · ');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function bridgeJobResultText(text, parsed = parseBridgeJob(text)) {
|
|
312
|
+
const value = String(text ?? '').trim();
|
|
313
|
+
if (!value) return '';
|
|
314
|
+
if (parsed?.taskId) {
|
|
315
|
+
const { body } = splitBridgeEnvelope(value);
|
|
316
|
+
const cleanBody = stripSyntheticAgentTags(body);
|
|
317
|
+
if (cleanBody) return cleanBody;
|
|
318
|
+
return bridgeJobStatusText(parsed);
|
|
319
|
+
}
|
|
320
|
+
return stripSyntheticAgentTags(value) || value;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function parseBackgroundTaskEnvelope(text) {
|
|
324
|
+
const value = String(text ?? '').trim();
|
|
325
|
+
if (!/^background task\b/i.test(value)) return null;
|
|
326
|
+
const allLines = value.split('\n');
|
|
327
|
+
const rest = allLines.slice(1);
|
|
328
|
+
const blank = rest.findIndex((line) => !line.trim());
|
|
329
|
+
const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
|
|
330
|
+
const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
|
|
331
|
+
const fields = {};
|
|
332
|
+
for (const line of headLines) {
|
|
333
|
+
const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
|
|
334
|
+
if (match) fields[match[1].toLowerCase()] = match[2].trim();
|
|
335
|
+
}
|
|
336
|
+
const surface = String(fields.surface || fields.operation || 'task').toLowerCase();
|
|
337
|
+
const name = surface === 'explore' || surface === 'search' || surface === 'shell' ? surface : 'task';
|
|
338
|
+
const status = String(fields.status || '').toLowerCase();
|
|
339
|
+
const taskId = fields.task_id || fields.taskid || '';
|
|
340
|
+
return {
|
|
341
|
+
name,
|
|
342
|
+
label: status || 'notification',
|
|
343
|
+
args: {
|
|
344
|
+
type: body ? 'result' : (fields.operation || 'status'),
|
|
345
|
+
status,
|
|
346
|
+
task_id: taskId || undefined,
|
|
347
|
+
surface,
|
|
348
|
+
label: fields.label || undefined,
|
|
349
|
+
},
|
|
350
|
+
result: body || [status ? `status: ${status}` : '', taskId ? `task_id: ${taskId}` : ''].filter(Boolean).join(' · ') || 'background task',
|
|
351
|
+
isError: /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status) || /^error:/i.test(body),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function bracketField(text, name) {
|
|
356
|
+
const re = new RegExp(`^\\[${name}:\\s*([^\\]]*)\\]`, 'mi');
|
|
357
|
+
return re.exec(String(text ?? ''))?.[1]?.trim() || '';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function parseSyntheticAgentMessage(text) {
|
|
361
|
+
const value = String(text ?? '').trim();
|
|
362
|
+
if (!value) return null;
|
|
363
|
+
const finalAnswer = textBetweenTag(value, 'final-answer');
|
|
364
|
+
if (finalAnswer) {
|
|
365
|
+
return {
|
|
366
|
+
name: 'bridge',
|
|
367
|
+
label: 'final',
|
|
368
|
+
args: { type: 'read', description: 'agent result' },
|
|
369
|
+
result: finalAnswer,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const backgroundTask = parseBackgroundTaskEnvelope(value);
|
|
373
|
+
if (backgroundTask) return backgroundTask;
|
|
374
|
+
const shellTaskId = bracketField(value, 'task_id');
|
|
375
|
+
if (shellTaskId) {
|
|
376
|
+
const status = bracketField(value, 'status') || 'done';
|
|
377
|
+
const exit = bracketField(value, 'exit');
|
|
378
|
+
const command = bracketField(value, 'command');
|
|
379
|
+
return {
|
|
380
|
+
name: 'shell',
|
|
381
|
+
label: status,
|
|
382
|
+
args: { type: 'result', task_id: shellTaskId, command },
|
|
383
|
+
result: value,
|
|
384
|
+
isError: /^(failed|error|timeout|cancelled|killed)$/i.test(status) || (exit && exit !== '0' && exit !== 'n/a'),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const bridgeJob = parseBridgeJob(value);
|
|
388
|
+
if (bridgeJob?.taskId) {
|
|
389
|
+
const label = bridgeJob.status || 'notification';
|
|
390
|
+
const result = bridgeJobResultText(value, bridgeJob);
|
|
391
|
+
return {
|
|
392
|
+
name: 'bridge',
|
|
393
|
+
label,
|
|
394
|
+
args: bridgeArgsWithResultMetadata({ type: bridgeJob.type || 'notification', description: 'agent notification' }, bridgeJob),
|
|
395
|
+
result: result || bridgeJobStatusText(bridgeJob) || 'agent notification',
|
|
396
|
+
isError: /^(failed|error|timeout|cancelled|killed)$/i.test(label),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (/<task-notification\b/i.test(value)) {
|
|
400
|
+
const status = textBetweenTag(value, 'status') || 'completed';
|
|
401
|
+
const summary = textBetweenTag(value, 'summary') || `Agent ${status}`;
|
|
402
|
+
const taskId = textBetweenTag(value, 'task-id');
|
|
403
|
+
const result = stripSyntheticAgentTags(value);
|
|
404
|
+
return {
|
|
405
|
+
name: 'bridge',
|
|
406
|
+
label: status,
|
|
407
|
+
taskId,
|
|
408
|
+
summary,
|
|
409
|
+
result: result || summary,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function normalizeToolName(name) {
|
|
416
|
+
return String(name || 'tool')
|
|
417
|
+
.replace(/^mcp__.*__/, '')
|
|
418
|
+
.replace(/^functions\./, '')
|
|
419
|
+
.replace(/-/g, '_')
|
|
420
|
+
.toLowerCase();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function parseToolArgs(args) {
|
|
424
|
+
if (!args) return {};
|
|
425
|
+
if (typeof args === 'string') {
|
|
426
|
+
try {
|
|
427
|
+
const parsed = JSON.parse(args);
|
|
428
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
429
|
+
} catch {
|
|
430
|
+
return {};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return typeof args === 'object' ? args : {};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const yieldToRenderer = () => new Promise((resolve) => setImmediate(resolve));
|
|
437
|
+
|
|
438
|
+
function parseBridgeJob(text) {
|
|
439
|
+
const value = String(text || '');
|
|
440
|
+
const idMatch = /^bridge task:\s*([^\s]+)/m.exec(value) || /^task_id:\s*([^\s]+)/m.exec(value);
|
|
441
|
+
if (!idMatch) return null;
|
|
442
|
+
const statusMatch = /^status:\s*([^\s(]+)/m.exec(value);
|
|
443
|
+
const typeMatch = /^type:\s*(.+)$/m.exec(value);
|
|
444
|
+
const targetMatch = /^target:\s*(.+)$/m.exec(value);
|
|
445
|
+
const roleMatch = /^(?:agent|role):\s*(.+)$/m.exec(value);
|
|
446
|
+
const presetMatch = /^preset:\s*(.+)$/m.exec(value);
|
|
447
|
+
const modelMatch = /^model:\s*([^/\s]+)\/(.+)$/m.exec(value);
|
|
448
|
+
const effortMatch = /^effort:\s*(.+)$/m.exec(value);
|
|
449
|
+
const fastMatch = /^fast:\s*(on|off|true|false)$/m.exec(value);
|
|
450
|
+
return {
|
|
451
|
+
taskId: idMatch[1],
|
|
452
|
+
status: (statusMatch?.[1] || '').toLowerCase(),
|
|
453
|
+
type: (typeMatch?.[1] || '').trim(),
|
|
454
|
+
target: (targetMatch?.[1] || '').trim(),
|
|
455
|
+
role: (roleMatch?.[1] || '').trim(),
|
|
456
|
+
preset: (presetMatch?.[1] || '').trim(),
|
|
457
|
+
provider: (modelMatch?.[1] || '').trim(),
|
|
458
|
+
model: (modelMatch?.[2] || '').trim(),
|
|
459
|
+
effort: (effortMatch?.[1] || '').trim(),
|
|
460
|
+
fast: fastMatch ? /^(on|true)$/i.test(fastMatch[1]) : undefined,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const QUEUE_PRIORITY = { now: 0, next: 1, later: 2 };
|
|
465
|
+
|
|
466
|
+
function queuePriorityValue(value) {
|
|
467
|
+
return QUEUE_PRIORITY[String(value || 'next')] ?? QUEUE_PRIORITY.next;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function defaultQueuePriority(mode) {
|
|
471
|
+
return mode === 'task-notification' ? 'next' : 'later';
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function isQueuedEntryEditable(entry) {
|
|
475
|
+
return (entry?.mode || 'prompt') !== 'task-notification';
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function firstQueueLine(text) {
|
|
479
|
+
return String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function notificationDisplayText(text) {
|
|
483
|
+
const parsed = parseBridgeJob(text);
|
|
484
|
+
const result = bridgeJobResultText(text, parsed);
|
|
485
|
+
const synthetic = parseSyntheticAgentMessage(text);
|
|
486
|
+
return firstQueueLine(synthetic?.result || result || text) || 'agent notification';
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function promptContentText(content) {
|
|
490
|
+
if (typeof content === 'string') return content;
|
|
491
|
+
if (Array.isArray(content)) {
|
|
492
|
+
return content.map((part) => {
|
|
493
|
+
if (typeof part === 'string') return part;
|
|
494
|
+
if (part?.type === 'text') return part.text || '';
|
|
495
|
+
if (part?.type === 'image') return '[Image]';
|
|
496
|
+
return part?.text || '';
|
|
497
|
+
}).filter(Boolean).join('\n');
|
|
498
|
+
}
|
|
499
|
+
return String(content ?? '');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function promptDisplayText(content, options = {}) {
|
|
503
|
+
if (typeof options.displayText === 'string') return options.displayText;
|
|
504
|
+
return promptContentText(content);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function mergePromptContents(entries) {
|
|
508
|
+
const batch = Array.isArray(entries) ? entries : [];
|
|
509
|
+
if (batch.every((entry) => typeof entry?.content === 'string')) {
|
|
510
|
+
return batch.map((entry) => entry.content).filter((text) => String(text || '').trim()).join('\n');
|
|
511
|
+
}
|
|
512
|
+
const parts = [];
|
|
513
|
+
for (const entry of batch) {
|
|
514
|
+
const content = entry?.content;
|
|
515
|
+
if (typeof content === 'string') {
|
|
516
|
+
if (content.trim()) parts.push({ type: 'text', text: content });
|
|
517
|
+
} else if (Array.isArray(content)) {
|
|
518
|
+
parts.push(...content);
|
|
519
|
+
}
|
|
520
|
+
parts.push({ type: 'text', text: '\n' });
|
|
521
|
+
}
|
|
522
|
+
while (parts.length && parts[parts.length - 1]?.type === 'text' && parts[parts.length - 1]?.text === '\n') parts.pop();
|
|
523
|
+
return parts.length === 1 && parts[0]?.type === 'text' ? parts[0].text : parts;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function mergePastedImages(entries) {
|
|
527
|
+
const out = {};
|
|
528
|
+
for (const entry of entries || []) {
|
|
529
|
+
const images = entry?.pastedImages;
|
|
530
|
+
if (!images || typeof images !== 'object') continue;
|
|
531
|
+
for (const [id, image] of Object.entries(images)) {
|
|
532
|
+
if (image) out[id] = image;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function callCommitCallbacks(entries) {
|
|
539
|
+
for (const entry of entries || []) {
|
|
540
|
+
try { entry?.onCommitted?.(); } catch {}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function notificationQueueKey(event, text, parsed) {
|
|
545
|
+
const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
|
|
546
|
+
const id = String(meta.execution_id || parsed?.taskId || '').trim();
|
|
547
|
+
if (!id) return '';
|
|
548
|
+
const type = String(meta.type || '').trim();
|
|
549
|
+
const status = String(meta.status || parsed?.status || '').trim();
|
|
550
|
+
const fallbackKind = String(text || '').split('\n', 1)[0]?.trim() || 'notification';
|
|
551
|
+
return [id, type || fallbackKind, status].filter(Boolean).join(':');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function bridgeArgsWithResultMetadata(args, parsed) {
|
|
555
|
+
if (!parsed) return args;
|
|
556
|
+
const next = { ...(args && typeof args === 'object' ? args : {}) };
|
|
557
|
+
if (parsed.type) next.type = parsed.type;
|
|
558
|
+
if (parsed.status) next.status = parsed.status;
|
|
559
|
+
if (parsed.taskId) next.task_id = parsed.taskId;
|
|
560
|
+
if (parsed.role) next.role = parsed.role;
|
|
561
|
+
if (parsed.preset) next.preset = parsed.preset;
|
|
562
|
+
if (parsed.provider) next.provider = parsed.provider;
|
|
563
|
+
if (parsed.model) next.model = parsed.model;
|
|
564
|
+
if (parsed.effort) next.effort = parsed.effort;
|
|
565
|
+
if (parsed.fast !== undefined) next.fast = parsed.fast;
|
|
566
|
+
if (!next.tag && parsed.target) {
|
|
567
|
+
const target = parsed.target.split(/\s+/)[0];
|
|
568
|
+
if (target && !target.startsWith('sess_')) next.tag = target;
|
|
569
|
+
}
|
|
570
|
+
return next;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export async function createEngineSession({
|
|
574
|
+
provider: providerName,
|
|
575
|
+
model,
|
|
576
|
+
toolMode = 'full',
|
|
577
|
+
} = {}) {
|
|
578
|
+
const startedAt = performance.now();
|
|
579
|
+
bootProfile('engine:create:start', { provider: providerName, model, toolMode });
|
|
580
|
+
// Silence provider/session diagnostics so they cannot tear through the
|
|
581
|
+
// alternate-screen React/ink render.
|
|
582
|
+
process.env.MIXDOG_QUIET_PROVIDER_LOG = '1';
|
|
583
|
+
process.env.MIXDOG_QUIET_SESSION_LOG = '1';
|
|
584
|
+
process.env.MIXDOG_QUIET_MCP_LOG = '1';
|
|
585
|
+
process.env.MIXDOG_QUIET_MEMORY_LOG = '1';
|
|
586
|
+
process.env.MIXDOG_PATCH_NATIVE_PREWARM ??= '0';
|
|
587
|
+
|
|
588
|
+
const importStartedAt = performance.now();
|
|
589
|
+
const { createMixdogSessionRuntime } = await import(SESSION_RUNTIME_MODULE);
|
|
590
|
+
bootProfile('session-runtime:imported', { ms: (performance.now() - importStartedAt).toFixed(1) });
|
|
591
|
+
const runtime = await createMixdogSessionRuntime({ provider: providerName, model, toolMode });
|
|
592
|
+
bootProfile('engine:create:runtime-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
593
|
+
const cwd = runtime.cwd || process.cwd();
|
|
594
|
+
const stateStartedAt = performance.now();
|
|
595
|
+
const autoClearState = () => runtime.getAutoClear?.() || runtime.autoClear || { enabled: true, idleMs: 60 * 60 * 1000 };
|
|
596
|
+
const BRIDGE_STATUS_CACHE_MS = 250;
|
|
597
|
+
let bridgeStatusCache = null;
|
|
598
|
+
let bridgeStatusCacheAt = 0;
|
|
599
|
+
const bridgeStatusState = ({ force = false } = {}) => {
|
|
600
|
+
const now = Date.now();
|
|
601
|
+
if (!force && bridgeStatusCache && now - bridgeStatusCacheAt < BRIDGE_STATUS_CACHE_MS) return bridgeStatusCache;
|
|
602
|
+
const status = runtime.bridgeStatus?.() || {};
|
|
603
|
+
bridgeStatusCache = {
|
|
604
|
+
bridgeMode: runtime.bridgeMode || status.bridgeMode || 'async',
|
|
605
|
+
bridgeWorkers: Array.isArray(status.bridgeWorkers) ? status.bridgeWorkers : [],
|
|
606
|
+
bridgeJobs: Array.isArray(status.bridgeJobs) ? status.bridgeJobs : [],
|
|
607
|
+
bridgeScope: status.bridgeScope || null,
|
|
608
|
+
};
|
|
609
|
+
bridgeStatusCacheAt = now;
|
|
610
|
+
return bridgeStatusCache;
|
|
611
|
+
};
|
|
612
|
+
const routeState = () => ({
|
|
613
|
+
sessionId: runtime.id,
|
|
614
|
+
clientHostPid: runtime.clientHostPid || null,
|
|
615
|
+
model: runtime.model,
|
|
616
|
+
provider: runtime.provider,
|
|
617
|
+
effort: runtime.effort,
|
|
618
|
+
effortOptions: runtime.effortOptions,
|
|
619
|
+
fast: runtime.fast,
|
|
620
|
+
fastCapable: runtime.fastCapable,
|
|
621
|
+
contextWindow: runtime.contextWindow,
|
|
622
|
+
rawContextWindow: runtime.rawContextWindow,
|
|
623
|
+
effectiveContextWindowPercent: runtime.effectiveContextWindowPercent,
|
|
624
|
+
cwd: runtime.cwd || process.cwd(),
|
|
625
|
+
systemShell: runtime.systemShell || { source: 'auto', command: '', effective: '' },
|
|
626
|
+
autoClear: autoClearState(),
|
|
627
|
+
workflow: runtime.workflow || null,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const routeStateStartedAt = performance.now();
|
|
631
|
+
const initialRouteState = routeState();
|
|
632
|
+
bootProfile('engine:route-state-ready', { ms: (performance.now() - routeStateStartedAt).toFixed(1) });
|
|
633
|
+
const initialBridgeState = {
|
|
634
|
+
bridgeMode: runtime.bridgeMode || 'async',
|
|
635
|
+
bridgeWorkers: [],
|
|
636
|
+
bridgeJobs: [],
|
|
637
|
+
bridgeScope: null,
|
|
638
|
+
};
|
|
639
|
+
let state = {
|
|
640
|
+
items: [],
|
|
641
|
+
toasts: [],
|
|
642
|
+
busy: false,
|
|
643
|
+
commandBusy: false,
|
|
644
|
+
commandStatus: null,
|
|
645
|
+
spinner: null,
|
|
646
|
+
queued: [],
|
|
647
|
+
thinking: null,
|
|
648
|
+
lastTurn: null,
|
|
649
|
+
stats: createSessionStats(),
|
|
650
|
+
...initialRouteState,
|
|
651
|
+
...initialBridgeState,
|
|
652
|
+
toolMode: runtime.toolMode,
|
|
653
|
+
cwd,
|
|
654
|
+
};
|
|
655
|
+
bootProfile('engine:state-ready', { ms: (performance.now() - stateStartedAt).toFixed(1) });
|
|
656
|
+
const syncContextStats = ({ allowEstimated = false } = {}) => {
|
|
657
|
+
const ctx = runtime.contextStatus?.() || null;
|
|
658
|
+
const hasProviderUsage = Number(state.stats.latestPromptTokens || state.stats.latestInputTokens || state.stats.inputTokens || 0) > 0;
|
|
659
|
+
const lastApiTokens = Number(ctx?.lastApiRequestStale ? 0 : (ctx?.lastApiRequestTokens || ctx?.usage?.lastContextTokens || 0));
|
|
660
|
+
const estimatedTokens = Number(ctx?.currentEstimatedTokens || 0);
|
|
661
|
+
const used = lastApiTokens;
|
|
662
|
+
if (!allowEstimated && !hasProviderUsage && ctx?.usedSource !== 'last_api_request') return ctx;
|
|
663
|
+
if (Number.isFinite(used) && used > 0) state.stats.currentContextTokens = Math.max(0, used);
|
|
664
|
+
else state.stats.currentContextTokens = 0;
|
|
665
|
+
state.stats.currentEstimatedContextTokens = Number.isFinite(estimatedTokens) ? Math.max(0, estimatedTokens) : 0;
|
|
666
|
+
state.stats.currentContextSource = used > 0 ? 'last_api_request' : (estimatedTokens > 0 ? 'estimated' : null);
|
|
667
|
+
state.stats.currentContextUpdatedAt = Date.now();
|
|
668
|
+
return ctx;
|
|
669
|
+
};
|
|
670
|
+
const contextStartedAt = performance.now();
|
|
671
|
+
syncContextStats({ allowEstimated: true });
|
|
672
|
+
bootProfile('engine:context-ready', { ms: (performance.now() - contextStartedAt).toFixed(1) });
|
|
673
|
+
const listeners = new Set();
|
|
674
|
+
const emit = () => { for (const l of listeners) l(); };
|
|
675
|
+
const set = (patch) => {
|
|
676
|
+
if (!patch || typeof patch !== 'object') return false;
|
|
677
|
+
let changed = false;
|
|
678
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
679
|
+
if (!Object.is(state[key], value)) {
|
|
680
|
+
changed = true;
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (!changed) return false;
|
|
685
|
+
state = { ...state, ...patch };
|
|
686
|
+
emit();
|
|
687
|
+
return true;
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const itemIndexById = new Map();
|
|
691
|
+
const replaceItems = (items) => {
|
|
692
|
+
const nextItems = Array.isArray(items) ? items : [];
|
|
693
|
+
itemIndexById.clear();
|
|
694
|
+
for (let i = 0; i < nextItems.length; i++) {
|
|
695
|
+
const id = nextItems[i]?.id;
|
|
696
|
+
if (id != null) itemIndexById.set(id, i);
|
|
697
|
+
}
|
|
698
|
+
return nextItems;
|
|
699
|
+
};
|
|
700
|
+
const pushItem = (item) => {
|
|
701
|
+
const index = state.items.length;
|
|
702
|
+
const items = [...state.items, item];
|
|
703
|
+
if (item?.id != null) itemIndexById.set(item.id, index);
|
|
704
|
+
set({ items });
|
|
705
|
+
};
|
|
706
|
+
const removeItemsByIds = (ids) => {
|
|
707
|
+
const idSet = new Set((ids || []).filter((id) => id != null));
|
|
708
|
+
if (idSet.size === 0) return false;
|
|
709
|
+
const items = state.items.filter((item) => !idSet.has(item?.id));
|
|
710
|
+
if (items.length === state.items.length) return false;
|
|
711
|
+
set({ items: replaceItems(items) });
|
|
712
|
+
return true;
|
|
713
|
+
};
|
|
714
|
+
const pushUserOrSyntheticItem = (text, id = nextId()) => {
|
|
715
|
+
const synthetic = parseSyntheticAgentMessage(text);
|
|
716
|
+
if (!synthetic) {
|
|
717
|
+
pushItem({ kind: 'user', id, text });
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const label = synthetic.label || 'notification';
|
|
721
|
+
pushItem({
|
|
722
|
+
kind: 'tool',
|
|
723
|
+
id,
|
|
724
|
+
name: synthetic.name || 'bridge',
|
|
725
|
+
args: synthetic.args || {
|
|
726
|
+
type: label,
|
|
727
|
+
task_id: synthetic.taskId || undefined,
|
|
728
|
+
description: synthetic.summary || 'agent notification',
|
|
729
|
+
},
|
|
730
|
+
result: synthetic.result,
|
|
731
|
+
isError: synthetic.isError ?? /^(failed|error|killed|cancelled)$/i.test(label),
|
|
732
|
+
expanded: false,
|
|
733
|
+
count: 1,
|
|
734
|
+
completedCount: 1,
|
|
735
|
+
startedAt: Date.now(),
|
|
736
|
+
completedAt: Date.now(),
|
|
737
|
+
});
|
|
738
|
+
};
|
|
739
|
+
const pushToast = (text, tone = 'info', ttlMs = 3000) => {
|
|
740
|
+
const id = nextId();
|
|
741
|
+
const value = String(text ?? '').trim();
|
|
742
|
+
if (!value) return null;
|
|
743
|
+
set({ toasts: [...state.toasts.filter((toast) => toast.id !== id), { id, text: value, tone }] });
|
|
744
|
+
const timer = setTimeout(() => {
|
|
745
|
+
toastTimers.delete(timer);
|
|
746
|
+
if (disposed) return;
|
|
747
|
+
set({ toasts: state.toasts.filter((toast) => toast.id !== id) });
|
|
748
|
+
}, ttlMs);
|
|
749
|
+
toastTimers.add(timer);
|
|
750
|
+
timer.unref?.();
|
|
751
|
+
return id;
|
|
752
|
+
};
|
|
753
|
+
const pushNotice = (text, tone = 'info', options = {}) => {
|
|
754
|
+
const value = polishNoticeText(text);
|
|
755
|
+
if (!value) return null;
|
|
756
|
+
const forceTranscript = options.transcript === true;
|
|
757
|
+
if (!forceTranscript) return pushToast(value, tone, options.ttlMs);
|
|
758
|
+
const id = nextId();
|
|
759
|
+
pushItem({ kind: 'notice', id, text: value, tone });
|
|
760
|
+
return id;
|
|
761
|
+
};
|
|
762
|
+
const patchItem = (id, patch) => {
|
|
763
|
+
let index = itemIndexById.get(id);
|
|
764
|
+
if (!Number.isInteger(index) || state.items[index]?.id !== id) {
|
|
765
|
+
index = state.items.findIndex((it) => it.id === id);
|
|
766
|
+
if (index >= 0) itemIndexById.set(id, index);
|
|
767
|
+
}
|
|
768
|
+
if (index < 0) return false;
|
|
769
|
+
const current = state.items[index];
|
|
770
|
+
let changed = false;
|
|
771
|
+
for (const [key, value] of Object.entries(patch || {})) {
|
|
772
|
+
if (!Object.is(current[key], value)) {
|
|
773
|
+
changed = true;
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (!changed) return false;
|
|
778
|
+
const items = state.items.slice();
|
|
779
|
+
items[index] = { ...current, ...patch };
|
|
780
|
+
set({ items });
|
|
781
|
+
return true;
|
|
782
|
+
};
|
|
783
|
+
const toastTimers = new Set();
|
|
784
|
+
let disposed = false;
|
|
785
|
+
|
|
786
|
+
function clearToastTimers() {
|
|
787
|
+
for (const timer of toastTimers) {
|
|
788
|
+
clearTimeout(timer);
|
|
789
|
+
}
|
|
790
|
+
toastTimers.clear();
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
let unsubscribeRuntimeNotifications = null;
|
|
794
|
+
let lastUserActivityAt = Date.now();
|
|
795
|
+
let autoClearRunning = false;
|
|
796
|
+
const pendingNotificationKeys = new Set();
|
|
797
|
+
|
|
798
|
+
function updateBridgeJobCard(itemId, text, isError = false) {
|
|
799
|
+
const parsed = parseBridgeJob(text);
|
|
800
|
+
const current = state.items.find((it) => it.id === itemId);
|
|
801
|
+
const rawDisplayText = bridgeJobResultText(text, parsed) || String(text || '').trim();
|
|
802
|
+
const displayText = isError ? toolErrorDisplay(rawDisplayText, 'bridge') : rawDisplayText;
|
|
803
|
+
patchItem(itemId, {
|
|
804
|
+
result: displayText,
|
|
805
|
+
text: displayText,
|
|
806
|
+
isError,
|
|
807
|
+
errorCount: isError ? 1 : 0,
|
|
808
|
+
...(parsed ? { args: bridgeArgsWithResultMetadata(current?.args, parsed) } : {}),
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (typeof runtime.onNotification === 'function') {
|
|
813
|
+
unsubscribeRuntimeNotifications = runtime.onNotification((event) => {
|
|
814
|
+
if (disposed) return;
|
|
815
|
+
const text = String(event?.content ?? event?.text ?? event ?? '').trim();
|
|
816
|
+
if (!text) return;
|
|
817
|
+
const parsed = parseBridgeJob(text);
|
|
818
|
+
const notificationKey = notificationQueueKey(event, text, parsed);
|
|
819
|
+
if (parsed?.taskId) {
|
|
820
|
+
const existing = [...state.items].reverse().find((item) => {
|
|
821
|
+
if (!item || item.kind !== 'tool' || item.name !== 'bridge') return false;
|
|
822
|
+
const args = parseToolArgs(item.args);
|
|
823
|
+
return args.task_id === parsed.taskId;
|
|
824
|
+
});
|
|
825
|
+
if (existing) {
|
|
826
|
+
updateBridgeJobCard(existing.id, text, /^(failed|error|timeout|cancelled|killed)$/i.test(parsed.status));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
enqueue(text, {
|
|
830
|
+
mode: 'task-notification',
|
|
831
|
+
priority: 'next',
|
|
832
|
+
key: notificationKey || undefined,
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function groupedToolResultText(group) {
|
|
838
|
+
const completed = Math.min(group.count, group.completed);
|
|
839
|
+
if (group.count <= 1) return group.results.at(-1)?.text ?? '';
|
|
840
|
+
if (group.errors > 0) {
|
|
841
|
+
const succeeded = Math.max(0, completed - group.errors);
|
|
842
|
+
const reasons = group.results
|
|
843
|
+
.filter((result) => result?.isError)
|
|
844
|
+
.map((result) => firstErrorLine(result?.text))
|
|
845
|
+
.filter(Boolean);
|
|
846
|
+
const uniqueReasons = [...new Set(reasons)].slice(0, 2);
|
|
847
|
+
const base = succeeded > 0
|
|
848
|
+
? `${succeeded} Ok · ${group.errors} Failed`
|
|
849
|
+
: `${group.errors} Failed`;
|
|
850
|
+
return [
|
|
851
|
+
`${base}${uniqueReasons[0] ? ` · ${uniqueReasons[0]}` : ''}`,
|
|
852
|
+
...uniqueReasons.slice(1),
|
|
853
|
+
].join('\n');
|
|
854
|
+
}
|
|
855
|
+
return '';
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function firstErrorLine(text) {
|
|
859
|
+
const clean = toolErrorDisplay(text, 'tool');
|
|
860
|
+
if (clean) return clean;
|
|
861
|
+
for (const line of String(text || '').split('\n')) {
|
|
862
|
+
const trimmed = line.trim();
|
|
863
|
+
if (!trimmed) continue;
|
|
864
|
+
if (/^(Error|\[?error|FAIL\b)/i.test(trimmed)) return trimmed;
|
|
865
|
+
}
|
|
866
|
+
return String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function aggregateRawResult(calls) {
|
|
870
|
+
const chunks = [];
|
|
871
|
+
for (const rec of calls || []) {
|
|
872
|
+
const text = String(rec?.resultText || '').replace(/\s+$/, '');
|
|
873
|
+
if (!text.trim()) continue;
|
|
874
|
+
const label = String(rec?.name || rec?.category || 'tool').trim() || 'tool';
|
|
875
|
+
chunks.push(`${chunks.length + 1}. ${label}\n${text}`);
|
|
876
|
+
}
|
|
877
|
+
return chunks.join('\n\n');
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function aggregateBucketForCategory(category) {
|
|
881
|
+
switch (category) {
|
|
882
|
+
case 'Read':
|
|
883
|
+
case 'Search':
|
|
884
|
+
return 'local-discovery';
|
|
885
|
+
case 'Web Research':
|
|
886
|
+
return 'web-research';
|
|
887
|
+
case 'Memory':
|
|
888
|
+
return 'memory';
|
|
889
|
+
case 'Explore':
|
|
890
|
+
return 'explore';
|
|
891
|
+
case 'Patch':
|
|
892
|
+
return 'patch';
|
|
893
|
+
default:
|
|
894
|
+
// Shell/Agent/Channel/Setup/Other stay as their own cards so risky or
|
|
895
|
+
// semantically distinct actions do not disappear inside a discovery log.
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function aggregateSummaries(aggregate) {
|
|
901
|
+
return [...(aggregate?.calls?.values?.() || [])]
|
|
902
|
+
.filter((r) => r.summary)
|
|
903
|
+
.sort((a, b) => Number(a.summarySeq ?? 0) - Number(b.summarySeq ?? 0))
|
|
904
|
+
.map((r) => r.summary);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function assignAggregateSummaryOrder(aggregate, callRec) {
|
|
908
|
+
if (!aggregate || !callRec?.summary || callRec.summarySeq != null) return;
|
|
909
|
+
const next = Math.max(0, Number(aggregate.nextSummarySeq || 0));
|
|
910
|
+
callRec.summarySeq = next;
|
|
911
|
+
aggregate.nextSummarySeq = next + 1;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function patchToolCardResult(card, message, toolGroups, done) {
|
|
915
|
+
if (!card || card.done) return false;
|
|
916
|
+
const callId = toolResultCallId(message) || card.callId;
|
|
917
|
+
if (callId && done.has(callId)) return false;
|
|
918
|
+
const rawText = toolResultText(message?.content);
|
|
919
|
+
const isError = message?.isError === true || message?.toolKind === 'error' || /^\s*\[?error/i.test(rawText);
|
|
920
|
+
const text = isError ? toolErrorDisplay(rawText, card?.name || 'tool') : rawText;
|
|
921
|
+
|
|
922
|
+
// Aggregate card handling — collect semantic summaries per call
|
|
923
|
+
const aggregate = card.aggregate;
|
|
924
|
+
if (aggregate && card.itemId === aggregate.itemId) {
|
|
925
|
+
const callRec = callId ? aggregate.calls.get(callId) : null;
|
|
926
|
+
if (callRec) {
|
|
927
|
+
callRec.summary = !isError ? summarizeToolResult(callRec.name, callRec.args, rawText, isError) : null;
|
|
928
|
+
assignAggregateSummaryOrder(aggregate, callRec);
|
|
929
|
+
callRec.isError = isError;
|
|
930
|
+
callRec.resultText = text;
|
|
931
|
+
callRec.resolved = true;
|
|
932
|
+
}
|
|
933
|
+
const allCalls = [...aggregate.calls.values()];
|
|
934
|
+
const completed = allCalls.filter((r) => r.resolved).length;
|
|
935
|
+
const errors = allCalls.filter((r) => r.isError).length;
|
|
936
|
+
const summaries = aggregateSummaries(aggregate);
|
|
937
|
+
let detailText;
|
|
938
|
+
if (errors > 0 && summaries.length === 0) {
|
|
939
|
+
const succeeded = completed - errors;
|
|
940
|
+
detailText = succeeded > 0 ? `${succeeded} Ok · ${errors} Failed` : `${errors} Failed`;
|
|
941
|
+
} else {
|
|
942
|
+
detailText = formatAggregateDetail(summaries) || '';
|
|
943
|
+
}
|
|
944
|
+
const currentItem = state.items.find((it) => it.id === card.itemId);
|
|
945
|
+
const visualCompleted = Math.max(completed, Math.min(allCalls.length, Number(currentItem?.completedCount || 0)));
|
|
946
|
+
const rawResult = aggregateRawResult(allCalls);
|
|
947
|
+
patchItem(card.itemId, {
|
|
948
|
+
result: detailText,
|
|
949
|
+
text: detailText,
|
|
950
|
+
rawResult: rawResult || null,
|
|
951
|
+
isError: errors > 0,
|
|
952
|
+
errorCount: errors,
|
|
953
|
+
count: allCalls.length,
|
|
954
|
+
completedCount: visualCompleted,
|
|
955
|
+
completedAt: Date.now(),
|
|
956
|
+
});
|
|
957
|
+
card.done = true;
|
|
958
|
+
if (callId) done.add(callId);
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Non-aggregate (legacy bridge-job cards, etc.)
|
|
963
|
+
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
|
|
964
|
+
group.completed = Math.min(group.count, group.completed + 1);
|
|
965
|
+
group.errors += isError ? 1 : 0;
|
|
966
|
+
group.results.push({ text, isError });
|
|
967
|
+
toolGroups.set(card.itemId, group);
|
|
968
|
+
const resultText = groupedToolResultText(group);
|
|
969
|
+
const patch = {
|
|
970
|
+
result: resultText,
|
|
971
|
+
text: resultText,
|
|
972
|
+
isError: group.errors > 0,
|
|
973
|
+
errorCount: group.errors,
|
|
974
|
+
count: group.count,
|
|
975
|
+
completedCount: group.completed,
|
|
976
|
+
completedAt: Date.now(),
|
|
977
|
+
};
|
|
978
|
+
if (group.count <= 1) {
|
|
979
|
+
const parsedBridge = parseBridgeJob(rawText);
|
|
980
|
+
if (parsedBridge) patch.args = bridgeArgsWithResultMetadata(state.items.find((it) => it.id === card.itemId)?.args, parsedBridge);
|
|
981
|
+
}
|
|
982
|
+
patchItem(card.itemId, patch);
|
|
983
|
+
if (group.count <= 1) updateBridgeJobCard(card.itemId, rawText, isError);
|
|
984
|
+
card.done = true;
|
|
985
|
+
if (callId) done.add(callId);
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const flushToolResults = (messages, toolCards, cardByCallId, toolGroups, done, { finalize = false } = {}) => {
|
|
990
|
+
const results = [];
|
|
991
|
+
for (const m of messages || []) {
|
|
992
|
+
if (!m || m.role !== 'tool') continue;
|
|
993
|
+
const callId = toolResultCallId(m);
|
|
994
|
+
results.push({ message: m, callId, used: false });
|
|
995
|
+
if (!callId || done.has(callId)) continue;
|
|
996
|
+
const card = cardByCallId.get(callId);
|
|
997
|
+
if (patchToolCardResult(card, m, toolGroups, done)) {
|
|
998
|
+
results[results.length - 1].used = true;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const openCards = (toolCards || []).filter((card) => !card.done);
|
|
1003
|
+
if (openCards.length === 0) return;
|
|
1004
|
+
|
|
1005
|
+
const unusedResults = results.filter((result) => !result.used);
|
|
1006
|
+
const fallbackResults = unusedResults.slice(-openCards.length);
|
|
1007
|
+
for (let i = 0; i < fallbackResults.length; i++) {
|
|
1008
|
+
const card = openCards[i];
|
|
1009
|
+
const result = fallbackResults[i];
|
|
1010
|
+
if (!card || !result || card.done) continue;
|
|
1011
|
+
if (patchToolCardResult(card, result.message, toolGroups, done)) {
|
|
1012
|
+
if (result.callId) done.add(result.callId);
|
|
1013
|
+
result.used = true;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (!finalize) return;
|
|
1018
|
+
for (const card of toolCards || []) {
|
|
1019
|
+
if (card.done) continue;
|
|
1020
|
+
// Aggregate finalize — mark any remaining calls as done
|
|
1021
|
+
const aggregate = card.aggregate;
|
|
1022
|
+
if (aggregate && card.itemId === aggregate.itemId) {
|
|
1023
|
+
const allCalls = [...aggregate.calls.values()];
|
|
1024
|
+
const completed = allCalls.filter((r) => r.resolved).length;
|
|
1025
|
+
const remaining = allCalls.length - completed;
|
|
1026
|
+
const totalCompleted = remaining > 0 ? completed + remaining : completed;
|
|
1027
|
+
const errors = allCalls.filter((r) => r.isError).length;
|
|
1028
|
+
const summaries = aggregateSummaries(aggregate);
|
|
1029
|
+
const detailText = formatAggregateDetail(summaries) || '';
|
|
1030
|
+
const rawResult = aggregateRawResult(allCalls);
|
|
1031
|
+
patchItem(card.itemId, {
|
|
1032
|
+
result: detailText,
|
|
1033
|
+
text: detailText,
|
|
1034
|
+
rawResult: rawResult || null,
|
|
1035
|
+
isError: errors > 0,
|
|
1036
|
+
errorCount: errors,
|
|
1037
|
+
count: allCalls.length,
|
|
1038
|
+
completedCount: totalCompleted,
|
|
1039
|
+
completedAt: Date.now(),
|
|
1040
|
+
});
|
|
1041
|
+
for (const sibling of toolCards || []) {
|
|
1042
|
+
if (sibling.itemId !== card.itemId) continue;
|
|
1043
|
+
sibling.done = true;
|
|
1044
|
+
if (sibling.callId) done.add(sibling.callId);
|
|
1045
|
+
}
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
// Non-aggregate finalize
|
|
1049
|
+
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
|
|
1050
|
+
group.completed = Math.min(group.count, group.completed + 1);
|
|
1051
|
+
toolGroups.set(card.itemId, group);
|
|
1052
|
+
const resultText = groupedToolResultText(group);
|
|
1053
|
+
patchItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, count: group.count, completedCount: group.completed, completedAt: Date.now() });
|
|
1054
|
+
card.done = true;
|
|
1055
|
+
if (card.callId) done.add(card.callId);
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
async function runTurn(userText, options = {}) {
|
|
1060
|
+
const turnIndex = state.stats.turns || 0;
|
|
1061
|
+
const startedAt = Date.now();
|
|
1062
|
+
const inputBaseline = state.stats.inputTokens;
|
|
1063
|
+
const outputBaseline = state.stats.outputTokens;
|
|
1064
|
+
const submittedIds = Array.isArray(options.submittedIds) ? options.submittedIds : [];
|
|
1065
|
+
const displayText = promptDisplayText(userText, options);
|
|
1066
|
+
let promptCommittedCallbackCalled = false;
|
|
1067
|
+
activePromptRestore = {
|
|
1068
|
+
text: String(displayText || '').trim(),
|
|
1069
|
+
pastedImages: options.pastedImages && typeof options.pastedImages === 'object' ? options.pastedImages : null,
|
|
1070
|
+
onCommitted: typeof options.onCommitted === 'function' ? options.onCommitted : null,
|
|
1071
|
+
restorable: options.restorable !== false,
|
|
1072
|
+
submittedIds,
|
|
1073
|
+
reclaimed: false,
|
|
1074
|
+
committed: false,
|
|
1075
|
+
requeueEntries: Array.isArray(options.requeueOnAbort) ? options.requeueOnAbort.slice() : [],
|
|
1076
|
+
};
|
|
1077
|
+
set({ busy: true, lastTurn: null, spinner: { active: true, verb: pickVerb(turnIndex), startedAt, responseLength: 0, inputTokens: 0, outputTokens: 0, mode: 'requesting' } });
|
|
1078
|
+
|
|
1079
|
+
let assistantText = '';
|
|
1080
|
+
let currentAssistantId = null;
|
|
1081
|
+
let currentAssistantText = '';
|
|
1082
|
+
let thinkingText = '';
|
|
1083
|
+
let thinkingStartedAt = 0;
|
|
1084
|
+
let thinkingSegmentStartedAt = 0;
|
|
1085
|
+
let accumulatedThinkingMs = 0;
|
|
1086
|
+
let cancelled = false;
|
|
1087
|
+
const cardByCallId = new Map();
|
|
1088
|
+
const toolCards = [];
|
|
1089
|
+
const toolGroups = new Map();
|
|
1090
|
+
const resultsDone = new Set();
|
|
1091
|
+
const aggregateCards = []; // active aggregate cards in the current consecutive tool block
|
|
1092
|
+
let openAggregateCard = null;
|
|
1093
|
+
let autoCompactStatus = null;
|
|
1094
|
+
|
|
1095
|
+
const markPromptCommitted = () => {
|
|
1096
|
+
if (activePromptRestore) {
|
|
1097
|
+
if (!promptCommittedCallbackCalled && typeof activePromptRestore.onCommitted === 'function') {
|
|
1098
|
+
promptCommittedCallbackCalled = true;
|
|
1099
|
+
try { activePromptRestore.onCommitted(); } catch {}
|
|
1100
|
+
}
|
|
1101
|
+
activePromptRestore.restorable = false;
|
|
1102
|
+
activePromptRestore.committed = true;
|
|
1103
|
+
activePromptRestore.requeueEntries = [];
|
|
1104
|
+
activePromptRestore.pastedImages = null;
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const finalizeToolHeaders = () => {
|
|
1109
|
+
const ids = new Set();
|
|
1110
|
+
for (const card of toolCards || []) {
|
|
1111
|
+
if (card?.itemId) ids.add(card.itemId);
|
|
1112
|
+
}
|
|
1113
|
+
for (const aggregate of aggregateCards || []) {
|
|
1114
|
+
if (aggregate?.itemId) ids.add(aggregate.itemId);
|
|
1115
|
+
}
|
|
1116
|
+
if (ids.size === 0) return false;
|
|
1117
|
+
let changed = false;
|
|
1118
|
+
const items = state.items.map((item) => {
|
|
1119
|
+
if (!ids.has(item?.id) || item.kind !== 'tool' || item.headerFinalized !== false) return item;
|
|
1120
|
+
changed = true;
|
|
1121
|
+
return { ...item, headerFinalized: true };
|
|
1122
|
+
});
|
|
1123
|
+
if (changed) set({ items });
|
|
1124
|
+
return changed;
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
const completeAggregateVisual = () => {
|
|
1128
|
+
for (const aggregate of aggregateCards) {
|
|
1129
|
+
const allCalls = [...aggregate.calls.values()];
|
|
1130
|
+
if (allCalls.length === 0) continue;
|
|
1131
|
+
const errors = allCalls.filter((r) => r.isError).length;
|
|
1132
|
+
const summaries = aggregateSummaries(aggregate);
|
|
1133
|
+
const detailText = formatAggregateDetail(summaries) || '';
|
|
1134
|
+
const rawResult = aggregateRawResult(allCalls);
|
|
1135
|
+
patchItem(aggregate.itemId, {
|
|
1136
|
+
result: detailText,
|
|
1137
|
+
text: detailText,
|
|
1138
|
+
rawResult: rawResult || null,
|
|
1139
|
+
isError: errors > 0,
|
|
1140
|
+
count: allCalls.length,
|
|
1141
|
+
completedCount: allCalls.length,
|
|
1142
|
+
completedAt: Date.now(),
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
const clearAggregateContinuation = () => {
|
|
1148
|
+
completeAggregateVisual();
|
|
1149
|
+
finalizeToolHeaders();
|
|
1150
|
+
aggregateCards.length = 0;
|
|
1151
|
+
openAggregateCard = null;
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
const ensureAggregateCard = (bucket) => {
|
|
1155
|
+
if (openAggregateCard?.bucket === bucket) return openAggregateCard;
|
|
1156
|
+
const itemId = nextId();
|
|
1157
|
+
const aggregate = {
|
|
1158
|
+
itemId,
|
|
1159
|
+
bucket,
|
|
1160
|
+
categories: new Map(),
|
|
1161
|
+
categoryOrder: [],
|
|
1162
|
+
calls: new Map(),
|
|
1163
|
+
nextSummarySeq: 0,
|
|
1164
|
+
pushed: false,
|
|
1165
|
+
startedAt: Date.now(),
|
|
1166
|
+
};
|
|
1167
|
+
aggregateCards.push(aggregate);
|
|
1168
|
+
openAggregateCard = aggregate;
|
|
1169
|
+
return aggregate;
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
const syncAggregateHeader = (aggregate) => {
|
|
1173
|
+
if (!aggregate?.itemId) return;
|
|
1174
|
+
const patch = {
|
|
1175
|
+
args: { categoryOrder: aggregate.categoryOrder.slice() },
|
|
1176
|
+
count: aggregate.calls.size,
|
|
1177
|
+
completedCount: [...aggregate.calls.values()].filter((r) => r.resolved).length,
|
|
1178
|
+
categories: Object.fromEntries(aggregate.categories),
|
|
1179
|
+
};
|
|
1180
|
+
if (aggregate.pushed) {
|
|
1181
|
+
patchItem(aggregate.itemId, patch);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
aggregate.pushed = true;
|
|
1185
|
+
pushItem({
|
|
1186
|
+
kind: 'tool',
|
|
1187
|
+
id: aggregate.itemId,
|
|
1188
|
+
name: '__aggregate__',
|
|
1189
|
+
...patch,
|
|
1190
|
+
aggregate: true,
|
|
1191
|
+
result: null,
|
|
1192
|
+
rawResult: null,
|
|
1193
|
+
isError: false,
|
|
1194
|
+
expanded: false,
|
|
1195
|
+
headerFinalized: false,
|
|
1196
|
+
startedAt: aggregate.startedAt || Date.now(),
|
|
1197
|
+
});
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
const ensureAssistant = () => {
|
|
1201
|
+
if (!currentAssistantId) {
|
|
1202
|
+
currentAssistantId = nextId();
|
|
1203
|
+
currentAssistantText = '';
|
|
1204
|
+
pushItem({ kind: 'assistant', id: currentAssistantId, text: '', streaming: true });
|
|
1205
|
+
}
|
|
1206
|
+
return currentAssistantId;
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
const closeAssistantSegment = () => {
|
|
1210
|
+
currentAssistantId = null;
|
|
1211
|
+
currentAssistantText = '';
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
const startThinkingSegment = () => {
|
|
1215
|
+
const now = Date.now();
|
|
1216
|
+
if (!thinkingStartedAt) thinkingStartedAt = now;
|
|
1217
|
+
if (!thinkingSegmentStartedAt) thinkingSegmentStartedAt = now;
|
|
1218
|
+
return now;
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
const closeThinkingSegment = () => {
|
|
1222
|
+
if (!thinkingSegmentStartedAt) return;
|
|
1223
|
+
const now = Date.now();
|
|
1224
|
+
accumulatedThinkingMs += Math.max(0, now - thinkingSegmentStartedAt);
|
|
1225
|
+
thinkingSegmentStartedAt = 0;
|
|
1226
|
+
return now;
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
// --- Streaming-delta batcher ---
|
|
1230
|
+
// onTextDelta and onReasoningDelta fire on every tiny chunk (often <10 chars).
|
|
1231
|
+
// Each call previously called set() → emit() → full React reconcile. We
|
|
1232
|
+
// batch accumulated text and flush at most once per STREAM_BATCH_INTERVAL_MS
|
|
1233
|
+
// (≈16ms / 60fps cap). A forced flush happens before any tool call,
|
|
1234
|
+
// finalization, or error so those code paths see the correct text state.
|
|
1235
|
+
const STREAM_BATCH_INTERVAL_MS = 16;
|
|
1236
|
+
let _batchTimer = null;
|
|
1237
|
+
let _pendingTextFlush = false; // true when a text/spinner update is queued
|
|
1238
|
+
let _pendingThinkFlush = false; // true when a thinking update is queued
|
|
1239
|
+
let _pendingThinkingLastEndedAt = 0;
|
|
1240
|
+
|
|
1241
|
+
const flushStreamBatch = () => {
|
|
1242
|
+
if (_batchTimer !== null) {
|
|
1243
|
+
clearTimeout(_batchTimer);
|
|
1244
|
+
_batchTimer = null;
|
|
1245
|
+
}
|
|
1246
|
+
if (_pendingTextFlush) {
|
|
1247
|
+
_pendingTextFlush = false;
|
|
1248
|
+
const id = ensureAssistant();
|
|
1249
|
+
// Emit the accumulated assistant text and spinner update together so a
|
|
1250
|
+
// streaming batch costs one set() → one emit() → one React reconcile.
|
|
1251
|
+
const patch = {};
|
|
1252
|
+
const index = state.items.findIndex((it) => it.id === id);
|
|
1253
|
+
if (index >= 0) {
|
|
1254
|
+
const current = state.items[index];
|
|
1255
|
+
if (!Object.is(current.text, currentAssistantText) || current.streaming !== true) {
|
|
1256
|
+
const items = state.items.slice();
|
|
1257
|
+
items[index] = { ...current, text: currentAssistantText, streaming: true };
|
|
1258
|
+
patch.items = items;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const responseLengthVal = assistantText.length + thinkingText.length;
|
|
1262
|
+
if (state.spinner) {
|
|
1263
|
+
patch.spinner = { ...state.spinner, responseLength: responseLengthVal, thinking: false, thinkingLastEndedAt: _pendingThinkingLastEndedAt || state.spinner.thinkingLastEndedAt, mode: 'responding' };
|
|
1264
|
+
}
|
|
1265
|
+
if (Object.keys(patch).length > 0) set(patch);
|
|
1266
|
+
_pendingThinkingLastEndedAt = 0;
|
|
1267
|
+
}
|
|
1268
|
+
if (_pendingThinkFlush) {
|
|
1269
|
+
_pendingThinkFlush = false;
|
|
1270
|
+
const responseLengthVal = assistantText.length + thinkingText.length;
|
|
1271
|
+
const thinkingElapsedMs = accumulatedThinkingMs + (thinkingSegmentStartedAt ? Math.max(0, Date.now() - thinkingSegmentStartedAt) : 0);
|
|
1272
|
+
const patch = { thinking: thinkingText };
|
|
1273
|
+
if (state.spinner) {
|
|
1274
|
+
patch.spinner = { ...state.spinner, responseLength: responseLengthVal, thinking: true, thinkingStartedAt, thinkingSegmentStartedAt, thinkingAccumulatedMs: accumulatedThinkingMs, thinkingElapsedMs, thinkingLastEndedAt: 0, mode: 'thinking' };
|
|
1275
|
+
}
|
|
1276
|
+
set(patch);
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
const scheduleStreamFlush = () => {
|
|
1281
|
+
if (_batchTimer !== null) return; // already scheduled; do not re-arm
|
|
1282
|
+
_batchTimer = setTimeout(flushStreamBatch, STREAM_BATCH_INTERVAL_MS);
|
|
1283
|
+
if (_batchTimer?.unref) _batchTimer.unref(); // don't prevent process exit
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
try {
|
|
1287
|
+
const { result, session } = await runtime.ask(userText, {
|
|
1288
|
+
drainSteering: () => drainPendingSteering(),
|
|
1289
|
+
onSteerMessage: (text) => {
|
|
1290
|
+
const value = String(text || '').trim();
|
|
1291
|
+
if (value) {
|
|
1292
|
+
finalizeToolHeaders();
|
|
1293
|
+
pushUserOrSyntheticItem(value);
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
onToolCall: async (_iter, calls) => {
|
|
1297
|
+
markPromptCommitted();
|
|
1298
|
+
if (thinkingText && state.thinking) {
|
|
1299
|
+
const thinkingLastEndedAt = closeThinkingSegment();
|
|
1300
|
+
flushStreamBatch(); // flush any buffered text/thinking before the tool card appears
|
|
1301
|
+
set({ thinking: null, spinner: state.spinner ? { ...state.spinner, thinking: false, thinkingAccumulatedMs: accumulatedThinkingMs, thinkingLastEndedAt, mode: 'tool-use' } : state.spinner });
|
|
1302
|
+
} else if (state.spinner) {
|
|
1303
|
+
flushStreamBatch(); // flush any buffered text before the tool card appears
|
|
1304
|
+
set({ spinner: { ...state.spinner, mode: 'tool-use' } });
|
|
1305
|
+
}
|
|
1306
|
+
const batchCalls = (calls || []).filter(Boolean);
|
|
1307
|
+
if (batchCalls.length === 0) return;
|
|
1308
|
+
closeAssistantSegment();
|
|
1309
|
+
|
|
1310
|
+
const touchedAggregates = new Set();
|
|
1311
|
+
for (let i = 0; i < batchCalls.length; i++) {
|
|
1312
|
+
const c = batchCalls[i];
|
|
1313
|
+
const name = toolCallName(c);
|
|
1314
|
+
const args = toolCallArgs(c);
|
|
1315
|
+
const category = classifyToolCategory(name, args);
|
|
1316
|
+
const bucket = aggregateBucketForCategory(category);
|
|
1317
|
+
const callId = toolCallId(c);
|
|
1318
|
+
const callKey = callId || `__tool_${toolCards.length}_${i}`;
|
|
1319
|
+
|
|
1320
|
+
if (!bucket) {
|
|
1321
|
+
openAggregateCard = null;
|
|
1322
|
+
const itemId = nextId();
|
|
1323
|
+
pushItem({
|
|
1324
|
+
kind: 'tool',
|
|
1325
|
+
id: itemId,
|
|
1326
|
+
name,
|
|
1327
|
+
args,
|
|
1328
|
+
result: null,
|
|
1329
|
+
isError: false,
|
|
1330
|
+
expanded: false,
|
|
1331
|
+
headerFinalized: false,
|
|
1332
|
+
count: 1,
|
|
1333
|
+
completedCount: 0,
|
|
1334
|
+
startedAt: Date.now(),
|
|
1335
|
+
});
|
|
1336
|
+
const card = { itemId, callId: callKey, done: false };
|
|
1337
|
+
if (callId) {
|
|
1338
|
+
cardByCallId.set(callId, card);
|
|
1339
|
+
}
|
|
1340
|
+
toolCards.push(card);
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const aggregateCard = ensureAggregateCard(bucket);
|
|
1345
|
+
if (!aggregateCard.categories.has(category)) aggregateCard.categoryOrder.push(category);
|
|
1346
|
+
aggregateCard.categories.set(category, (aggregateCard.categories.get(category) || 0) + 1);
|
|
1347
|
+
aggregateCard.calls.set(callKey, { name, args, category, summary: null, summarySeq: null, isError: false, resultText: null, resolved: false });
|
|
1348
|
+
touchedAggregates.add(aggregateCard);
|
|
1349
|
+
const card = { itemId: aggregateCard.itemId, callId: callKey, done: false, aggregate: aggregateCard };
|
|
1350
|
+
if (callId) {
|
|
1351
|
+
cardByCallId.set(callId, card);
|
|
1352
|
+
}
|
|
1353
|
+
toolCards.push(card);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
for (const aggregateCard of touchedAggregates) {
|
|
1357
|
+
syncAggregateHeader(aggregateCard);
|
|
1358
|
+
}
|
|
1359
|
+
await yieldToRenderer();
|
|
1360
|
+
},
|
|
1361
|
+
onToolResult: (message) => {
|
|
1362
|
+
flushToolResults([message], toolCards, cardByCallId, toolGroups, resultsDone);
|
|
1363
|
+
},
|
|
1364
|
+
onStageChange: (stage) => {
|
|
1365
|
+
if (!state.spinner) return;
|
|
1366
|
+
const value = String(stage || '');
|
|
1367
|
+
const mode = value === 'requesting'
|
|
1368
|
+
? 'requesting'
|
|
1369
|
+
: value === 'streaming'
|
|
1370
|
+
? (state.spinner.thinking ? 'thinking' : 'responding')
|
|
1371
|
+
: value === 'compacting'
|
|
1372
|
+
? 'compacting'
|
|
1373
|
+
: null;
|
|
1374
|
+
if (!mode || state.spinner.mode === mode) return;
|
|
1375
|
+
set({ spinner: { ...state.spinner, mode } });
|
|
1376
|
+
},
|
|
1377
|
+
onTextDelta: (chunk) => {
|
|
1378
|
+
const textChunk = String(chunk ?? '');
|
|
1379
|
+
if (!textChunk) return;
|
|
1380
|
+
markPromptCommitted();
|
|
1381
|
+
const thinkingLastEndedAt = closeThinkingSegment();
|
|
1382
|
+
if (state.thinking) set({ thinking: null }); // collapse thinking panel immediately, no batch delay
|
|
1383
|
+
if (textChunk.trim()) clearAggregateContinuation();
|
|
1384
|
+
assistantText += textChunk;
|
|
1385
|
+
ensureAssistant(); // create the assistant item in state immediately so the slot exists
|
|
1386
|
+
currentAssistantText += textChunk;
|
|
1387
|
+
// Accumulate text; fire at most one render per STREAM_BATCH_INTERVAL_MS.
|
|
1388
|
+
_pendingTextFlush = true;
|
|
1389
|
+
if (thinkingLastEndedAt) _pendingThinkingLastEndedAt = thinkingLastEndedAt;
|
|
1390
|
+
scheduleStreamFlush();
|
|
1391
|
+
},
|
|
1392
|
+
onReasoningDelta: (chunk) => {
|
|
1393
|
+
if (String(chunk ?? '')) markPromptCommitted();
|
|
1394
|
+
startThinkingSegment();
|
|
1395
|
+
thinkingText += String(chunk ?? '');
|
|
1396
|
+
// Accumulate reasoning text; fire at most one render per STREAM_BATCH_INTERVAL_MS.
|
|
1397
|
+
_pendingThinkFlush = true;
|
|
1398
|
+
scheduleStreamFlush();
|
|
1399
|
+
},
|
|
1400
|
+
onUsageDelta: (delta) => {
|
|
1401
|
+
applyUsageDelta(state.stats, delta);
|
|
1402
|
+
syncContextStats();
|
|
1403
|
+
const currentTurnInput = Math.max(0, state.stats.inputTokens - inputBaseline);
|
|
1404
|
+
const currentTurnOutput = Math.max(0, state.stats.outputTokens - outputBaseline);
|
|
1405
|
+
if (state.spinner) {
|
|
1406
|
+
set({ stats: { ...state.stats }, spinner: { ...state.spinner, inputTokens: currentTurnInput, outputTokens: currentTurnOutput } });
|
|
1407
|
+
} else {
|
|
1408
|
+
set({ stats: { ...state.stats } });
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
});
|
|
1412
|
+
markPromptCommitted();
|
|
1413
|
+
if (result?.postTurnCompact?.changed) autoCompactStatus = result.postTurnCompact;
|
|
1414
|
+
|
|
1415
|
+
flushToolResults(session?.messages || [], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true });
|
|
1416
|
+
finalizeToolHeaders();
|
|
1417
|
+
flushStreamBatch(); // force-flush any batched streaming text before finalization writes
|
|
1418
|
+
syncContextStats();
|
|
1419
|
+
|
|
1420
|
+
const finalText = (result?.content != null && String(result.content)) || assistantText;
|
|
1421
|
+
if (assistantText.trim() && currentAssistantId) {
|
|
1422
|
+
patchItem(currentAssistantId, { text: currentAssistantText || assistantText, streaming: false });
|
|
1423
|
+
}
|
|
1424
|
+
if (finalText && !assistantText.trim()) {
|
|
1425
|
+
const id = ensureAssistant();
|
|
1426
|
+
currentAssistantText = finalText;
|
|
1427
|
+
patchItem(id, { text: finalText, streaming: false });
|
|
1428
|
+
}
|
|
1429
|
+
state.stats.turns = (state.stats.turns || 0) + 1;
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
flushStreamBatch(); // ensure any batched text lands before the error notice
|
|
1432
|
+
if (error?.name === 'SessionClosedError') {
|
|
1433
|
+
cancelled = true;
|
|
1434
|
+
if (assistantText.trim() && currentAssistantId) {
|
|
1435
|
+
patchItem(currentAssistantId, { text: currentAssistantText || assistantText, streaming: false });
|
|
1436
|
+
}
|
|
1437
|
+
// Finalize pending tool cards so they don't stay "Running..." forever
|
|
1438
|
+
// after cancellation. Without this, the spinner vanishes and TurnDone
|
|
1439
|
+
// shows "cancelled", but in-flight tool cards remain in a perpetual
|
|
1440
|
+
// pending/blinking state because the normal finalize path (line 992)
|
|
1441
|
+
// was skipped when the error interrupted the try block.
|
|
1442
|
+
flushToolResults([], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true });
|
|
1443
|
+
finalizeToolHeaders();
|
|
1444
|
+
} else {
|
|
1445
|
+
finalizeToolHeaders();
|
|
1446
|
+
pushNotice(toolErrorDisplay(error, 'turn'), 'error');
|
|
1447
|
+
}
|
|
1448
|
+
} finally {
|
|
1449
|
+
const reclaimed = cancelled && activePromptRestore?.reclaimed === true;
|
|
1450
|
+
activePromptRestore = null;
|
|
1451
|
+
closeThinkingSegment();
|
|
1452
|
+
const elapsedMs = Date.now() - startedAt;
|
|
1453
|
+
const thinkingElapsedMs = thinkingStartedAt ? accumulatedThinkingMs : 0;
|
|
1454
|
+
const finalOutputTokens = Math.max(0, Number(state.spinner?.outputTokens || 0), Math.round(Number(state.spinner?.responseLength || 0) / 4));
|
|
1455
|
+
const turnStatus = cancelled ? 'cancelled' : 'done';
|
|
1456
|
+
// Pin the post-think summary into the transcript right after this turn's
|
|
1457
|
+
// output so it scrolls up with the answer and stays in the scrollback,
|
|
1458
|
+
// mirroring Claude Code. (Previously TurnDone rendered only in the
|
|
1459
|
+
// bottom-fixed live-status slot and vanished on the next turn.)
|
|
1460
|
+
if (!reclaimed) {
|
|
1461
|
+
pushItem({ kind: 'turndone', id: nextId(), elapsedMs, status: turnStatus, outputTokens: finalOutputTokens, thinkingElapsedMs, verb: pickDoneVerb(turnIndex) });
|
|
1462
|
+
if (!cancelled && autoCompactStatus?.changed) {
|
|
1463
|
+
const before = formatTokenCount(autoCompactStatus.beforeTokens || autoCompactStatus.pressureTokens || 0);
|
|
1464
|
+
const after = formatTokenCount(autoCompactStatus.afterTokens || 0);
|
|
1465
|
+
const trigger = formatTokenCount(autoCompactStatus.triggerTokens || 0);
|
|
1466
|
+
pushItem({
|
|
1467
|
+
kind: 'statusdone',
|
|
1468
|
+
id: nextId(),
|
|
1469
|
+
label: 'Auto-compact complete',
|
|
1470
|
+
detail: [`context ${before}→${after}`, trigger !== '0' ? `trigger ${trigger}` : '']
|
|
1471
|
+
.filter(Boolean)
|
|
1472
|
+
.join(' · '),
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
set({
|
|
1477
|
+
busy: false,
|
|
1478
|
+
spinner: null,
|
|
1479
|
+
thinking: null,
|
|
1480
|
+
lastTurn: null,
|
|
1481
|
+
stats: { ...state.stats },
|
|
1482
|
+
...routeState(),
|
|
1483
|
+
toolMode: runtime.toolMode,
|
|
1484
|
+
...bridgeStatusState(),
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
return cancelled ? 'cancelled' : 'done';
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
const pending = [];
|
|
1491
|
+
let draining = false;
|
|
1492
|
+
let activePromptRestore = null;
|
|
1493
|
+
|
|
1494
|
+
function makeQueueEntry(text, options = {}) {
|
|
1495
|
+
const mode = options.mode || 'prompt';
|
|
1496
|
+
const priority = options.priority || defaultQueuePriority(mode);
|
|
1497
|
+
const displayText = promptDisplayText(text, options);
|
|
1498
|
+
return {
|
|
1499
|
+
id: options.id || nextId(),
|
|
1500
|
+
text: displayText,
|
|
1501
|
+
content: text,
|
|
1502
|
+
pastedImages: options.pastedImages && typeof options.pastedImages === 'object' ? options.pastedImages : null,
|
|
1503
|
+
onCommitted: typeof options.onCommitted === 'function' ? options.onCommitted : null,
|
|
1504
|
+
mode,
|
|
1505
|
+
priority,
|
|
1506
|
+
key: options.key || null,
|
|
1507
|
+
displayText: mode === 'task-notification' ? notificationDisplayText(displayText) : String(displayText || ''),
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function removeQueuedEntries(entries) {
|
|
1512
|
+
const ids = new Set(entries.map((entry) => entry.id));
|
|
1513
|
+
const keys = entries.map((entry) => entry.key).filter(Boolean);
|
|
1514
|
+
for (const key of keys) pendingNotificationKeys.delete(key);
|
|
1515
|
+
set({ queued: state.queued.filter((q) => !ids.has(q.id)) });
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function requeueEntriesFront(entries) {
|
|
1519
|
+
const restored = [];
|
|
1520
|
+
for (const entry of entries || []) {
|
|
1521
|
+
if (!entry || !String(entry.text || '').trim()) continue;
|
|
1522
|
+
const next = {
|
|
1523
|
+
...entry,
|
|
1524
|
+
displayText: entry.displayText || (entry.mode === 'task-notification' ? notificationDisplayText(entry.text) : String(entry.text || '')),
|
|
1525
|
+
};
|
|
1526
|
+
if (next.mode === 'task-notification' && next.key) {
|
|
1527
|
+
if (pendingNotificationKeys.has(next.key)) continue;
|
|
1528
|
+
pendingNotificationKeys.add(next.key);
|
|
1529
|
+
}
|
|
1530
|
+
restored.push(next);
|
|
1531
|
+
}
|
|
1532
|
+
if (restored.length === 0) return false;
|
|
1533
|
+
pending.unshift(...restored);
|
|
1534
|
+
set({ queued: [...restored, ...state.queued] });
|
|
1535
|
+
return true;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function dequeueQueueBatch(maxPriority = 'later') {
|
|
1539
|
+
if (pending.length === 0) return [];
|
|
1540
|
+
const max = queuePriorityValue(maxPriority);
|
|
1541
|
+
let bestPriority = Infinity;
|
|
1542
|
+
let targetMode = null;
|
|
1543
|
+
for (const entry of pending) {
|
|
1544
|
+
const p = queuePriorityValue(entry.priority);
|
|
1545
|
+
if (p > max) continue;
|
|
1546
|
+
if (p < bestPriority) {
|
|
1547
|
+
bestPriority = p;
|
|
1548
|
+
targetMode = entry.mode || 'prompt';
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (!targetMode) return [];
|
|
1552
|
+
const batch = [];
|
|
1553
|
+
for (let i = 0; i < pending.length;) {
|
|
1554
|
+
const entry = pending[i];
|
|
1555
|
+
if ((entry.mode || 'prompt') === targetMode && queuePriorityValue(entry.priority) === bestPriority) {
|
|
1556
|
+
batch.push(entry);
|
|
1557
|
+
pending.splice(i, 1);
|
|
1558
|
+
} else {
|
|
1559
|
+
i += 1;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
removeQueuedEntries(batch);
|
|
1563
|
+
return batch;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
async function drain() {
|
|
1567
|
+
if (draining) return;
|
|
1568
|
+
draining = true;
|
|
1569
|
+
try {
|
|
1570
|
+
while (pending.length > 0) {
|
|
1571
|
+
// Drain one priority/mode bucket at a time, matching Claude Code's
|
|
1572
|
+
// unified command queue semantics: prompt steering stays editable and
|
|
1573
|
+
// task notifications stay non-editable but model-visible.
|
|
1574
|
+
const batch = dequeueQueueBatch('later');
|
|
1575
|
+
if (batch.length === 0) break;
|
|
1576
|
+
const ids = new Set(batch.map((e) => e.id));
|
|
1577
|
+
const merged = mergePromptContents(batch);
|
|
1578
|
+
for (const entry of batch) {
|
|
1579
|
+
pushUserOrSyntheticItem(entry.text, entry.id);
|
|
1580
|
+
}
|
|
1581
|
+
const nonEditable = batch.filter((entry) => !isQueuedEntryEditable(entry));
|
|
1582
|
+
const batchPastedImages = mergePastedImages(batch);
|
|
1583
|
+
const turnStatus = await runTurn(merged, {
|
|
1584
|
+
displayText: batch.map((entry) => entry.text).filter((text) => String(text || '').trim()).join('\n'),
|
|
1585
|
+
pastedImages: batchPastedImages,
|
|
1586
|
+
onCommitted: () => callCommitCallbacks(batch),
|
|
1587
|
+
submittedIds: [...ids],
|
|
1588
|
+
restorable: nonEditable.length === 0,
|
|
1589
|
+
requeueOnAbort: nonEditable,
|
|
1590
|
+
});
|
|
1591
|
+
// If the user re-submits the reclaimed prompt while the cancelled turn
|
|
1592
|
+
// is still unwinding, enqueue() cannot start another drain because this
|
|
1593
|
+
// drain loop is still active. Continue when pending work appeared during
|
|
1594
|
+
// cancellation so the fresh submit does not get stuck in queued state.
|
|
1595
|
+
if (turnStatus === 'cancelled' && pending.length === 0) break;
|
|
1596
|
+
}
|
|
1597
|
+
} finally {
|
|
1598
|
+
draining = false;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
function enqueue(text, options = {}) {
|
|
1602
|
+
const entry = makeQueueEntry(text, options);
|
|
1603
|
+
if (entry.mode === 'task-notification' && entry.key) {
|
|
1604
|
+
if (pendingNotificationKeys.has(entry.key)) return false;
|
|
1605
|
+
pendingNotificationKeys.add(entry.key);
|
|
1606
|
+
}
|
|
1607
|
+
pending.push(entry);
|
|
1608
|
+
set({ queued: [...state.queued, entry] });
|
|
1609
|
+
void drain();
|
|
1610
|
+
return true;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function drainPendingSteering() {
|
|
1614
|
+
const batch = dequeueQueueBatch('next');
|
|
1615
|
+
if (batch.length === 0) return [];
|
|
1616
|
+
const out = batch
|
|
1617
|
+
.map((entry) => {
|
|
1618
|
+
const content = entry.content;
|
|
1619
|
+
if (typeof content === 'string') return content.trim();
|
|
1620
|
+
return { text: String(entry.text || '').trim(), content };
|
|
1621
|
+
})
|
|
1622
|
+
.filter((entry) => {
|
|
1623
|
+
if (typeof entry === 'string') return entry.length > 0;
|
|
1624
|
+
if (Array.isArray(entry?.content)) return entry.content.length > 0;
|
|
1625
|
+
return String(entry?.content ?? '').trim().length > 0;
|
|
1626
|
+
});
|
|
1627
|
+
callCommitCallbacks(batch);
|
|
1628
|
+
return out;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
async function autoClearBeforeSubmit() {
|
|
1632
|
+
const cfg = autoClearState();
|
|
1633
|
+
const now = Date.now();
|
|
1634
|
+
const idleMs = now - lastUserActivityAt;
|
|
1635
|
+
if (!cfg.enabled || state.busy || pending.length > 0 || autoClearRunning || idleMs < cfg.idleMs) {
|
|
1636
|
+
lastUserActivityAt = now;
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1639
|
+
autoClearRunning = true;
|
|
1640
|
+
try {
|
|
1641
|
+
const beforeContext = runtime.contextStatus?.() || null;
|
|
1642
|
+
await runtime.clear();
|
|
1643
|
+
resetStats();
|
|
1644
|
+
const afterContext = syncContextStats({ allowEstimated: true }) || runtime.contextStatus?.() || null;
|
|
1645
|
+
const idleLabel = formatIdleDuration(idleMs);
|
|
1646
|
+
const thresholdLabel = formatIdleDuration(cfg.idleMs);
|
|
1647
|
+
const beforeTokens = Number(beforeContext?.usedTokens || beforeContext?.currentEstimatedTokens || beforeContext?.usage?.lastContextTokens || 0);
|
|
1648
|
+
const afterTokens = Number(afterContext?.usedTokens || afterContext?.currentEstimatedTokens || afterContext?.usage?.lastContextTokens || 0);
|
|
1649
|
+
const contextDetail = beforeTokens > 0 || afterTokens > 0
|
|
1650
|
+
? `context ${formatTokenCount(beforeTokens)}→${formatTokenCount(afterTokens)}`
|
|
1651
|
+
: 'context reset';
|
|
1652
|
+
set({
|
|
1653
|
+
items: replaceItems([]),
|
|
1654
|
+
toasts: [],
|
|
1655
|
+
queued: [],
|
|
1656
|
+
thinking: null,
|
|
1657
|
+
spinner: null,
|
|
1658
|
+
lastTurn: null,
|
|
1659
|
+
...routeState(),
|
|
1660
|
+
stats: { ...state.stats },
|
|
1661
|
+
});
|
|
1662
|
+
pushItem({
|
|
1663
|
+
kind: 'statusdone',
|
|
1664
|
+
id: nextId(),
|
|
1665
|
+
label: 'Auto-clear complete',
|
|
1666
|
+
detail: [idleLabel ? `idle ${idleLabel}` : '', contextDetail, thresholdLabel ? `threshold ${thresholdLabel}` : '']
|
|
1667
|
+
.filter(Boolean)
|
|
1668
|
+
.join(' · '),
|
|
1669
|
+
});
|
|
1670
|
+
return true;
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
pushNotice(`auto-clear failed: ${error?.message || error}`, 'error');
|
|
1673
|
+
return false;
|
|
1674
|
+
} finally {
|
|
1675
|
+
lastUserActivityAt = Date.now();
|
|
1676
|
+
autoClearRunning = false;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
function restoreQueued(currentText = '') {
|
|
1681
|
+
const queued = [];
|
|
1682
|
+
for (let i = 0; i < pending.length;) {
|
|
1683
|
+
const entry = pending[i];
|
|
1684
|
+
if (isQueuedEntryEditable(entry)) {
|
|
1685
|
+
queued.push(entry);
|
|
1686
|
+
pending.splice(i, 1);
|
|
1687
|
+
} else {
|
|
1688
|
+
i += 1;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
removeQueuedEntries(queued);
|
|
1692
|
+
const queuedText = queued.map((item) => item.text).filter((text) => String(text || '').trim()).join('\n');
|
|
1693
|
+
const combinedText = [queuedText, String(currentText || '')].filter((text) => text.trim()).join('\n');
|
|
1694
|
+
return { count: queued.length, text: combinedText, pastedImages: mergePastedImages(queued) };
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
const resetStats = () => {
|
|
1698
|
+
state.stats = createSessionStats();
|
|
1699
|
+
return state.stats;
|
|
1700
|
+
};
|
|
1701
|
+
const resetStatsAndSyncContext = () => {
|
|
1702
|
+
resetStats();
|
|
1703
|
+
syncContextStats({ allowEstimated: true });
|
|
1704
|
+
return state.stats;
|
|
1705
|
+
};
|
|
1706
|
+
|
|
1707
|
+
return {
|
|
1708
|
+
getState: () => state,
|
|
1709
|
+
patchItem,
|
|
1710
|
+
subscribe: (listener) => {
|
|
1711
|
+
listeners.add(listener);
|
|
1712
|
+
return () => listeners.delete(listener);
|
|
1713
|
+
},
|
|
1714
|
+
submit: (text, options = {}) => {
|
|
1715
|
+
const t = promptDisplayText(text, options).trim();
|
|
1716
|
+
if (!t || state.commandBusy) return false;
|
|
1717
|
+
const mode = options.mode || 'prompt';
|
|
1718
|
+
// Plain user input entered while a turn is busy is a post-turn follow-up
|
|
1719
|
+
// by default, so it lands after any auto-compact/save boundary instead of
|
|
1720
|
+
// being swallowed as mid-turn steering. Task notifications still opt into
|
|
1721
|
+
// priority: 'next' and are injected at the next tool boundary.
|
|
1722
|
+
const priority = options.priority;
|
|
1723
|
+
const queueOptions = {
|
|
1724
|
+
...options,
|
|
1725
|
+
mode,
|
|
1726
|
+
displayText: promptDisplayText(text, options),
|
|
1727
|
+
priority,
|
|
1728
|
+
};
|
|
1729
|
+
if (state.busy) {
|
|
1730
|
+
enqueue(text, queueOptions);
|
|
1731
|
+
return true;
|
|
1732
|
+
}
|
|
1733
|
+
void autoClearBeforeSubmit().then(() => enqueue(text, queueOptions));
|
|
1734
|
+
return true;
|
|
1735
|
+
},
|
|
1736
|
+
restoreQueued,
|
|
1737
|
+
setModel: async (m) => {
|
|
1738
|
+
if (state.commandBusy) return false;
|
|
1739
|
+
set({ commandBusy: true });
|
|
1740
|
+
try {
|
|
1741
|
+
await runtime.setRoute({ model: m });
|
|
1742
|
+
resetStatsAndSyncContext();
|
|
1743
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1744
|
+
return true;
|
|
1745
|
+
} finally {
|
|
1746
|
+
set({ commandBusy: false });
|
|
1747
|
+
}
|
|
1748
|
+
},
|
|
1749
|
+
setEffort: async (value) => {
|
|
1750
|
+
if (state.commandBusy) return false;
|
|
1751
|
+
set({ commandBusy: true });
|
|
1752
|
+
try {
|
|
1753
|
+
await runtime.setEffort(value);
|
|
1754
|
+
set({ ...routeState() });
|
|
1755
|
+
return runtime.effort || 'auto';
|
|
1756
|
+
} finally {
|
|
1757
|
+
set({ commandBusy: false });
|
|
1758
|
+
}
|
|
1759
|
+
},
|
|
1760
|
+
setFast: async (value) => {
|
|
1761
|
+
if (state.commandBusy) return null;
|
|
1762
|
+
set({ commandBusy: true });
|
|
1763
|
+
try {
|
|
1764
|
+
const enabled = await runtime.setFast(value);
|
|
1765
|
+
set({ ...routeState() });
|
|
1766
|
+
return enabled;
|
|
1767
|
+
} finally {
|
|
1768
|
+
set({ commandBusy: false });
|
|
1769
|
+
}
|
|
1770
|
+
},
|
|
1771
|
+
toggleFast: async () => {
|
|
1772
|
+
if (state.commandBusy) return null;
|
|
1773
|
+
set({ commandBusy: true });
|
|
1774
|
+
try {
|
|
1775
|
+
const enabled = await runtime.toggleFast();
|
|
1776
|
+
set({ ...routeState() });
|
|
1777
|
+
return enabled;
|
|
1778
|
+
} finally {
|
|
1779
|
+
set({ commandBusy: false });
|
|
1780
|
+
}
|
|
1781
|
+
},
|
|
1782
|
+
setToolMode: (m) => {
|
|
1783
|
+
void runtime.setToolMode(m)
|
|
1784
|
+
.then(() => {
|
|
1785
|
+
resetStatsAndSyncContext();
|
|
1786
|
+
set({ ...routeState(), toolMode: runtime.toolMode, stats: { ...state.stats } });
|
|
1787
|
+
})
|
|
1788
|
+
.catch((error) => pushNotice(toolErrorDisplay(error, 'tool'), 'error'));
|
|
1789
|
+
},
|
|
1790
|
+
toggleBridgeMode: () => {
|
|
1791
|
+
const mode = runtime.toggleBridgeMode();
|
|
1792
|
+
set(bridgeStatusState({ force: true }));
|
|
1793
|
+
pushNotice(`bridge mode -> ${mode}`, 'info');
|
|
1794
|
+
return mode;
|
|
1795
|
+
},
|
|
1796
|
+
setBridgeMode: (mode) => {
|
|
1797
|
+
const next = runtime.setBridgeMode(mode);
|
|
1798
|
+
set(bridgeStatusState({ force: true }));
|
|
1799
|
+
return next;
|
|
1800
|
+
},
|
|
1801
|
+
getAutoClear: () => autoClearState(),
|
|
1802
|
+
setAutoClear: (input = {}) => {
|
|
1803
|
+
const next = runtime.setAutoClear?.(input) || autoClearState();
|
|
1804
|
+
set({ autoClear: next });
|
|
1805
|
+
return next;
|
|
1806
|
+
},
|
|
1807
|
+
bridgeControl: async (args = {}) => {
|
|
1808
|
+
if (state.commandBusy) return null;
|
|
1809
|
+
set({ commandBusy: true });
|
|
1810
|
+
try {
|
|
1811
|
+
const result = await runtime.bridgeControl(args);
|
|
1812
|
+
const text = String(result || '').trim() || '(empty bridge result)';
|
|
1813
|
+
const itemId = nextId();
|
|
1814
|
+
pushItem({
|
|
1815
|
+
kind: 'tool',
|
|
1816
|
+
id: itemId,
|
|
1817
|
+
name: 'bridge',
|
|
1818
|
+
args,
|
|
1819
|
+
result: null,
|
|
1820
|
+
isError: false,
|
|
1821
|
+
expanded: false,
|
|
1822
|
+
count: 1,
|
|
1823
|
+
completedCount: 0,
|
|
1824
|
+
startedAt: Date.now(),
|
|
1825
|
+
});
|
|
1826
|
+
updateBridgeJobCard(itemId, text, /^error:/i.test(text));
|
|
1827
|
+
set(bridgeStatusState({ force: true }));
|
|
1828
|
+
return result;
|
|
1829
|
+
} finally {
|
|
1830
|
+
set({ commandBusy: false });
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
toolsStatus: (query = '') => {
|
|
1834
|
+
return runtime.toolsStatus?.(query) || { mode: state.toolMode, count: 0, activeCount: 0, tools: [] };
|
|
1835
|
+
},
|
|
1836
|
+
selectTools: (names) => {
|
|
1837
|
+
const result = runtime.selectTools?.(names) || { added: [], already: [], blocked: [], missing: [] };
|
|
1838
|
+
const added = result.added?.length ? `added ${result.added.join(', ')}` : '';
|
|
1839
|
+
const already = result.already?.length ? `already ${result.already.join(', ')}` : '';
|
|
1840
|
+
const blocked = result.blocked?.length ? `blocked ${result.blocked.map((row) => row.name).join(', ')}` : '';
|
|
1841
|
+
const missing = result.missing?.length ? `missing ${result.missing.join(', ')}` : '';
|
|
1842
|
+
pushNotice(
|
|
1843
|
+
[added, already, blocked, missing].filter(Boolean).join(' - ') || 'no tool changes',
|
|
1844
|
+
result.blocked?.length || result.missing?.length ? 'warn' : 'info',
|
|
1845
|
+
);
|
|
1846
|
+
return result;
|
|
1847
|
+
},
|
|
1848
|
+
setCwd: (path) => {
|
|
1849
|
+
const next = runtime.setCwd(path);
|
|
1850
|
+
set({ cwd: next });
|
|
1851
|
+
pushNotice(`cwd -> ${next}`, 'info');
|
|
1852
|
+
return next;
|
|
1853
|
+
},
|
|
1854
|
+
getSystemShell: () => {
|
|
1855
|
+
return runtime.getSystemShell?.() || runtime.systemShell || { source: 'auto', command: '', effective: '' };
|
|
1856
|
+
},
|
|
1857
|
+
setSystemShell: (command) => {
|
|
1858
|
+
const next = runtime.setSystemShell?.(command) || { source: 'auto', command: '', effective: '' };
|
|
1859
|
+
set({ ...routeState(), systemShell: next });
|
|
1860
|
+
pushNotice(`system shell -> ${next.effective || 'auto'}`, 'info');
|
|
1861
|
+
return next;
|
|
1862
|
+
},
|
|
1863
|
+
mcpStatus: () => {
|
|
1864
|
+
return runtime.mcpStatus?.() || { servers: [], configuredCount: 0, connectedCount: 0, failedCount: 0 };
|
|
1865
|
+
},
|
|
1866
|
+
reconnectMcp: async () => {
|
|
1867
|
+
if (state.commandBusy) return null;
|
|
1868
|
+
set({ commandBusy: true });
|
|
1869
|
+
try {
|
|
1870
|
+
const status = await runtime.reconnectMcp?.();
|
|
1871
|
+
resetStatsAndSyncContext();
|
|
1872
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1873
|
+
pushNotice(
|
|
1874
|
+
`mcp reconnect: ${status?.connectedCount || 0}/${status?.configuredCount || 0} connected${status?.failedCount ? ` - ${status.failedCount} failed` : ''}`,
|
|
1875
|
+
status?.failedCount ? 'warn' : 'info',
|
|
1876
|
+
);
|
|
1877
|
+
return status;
|
|
1878
|
+
} finally {
|
|
1879
|
+
set({ commandBusy: false });
|
|
1880
|
+
}
|
|
1881
|
+
},
|
|
1882
|
+
addMcpServer: async (input) => {
|
|
1883
|
+
if (state.commandBusy) return null;
|
|
1884
|
+
set({ commandBusy: true });
|
|
1885
|
+
try {
|
|
1886
|
+
const result = await runtime.addMcpServer?.(input);
|
|
1887
|
+
resetStatsAndSyncContext();
|
|
1888
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1889
|
+
pushNotice(`mcp added: ${result?.name || input?.name || 'server'}`, 'info');
|
|
1890
|
+
return result;
|
|
1891
|
+
} finally {
|
|
1892
|
+
set({ commandBusy: false });
|
|
1893
|
+
}
|
|
1894
|
+
},
|
|
1895
|
+
removeMcpServer: async (name) => {
|
|
1896
|
+
if (state.commandBusy) return null;
|
|
1897
|
+
set({ commandBusy: true });
|
|
1898
|
+
try {
|
|
1899
|
+
const status = await runtime.removeMcpServer?.(name);
|
|
1900
|
+
resetStatsAndSyncContext();
|
|
1901
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1902
|
+
pushNotice(`mcp removed: ${name}`, 'info');
|
|
1903
|
+
return status;
|
|
1904
|
+
} finally {
|
|
1905
|
+
set({ commandBusy: false });
|
|
1906
|
+
}
|
|
1907
|
+
},
|
|
1908
|
+
setMcpServerEnabled: async (name, enabled) => {
|
|
1909
|
+
if (state.commandBusy) return null;
|
|
1910
|
+
set({ commandBusy: true });
|
|
1911
|
+
try {
|
|
1912
|
+
const status = await runtime.setMcpServerEnabled?.(name, enabled);
|
|
1913
|
+
resetStatsAndSyncContext();
|
|
1914
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1915
|
+
pushNotice(`mcp ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
|
|
1916
|
+
return status;
|
|
1917
|
+
} finally {
|
|
1918
|
+
set({ commandBusy: false });
|
|
1919
|
+
}
|
|
1920
|
+
},
|
|
1921
|
+
skillsStatus: () => {
|
|
1922
|
+
return runtime.skillsStatus?.() || { cwd: state.cwd, count: 0, skills: [] };
|
|
1923
|
+
},
|
|
1924
|
+
skillContent: (name) => {
|
|
1925
|
+
return runtime.skillContent?.(name);
|
|
1926
|
+
},
|
|
1927
|
+
addSkill: async (input) => {
|
|
1928
|
+
if (state.commandBusy) return null;
|
|
1929
|
+
set({ commandBusy: true });
|
|
1930
|
+
try {
|
|
1931
|
+
const result = await runtime.addSkill?.(input);
|
|
1932
|
+
resetStatsAndSyncContext();
|
|
1933
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1934
|
+
pushNotice(`skill added: ${result?.skill?.name || input?.name || 'skill'}`, 'info');
|
|
1935
|
+
return result;
|
|
1936
|
+
} finally {
|
|
1937
|
+
set({ commandBusy: false });
|
|
1938
|
+
}
|
|
1939
|
+
},
|
|
1940
|
+
reloadSkills: async () => {
|
|
1941
|
+
if (state.commandBusy) return null;
|
|
1942
|
+
set({ commandBusy: true });
|
|
1943
|
+
try {
|
|
1944
|
+
const status = await runtime.reloadSkills?.();
|
|
1945
|
+
resetStatsAndSyncContext();
|
|
1946
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1947
|
+
pushNotice(`skills reload: ${status?.count || 0} available`, 'info');
|
|
1948
|
+
return status;
|
|
1949
|
+
} finally {
|
|
1950
|
+
set({ commandBusy: false });
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1953
|
+
pluginsStatus: () => {
|
|
1954
|
+
return runtime.pluginsStatus?.() || { count: 0, plugins: [] };
|
|
1955
|
+
},
|
|
1956
|
+
reloadPlugins: async () => {
|
|
1957
|
+
if (state.commandBusy) return null;
|
|
1958
|
+
set({ commandBusy: true });
|
|
1959
|
+
try {
|
|
1960
|
+
const status = await runtime.reloadPlugins?.();
|
|
1961
|
+
resetStatsAndSyncContext();
|
|
1962
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1963
|
+
pushNotice(`plugins reload: ${status?.count || 0} detected`, 'info');
|
|
1964
|
+
return status;
|
|
1965
|
+
} finally {
|
|
1966
|
+
set({ commandBusy: false });
|
|
1967
|
+
}
|
|
1968
|
+
},
|
|
1969
|
+
addPlugin: async (source) => {
|
|
1970
|
+
if (state.commandBusy) return null;
|
|
1971
|
+
set({ commandBusy: true });
|
|
1972
|
+
try {
|
|
1973
|
+
const result = await runtime.addPlugin?.(source);
|
|
1974
|
+
resetStatsAndSyncContext();
|
|
1975
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1976
|
+
pushNotice(`plugin added: ${result?.plugin?.title || result?.plugin?.name || source}`, 'info');
|
|
1977
|
+
return result;
|
|
1978
|
+
} finally {
|
|
1979
|
+
set({ commandBusy: false });
|
|
1980
|
+
}
|
|
1981
|
+
},
|
|
1982
|
+
updatePlugin: async (plugin) => {
|
|
1983
|
+
if (state.commandBusy) return null;
|
|
1984
|
+
set({ commandBusy: true });
|
|
1985
|
+
try {
|
|
1986
|
+
const result = await runtime.updatePlugin?.(plugin);
|
|
1987
|
+
resetStatsAndSyncContext();
|
|
1988
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
1989
|
+
pushNotice(`plugin updated: ${result?.plugin?.title || result?.plugin?.name || plugin?.name || plugin}`, 'info');
|
|
1990
|
+
return result;
|
|
1991
|
+
} finally {
|
|
1992
|
+
set({ commandBusy: false });
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1995
|
+
removePlugin: async (plugin) => {
|
|
1996
|
+
if (state.commandBusy) return null;
|
|
1997
|
+
set({ commandBusy: true });
|
|
1998
|
+
try {
|
|
1999
|
+
const result = await runtime.removePlugin?.(plugin);
|
|
2000
|
+
resetStatsAndSyncContext();
|
|
2001
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
2002
|
+
pushNotice(`plugin uninstalled: ${result?.plugin?.title || result?.plugin?.name || plugin?.name || plugin}`, 'info');
|
|
2003
|
+
return result;
|
|
2004
|
+
} finally {
|
|
2005
|
+
set({ commandBusy: false });
|
|
2006
|
+
}
|
|
2007
|
+
},
|
|
2008
|
+
enablePluginMcp: async (plugin) => {
|
|
2009
|
+
if (state.commandBusy) return null;
|
|
2010
|
+
set({ commandBusy: true });
|
|
2011
|
+
try {
|
|
2012
|
+
const result = await runtime.enablePluginMcp?.(plugin);
|
|
2013
|
+
resetStatsAndSyncContext();
|
|
2014
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
2015
|
+
pushNotice(`plugin MCP enabled: ${result?.serverName || plugin?.name || 'plugin'}`, 'info');
|
|
2016
|
+
return result;
|
|
2017
|
+
} finally {
|
|
2018
|
+
set({ commandBusy: false });
|
|
2019
|
+
}
|
|
2020
|
+
},
|
|
2021
|
+
hooksStatus: () => {
|
|
2022
|
+
return runtime.hooksStatus?.() || { enabled: false, events: [], recent: [] };
|
|
2023
|
+
},
|
|
2024
|
+
contextStatus: () => {
|
|
2025
|
+
return runtime.contextStatus?.() || null;
|
|
2026
|
+
},
|
|
2027
|
+
addHookRule: (rule) => {
|
|
2028
|
+
const rules = runtime.addHookRule?.(rule) || [];
|
|
2029
|
+
pushNotice(`hook rule added (${rules.length} total)`, 'info');
|
|
2030
|
+
return rules;
|
|
2031
|
+
},
|
|
2032
|
+
setHookRuleEnabled: (index, enabled) => {
|
|
2033
|
+
const rules = runtime.setHookRuleEnabled?.(index, enabled) || [];
|
|
2034
|
+
pushNotice(`hook rule ${index + 1} ${enabled ? 'enabled' : 'disabled'}`, 'info');
|
|
2035
|
+
return rules;
|
|
2036
|
+
},
|
|
2037
|
+
deleteHookRule: (index) => {
|
|
2038
|
+
const rules = runtime.deleteHookRule?.(index) || [];
|
|
2039
|
+
pushNotice(`hook rule ${index + 1} deleted`, 'info');
|
|
2040
|
+
return rules;
|
|
2041
|
+
},
|
|
2042
|
+
memoryControl: async (args = {}, options = {}) => {
|
|
2043
|
+
if (state.commandBusy) return null;
|
|
2044
|
+
set({ commandBusy: true });
|
|
2045
|
+
try {
|
|
2046
|
+
const result = await runtime.memoryControl(args);
|
|
2047
|
+
const text = String(result || '').trim() || '(empty memory result)';
|
|
2048
|
+
if (!options.silent) pushNotice(text, 'info');
|
|
2049
|
+
return result;
|
|
2050
|
+
} finally {
|
|
2051
|
+
set({ commandBusy: false });
|
|
2052
|
+
}
|
|
2053
|
+
},
|
|
2054
|
+
recall: async (query, args = {}) => {
|
|
2055
|
+
if (state.commandBusy) return null;
|
|
2056
|
+
const startedAt = Date.now();
|
|
2057
|
+
set({ commandBusy: true, commandStatus: { active: true, verb: 'Recalling memory', startedAt, mode: 'recalling' } });
|
|
2058
|
+
try {
|
|
2059
|
+
const result = await runtime.recall(query, args);
|
|
2060
|
+
pushNotice(String(result || '').trim() || '(empty recall result)', 'info');
|
|
2061
|
+
return result;
|
|
2062
|
+
} finally {
|
|
2063
|
+
set({ commandBusy: false, commandStatus: null });
|
|
2064
|
+
}
|
|
2065
|
+
},
|
|
2066
|
+
compact: async () => {
|
|
2067
|
+
if (state.commandBusy) return null;
|
|
2068
|
+
const startedAt = Date.now();
|
|
2069
|
+
set({ commandBusy: true, commandStatus: { active: true, verb: 'Compacting conversation', startedAt, mode: 'compacting' } });
|
|
2070
|
+
try {
|
|
2071
|
+
const result = await runtime.compact({ recoverBridge: true });
|
|
2072
|
+
syncContextStats({ allowEstimated: true });
|
|
2073
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
2074
|
+
if (result) {
|
|
2075
|
+
pushItem({ kind: 'turndone', id: nextId(), elapsedMs: Date.now() - startedAt, status: 'done', outputTokens: 0, thinkingElapsedMs: 0, verb: 'Compacted' });
|
|
2076
|
+
}
|
|
2077
|
+
return result;
|
|
2078
|
+
} finally {
|
|
2079
|
+
set({ commandBusy: false, commandStatus: null });
|
|
2080
|
+
}
|
|
2081
|
+
},
|
|
2082
|
+
abort: () => {
|
|
2083
|
+
if (!state.busy) return false;
|
|
2084
|
+
const restoreState = activePromptRestore;
|
|
2085
|
+
const restoreText = restoreState?.restorable ? restoreState.text : '';
|
|
2086
|
+
const restorePastedImages = restoreState?.restorable && restoreState?.pastedImages ? restoreState.pastedImages : null;
|
|
2087
|
+
const requeueEntries = restoreState && !restoreState.committed && Array.isArray(restoreState.requeueEntries)
|
|
2088
|
+
? restoreState.requeueEntries.slice()
|
|
2089
|
+
: [];
|
|
2090
|
+
const aborted = runtime.abort('cli-react-abort');
|
|
2091
|
+
if (restoreState) {
|
|
2092
|
+
if ((restoreText || requeueEntries.length > 0) && aborted !== false) {
|
|
2093
|
+
restoreState.reclaimed = true;
|
|
2094
|
+
const idSet = new Set((restoreState.submittedIds || []).filter((id) => id != null));
|
|
2095
|
+
const patch = { spinner: null, thinking: null, lastTurn: null };
|
|
2096
|
+
if (idSet.size > 0) {
|
|
2097
|
+
const items = state.items.filter((item) => !idSet.has(item?.id));
|
|
2098
|
+
if (items.length !== state.items.length) {
|
|
2099
|
+
patch.items = replaceItems(items);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
set(patch);
|
|
2103
|
+
if (requeueEntries.length > 0) requeueEntriesFront(requeueEntries);
|
|
2104
|
+
}
|
|
2105
|
+
restoreState.restorable = false;
|
|
2106
|
+
restoreState.requeueEntries = [];
|
|
2107
|
+
}
|
|
2108
|
+
return { aborted, restoreText, pastedImages: restorePastedImages };
|
|
2109
|
+
},
|
|
2110
|
+
listPresets: () => {
|
|
2111
|
+
return runtime.listPresets();
|
|
2112
|
+
},
|
|
2113
|
+
listProviderModels: (options = {}) => {
|
|
2114
|
+
return runtime.listProviderModels(options);
|
|
2115
|
+
},
|
|
2116
|
+
listAgents: () => {
|
|
2117
|
+
return runtime.listAgents?.() || [];
|
|
2118
|
+
},
|
|
2119
|
+
listWorkflows: () => {
|
|
2120
|
+
return runtime.listWorkflows?.() || [];
|
|
2121
|
+
},
|
|
2122
|
+
getOutputStyle: () => {
|
|
2123
|
+
return runtime.getOutputStyle?.() || runtime.listOutputStyles?.() || null;
|
|
2124
|
+
},
|
|
2125
|
+
listOutputStyles: () => {
|
|
2126
|
+
return runtime.listOutputStyles?.() || runtime.getOutputStyle?.() || { styles: [], current: null, configured: 'default' };
|
|
2127
|
+
},
|
|
2128
|
+
setOutputStyle: async (styleId) => {
|
|
2129
|
+
if (state.commandBusy) return null;
|
|
2130
|
+
set({ commandBusy: true });
|
|
2131
|
+
try {
|
|
2132
|
+
const result = await runtime.setOutputStyle?.(styleId);
|
|
2133
|
+
resetStatsAndSyncContext();
|
|
2134
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
2135
|
+
return result;
|
|
2136
|
+
} finally {
|
|
2137
|
+
set({ commandBusy: false });
|
|
2138
|
+
}
|
|
2139
|
+
},
|
|
2140
|
+
setWorkflow: async (workflowId) => {
|
|
2141
|
+
if (state.commandBusy) return null;
|
|
2142
|
+
set({ commandBusy: true });
|
|
2143
|
+
try {
|
|
2144
|
+
const result = runtime.setWorkflow?.(workflowId);
|
|
2145
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
2146
|
+
return result;
|
|
2147
|
+
} finally {
|
|
2148
|
+
set({ commandBusy: false });
|
|
2149
|
+
}
|
|
2150
|
+
},
|
|
2151
|
+
setAgentRoute: async (agentId, opts) => {
|
|
2152
|
+
return await runtime.setAgentRoute?.(agentId, opts);
|
|
2153
|
+
},
|
|
2154
|
+
listProviders: () => {
|
|
2155
|
+
return runtime.listProviders();
|
|
2156
|
+
},
|
|
2157
|
+
getProviderSetup: () => {
|
|
2158
|
+
return runtime.getProviderSetup();
|
|
2159
|
+
},
|
|
2160
|
+
getUsageDashboard: async (options = {}) => {
|
|
2161
|
+
return await runtime.getUsageDashboard?.(options);
|
|
2162
|
+
},
|
|
2163
|
+
getOnboardingStatus: () => {
|
|
2164
|
+
return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
|
|
2165
|
+
},
|
|
2166
|
+
completeOnboarding: async (payload = {}) => {
|
|
2167
|
+
if (state.commandBusy) return null;
|
|
2168
|
+
set({ commandBusy: true });
|
|
2169
|
+
try {
|
|
2170
|
+
const result = await runtime.completeOnboarding?.(payload);
|
|
2171
|
+
resetStatsAndSyncContext();
|
|
2172
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
2173
|
+
pushNotice('first-run setup saved', 'info');
|
|
2174
|
+
return result;
|
|
2175
|
+
} finally {
|
|
2176
|
+
set({ commandBusy: false });
|
|
2177
|
+
}
|
|
2178
|
+
},
|
|
2179
|
+
loginOAuthProvider: async (provider) => {
|
|
2180
|
+
if (state.commandBusy) return false;
|
|
2181
|
+
set({ commandBusy: true });
|
|
2182
|
+
try {
|
|
2183
|
+
const result = await runtime.loginOAuthProvider(provider);
|
|
2184
|
+
pushNotice(`provider oauth ok: ${result.provider}`, 'info');
|
|
2185
|
+
return true;
|
|
2186
|
+
} finally {
|
|
2187
|
+
set({ commandBusy: false });
|
|
2188
|
+
}
|
|
2189
|
+
},
|
|
2190
|
+
saveProviderApiKey: (provider, secret) => {
|
|
2191
|
+
const result = runtime.saveProviderApiKey(provider, secret);
|
|
2192
|
+
pushNotice(`provider api key saved: ${result.provider}`, 'info');
|
|
2193
|
+
return true;
|
|
2194
|
+
},
|
|
2195
|
+
saveOpenCodeGoUsageAuth: (opts) => {
|
|
2196
|
+
const result = runtime.saveOpenCodeGoUsageAuth(opts);
|
|
2197
|
+
pushNotice(result.workspaceId
|
|
2198
|
+
? `OpenCode Go usage auth saved: ${result.workspaceId}`
|
|
2199
|
+
: 'OpenCode Go usage auth saved',
|
|
2200
|
+
'info');
|
|
2201
|
+
return true;
|
|
2202
|
+
},
|
|
2203
|
+
saveOpenAIUsageSessionKey: (secret) => {
|
|
2204
|
+
runtime.saveOpenAIUsageSessionKey(secret);
|
|
2205
|
+
pushNotice('OpenAI usage auth saved', 'info');
|
|
2206
|
+
return true;
|
|
2207
|
+
},
|
|
2208
|
+
setLocalProvider: (provider, opts) => {
|
|
2209
|
+
const result = runtime.setLocalProvider(provider, opts);
|
|
2210
|
+
pushNotice(`local provider ${result.enabled ? 'enabled' : 'disabled'}: ${result.provider}`, 'info');
|
|
2211
|
+
return true;
|
|
2212
|
+
},
|
|
2213
|
+
authenticateProvider: async (provider, secret) => {
|
|
2214
|
+
if (state.commandBusy) return false;
|
|
2215
|
+
set({ commandBusy: true });
|
|
2216
|
+
try {
|
|
2217
|
+
const result = await runtime.authenticateProvider(provider, secret);
|
|
2218
|
+
pushNotice(`provider auth ok: ${result.provider} (${result.type})`, 'info');
|
|
2219
|
+
return true;
|
|
2220
|
+
} finally {
|
|
2221
|
+
set({ commandBusy: false });
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
forgetProviderAuth: (provider) => {
|
|
2225
|
+
const result = runtime.forgetProviderAuth(provider);
|
|
2226
|
+
pushNotice(`provider auth forgotten: ${result.provider}`, 'info');
|
|
2227
|
+
return true;
|
|
2228
|
+
},
|
|
2229
|
+
getChannelSetup: () => {
|
|
2230
|
+
return runtime.getChannelSetup();
|
|
2231
|
+
},
|
|
2232
|
+
saveDiscordToken: (token) => {
|
|
2233
|
+
const result = runtime.saveDiscordToken(token);
|
|
2234
|
+
pushNotice('discord token saved', 'info');
|
|
2235
|
+
return result;
|
|
2236
|
+
},
|
|
2237
|
+
forgetDiscordToken: () => {
|
|
2238
|
+
const result = runtime.forgetDiscordToken();
|
|
2239
|
+
pushNotice('discord token forgotten', 'info');
|
|
2240
|
+
return result;
|
|
2241
|
+
},
|
|
2242
|
+
saveWebhookAuthtoken: (token) => {
|
|
2243
|
+
const result = runtime.saveWebhookAuthtoken(token);
|
|
2244
|
+
pushNotice('webhook/ngrok authtoken saved', 'info');
|
|
2245
|
+
return result;
|
|
2246
|
+
},
|
|
2247
|
+
forgetWebhookAuthtoken: () => {
|
|
2248
|
+
const result = runtime.forgetWebhookAuthtoken();
|
|
2249
|
+
pushNotice('webhook/ngrok authtoken forgotten', 'info');
|
|
2250
|
+
return result;
|
|
2251
|
+
},
|
|
2252
|
+
saveChannel: (entry) => {
|
|
2253
|
+
const result = runtime.saveChannel(entry);
|
|
2254
|
+
pushNotice(`channel saved: ${entry.name}`, 'info');
|
|
2255
|
+
return result;
|
|
2256
|
+
},
|
|
2257
|
+
deleteChannel: (name) => {
|
|
2258
|
+
const result = runtime.deleteChannel(name);
|
|
2259
|
+
pushNotice(`channel deleted: ${name}`, 'info');
|
|
2260
|
+
return result;
|
|
2261
|
+
},
|
|
2262
|
+
setWebhookConfig: (patch) => {
|
|
2263
|
+
const result = runtime.setWebhookConfig(patch);
|
|
2264
|
+
pushNotice('webhook config updated', 'info');
|
|
2265
|
+
return result;
|
|
2266
|
+
},
|
|
2267
|
+
saveSchedule: (entry) => {
|
|
2268
|
+
const result = runtime.saveSchedule(entry);
|
|
2269
|
+
pushNotice(`schedule saved: ${result.name}`, 'info');
|
|
2270
|
+
return result;
|
|
2271
|
+
},
|
|
2272
|
+
deleteSchedule: (name) => {
|
|
2273
|
+
const result = runtime.deleteSchedule(name);
|
|
2274
|
+
pushNotice(`schedule deleted: ${name}`, 'info');
|
|
2275
|
+
return result;
|
|
2276
|
+
},
|
|
2277
|
+
setScheduleEnabled: (name, enabled) => {
|
|
2278
|
+
const result = runtime.setScheduleEnabled(name, enabled);
|
|
2279
|
+
pushNotice(`schedule ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
|
|
2280
|
+
return result;
|
|
2281
|
+
},
|
|
2282
|
+
saveWebhook: (entry) => {
|
|
2283
|
+
const result = runtime.saveWebhook(entry);
|
|
2284
|
+
pushNotice(`webhook saved: ${result.name}`, 'info');
|
|
2285
|
+
return result;
|
|
2286
|
+
},
|
|
2287
|
+
deleteWebhook: (name) => {
|
|
2288
|
+
const result = runtime.deleteWebhook(name);
|
|
2289
|
+
pushNotice(`webhook deleted: ${name}`, 'info');
|
|
2290
|
+
return result;
|
|
2291
|
+
},
|
|
2292
|
+
setWebhookEnabled: (name, enabled) => {
|
|
2293
|
+
const result = runtime.setWebhookEnabled(name, enabled);
|
|
2294
|
+
pushNotice(`webhook ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
|
|
2295
|
+
return result;
|
|
2296
|
+
},
|
|
2297
|
+
setRoute: async (opts) => {
|
|
2298
|
+
if (state.commandBusy) return false;
|
|
2299
|
+
set({ commandBusy: true });
|
|
2300
|
+
try {
|
|
2301
|
+
await runtime.setRoute(opts);
|
|
2302
|
+
resetStatsAndSyncContext();
|
|
2303
|
+
set({ ...routeState(), stats: { ...state.stats } });
|
|
2304
|
+
return true;
|
|
2305
|
+
} finally {
|
|
2306
|
+
set({ commandBusy: false });
|
|
2307
|
+
}
|
|
2308
|
+
},
|
|
2309
|
+
pushNotice,
|
|
2310
|
+
clear: async () => {
|
|
2311
|
+
if (state.commandBusy) return false;
|
|
2312
|
+
set({ commandBusy: true });
|
|
2313
|
+
clearToastTimers();
|
|
2314
|
+
try {
|
|
2315
|
+
await runtime.clear({ recoverBridge: true });
|
|
2316
|
+
resetStatsAndSyncContext();
|
|
2317
|
+
set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...state.stats } });
|
|
2318
|
+
lastUserActivityAt = Date.now();
|
|
2319
|
+
return true;
|
|
2320
|
+
} finally {
|
|
2321
|
+
set({ commandBusy: false });
|
|
2322
|
+
}
|
|
2323
|
+
},
|
|
2324
|
+
listSessions: () => {
|
|
2325
|
+
return runtime.listSessions();
|
|
2326
|
+
},
|
|
2327
|
+
newSession: async () => {
|
|
2328
|
+
if (state.commandBusy) return false;
|
|
2329
|
+
set({ commandBusy: true });
|
|
2330
|
+
clearToastTimers();
|
|
2331
|
+
try {
|
|
2332
|
+
await runtime.newSession();
|
|
2333
|
+
resetStatsAndSyncContext();
|
|
2334
|
+
set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...state.stats } });
|
|
2335
|
+
return true;
|
|
2336
|
+
} finally {
|
|
2337
|
+
set({ commandBusy: false });
|
|
2338
|
+
}
|
|
2339
|
+
},
|
|
2340
|
+
resume: async (id) => {
|
|
2341
|
+
if (state.commandBusy) return false;
|
|
2342
|
+
set({ commandBusy: true, commandStatus: { active: true, verb: 'Resuming conversation', startedAt: Date.now(), mode: 'resuming' } });
|
|
2343
|
+
clearToastTimers();
|
|
2344
|
+
try {
|
|
2345
|
+
const r = await runtime.resume(id);
|
|
2346
|
+
if (!r) return false;
|
|
2347
|
+
resetStatsAndSyncContext();
|
|
2348
|
+
const items = [];
|
|
2349
|
+
for (const m of r.messages || []) {
|
|
2350
|
+
if (m.role === 'user') {
|
|
2351
|
+
// content may be a string OR an array of parts (text/tool-call
|
|
2352
|
+
// interleaving) — toolResultText coerces both to readable text so
|
|
2353
|
+
// array-content messages aren't silently dropped.
|
|
2354
|
+
const text = (typeof m.content === 'string' ? m.content : toolResultText(m.content)).trim();
|
|
2355
|
+
if (text) {
|
|
2356
|
+
const synthetic = parseSyntheticAgentMessage(text);
|
|
2357
|
+
if (synthetic) {
|
|
2358
|
+
const label = synthetic.label || 'notification';
|
|
2359
|
+
items.push({
|
|
2360
|
+
kind: 'tool',
|
|
2361
|
+
id: nextId(),
|
|
2362
|
+
name: synthetic.name || 'bridge',
|
|
2363
|
+
args: synthetic.args || {
|
|
2364
|
+
type: label,
|
|
2365
|
+
task_id: synthetic.taskId || undefined,
|
|
2366
|
+
description: synthetic.summary || 'agent notification',
|
|
2367
|
+
},
|
|
2368
|
+
result: synthetic.result,
|
|
2369
|
+
isError: synthetic.isError ?? /^(failed|error|killed|cancelled)$/i.test(label),
|
|
2370
|
+
expanded: false,
|
|
2371
|
+
count: 1,
|
|
2372
|
+
completedCount: 1,
|
|
2373
|
+
startedAt: Date.now(),
|
|
2374
|
+
completedAt: Date.now(),
|
|
2375
|
+
});
|
|
2376
|
+
} else {
|
|
2377
|
+
items.push({ kind: 'user', id: nextId(), text });
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
} else if (m.role === 'assistant') {
|
|
2381
|
+
const text = (typeof m.content === 'string' ? m.content : toolResultText(m.content)).trim();
|
|
2382
|
+
if (text) items.push({ kind: 'assistant', id: nextId(), text });
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
set({
|
|
2386
|
+
items: replaceItems(items),
|
|
2387
|
+
toasts: [],
|
|
2388
|
+
queued: [],
|
|
2389
|
+
thinking: null,
|
|
2390
|
+
spinner: null,
|
|
2391
|
+
lastTurn: null,
|
|
2392
|
+
...routeState(),
|
|
2393
|
+
stats: { ...state.stats },
|
|
2394
|
+
});
|
|
2395
|
+
return true;
|
|
2396
|
+
} finally {
|
|
2397
|
+
set({ commandBusy: false, commandStatus: null });
|
|
2398
|
+
}
|
|
2399
|
+
},
|
|
2400
|
+
dispose: async (reason = 'cli-react-exit', options = {}) => {
|
|
2401
|
+
if (disposed) return;
|
|
2402
|
+
disposed = true;
|
|
2403
|
+
clearToastTimers();
|
|
2404
|
+
try { unsubscribeRuntimeNotifications?.(); } catch {}
|
|
2405
|
+
unsubscribeRuntimeNotifications = null;
|
|
2406
|
+
await runtime.close(reason, options);
|
|
2407
|
+
listeners.clear();
|
|
2408
|
+
},
|
|
2409
|
+
};
|
|
2410
|
+
}
|