mixdog 0.9.1 → 0.9.3
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/package.json +9 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +355 -22
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +80 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +75 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Reasoning-effort catalogs and coercion. Pure helpers.
|
|
2
|
+
import { clean } from './session-text.mjs';
|
|
3
|
+
|
|
4
|
+
export const TOOL_MODES = new Set(['full', 'readonly', 'lead']);
|
|
5
|
+
export const ALL_EFFORT_LEVELS = new Set(['none', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
6
|
+
export const EFFORT_LABELS = {
|
|
7
|
+
none: 'None',
|
|
8
|
+
low: 'Low',
|
|
9
|
+
medium: 'Medium',
|
|
10
|
+
high: 'High',
|
|
11
|
+
xhigh: 'Extra High',
|
|
12
|
+
max: 'Max',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const EFFORT_OPTIONS_BY_PROVIDER = {
|
|
16
|
+
openai: ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
17
|
+
'openai-oauth': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
18
|
+
anthropic: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
19
|
+
'anthropic-oauth': ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
20
|
+
xai: ['none', 'low', 'medium', 'high'],
|
|
21
|
+
'grok-oauth': ['none', 'low', 'medium', 'high'],
|
|
22
|
+
'opencode-go': ['high', 'max'],
|
|
23
|
+
};
|
|
24
|
+
export const EFFORT_BY_FAMILY = {
|
|
25
|
+
opus: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
26
|
+
sonnet: ['low', 'medium', 'high'],
|
|
27
|
+
haiku: [],
|
|
28
|
+
'gpt-5.5': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
29
|
+
'gpt-5.4': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
30
|
+
'gpt-5.2': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
31
|
+
'gpt-5': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
32
|
+
'gpt-mini': ['none', 'low', 'medium', 'high', 'xhigh'],
|
|
33
|
+
'gpt-nano': ['none', 'low', 'medium', 'high'],
|
|
34
|
+
'gpt-codex': ['none', 'low', 'medium', 'high'],
|
|
35
|
+
grok: ['none', 'low', 'medium', 'high'],
|
|
36
|
+
};
|
|
37
|
+
export const EFFORT_FALLBACKS = {
|
|
38
|
+
max: ['max', 'xhigh', 'high', 'medium', 'low'],
|
|
39
|
+
xhigh: ['xhigh', 'high', 'medium', 'low'],
|
|
40
|
+
high: ['high', 'medium', 'low'],
|
|
41
|
+
medium: ['medium', 'low'],
|
|
42
|
+
low: ['low'],
|
|
43
|
+
none: ['none'],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function normalizeToolMode(mode) {
|
|
47
|
+
const value = String(mode || '').trim().toLowerCase();
|
|
48
|
+
return TOOL_MODES.has(value) ? value : 'full';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function normalizeEffortInput(value) {
|
|
52
|
+
const v = clean(value).toLowerCase();
|
|
53
|
+
if (!v || v === 'auto') return null;
|
|
54
|
+
if (!ALL_EFFORT_LEVELS.has(v)) {
|
|
55
|
+
throw new Error(`effort must be one of auto, ${[...ALL_EFFORT_LEVELS].join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
return v;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function effortOptionsFor(provider, model) {
|
|
61
|
+
const providerAllowed = EFFORT_OPTIONS_BY_PROVIDER[provider] || null;
|
|
62
|
+
const filterProvider = (values) => {
|
|
63
|
+
const unique = [...new Set((values || []).map(clean).filter(Boolean))];
|
|
64
|
+
return providerAllowed ? unique.filter((v) => providerAllowed.includes(v)) : unique;
|
|
65
|
+
};
|
|
66
|
+
const declared = Array.isArray(model?.reasoningLevels)
|
|
67
|
+
? model.reasoningLevels.map(clean).filter(Boolean)
|
|
68
|
+
: [];
|
|
69
|
+
const family = clean(model?.family).toLowerCase();
|
|
70
|
+
if (Array.isArray(model?.reasoningLevels)) {
|
|
71
|
+
if (declared.length) return filterProvider(declared);
|
|
72
|
+
if (Object.prototype.hasOwnProperty.call(EFFORT_BY_FAMILY, family)) {
|
|
73
|
+
return filterProvider(EFFORT_BY_FAMILY[family]);
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const reasoningOptionEffort = Array.isArray(model?.reasoningOptions)
|
|
78
|
+
? model.reasoningOptions.find((option) => clean(option?.type).toLowerCase() === 'effort')
|
|
79
|
+
: null;
|
|
80
|
+
const reasoningOptionValues = Array.isArray(reasoningOptionEffort?.values)
|
|
81
|
+
? reasoningOptionEffort.values.map(clean).filter(Boolean)
|
|
82
|
+
: [];
|
|
83
|
+
if (reasoningOptionValues.length) return filterProvider(reasoningOptionValues);
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(EFFORT_BY_FAMILY, family)) {
|
|
85
|
+
return filterProvider(EFFORT_BY_FAMILY[family]);
|
|
86
|
+
}
|
|
87
|
+
return providerAllowed || [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function coerceEffortFor(provider, model, effort) {
|
|
91
|
+
if (!effort) return null;
|
|
92
|
+
const allowed = effortOptionsFor(provider, model);
|
|
93
|
+
if (!allowed || allowed.length === 0) return null;
|
|
94
|
+
if (allowed.includes(effort)) return effort;
|
|
95
|
+
for (const candidate of EFFORT_FALLBACKS[effort] || []) {
|
|
96
|
+
if (allowed.includes(candidate)) return candidate;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function normalizeSavedEffort(value) {
|
|
102
|
+
try {
|
|
103
|
+
return normalizeEffortInput(value);
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function effortItemsFor(provider, model, activeEffort) {
|
|
110
|
+
const allowed = effortOptionsFor(provider, model);
|
|
111
|
+
const items = [];
|
|
112
|
+
for (const value of allowed || []) {
|
|
113
|
+
items.push({
|
|
114
|
+
value,
|
|
115
|
+
label: EFFORT_LABELS[value] || value,
|
|
116
|
+
description: value === activeEffort ? 'current' : '',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return items;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function toolSpecForMode(mode) {
|
|
123
|
+
return mode === 'readonly' ? ['tools:readonly'] : 'full';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function deferredSurfaceModeForLead(mode) {
|
|
127
|
+
return mode === 'readonly' ? 'readonly' : 'lead';
|
|
128
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Small filesystem read helpers used by session-runtime submodules.
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export function readJsonSafe(path) {
|
|
5
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function readTextSafe(path) {
|
|
9
|
+
try { return readFileSync(path, 'utf8').trim(); } catch { return ''; }
|
|
10
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Provider/model capability probes: fast-tier + hosted web-search support, and
|
|
2
|
+
// model-settings persistence. Pure except saveModelSettings (takes cfgMod).
|
|
3
|
+
import { clean, hasOwn } from './session-text.mjs';
|
|
4
|
+
|
|
5
|
+
const FAST_CAPABLE_PROVIDERS = new Set(['anthropic', 'anthropic-oauth', 'openai', 'openai-oauth']);
|
|
6
|
+
export const LAZY_SECRET_PROVIDERS = new Set(['openai-oauth', 'anthropic-oauth', 'grok-oauth', 'ollama', 'lmstudio']);
|
|
7
|
+
|
|
8
|
+
export function routeFastKey(provider, model) {
|
|
9
|
+
const p = clean(provider);
|
|
10
|
+
const m = clean(model);
|
|
11
|
+
return p && m ? `${p}/${m}` : '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function openAiModelMetaSupportsFast(model) {
|
|
15
|
+
const tiers = Array.isArray(model?.serviceTiers) ? model.serviceTiers : [];
|
|
16
|
+
const speedTiers = Array.isArray(model?.additionalSpeedTiers) ? model.additionalSpeedTiers : [];
|
|
17
|
+
if (tiers.length || speedTiers.length || model?.defaultServiceTier) {
|
|
18
|
+
return tiers.some((tier) => tier?.id === 'priority')
|
|
19
|
+
|| speedTiers.includes('priority')
|
|
20
|
+
|| model?.defaultServiceTier === 'priority';
|
|
21
|
+
}
|
|
22
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
23
|
+
if (id.includes('mini') || id.includes('nano') || id.includes('codex')) return false;
|
|
24
|
+
return /^gpt-5(\.|-|$)/.test(id);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function openAiDirectModelSupportsFast(model) {
|
|
28
|
+
const id = clean(model?.id || model);
|
|
29
|
+
return /^gpt-5\.5(?:-\d{4}|$)/.test(id)
|
|
30
|
+
|| /^gpt-5\.4(?:-\d{4}|$)/.test(id)
|
|
31
|
+
|| /^gpt-5\.4-mini(?:-\d{4}|$)/.test(id);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function openAiModelSupportsHostedWebSearch(model) {
|
|
35
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
36
|
+
if (!id) return false;
|
|
37
|
+
if (model?.supportsWebSearch === true) return true;
|
|
38
|
+
const tools = [
|
|
39
|
+
...(Array.isArray(model?.supportedTools) ? model.supportedTools : []),
|
|
40
|
+
...(Array.isArray(model?.tools) ? model.tools : []),
|
|
41
|
+
...(Array.isArray(model?.capabilities?.tools) ? model.capabilities.tools : []),
|
|
42
|
+
].map((tool) => clean(tool?.type || tool?.name || tool).toLowerCase());
|
|
43
|
+
if (tools.some((tool) => tool === 'web_search' || tool === 'web_search_preview')) return true;
|
|
44
|
+
if (/codex|image|audio|tts|stt|embedding|rerank|moderation|search-preview/.test(id)) return false;
|
|
45
|
+
return /^gpt-(5(?:\.|$|-)|4\.1(?:-|$)|4o(?:-|$)|4\.5(?:-|$))/.test(id)
|
|
46
|
+
|| /^o[34](?:-|$)/.test(id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function grokModelSupportsHostedWebSearch(model) {
|
|
50
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
51
|
+
if (!id || /imagine|image|video|composer/.test(id)) return false;
|
|
52
|
+
if (id === 'grok-build') return false;
|
|
53
|
+
return /^grok-/.test(id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function geminiModelSupportsHostedWebSearch(model) {
|
|
57
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
58
|
+
if (!id || /embedding|aqa|imagen|veo|tts|image|computer-use|customtools/.test(id)) return false;
|
|
59
|
+
return /^gemini-(3(?:\.|-|$)|2\.5-|2\.0-flash)/.test(id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function anthropicModelSupportsHostedWebSearch(model) {
|
|
63
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
64
|
+
if (!id) return false;
|
|
65
|
+
const match = id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[-.](\d+))?/);
|
|
66
|
+
if (!match) return false;
|
|
67
|
+
const major = Number(match[2]) || 0;
|
|
68
|
+
const minor = Number(match[3]) || 0;
|
|
69
|
+
return major > 4 || (major === 4 && minor >= 0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function anthropicModelMetaSupportsFast(model) {
|
|
73
|
+
const id = clean(model?.id || model).toLowerCase();
|
|
74
|
+
return /^claude-(opus|sonnet)/.test(id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function fastCapableFor(provider, model) {
|
|
78
|
+
const p = clean(provider);
|
|
79
|
+
if (!FAST_CAPABLE_PROVIDERS.has(p)) return false;
|
|
80
|
+
if (p === 'openai') return openAiDirectModelSupportsFast(model);
|
|
81
|
+
if (p === 'openai-oauth') return openAiModelMetaSupportsFast(model);
|
|
82
|
+
if (p === 'anthropic' || p === 'anthropic-oauth') return anthropicModelMetaSupportsFast(model);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// searchCapableFor needs the search-route normalizers, which live in
|
|
87
|
+
// search-routes.mjs and themselves are pure. Wire them in via a factory to keep
|
|
88
|
+
// this module free of a circular import at load time.
|
|
89
|
+
export function makeSearchCapableFor(normalizeSearchProviderId, isSearchCapableProvider) {
|
|
90
|
+
return function searchCapableFor(provider, model) {
|
|
91
|
+
const p = normalizeSearchProviderId(provider);
|
|
92
|
+
if (!isSearchCapableProvider(p)) return false;
|
|
93
|
+
if (p === 'openai' || p === 'openai-oauth') return openAiModelSupportsHostedWebSearch(model);
|
|
94
|
+
if (p === 'grok-oauth' || p === 'xai') return grokModelSupportsHostedWebSearch(model);
|
|
95
|
+
if (p === 'gemini') return geminiModelSupportsHostedWebSearch(model);
|
|
96
|
+
if (p === 'anthropic' || p === 'anthropic-oauth') return anthropicModelSupportsHostedWebSearch(model);
|
|
97
|
+
return model?.supportsWebSearch === true;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function fastPreferenceFor(config, provider, model) {
|
|
102
|
+
const key = routeFastKey(provider, model);
|
|
103
|
+
if (!key) return false;
|
|
104
|
+
const saved = config?.modelSettings?.[key];
|
|
105
|
+
if (saved && typeof saved === 'object' && hasOwn(saved, 'fast')) return saved.fast === true;
|
|
106
|
+
return config?.fastModels?.[key] === true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function saveModelSettings(cfgMod, route, { fastCapable = true, baseConfig = null } = {}) {
|
|
110
|
+
const key = routeFastKey(route?.provider, route?.model);
|
|
111
|
+
if (!key) return baseConfig || cfgMod.loadConfig();
|
|
112
|
+
const nextConfig = baseConfig || cfgMod.loadConfig();
|
|
113
|
+
const modelSettings = { ...(nextConfig.modelSettings || {}) };
|
|
114
|
+
const nextSetting = { ...(modelSettings[key] || {}) };
|
|
115
|
+
if (hasOwn(route, 'effort') && route.effort) nextSetting.effort = route.effort;
|
|
116
|
+
else delete nextSetting.effort;
|
|
117
|
+
if (fastCapable) nextSetting.fast = route.fast === true;
|
|
118
|
+
else nextSetting.fast = false;
|
|
119
|
+
modelSettings[key] = nextSetting;
|
|
120
|
+
|
|
121
|
+
// Legacy compatibility: keep fastModels true entries for old readers, but
|
|
122
|
+
// let modelSettings.fast=false override them in new readers.
|
|
123
|
+
const fastModels = { ...(nextConfig.fastModels || {}) };
|
|
124
|
+
if (nextSetting.fast === true) fastModels[key] = true;
|
|
125
|
+
else delete fastModels[key];
|
|
126
|
+
|
|
127
|
+
const savedConfig = { ...nextConfig, modelSettings, fastModels };
|
|
128
|
+
cfgMod.saveConfig(savedConfig);
|
|
129
|
+
return savedConfig;
|
|
130
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Output-style catalog + metadata parsing. Roots are injected so this module
|
|
2
|
+
// stays free of the runtime's path constants.
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
5
|
+
import { clean } from './session-text.mjs';
|
|
6
|
+
import { readJsonSafe } from './fs-utils.mjs';
|
|
7
|
+
|
|
8
|
+
const OUTPUT_STYLE_ORDER = ['default', 'simple', 'minimal', 'oneline'];
|
|
9
|
+
const OUTPUT_STYLE_ALIASES = new Map([
|
|
10
|
+
['compact', 'default'],
|
|
11
|
+
['normal', 'default'],
|
|
12
|
+
['extreme', 'minimal'],
|
|
13
|
+
['extremesimple', 'minimal'],
|
|
14
|
+
['extreme-simple', 'minimal'],
|
|
15
|
+
['extreme_simple', 'minimal'],
|
|
16
|
+
['mono', 'oneline'],
|
|
17
|
+
['oneline', 'oneline'],
|
|
18
|
+
['one-line', 'oneline'],
|
|
19
|
+
['one_line', 'oneline'],
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function normalizeOutputStyleId(value) {
|
|
23
|
+
const raw = clean(value).toLowerCase();
|
|
24
|
+
if (!raw) return '';
|
|
25
|
+
const slug = raw.replace(/[_\s]+/g, '-').replace(/^-+|-+$/g, '');
|
|
26
|
+
const compact = slug.replace(/[_.-]+/g, '');
|
|
27
|
+
if (OUTPUT_STYLE_ALIASES.has(slug)) return OUTPUT_STYLE_ALIASES.get(slug);
|
|
28
|
+
if (OUTPUT_STYLE_ALIASES.has(compact)) return OUTPUT_STYLE_ALIASES.get(compact);
|
|
29
|
+
return /^[a-z0-9.-]+$/.test(slug) ? slug : '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function outputStyleCompactKey(value) {
|
|
33
|
+
return normalizeOutputStyleId(value).replace(/[_.-]+/g, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function titleCaseOutputStyle(id) {
|
|
37
|
+
return clean(id)
|
|
38
|
+
.split(/[_.-]+/)
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
41
|
+
.join(' ') || 'Default';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseOutputStyleFrontmatter(markdown) {
|
|
45
|
+
const match = String(markdown || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
46
|
+
const meta = {};
|
|
47
|
+
if (!match) return meta;
|
|
48
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
49
|
+
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
50
|
+
if (!kv) continue;
|
|
51
|
+
meta[kv[1]] = kv[2].replace(/^['"]|['"]$/g, '').trim();
|
|
52
|
+
}
|
|
53
|
+
return meta;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readOutputStyleMetadata(filePath, source) {
|
|
57
|
+
let raw = '';
|
|
58
|
+
try { raw = readFileSync(filePath, 'utf8'); } catch { return null; }
|
|
59
|
+
const meta = parseOutputStyleFrontmatter(raw);
|
|
60
|
+
const fileId = normalizeOutputStyleId(basename(filePath).replace(/\.md$/i, ''));
|
|
61
|
+
const id = normalizeOutputStyleId(meta.name) || fileId;
|
|
62
|
+
if (!id) return null;
|
|
63
|
+
const aliases = clean(meta.aliases)
|
|
64
|
+
.split(',')
|
|
65
|
+
.map((value) => normalizeOutputStyleId(value))
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
const label = clean(meta.title || meta.label) || titleCaseOutputStyle(id);
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
label,
|
|
71
|
+
description: clean(meta.description),
|
|
72
|
+
aliases,
|
|
73
|
+
source,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function listOutputStyleCatalog(rootDir, dataDir) {
|
|
78
|
+
const byId = new Map();
|
|
79
|
+
const dirs = [
|
|
80
|
+
{ dir: join(rootDir, 'output-styles'), source: 'builtin' },
|
|
81
|
+
{ dir: join(dataDir, 'output-styles'), source: 'user' },
|
|
82
|
+
];
|
|
83
|
+
for (const { dir, source } of dirs) {
|
|
84
|
+
let entries = [];
|
|
85
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.md')) continue;
|
|
88
|
+
const style = readOutputStyleMetadata(join(dir, entry.name), source);
|
|
89
|
+
if (style) byId.set(style.id, style);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...byId.values()].sort((a, b) => {
|
|
93
|
+
const ai = OUTPUT_STYLE_ORDER.indexOf(a.id);
|
|
94
|
+
const bi = OUTPUT_STYLE_ORDER.indexOf(b.id);
|
|
95
|
+
if (ai !== bi) return (ai < 0 ? 999 : ai) - (bi < 0 ? 999 : bi);
|
|
96
|
+
return a.label.localeCompare(b.label, 'en', { sensitivity: 'base' });
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function findOutputStyle(value, styles) {
|
|
101
|
+
const id = normalizeOutputStyleId(value);
|
|
102
|
+
const compact = outputStyleCompactKey(value);
|
|
103
|
+
if (!id && !compact) return null;
|
|
104
|
+
return (styles || []).find((style) => {
|
|
105
|
+
if (style.id === id || outputStyleCompactKey(style.id) === compact) return true;
|
|
106
|
+
if (outputStyleCompactKey(style.label) === compact) return true;
|
|
107
|
+
return (style.aliases || []).some((alias) => alias === id || outputStyleCompactKey(alias) === compact);
|
|
108
|
+
}) || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function configuredOutputStyleValue(dataDir) {
|
|
112
|
+
const unified = readJsonSafe(join(dataDir, 'mixdog-config.json')) || {};
|
|
113
|
+
return clean(unified.outputStyle || (unified.agent && unified.agent.outputStyle) || 'default') || 'default';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function outputStyleStatus(rootDir, dataDir) {
|
|
117
|
+
const styles = listOutputStyleCatalog(rootDir, dataDir);
|
|
118
|
+
const configured = configuredOutputStyleValue(dataDir);
|
|
119
|
+
const current = findOutputStyle(configured, styles)
|
|
120
|
+
|| findOutputStyle('default', styles)
|
|
121
|
+
|| styles[0]
|
|
122
|
+
|| { id: 'default', label: 'Default', description: '', aliases: [], source: 'builtin' };
|
|
123
|
+
return { configured, current, styles };
|
|
124
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Plugin/project MCP server discovery + normalization, and skill-file counting.
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { clean } from './session-text.mjs';
|
|
5
|
+
import { readJsonSafe } from './fs-utils.mjs';
|
|
6
|
+
|
|
7
|
+
// Project-local MCP ingress: read `.mcp.json` from the project root and return
|
|
8
|
+
// a cleaned { name: cfg } map. Best-effort — never throws. Accepts either the
|
|
9
|
+
// standard `{ mcpServers: {...} }` shape or a bare name->cfg map. Self-ref
|
|
10
|
+
// servers (`mixdog` / `trib-plugin`) are stripped for parity with loadConfig.
|
|
11
|
+
// Inputs are not mutated.
|
|
12
|
+
export function readProjectMcpServers(cwd) {
|
|
13
|
+
const path = join(cwd || '.', '.mcp.json');
|
|
14
|
+
if (!existsSync(path)) return {};
|
|
15
|
+
let raw;
|
|
16
|
+
try {
|
|
17
|
+
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
process.stderr.write(`[mcp-client] Ignoring unparseable .mcp.json at ${path}: ${error?.message || String(error)}\n`);
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
|
|
23
|
+
const map = raw.mcpServers && typeof raw.mcpServers === 'object' && !Array.isArray(raw.mcpServers)
|
|
24
|
+
? raw.mcpServers
|
|
25
|
+
: raw;
|
|
26
|
+
if (!map || typeof map !== 'object' || Array.isArray(map)) return {};
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const [name, cfg] of Object.entries(map)) {
|
|
29
|
+
const key = clean(name);
|
|
30
|
+
if (!key) continue;
|
|
31
|
+
const lower = key.toLowerCase();
|
|
32
|
+
if (lower === 'mixdog' || lower === 'trib-plugin') continue;
|
|
33
|
+
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) continue;
|
|
34
|
+
// stdio entries (command + no url) spawn relative to the process launch
|
|
35
|
+
// dir, but mixdog tracks the project dir in memory (no process.chdir).
|
|
36
|
+
// Anchor their cwd to the .mcp.json directory: default when absent, resolve
|
|
37
|
+
// relative values against it, keep absolute values as-is.
|
|
38
|
+
const isStdio = typeof cfg.command === 'string' && cfg.command !== '' && !cfg.url;
|
|
39
|
+
if (isStdio) {
|
|
40
|
+
out[key] = { ...cfg, cwd: typeof cfg.cwd === 'string' && cfg.cwd ? resolve(cwd, cfg.cwd) : resolve(cwd) };
|
|
41
|
+
} else {
|
|
42
|
+
out[key] = cfg;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function countSkillFiles(root) {
|
|
49
|
+
const skillsDir = join(root, 'skills');
|
|
50
|
+
if (!existsSync(skillsDir)) return 0;
|
|
51
|
+
let count = 0;
|
|
52
|
+
const walk = (dir) => {
|
|
53
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
const full = join(dir, entry.name);
|
|
55
|
+
if (entry.isDirectory()) walk(full);
|
|
56
|
+
else if (/^(SKILL|skill)\.md$/i.test(entry.name) || entry.name.toLowerCase().endsWith('.md')) count += 1;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
try { walk(skillsDir); } catch { return count; }
|
|
60
|
+
return count;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function mcpScriptForPlugin(root) {
|
|
64
|
+
const candidates = [
|
|
65
|
+
'.mcp.json',
|
|
66
|
+
'scripts/run-mcp.mjs',
|
|
67
|
+
'mcp/server.mjs',
|
|
68
|
+
'server.mjs',
|
|
69
|
+
];
|
|
70
|
+
return candidates.find((rel) => existsSync(join(root, rel))) || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function substitutePluginRootTokens(value, root) {
|
|
74
|
+
if (typeof value !== 'string') return value;
|
|
75
|
+
return value
|
|
76
|
+
.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, root)
|
|
77
|
+
.replace(/\$\{CODEX_PLUGIN_ROOT\}/g, root);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function substitutePluginRootTokensDeep(value, root) {
|
|
81
|
+
if (typeof value === 'string') return substitutePluginRootTokens(value, root);
|
|
82
|
+
if (Array.isArray(value)) return value.map((v) => substitutePluginRootTokensDeep(v, root));
|
|
83
|
+
if (value && typeof value === 'object') {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const [k, v] of Object.entries(value)) out[k] = substitutePluginRootTokensDeep(v, root);
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function normalizePluginMcpServerConfig(cfg, root) {
|
|
92
|
+
const substituted = substitutePluginRootTokensDeep(cfg, root) || {};
|
|
93
|
+
const out = { ...substituted };
|
|
94
|
+
if (typeof out.cwd === 'string' && out.cwd) {
|
|
95
|
+
out.cwd = isAbsolute(out.cwd) ? out.cwd : join(root, out.cwd);
|
|
96
|
+
} else {
|
|
97
|
+
out.cwd = root;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function pluginManifest(root) {
|
|
103
|
+
return readJsonSafe(join(root, '.codex-plugin', 'plugin.json'))
|
|
104
|
+
|| readJsonSafe(join(root, 'plugin.json'))
|
|
105
|
+
|| {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function pluginMcpServerName(plugin = {}) {
|
|
109
|
+
const base = clean(plugin.name || plugin.title || 'plugin')
|
|
110
|
+
.toLowerCase()
|
|
111
|
+
.replace(/[^a-z0-9_.-]+/g, '-')
|
|
112
|
+
.replace(/^-+|-+$/g, '');
|
|
113
|
+
return base ? `plugin-${base}` : 'plugin-mcp';
|
|
114
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Session message/preview text helpers. Pure, no runtime-closure deps.
|
|
2
|
+
|
|
3
|
+
export function sessionMessageText(content) {
|
|
4
|
+
if (content == null) return '';
|
|
5
|
+
if (typeof content === 'string') return content;
|
|
6
|
+
const parts = Array.isArray(content)
|
|
7
|
+
? content
|
|
8
|
+
: (content && typeof content === 'object' && Array.isArray(content.content) ? content.content : null);
|
|
9
|
+
if (parts) {
|
|
10
|
+
return parts.map((part) => {
|
|
11
|
+
if (typeof part === 'string') return part;
|
|
12
|
+
return part?.text ?? '';
|
|
13
|
+
}).filter(Boolean).join('\n');
|
|
14
|
+
}
|
|
15
|
+
if (typeof content === 'object' && typeof content.text === 'string') return content.text;
|
|
16
|
+
try { return JSON.stringify(content); } catch { return String(content); }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function messageContextText(message) {
|
|
20
|
+
if (!message || typeof message !== 'object') return '';
|
|
21
|
+
let text = sessionMessageText(message.content);
|
|
22
|
+
if (message.role === 'assistant' && Array.isArray(message.toolCalls) && message.toolCalls.length) {
|
|
23
|
+
try { text += `\n${JSON.stringify(message.toolCalls)}`; }
|
|
24
|
+
catch { text += `\n[${message.toolCalls.length} tool calls]`; }
|
|
25
|
+
}
|
|
26
|
+
if (message.role === 'tool' && message.toolCallId) text += `\n${message.toolCallId}`;
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isSessionPreviewNoise(text) {
|
|
31
|
+
const value = String(text || '').trim();
|
|
32
|
+
return !value
|
|
33
|
+
|| value.startsWith('<system-reminder>')
|
|
34
|
+
|| value.startsWith('</system-reminder>')
|
|
35
|
+
|| /^#\s*permission\b/i.test(value)
|
|
36
|
+
|| /^permission:\s*/i.test(value)
|
|
37
|
+
|| /^cwd:\s*/i.test(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function cleanSessionPreview(text) {
|
|
41
|
+
return String(text || '')
|
|
42
|
+
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, ' ')
|
|
43
|
+
.replace(/\s+/g, ' ')
|
|
44
|
+
.trim()
|
|
45
|
+
.slice(0, 160);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function clean(value) {
|
|
49
|
+
return String(value ?? '').trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function hasOwn(obj, key) {
|
|
53
|
+
return Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function toolResponseText(result) {
|
|
57
|
+
if (result && typeof result === 'object' && Array.isArray(result.content)) {
|
|
58
|
+
return result.content
|
|
59
|
+
.map((part) => (part?.type === 'text' ? part.text || '' : JSON.stringify(part)))
|
|
60
|
+
.join('\n');
|
|
61
|
+
}
|
|
62
|
+
if (typeof result === 'string') return result;
|
|
63
|
+
return JSON.stringify(result, null, 2);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isEmptyRecallText(value) {
|
|
67
|
+
const text = String(value || '').trim();
|
|
68
|
+
return !text || /^\(?no results\)?$/i.test(text) || /^\(?empty memory result\)?$/i.test(text);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function currentSessionRecallRows(session, query, { limit = 10 } = {}) {
|
|
72
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
73
|
+
if (!messages.length) return '(no results)';
|
|
74
|
+
const terms = [...new Set(String(query || '').toLowerCase().match(/[\p{L}\p{N}_./:-]{2,}/gu) || [])]
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.slice(0, 16);
|
|
77
|
+
const max = Math.max(1, Math.min(100, Number(limit) || 10));
|
|
78
|
+
const rows = [];
|
|
79
|
+
for (let i = messages.length - 1; i >= 0 && rows.length < max; i -= 1) {
|
|
80
|
+
const m = messages[i];
|
|
81
|
+
if (!m || (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'tool')) continue;
|
|
82
|
+
const text = messageContextText(m).replace(/\s+/g, ' ').trim();
|
|
83
|
+
if (!text) continue;
|
|
84
|
+
if (terms.length && !terms.some((term) => text.toLowerCase().includes(term))) continue;
|
|
85
|
+
rows.push(`[session:${i + 1}] ${m.role}: ${text.slice(0, 1000)}`);
|
|
86
|
+
}
|
|
87
|
+
return rows.length ? rows.join('\n') : '(no results)';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function sessionHasConversationMessages(activeSession) {
|
|
91
|
+
const messages = Array.isArray(activeSession?.messages) ? activeSession.messages : [];
|
|
92
|
+
return messages.some((message) => {
|
|
93
|
+
const role = message?.role;
|
|
94
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'tool') return false;
|
|
95
|
+
const text = sessionMessageText(message.content).trim();
|
|
96
|
+
if (!text && role !== 'assistant') return false;
|
|
97
|
+
if (role === 'user' && isSessionPreviewNoise(text)) return false;
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Statusline route serialization. Pure helpers.
|
|
2
|
+
import { clean, hasOwn } from './session-text.mjs';
|
|
3
|
+
|
|
4
|
+
export function routeForStatusline(route) {
|
|
5
|
+
const out = {
|
|
6
|
+
mode: 'fixed',
|
|
7
|
+
defaultProvider: route.provider,
|
|
8
|
+
defaultModel: route.model,
|
|
9
|
+
};
|
|
10
|
+
const preset = route.preset || {};
|
|
11
|
+
if (preset.id) out.presetId = preset.id;
|
|
12
|
+
if (preset.name) out.presetName = preset.name;
|
|
13
|
+
// Prefer the preset's curated label, then the route's resolved model display
|
|
14
|
+
// (set by refreshRouteEffort from the live/offline catalog). Without the
|
|
15
|
+
// route fallback, a preset-less direct model (e.g. claude-fable-5) reaches
|
|
16
|
+
// the statusline with no display and renders as the raw id.
|
|
17
|
+
const modelDisplay = clean(preset.modelDisplay) || clean(route.modelDisplay);
|
|
18
|
+
if (modelDisplay) out.modelDisplay = modelDisplay;
|
|
19
|
+
if (route.fast === true || route.fast === false) out.fast = route.fast;
|
|
20
|
+
else if (preset.fast === true || preset.fast === false) out.fast = preset.fast;
|
|
21
|
+
if (route.effectiveEffort) {
|
|
22
|
+
out.effort = route.effectiveEffort;
|
|
23
|
+
out.displayEffort = route.effectiveEffort;
|
|
24
|
+
} else if (hasOwn(route, 'effort')) {
|
|
25
|
+
delete out.effort;
|
|
26
|
+
delete out.displayEffort;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function writeStatuslineRoute(statusRoutes, session, route) {
|
|
32
|
+
if (!session?.id || !route) return;
|
|
33
|
+
const clientHostPid = session?.clientHostPid || process.pid;
|
|
34
|
+
statusRoutes?.writeGatewaySessionRoute?.(session.id, routeForStatusline(route), { clientHostPid });
|
|
35
|
+
}
|