mixdog 0.9.151 → 0.9.153
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/browser-import-NOTICE.txt +11 -0
- package/NOTICE.md +11 -0
- package/README.md +108 -68
- package/package.json +17 -4
- package/scripts/computer-task-cost-report.mjs +144 -0
- package/scripts/run-office-live-tests.mjs +32 -0
- package/scripts/tool-stress.mjs +0 -2
- package/src/agents/front-worker/AGENT.md +13 -0
- package/src/agents/front-worker/agent.json +6 -0
- package/src/agents/heavy-worker/AGENT.md +10 -11
- package/src/agents/heavy-worker/agent.json +1 -1
- package/src/agents/maintainer/AGENT.md +0 -2
- package/src/agents/reviewer/AGENT.md +9 -13
- package/src/agents/reviewer/agent.json +1 -1
- package/src/agents/security/AGENT.md +14 -0
- package/src/agents/security/agent.json +6 -0
- package/src/agents/worker/AGENT.md +8 -8
- package/src/agents/worker/agent.json +1 -1
- package/src/agents/writer/AGENT.md +14 -0
- package/src/agents/writer/agent.json +6 -0
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/rules/agent/00-common.md +4 -7
- package/src/rules/lead/01-general.md +0 -3
- package/src/rules/shared/10-tool-workflow.md +10 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
- package/src/runtime/agent/orchestrator/config.mjs +13 -68
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
- package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
- package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/attachments/pdf-extract.mjs +3 -0
- package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
- package/src/runtime/bridge-clients.test.mjs +827 -0
- package/src/runtime/browser-bridge/action-schema.mjs +251 -0
- package/src/runtime/browser-bridge/client.mjs +152 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
- package/src/runtime/computer-bridge/action-schema.mjs +653 -0
- package/src/runtime/computer-bridge/client.mjs +231 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/office/assurance-benchmark.mjs +358 -0
- package/src/runtime/office/assurance.mjs +833 -0
- package/src/runtime/office/benchmark.mjs +496 -0
- package/src/runtime/office/capabilities.mjs +551 -0
- package/src/runtime/office/com-adapter.mjs +283 -0
- package/src/runtime/office/content-model.mjs +183 -0
- package/src/runtime/office/contract-benchmark.mjs +105 -0
- package/src/runtime/office/design-library.mjs +1562 -0
- package/src/runtime/office/design-system.mjs +1966 -0
- package/src/runtime/office/index.mjs +2413 -0
- package/src/runtime/office/journal.mjs +82 -0
- package/src/runtime/office/office-assurance.test.mjs +371 -0
- package/src/runtime/office/office-com-host.ps1 +3781 -0
- package/src/runtime/office/office-com-session-host.ps1 +855 -0
- package/src/runtime/office/office-live-runtime.test.mjs +819 -0
- package/src/runtime/office/office-runtime.test.mjs +2070 -0
- package/src/runtime/office/ooxml-validator.mjs +271 -0
- package/src/runtime/office/pagination.mjs +132 -0
- package/src/runtime/office/pdf-adapter.mjs +591 -0
- package/src/runtime/office/pdf-analysis.mjs +372 -0
- package/src/runtime/office/pdf-render.mjs +167 -0
- package/src/runtime/office/pdf-security.mjs +63 -0
- package/src/runtime/office/polish-benchmark.mjs +190 -0
- package/src/runtime/office/portable-ooxml.mjs +1867 -0
- package/src/runtime/office/quality-live-benchmark.mjs +486 -0
- package/src/runtime/office/quality-pipeline.mjs +136 -0
- package/src/runtime/office/tabular.mjs +302 -0
- package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
- package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
- package/src/runtime/office/tool-defs.mjs +113 -0
- package/src/runtime/office/visual-diff.mjs +89 -0
- package/src/runtime/office/xlsx-assertions.mjs +172 -0
- package/src/runtime/office/xlsx-contract.mjs +147 -0
- package/src/runtime/shared/agent-route-config.mjs +9 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-execution-contract.mjs +16 -0
- package/src/runtime/shared/tool-surface.mjs +27 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/agent-disable.test.mjs +20 -1
- package/src/session-runtime/context-status.mjs +9 -3
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/deferred-tool-delta.mjs +104 -0
- package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
- package/src/session-runtime/goal-runtime.mjs +1141 -0
- package/src/session-runtime/goal-runtime.test.mjs +645 -0
- package/src/session-runtime/lifecycle-api.mjs +58 -11
- package/src/session-runtime/lifecycle-api.test.mjs +183 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/provider-request-snapshot.mjs +33 -9
- package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
- package/src/session-runtime/resource-api-global.test.mjs +109 -0
- package/src/session-runtime/resource-api.mjs +168 -116
- package/src/session-runtime/runtime-core.mjs +208 -29
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-lifecycle.mjs +34 -12
- package/src/session-runtime/session-title.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +37 -25
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-data.mjs +1 -1
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +42 -112
- package/src/session-runtime/tool-defs.mjs +6 -1
- package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
- package/src/session-runtime/tool-surface.mjs +30 -8
- package/src/session-runtime/workflow-agents-api.mjs +8 -25
- package/src/session-runtime/workflow.mjs +8 -6
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/job-views.mjs +3 -1
- package/src/standalone/agent-tool/notify.mjs +11 -9
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/agent-tool/render.mjs +3 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
- package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/agent-tool/worker-index.mjs +123 -0
- package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +57 -10
- package/src/standalone/daemon.mjs +113 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +709 -58
- package/src/standalone/session-runtime-inline-host.mjs +258 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +302 -26
- package/src/standalone/session-service-agent-tree.test.mjs +984 -0
- package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
- package/src/standalone/session-service.mjs +711 -11
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +187 -45
- package/src/tui/lib/voice-setup.mjs +4 -1
- package/src/tui/session/agent-message-source.test.mjs +37 -0
- package/src/tui/session/completion-card-restore.test.mjs +50 -0
- package/src/tui/session/context-state.mjs +3 -13
- package/src/tui/session/goal-continuation.mjs +141 -0
- package/src/tui/session/goal-continuation.test.mjs +203 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +190 -0
- package/src/tui/session/session-api-ext.mjs +48 -8
- package/src/tui/session/session-api.mjs +73 -1
- package/src/tui/session/session-flow.mjs +31 -1
- package/src/tui/session/turn.mjs +88 -15
- package/src/tui/session-local.mjs +39 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
package/src/tui/app/app-view.jsx
CHANGED
|
@@ -301,6 +301,7 @@ export function renderAppView(ctx) {
|
|
|
301
301
|
indexMode={picker.indexMode}
|
|
302
302
|
visibleCount={pickerVisibleRows}
|
|
303
303
|
fillHeight={expandedOptionPanel}
|
|
304
|
+
loading={picker.loading === true}
|
|
304
305
|
themeEpoch={state.themeEpoch || 0}
|
|
305
306
|
confirmBar={picker.confirmBar}
|
|
306
307
|
/>
|
|
@@ -39,6 +39,7 @@ export function createCoreMemoryPicker({
|
|
|
39
39
|
// Loading state lives in the header description row, not as a fake
|
|
40
40
|
// selectable menu item.
|
|
41
41
|
description: 'Loading memories…',
|
|
42
|
+
loading: true,
|
|
42
43
|
items: [],
|
|
43
44
|
onSelect: () => {},
|
|
44
45
|
onCancel: closeMemoryCorePicker,
|
|
@@ -113,6 +114,7 @@ export function createCoreMemoryPicker({
|
|
|
113
114
|
paintPanel({
|
|
114
115
|
title: 'Memory · List',
|
|
115
116
|
description: 'Loading memories…',
|
|
117
|
+
loading: true,
|
|
116
118
|
items: [],
|
|
117
119
|
onSelect: () => {},
|
|
118
120
|
onCancel: () => openMemoryCorePicker(),
|
|
@@ -158,7 +158,7 @@ export function createMaintenancePickers({
|
|
|
158
158
|
// binds it to the previous owner's epoch, which the open transition (a
|
|
159
159
|
// panel identity change) supersedes — leaving "Latest version" stuck on
|
|
160
160
|
// "checking…". Esc after the paint still closes the panel for good.
|
|
161
|
-
|
|
161
|
+
return Promise.resolve(render({ checking: true }))
|
|
162
162
|
.catch((e) => {
|
|
163
163
|
store.pushNotice(`update panel failed: ${e?.message || e}`, 'error');
|
|
164
164
|
return false;
|
|
@@ -308,8 +308,8 @@ export function createMaintenancePickers({
|
|
|
308
308
|
setSettingsPrompt(null);
|
|
309
309
|
own.context(null);
|
|
310
310
|
closeUsagePanel();
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
return Promise.resolve(options.advanced === true ? renderAdvanced() : render())
|
|
312
|
+
.catch((e) => store.pushNotice(`auto-clear panel failed: ${e?.message || e}`, 'error'));
|
|
313
313
|
};
|
|
314
314
|
|
|
315
315
|
const openProfilePicker = async (options = {}) => {
|
|
@@ -166,6 +166,7 @@ export const providerDisplayName = (provider) => {
|
|
|
166
166
|
if (key === 'xai' || key === 'xai-api') return 'xAI API';
|
|
167
167
|
if (key === 'deepseek' || key === 'deepseek-api') return 'DeepSeek API';
|
|
168
168
|
if (key === 'opencode-go') return 'OpenCode Go API';
|
|
169
|
+
if (key === 'openrouter') return 'OpenRouter';
|
|
169
170
|
if (key === 'ollama') return 'Ollama';
|
|
170
171
|
if (key === 'lmstudio') return 'LM Studio';
|
|
171
172
|
if (key === 'default') return 'Default';
|
|
@@ -182,6 +183,7 @@ export const providerDisplayRank = (provider) => {
|
|
|
182
183
|
'antigravity-oauth': 31,
|
|
183
184
|
'cursor-oauth': 32,
|
|
184
185
|
'opencode-go': 35,
|
|
186
|
+
openrouter: 36,
|
|
185
187
|
openai: 40,
|
|
186
188
|
'openai-api': 40,
|
|
187
189
|
anthropic: 50,
|
|
@@ -59,6 +59,9 @@ export function createModelPicker({
|
|
|
59
59
|
const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
|
|
60
60
|
const returnLabel = String(options.returnLabel || 'Agents');
|
|
61
61
|
const returnOnNestedCancel = options.returnOnNestedCancel === true;
|
|
62
|
+
const handoffPanel = options.handoffPanel && typeof options.handoffPanel === 'object'
|
|
63
|
+
? options.handoffPanel
|
|
64
|
+
: null;
|
|
62
65
|
const cancelModelPicker = () => {
|
|
63
66
|
modelPickerClosed = true;
|
|
64
67
|
if (returnTo) returnTo();
|
|
@@ -77,6 +80,7 @@ export function createModelPicker({
|
|
|
77
80
|
title: options.title || 'Model',
|
|
78
81
|
description: options.loadingDescription || 'Loading models...',
|
|
79
82
|
help: returnTo ? `↑/↓ Select · Enter Open · Esc ${returnLabel}` : '↑/↓ Select · Enter Open · Esc Back',
|
|
83
|
+
loading: true,
|
|
80
84
|
items: [],
|
|
81
85
|
onCancel: cancelModelPicker,
|
|
82
86
|
});
|
|
@@ -382,7 +386,7 @@ export function createModelPicker({
|
|
|
382
386
|
if (typeof options.onImmediateSelect === 'function') {
|
|
383
387
|
options.onImmediateSelect(routeInput, selected, effort);
|
|
384
388
|
} else {
|
|
385
|
-
own.paint(
|
|
389
|
+
own.paint(handoffPanel);
|
|
386
390
|
}
|
|
387
391
|
// Post-ack delegation (return to the caller's panel), bound to the
|
|
388
392
|
// claim AFTER this keypress's own navigation: an Esc between the
|
|
@@ -397,10 +401,13 @@ export function createModelPicker({
|
|
|
397
401
|
afterSelect();
|
|
398
402
|
return result;
|
|
399
403
|
})
|
|
400
|
-
.catch((e) =>
|
|
404
|
+
.catch((e) => {
|
|
405
|
+
store.pushNotice(`Couldn’t save model: ${e?.message || e}`, 'error');
|
|
406
|
+
if (handoffPanel) afterSelect();
|
|
407
|
+
});
|
|
401
408
|
return;
|
|
402
409
|
}
|
|
403
|
-
own.paint(
|
|
410
|
+
own.paint(handoffPanel);
|
|
404
411
|
const afterSelect = own.defer(() => {
|
|
405
412
|
if (typeof options.onAfterSelect === 'function') options.onAfterSelect();
|
|
406
413
|
});
|
|
@@ -415,7 +422,10 @@ export function createModelPicker({
|
|
|
415
422
|
);
|
|
416
423
|
if (ok) afterSelect();
|
|
417
424
|
})
|
|
418
|
-
.catch((e) =>
|
|
425
|
+
.catch((e) => {
|
|
426
|
+
store.pushNotice(`Couldn’t switch model: ${e?.message || e}`, 'error');
|
|
427
|
+
if (handoffPanel) afterSelect();
|
|
428
|
+
});
|
|
419
429
|
};
|
|
420
430
|
const renderProviderModels = () => {
|
|
421
431
|
const providerModelItems = buildProviderModelItems(models, provider, activeRoute);
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { createModelPicker } from './model-picker.mjs';
|
|
5
|
+
import { shouldSupersedePanelEpoch, supersedePanelEpoch } from './panel-epoch.mjs';
|
|
6
|
+
import { createPanelSurface } from './panel-surface.mjs';
|
|
7
|
+
import { createRoutePickers } from './route-pickers.mjs';
|
|
8
|
+
import { createSettingsPicker } from './settings-picker.mjs';
|
|
9
|
+
import { createSlashDispatch } from './slash-dispatch.mjs';
|
|
10
|
+
import { createThemeEffortPickers } from './theme-effort-pickers.mjs';
|
|
11
|
+
|
|
12
|
+
function deferred() {
|
|
13
|
+
let resolve;
|
|
14
|
+
let reject;
|
|
15
|
+
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
16
|
+
return { promise, resolve, reject };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const flush = async () => {
|
|
20
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
21
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function createPanelHost() {
|
|
25
|
+
let current = null;
|
|
26
|
+
const painted = [];
|
|
27
|
+
const setPicker = (next) => {
|
|
28
|
+
const previous = current;
|
|
29
|
+
current = typeof next === 'function' ? next(previous) : next;
|
|
30
|
+
if (shouldSupersedePanelEpoch(previous, current)) supersedePanelEpoch();
|
|
31
|
+
painted.push(current);
|
|
32
|
+
};
|
|
33
|
+
const surface = createPanelSurface({
|
|
34
|
+
setPicker,
|
|
35
|
+
setContextPanel: () => {},
|
|
36
|
+
setUsagePanel: () => {},
|
|
37
|
+
});
|
|
38
|
+
return { current: () => current, painted, setPicker, surface };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const HANDOFF = {
|
|
42
|
+
title: 'Settings',
|
|
43
|
+
description: 'Applying selection...',
|
|
44
|
+
help: 'Esc Close',
|
|
45
|
+
indexMode: 'never',
|
|
46
|
+
pickerKey: 'settings-handoff',
|
|
47
|
+
loading: true,
|
|
48
|
+
items: [],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function settingsDeps(host, store, opened = {}) {
|
|
52
|
+
const noop = () => {};
|
|
53
|
+
return {
|
|
54
|
+
store: { pushNotice: noop, ...store },
|
|
55
|
+
state: { model: 'gpt-5', provider: 'openai', workflow: { id: 'default' } },
|
|
56
|
+
surface: host.surface,
|
|
57
|
+
setProviderPrompt: noop,
|
|
58
|
+
setSettingsPrompt: noop,
|
|
59
|
+
settingsHeavyCacheRef: { current: null },
|
|
60
|
+
settingsRequestRef: { current: 0 },
|
|
61
|
+
formatDuration: (ms) => `${ms}ms`,
|
|
62
|
+
displayModelName: () => 'GPT-5',
|
|
63
|
+
routeModelLabel: () => '(unset)',
|
|
64
|
+
workflowDisplayName: () => 'Default',
|
|
65
|
+
workflowSwitchNotice: () => 'workflow switched',
|
|
66
|
+
themeNotice: () => 'theme set',
|
|
67
|
+
openModelPicker: (options) => { opened.model = options; },
|
|
68
|
+
openWebSearchPicker: noop,
|
|
69
|
+
openAgentsPicker: noop,
|
|
70
|
+
openWorkflowPicker: (options) => { opened.workflow = options; },
|
|
71
|
+
openOutputStylePicker: (options) => { opened.outputStyle = options; },
|
|
72
|
+
openProviderSetupPicker: noop,
|
|
73
|
+
openThemePicker: (options) => { opened.theme = options; },
|
|
74
|
+
openAutoClearPicker: noop,
|
|
75
|
+
openProfilePicker: noop,
|
|
76
|
+
openMcpPicker: noop,
|
|
77
|
+
openPluginsPicker: noop,
|
|
78
|
+
openHooksPicker: noop,
|
|
79
|
+
openSkillsPicker: noop,
|
|
80
|
+
openMemoryCorePicker: noop,
|
|
81
|
+
openUpdatePicker: noop,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
test('Settings paints its loading destination before the daemon snapshot settles', async () => {
|
|
86
|
+
supersedePanelEpoch();
|
|
87
|
+
const host = createPanelHost();
|
|
88
|
+
host.setPicker({ title: 'Theme' });
|
|
89
|
+
const snapshotGate = deferred();
|
|
90
|
+
const opened = {};
|
|
91
|
+
const { openSettingsPicker } = createSettingsPicker(settingsDeps(
|
|
92
|
+
host,
|
|
93
|
+
{ getSettingsSnapshot: () => snapshotGate.promise },
|
|
94
|
+
opened,
|
|
95
|
+
));
|
|
96
|
+
|
|
97
|
+
const opening = openSettingsPicker();
|
|
98
|
+
assert.equal(host.current()?.title, 'Settings');
|
|
99
|
+
assert.match(host.current()?.description || '', /Loading settings/i);
|
|
100
|
+
assert.notEqual(host.current(), null);
|
|
101
|
+
|
|
102
|
+
snapshotGate.resolve({});
|
|
103
|
+
await opening;
|
|
104
|
+
const settings = host.current();
|
|
105
|
+
assert.equal(settings?.title, 'Settings');
|
|
106
|
+
assert.ok(settings.items.length > 0);
|
|
107
|
+
|
|
108
|
+
for (const action of ['model', 'workflow', 'output-style', 'theme']) {
|
|
109
|
+
const item = settings.items.find((entry) => entry._action === action);
|
|
110
|
+
settings.onSelect(item.value, item);
|
|
111
|
+
}
|
|
112
|
+
for (const options of [opened.model, opened.workflow, opened.outputStyle, opened.theme]) {
|
|
113
|
+
assert.equal(options?.handoffPanel?.title, 'Settings');
|
|
114
|
+
assert.equal(options?.handoffPanel?.pickerKey, 'settings-handoff');
|
|
115
|
+
assert.equal(options?.handoffPanel?.loading, true);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('Model save keeps a Settings handoff panel visible until the write settles', async () => {
|
|
120
|
+
supersedePanelEpoch();
|
|
121
|
+
const host = createPanelHost();
|
|
122
|
+
const saveGate = deferred();
|
|
123
|
+
const { openModelPicker } = createModelPicker({
|
|
124
|
+
store: {
|
|
125
|
+
pushNotice: () => {},
|
|
126
|
+
listProviderModels: async () => [],
|
|
127
|
+
setRoute: () => saveGate.promise,
|
|
128
|
+
},
|
|
129
|
+
getState: () => ({ provider: 'openai', model: 'gpt-5', effort: null, fast: false }),
|
|
130
|
+
surface: host.surface,
|
|
131
|
+
setProviderPrompt: () => {},
|
|
132
|
+
setSettingsPrompt: () => {},
|
|
133
|
+
providerModelsCacheRef: {
|
|
134
|
+
current: {
|
|
135
|
+
at: Date.now(),
|
|
136
|
+
models: [{ provider: 'openai', id: 'gpt-5', display: 'GPT-5' }],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
webSearchModelsCacheRef: { current: { models: [], at: 0 } },
|
|
140
|
+
modelPickerRequestRef: { current: 0 },
|
|
141
|
+
clearModelCaches: () => {},
|
|
142
|
+
modelSwitchNotice: () => 'switched',
|
|
143
|
+
openProviderSetupPicker: () => {},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await openModelPicker({
|
|
147
|
+
handoffPanel: HANDOFF,
|
|
148
|
+
onAfterSelect: () => host.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
|
|
149
|
+
});
|
|
150
|
+
const providers = host.current();
|
|
151
|
+
providers.onSelect(providers.items[0].value, providers.items[0]);
|
|
152
|
+
const models = host.current();
|
|
153
|
+
models.onSelect(models.items[0].value, models.items[0]);
|
|
154
|
+
|
|
155
|
+
assert.equal(host.current(), HANDOFF);
|
|
156
|
+
assert.equal(host.painted.at(-1), HANDOFF);
|
|
157
|
+
|
|
158
|
+
saveGate.resolve(true);
|
|
159
|
+
await flush();
|
|
160
|
+
assert.equal(host.current()?.title, 'Settings');
|
|
161
|
+
assert.notEqual(host.current(), HANDOFF);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
function routePicker(host, store) {
|
|
165
|
+
return createRoutePickers({
|
|
166
|
+
store: { pushNotice: () => {}, ...store },
|
|
167
|
+
state: {},
|
|
168
|
+
surface: host.surface,
|
|
169
|
+
setProviderPrompt: () => {},
|
|
170
|
+
setSettingsPrompt: () => {},
|
|
171
|
+
closeUsagePanel: () => {},
|
|
172
|
+
clean: (value) => value,
|
|
173
|
+
routeLabel: () => 'route',
|
|
174
|
+
agentModelParts: () => [],
|
|
175
|
+
agentModelProfile: () => 'profile',
|
|
176
|
+
workflowSwitchNotice: () => 'workflow switched',
|
|
177
|
+
openModelPicker: () => {},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
test('Workflow and output-style saves keep the Settings handoff visible until their writes settle', async () => {
|
|
182
|
+
supersedePanelEpoch();
|
|
183
|
+
const workflowHost = createPanelHost();
|
|
184
|
+
const workflowGate = deferred();
|
|
185
|
+
const workflow = routePicker(workflowHost, {
|
|
186
|
+
listWorkflows: async () => [{ id: 'solo', name: 'Solo', active: true }],
|
|
187
|
+
setWorkflow: () => workflowGate.promise,
|
|
188
|
+
});
|
|
189
|
+
await workflow.openWorkflowPicker({
|
|
190
|
+
returnTo: () => workflowHost.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
|
|
191
|
+
handoffPanel: HANDOFF,
|
|
192
|
+
});
|
|
193
|
+
const workflowPanel = workflowHost.current();
|
|
194
|
+
workflowPanel.onSelect(workflowPanel.items[0].value, workflowPanel.items[0]);
|
|
195
|
+
assert.equal(workflowHost.current(), HANDOFF);
|
|
196
|
+
|
|
197
|
+
workflowGate.resolve({ id: 'solo' });
|
|
198
|
+
await flush();
|
|
199
|
+
assert.equal(workflowHost.current()?.title, 'Settings');
|
|
200
|
+
|
|
201
|
+
const styleHost = createPanelHost();
|
|
202
|
+
const styleGate = deferred();
|
|
203
|
+
const outputStyle = routePicker(styleHost, {
|
|
204
|
+
listOutputStyles: async () => ({
|
|
205
|
+
current: { id: 'simple' },
|
|
206
|
+
styles: [{ id: 'simple', label: 'Simple' }],
|
|
207
|
+
}),
|
|
208
|
+
setOutputStyle: () => styleGate.promise,
|
|
209
|
+
});
|
|
210
|
+
await outputStyle.openOutputStylePicker({
|
|
211
|
+
returnTo: () => styleHost.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
|
|
212
|
+
handoffPanel: HANDOFF,
|
|
213
|
+
});
|
|
214
|
+
const stylePanel = styleHost.current();
|
|
215
|
+
stylePanel.onSelect(stylePanel.items[0].value, stylePanel.items[0]);
|
|
216
|
+
assert.equal(styleHost.current(), HANDOFF);
|
|
217
|
+
|
|
218
|
+
styleGate.resolve({ current: { id: 'simple', label: 'Simple' } });
|
|
219
|
+
await flush();
|
|
220
|
+
assert.equal(styleHost.current()?.title, 'Settings');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('Theme selection hands directly to Settings without a null panel', () => {
|
|
224
|
+
supersedePanelEpoch();
|
|
225
|
+
const host = createPanelHost();
|
|
226
|
+
const { openThemePicker } = createThemeEffortPickers({
|
|
227
|
+
state: {},
|
|
228
|
+
store: {
|
|
229
|
+
listThemes: () => [{ id: 'basic', label: 'Basic' }],
|
|
230
|
+
getTheme: () => 'basic',
|
|
231
|
+
setTheme: (id) => ({ id, label: 'Basic' }),
|
|
232
|
+
pushNotice: () => {},
|
|
233
|
+
},
|
|
234
|
+
surface: host.surface,
|
|
235
|
+
setProviderPrompt: () => {},
|
|
236
|
+
setSettingsPrompt: () => {},
|
|
237
|
+
closeUsagePanel: () => {},
|
|
238
|
+
clean: (value) => value,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
openThemePicker({
|
|
242
|
+
returnTo: () => host.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
|
|
243
|
+
handoffPanel: HANDOFF,
|
|
244
|
+
});
|
|
245
|
+
const theme = host.current();
|
|
246
|
+
const paintedBefore = host.painted.length;
|
|
247
|
+
theme.onSelect(theme.items[0].value, theme.items[0]);
|
|
248
|
+
|
|
249
|
+
const transitionPaints = host.painted.slice(paintedBefore);
|
|
250
|
+
assert.deepEqual(transitionPaints.map((panel) => panel?.title), ['Settings', 'Settings']);
|
|
251
|
+
assert.ok(transitionPaints.every(Boolean));
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
function workflowSlashDispatch(host, openWorkflowPicker, notices = []) {
|
|
255
|
+
return createSlashDispatch({
|
|
256
|
+
state: { busy: false, commandBusy: false },
|
|
257
|
+
store: { pushNotice: (message, tone) => notices.push([message, tone]) },
|
|
258
|
+
normalizeSlashCommandName: (value) => value,
|
|
259
|
+
surface: host.surface,
|
|
260
|
+
closeUsagePanel: () => {},
|
|
261
|
+
openWorkflowPicker,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
test('slash option entry keeps a loading panel until the async picker paints', async () => {
|
|
266
|
+
supersedePanelEpoch();
|
|
267
|
+
const host = createPanelHost();
|
|
268
|
+
const openGate = deferred();
|
|
269
|
+
const { runSlashCommand } = workflowSlashDispatch(host, () => {
|
|
270
|
+
const own = host.surface.claim();
|
|
271
|
+
return openGate.promise.then(() => own.paint({
|
|
272
|
+
title: 'Workflow',
|
|
273
|
+
description: 'Select active workflow.',
|
|
274
|
+
items: [{ value: 'solo', label: 'Solo' }],
|
|
275
|
+
}));
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
assert.equal(runSlashCommand('workflow'), true);
|
|
279
|
+
assert.equal(host.current()?._kind, 'slash-loading:workflow');
|
|
280
|
+
assert.equal(host.current()?.title, 'Workflow');
|
|
281
|
+
assert.equal(host.current()?.loading, true);
|
|
282
|
+
|
|
283
|
+
openGate.resolve();
|
|
284
|
+
await flush();
|
|
285
|
+
assert.equal(host.current()?.title, 'Workflow');
|
|
286
|
+
assert.equal(host.current()?._kind, undefined);
|
|
287
|
+
assert.ok(host.current()?.items.length > 0);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('Esc on slash loading prevents the pending picker from appearing later', async () => {
|
|
291
|
+
supersedePanelEpoch();
|
|
292
|
+
const host = createPanelHost();
|
|
293
|
+
const openGate = deferred();
|
|
294
|
+
const { runSlashCommand } = workflowSlashDispatch(host, () => {
|
|
295
|
+
const own = host.surface.claim();
|
|
296
|
+
return openGate.promise.then(() => own.paint({
|
|
297
|
+
title: 'Workflow',
|
|
298
|
+
items: [{ value: 'solo', label: 'Solo' }],
|
|
299
|
+
}));
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
runSlashCommand('workflow');
|
|
303
|
+
const loading = host.current();
|
|
304
|
+
assert.equal(loading?._kind, 'slash-loading:workflow');
|
|
305
|
+
loading.onCancel();
|
|
306
|
+
assert.equal(host.current(), null);
|
|
307
|
+
|
|
308
|
+
openGate.resolve();
|
|
309
|
+
await flush();
|
|
310
|
+
assert.equal(host.current(), null);
|
|
311
|
+
});
|
|
@@ -134,6 +134,7 @@ export function createProjectPicker({
|
|
|
134
134
|
description: 'Opening folder picker… choose a folder in the dialog window.',
|
|
135
135
|
help: 'Waiting for the system folder dialog…',
|
|
136
136
|
indexMode: 'never',
|
|
137
|
+
loading: true,
|
|
137
138
|
items: [],
|
|
138
139
|
onSelect: () => {},
|
|
139
140
|
onCancel: () => {},
|
|
@@ -188,6 +188,9 @@ export function createRoutePickers({
|
|
|
188
188
|
|
|
189
189
|
const openWorkflowPicker = async (options = {}) => {
|
|
190
190
|
const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
|
|
191
|
+
const handoffPanel = options.handoffPanel && typeof options.handoffPanel === 'object'
|
|
192
|
+
? options.handoffPanel
|
|
193
|
+
: null;
|
|
191
194
|
const own = surface.claim();
|
|
192
195
|
let workflows = [];
|
|
193
196
|
try {
|
|
@@ -225,21 +228,26 @@ export function createRoutePickers({
|
|
|
225
228
|
// Clear-and-continue: the flow keeps the surface it just emptied, so
|
|
226
229
|
// the post-ack hop below is bound to THIS keypress. An Esc before the
|
|
227
230
|
// switch acks cancels the hop instead of re-opening Settings.
|
|
228
|
-
own.paint(
|
|
231
|
+
own.paint(handoffPanel);
|
|
229
232
|
const returnAfterSwitch = own.defer(() => { if (returnTo) returnTo(); });
|
|
230
233
|
void store.setWorkflow?.(workflow.id)
|
|
231
234
|
.then((result) => {
|
|
232
235
|
if (!result) {
|
|
233
236
|
store.pushNotice('Workflow switch is already running.', 'warn');
|
|
237
|
+
if (handoffPanel) returnAfterSwitch();
|
|
234
238
|
return;
|
|
235
239
|
}
|
|
236
240
|
store.pushNotice(workflowSwitchNotice(result), 'info');
|
|
237
241
|
returnAfterSwitch();
|
|
238
242
|
})
|
|
239
|
-
.catch((e) =>
|
|
243
|
+
.catch((e) => {
|
|
244
|
+
store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error');
|
|
245
|
+
if (handoffPanel) returnAfterSwitch();
|
|
246
|
+
});
|
|
240
247
|
},
|
|
241
248
|
onCancel: () => {
|
|
242
|
-
own.
|
|
249
|
+
if (handoffPanel) own.paint(handoffPanel);
|
|
250
|
+
else own.close();
|
|
243
251
|
if (returnTo) returnTo();
|
|
244
252
|
},
|
|
245
253
|
});
|
|
@@ -247,6 +255,9 @@ export function createRoutePickers({
|
|
|
247
255
|
|
|
248
256
|
const openOutputStylePicker = async (options = {}) => {
|
|
249
257
|
const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
|
|
258
|
+
const handoffPanel = options.handoffPanel && typeof options.handoffPanel === 'object'
|
|
259
|
+
? options.handoffPanel
|
|
260
|
+
: null;
|
|
250
261
|
// Onboarding mode: Enter (row select) and ConfirmBar Next must both persist
|
|
251
262
|
// the chosen style, then advance. `onboarding.onAdvance/onBack` drive the
|
|
252
263
|
// wizard; the confirm bar is built here so both paths share `saveStyle`.
|
|
@@ -284,7 +295,7 @@ export function createRoutePickers({
|
|
|
284
295
|
// Onboarding advance: keep the current picker visible during the async
|
|
285
296
|
// style switch so the screen never flashes empty between steps; the next
|
|
286
297
|
// step (or finishOnboarding) replaces/clears the picker itself.
|
|
287
|
-
if (!(advance && onboarding)) own.paint(
|
|
298
|
+
if (!(advance && onboarding)) own.paint(handoffPanel);
|
|
288
299
|
// Post-ack delegation (next onboarding step, or back to the caller):
|
|
289
300
|
// bound after this keypress's own navigation so an Esc while the switch
|
|
290
301
|
// is in flight cannot paint a panel over the new surface.
|
|
@@ -301,7 +312,10 @@ export function createRoutePickers({
|
|
|
301
312
|
}
|
|
302
313
|
advanceAfterSave();
|
|
303
314
|
})
|
|
304
|
-
.catch((e) =>
|
|
315
|
+
.catch((e) => {
|
|
316
|
+
store.pushNotice(`Couldn’t switch output style: ${e?.message || e}`, 'error');
|
|
317
|
+
if (handoffPanel) advanceAfterSave();
|
|
318
|
+
});
|
|
305
319
|
};
|
|
306
320
|
own.paint({
|
|
307
321
|
title: 'Output Style',
|
|
@@ -334,7 +348,8 @@ export function createRoutePickers({
|
|
|
334
348
|
saveStyle(style.id, { advance: Boolean(onboarding) });
|
|
335
349
|
},
|
|
336
350
|
onCancel: () => {
|
|
337
|
-
own.
|
|
351
|
+
if (handoffPanel) own.paint(handoffPanel);
|
|
352
|
+
else own.close();
|
|
338
353
|
if (onboarding) onboarding.onCancel?.();
|
|
339
354
|
else if (returnTo) returnTo();
|
|
340
355
|
},
|
|
@@ -41,6 +41,15 @@ export function createSettingsPicker({
|
|
|
41
41
|
openMemoryCorePicker,
|
|
42
42
|
openUpdatePicker,
|
|
43
43
|
}) {
|
|
44
|
+
const settingsHandoffPanel = (description = 'Loading settings...') => ({
|
|
45
|
+
title: 'Settings',
|
|
46
|
+
description,
|
|
47
|
+
help: 'Esc Close',
|
|
48
|
+
indexMode: 'never',
|
|
49
|
+
pickerKey: 'settings-handoff',
|
|
50
|
+
loading: true,
|
|
51
|
+
items: [],
|
|
52
|
+
});
|
|
44
53
|
// Build generation guard. Every open/refresh takes a ticket and Esc bumps it,
|
|
45
54
|
// so a slow snapshot from a superseded (or already closed) build can never
|
|
46
55
|
// re-open Settings. The ref is owned by App so it survives this per-render
|
|
@@ -60,6 +69,10 @@ export function createSettingsPicker({
|
|
|
60
69
|
// caught here instead.
|
|
61
70
|
const own = surface.claim();
|
|
62
71
|
const light = opts.light === true;
|
|
72
|
+
// A full Settings open usually follows another option panel. Paint the
|
|
73
|
+
// destination synchronously before the daemon snapshot so the empty-chat
|
|
74
|
+
// welcome logo cannot appear between the old panel and Settings.
|
|
75
|
+
if (!light && !own.paint(settingsHandoffPanel())) return;
|
|
63
76
|
const overrides = opts.overrides || null;
|
|
64
77
|
const heavyCache = light ? settingsHeavyCacheRef.current : null;
|
|
65
78
|
// ONE round-trip for the whole panel: on a daemon-backed store each getter
|
|
@@ -448,14 +461,24 @@ export function createSettingsPicker({
|
|
|
448
461
|
else if (item._action === 'memory-enabled') toggleMemory();
|
|
449
462
|
else if (item._action === 'memory-cycles') toggleMemoryCycles();
|
|
450
463
|
else if (item._action === 'voice') applyVoice();
|
|
451
|
-
else if (item._action === 'output-style') openOutputStylePicker({
|
|
452
|
-
|
|
453
|
-
|
|
464
|
+
else if (item._action === 'output-style') openOutputStylePicker({
|
|
465
|
+
returnTo: openSettingsPicker,
|
|
466
|
+
handoffPanel: settingsHandoffPanel('Applying output style...'),
|
|
467
|
+
});
|
|
468
|
+
else if (item._action === 'theme') openThemePicker({
|
|
469
|
+
returnTo: openSettingsPicker,
|
|
470
|
+
handoffPanel: settingsHandoffPanel('Applying theme...'),
|
|
471
|
+
});
|
|
472
|
+
else if (item._action === 'workflow') openWorkflowPicker({
|
|
473
|
+
returnTo: openSettingsPicker,
|
|
474
|
+
handoffPanel: settingsHandoffPanel('Switching workflow...'),
|
|
475
|
+
});
|
|
454
476
|
else if (item._action === 'model') openModelPicker({
|
|
455
477
|
returnTo: openSettingsPicker,
|
|
456
478
|
returnLabel: 'Settings',
|
|
457
479
|
returnOnNestedCancel: true,
|
|
458
480
|
onAfterSelect: openSettingsPicker,
|
|
481
|
+
handoffPanel: settingsHandoffPanel('Switching model...'),
|
|
459
482
|
});
|
|
460
483
|
else if (item._action === 'websearch') openWebSearchPicker({
|
|
461
484
|
returnTo: openSettingsPicker,
|
|
@@ -7,6 +7,7 @@ export const SLASH_COMMANDS = [
|
|
|
7
7
|
{ name: 'clear', usage: '/clear', aliases: ['new'], aliasUsage: ['new'], description: 'Start a fresh chat' },
|
|
8
8
|
{ name: 'project', usage: '/project', aliases: ['projects'], aliasUsage: ['projects'], showAliasUsage: false, params: '[path]', description: 'Switch working directory (project)' },
|
|
9
9
|
{ name: 'compact', usage: '/compact', description: 'Compact older conversation context' },
|
|
10
|
+
{ name: 'goal', usage: '/goal', params: '[objective --time 1h|status|pause|resume|edit|complete|clear]', description: 'Run and manage a durable session Goal' },
|
|
10
11
|
{ name: 'autoclear', usage: '/autoclear', params: '[on|off|duration]', description: 'Reduce cache-miss cost after long idle gaps' },
|
|
11
12
|
{ name: 'resume', usage: '/resume', params: '[id]', description: 'Resume a saved chat' },
|
|
12
13
|
{ name: 'context', usage: '/context', description: 'Show current context surface' },
|