mixdog 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/package.json +2 -1
- package/scripts/lib/run-node-tests.mjs +9 -0
- package/scripts/prune-desktop-runtime.mjs +3 -0
- package/scripts/test-direct.mjs +4 -2
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/pptx/SKILL.md +6 -6
- package/src/defaults/skills/pptx/references/review.md +6 -4
- package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
- package/src/defaults/skills/setup/references/actions.md +18 -5
- package/src/defaults/skills/visualize/SKILL.md +160 -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/help.mjs +2 -0
- package/src/rules/shared/60-verification.md +3 -1
- package/src/rules/shared/70-delivery.md +4 -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/cache-mtime.mjs +26 -5
- 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 +19 -5
- package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
- package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
- package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
- package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- 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 +32 -12
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
- package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
- 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/lib/gemini-model-catalog.mjs +7 -1
- 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-bridge.mjs +1080 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -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 +15 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
- 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/openai-ws.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
- 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 +15 -11
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
- 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/catalog-db/index.mjs +284 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
- 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/conversation-import/claude.mjs +184 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
- package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
- 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 +5 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -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/pending-message-entry.mjs +17 -4
- 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 +0 -1
- 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 +33 -22
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -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-catalog.mjs +817 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -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/lib/list-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
- 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 +83 -68
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
- 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/search-glob-tool.mjs +13 -10
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
- 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/dispatch/root-resolution.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -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-snapshot.mjs +4 -0
- package/src/runtime/attachments/store.d.mts +3 -0
- package/src/runtime/attachments/store.mjs +38 -0
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
- package/src/runtime/channels/lib/worker-main.mjs +17 -14
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
- package/src/runtime/effort-judge/judge-client.mjs +56 -18
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/session-ingest.mjs +2 -2
- package/src/runtime/shared/atomic-file.mjs +33 -6
- 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/extension-scopes.mjs +5 -3
- package/src/runtime/shared/file-lock.mjs +37 -11
- package/src/runtime/shared/git-runner-worker.mjs +3 -2
- package/src/runtime/shared/git-runner.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
- 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 -130
- package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -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/plugin-manifest.mjs +18 -9
- package/src/runtime/shared/pristine-execution-contract.json +9 -2
- package/src/runtime/shared/pristine-execution.mjs +28 -4
- package/src/runtime/shared/provider-accounts.mjs +125 -21
- package/src/runtime/shared/provider-api-key.mjs +33 -1
- package/src/runtime/shared/schedule-model-ref.mjs +11 -2
- package/src/runtime/shared/session-usage-totals.mjs +19 -0
- package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
- package/src/runtime/shared/shell-display.mjs +9 -3
- package/src/runtime/shared/singleton-owner.mjs +36 -6
- package/src/runtime/shared/skill-mentions.d.mts +10 -0
- package/src/runtime/shared/skill-mentions.mjs +133 -0
- package/src/runtime/shared/skill-script-runtime.mjs +264 -0
- package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
- package/src/runtime/shared/tool-primitives.mjs +35 -2
- package/src/runtime/shared/tool-surface.mjs +7 -0
- 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 +185 -15
- package/src/runtime/shared/worker-requests.mjs +15 -2
- 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/automation-agents.mjs +8 -2
- package/src/session-runtime/boot/apis.mjs +90 -0
- package/src/session-runtime/boot/config.mjs +7 -2
- package/src/session-runtime/boot/facade.mjs +21 -0
- package/src/session-runtime/boot/infrastructure.mjs +30 -18
- package/src/session-runtime/boot/session.mjs +1 -0
- package/src/session-runtime/boot/tools.mjs +4 -0
- package/src/session-runtime/config-lifecycle.mjs +10 -1
- package/src/session-runtime/conversation-import/discovery.mjs +698 -0
- package/src/session-runtime/conversation-import/service.mjs +369 -0
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
- 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 +68 -16
- package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
- package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
- package/src/session-runtime/mcp-glue.mjs +22 -2
- 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/new-session-config.mjs +3 -2
- package/src/session-runtime/notification-bus.mjs +18 -3
- package/src/session-runtime/provider-auth/accounts.mjs +33 -15
- package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
- package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
- package/src/session-runtime/provider-readiness.mjs +16 -10
- package/src/session-runtime/resource-extension-sync.mjs +45 -7
- package/src/session-runtime/resource-mcp-api.mjs +121 -84
- package/src/session-runtime/resource-plugins-api.mjs +29 -41
- package/src/session-runtime/resource-scope-decorate.mjs +6 -8
- package/src/session-runtime/resource-skills-api.mjs +23 -2
- package/src/session-runtime/route-resolution.mjs +10 -1
- package/src/session-runtime/route-state.mjs +16 -2
- package/src/session-runtime/runtime-core.mjs +5 -0
- package/src/session-runtime/runtime-facade.mjs +8 -0
- package/src/session-runtime/runtime-review-api.mjs +9 -0
- package/src/session-runtime/schedule-session-run.mjs +1 -1
- 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-session-router.mjs +4 -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/services/hook-bus/command-handler.mjs +4 -1
- package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
- package/src/session-runtime/services/provider-admin.mjs +64 -18
- package/src/session-runtime/services/usage-dashboard-model.mjs +3 -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-worktree-api.mjs +17 -0
- package/src/session-runtime/setup-tool/executor.mjs +23 -5
- package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
- package/src/session-runtime/skills-api.mjs +7 -3
- package/src/session-runtime/turn/agent-ops.mjs +12 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -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/webhook-session-run.mjs +16 -4
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
- 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/skill-script-entry.mjs +36 -0
- package/src/standalone/channel-transport/http-routes.mjs +17 -0
- package/src/standalone/channel-transport.mjs +13 -1
- 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 +15 -4
- package/src/standalone/daemon.mjs +179 -8
- package/src/standalone/desktop-service-registry.mjs +20 -0
- package/src/standalone/local-session-runtime.mjs +11 -0
- 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 +21 -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 +17 -4
- 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.jsx +9 -1
- package/src/tui/app/app-view/floating-panel.jsx +9 -1
- package/src/tui/app/app-view.jsx +6 -0
- package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
- package/src/tui/app/message-selector.mjs +2 -1
- package/src/tui/app/model-options.mjs +37 -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/app/skill-mention-palette.mjs +88 -0
- package/src/tui/app/slash-palette.mjs +23 -7
- package/src/tui/app/transcript-row-estimate.mjs +11 -3
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
- package/src/tui/app/use-prompt-state.mjs +5 -0
- package/src/tui/components/Message.jsx +22 -1
- package/src/tui/components/PromptInput.jsx +21 -3
- package/src/tui/components/SlashCommandPalette.jsx +12 -3
- package/src/tui/components/TranscriptItem.jsx +9 -1
- package/src/tui/components/prompt-input/key-handler.mjs +21 -8
- package/src/tui/components/user-message-skills.mjs +38 -0
- package/src/tui/dist/index.mjs +839 -207
- 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/queue-helpers.mjs +7 -2
- 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 +4 -2
- package/src/tui/session/session-api/intake/rewind.mjs +2 -1
- package/src/tui/session/session-api/integrations.mjs +17 -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 +9 -0
- 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 +27 -3
- 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/tui-steering-persist.mjs +25 -9
- 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 +56 -10
- package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
- package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
|
@@ -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 {
|
|
4
|
+
import { parseJsonText, updateJsonAtomic } from './atomic-file.mjs';
|
|
5
5
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
6
6
|
|
|
7
7
|
export const ACCOUNT_PROVIDERS = Object.freeze([
|
|
@@ -9,6 +9,7 @@ export const ACCOUNT_PROVIDERS = Object.freeze([
|
|
|
9
9
|
'anthropic-oauth',
|
|
10
10
|
'grok-oauth',
|
|
11
11
|
'muse-code-oauth',
|
|
12
|
+
'command-code-oauth',
|
|
12
13
|
'mimo-oauth',
|
|
13
14
|
'cursor-oauth',
|
|
14
15
|
'antigravity-oauth',
|
|
@@ -48,7 +49,7 @@ function validate(raw) {
|
|
|
48
49
|
|
|
49
50
|
function read(dataDir) {
|
|
50
51
|
try {
|
|
51
|
-
return validate(
|
|
52
|
+
return validate(parseJsonText(readFileSync(file(dataDir), 'utf8')));
|
|
52
53
|
} catch (error) {
|
|
53
54
|
if (error.code === 'ENOENT') return validate(null);
|
|
54
55
|
throw error;
|
|
@@ -74,16 +75,39 @@ function normalizeLabels(pool) {
|
|
|
74
75
|
return { ...pool, accounts };
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
// How the roster picks an account. `manual` serves from the selected account
|
|
79
|
+
// only; `limit` keeps the selected account until it reaches a limit and then
|
|
80
|
+
// moves to the account with the most headroom; `headroom` starts every new
|
|
81
|
+
// session on the account with the most headroom and keeps it for that session.
|
|
82
|
+
export const ACCOUNT_POLICIES = Object.freeze(['manual', 'limit', 'headroom']);
|
|
83
|
+
|
|
84
|
+
// `auto` predates `policy` and is still written beside it, so a build that
|
|
85
|
+
// knows only `auto` reads the same switching on/off. Whenever the two
|
|
86
|
+
// disagree (an older build toggled `auto`), `auto` decides whether switching
|
|
87
|
+
// is on at all.
|
|
88
|
+
export function providerAccountPolicy(pool) {
|
|
89
|
+
if (pool?.auto === false) return 'manual';
|
|
90
|
+
return pool?.policy === 'headroom' ? 'headroom' : 'limit';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizePool(pool) {
|
|
94
|
+
if (!pool) return pool;
|
|
95
|
+
const policy = providerAccountPolicy(pool);
|
|
96
|
+
return { ...normalizeLabels(pool), policy, auto: policy !== 'manual' };
|
|
97
|
+
}
|
|
98
|
+
|
|
77
99
|
export function readProviderAccountPool(provider, dataDir = resolvePluginData()) {
|
|
78
100
|
requireAccountProvider(provider);
|
|
79
|
-
return
|
|
101
|
+
return (
|
|
102
|
+
normalizePool(read(dataDir).providers[provider]) || { accounts: [], selectedId: null, auto: true, policy: 'limit' }
|
|
103
|
+
);
|
|
80
104
|
}
|
|
81
105
|
|
|
82
|
-
function update(provider, mutate) {
|
|
106
|
+
async function update(provider, mutate) {
|
|
83
107
|
requireAccountProvider(provider);
|
|
84
108
|
// Read failures must not be interpreted as an empty account roster.
|
|
85
109
|
read();
|
|
86
|
-
const result =
|
|
110
|
+
const result = await updateJsonAtomic(
|
|
87
111
|
file(),
|
|
88
112
|
(raw) => {
|
|
89
113
|
const state = validate(raw);
|
|
@@ -94,7 +118,7 @@ function update(provider, mutate) {
|
|
|
94
118
|
},
|
|
95
119
|
{ mode: 0o600, secret: true, fsyncDir: true }
|
|
96
120
|
);
|
|
97
|
-
return
|
|
121
|
+
return normalizePool(result.providers[provider]);
|
|
98
122
|
}
|
|
99
123
|
|
|
100
124
|
/**
|
|
@@ -117,7 +141,7 @@ export function newProviderAccountId() {
|
|
|
117
141
|
return randomUUID();
|
|
118
142
|
}
|
|
119
143
|
|
|
120
|
-
export function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
|
|
144
|
+
export async function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
|
|
121
145
|
requireAccountId(id);
|
|
122
146
|
return update(provider, (pool) => {
|
|
123
147
|
if (includeDefault && !pool.accounts.some((row) => row.id === 'default')) {
|
|
@@ -131,12 +155,12 @@ export function registerProviderAccount(provider, id, { label = '', includeDefau
|
|
|
131
155
|
});
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
export function changeProviderAccounts(provider, change) {
|
|
158
|
+
export async function changeProviderAccounts(provider, change) {
|
|
135
159
|
if (
|
|
136
160
|
!change ||
|
|
137
161
|
typeof change !== 'object' ||
|
|
138
162
|
Array.isArray(change) ||
|
|
139
|
-
Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'useCredits', 'rename'].includes(key))
|
|
163
|
+
Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'policy', 'useCredits', 'rename'].includes(key))
|
|
140
164
|
) {
|
|
141
165
|
throw new TypeError('Invalid provider account change.');
|
|
142
166
|
}
|
|
@@ -175,8 +199,19 @@ export function changeProviderAccounts(provider, change) {
|
|
|
175
199
|
}
|
|
176
200
|
if (change.auto !== undefined) {
|
|
177
201
|
if (typeof change.auto !== 'boolean') throw new TypeError('Automatic account switching must be boolean.');
|
|
202
|
+
// Turning switching on keeps a non-manual policy; on from manual is the
|
|
203
|
+
// classic switch-at-limit behaviour.
|
|
204
|
+
const current = providerAccountPolicy(pool);
|
|
205
|
+
if (!change.auto) pool.policy = 'manual';
|
|
206
|
+
else if (current === 'manual') pool.policy = 'limit';
|
|
207
|
+
else pool.policy = current;
|
|
178
208
|
pool.auto = change.auto;
|
|
179
209
|
}
|
|
210
|
+
if (change.policy !== undefined) {
|
|
211
|
+
if (!ACCOUNT_POLICIES.includes(change.policy)) throw new TypeError('Unknown account switching policy.');
|
|
212
|
+
pool.policy = change.policy;
|
|
213
|
+
pool.auto = change.policy !== 'manual';
|
|
214
|
+
}
|
|
180
215
|
if (change.useCredits !== undefined) {
|
|
181
216
|
if (typeof change.useCredits !== 'boolean') throw new TypeError('Credit use must be boolean.');
|
|
182
217
|
pool.useCredits = change.useCredits;
|
|
@@ -184,7 +219,24 @@ export function changeProviderAccounts(provider, change) {
|
|
|
184
219
|
});
|
|
185
220
|
}
|
|
186
221
|
|
|
187
|
-
|
|
222
|
+
// Selects `id` only while the selection is still `expectedSelectedId`, checked
|
|
223
|
+
// under the write's own lock, so a newer selection (a manual switch) wins. A
|
|
224
|
+
// given `expectedPolicy` must also still hold: a policy change while the
|
|
225
|
+
// request ran (e.g. to Manual) cancels the commit.
|
|
226
|
+
// Returns the pool when the selection moved, otherwise null.
|
|
227
|
+
export async function selectProviderAccountIf(provider, expectedSelectedId, id, expectedPolicy) {
|
|
228
|
+
requireAccountId(id);
|
|
229
|
+
let moved = false;
|
|
230
|
+
const pool = await update(provider, (pool) => {
|
|
231
|
+
if (pool.selectedId !== expectedSelectedId || !pool.accounts.some((row) => row.id === id)) return;
|
|
232
|
+
if (expectedPolicy && providerAccountPolicy(pool) !== expectedPolicy) return;
|
|
233
|
+
pool.selectedId = id;
|
|
234
|
+
moved = true;
|
|
235
|
+
});
|
|
236
|
+
return moved ? pool : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function removeProviderAccount(provider, id) {
|
|
188
240
|
requireAccountId(id);
|
|
189
241
|
return update(provider, (pool) => {
|
|
190
242
|
pool.accounts = pool.accounts.filter((row) => row.id !== id);
|
|
@@ -192,7 +244,7 @@ export function removeProviderAccount(provider, id) {
|
|
|
192
244
|
});
|
|
193
245
|
}
|
|
194
246
|
|
|
195
|
-
export function recordProviderAccountUsage(provider, id, snapshot) {
|
|
247
|
+
export async function recordProviderAccountUsage(provider, id, snapshot) {
|
|
196
248
|
if (!snapshot || !Array.isArray(snapshot.quotaWindows)) return;
|
|
197
249
|
const windows = snapshot.quotaWindows.slice(0, 12).map((row) => ({
|
|
198
250
|
label: String(row.label || '').slice(0, 40),
|
|
@@ -200,7 +252,7 @@ export function recordProviderAccountUsage(provider, id, snapshot) {
|
|
|
200
252
|
resetAt: typeof row.resetAt === 'number' && Number.isFinite(row.resetAt) ? row.resetAt : null,
|
|
201
253
|
}));
|
|
202
254
|
if (!readProviderAccountPool(provider).accounts.some((row) => row.id === id)) return;
|
|
203
|
-
update(provider, (pool) => {
|
|
255
|
+
await update(provider, (pool) => {
|
|
204
256
|
const row = pool.accounts.find((entry) => entry.id === id);
|
|
205
257
|
if (!row) return;
|
|
206
258
|
row.usage = { windows, checkedAt: Date.now() };
|
|
@@ -225,7 +277,7 @@ const MAX_BLOCK_MS = 6 * 60 * 60_000;
|
|
|
225
277
|
// early can tell us anything.
|
|
226
278
|
// `options.quota` records that the block came from a typed quota refusal or an
|
|
227
279
|
// exhausted usage window, so later sends still count it as quota evidence.
|
|
228
|
-
export function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
|
|
280
|
+
export async function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
|
|
229
281
|
return update(provider, (pool) => {
|
|
230
282
|
const row = pool.accounts.find((entry) => entry.id === id);
|
|
231
283
|
if (!row) return;
|
|
@@ -245,7 +297,7 @@ export function blockProviderAccount(provider, id, until, model = '', source = '
|
|
|
245
297
|
// the refusal window and the meter recorded against the OLD subscription would
|
|
246
298
|
// otherwise outlive it and refuse a request the provider would have served.
|
|
247
299
|
// Both are dropped; the next usage reading refills them.
|
|
248
|
-
export function clearProviderAccountQuotaState(provider, id) {
|
|
300
|
+
export async function clearProviderAccountQuotaState(provider, id) {
|
|
249
301
|
return update(provider, (pool) => {
|
|
250
302
|
const row = pool.accounts.find((entry) => entry.id === id);
|
|
251
303
|
if (!row) return;
|
|
@@ -318,14 +370,53 @@ export function providerAccountsResetAt(rows, now = Date.now(), model = '', opti
|
|
|
318
370
|
return known.length ? Math.min(...known) : null;
|
|
319
371
|
}
|
|
320
372
|
|
|
373
|
+
function windowFull(row, window, now) {
|
|
374
|
+
return window.usedPct >= 100 && (window.resetAt ? window.resetAt > now : now - row.usage.checkedAt < 60_000);
|
|
375
|
+
}
|
|
376
|
+
|
|
321
377
|
export function providerAccountExhausted(row, now = Date.now(), model = '', options = {}) {
|
|
322
378
|
if (row?.blockedUntil > now && blockAppliesToModel(row, model)) return true;
|
|
323
379
|
if (options.useCredits === true) return false;
|
|
324
380
|
return (row?.usage?.windows || []).some(
|
|
325
|
-
(window) =>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
381
|
+
(window) => quotaWindowAppliesToModel(window, model) && windowFull(row, window, now)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function quotaWindowFamily(window) {
|
|
386
|
+
return FAMILY_QUOTA_WINDOW.exec(String(window?.label || '').trim())?.[1] || '';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Which limit currently holds the account, independent of any one model:
|
|
390
|
+
// `{ scope: 'account' }` when every request is refused (an unscoped window is
|
|
391
|
+
// full, or a block with no narrower meter to attribute it to), `{ scope:
|
|
392
|
+
// 'model', families }` when only model-family meters ("7D Fable") or a block
|
|
393
|
+
// attributed to one are spent, so other models still run — else null.
|
|
394
|
+
export function providerAccountLimit(row, now = Date.now(), options = {}) {
|
|
395
|
+
if (!row) return null;
|
|
396
|
+
const windows = row.usage?.windows || [];
|
|
397
|
+
const blocked = row.blockedUntil > now;
|
|
398
|
+
const scope = String(row.blockedModel || '').trim();
|
|
399
|
+
const scopedWindows = windows.filter((window) => quotaWindowFamily(window));
|
|
400
|
+
// A block counts as family-scoped only when a family meter covers the model
|
|
401
|
+
// that earned it; any other refusal is treated as the account's.
|
|
402
|
+
const covering = scope ? scopedWindows.filter((window) => quotaWindowAppliesToModel(window, scope)) : [];
|
|
403
|
+
if (blocked && !covering.length) return { scope: 'account' };
|
|
404
|
+
const credits = options.useCredits === true;
|
|
405
|
+
if (!credits && windows.some((window) => !quotaWindowFamily(window) && windowFull(row, window, now))) {
|
|
406
|
+
return { scope: 'account' };
|
|
407
|
+
}
|
|
408
|
+
const families = new Set(blocked ? covering.map(quotaWindowFamily) : []);
|
|
409
|
+
if (!credits) {
|
|
410
|
+
for (const window of scopedWindows) if (windowFull(row, window, now)) families.add(quotaWindowFamily(window));
|
|
411
|
+
}
|
|
412
|
+
return families.size ? { scope: 'model', families: [...families] } : null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// The account refuses `model` only through a model-scoped limit: it still
|
|
416
|
+
// serves other models, so it remains the provider's account of record.
|
|
417
|
+
export function providerAccountExhaustedForModelOnly(row, now = Date.now(), model = '', options = {}) {
|
|
418
|
+
return (
|
|
419
|
+
providerAccountExhausted(row, now, model, options) && providerAccountLimit(row, now, options)?.scope === 'model'
|
|
329
420
|
);
|
|
330
421
|
}
|
|
331
422
|
|
|
@@ -347,15 +438,28 @@ function accountHeadroom(row, now, model) {
|
|
|
347
438
|
return used.length ? 100 - Math.max(...used) : null;
|
|
348
439
|
}
|
|
349
440
|
|
|
350
|
-
|
|
441
|
+
// `sessionAccountId` is the account the requesting session already runs on
|
|
442
|
+
// (headroom policy only): kept while usable, so a session switches only on a
|
|
443
|
+
// limit. Without one, the session's first request takes the account with the
|
|
444
|
+
// most headroom.
|
|
445
|
+
export function chooseProviderAccount(
|
|
446
|
+
pool,
|
|
447
|
+
excluded = new Set(),
|
|
448
|
+
now = Date.now(),
|
|
449
|
+
model = '',
|
|
450
|
+
{ sessionAccountId } = {}
|
|
451
|
+
) {
|
|
351
452
|
const options = { useCredits: pool.useCredits === true };
|
|
352
453
|
const usable = (row) => row && !excluded.has(row.id) && !providerAccountExhausted(row, now, model, options);
|
|
353
454
|
const selected = pool.accounts.find((row) => row.id === pool.selectedId);
|
|
354
|
-
|
|
455
|
+
const policy = providerAccountPolicy(pool);
|
|
456
|
+
if (policy === 'manual') {
|
|
355
457
|
if (excluded.has(selected?.id)) return null;
|
|
356
458
|
return options.useCredits || usable(selected) ? selected || null : null;
|
|
357
459
|
}
|
|
358
|
-
|
|
460
|
+
const preferred =
|
|
461
|
+
policy === 'headroom' ? pool.accounts.find((row) => sessionAccountId && row.id === sessionAccountId) : selected;
|
|
462
|
+
if (usable(preferred)) return preferred;
|
|
359
463
|
// Falling back in roster order is blind: the account after the one that just
|
|
360
464
|
// refused may itself be nearly spent, so the request pays a second switch to
|
|
361
465
|
// learn what a recorded meter already knew. Ranking needs evidence for EVERY
|
|
@@ -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,31 @@ export function getAgentApiKey(provider) {
|
|
|
64
68
|
}
|
|
65
69
|
return null;
|
|
66
70
|
}
|
|
71
|
+
|
|
72
|
+
/** The one API-key resolver for key-based providers: an explicit config key
|
|
73
|
+
* wins, otherwise the environment/keychain (getAgentApiKey). */
|
|
74
|
+
export function resolveProviderApiKey(provider, config) {
|
|
75
|
+
return config?.apiKey || getAgentApiKey(provider) || '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Auth-error reload: the stored/environment key is re-read first so a
|
|
79
|
+
* rotated key beats the stale one captured in config. */
|
|
80
|
+
export function reloadProviderApiKey(provider, config) {
|
|
81
|
+
return getAgentApiKey(provider) || config?.apiKey || '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One provider's key read on demand, warming no other account: the
|
|
85
|
+
* environment first (getAgentApiKey's precedence), then only this provider's
|
|
86
|
+
* keychain entry through the async path (no whole-keyring snapshot). */
|
|
87
|
+
export async function readAgentApiKeyAsync(provider) {
|
|
88
|
+
if (modelProxyClientMode()) return null;
|
|
89
|
+
const id = String(provider || '').trim();
|
|
90
|
+
if (!id) return null;
|
|
91
|
+
const environment = environmentApiKey(id);
|
|
92
|
+
if (environment) return environment;
|
|
93
|
+
try {
|
|
94
|
+
return (await getSecretAsync(`agent.${id}.apiKey`)) || null;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -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,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
|
+
}
|
|
@@ -11,9 +11,15 @@
|
|
|
11
11
|
export const POWERSHELL_UTF8_PREFIX =
|
|
12
12
|
'[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $OutputEncoding=New-Object System.Text.UTF8Encoding $false;';
|
|
13
13
|
|
|
14
|
+
// The `mixdog-skill-node` definition PowerShell commands carry next
|
|
15
|
+
// (renderPowerShellSkillFunction) is the same kind of execution detail.
|
|
16
|
+
const SKILL_FUNCTION_LINE = /^[ \t]*function mixdog-skill-node \{[^\r\n]*\r?\n/;
|
|
17
|
+
|
|
14
18
|
export function displayShellCommand(value) {
|
|
15
19
|
const text = String(value ?? '');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
let rest = text.trimStart();
|
|
21
|
+
const prefixed = rest.startsWith(POWERSHELL_UTF8_PREFIX);
|
|
22
|
+
if (prefixed) rest = rest.slice(POWERSHELL_UTF8_PREFIX.length).replace(/^[ \t]*\r?\n/, '');
|
|
23
|
+
const withoutFunction = rest.replace(SKILL_FUNCTION_LINE, '');
|
|
24
|
+
return prefixed || withoutFunction !== rest ? withoutFunction : text;
|
|
19
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
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SkillMention {
|
|
2
|
+
name: string;
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
}
|
|
6
|
+
export function isReservedMentionName(name: unknown): boolean;
|
|
7
|
+
export function isInMarkdownCode(text: unknown, offset: number): boolean;
|
|
8
|
+
export function skillMentionCandidates(text: unknown): SkillMention[];
|
|
9
|
+
export function skillMentions(text: unknown, isSkill: (name: string) => boolean): SkillMention[];
|
|
10
|
+
export function mentionedSkillNames(text: unknown, isSkill: (name: string) => boolean): string[];
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Inline `$name` skill mentions: a skill can be named anywhere in a prompt.
|
|
2
|
+
// A candidate is `$` at the start of the text or after whitespace or an
|
|
3
|
+
// opening bracket/quote, followed by a skill-name token (the same
|
|
4
|
+
// lowercase-letters, digits and hyphens rule, 64 characters at most, that
|
|
5
|
+
// skill-document.mjs enforces). It selects a skill only when the token is
|
|
6
|
+
// exactly a known skill name, so `$HOME`, `$300` and unknown names stay prose.
|
|
7
|
+
// The token ends where no ASCII identifier character follows (`$pptx_env`,
|
|
8
|
+
// `$pptxUPPER` are not `pptx`); any other character, Korean particles
|
|
9
|
+
// included (`$pptx로`), ends it.
|
|
10
|
+
const MENTION = /(^|[\s([{"'`])\$([a-z0-9][a-z0-9-]{0,63})(?![A-Za-z0-9_-])/g;
|
|
11
|
+
|
|
12
|
+
// Common environment-variable names (compared case-insensitively) and
|
|
13
|
+
// numeric-only tokens are never inline mentions, even when a skill has that
|
|
14
|
+
// name: `$path` or `$300` in prose means the variable or the amount. Choosing
|
|
15
|
+
// such a skill explicitly (the `Skill:` header chip) still works.
|
|
16
|
+
const RESERVED_NAMES = new Set([
|
|
17
|
+
'path',
|
|
18
|
+
'home',
|
|
19
|
+
'user',
|
|
20
|
+
'shell',
|
|
21
|
+
'pwd',
|
|
22
|
+
'tmpdir',
|
|
23
|
+
'temp',
|
|
24
|
+
'tmp',
|
|
25
|
+
'lang',
|
|
26
|
+
'term',
|
|
27
|
+
'xdg_config_home',
|
|
28
|
+
'userprofile',
|
|
29
|
+
'appdata',
|
|
30
|
+
'localappdata',
|
|
31
|
+
'programfiles',
|
|
32
|
+
'systemroot',
|
|
33
|
+
'windir',
|
|
34
|
+
'comspec',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/** True when `name` can never be an inline mention (env var or number). */
|
|
38
|
+
export function isReservedMentionName(name) {
|
|
39
|
+
const value = String(name ?? '');
|
|
40
|
+
return /^[0-9]+$/.test(value) || RESERVED_NAMES.has(value.toLowerCase());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Markdown code is never a mention: fenced blocks (``` or ~~~, 3+ chars,
|
|
44
|
+
// optional info string, closed by a fence of the same char at least as long,
|
|
45
|
+
// else running to the end) and inline code spans (a backtick run closed by
|
|
46
|
+
// the next run of exactly the same length; an unmatched run is literal).
|
|
47
|
+
// Deliberate Mixdog difference: Codex (codex-rs/skills/src/mentions.rs) scans
|
|
48
|
+
// raw bytes with no code handling. Offsets stay those of the original text,
|
|
49
|
+
// so desktop highlighting lines up (and never lands inside code).
|
|
50
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
51
|
+
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
52
|
+
|
|
53
|
+
function inlineCodeRanges(source, from, to, ranges) {
|
|
54
|
+
const runs = [];
|
|
55
|
+
const re = /`+/g;
|
|
56
|
+
re.lastIndex = from;
|
|
57
|
+
for (let match = re.exec(source); match && match.index < to; match = re.exec(source)) {
|
|
58
|
+
runs.push({ start: match.index, end: Math.min(to, match.index + match[0].length) });
|
|
59
|
+
}
|
|
60
|
+
for (let index = 0; index < runs.length; index += 1) {
|
|
61
|
+
const length = runs[index].end - runs[index].start;
|
|
62
|
+
const close = runs.findIndex((run, at) => at > index && run.end - run.start === length);
|
|
63
|
+
if (close < 0) continue;
|
|
64
|
+
ranges.push([runs[index].start, runs[close].end]);
|
|
65
|
+
index = close;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** [start, end) offsets of Markdown code (fenced blocks and inline spans). */
|
|
70
|
+
function codeRanges(source) {
|
|
71
|
+
const ranges = [];
|
|
72
|
+
let open = null;
|
|
73
|
+
let proseStart = 0;
|
|
74
|
+
for (let at = 0; at <= source.length; ) {
|
|
75
|
+
const newline = source.indexOf('\n', at);
|
|
76
|
+
const end = newline < 0 ? source.length : newline;
|
|
77
|
+
const next = newline < 0 ? source.length + 1 : newline + 1;
|
|
78
|
+
const line = source.slice(at, end).replace(/\r$/, '');
|
|
79
|
+
if (!open) {
|
|
80
|
+
const fence = FENCE_OPEN.exec(line);
|
|
81
|
+
if (fence && !(fence[1][0] === '`' && fence[2].includes('`'))) {
|
|
82
|
+
inlineCodeRanges(source, proseStart, at, ranges);
|
|
83
|
+
open = { char: fence[1][0], length: fence[1].length, start: at };
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
const fence = FENCE_CLOSE.exec(line);
|
|
87
|
+
if (fence && fence[1][0] === open.char && fence[1].length >= open.length) {
|
|
88
|
+
ranges.push([open.start, Math.min(next, source.length)]);
|
|
89
|
+
proseStart = Math.min(next, source.length);
|
|
90
|
+
open = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
at = next;
|
|
94
|
+
}
|
|
95
|
+
if (open) ranges.push([open.start, source.length]);
|
|
96
|
+
else inlineCodeRanges(source, proseStart, source.length, ranges);
|
|
97
|
+
return ranges;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** True when `offset` in `text` lies inside Markdown code (a fenced block or
|
|
101
|
+
* an inline code span) by the same rules mention recognition uses, so a
|
|
102
|
+
* composer can tell whether a `$` being typed can become a mention. */
|
|
103
|
+
export function isInMarkdownCode(text, offset) {
|
|
104
|
+
const source = typeof text === 'string' ? text : '';
|
|
105
|
+
if (!Number.isInteger(offset) || (!source.includes('`') && !source.includes('~~~'))) return false;
|
|
106
|
+
return codeRanges(source).some(([from, to]) => offset >= from && offset < to);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Every syntactically valid `$name` in `text` outside Markdown code, in
|
|
110
|
+
* order: `start` is the `$`. */
|
|
111
|
+
export function skillMentionCandidates(text) {
|
|
112
|
+
const source = typeof text === 'string' ? text : '';
|
|
113
|
+
const found = [];
|
|
114
|
+
let code = null;
|
|
115
|
+
for (const match of source.matchAll(MENTION)) {
|
|
116
|
+
if (isReservedMentionName(match[2])) continue;
|
|
117
|
+
const start = match.index + match[1].length;
|
|
118
|
+
code ??= source.includes('`') || source.includes('~~~') ? codeRanges(source) : [];
|
|
119
|
+
if (code.some(([from, to]) => start >= from && start < to)) continue;
|
|
120
|
+
found.push({ name: match[2], start, end: start + 1 + match[2].length });
|
|
121
|
+
}
|
|
122
|
+
return found;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Candidates whose name `isSkill(name)` accepts (all occurrences, in order). */
|
|
126
|
+
export function skillMentions(text, isSkill) {
|
|
127
|
+
return skillMentionCandidates(text).filter((mention) => isSkill(mention.name));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Distinct mentioned skill names in first-mention order. */
|
|
131
|
+
export function mentionedSkillNames(text, isSkill) {
|
|
132
|
+
return [...new Set(skillMentions(text, isSkill).map((mention) => mention.name))];
|
|
133
|
+
}
|