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,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript writer for remote (Discord) mode.
|
|
3
|
+
*
|
|
4
|
+
* The channel worker's OutputForwarder tails a newline-delimited JSON
|
|
5
|
+
* transcript and forwards the "surface" view (assistant text + one-line tool
|
|
6
|
+
* summaries) to Discord. Nothing wrote that file for standalone sessions, so
|
|
7
|
+
* remote outbound never worked. This module writes the JSONL in the exact
|
|
8
|
+
* schema the forwarder parses (see channels/lib/output-forwarder.mjs
|
|
9
|
+
* extractNewText) plus a session record the forwarder's discovery reads
|
|
10
|
+
* (channels/lib/session-discovery.mjs readSessionRecord).
|
|
11
|
+
*
|
|
12
|
+
* All writes are best-effort: a failure must never break the ask() turn. We
|
|
13
|
+
* log the FIRST occurrence of each distinct failure string to stderr (prefix
|
|
14
|
+
* `mixdog: transcript-writer: `) and suppress duplicates so a broken path
|
|
15
|
+
* cannot spam the terminal.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, mkdirSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
|
+
import { appendBuffered, drainPathSync, hasInFlightWrite } from './buffered-appender.mjs';
|
|
20
|
+
|
|
21
|
+
// Rotate the JSONL transcript once it exceeds this size, keeping one prior
|
|
22
|
+
// generation (`<path>.1`). Checked on each append; the check itself is a
|
|
23
|
+
// cheap statSync so no extra timer/interval is needed.
|
|
24
|
+
const TRANSCRIPT_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
// Byte-identical to cwdToProjectSlug() in
|
|
27
|
+
// src/runtime/channels/lib/session-discovery.mjs. Inlined to avoid a
|
|
28
|
+
// shared -> channels import coupling; keep the two in sync if either changes.
|
|
29
|
+
function cwdToProjectSlug(cwd) {
|
|
30
|
+
return resolve(cwd).replace(/\\/g, '/').replace(/^([A-Za-z]):/, '$1-').replace(/\//g, '-');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {}) {
|
|
34
|
+
if (!mixdogHome) throw new Error('transcript-writer: mixdogHome is required');
|
|
35
|
+
if (!sessionId) throw new Error('transcript-writer: sessionId is required');
|
|
36
|
+
if (!cwd) throw new Error('transcript-writer: cwd is required');
|
|
37
|
+
|
|
38
|
+
const resolvedCwd = resolve(cwd);
|
|
39
|
+
const projectDir = join(mixdogHome, 'projects', cwdToProjectSlug(resolvedCwd));
|
|
40
|
+
const transcriptPath = join(projectDir, `${sessionId}.jsonl`);
|
|
41
|
+
const sessionRecordPath = join(mixdogHome, 'sessions', `${pid ?? process.pid}.json`);
|
|
42
|
+
const startedAt = Date.now();
|
|
43
|
+
|
|
44
|
+
// Repeated-failure guard: log the first time each distinct message is seen,
|
|
45
|
+
// then stay quiet so a persistently-broken path does not flood stderr.
|
|
46
|
+
const seenErrors = new Set();
|
|
47
|
+
function logOnce(err) {
|
|
48
|
+
const msg = err && err.message ? err.message : String(err);
|
|
49
|
+
if (seenErrors.has(msg)) return;
|
|
50
|
+
seenErrors.add(msg);
|
|
51
|
+
try { process.stderr.write(`mixdog: transcript-writer: ${msg}\n`); } catch { /* stderr broken */ }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let projectDirReady = false;
|
|
55
|
+
function ensureProjectDir() {
|
|
56
|
+
if (projectDirReady) return;
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(projectDir, { recursive: true });
|
|
59
|
+
projectDirReady = true;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
logOnce(err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function rotateIfNeeded() {
|
|
66
|
+
try {
|
|
67
|
+
if (!existsSync(transcriptPath)) return;
|
|
68
|
+
const { size } = statSync(transcriptPath);
|
|
69
|
+
if (size < TRANSCRIPT_ROTATE_BYTES) return;
|
|
70
|
+
// An async appendFile may be in flight for this path; renaming out
|
|
71
|
+
// from under it races the write on Windows. Skip this round and
|
|
72
|
+
// retry rotation on the next append instead.
|
|
73
|
+
if (hasInFlightWrite(transcriptPath)) return;
|
|
74
|
+
// Force any still-buffered bytes onto disk before renaming, so the
|
|
75
|
+
// rotated-out file ends with everything queued for it and the fresh
|
|
76
|
+
// file post-rename doesn't inherit stale in-memory chunks.
|
|
77
|
+
drainPathSync(transcriptPath);
|
|
78
|
+
const rotatedPath = `${transcriptPath}.1`;
|
|
79
|
+
try { renameSync(transcriptPath, rotatedPath); } catch (err) { logOnce(err); }
|
|
80
|
+
} catch (err) {
|
|
81
|
+
logOnce(err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function appendLine(entry) {
|
|
86
|
+
ensureProjectDir();
|
|
87
|
+
rotateIfNeeded();
|
|
88
|
+
try {
|
|
89
|
+
appendBuffered(transcriptPath, `${JSON.stringify(entry)}\n`);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logOnce(err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function writeSessionRecord() {
|
|
96
|
+
try {
|
|
97
|
+
mkdirSync(dirname(sessionRecordPath), { recursive: true });
|
|
98
|
+
writeFileSync(sessionRecordPath, JSON.stringify({
|
|
99
|
+
sessionId,
|
|
100
|
+
cwd: resolvedCwd,
|
|
101
|
+
transcriptPath,
|
|
102
|
+
startedAt,
|
|
103
|
+
updatedAt: Date.now(),
|
|
104
|
+
kind: 'interactive',
|
|
105
|
+
entrypoint: 'cli',
|
|
106
|
+
}));
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logOnce(err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Refresh only the session record's updatedAt so discovery keeps ranking
|
|
113
|
+
// this session as live across long-lived remote sessions.
|
|
114
|
+
function refresh() {
|
|
115
|
+
writeSessionRecord();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function appendAssistant(text) {
|
|
119
|
+
const value = typeof text === 'string' ? text : (text == null ? '' : String(text));
|
|
120
|
+
if (!value.trim()) return;
|
|
121
|
+
appendLine({
|
|
122
|
+
type: 'assistant',
|
|
123
|
+
sessionId,
|
|
124
|
+
message: { content: [{ type: 'text', text: value }] },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function appendToolUse(name, input) {
|
|
129
|
+
if (!name) return;
|
|
130
|
+
appendLine({
|
|
131
|
+
type: 'assistant',
|
|
132
|
+
sessionId,
|
|
133
|
+
message: { content: [{ type: 'tool_use', name, input: input || {} }] },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function appendToolResult(toolUseResult) {
|
|
138
|
+
if (!toolUseResult) return;
|
|
139
|
+
appendLine({
|
|
140
|
+
type: 'user',
|
|
141
|
+
sessionId,
|
|
142
|
+
message: { content: [{ type: 'tool_result' }] },
|
|
143
|
+
toolUseResult,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
transcriptPath,
|
|
149
|
+
sessionRecordPath,
|
|
150
|
+
writeSessionRecord,
|
|
151
|
+
refresh,
|
|
152
|
+
appendAssistant,
|
|
153
|
+
appendToolUse,
|
|
154
|
+
appendToolResult,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* update-checker.mjs — npm registry version check + self-update for the
|
|
3
|
+
* globally-installed `mixdog` CLI package.
|
|
4
|
+
*
|
|
5
|
+
* checkLatestVersion({force}) hits the npm registry for the `latest`
|
|
6
|
+
* dist-tag, compares against the local package.json version, and caches
|
|
7
|
+
* the result on disk (TTL 24h) so repeated calls (e.g. on every TUI mount)
|
|
8
|
+
* don't hammer the registry. Network/parse failures are silent — this is a
|
|
9
|
+
* best-effort convenience check, never a boot-blocking dependency.
|
|
10
|
+
*
|
|
11
|
+
* runGlobalUpdate() shells out to `npm install -g mixdog@latest` in the
|
|
12
|
+
* background (windowsHide so no console flash on Windows) and resolves once
|
|
13
|
+
* the child exits, reporting the resolved version on success.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { resolvePluginData } from './plugin-paths.mjs';
|
|
21
|
+
|
|
22
|
+
const PACKAGE_NAME = 'mixdog';
|
|
23
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
24
|
+
const FETCH_TIMEOUT_MS = 5_000;
|
|
25
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
const CACHE_FILE_NAME = 'update-check-cache.json';
|
|
27
|
+
|
|
28
|
+
// Resolve the local package.json shipped alongside src/ (two levels up from
|
|
29
|
+
// src/runtime/shared/).
|
|
30
|
+
const _MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const _PACKAGE_JSON_PATH = join(_MODULE_DIR, '..', '..', '..', 'package.json');
|
|
32
|
+
|
|
33
|
+
let _localVersionCache = null;
|
|
34
|
+
export function localPackageVersion() {
|
|
35
|
+
if (_localVersionCache) return _localVersionCache;
|
|
36
|
+
try {
|
|
37
|
+
const raw = JSON.parse(readFileSync(_PACKAGE_JSON_PATH, 'utf8'));
|
|
38
|
+
_localVersionCache = String(raw?.version || '0.0.0');
|
|
39
|
+
} catch {
|
|
40
|
+
_localVersionCache = '0.0.0';
|
|
41
|
+
}
|
|
42
|
+
return _localVersionCache;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function cacheFilePath(dataDir) {
|
|
46
|
+
const dir = dataDir || resolvePluginData();
|
|
47
|
+
return join(dir, CACHE_FILE_NAME);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readCache(dataDir) {
|
|
51
|
+
try {
|
|
52
|
+
const raw = JSON.parse(readFileSync(cacheFilePath(dataDir), 'utf8'));
|
|
53
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
54
|
+
return raw;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeCache(dataDir, payload) {
|
|
61
|
+
try {
|
|
62
|
+
const dir = dataDir || resolvePluginData();
|
|
63
|
+
mkdirSync(dir, { recursive: true });
|
|
64
|
+
writeFileSync(cacheFilePath(dataDir), JSON.stringify(payload), 'utf8');
|
|
65
|
+
} catch {
|
|
66
|
+
// Best-effort — cache miss just means the next call re-fetches.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Parse a semver-ish string into { parts:[maj,min,patch], prerelease }. */
|
|
71
|
+
function parseSemver(value) {
|
|
72
|
+
const text = String(value || '').trim().replace(/^v/i, '');
|
|
73
|
+
const [core, ...preParts] = text.split('-');
|
|
74
|
+
const parts = core.split('.').map((n) => {
|
|
75
|
+
const num = Number.parseInt(n, 10);
|
|
76
|
+
return Number.isFinite(num) ? num : 0;
|
|
77
|
+
});
|
|
78
|
+
while (parts.length < 3) parts.push(0);
|
|
79
|
+
return { parts, prerelease: preParts.join('-') };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compare two semver strings. Returns positive if `a` > `b`, negative if
|
|
84
|
+
* `a` < `b`, 0 if equal. A version with a prerelease tag is considered
|
|
85
|
+
* lower than the same core version without one (standard semver rule).
|
|
86
|
+
*/
|
|
87
|
+
export function compareSemver(a, b) {
|
|
88
|
+
const pa = parseSemver(a);
|
|
89
|
+
const pb = parseSemver(b);
|
|
90
|
+
for (let i = 0; i < 3; i++) {
|
|
91
|
+
const diff = (pa.parts[i] || 0) - (pb.parts[i] || 0);
|
|
92
|
+
if (diff !== 0) return diff;
|
|
93
|
+
}
|
|
94
|
+
if (pa.prerelease && !pb.prerelease) return -1;
|
|
95
|
+
if (!pa.prerelease && pb.prerelease) return 1;
|
|
96
|
+
if (pa.prerelease !== pb.prerelease) return pa.prerelease < pb.prerelease ? -1 : 1;
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isNewerVersion(latest, current) {
|
|
101
|
+
if (!latest || !current) return false;
|
|
102
|
+
return compareSemver(latest, current) > 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function fetchLatestFromRegistry() {
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
108
|
+
try {
|
|
109
|
+
const res = await fetch(REGISTRY_URL, { signal: controller.signal });
|
|
110
|
+
if (!res.ok) return null;
|
|
111
|
+
const body = await res.json();
|
|
112
|
+
const version = body && typeof body === 'object' ? String(body.version || '').trim() : '';
|
|
113
|
+
return version || null;
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
} finally {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* checkLatestVersion({force, dataDir}) — resolve the latest published
|
|
123
|
+
* version of the `mixdog` npm package.
|
|
124
|
+
*
|
|
125
|
+
* Returns:
|
|
126
|
+
* { currentVersion, latestVersion, updateAvailable, lastCheckedAt, fromCache }
|
|
127
|
+
* `latestVersion` is null when the registry could not be reached and no
|
|
128
|
+
* usable cache exists — this call never throws.
|
|
129
|
+
*/
|
|
130
|
+
export async function checkLatestVersion({ force = false, dataDir } = {}) {
|
|
131
|
+
const currentVersion = localPackageVersion();
|
|
132
|
+
const cache = force ? null : readCache(dataDir);
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
if (cache && typeof cache.lastCheckedAt === 'number' && now - cache.lastCheckedAt < CACHE_TTL_MS) {
|
|
135
|
+
return {
|
|
136
|
+
currentVersion,
|
|
137
|
+
latestVersion: cache.latestVersion || null,
|
|
138
|
+
updateAvailable: isNewerVersion(cache.latestVersion, currentVersion),
|
|
139
|
+
lastCheckedAt: cache.lastCheckedAt,
|
|
140
|
+
fromCache: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const latestVersion = await fetchLatestFromRegistry();
|
|
144
|
+
if (latestVersion) {
|
|
145
|
+
const payload = { latestVersion, lastCheckedAt: now };
|
|
146
|
+
writeCache(dataDir, payload);
|
|
147
|
+
return {
|
|
148
|
+
currentVersion,
|
|
149
|
+
latestVersion,
|
|
150
|
+
updateAvailable: isNewerVersion(latestVersion, currentVersion),
|
|
151
|
+
lastCheckedAt: now,
|
|
152
|
+
fromCache: false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Registry unreachable: fall back to a stale cache (if any) rather than
|
|
156
|
+
// reporting "no update info" every time the network hiccups.
|
|
157
|
+
if (cache && cache.latestVersion) {
|
|
158
|
+
return {
|
|
159
|
+
currentVersion,
|
|
160
|
+
latestVersion: cache.latestVersion,
|
|
161
|
+
updateAvailable: isNewerVersion(cache.latestVersion, currentVersion),
|
|
162
|
+
lastCheckedAt: cache.lastCheckedAt || now,
|
|
163
|
+
fromCache: true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
currentVersion,
|
|
168
|
+
latestVersion: null,
|
|
169
|
+
updateAvailable: false,
|
|
170
|
+
lastCheckedAt: now,
|
|
171
|
+
fromCache: false,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* runGlobalUpdate() — `npm install -g mixdog@latest` in a background child
|
|
177
|
+
* process (windowsHide so no console flash on Windows). Resolves once the
|
|
178
|
+
* child exits; never throws — failures come back as {ok:false, error}.
|
|
179
|
+
*/
|
|
180
|
+
export function runGlobalUpdate() {
|
|
181
|
+
return new Promise((resolvePromise) => {
|
|
182
|
+
let child;
|
|
183
|
+
try {
|
|
184
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
185
|
+
child = spawn(npmCmd, ['install', '-g', `${PACKAGE_NAME}@latest`], {
|
|
186
|
+
stdio: 'ignore',
|
|
187
|
+
windowsHide: true,
|
|
188
|
+
shell: process.platform === 'win32',
|
|
189
|
+
});
|
|
190
|
+
} catch (err) {
|
|
191
|
+
resolvePromise({ ok: false, error: err?.message || String(err) });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
child.once('error', (err) => {
|
|
195
|
+
resolvePromise({ ok: false, error: err?.message || String(err) });
|
|
196
|
+
});
|
|
197
|
+
// Detach from the event-loop keep-alive so a boot-time auto-update can
|
|
198
|
+
// never hold the TUI/node process open after the user quits; the exit
|
|
199
|
+
// listener still fires as long as the process is otherwise alive.
|
|
200
|
+
child.unref?.();
|
|
201
|
+
child.once('exit', async (code) => {
|
|
202
|
+
if (code === 0) {
|
|
203
|
+
// Best-effort: report whichever version the registry now reports as
|
|
204
|
+
// latest (the just-installed one), falling back to 'unknown'.
|
|
205
|
+
const version = await fetchLatestFromRegistry();
|
|
206
|
+
resolvePromise({ ok: true, version: version || 'unknown' });
|
|
207
|
+
} else {
|
|
208
|
+
resolvePromise({ ok: false, error: `npm install exited with code ${code}` });
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { PACKAGE_NAME as UPDATE_PACKAGE_NAME };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Config normalization helpers: default provider, presets/routes, module
|
|
2
|
+
// enable flags, shell/auto-clear/compaction config, and duration formatting.
|
|
3
|
+
import { clean, hasOwn } from './session-text.mjs';
|
|
4
|
+
import { normalizeEffortInput, normalizeSavedEffort } from './effort.mjs';
|
|
5
|
+
import { routeFastKey, fastPreferenceFor } from './model-capabilities.mjs';
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_PROVIDER = 'anthropic-oauth';
|
|
8
|
+
export const DEFAULT_MODEL = '';
|
|
9
|
+
|
|
10
|
+
// Resolve the provider to use when a route carries no explicit provider.
|
|
11
|
+
// Priority: config.defaultProvider (when it names a known provider) > DEFAULT_PROVIDER.
|
|
12
|
+
export function makeResolveDefaultProvider(isKnownProvider) {
|
|
13
|
+
return function resolveDefaultProvider(config) {
|
|
14
|
+
const configured = clean(config?.defaultProvider);
|
|
15
|
+
if (configured && isKnownProvider(configured)) return configured;
|
|
16
|
+
return DEFAULT_PROVIDER;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function modelSettingsFor(config, provider, model) {
|
|
21
|
+
const key = routeFastKey(provider, model);
|
|
22
|
+
const value = key ? config?.modelSettings?.[key] : null;
|
|
23
|
+
return value && typeof value === 'object' ? value : {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function findPreset(config, key) {
|
|
27
|
+
const wanted = clean(key).toLowerCase();
|
|
28
|
+
if (!wanted) return null;
|
|
29
|
+
const presets = Array.isArray(config?.presets) ? config.presets : [];
|
|
30
|
+
return presets.find((p) => {
|
|
31
|
+
const id = clean(p?.id).toLowerCase();
|
|
32
|
+
const name = clean(p?.name).toLowerCase();
|
|
33
|
+
return id === wanted || name === wanted;
|
|
34
|
+
}) || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function makeResolveRoute(resolveDefaultProvider) {
|
|
38
|
+
return function resolveRoute(config, { provider, model, effort, fast } = {}) {
|
|
39
|
+
const explicitProvider = clean(provider);
|
|
40
|
+
const explicitModel = clean(model);
|
|
41
|
+
const hasExplicitEffort = effort !== undefined;
|
|
42
|
+
const explicitEffort = hasExplicitEffort ? normalizeEffortInput(effort) : undefined;
|
|
43
|
+
const hasExplicitFast = fast !== undefined;
|
|
44
|
+
const explicitFast = fast === true;
|
|
45
|
+
|
|
46
|
+
if (explicitModel && !explicitProvider) {
|
|
47
|
+
const preset = findPreset(config, explicitModel);
|
|
48
|
+
if (preset) {
|
|
49
|
+
const p = clean(preset.provider) || DEFAULT_PROVIDER;
|
|
50
|
+
const m = clean(preset.model) || DEFAULT_MODEL;
|
|
51
|
+
const saved = modelSettingsFor(config, p, m);
|
|
52
|
+
return {
|
|
53
|
+
provider: p,
|
|
54
|
+
model: m,
|
|
55
|
+
preset,
|
|
56
|
+
effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort ?? preset.effort),
|
|
57
|
+
fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : (preset.fast === true || fastPreferenceFor(config, p, m))),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!explicitProvider && !explicitModel) {
|
|
63
|
+
const defaultKey = config?.default;
|
|
64
|
+
const preset = findPreset(config, defaultKey);
|
|
65
|
+
if (preset) {
|
|
66
|
+
const p = clean(preset.provider) || DEFAULT_PROVIDER;
|
|
67
|
+
const m = clean(preset.model) || DEFAULT_MODEL;
|
|
68
|
+
const saved = modelSettingsFor(config, p, m);
|
|
69
|
+
return {
|
|
70
|
+
provider: p,
|
|
71
|
+
model: m,
|
|
72
|
+
preset,
|
|
73
|
+
effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort ?? preset.effort),
|
|
74
|
+
fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : (preset.fast === true || fastPreferenceFor(config, p, m))),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const p = explicitProvider || resolveDefaultProvider(config);
|
|
80
|
+
const m = explicitModel || DEFAULT_MODEL;
|
|
81
|
+
const saved = modelSettingsFor(config, p, m);
|
|
82
|
+
return {
|
|
83
|
+
provider: p,
|
|
84
|
+
model: m,
|
|
85
|
+
preset: null,
|
|
86
|
+
effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort),
|
|
87
|
+
fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : fastPreferenceFor(config, p, m)),
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isLikelyRawModelId(value) {
|
|
93
|
+
const model = clean(value);
|
|
94
|
+
if (!model || model.length > 160) return false;
|
|
95
|
+
if (/\s/.test(model)) return false;
|
|
96
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/.test(model);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function validateRequestedModelSelector(config, requested = {}) {
|
|
100
|
+
const model = clean(requested.model);
|
|
101
|
+
if (!model) return;
|
|
102
|
+
if (findPreset(config, model)) return;
|
|
103
|
+
if (isLikelyRawModelId(model)) return;
|
|
104
|
+
throw new Error(`Invalid model selector "${model}". Use a preset or a model id; free-form text cannot be used as a model.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function ensureProviderEnabled(config, provider) {
|
|
108
|
+
const providers = { ...(config?.providers || {}) };
|
|
109
|
+
providers[provider] = { ...(providers[provider] || {}), enabled: true };
|
|
110
|
+
return providers;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const AUTO_CLEAR_DEFAULT_IDLE_MS = 60 * 60 * 1000;
|
|
114
|
+
|
|
115
|
+
export function normalizeSystemShellConfig(value = {}) {
|
|
116
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
117
|
+
const command = clean(raw.command ?? raw.path ?? raw.executable ?? raw.shell);
|
|
118
|
+
const envCommand = clean(process.env.MIXDOG_SHELL);
|
|
119
|
+
return {
|
|
120
|
+
command,
|
|
121
|
+
effective: command || envCommand || '',
|
|
122
|
+
source: command ? 'config' : (envCommand ? 'env' : 'auto'),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function normalizeSystemShellCommand(value) {
|
|
127
|
+
const command = clean(value).replace(/^auto$/i, '').replace(/^['"](.+)['"]$/, '$1').trim();
|
|
128
|
+
if (!command) return '';
|
|
129
|
+
if (process.platform === 'win32') {
|
|
130
|
+
const stem = command.split(/[\\/]/).pop().toLowerCase().replace(/\.exe$/, '');
|
|
131
|
+
if (stem !== 'powershell' && stem !== 'pwsh') {
|
|
132
|
+
throw new Error('system shell command must be powershell.exe or pwsh on Windows');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return command;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function normalizeAutoClearConfig(value = {}) {
|
|
139
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
140
|
+
const idleMs = Number(raw.idleMs ?? raw.thresholdMs ?? raw.idleMillis);
|
|
141
|
+
return {
|
|
142
|
+
enabled: raw.enabled !== false,
|
|
143
|
+
idleMs: Number.isFinite(idleMs) && idleMs > 0 ? Math.max(60_000, Math.round(idleMs)) : AUTO_CLEAR_DEFAULT_IDLE_MS,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function normalizeCompactTypeSetting(value, fallback = 'semantic') {
|
|
148
|
+
const raw = clean(value).toLowerCase().replace(/_/g, '-');
|
|
149
|
+
if (!raw) return fallback;
|
|
150
|
+
if (raw === '1' || raw === 'type1' || raw === 'type-1' || raw === 'semantic' || raw === 'summary' || raw === 'default') return 'semantic';
|
|
151
|
+
if (raw === '2' || raw === 'type2' || raw === 'type-2' || raw === 'recall' || raw === 'recall-fast' || raw === 'recall-fasttrack' || raw === 'recall-fast-track' || raw === 'fasttrack' || raw === 'fast-track') return 'recall-fasttrack';
|
|
152
|
+
return fallback;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function normalizeCompactionConfig(value = {}, { memoryEnabled = true } = {}) {
|
|
156
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
157
|
+
let compactType = normalizeCompactTypeSetting(raw.compactType ?? raw.compact_type ?? raw.type, 'semantic');
|
|
158
|
+
if (compactType === 'recall-fasttrack' && memoryEnabled === false) compactType = 'semantic';
|
|
159
|
+
return {
|
|
160
|
+
...raw,
|
|
161
|
+
auto: raw.auto !== false && raw.enabled !== false,
|
|
162
|
+
type: compactType,
|
|
163
|
+
compactType,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function moduleEnabled(configLike, name, fallback = true) {
|
|
168
|
+
const entry = configLike?.modules?.[name];
|
|
169
|
+
if (entry && typeof entry === 'object' && entry.enabled === false) return false;
|
|
170
|
+
return fallback !== false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function setModuleEnabledInConfig(configLike, name, enabled) {
|
|
174
|
+
const next = { ...(configLike || {}) };
|
|
175
|
+
next.modules = { ...(next.modules || {}) };
|
|
176
|
+
next.modules[name] = {
|
|
177
|
+
...(next.modules[name] || {}),
|
|
178
|
+
enabled: enabled !== false,
|
|
179
|
+
};
|
|
180
|
+
return next;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function formatDurationMs(ms) {
|
|
184
|
+
const value = Math.max(0, Number(ms) || 0);
|
|
185
|
+
if (value % 3_600_000 === 0) return `${value / 3_600_000}h`;
|
|
186
|
+
if (value % 60_000 === 0) return `${value / 60_000}m`;
|
|
187
|
+
return `${Math.round(value / 1000)}s`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function parseDurationMs(input) {
|
|
191
|
+
const text = clean(input).toLowerCase();
|
|
192
|
+
if (!text) return null;
|
|
193
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(text);
|
|
194
|
+
if (!match) return null;
|
|
195
|
+
const n = Number(match[1]);
|
|
196
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
197
|
+
const unit = match[2] || 'm';
|
|
198
|
+
const mult = unit === 'h' ? 3_600_000 : unit === 'm' ? 60_000 : unit === 's' ? 1000 : 1;
|
|
199
|
+
return Math.max(60_000, Math.round(n * mult));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// A resolved model meta carries catalog-derived fields (contextWindow, pricing,
|
|
203
|
+
// capabilities, …). The lookupModelMeta() fallback for an unknown id is the
|
|
204
|
+
// bare shape `{ id, provider }`, so "more than id/provider" reliably tells a
|
|
205
|
+
// real catalog hit apart from that placeholder.
|
|
206
|
+
export function modelMetaLooksResolved(meta) {
|
|
207
|
+
if (!meta || typeof meta !== 'object') return false;
|
|
208
|
+
return Object.keys(meta).some((key) => key !== 'id' && key !== 'provider');
|
|
209
|
+
}
|