mixdog 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +2 -2
- package/scripts/lib/run-node-tests.mjs +9 -0
- package/scripts/prune-desktop-runtime.mjs +3 -0
- package/scripts/release-paths.mjs +39 -2
- package/scripts/test-direct.mjs +4 -2
- package/src/defaults/shipped-history.json +6 -0
- package/src/defaults/skills/setup/references/actions.md +5 -2
- package/src/defaults/skills/visualize/SKILL.md +157 -0
- package/src/defaults/skills/visualize/references/craft.md +88 -0
- package/src/headless-command.mjs +56 -3
- package/src/headless-exec.mjs +17 -1
- package/src/headless-role.mjs +2 -2
- package/src/help.mjs +2 -0
- package/src/lib/rules-builder.cjs +0 -23
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/70-delivery.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
- package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +9 -1
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
- package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +11 -8
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
- package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -10
- package/src/runtime/attachments/store.mjs +38 -0
- package/src/runtime/channels/index.mjs +1 -5
- package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
- package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
- package/src/runtime/channels/lib/worker-main.mjs +4 -34
- package/src/runtime/shared/atomic-file.mjs +8 -2
- package/src/runtime/shared/best-effort-stderr.mjs +8 -0
- package/src/runtime/shared/code-graph-state.mjs +12 -0
- package/src/runtime/shared/defaults-separation.mjs +2 -0
- package/src/runtime/shared/git-runner-worker.mjs +3 -2
- package/src/runtime/shared/git-runner.mjs +4 -3
- package/src/runtime/shared/llm/default-context-window.mjs +6 -0
- package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
- package/src/runtime/shared/llm/model-catalog.mjs +240 -118
- package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
- package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
- package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
- package/src/runtime/shared/model-proxy-mode.mjs +19 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -2
- package/src/runtime/shared/provider-api-key.mjs +21 -1
- package/src/runtime/shared/schedule-model-ref.mjs +11 -2
- package/src/runtime/shared/semver.d.mts +1 -0
- package/src/runtime/shared/semver.mjs +57 -0
- package/src/runtime/shared/session-usage-totals.mjs +19 -0
- package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
- package/src/runtime/shared/singleton-owner.mjs +36 -6
- package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
- package/src/runtime/shared/tool-surface.mjs +9 -2
- package/src/runtime/shared/tool-work-units.mjs +14 -0
- package/src/runtime/shared/transcript-metadata.mjs +17 -1
- package/src/runtime/shared/turn-changes.mjs +791 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
- package/src/runtime/shared/turn-snapshot.mjs +690 -81
- package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
- package/src/runtime/shared/update-checker.mjs +2 -31
- package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
- package/src/runtime/shared/worktrees/contract.mjs +551 -0
- package/src/runtime/shared/worktrees/disk.mjs +57 -0
- package/src/runtime/shared/worktrees/git.mjs +247 -0
- package/src/runtime/shared/worktrees/include.mjs +480 -0
- package/src/runtime/shared/worktrees/lock.mjs +46 -0
- package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
- package/src/runtime/shared/worktrees/manager.mjs +1215 -0
- package/src/runtime/shared/worktrees/merge.mjs +182 -0
- package/src/runtime/shared/worktrees/owner.mjs +46 -0
- package/src/runtime/shared/worktrees/paths.mjs +62 -0
- package/src/runtime/shared/worktrees/registry.mjs +110 -0
- package/src/runtime/shared/worktrees/settings.mjs +84 -0
- package/src/runtime/shared/worktrees/slug.mjs +62 -0
- package/src/runtime/shared/worktrees/test-support.mjs +96 -0
- package/src/runtime/visualize-bridge/client.mjs +181 -0
- package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
- package/src/session-runtime/boot/apis.mjs +35 -0
- package/src/session-runtime/boot/begin.mjs +0 -2
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +20 -0
- package/src/session-runtime/boot/infrastructure.mjs +1 -0
- package/src/session-runtime/boot/session.mjs +1 -0
- package/src/session-runtime/boot/tools.mjs +9 -4
- package/src/session-runtime/config-lifecycle.mjs +10 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
- package/src/session-runtime/global-extensions.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
- package/src/session-runtime/internal-tool-executor.mjs +9 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
- package/src/session-runtime/mcp-glue/connect.mjs +11 -6
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
- package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
- package/src/session-runtime/model-route/route-persist.mjs +3 -0
- package/src/session-runtime/model-route/set-route.mjs +15 -2
- package/src/session-runtime/notification-bus.mjs +9 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
- package/src/session-runtime/resource-extension-sync.mjs +30 -7
- package/src/session-runtime/resource-mcp-api.mjs +27 -10
- package/src/session-runtime/route-resolution.mjs +10 -1
- package/src/session-runtime/route-state.mjs +16 -2
- package/src/session-runtime/runtime-bootstrap.mjs +0 -4
- package/src/session-runtime/runtime-core.mjs +5 -0
- package/src/session-runtime/runtime-facade.mjs +8 -0
- package/src/session-runtime/runtime-paths.mjs +0 -1
- package/src/session-runtime/runtime-review-api.mjs +9 -0
- package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
- package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
- package/src/session-runtime/services/agent-tool.mjs +1 -0
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/daemon-candidate.mjs +3 -2
- package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
- package/src/session-runtime/session-create/session-options.mjs +10 -1
- package/src/session-runtime/session-turn-api.mjs +1 -0
- package/src/session-runtime/settings-api.mjs +4 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
- package/src/session-runtime/settings-profile-api.mjs +27 -2
- package/src/session-runtime/settings-worktree-api.mjs +17 -0
- package/src/session-runtime/setup-tool/executor.mjs +11 -4
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
- package/src/session-runtime/starter-guide-state.mjs +62 -0
- package/src/session-runtime/turn/agent-ops.mjs +12 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
- package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
- package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
- package/src/session-runtime/turn/turn-run.mjs +6 -7
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
- package/src/session-runtime/workspace/admission-gate.mjs +197 -0
- package/src/session-runtime/workspace/project-branches.mjs +136 -0
- package/src/session-runtime/workspace/transition-steps.mjs +364 -0
- package/src/session-runtime/workspace/transitions.mjs +651 -0
- package/src/session-runtime/workspace/workspace-state.mjs +112 -0
- package/src/standalone/channel-binding.mjs +1 -2
- package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
- package/src/standalone/channel-transport/http-routes.mjs +17 -0
- package/src/standalone/channel-transport.mjs +13 -1
- package/src/standalone/daemon-channels-loader.mjs +2 -3
- package/src/standalone/daemon-desktop-runtime.mjs +1 -0
- package/src/standalone/daemon-owner.mjs +372 -0
- package/src/standalone/daemon-stored-session-views.mjs +4 -1
- package/src/standalone/daemon.mjs +139 -5
- package/src/standalone/desktop-service-registry.mjs +20 -0
- package/src/standalone/local-session-runtime.mjs +13 -78
- package/src/standalone/session-client/attachment-calls.mjs +3 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
- package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
- package/src/standalone/session-client.mjs +104 -13
- package/src/standalone/session-protocol.mjs +17 -0
- package/src/standalone/session-proxy.mjs +3 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
- package/src/standalone/session-service/projection/revisions.mjs +2 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
- package/src/standalone/session-service/projection/workspace.mjs +14 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
- package/src/standalone/session-transport/call-route.mjs +13 -0
- package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
- package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
- package/src/standalone/session-transport/client-registry.mjs +20 -3
- package/src/standalone/session-transport/routes.mjs +79 -2
- package/src/standalone/session-transport/stack.mjs +2 -0
- package/src/standalone/session-transport.mjs +24 -1
- package/src/tui/app/message-selector.mjs +2 -1
- package/src/tui/app/model-options.mjs +35 -5
- package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
- package/src/tui/app/model-picker.mjs +4 -2
- package/src/tui/dist/index.mjs +332 -36
- package/src/tui/index.jsx +10 -2
- package/src/tui/session/compaction-view.mjs +147 -0
- package/src/tui/session/context-state.mjs +6 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/quota-resume.mjs +2 -1
- package/src/tui/session/session-api/commands.mjs +10 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +12 -8
- package/src/tui/session/session-api/intake/rewind.mjs +2 -1
- package/src/tui/session/session-api/integrations.mjs +18 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
- package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
- package/src/tui/session/session-api/routes.mjs +10 -1
- package/src/tui/session/session-api/settings.mjs +2 -0
- package/src/tui/session/session-api/shared.mjs +10 -6
- package/src/tui/session/session-api/worktree.mjs +60 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +31 -12
- package/src/tui/session/session-flow/drain.mjs +15 -1
- package/src/tui/session/session-flow/session-reset.mjs +4 -3
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/transcript-intake.mjs +14 -5
- package/src/tui/session/turn-stream.mjs +32 -4
- package/src/tui/session/turn-tool-cards.mjs +10 -2
- package/src/tui/session/turn.mjs +39 -10
- package/src/runtime/channels/tool-defs.mjs +0 -17
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Where each mixdog provider's model metadata comes from. One table read by
|
|
2
|
+
// both lookup paths (async list enrichment and the sync lookup), so every
|
|
3
|
+
// provider resolves effort ladders, limits, capabilities and pricing in the
|
|
4
|
+
// same precedence order (see lookupModelMetadata in model-catalog.mjs).
|
|
5
|
+
//
|
|
6
|
+
// Fields:
|
|
7
|
+
// modelsDev models.dev provider id holding this provider's rows.
|
|
8
|
+
// sameEndpoint true when that models.dev entry describes the endpoint this
|
|
9
|
+
// provider actually calls; its effort ladders, limits and
|
|
10
|
+
// capabilities then apply. Otherwise it is only the same
|
|
11
|
+
// vendor (another endpoint/account plan): it prices the model
|
|
12
|
+
// and is a first guess for capabilities and the effort ladder
|
|
13
|
+
// the provider's own sources leave undeclared — never limits.
|
|
14
|
+
// relay sells other vendors' models; the serving vendor (read off
|
|
15
|
+
// the model id) is likewise a first guess for capabilities
|
|
16
|
+
// and the effort ladder, and prices a model with no own rates.
|
|
17
|
+
// rosterFiles live-roster cache files the provider's listModels persists.
|
|
18
|
+
// rosterLimitsAuthoritative
|
|
19
|
+
// live-roster limits beat catalog limits (the roster reflects
|
|
20
|
+
// the account/backend window); otherwise they only fill gaps.
|
|
21
|
+
// rosterLadderFinal
|
|
22
|
+
// a roster row's effort ladder is complete, empty included:
|
|
23
|
+
// no guessed ladder is added to a model the roster lists.
|
|
24
|
+
// seedRow offline seed row for a model id, applied after fetched data.
|
|
25
|
+
import { mimoSeedRow, museSeedRow } from './provider-seed-rosters.mjs';
|
|
26
|
+
|
|
27
|
+
function mimoSeedMetadata(model) {
|
|
28
|
+
const seed = mimoSeedRow(model);
|
|
29
|
+
return seed
|
|
30
|
+
? {
|
|
31
|
+
contextWindow: seed.contextWindow,
|
|
32
|
+
outputTokens: seed.outputTokens,
|
|
33
|
+
supportsReasoning: seed.reasoning === true,
|
|
34
|
+
supportsVision: seed.vision === true,
|
|
35
|
+
}
|
|
36
|
+
: null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const PROVIDER_CATALOGS = Object.freeze({
|
|
40
|
+
// api.openai.com — the endpoint models.dev `openai` describes.
|
|
41
|
+
openai: { modelsDev: 'openai', sameEndpoint: true },
|
|
42
|
+
// chatgpt.com/backend-api/codex: subscription backend, not the public API.
|
|
43
|
+
'openai-oauth': {
|
|
44
|
+
modelsDev: 'openai',
|
|
45
|
+
sameEndpoint: false,
|
|
46
|
+
rosterFiles: ['openai-oauth-models.json'],
|
|
47
|
+
rosterLimitsAuthoritative: true,
|
|
48
|
+
},
|
|
49
|
+
// api.anthropic.com with a subscription token: account-scoped windows and
|
|
50
|
+
// effort surface differ from the public API SKU.
|
|
51
|
+
'anthropic-oauth': {
|
|
52
|
+
modelsDev: 'anthropic',
|
|
53
|
+
sameEndpoint: false,
|
|
54
|
+
rosterFiles: ['anthropic-oauth-models.json'],
|
|
55
|
+
rosterLimitsAuthoritative: true,
|
|
56
|
+
},
|
|
57
|
+
// api.anthropic.com with an API key. Shares the OAuth roster file.
|
|
58
|
+
anthropic: { modelsDev: 'anthropic', sameEndpoint: true, rosterFiles: ['anthropic-oauth-models.json'] },
|
|
59
|
+
// generativelanguage.googleapis.com — the endpoint models.dev `google` describes.
|
|
60
|
+
gemini: { modelsDev: 'google', sameEndpoint: true, rosterFiles: ['gemini-models.json'] },
|
|
61
|
+
// cli-chat-proxy.grok.com, not api.x.ai.
|
|
62
|
+
'grok-oauth': {
|
|
63
|
+
modelsDev: 'xai',
|
|
64
|
+
sameEndpoint: false,
|
|
65
|
+
rosterFiles: ['grok-oauth-models.json'],
|
|
66
|
+
rosterLimitsAuthoritative: true,
|
|
67
|
+
},
|
|
68
|
+
// api.meta.ai/v1 — the endpoint models.dev `meta` describes.
|
|
69
|
+
'muse-code-oauth': {
|
|
70
|
+
modelsDev: 'meta',
|
|
71
|
+
sameEndpoint: true,
|
|
72
|
+
rosterFiles: ['muse-code-oauth-models.json'],
|
|
73
|
+
rosterLimitsAuthoritative: true,
|
|
74
|
+
seedRow: museSeedRow,
|
|
75
|
+
},
|
|
76
|
+
// token-plan-*.xiaomimimo.com subscription gateway, not the public API.
|
|
77
|
+
'mimo-oauth': {
|
|
78
|
+
modelsDev: 'xiaomi',
|
|
79
|
+
sameEndpoint: false,
|
|
80
|
+
rosterFiles: ['mimo-oauth-models.json'],
|
|
81
|
+
rosterLimitsAuthoritative: true,
|
|
82
|
+
seedRow: mimoSeedMetadata,
|
|
83
|
+
},
|
|
84
|
+
// Relays: their own models.dev id (when published) is their own endpoint.
|
|
85
|
+
// Effort tiers live in the wire id; a row without levels has no effort
|
|
86
|
+
// surface (resolveAntigravityWireModel drops the effort).
|
|
87
|
+
'antigravity-oauth': { relay: true, rosterFiles: ['antigravity-oauth-models.json'], rosterLadderFinal: true },
|
|
88
|
+
'cursor-oauth': { relay: true },
|
|
89
|
+
'cursor-api': { relay: true },
|
|
90
|
+
'opencode-go': { relay: true },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Confirmed upstream catalog gaps, keyed provider → model. Each entry patches
|
|
94
|
+
// the mapped models.dev row; a live roster that declares the field still wins.
|
|
95
|
+
const VERIFIED_CORRECTIONS = Object.freeze({
|
|
96
|
+
'muse-code-oauth': Object.freeze({
|
|
97
|
+
// models.dev `meta` stops this ladder at xhigh; the Muse OAuth endpoint
|
|
98
|
+
// accepts reasoning.effort "max" for the contributor tier as for 1.3.
|
|
99
|
+
'muse-spark-1.3-contributor': Object.freeze({
|
|
100
|
+
reasoningOptions: Object.freeze([
|
|
101
|
+
Object.freeze({ type: 'effort', values: Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']) }),
|
|
102
|
+
]),
|
|
103
|
+
}),
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const key = (provider) =>
|
|
108
|
+
String(provider || '')
|
|
109
|
+
.trim()
|
|
110
|
+
.toLowerCase();
|
|
111
|
+
|
|
112
|
+
/** Registry entry with defaults; an unlisted provider is its own models.dev id and endpoint. */
|
|
113
|
+
export function providerCatalogEntry(provider) {
|
|
114
|
+
const p = key(provider);
|
|
115
|
+
if (!p) return null;
|
|
116
|
+
const entry = PROVIDER_CATALOGS[p] || {};
|
|
117
|
+
return {
|
|
118
|
+
provider: p,
|
|
119
|
+
modelsDev: entry.modelsDev || p,
|
|
120
|
+
sameEndpoint: entry.sameEndpoint !== false,
|
|
121
|
+
relay: entry.relay === true,
|
|
122
|
+
rosterFiles: entry.rosterFiles || [],
|
|
123
|
+
rosterLimitsAuthoritative: entry.rosterLimitsAuthoritative === true,
|
|
124
|
+
rosterLadderFinal: entry.rosterLadderFinal === true,
|
|
125
|
+
seedRow: entry.seedRow || null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function modelsDevProviderId(provider) {
|
|
130
|
+
return providerCatalogEntry(provider)?.modelsDev || null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function providerRosterFiles(provider) {
|
|
134
|
+
return providerCatalogEntry(provider)?.rosterFiles || [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Providers owning a live-roster file; a provider reading another's file is skipped. */
|
|
138
|
+
export function rosterProviders() {
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const [provider, entry] of Object.entries(PROVIDER_CATALOGS)) {
|
|
142
|
+
const files = entry.rosterFiles || [];
|
|
143
|
+
if (!files.length || files.every((file) => seen.has(file))) continue;
|
|
144
|
+
for (const file of files) seen.add(file);
|
|
145
|
+
out.push(provider);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function providerSeedRow(provider, model) {
|
|
151
|
+
return providerCatalogEntry(provider)?.seedRow?.(String(model || '')) || null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function providerMetadataCorrection(provider, model) {
|
|
155
|
+
return VERIFIED_CORRECTIONS[key(provider)]?.[String(model || '')] || null;
|
|
156
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Offline seed rosters for providers whose discovery endpoint lists bare ids.
|
|
2
|
+
// A seed is the cold-start fallback only: the metadata lookup applies it to
|
|
3
|
+
// fields nothing fetched (live roster, models.dev) supplied, and provider
|
|
4
|
+
// rows never let it override discovered data. Pure data, no provider imports,
|
|
5
|
+
// so the shared sync lookup and the provider transports read one copy.
|
|
6
|
+
|
|
7
|
+
// ── Muse Code (Meta Muse Spark) ─────────────────────────────────────────────
|
|
8
|
+
export const MUSE_DEFAULT_EFFORT_LEVELS = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
|
9
|
+
const MUSE_SIX_LEVELS = Object.freeze([...MUSE_DEFAULT_EFFORT_LEVELS, 'max']);
|
|
10
|
+
const MUSE_SPARK_CONTEXT = 1_048_576;
|
|
11
|
+
export const MUSE_SPARK_MAX_OUTPUT = 131_072;
|
|
12
|
+
const STANDARD_COST = { inputCostPerM: 1.25, outputCostPerM: 4.25, cacheReadCostPerM: 0.15, cacheWriteCostPerM: 0 };
|
|
13
|
+
const CONTRIBUTOR_COST = { inputCostPerM: 0.1, outputCostPerM: 0.2, cacheReadCostPerM: 0.002, cacheWriteCostPerM: 0 };
|
|
14
|
+
|
|
15
|
+
// Only the 1.3 revisions accept `max`.
|
|
16
|
+
const MUSE_SEEDS = Object.freeze({
|
|
17
|
+
'muse-spark-1.1': { name: 'Muse Spark 1.1', levels: MUSE_DEFAULT_EFFORT_LEVELS, cost: STANDARD_COST },
|
|
18
|
+
'muse-spark-1.2': { name: 'Muse Spark 1.2', levels: MUSE_DEFAULT_EFFORT_LEVELS, cost: STANDARD_COST },
|
|
19
|
+
'muse-spark-1.2-contributor': {
|
|
20
|
+
name: 'Muse Spark 1.2 (C)',
|
|
21
|
+
levels: MUSE_DEFAULT_EFFORT_LEVELS,
|
|
22
|
+
cost: CONTRIBUTOR_COST,
|
|
23
|
+
},
|
|
24
|
+
'muse-spark-1.3': { name: 'Muse Spark 1.3', levels: MUSE_SIX_LEVELS, cost: STANDARD_COST },
|
|
25
|
+
'muse-spark-1.3-contributor': { name: 'Muse Spark 1.3 (C)', levels: MUSE_SIX_LEVELS, cost: CONTRIBUTOR_COST },
|
|
26
|
+
});
|
|
27
|
+
export const MUSE_SEED_IDS = Object.freeze(Object.keys(MUSE_SEEDS));
|
|
28
|
+
|
|
29
|
+
function museEffortRow(id, name, levels, cost) {
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
name,
|
|
33
|
+
provider: 'muse-code-oauth',
|
|
34
|
+
family: 'muse-spark',
|
|
35
|
+
contextWindow: MUSE_SPARK_CONTEXT,
|
|
36
|
+
outputTokens: MUSE_SPARK_MAX_OUTPUT,
|
|
37
|
+
supportsReasoning: true,
|
|
38
|
+
supportsVision: true,
|
|
39
|
+
supportsFunctionCalling: true,
|
|
40
|
+
reasoningLevels: [...levels],
|
|
41
|
+
reasoningOptions: [{ type: 'effort', values: [...levels] }],
|
|
42
|
+
...cost,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Revisioned Spark ids the seed does not list inherit the newest seeded
|
|
47
|
+
// revision of their tier (limits, vision, pricing) but take the five-level
|
|
48
|
+
// ladder: only reviewed seeds advertise `max`.
|
|
49
|
+
const SPARK_ID_RE = /^muse-spark-(\d+)\.(\d+)(?:\.(\d+))?(-contributor)?$/i;
|
|
50
|
+
|
|
51
|
+
function revisionOf(id) {
|
|
52
|
+
const m = SPARK_ID_RE.exec(id);
|
|
53
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3] || 0)] : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function compareRevision(a, b) {
|
|
57
|
+
for (let i = 0; i < 3; i += 1) if (a[i] !== b[i]) return a[i] - b[i];
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function museLineageRow(id) {
|
|
62
|
+
const match = SPARK_ID_RE.exec(id);
|
|
63
|
+
if (!match) return null;
|
|
64
|
+
const contributor = Boolean(match[4]);
|
|
65
|
+
let template = null;
|
|
66
|
+
let templateRevision = null;
|
|
67
|
+
for (const seedId of MUSE_SEED_IDS) {
|
|
68
|
+
if (SPARK_ID_RE.exec(seedId)[4] ? !contributor : contributor) continue;
|
|
69
|
+
const candidate = revisionOf(seedId);
|
|
70
|
+
if (!templateRevision || compareRevision(candidate, templateRevision) > 0) {
|
|
71
|
+
template = seedId;
|
|
72
|
+
templateRevision = candidate;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!template) return null;
|
|
76
|
+
const revision =
|
|
77
|
+
match[3] && Number(match[3]) !== 0 ? `${match[1]}.${match[2]}.${match[3]}` : `${match[1]}.${match[2]}`;
|
|
78
|
+
return museEffortRow(
|
|
79
|
+
id,
|
|
80
|
+
`Muse Spark ${revision}${contributor ? ' (C)' : ''}`,
|
|
81
|
+
MUSE_DEFAULT_EFFORT_LEVELS,
|
|
82
|
+
MUSE_SEEDS[template].cost
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Seed row for a seeded id, else the lineage row of a revisioned Spark id, else null. */
|
|
87
|
+
export function museSeedRow(id) {
|
|
88
|
+
const key = String(id || '');
|
|
89
|
+
const seed = MUSE_SEEDS[key];
|
|
90
|
+
if (seed) return museEffortRow(key, seed.name, seed.levels, seed.cost);
|
|
91
|
+
return museLineageRow(key);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Xiaomi MiMo ─────────────────────────────────────────────────────────────
|
|
95
|
+
const MIMO_SEED_CONTEXT = 1_048_576;
|
|
96
|
+
const MIMO_SEED_OUTPUT = 131_072;
|
|
97
|
+
export const MIMO_SEED_MODELS = Object.freeze(
|
|
98
|
+
[
|
|
99
|
+
['mimo-v2.6-pro', 'MiMo-V2.6-Pro', MIMO_SEED_CONTEXT],
|
|
100
|
+
['mimo-v2.6-flash', 'MiMo-V2.6-Flash', MIMO_SEED_CONTEXT],
|
|
101
|
+
['mimo-v2.6-pro-ultraspeed', 'MiMo-V2.6-Pro-Ultraspeed', MIMO_SEED_CONTEXT],
|
|
102
|
+
['mimo-v2.5-pro', 'MiMo-V2.5-Pro', MIMO_SEED_CONTEXT],
|
|
103
|
+
['mimo-v2.5', 'MiMo-V2.5', MIMO_SEED_CONTEXT],
|
|
104
|
+
['mimo-v2-pro', 'MiMo-V2-Pro', MIMO_SEED_CONTEXT],
|
|
105
|
+
['mimo-v2-omni', 'MiMo-V2-Omni', 262_144],
|
|
106
|
+
].map(([id, display_name, context_window]) =>
|
|
107
|
+
Object.freeze({ id, display_name, context_window, max_output_tokens: MIMO_SEED_OUTPUT })
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/** Exact seed id, else the longest seed whose id is a lineage prefix of the
|
|
112
|
+
* model (dated or suffixed variants of a seeded model). */
|
|
113
|
+
export function mimoSeedRow(model) {
|
|
114
|
+
const id = String(model || '').toLowerCase();
|
|
115
|
+
let seed = MIMO_SEED_MODELS.find((item) => item.id === id) || null;
|
|
116
|
+
if (!seed) {
|
|
117
|
+
for (const item of MIMO_SEED_MODELS) {
|
|
118
|
+
if (id.startsWith(`${item.id}-`) && (!seed || item.id.length > seed.id.length)) seed = item;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!seed) return null;
|
|
122
|
+
return {
|
|
123
|
+
id: String(model),
|
|
124
|
+
contextWindow: seed.context_window,
|
|
125
|
+
outputTokens: seed.max_output_tokens,
|
|
126
|
+
reasoning: true,
|
|
127
|
+
vision: true,
|
|
128
|
+
inputModalities: ['text', 'image'],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Remote (SSH) mode switch. With MIXDOG_MODEL_PROXY=client the daemon runs on
|
|
2
|
+
// a server that must never hold model credentials: every model call is
|
|
3
|
+
// executed by the connected desktop client on the user's PC and streamed back
|
|
4
|
+
// (providers/model-proxy-channel.mjs). Read per call so a test or host can
|
|
5
|
+
// flip the environment without reloading modules.
|
|
6
|
+
export const MODEL_PROXY_ENV = 'MIXDOG_MODEL_PROXY';
|
|
7
|
+
|
|
8
|
+
export function modelProxyClientMode(env = process.env) {
|
|
9
|
+
return env?.[MODEL_PROXY_ENV] === 'client';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The refusal every server-side credential refresh raises in this mode. */
|
|
13
|
+
export function serverModelCredentialsDisabledError(what = 'model credentials') {
|
|
14
|
+
const error = new Error(
|
|
15
|
+
`${what} are not available on this server: ${MODEL_PROXY_ENV}=client runs every model call on the client PC.`
|
|
16
|
+
);
|
|
17
|
+
error.code = 'MODEL_CREDENTIALS_DISABLED';
|
|
18
|
+
return error;
|
|
19
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { updateJsonAtomicSync } from './atomic-file.mjs';
|
|
4
|
+
import { parseJsonText, updateJsonAtomicSync } from './atomic-file.mjs';
|
|
5
5
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
6
6
|
|
|
7
7
|
export const ACCOUNT_PROVIDERS = Object.freeze([
|
|
@@ -48,7 +48,7 @@ function validate(raw) {
|
|
|
48
48
|
|
|
49
49
|
function read(dataDir) {
|
|
50
50
|
try {
|
|
51
|
-
return validate(
|
|
51
|
+
return validate(parseJsonText(readFileSync(file(dataDir), 'utf8')));
|
|
52
52
|
} catch (error) {
|
|
53
53
|
if (error.code === 'ENOENT') return validate(null);
|
|
54
54
|
throw error;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
+
import { modelProxyClientMode } from './model-proxy-mode.mjs';
|
|
2
3
|
|
|
3
4
|
const require = createRequire(import.meta.url);
|
|
4
|
-
const { getSecret, hasSecret } = require('../../lib/keychain-cjs.cjs');
|
|
5
|
+
const { getSecret, getSecretAsync, hasSecret } = require('../../lib/keychain-cjs.cjs');
|
|
5
6
|
|
|
6
7
|
// Lightweight API-key lookup shared by pristine startup and the general config
|
|
7
8
|
// loader. This module deliberately has no mixdog-config.json dependency: asking
|
|
@@ -41,6 +42,8 @@ function environmentApiKey(provider) {
|
|
|
41
42
|
/** Presence-only credential probe. On Windows this is a cheap DPAPI-file
|
|
42
43
|
* check; decrypting through PowerShell is reserved for an actual API call. */
|
|
43
44
|
export function hasAgentApiKey(provider) {
|
|
45
|
+
// Remote mode: this server holds no model credentials (model-proxy-mode.mjs).
|
|
46
|
+
if (modelProxyClientMode()) return false;
|
|
44
47
|
const id = String(provider || '').trim();
|
|
45
48
|
if (!id) return false;
|
|
46
49
|
if (environmentApiKey(id)) return true;
|
|
@@ -52,6 +55,7 @@ export function hasAgentApiKey(provider) {
|
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
export function getAgentApiKey(provider) {
|
|
58
|
+
if (modelProxyClientMode()) return null;
|
|
55
59
|
const id = String(provider || '').trim();
|
|
56
60
|
const environment = environmentApiKey(id);
|
|
57
61
|
if (environment) return environment;
|
|
@@ -64,3 +68,19 @@ export function getAgentApiKey(provider) {
|
|
|
64
68
|
}
|
|
65
69
|
return null;
|
|
66
70
|
}
|
|
71
|
+
|
|
72
|
+
/** One provider's key read on demand, warming no other account: the
|
|
73
|
+
* environment first (getAgentApiKey's precedence), then only this provider's
|
|
74
|
+
* keychain entry through the async path (no whole-keyring snapshot). */
|
|
75
|
+
export async function readAgentApiKeyAsync(provider) {
|
|
76
|
+
if (modelProxyClientMode()) return null;
|
|
77
|
+
const id = String(provider || '').trim();
|
|
78
|
+
if (!id) return null;
|
|
79
|
+
const environment = environmentApiKey(id);
|
|
80
|
+
if (environment) return environment;
|
|
81
|
+
try {
|
|
82
|
+
return (await getSecretAsync(`agent.${id}.apiKey`)) || null;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// schedule.model wire format shared by the channels-worker scheduler and the
|
|
2
2
|
// engine-side run-now dispatch: either a config.presets id/name (legacy) or a
|
|
3
|
-
// direct "provider/model[@effort][+fast][?parameter=value]" route string
|
|
4
|
-
// by the desktop schedule editor
|
|
3
|
+
// direct "provider/model[@effort][+fast][+noauto][?parameter=value]" route string
|
|
4
|
+
// written by the desktop schedule editor (`+noauto`: Auto effort off for the
|
|
5
|
+
// route; absent means on). Slash-form values become direct route
|
|
5
6
|
// objects, which agent-dispatch consumes without a presets lookup.
|
|
6
7
|
// The optional route fields an automation run carries, from a preset, a
|
|
7
8
|
// maintenance slot or a parsed route ref. One shape so schedule and webhook
|
|
@@ -13,6 +14,7 @@ export function modelRouteFields(slot = {}) {
|
|
|
13
14
|
model: slot.model,
|
|
14
15
|
...(slot.effort ? { effort: slot.effort } : {}),
|
|
15
16
|
...(slot.fast === true ? { fast: true } : {}),
|
|
17
|
+
...(slot.autoEffort === false ? { autoEffort: false } : {}),
|
|
16
18
|
...(slot.modelParameters ? { modelParameters: { ...slot.modelParameters } } : {}),
|
|
17
19
|
};
|
|
18
20
|
}
|
|
@@ -24,6 +26,7 @@ export function formatScheduleModelRef(route) {
|
|
|
24
26
|
`${route.provider}/${route.model}` +
|
|
25
27
|
(route.effort ? `@${route.effort}` : '') +
|
|
26
28
|
(route.fast === true ? '+fast' : '') +
|
|
29
|
+
(route.autoEffort === false ? '+noauto' : '') +
|
|
27
30
|
(parameters.length ? `?${new URLSearchParams(parameters).toString()}` : '')
|
|
28
31
|
);
|
|
29
32
|
}
|
|
@@ -37,6 +40,11 @@ export function parseScheduleModelRef(ref) {
|
|
|
37
40
|
if (slash <= 0) return raw;
|
|
38
41
|
let rest = routeRef.slice(slash + 1);
|
|
39
42
|
let fast = false;
|
|
43
|
+
let autoEffort = true;
|
|
44
|
+
if (rest.endsWith('+noauto')) {
|
|
45
|
+
autoEffort = false;
|
|
46
|
+
rest = rest.slice(0, -7);
|
|
47
|
+
}
|
|
40
48
|
if (rest.endsWith('+fast')) {
|
|
41
49
|
fast = true;
|
|
42
50
|
rest = rest.slice(0, -5);
|
|
@@ -52,6 +60,7 @@ export function parseScheduleModelRef(ref) {
|
|
|
52
60
|
model: rest,
|
|
53
61
|
...(effort ? { effort } : {}),
|
|
54
62
|
...(fast ? { fast: true } : {}),
|
|
63
|
+
...(autoEffort ? {} : { autoEffort: false }),
|
|
55
64
|
...(query ? { modelParameters: Object.fromEntries(new URLSearchParams(query)) } : {}),
|
|
56
65
|
};
|
|
57
66
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function compareSemver(a: string, b: string): number;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SemVer 2.0.0 precedence (§11), dependency-free so both Node and the desktop
|
|
2
|
+
// renderer can use it. A leading `v` is tolerated, build metadata is ignored,
|
|
3
|
+
// and a missing or non-numeric core part reads as 0.
|
|
4
|
+
|
|
5
|
+
/** Parse a semver-ish string into { parts:[maj,min,patch], prerelease:[ids] }. */
|
|
6
|
+
function parseSemver(value) {
|
|
7
|
+
const text = String(value || '')
|
|
8
|
+
.trim()
|
|
9
|
+
.replace(/^v/i, '')
|
|
10
|
+
.split('+')[0];
|
|
11
|
+
const dash = text.indexOf('-');
|
|
12
|
+
const core = dash === -1 ? text : text.slice(0, dash);
|
|
13
|
+
const parts = core.split('.').map((n) => {
|
|
14
|
+
const num = Number.parseInt(n, 10);
|
|
15
|
+
return Number.isFinite(num) ? num : 0;
|
|
16
|
+
});
|
|
17
|
+
while (parts.length < 3) parts.push(0);
|
|
18
|
+
return { parts, prerelease: dash === -1 ? [] : text.slice(dash + 1).split('.') };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const NUMERIC = /^\d+$/;
|
|
22
|
+
|
|
23
|
+
// Numeric identifiers compare numerically and rank below alphanumeric ones,
|
|
24
|
+
// which compare in ASCII order.
|
|
25
|
+
function compareIdentifiers(a, b) {
|
|
26
|
+
const aNumeric = NUMERIC.test(a);
|
|
27
|
+
const bNumeric = NUMERIC.test(b);
|
|
28
|
+
if (aNumeric && bNumeric) {
|
|
29
|
+
const diff = BigInt(a) - BigInt(b);
|
|
30
|
+
return diff === 0n ? 0 : diff > 0n ? 1 : -1;
|
|
31
|
+
}
|
|
32
|
+
if (aNumeric) return -1;
|
|
33
|
+
if (bNumeric) return 1;
|
|
34
|
+
return a === b ? 0 : a < b ? -1 : 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compare two semver strings: positive if `a` > `b`, negative if `a` < `b`,
|
|
39
|
+
* 0 if they have equal precedence. A prerelease ranks below its release, and
|
|
40
|
+
* of two prereleases sharing every identifier the shorter one is lower.
|
|
41
|
+
*/
|
|
42
|
+
export function compareSemver(a, b) {
|
|
43
|
+
const pa = parseSemver(a);
|
|
44
|
+
const pb = parseSemver(b);
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
const diff = (pa.parts[i] || 0) - (pb.parts[i] || 0);
|
|
47
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
48
|
+
}
|
|
49
|
+
const aPre = pa.prerelease.length;
|
|
50
|
+
const bPre = pb.prerelease.length;
|
|
51
|
+
if (!aPre || !bPre) return aPre === bPre ? 0 : aPre ? -1 : 1;
|
|
52
|
+
for (let i = 0; i < Math.min(aPre, bPre); i++) {
|
|
53
|
+
const diff = compareIdentifiers(pa.prerelease[i], pb.prerelease[i]);
|
|
54
|
+
if (diff !== 0) return diff;
|
|
55
|
+
}
|
|
56
|
+
return Math.sign(aPre - bPre);
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cumulative per-session accounting as agent pool rows publish it, read off
|
|
3
|
+
* the counters the session already persists (totalInputTokens /
|
|
4
|
+
* totalOutputTokens / totalToolCalls, advanced once per provider response).
|
|
5
|
+
* A counter the session never recorded stays unknown (null), not zero.
|
|
6
|
+
*/
|
|
7
|
+
const counter = (value) => (Number.isFinite(value) && value >= 0 ? value : null);
|
|
8
|
+
|
|
9
|
+
export function sessionUsageTotals(session) {
|
|
10
|
+
const input = counter(session?.totalInputTokens);
|
|
11
|
+
const output = counter(session?.totalOutputTokens);
|
|
12
|
+
return {
|
|
13
|
+
usage:
|
|
14
|
+
input === null && output === null
|
|
15
|
+
? null
|
|
16
|
+
: { inputTokens: input ?? 0, outputTokens: output ?? 0, totalTokens: (input ?? 0) + (output ?? 0) },
|
|
17
|
+
toolCalls: counter(session?.totalToolCalls),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Route sharp to its WebAssembly build when this process runs on Electron's
|
|
2
|
+
// Linux binary (the desktop daemon runs as ELECTRON_RUN_AS_NODE).
|
|
3
|
+
//
|
|
4
|
+
// The Electron executable links the system libglib/libgobject for GTK, so they
|
|
5
|
+
// sit in the global symbol scope before sharp loads. sharp's native addon then
|
|
6
|
+
// binds g_object_* to the system GObject instead of the copy bundled in its
|
|
7
|
+
// libvips, and the first pipeline dies with SIGSEGV inside g_object_unref —
|
|
8
|
+
// one pasted image restarted the whole daemon. sharp itself warns about this
|
|
9
|
+
// (SharpElectronLinux). The wasm build links nothing from the host, so it is
|
|
10
|
+
// immune; plain Node keeps the faster native addon.
|
|
11
|
+
import { registerHooks } from 'node:module';
|
|
12
|
+
|
|
13
|
+
const NATIVE_SHARP = /^@img\/sharp-linux(?:musl)?-[^/]+\/sharp\.node$/;
|
|
14
|
+
|
|
15
|
+
export function sharpElectronLinuxRedirect(specifier) {
|
|
16
|
+
return NATIVE_SHARP.test(specifier) ? '@img/sharp-wasm32/sharp.node' : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (process.versions.electron && process.platform === 'linux') {
|
|
20
|
+
registerHooks({
|
|
21
|
+
resolve(specifier, context, nextResolve) {
|
|
22
|
+
return nextResolve(sharpElectronLinuxRedirect(specifier) || specifier, context);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -13,6 +13,9 @@ function writeJsonAtomic(path, value) {
|
|
|
13
13
|
|
|
14
14
|
function acquireClaimLock(path) {
|
|
15
15
|
const lockPath = `${path}.claim.lock`;
|
|
16
|
+
// A first boot (e.g. a daemon an ssh bootstrap starts on a fresh server)
|
|
17
|
+
// has no data directory yet: the lock's parent must exist.
|
|
18
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
16
19
|
for (let i = 0; i < 20; i++) {
|
|
17
20
|
try {
|
|
18
21
|
mkdirSync(lockPath, { recursive: false });
|
|
@@ -32,22 +35,32 @@ function acquireClaimLock(path) {
|
|
|
32
35
|
return null;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
// A live pid alone does not prove the record's process still owns it: the OS
|
|
39
|
+
// reuses pids. `isStale(owner)` lets a caller with a stronger proof (the
|
|
40
|
+
// daemon's published discovery) reject a live-pid record; `stale` reports it.
|
|
41
|
+
export function readSingletonOwner(path, { isStale = null } = {}) {
|
|
36
42
|
const owner = readJsonSafe(path);
|
|
37
|
-
if (!owner || typeof owner !== 'object') return { owner: null, alive: false };
|
|
38
|
-
|
|
43
|
+
if (!owner || typeof owner !== 'object') return { owner: null, alive: false, stale: false };
|
|
44
|
+
const pidAlive = isPidAlive(owner.pid);
|
|
45
|
+
const stale = pidAlive && typeof isStale === 'function' && isStale(owner) === true;
|
|
46
|
+
return { owner, alive: pidAlive && !stale, stale };
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
export function claimSingletonOwner(path, { kind = 'runtime', pid = process.pid, meta = {} } = {}) {
|
|
49
|
+
export function claimSingletonOwner(path, { kind = 'runtime', pid = process.pid, meta = {}, isStale = null } = {}) {
|
|
42
50
|
const owner = {
|
|
43
51
|
kind,
|
|
44
52
|
pid,
|
|
45
53
|
claimedAt: new Date().toISOString(),
|
|
46
54
|
...meta,
|
|
47
55
|
};
|
|
56
|
+
const livePeer = (current) =>
|
|
57
|
+
Boolean(current?.pid) &&
|
|
58
|
+
Number(current.pid) !== Number(pid) &&
|
|
59
|
+
isPidAlive(current.pid) &&
|
|
60
|
+
!(typeof isStale === 'function' && isStale(current) === true);
|
|
48
61
|
|
|
49
62
|
const current = readJsonSafe(path);
|
|
50
|
-
if (
|
|
63
|
+
if (livePeer(current)) {
|
|
51
64
|
return { owned: false, owner: current };
|
|
52
65
|
}
|
|
53
66
|
|
|
@@ -55,7 +68,7 @@ export function claimSingletonOwner(path, { kind = 'runtime', pid = process.pid,
|
|
|
55
68
|
if (!lockPath) return { owned: false, owner: readJsonSafe(path) };
|
|
56
69
|
try {
|
|
57
70
|
const lockedCurrent = readJsonSafe(path);
|
|
58
|
-
if (
|
|
71
|
+
if (livePeer(lockedCurrent)) {
|
|
59
72
|
return { owned: false, owner: lockedCurrent };
|
|
60
73
|
}
|
|
61
74
|
writeJsonAtomic(path, owner);
|
|
@@ -76,6 +89,23 @@ export function releaseSingletonOwner(path, pid = process.pid) {
|
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
|
|
92
|
+
// Rewrite the caller's own record (`owner.pid`) under the claim lock: when it
|
|
93
|
+
// still names that pid or is gone, it becomes `owner` ('refreshed'); a record
|
|
94
|
+
// naming another pid is reported, never overwritten ('superseded'); a claim
|
|
95
|
+
// lock held past its wait leaves the record alone ('busy').
|
|
96
|
+
export function refreshSingletonOwner(path, owner) {
|
|
97
|
+
const lockPath = acquireClaimLock(path);
|
|
98
|
+
if (!lockPath) return { status: 'busy', owner: null };
|
|
99
|
+
try {
|
|
100
|
+
const current = readJsonSafe(path);
|
|
101
|
+
if (current?.pid && Number(current.pid) !== Number(owner.pid)) return { status: 'superseded', owner: current };
|
|
102
|
+
writeJsonAtomic(path, owner);
|
|
103
|
+
return { status: 'refreshed', owner };
|
|
104
|
+
} finally {
|
|
105
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
// Atomic ownership handoff: replace the owner record from fromPid to toOwner
|
|
80
110
|
// under the SAME claim lock, so a concurrent claimSingletonOwner() cannot slip
|
|
81
111
|
// between a release() and a re-claim() and fork a competing daemon. Verifies
|