mixdog 0.9.95 → 0.9.97
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/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/MIT.txt +56 -0
- package/LICENSES/codex-NOTICE.txt +6 -0
- package/NOTICE.md +46 -9
- package/README.md +0 -17
- package/package.json +16 -12
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +21 -29
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
- package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +34 -34
- package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +76 -248
- package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
- package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
- package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
- package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
- package/src/runtime/memory/lib/http-router.mjs +9 -6
- package/src/runtime/memory/lib/ko-morph.mjs +3 -20
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/agent-route-config.mjs +119 -0
- package/src/runtime/shared/atomic-file.mjs +80 -4
- package/src/runtime/shared/channel-notification-routing.mjs +2 -2
- package/src/runtime/shared/child-guardian.mjs +204 -108
- package/src/runtime/shared/child-spawn-gate.mjs +139 -72
- package/src/runtime/shared/config.mjs +120 -47
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +150 -100
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -27
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +47 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +63 -39
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +45 -17
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +96 -24
- package/src/session-runtime/settings-api.mjs +12 -24
- package/src/session-runtime/tool-defs.mjs +3 -3
- package/src/session-runtime/workflow-agents-api.mjs +55 -81
- package/src/session-runtime/workflow.mjs +14 -24
- package/src/standalone/agent-dispatch-broker.mjs +198 -0
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
- package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
- package/src/standalone/agent-tool/tool-def.mjs +0 -14
- package/src/standalone/channel-admin.mjs +53 -64
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/session-client.mjs +1134 -0
- package/src/standalone/session-protocol.mjs +192 -0
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +34 -21
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +3 -4
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +48 -43
- package/src/tui/app/resume-picker.mjs +2 -2
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker.mjs +31 -33
- package/src/tui/app/transcript-window.mjs +38 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
- package/src/tui/app/use-prompt-handlers.mjs +86 -21
- package/src/tui/app/use-prompt-hint.mjs +3 -2
- package/src/tui/app/use-prompt-queue-history.mjs +10 -3
- package/src/tui/app/use-transcript-scroll.mjs +10 -3
- package/src/tui/app/use-transcript-window.mjs +50 -17
- package/src/tui/components/PromptInput.jsx +71 -38
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
- package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
- package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
- package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
- package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +1031 -1676
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +12 -13
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +38 -4
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
- package/src/tui/{engine → session}/session-api.mjs +110 -93
- package/src/tui/{engine → session}/session-flow.mjs +8 -2
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +38 -176
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
- package/src/tui/session.mjs +22 -0
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-client.mjs +0 -894
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
- package/src/standalone/engine-daemon-protocol.mjs +0 -33
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
} from './tool-catalog.mjs';
|
|
12
12
|
import { getMcpTools } from '../runtime/agent/orchestrator/mcp/client.mjs';
|
|
13
13
|
import { traceTurnTiming } from '../runtime/agent/orchestrator/agent-trace.mjs';
|
|
14
|
-
import { beginTurnSnapshot, completeTurnSnapshot } from '../runtime/shared/turn-snapshot.mjs';
|
|
14
|
+
import { beginTurnSnapshot, cancelTurnSnapshot, completeTurnSnapshot } from '../runtime/shared/turn-snapshot.mjs';
|
|
15
|
+
import { isVisibleStreamProgress } from '../runtime/shared/stream-progress.mjs';
|
|
16
|
+
import { runAbortable, settleWithin, throwIfAborted } from '../runtime/shared/abort-race.mjs';
|
|
15
17
|
|
|
16
18
|
export function splitToolStatusCounts(rows) {
|
|
17
19
|
const list = Array.isArray(rows) ? rows : [];
|
|
@@ -44,8 +46,12 @@ export function createSessionTurnApi(deps) {
|
|
|
44
46
|
scheduleProviderWarmup, scheduleProviderModelWarmup, invalidateContextStatusCache,
|
|
45
47
|
agentTool, recreateCurrentSessionIfReady, invalidatePreSessionToolSurface,
|
|
46
48
|
activeToolSurface, applyResolvedCwd, resolveCwdPath, agentStatusState, notificationListeners,
|
|
47
|
-
awaitInitialMcpConnect, mcpTurnGraceMs =
|
|
48
|
-
getReservedSessionId, sessionTitles,
|
|
49
|
+
awaitInitialMcpConnect, mcpTurnGraceMs = 0, awaitRoutePreparation,
|
|
50
|
+
getReservedSessionId, sessionTitles, registerActiveTurnController,
|
|
51
|
+
beginTurnSnapshotForTurn = beginTurnSnapshot,
|
|
52
|
+
cancelTurnSnapshotForTurn = cancelTurnSnapshot,
|
|
53
|
+
completeTurnSnapshotForTurn = completeTurnSnapshot,
|
|
54
|
+
turnCleanupSettleMs = 2_000,
|
|
49
55
|
} = deps;
|
|
50
56
|
const enqueueRemoteAttachedPrompt = (prompt) => {
|
|
51
57
|
const attachedSession = getSession();
|
|
@@ -79,7 +85,14 @@ export function createSessionTurnApi(deps) {
|
|
|
79
85
|
// a normal user turn and this surface refreshes from disk.
|
|
80
86
|
const attachedSession = getSession();
|
|
81
87
|
if (attachedSession?.remoteAttached) {
|
|
82
|
-
|
|
88
|
+
// Carry the submission id into the spool: the owner reuses it for the
|
|
89
|
+
// queue entry and the settled user row, so the submitting surface's
|
|
90
|
+
// optimistic bubble is released instead of standing beside the
|
|
91
|
+
// mirrored twin (user: 같은 세션에서 메세지가 두 개로 보인다).
|
|
92
|
+
const submissionId = String(options.id || '').trim();
|
|
93
|
+
const delivered = enqueueRemoteAttachedPrompt(
|
|
94
|
+
submissionId ? { content: prompt, id: submissionId } : prompt,
|
|
95
|
+
);
|
|
83
96
|
return {
|
|
84
97
|
// This branch is only a race-safe fallback for callers that reached
|
|
85
98
|
// ask() before the live pipe was ready. Never manufacture an
|
|
@@ -93,6 +106,12 @@ export function createSessionTurnApi(deps) {
|
|
|
93
106
|
// actual turn waits, guaranteeing that it cannot run on a stale route.
|
|
94
107
|
const timingStartedAt = performance.now();
|
|
95
108
|
const timingStartedAtEpoch = Date.now();
|
|
109
|
+
const turnAbortController = new AbortController();
|
|
110
|
+
const unregisterTurnAbort = typeof registerActiveTurnController === 'function'
|
|
111
|
+
? registerActiveTurnController(turnAbortController)
|
|
112
|
+
: () => {};
|
|
113
|
+
const turnSignal = turnAbortController.signal;
|
|
114
|
+
const awaitTurn = (task) => runAbortable(turnSignal, task, 'Turn aborted');
|
|
96
115
|
const submittedAt = Number(options.submittedAt);
|
|
97
116
|
const hasSubmittedAt = Number.isFinite(submittedAt) && submittedAt > 0;
|
|
98
117
|
let turnSnapshotSessionId = null;
|
|
@@ -104,12 +123,13 @@ export function createSessionTurnApi(deps) {
|
|
|
104
123
|
// Capture immediately, but do not put Git on the first-token critical
|
|
105
124
|
// path. Every actual tool execution joins this same promise below, so
|
|
106
125
|
// shell/apply_patch cannot mutate the worktree before the baseline.
|
|
107
|
-
turnSnapshotPromise = Promise.resolve(
|
|
126
|
+
turnSnapshotPromise = Promise.resolve()
|
|
127
|
+
.then(() => beginTurnSnapshotForTurn(getCurrentCwd(), id))
|
|
128
|
+
.catch(() => undefined);
|
|
108
129
|
};
|
|
109
130
|
const routeStartedAt = performance.now();
|
|
110
131
|
startTurnSnapshot(getSession()?.id || getReservedSessionId?.());
|
|
111
|
-
|
|
112
|
-
const routeWaitMs = performance.now() - routeStartedAt;
|
|
132
|
+
let routeWaitMs = 0;
|
|
113
133
|
setActiveTurnCount(getActiveTurnCount() + 1);
|
|
114
134
|
let mcpWaitMs = 0;
|
|
115
135
|
let providerStartedAt = 0;
|
|
@@ -117,9 +137,10 @@ export function createSessionTurnApi(deps) {
|
|
|
117
137
|
let turnTimingEmitted = false;
|
|
118
138
|
// Heavy runtime warmup is one-shot and demand-driven: idle desktop panes
|
|
119
139
|
// never spawn shell/token/shard helpers or a code-graph worker. More
|
|
120
|
-
// importantly, do not start them until the provider has produced
|
|
121
|
-
//
|
|
122
|
-
//
|
|
140
|
+
// importantly, do not start them until the provider has produced visible
|
|
141
|
+
// text/reasoning/tool progress: transport headers and response-created
|
|
142
|
+
// acknowledgements arrive earlier and would make PowerShell + graph
|
|
143
|
+
// workers compete with the actual first-token critical path.
|
|
123
144
|
const heavyRuntimeWarmupPending = typeof getCodeGraphFirstTurnPrewarmDone === 'function'
|
|
124
145
|
&& !getCodeGraphFirstTurnPrewarmDone();
|
|
125
146
|
if (heavyRuntimeWarmupPending) {
|
|
@@ -167,8 +188,13 @@ export function createSessionTurnApi(deps) {
|
|
|
167
188
|
};
|
|
168
189
|
const startedAt = Date.now();
|
|
169
190
|
try {
|
|
170
|
-
await
|
|
171
|
-
|
|
191
|
+
await awaitTurn(() => awaitRoutePreparation?.());
|
|
192
|
+
routeWaitMs = performance.now() - routeStartedAt;
|
|
193
|
+
await awaitTurn(() => refreshSessionForCwdIfNeeded('cwd-change'));
|
|
194
|
+
if (!getSession()?.id) {
|
|
195
|
+
await awaitTurn(() => createCurrentSession('turn', { signal: turnSignal }));
|
|
196
|
+
}
|
|
197
|
+
throwIfAborted(turnSignal);
|
|
172
198
|
// Remote outbound: ensure a transcript writer bound to the current
|
|
173
199
|
// session.id + cwd. Gated on remoteEnabled so non-remote sessions write nothing.
|
|
174
200
|
if (getRemoteEnabled()) {
|
|
@@ -218,7 +244,7 @@ export function createSessionTurnApi(deps) {
|
|
|
218
244
|
session0.deferredInitialRefreshPending = false;
|
|
219
245
|
// Give an in-flight INITIAL MCP connect only a short TTFT grace.
|
|
220
246
|
// Slower servers remain available through the late-tool path.
|
|
221
|
-
try { await awaitMcpGrace(); }
|
|
247
|
+
try { await awaitTurn(() => awaitMcpGrace()); }
|
|
222
248
|
catch { /* gate must never break the turn */ }
|
|
223
249
|
try { refreshInitialDeferredMcpSurface(session0, getMcpTools()); }
|
|
224
250
|
catch { /* first-turn MCP fold must never break the turn */ }
|
|
@@ -234,7 +260,7 @@ export function createSessionTurnApi(deps) {
|
|
|
234
260
|
// short TTFT grace BEFORE folding the catalog so a slow reconnect
|
|
235
261
|
// cannot hold the provider request. No reconnect in flight →
|
|
236
262
|
// immediate no-op.
|
|
237
|
-
try { await awaitMcpGrace(); }
|
|
263
|
+
try { await awaitTurn(() => awaitMcpGrace()); }
|
|
238
264
|
catch { /* gate must never break the turn */ }
|
|
239
265
|
try {
|
|
240
266
|
reconcileDeferredMcpToolCatalog(session0, getMcpTools(), {
|
|
@@ -252,8 +278,13 @@ export function createSessionTurnApi(deps) {
|
|
|
252
278
|
// UserPromptSubmit: a hook FAILURE must not block the turn, but blocked===true MUST throw.
|
|
253
279
|
let promptDispatch = null;
|
|
254
280
|
try {
|
|
255
|
-
promptDispatch = await
|
|
256
|
-
|
|
281
|
+
promptDispatch = await awaitTurn(
|
|
282
|
+
() => hooks.dispatch('UserPromptSubmit', hookCommonPayload({ session_id: session0.id, prompt })),
|
|
283
|
+
);
|
|
284
|
+
} catch {
|
|
285
|
+
throwIfAborted(turnSignal);
|
|
286
|
+
// Ordinary hook failure never blocks the turn.
|
|
287
|
+
}
|
|
257
288
|
if (promptDispatch?.blocked === true) {
|
|
258
289
|
throw new Error(`prompt blocked by hook: ${promptDispatch.reason || ''}`);
|
|
259
290
|
}
|
|
@@ -264,7 +295,6 @@ export function createSessionTurnApi(deps) {
|
|
|
264
295
|
.map((part) => String(part || '').trim())
|
|
265
296
|
.filter(Boolean)
|
|
266
297
|
.join('\n\n');
|
|
267
|
-
providerStartedAt = performance.now();
|
|
268
298
|
const result = await mgr.askSession(
|
|
269
299
|
session0.id,
|
|
270
300
|
prompt,
|
|
@@ -322,22 +352,36 @@ export function createSessionTurnApi(deps) {
|
|
|
322
352
|
onToolApproval: options.onToolApproval,
|
|
323
353
|
onCompactEvent: options.onCompactEvent,
|
|
324
354
|
onStageChange: options.onStageChange,
|
|
355
|
+
onProviderSendStarted: (...args) => {
|
|
356
|
+
// This callback is emitted immediately before provider.send,
|
|
357
|
+
// after session locking, history shaping and pre-send compact.
|
|
358
|
+
// The old timestamp above mgr.askSession mislabeled all of that
|
|
359
|
+
// Mixdog work as provider latency.
|
|
360
|
+
if (!providerStartedAt) providerStartedAt = performance.now();
|
|
361
|
+
return options.onProviderSendStarted?.(...args);
|
|
362
|
+
},
|
|
363
|
+
onToolPhaseStarted: options.onToolPhaseStarted,
|
|
364
|
+
onToolPhaseCompleted: options.onToolPhaseCompleted,
|
|
325
365
|
onStreamDelta: (...args) => {
|
|
326
366
|
let value;
|
|
327
367
|
try {
|
|
328
368
|
value = options.onStreamDelta?.(...args);
|
|
329
369
|
} finally {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
370
|
+
if (isVisibleStreamProgress(args[0])) {
|
|
371
|
+
turnTimingStatus = 'first-visible';
|
|
372
|
+
emitTurnTiming(turnTimingStatus);
|
|
373
|
+
armHeavyRuntimeWarmup('first-visible');
|
|
374
|
+
}
|
|
333
375
|
}
|
|
334
376
|
return value;
|
|
335
377
|
},
|
|
336
378
|
drainSteering: options.drainSteering,
|
|
337
379
|
onSteerMessage: options.onSteerMessage,
|
|
338
380
|
notifyFn: notifyFnForSession(session0.id),
|
|
381
|
+
signal: turnSignal,
|
|
339
382
|
},
|
|
340
383
|
);
|
|
384
|
+
throwIfAborted(turnSignal);
|
|
341
385
|
if (!turnTimingEmitted) turnTimingStatus = 'complete-no-delta';
|
|
342
386
|
setSession(mgr.getSession(session0.id) || getSession());
|
|
343
387
|
try { sessionTitles?.observeThird(getSession()); } catch { /* title refresh is best-effort */ }
|
|
@@ -354,8 +398,11 @@ export function createSessionTurnApi(deps) {
|
|
|
354
398
|
}
|
|
355
399
|
hooks.emit('turn:end', { sessionId: session0.id, elapsedMs: Date.now() - startedAt });
|
|
356
400
|
try {
|
|
357
|
-
await hooks.dispatch('Stop', hookCommonPayload({ session_id: session0.id }));
|
|
358
|
-
} catch {
|
|
401
|
+
await awaitTurn(() => hooks.dispatch('Stop', hookCommonPayload({ session_id: session0.id })));
|
|
402
|
+
} catch {
|
|
403
|
+
throwIfAborted(turnSignal);
|
|
404
|
+
// Ordinary Stop hook failure is best-effort.
|
|
405
|
+
}
|
|
359
406
|
return { result, session: getSession() };
|
|
360
407
|
} catch (error) {
|
|
361
408
|
hooks.emit('turn:error', { sessionId: getSession()?.id || null, elapsedMs: Date.now() - startedAt, error: error?.message || String(error) });
|
|
@@ -372,8 +419,21 @@ export function createSessionTurnApi(deps) {
|
|
|
372
419
|
} finally {
|
|
373
420
|
emitTurnTiming(turnTimingStatus);
|
|
374
421
|
armHeavyRuntimeWarmup('turn-settled');
|
|
375
|
-
|
|
376
|
-
|
|
422
|
+
if (turnSignal.aborted) {
|
|
423
|
+
try { cancelTurnSnapshotForTurn(turnSnapshotSessionId); } catch {}
|
|
424
|
+
void Promise.resolve(turnSnapshotPromise).catch(() => {});
|
|
425
|
+
} else {
|
|
426
|
+
const snapshotCleanup = Promise.resolve(turnSnapshotPromise)
|
|
427
|
+
.then(() => completeTurnSnapshotForTurn(turnSnapshotSessionId));
|
|
428
|
+
snapshotCleanup.catch(() => {});
|
|
429
|
+
try {
|
|
430
|
+
await awaitTurn(() => settleWithin(snapshotCleanup, turnCleanupSettleMs));
|
|
431
|
+
} catch { /* optional review cleanup never overrides turn settlement */ }
|
|
432
|
+
if (turnSignal.aborted) {
|
|
433
|
+
try { cancelTurnSnapshotForTurn(turnSnapshotSessionId); } catch {}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
try { unregisterTurnAbort?.(); } catch {}
|
|
377
437
|
setActiveTurnCount(Math.max(0, getActiveTurnCount() - 1));
|
|
378
438
|
if (!isFirstTurnCompleted()) {
|
|
379
439
|
setFirstTurnCompleted(true);
|
|
@@ -399,6 +459,18 @@ export function createSessionTurnApi(deps) {
|
|
|
399
459
|
pushTranscriptRebind?.();
|
|
400
460
|
return true;
|
|
401
461
|
},
|
|
462
|
+
// Message-selector rewind: drop the chosen user prompt and everything
|
|
463
|
+
// after it from the model history. Unlike clear() the session object is
|
|
464
|
+
// kept (same id, same provider/model/cwd) — only the tail is undone.
|
|
465
|
+
async rewindMessages(options = {}) {
|
|
466
|
+
const session = getSession();
|
|
467
|
+
if (!session?.id) return null;
|
|
468
|
+
const result = await mgr.rewindSessionMessagesTo?.(session.id, options);
|
|
469
|
+
if (!result) return null;
|
|
470
|
+
setSession(mgr.getSession(session.id) || session);
|
|
471
|
+
invalidateContextStatusCache();
|
|
472
|
+
return result;
|
|
473
|
+
},
|
|
402
474
|
// session_manage tool handoff: the engine polls this at turn end and, if
|
|
403
475
|
// set, runs the same clear path the idle auto-clear uses. One-shot read.
|
|
404
476
|
consumePendingSessionReset() {
|
|
@@ -15,7 +15,7 @@ export function createSettingsApi({
|
|
|
15
15
|
// config-lifecycle
|
|
16
16
|
adoptConfig,
|
|
17
17
|
saveConfigAndAdopt,
|
|
18
|
-
|
|
18
|
+
scheduleChannelProviderSave,
|
|
19
19
|
scheduleSkillsSave,
|
|
20
20
|
// normalizers / helpers
|
|
21
21
|
cfgMod,
|
|
@@ -35,7 +35,6 @@ export function createSettingsApi({
|
|
|
35
35
|
formatDurationMs,
|
|
36
36
|
localPackageVersion,
|
|
37
37
|
// state getters / feature flags
|
|
38
|
-
memoryEnabled,
|
|
39
38
|
recapEnabledFn,
|
|
40
39
|
channelsEnabled,
|
|
41
40
|
autoUpdateEnabled,
|
|
@@ -56,7 +55,7 @@ export function createSettingsApi({
|
|
|
56
55
|
forgetDiscordToken,
|
|
57
56
|
saveTelegramToken,
|
|
58
57
|
forgetTelegramToken,
|
|
59
|
-
|
|
58
|
+
setChannelProvider,
|
|
60
59
|
}) {
|
|
61
60
|
return {
|
|
62
61
|
getOnboardingStatus() {
|
|
@@ -173,11 +172,11 @@ export function createSettingsApi({
|
|
|
173
172
|
},
|
|
174
173
|
getCompactionSettings() {
|
|
175
174
|
const config = getConfig();
|
|
176
|
-
return normalizeCompactionConfig(config.compaction
|
|
175
|
+
return normalizeCompactionConfig(config.compaction);
|
|
177
176
|
},
|
|
178
177
|
setCompactionSettings(input = {}) {
|
|
179
178
|
const config = getConfig();
|
|
180
|
-
const current = normalizeCompactionConfig(config.compaction
|
|
179
|
+
const current = normalizeCompactionConfig(config.compaction);
|
|
181
180
|
const next = { ...current };
|
|
182
181
|
if (hasOwn(input, 'auto')) next.auto = input.auto !== false;
|
|
183
182
|
if (hasOwn(input, 'enabled')) next.auto = input.enabled !== false;
|
|
@@ -193,23 +192,23 @@ export function createSettingsApi({
|
|
|
193
192
|
if (hasOwn(input, key)) next[key] = input[key];
|
|
194
193
|
}
|
|
195
194
|
const nextConfig = { ...config };
|
|
196
|
-
nextConfig.compaction = normalizeCompactionConfig(next
|
|
195
|
+
nextConfig.compaction = normalizeCompactionConfig(next);
|
|
197
196
|
saveConfigAndAdopt(nextConfig);
|
|
198
197
|
const config2 = getConfig();
|
|
199
198
|
const session = getSession();
|
|
200
199
|
if (session) {
|
|
201
200
|
session.compaction = {
|
|
202
201
|
...(session.compaction || {}),
|
|
203
|
-
...normalizeCompactionConfig(config2.compaction
|
|
202
|
+
...normalizeCompactionConfig(config2.compaction),
|
|
204
203
|
};
|
|
205
204
|
}
|
|
206
205
|
invalidateContextStatusCache();
|
|
207
|
-
return normalizeCompactionConfig(config2.compaction
|
|
206
|
+
return normalizeCompactionConfig(config2.compaction);
|
|
208
207
|
},
|
|
209
208
|
// Recap toggle: user-facing switch that gates ONLY the background memory
|
|
210
209
|
// cycles (1/2/3). The memory module (transcript watcher/ingest, on-demand
|
|
211
210
|
// recall/fasttrack drains) is always-on. Persisted via the same
|
|
212
|
-
// saveConfigAndAdopt path as compaction/autoClear. The memory
|
|
211
|
+
// saveConfigAndAdopt path as compaction/autoClear. The memory runtime
|
|
213
212
|
// re-reads recap from the agent config section each cycle tick, so toggling
|
|
214
213
|
// takes effect without a restart (no memory-service stop/start here).
|
|
215
214
|
getRecapSettings() {
|
|
@@ -223,17 +222,6 @@ export function createSettingsApi({
|
|
|
223
222
|
invalidateContextStatusCache();
|
|
224
223
|
return this.getRecapSettings();
|
|
225
224
|
},
|
|
226
|
-
// Thin aliases kept for the current TUI callsites (updated separately).
|
|
227
|
-
// enabled here reflects the recap toggle; memory itself is always-on.
|
|
228
|
-
getMemorySettings() {
|
|
229
|
-
return {
|
|
230
|
-
enabled: recapEnabledFn(),
|
|
231
|
-
compactFastTrackAvailable: true,
|
|
232
|
-
};
|
|
233
|
-
},
|
|
234
|
-
async setMemoryEnabled(enabled) {
|
|
235
|
-
return this.setRecapEnabled(enabled);
|
|
236
|
-
},
|
|
237
225
|
getChannelSettings(options = {}) {
|
|
238
226
|
return {
|
|
239
227
|
enabled: channelsEnabled(),
|
|
@@ -362,7 +350,7 @@ export function createSettingsApi({
|
|
|
362
350
|
reloadChannelsSoon();
|
|
363
351
|
return result;
|
|
364
352
|
},
|
|
365
|
-
|
|
353
|
+
setChannelProvider(name) {
|
|
366
354
|
// Validate synchronously (same contract as before: bad input throws on
|
|
367
355
|
// this call so the TUI's try/catch can react immediately). The actual
|
|
368
356
|
// channels-section file read-modify-write is the hitch source on the
|
|
@@ -371,10 +359,10 @@ export function createSettingsApi({
|
|
|
371
359
|
// disk synchronously inside the key handler.
|
|
372
360
|
const value = String(name || '').trim();
|
|
373
361
|
if (value !== 'discord' && value !== 'telegram') {
|
|
374
|
-
throw new Error('
|
|
362
|
+
throw new Error('provider must be discord or telegram');
|
|
375
363
|
}
|
|
376
|
-
|
|
377
|
-
return { ok: true,
|
|
364
|
+
scheduleChannelProviderSave(value);
|
|
365
|
+
return { ok: true, provider: value };
|
|
378
366
|
},
|
|
379
367
|
};
|
|
380
368
|
}
|
|
@@ -13,7 +13,7 @@ export const TOOL_SEARCH_TOOL = {
|
|
|
13
13
|
openWorldHint: false,
|
|
14
14
|
agentHidden: true,
|
|
15
15
|
},
|
|
16
|
-
description: '
|
|
16
|
+
description: 'Deferred-tool activation status; direct calls auto-load.',
|
|
17
17
|
inputSchema: {
|
|
18
18
|
type: 'object',
|
|
19
19
|
properties: {
|
|
@@ -34,7 +34,7 @@ export const CWD_TOOL = {
|
|
|
34
34
|
openWorldHint: false,
|
|
35
35
|
agentHidden: true,
|
|
36
36
|
},
|
|
37
|
-
description: 'Show or set the session work project for tool execution.
|
|
37
|
+
description: 'Show or set the session work project for tool execution.',
|
|
38
38
|
inputSchema: {
|
|
39
39
|
type: 'object',
|
|
40
40
|
properties: {
|
|
@@ -84,7 +84,7 @@ export const SESSION_MANAGE_TOOL = {
|
|
|
84
84
|
openWorldHint: false,
|
|
85
85
|
agentHidden: true,
|
|
86
86
|
},
|
|
87
|
-
description: '
|
|
87
|
+
description: 'Schedule conversation reset at current-turn end.',
|
|
88
88
|
inputSchema: {
|
|
89
89
|
type: 'object',
|
|
90
90
|
properties: {
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
workflowPresetId,
|
|
11
11
|
WORKFLOW_ROUTE_SLOTS,
|
|
12
12
|
FIXED_AGENT_SLOTS,
|
|
13
|
-
agentPresetSlot,
|
|
14
13
|
normalizeAgentId,
|
|
15
14
|
normalizeWorkflowId,
|
|
16
15
|
workflowIdFromName,
|
|
@@ -24,6 +23,7 @@ import { ONBOARDING_VERSION } from './quick-search-models.mjs';
|
|
|
24
23
|
import { findOutputStyle } from './output-styles.mjs';
|
|
25
24
|
import { ensureProviderEnabled } from './config-helpers.mjs';
|
|
26
25
|
import { fastCapableFor } from './model-capabilities.mjs';
|
|
26
|
+
import { canonicalizeAgentRouteStorage } from '../runtime/shared/agent-route-config.mjs';
|
|
27
27
|
|
|
28
28
|
// Onboarding + agents/workflows/output-style selection surface. Extracted
|
|
29
29
|
// verbatim from the runtime API object; stateless helpers are imported directly
|
|
@@ -46,67 +46,54 @@ export function createWorkflowAgentsApi(deps) {
|
|
|
46
46
|
// Search/agent picks) omits defaultRoute entirely and must NOT persist the
|
|
47
47
|
// current route as Main or recreate the session.
|
|
48
48
|
const config = getConfig();
|
|
49
|
-
const defaultRoute = hasOwn(payload, 'defaultRoute')
|
|
50
|
-
? normalizeWorkflowRoute(payload.defaultRoute, getRoute())
|
|
51
|
-
: null;
|
|
52
49
|
const workflowInput = payload.workflowRoutes && typeof payload.workflowRoutes === 'object'
|
|
53
50
|
? payload.workflowRoutes
|
|
54
51
|
: {};
|
|
55
52
|
const nextConfig = { ...config };
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (!isKnownProvider(requested)) throw new Error(`unknown provider "${payload.defaultProvider}"`);
|
|
60
|
-
nextConfig.defaultProvider = requested;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
53
|
+
const defaultRoute = hasOwn(payload, 'defaultRoute')
|
|
54
|
+
? normalizeWorkflowRoute(payload.defaultRoute, getRoute())
|
|
55
|
+
: null;
|
|
63
56
|
let presets = Array.isArray(nextConfig.presets) ? nextConfig.presets.slice() : [];
|
|
64
|
-
const
|
|
65
|
-
const touchedWorkflowSlots = new Set();
|
|
57
|
+
const agentRoutes = { ...(nextConfig.agents || {}) };
|
|
66
58
|
|
|
67
59
|
if (defaultRoute) {
|
|
68
60
|
presets = upsertWorkflowPreset(presets, 'lead', defaultRoute);
|
|
69
|
-
workflowRoutes.lead = defaultRoute;
|
|
70
61
|
nextConfig.default = workflowPresetId('lead');
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
for (const slot of WORKFLOW_ROUTE_SLOTS) {
|
|
65
|
+
if (!hasOwn(workflowInput, slot)) continue;
|
|
74
66
|
const normalized = normalizeWorkflowRoute(workflowInput[slot]);
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
if (slot === 'lead') {
|
|
68
|
+
if (!normalized) continue;
|
|
69
|
+
presets = upsertWorkflowPreset(presets, 'lead', normalized);
|
|
70
|
+
nextConfig.default = workflowPresetId('lead');
|
|
71
|
+
} else if (slot === 'agent') {
|
|
72
|
+
if (normalized) agentRoutes.worker = normalized;
|
|
73
|
+
else delete agentRoutes.worker;
|
|
74
|
+
} else if (slot === 'explorer') {
|
|
75
|
+
if (normalized) agentRoutes.explore = normalized;
|
|
76
|
+
else delete agentRoutes.explore;
|
|
77
|
+
} else if (slot === 'memory') {
|
|
78
|
+
if (normalized) agentRoutes.maintainer = normalized;
|
|
79
|
+
else delete agentRoutes.maintainer;
|
|
80
|
+
}
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
nextConfig.presets = presets;
|
|
82
|
-
nextConfig.
|
|
83
|
-
nextConfig.maintenance = {
|
|
84
|
-
...(nextConfig.maintenance || {}),
|
|
85
|
-
...(touchedWorkflowSlots.has('explorer') ? { explore: normalizeWorkflowRoute(workflowRoutes.explorer) } : {}),
|
|
86
|
-
...(touchedWorkflowSlots.has('memory') ? { memory: normalizeWorkflowRoute(workflowRoutes.memory) } : {}),
|
|
87
|
-
};
|
|
84
|
+
nextConfig.agents = agentRoutes;
|
|
88
85
|
const agentInput = payload.agentRoutes && typeof payload.agentRoutes === 'object'
|
|
89
86
|
? payload.agentRoutes
|
|
90
87
|
: null;
|
|
91
88
|
if (agentInput) {
|
|
92
|
-
const nextAgents = { ...
|
|
93
|
-
const nextMaintenance = { ...(nextConfig.maintenance || {}) };
|
|
89
|
+
const nextAgents = { ...agentRoutes };
|
|
94
90
|
for (const agent of FIXED_AGENT_SLOTS) {
|
|
91
|
+
if (!hasOwn(agentInput, agent.id)) continue;
|
|
95
92
|
const routeToSave = normalizeWorkflowRoute(agentInput[agent.id]);
|
|
96
|
-
if (
|
|
97
|
-
nextAgents[agent.id]
|
|
98
|
-
presets = upsertWorkflowPreset(presets, agentPresetSlot(agent.id), routeToSave);
|
|
99
|
-
if (agent.workflowSlot) {
|
|
100
|
-
workflowRoutes[agent.workflowSlot] = routeToSave;
|
|
101
|
-
presets = upsertWorkflowPreset(presets, agent.workflowSlot, routeToSave);
|
|
102
|
-
if (agent.id === 'explore') nextMaintenance.explore = routeToSave;
|
|
103
|
-
if (agent.id === 'maintainer') nextMaintenance.memory = routeToSave;
|
|
104
|
-
}
|
|
93
|
+
if (routeToSave) nextAgents[agent.id] = routeToSave;
|
|
94
|
+
else delete nextAgents[agent.id];
|
|
105
95
|
}
|
|
106
96
|
nextConfig.agents = nextAgents;
|
|
107
|
-
nextConfig.presets = presets;
|
|
108
|
-
nextConfig.workflowRoutes = workflowRoutes;
|
|
109
|
-
nextConfig.maintenance = nextMaintenance;
|
|
110
97
|
}
|
|
111
98
|
nextConfig.onboarding = {
|
|
112
99
|
...(nextConfig.onboarding || {}),
|
|
@@ -116,11 +103,13 @@ export function createWorkflowAgentsApi(deps) {
|
|
|
116
103
|
};
|
|
117
104
|
|
|
118
105
|
if (payload.searchRoute) {
|
|
119
|
-
const searchToSave =
|
|
106
|
+
const searchToSave = clean(payload.searchRoute.provider)
|
|
107
|
+
? normalizeSearchRouteConfig(payload.searchRoute)
|
|
108
|
+
: normalizeSearchRouteConfig({ provider: 'default', model: 'default', toolType: payload.searchRoute.toolType });
|
|
120
109
|
if (searchToSave) nextConfig.searchRoute = searchToSave;
|
|
121
110
|
}
|
|
122
111
|
|
|
123
|
-
saveConfigAndAdopt(nextConfig);
|
|
112
|
+
saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
124
113
|
if (defaultRoute) {
|
|
125
114
|
setRouteState(resolveRoute(getConfig(), { provider: defaultRoute.provider, model: defaultRoute.model, effort: defaultRoute.effort }));
|
|
126
115
|
const session = getSession();
|
|
@@ -362,12 +351,6 @@ export function createWorkflowAgentsApi(deps) {
|
|
|
362
351
|
clearAgentDefinitionCache(id);
|
|
363
352
|
if (payload.route) {
|
|
364
353
|
await this.setAgentRoute(id, payload.route);
|
|
365
|
-
} else if (!FIXED_AGENT_SLOTS.some((agent) => agent.id === id)
|
|
366
|
-
&& !agentRouteFromConfig(getConfig(), id, dataDir)) {
|
|
367
|
-
// Custom roles have no built-in preset fallback, so a model-less save
|
|
368
|
-
// would spawn as `agent "<id>" has no model assignment`. Seed the
|
|
369
|
-
// current default route; an unconfigured install just stays routeless.
|
|
370
|
-
try { await this.setAgentRoute(id, {}); } catch { /* no default route yet */ }
|
|
371
354
|
}
|
|
372
355
|
return this.getAgentDefinition(id);
|
|
373
356
|
},
|
|
@@ -412,10 +395,7 @@ export function createWorkflowAgentsApi(deps) {
|
|
|
412
395
|
delete agents[id];
|
|
413
396
|
nextConfig.agents = agents;
|
|
414
397
|
}
|
|
415
|
-
|
|
416
|
-
nextConfig.presets = (Array.isArray(nextConfig.presets) ? nextConfig.presets : [])
|
|
417
|
-
.filter((preset) => clean(preset?.id) !== presetId);
|
|
418
|
-
saveConfigAndAdopt(nextConfig);
|
|
398
|
+
saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
419
399
|
}
|
|
420
400
|
return { id, deleted: true, revertedToBuiltIn };
|
|
421
401
|
},
|
|
@@ -435,23 +415,31 @@ export function createWorkflowAgentsApi(deps) {
|
|
|
435
415
|
const requested = { ...(next || {}) };
|
|
436
416
|
const routeDataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
437
417
|
const stored = agentRouteFromConfig(getConfig(), id, routeDataDir) || {};
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
: (sameModel ? (stored.effort || null) : null),
|
|
450
|
-
fast: requested.fast !== undefined
|
|
451
|
-
? selectedRoute.fast === true
|
|
452
|
-
: (sameModel && stored.fast === true),
|
|
453
|
-
};
|
|
418
|
+
// No provider means no explicit override. Remove the stored route so the
|
|
419
|
+
// agent follows Main dynamically; never synthesize a provider.
|
|
420
|
+
if (!clean(requested.provider)) {
|
|
421
|
+
const nextConfig = { ...getConfig() };
|
|
422
|
+
const agents = { ...(nextConfig.agents || {}) };
|
|
423
|
+
const hadOverride = Object.prototype.hasOwnProperty.call(agents, id);
|
|
424
|
+
delete agents[id];
|
|
425
|
+
nextConfig.agents = agents;
|
|
426
|
+
if (hadOverride) saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
427
|
+
const inherited = normalizeWorkflowRoute(resolveRoute(getConfig(), {}));
|
|
428
|
+
return { ...(inherited || {}), inherited: true };
|
|
454
429
|
}
|
|
430
|
+
if (!clean(requested.model)) throw new Error('agent route requires provider and model');
|
|
431
|
+
let selectedRoute = resolveRoute(getConfig(), requested);
|
|
432
|
+
const sameModel = clean(selectedRoute.provider) === clean(stored.provider)
|
|
433
|
+
&& clean(selectedRoute.model) === clean(stored.model);
|
|
434
|
+
selectedRoute = {
|
|
435
|
+
...selectedRoute,
|
|
436
|
+
effort: requested.effort !== undefined
|
|
437
|
+
? selectedRoute.effort
|
|
438
|
+
: (sameModel ? (stored.effort || null) : null),
|
|
439
|
+
fast: requested.fast !== undefined
|
|
440
|
+
? selectedRoute.fast === true
|
|
441
|
+
: (sameModel && stored.fast === true),
|
|
442
|
+
};
|
|
455
443
|
await ensureProvidersReady(ensureProviderEnabled(getConfig(), selectedRoute.provider));
|
|
456
444
|
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model);
|
|
457
445
|
const fastCapable = fastCapableFor(selectedRoute.provider, modelMeta);
|
|
@@ -459,26 +447,12 @@ export function createWorkflowAgentsApi(deps) {
|
|
|
459
447
|
|
|
460
448
|
const routeToSave = normalizeWorkflowRoute(selectedRoute);
|
|
461
449
|
if (!routeToSave) throw new Error('agent route requires provider and model');
|
|
462
|
-
const agent = FIXED_AGENT_SLOTS.find((item) => item.id === id);
|
|
463
450
|
const nextConfig = { ...getConfig() };
|
|
464
451
|
nextConfig.agents = {
|
|
465
452
|
...(nextConfig.agents || {}),
|
|
466
453
|
[id]: routeToSave,
|
|
467
454
|
};
|
|
468
|
-
|
|
469
|
-
if (agent?.workflowSlot) {
|
|
470
|
-
nextConfig.workflowRoutes = {
|
|
471
|
-
...(nextConfig.workflowRoutes || {}),
|
|
472
|
-
[agent.workflowSlot]: routeToSave,
|
|
473
|
-
};
|
|
474
|
-
nextConfig.presets = upsertWorkflowPreset(nextConfig.presets, agent.workflowSlot, routeToSave);
|
|
475
|
-
nextConfig.maintenance = {
|
|
476
|
-
...(nextConfig.maintenance || {}),
|
|
477
|
-
...(id === 'explore' ? { explore: routeToSave } : {}),
|
|
478
|
-
...(id === 'maintainer' ? { memory: routeToSave } : {}),
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
saveConfigAndAdopt(nextConfig);
|
|
455
|
+
saveConfigAndAdopt(canonicalizeAgentRouteStorage(nextConfig));
|
|
482
456
|
return routeToSave;
|
|
483
457
|
},
|
|
484
458
|
};
|
|
@@ -9,6 +9,7 @@ import { normalizeEffortInput } from './effort.mjs';
|
|
|
9
9
|
import { isLikelyRawModelId } from './config-helpers.mjs';
|
|
10
10
|
import { readTextSafe, readJsonSafe } from './fs-utils.mjs';
|
|
11
11
|
import { isHiddenAgent } from '../runtime/agent/orchestrator/internal-agents.mjs';
|
|
12
|
+
import { configuredAgentRouteCandidates } from '../runtime/shared/agent-route-config.mjs';
|
|
12
13
|
|
|
13
14
|
export const WORKFLOW_ROUTE_SLOTS = ['lead', 'agent', 'explorer', 'memory'];
|
|
14
15
|
export const FIXED_AGENT_SLOTS = Object.freeze([
|
|
@@ -415,16 +416,16 @@ export function upsertWorkflowPreset(presets, slot, routeLike) {
|
|
|
415
416
|
// the runtime; created via this factory.
|
|
416
417
|
export function createWorkflowRouteHelpers({ resolveDefaultProvider, findPreset }) {
|
|
417
418
|
function summarizeWorkflowRoutes(config) {
|
|
418
|
-
const routes = config?.workflowRoutes && typeof config.workflowRoutes === 'object' ? config.workflowRoutes : {};
|
|
419
|
-
const fallbackProvider = resolveDefaultProvider(config);
|
|
420
419
|
const out = {};
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
420
|
+
const lead = routeFromPreset(config, config?.default);
|
|
421
|
+
if (lead) out.lead = lead;
|
|
422
|
+
for (const [slot, agentId] of [
|
|
423
|
+
['agent', 'worker'],
|
|
424
|
+
['explorer', 'explore'],
|
|
425
|
+
['memory', 'maintainer'],
|
|
426
|
+
]) {
|
|
427
|
+
const route = agentRouteFromConfig(config, agentId);
|
|
428
|
+
if (route) out[slot] = route;
|
|
428
429
|
}
|
|
429
430
|
return out;
|
|
430
431
|
}
|
|
@@ -446,22 +447,11 @@ export function createWorkflowRouteHelpers({ resolveDefaultProvider, findPreset
|
|
|
446
447
|
// their routes live in config.agents[<id>] like the fixed roles.
|
|
447
448
|
const id = normalizeAgentId(agentId) || normalizeWorkflowId(agentId);
|
|
448
449
|
if (!id) return null;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|| (id === 'maintainer' ? normalizeWorkflowRoute(config?.agents?.maintenance, fallback) : null);
|
|
454
|
-
if (explicit) return explicit;
|
|
455
|
-
|
|
456
|
-
const agent = FIXED_AGENT_SLOTS.find((item) => item.id === id);
|
|
457
|
-
if (agent?.workflowSlot) {
|
|
458
|
-
const workflowRoute = normalizeWorkflowRoute(config?.workflowRoutes?.[agent.workflowSlot], fallback);
|
|
459
|
-
if (workflowRoute) return workflowRoute;
|
|
450
|
+
for (const candidate of configuredAgentRouteCandidates(config, id)) {
|
|
451
|
+
const route = normalizeWorkflowRoute(candidate)
|
|
452
|
+
|| routeFromPreset(config, candidate);
|
|
453
|
+
if (route) return route;
|
|
460
454
|
}
|
|
461
|
-
|
|
462
|
-
if (id === 'explore') return routeFromPreset(config, config?.maintenance?.explore);
|
|
463
|
-
if (id === 'maintainer') return routeFromPreset(config, config?.maintenance?.memory);
|
|
464
|
-
|
|
465
455
|
return null;
|
|
466
456
|
}
|
|
467
457
|
|