mixdog 0.9.147 → 0.9.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +23 -3
- package/scripts/build-voice-runtime-unix.sh +101 -0
- package/scripts/embedding-quality-probe.mjs +96 -0
- package/scripts/generate-voice-runtime-manifest.mjs +300 -0
- package/scripts/generate-voice-runtime-manifest.test.mjs +135 -0
- package/scripts/native-binary-arch.mjs +30 -13
- package/scripts/native-binary-arch.test.mjs +40 -4
- package/scripts/plan-voice-runtime-release.mjs +73 -0
- package/scripts/prepare-native-assets.mjs +50 -9
- package/scripts/prune-embedding-runtime.mjs +3 -3
- package/scripts/sync-voice-runtime-manifest.mjs +46 -0
- package/scripts/verify-release-assets.mjs +106 -6
- package/scripts/verify-voice-runtime-release.mjs +89 -0
- package/scripts/voice-runtime-config.json +52 -0
- package/src/app.mjs +42 -5
- package/src/headless-exec.mjs +58 -32
- package/src/headless-exec.test.mjs +74 -13
- package/src/rules/lead/01-general.md +2 -0
- package/src/rules/shared/10-tool-workflow.md +7 -4
- package/src/rules/shared/30-exploration.md +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +28 -9
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +6 -1
- package/src/runtime/agent/orchestrator/config.mjs +53 -18
- package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +139 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +34 -5
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -10
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +98 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +67 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +4 -82
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +0 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +62 -121
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +48 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +215 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +44 -48
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +54 -31
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +56 -2
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +34 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +61 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +61 -15
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +120 -1
- package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +0 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +187 -84
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +32 -9
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +78 -33
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +21 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +36 -36
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +16 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +50 -213
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +135 -126
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +20 -11
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +112 -36
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +220 -50
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +112 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +130 -60
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +28 -10
- package/src/runtime/agent/orchestrator/providers/registry.mjs +122 -95
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +69 -3
- package/src/runtime/agent/orchestrator/providers/tool-stream-state.mjs +4 -22
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -14
- package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +27 -48
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +19 -14
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -43
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +43 -5
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +325 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +599 -195
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +22 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +26 -16
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +23 -11
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +118 -3
- package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +332 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +38 -1
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +27 -6
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +291 -128
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -9
- package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +224 -0
- package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +53 -3
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +255 -25
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +87 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +132 -9
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +29 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +180 -31
- package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +32 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +10 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +38 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +31 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +11 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-windows.mjs +22 -4
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +6 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +25 -24
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +152 -133
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +114 -42
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +129 -15
- package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +72 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +7 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +201 -19
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +26 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +10 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +55 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +115 -25
- package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +268 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +296 -46
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +104 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +136 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +127 -38
- package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +661 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +350 -9
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +418 -44
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +27 -3
- package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +552 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +65 -15
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +284 -109
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +51 -21
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +44 -27
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/attachments/store.mjs +11 -3
- package/src/runtime/channels/data/voice-runtime-manifest.json +46 -70
- package/src/runtime/channels/lib/event-queue.mjs +51 -41
- package/src/runtime/channels/lib/scheduler.mjs +23 -28
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -17
- package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +17 -0
- package/src/runtime/channels/lib/webhook.mjs +39 -20
- package/src/runtime/channels/lib/whisper-server.mjs +46 -1
- package/src/runtime/channels/lib/worker-main.mjs +3 -1
- package/src/runtime/media/adapters/gemini-video.mjs +20 -4
- package/src/runtime/media/jobs.mjs +13 -0
- package/src/runtime/media/renditions.mjs +7 -1
- package/src/runtime/media/store.mjs +42 -22
- package/src/runtime/memory/index.mjs +4 -9
- package/src/runtime/memory/lib/agent-ipc.mjs +1 -6
- package/src/runtime/memory/lib/archive-security.test.mjs +21 -1
- package/src/runtime/memory/lib/core-memory-store.mjs +28 -50
- package/src/runtime/memory/lib/embedding-model-config.mjs +37 -1
- package/src/runtime/memory/lib/embedding-model-config.test.mjs +33 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +34 -12
- package/src/runtime/memory/lib/embedding-worker.mjs +37 -5
- package/src/runtime/memory/lib/ko-morph.mjs +46 -15
- package/src/runtime/memory/lib/memory-action-handlers.mjs +18 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -18
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -18
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +98 -45
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +71 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +78 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +37 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +50 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +9 -24
- package/src/runtime/memory/lib/memory.mjs +50 -12
- package/src/runtime/memory/lib/pg/process.mjs +1 -10
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -9
- package/src/runtime/memory/lib/query-handlers.mjs +2 -4
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +2 -2
- package/src/runtime/shared/background-tasks.mjs +15 -0
- package/src/runtime/shared/background-tasks.test.mjs +78 -0
- package/src/runtime/shared/child-guardian.mjs +2 -10
- package/src/runtime/shared/pid-liveness.mjs +27 -0
- package/src/runtime/shared/pristine-execution-contract.json +0 -1
- package/src/runtime/shared/runtime-root.mjs +1 -11
- package/src/runtime/shared/service-discovery.mjs +1 -17
- package/src/runtime/shared/singleton-owner.mjs +1 -11
- package/src/runtime/shared/staged-update.mjs +2 -12
- package/src/runtime/shared/tool-status.mjs +5 -1
- package/src/runtime/shared/tool-surface.mjs +15 -7
- package/src/runtime/shared/turn-snapshot-store.mjs +168 -0
- package/src/runtime/shared/turn-snapshot.mjs +137 -13
- package/src/runtime/shared/turn-worktree-snapshot.mjs +110 -18
- package/src/runtime/shared/webhook-session-run.mjs +16 -2
- package/src/runtime/web-search/lib/http-fetch.mjs +34 -4
- package/src/runtime/web-search/lib/state.mjs +15 -15
- package/src/runtime/web-search/lib/web-tools.mjs +46 -5
- package/src/session-runtime/context-status.mjs +4 -12
- package/src/session-runtime/runtime-core.mjs +20 -1
- package/src/session-runtime/session-lifecycle.mjs +9 -0
- package/src/standalone/agent-dispatch-broker.mjs +33 -2
- package/src/standalone/agent-tool/job-task-reconcile.mjs +78 -0
- package/src/standalone/agent-tool/notify.mjs +3 -1
- package/src/standalone/agent-tool/spawn-flow.mjs +52 -70
- package/src/standalone/agent-tool/tag-registry.mjs +116 -33
- package/src/standalone/agent-tool/tool-def.mjs +2 -2
- package/src/standalone/agent-tool/worker-index.mjs +8 -11
- package/src/standalone/agent-tool/worker-rows.mjs +4 -1
- package/src/standalone/agent-tool.mjs +20 -55
- package/src/standalone/agent-watchdog-registry.mjs +28 -4
- package/src/standalone/channel-client.mjs +47 -15
- package/src/standalone/channel-transport.mjs +44 -25
- package/src/standalone/daemon.mjs +31 -2
- package/src/standalone/hook-bus/config.mjs +25 -9
- package/src/standalone/hook-bus/handlers.mjs +16 -52
- package/src/standalone/hook-bus.mjs +29 -23
- package/src/standalone/memory-runtime-proxy.mjs +1 -16
- package/src/standalone/session-client.mjs +86 -23
- package/src/standalone/session-protocol.mjs +0 -2
- package/src/standalone/session-runtime-host-health.test.mjs +5 -4
- package/src/standalone/session-runtime-host.mjs +101 -6
- package/src/standalone/session-runtime-record.mjs +1 -5
- package/src/standalone/session-runtime-worker.mjs +52 -57
- package/src/standalone/session-service.mjs +4 -1
- package/src/standalone/session-transport.mjs +56 -25
- package/src/tui/App.jsx +40 -49
- package/src/tui/app/app-view.jsx +14 -21
- package/src/tui/app/core-memory-picker.mjs +29 -16
- package/src/tui/app/create-app-pickers.mjs +15 -36
- package/src/tui/app/extension-pickers.mjs +60 -49
- package/src/tui/app/maintenance-pickers.mjs +100 -57
- package/src/tui/app/message-selector.mjs +6 -5
- package/src/tui/app/model-picker.mjs +30 -13
- package/src/tui/app/onboarding-steps.mjs +21 -12
- package/src/tui/app/panel-epoch.mjs +56 -0
- package/src/tui/app/panel-epoch.test.mjs +1009 -0
- package/src/tui/app/panel-surface.mjs +98 -0
- package/src/tui/app/project-picker.mjs +30 -29
- package/src/tui/app/prompt-submit.mjs +192 -97
- package/src/tui/app/prompt-submit.test.mjs +206 -0
- package/src/tui/app/provider-setup-picker.mjs +74 -43
- package/src/tui/app/resume-picker.mjs +11 -8
- package/src/tui/app/route-pickers.mjs +45 -28
- package/src/tui/app/settings-picker.mjs +69 -18
- package/src/tui/app/shell-layout.mjs +1 -3
- package/src/tui/app/slash-commands.mjs +0 -2
- package/src/tui/app/slash-dispatch.mjs +4 -2
- package/src/tui/app/text-entry-policy.mjs +25 -0
- package/src/tui/app/theme-effort-pickers.mjs +11 -16
- package/src/tui/app/usage-context-panels.mjs +21 -25
- package/src/tui/app/use-global-key-input.mjs +3 -2
- package/src/tui/app/use-prompt-draft-flow.mjs +6 -18
- package/src/tui/app/use-prompt-handlers.mjs +3 -2
- package/src/tui/app/use-welcome-prompt-hint.mjs +17 -3
- package/src/tui/components/Picker.jsx +1 -1
- package/src/tui/components/TextEntryPanel.jsx +7 -2
- package/src/tui/dist/index.mjs +652 -561
- package/src/tui/lib/voice-setup.mjs +1 -2
- package/src/tui/session/agent-job-feed.mjs +73 -11
- package/src/tui/session/session-api-ext.mjs +5 -6
- package/src/tui/session/session-api.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +15 -5
- package/src/tui/session/tui-steering-persist.mjs +33 -19
- package/src/tui/session/tui-steering-persist.test.mjs +104 -0
- package/src/tui/session-local.mjs +0 -22
- package/scripts/.tmp-coverage-report.mjs +0 -96
- package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.mjs +0 -57
- package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.test.mjs +0 -57
|
@@ -2,25 +2,33 @@
|
|
|
2
2
|
* maintenance-pickers.mjs — Update / Auto-clear / Profile picker cluster.
|
|
3
3
|
*
|
|
4
4
|
* Extracted from App.jsx behavior-preservingly as a dependency-injection
|
|
5
|
-
* factory. These openers drive
|
|
6
|
-
* store state, so they can't be pure. Every function body is the original
|
|
7
|
-
* logic verbatim, with closure identifiers threaded through the factory
|
|
5
|
+
* factory. These openers drive the panel surface + setSettingsPrompt and read
|
|
6
|
+
* live store state, so they can't be pure. Every function body is the original
|
|
7
|
+
* App logic verbatim, with closure identifiers threaded through the factory
|
|
8
8
|
* argument.
|
|
9
9
|
*/
|
|
10
10
|
export function createMaintenancePickers({
|
|
11
11
|
store,
|
|
12
12
|
theme,
|
|
13
13
|
formatDuration,
|
|
14
|
-
|
|
14
|
+
surface,
|
|
15
15
|
setProviderPrompt,
|
|
16
|
-
setChannelPrompt,
|
|
17
|
-
setHookPrompt,
|
|
18
16
|
setSettingsPrompt,
|
|
19
|
-
setContextPanel,
|
|
20
17
|
closeUsagePanel,
|
|
21
18
|
}) {
|
|
22
19
|
const openUpdatePicker = (options = {}) => {
|
|
23
20
|
const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
|
|
21
|
+
// Surface claim for this panel, taken at the user's open action — BEFORE
|
|
22
|
+
// the first daemon read. Every paint through it re-validates and re-arms,
|
|
23
|
+
// so a re-check or install settling after Esc cannot paint over whatever
|
|
24
|
+
// the user is looking at now.
|
|
25
|
+
const own = surface.claim();
|
|
26
|
+
const paintPanel = (panel) => {
|
|
27
|
+
if (!own.owns()) return false;
|
|
28
|
+
setProviderPrompt(null);
|
|
29
|
+
setSettingsPrompt(null);
|
|
30
|
+
return own.paint(panel);
|
|
31
|
+
};
|
|
24
32
|
// Async: both reads are remote calls on a daemon-backed store, so the sync
|
|
25
33
|
// versions rendered every row from an unresolved promise.
|
|
26
34
|
const readSettings = async () => {
|
|
@@ -64,11 +72,7 @@ export function createMaintenancePickers({
|
|
|
64
72
|
_action: 'auto-update',
|
|
65
73
|
},
|
|
66
74
|
];
|
|
67
|
-
|
|
68
|
-
setChannelPrompt(null);
|
|
69
|
-
setHookPrompt(null);
|
|
70
|
-
setSettingsPrompt(null);
|
|
71
|
-
setPicker({
|
|
75
|
+
return paintPanel({
|
|
72
76
|
title: 'Update',
|
|
73
77
|
description: 'Check version and update mixdog.',
|
|
74
78
|
help: '↑/↓ Select · Enter Open/Toggle · Esc Close',
|
|
@@ -101,31 +105,40 @@ export function createMaintenancePickers({
|
|
|
101
105
|
}
|
|
102
106
|
},
|
|
103
107
|
onCancel: () => {
|
|
104
|
-
|
|
108
|
+
own.close();
|
|
105
109
|
if (returnTo) returnTo();
|
|
106
110
|
},
|
|
107
111
|
});
|
|
108
112
|
};
|
|
113
|
+
// Every render() is an async daemon read; a detached call would surface a
|
|
114
|
+
// failed read as an unhandled rejection (fatal for the TUI process).
|
|
115
|
+
const rerender = (opts = {}) => {
|
|
116
|
+
void Promise.resolve(render(opts))
|
|
117
|
+
.catch((e) => store.pushNotice(`update panel failed: ${e?.message || e}`, 'error'));
|
|
118
|
+
};
|
|
119
|
+
// Deferred repaint bound to the claim AT ACTION TIME: a check/install that
|
|
120
|
+
// settles after Esc must not re-open the Update panel.
|
|
121
|
+
const deferredRerender = (opts = {}) => own.defer(() => rerender(opts));
|
|
109
122
|
const toggleAutoUpdate = (enabled) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
store.pushNotice(`Auto-update ${enabled ? 'on' : 'off'}`, 'info')
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
render();
|
|
123
|
+
// Persisted by the daemon: only claim the new value once it is written.
|
|
124
|
+
void Promise.resolve(store.setAutoUpdate?.(enabled))
|
|
125
|
+
.then(() => store.pushNotice(`Auto-update ${enabled ? 'on' : 'off'}`, 'info'))
|
|
126
|
+
.catch((e) => store.pushNotice(`auto-update failed: ${e?.message || e}`, 'error'))
|
|
127
|
+
.finally(deferredRerender());
|
|
117
128
|
};
|
|
118
129
|
const recheck = () => {
|
|
119
|
-
|
|
130
|
+
rerender({ checking: true });
|
|
131
|
+
const settled = deferredRerender();
|
|
120
132
|
void Promise.resolve(store.checkForUpdate?.({ force: true }))
|
|
121
|
-
.then(() =>
|
|
133
|
+
.then(() => settled())
|
|
122
134
|
.catch((e) => {
|
|
123
135
|
store.pushNotice(`update check failed: ${e?.message || e}`, 'error');
|
|
124
|
-
|
|
136
|
+
settled();
|
|
125
137
|
});
|
|
126
138
|
};
|
|
127
139
|
const runUpdate = () => {
|
|
128
140
|
store.pushNotice('Updating…', 'info');
|
|
141
|
+
const settled = deferredRerender();
|
|
129
142
|
void Promise.resolve(store.runUpdateNow?.())
|
|
130
143
|
.then((result) => {
|
|
131
144
|
if (result?.ok) {
|
|
@@ -133,21 +146,42 @@ export function createMaintenancePickers({
|
|
|
133
146
|
} else {
|
|
134
147
|
store.pushNotice(`Update failed: ${result?.error || 'unknown error'}`, 'error');
|
|
135
148
|
}
|
|
136
|
-
|
|
149
|
+
settled();
|
|
137
150
|
})
|
|
138
151
|
.catch((e) => {
|
|
139
152
|
store.pushNotice(`Update failed: ${e?.message || e}`, 'error');
|
|
140
|
-
|
|
153
|
+
settled();
|
|
141
154
|
});
|
|
142
155
|
};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
// First paint, THEN the initial check. The repaint epoch is captured after
|
|
157
|
+
// this panel has taken the surface: capturing it before the first paint
|
|
158
|
+
// binds it to the previous owner's epoch, which the open transition (a
|
|
159
|
+
// panel identity change) supersedes — leaving "Latest version" stuck on
|
|
160
|
+
// "checking…". Esc after the paint still closes the panel for good.
|
|
161
|
+
void Promise.resolve(render({ checking: true }))
|
|
162
|
+
.catch((e) => {
|
|
163
|
+
store.pushNotice(`update panel failed: ${e?.message || e}`, 'error');
|
|
164
|
+
return false;
|
|
165
|
+
})
|
|
166
|
+
.then((painted) => {
|
|
167
|
+
// Open abandoned (Esc while the first read was pending): the panel
|
|
168
|
+
// never took the surface, so the check result must not paint it either.
|
|
169
|
+
if (!painted) return;
|
|
170
|
+
const initialChecked = deferredRerender();
|
|
171
|
+
void Promise.resolve(store.checkForUpdate?.({}))
|
|
172
|
+
.then(() => initialChecked())
|
|
173
|
+
.catch(() => initialChecked());
|
|
174
|
+
});
|
|
147
175
|
};
|
|
148
176
|
|
|
149
177
|
const openAutoClearPicker = (options = {}) => {
|
|
150
178
|
const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
|
|
179
|
+
// Surface claim, same rule as openUpdatePicker: every paint (render() and
|
|
180
|
+
// renderAdvanced()) re-validates and re-arms, so a readCurrent() settling
|
|
181
|
+
// after Esc — on open or on any later toggle — can never paint over the
|
|
182
|
+
// user's surface.
|
|
183
|
+
const own = surface.claim();
|
|
184
|
+
const paintPanel = (panel) => own.paint(panel);
|
|
151
185
|
// Lead BP4 messages cache TTL follows autoClear (cache-strategy.mjs
|
|
152
186
|
// resolveLeadMessagesTtl): off or idle>=1h -> 1h, shorter idle -> 5m.
|
|
153
187
|
const HOUR_MS = 60 * 60 * 1000;
|
|
@@ -162,6 +196,12 @@ export function createMaintenancePickers({
|
|
|
162
196
|
try { return (await store.getAutoClear?.()) || null; } catch { return null; }
|
|
163
197
|
};
|
|
164
198
|
const applyAutoClear = (patch = {}) => {
|
|
199
|
+
// Bound to the claim on this keypress: a write acking after Esc must not
|
|
200
|
+
// re-open the Auto-clear panel.
|
|
201
|
+
const settled = own.defer(() => {
|
|
202
|
+
void Promise.resolve(render())
|
|
203
|
+
.catch((e) => store.pushNotice(`auto-clear panel failed: ${e?.message || e}`, 'error'));
|
|
204
|
+
});
|
|
165
205
|
void Promise.resolve(store.setAutoClear?.(patch))
|
|
166
206
|
.then((next) => {
|
|
167
207
|
if (!next) {
|
|
@@ -171,11 +211,11 @@ export function createMaintenancePickers({
|
|
|
171
211
|
store.pushNotice(next.enabled ? `autoclear on · idle ${formatDuration(next.idleMs)}` : 'autoclear off', 'info');
|
|
172
212
|
})
|
|
173
213
|
.catch((e) => store.pushNotice(`autoclear failed: ${e?.message || e}`, 'error'))
|
|
174
|
-
.finally(
|
|
214
|
+
.finally(settled);
|
|
175
215
|
};
|
|
176
216
|
const openProviderDurationEditor = (entry) => {
|
|
177
217
|
if (!entry?.provider) return;
|
|
178
|
-
|
|
218
|
+
own.close();
|
|
179
219
|
setSettingsPrompt({
|
|
180
220
|
kind: 'autoclear-provider',
|
|
181
221
|
label: `Auto-clear · ${entry.provider}`,
|
|
@@ -200,7 +240,7 @@ export function createMaintenancePickers({
|
|
|
200
240
|
_action: 'provider-default',
|
|
201
241
|
_entry: entry,
|
|
202
242
|
}));
|
|
203
|
-
|
|
243
|
+
paintPanel({
|
|
204
244
|
title: 'Auto-clear · Advanced',
|
|
205
245
|
description: 'Provider default idle windows. Enter edits the duration text.',
|
|
206
246
|
help: '↑/↓ Select · Enter Edit · Esc Back',
|
|
@@ -238,7 +278,7 @@ export function createMaintenancePickers({
|
|
|
238
278
|
_action: 'advanced',
|
|
239
279
|
},
|
|
240
280
|
];
|
|
241
|
-
|
|
281
|
+
paintPanel({
|
|
242
282
|
title: 'Auto-clear',
|
|
243
283
|
description: `Clear idle context after ${enabled ? formatDuration(idleMs) : 'never'} · lead cache TTL ${cacheTtlLabel}.`,
|
|
244
284
|
help: '↑/↓ Select · ←/→ Toggle On/Off · Enter Open/Toggle · Esc Close',
|
|
@@ -259,16 +299,14 @@ export function createMaintenancePickers({
|
|
|
259
299
|
}
|
|
260
300
|
},
|
|
261
301
|
onCancel: () => {
|
|
262
|
-
|
|
302
|
+
own.close();
|
|
263
303
|
if (returnTo) returnTo();
|
|
264
304
|
},
|
|
265
305
|
});
|
|
266
306
|
};
|
|
267
307
|
setProviderPrompt(null);
|
|
268
|
-
setChannelPrompt(null);
|
|
269
|
-
setHookPrompt(null);
|
|
270
308
|
setSettingsPrompt(null);
|
|
271
|
-
|
|
309
|
+
own.context(null);
|
|
272
310
|
closeUsagePanel();
|
|
273
311
|
if (options.advanced === true) renderAdvanced();
|
|
274
312
|
else render();
|
|
@@ -276,6 +314,9 @@ export function createMaintenancePickers({
|
|
|
276
314
|
|
|
277
315
|
const openProfilePicker = async (options = {}) => {
|
|
278
316
|
const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
|
|
317
|
+
// Surface claim (panel-surface.mjs): getProfile() is a daemon read, so Esc
|
|
318
|
+
// can land before the first paint.
|
|
319
|
+
const own = surface.claim();
|
|
279
320
|
let profile = null;
|
|
280
321
|
try {
|
|
281
322
|
profile = (await store.getProfile?.()) || null;
|
|
@@ -298,16 +339,22 @@ export function createMaintenancePickers({
|
|
|
298
339
|
const currentExperienceLevelId = profile?.experienceLevel || '';
|
|
299
340
|
const currentExperienceLevel = experienceLevels.find((level) => level.id === currentExperienceLevelId) || null;
|
|
300
341
|
const titleValue = String(profile?.title || '').trim();
|
|
342
|
+
// setProfile is a daemon write: rebuild the panel only after it settles so
|
|
343
|
+
// the row cannot show a value the daemon rejected (and so a rejected write
|
|
344
|
+
// cannot escape as an unhandled rejection).
|
|
345
|
+
// Bound to the claim when the cycle keypress builds its chain: a setProfile
|
|
346
|
+
// that acks after Esc must not re-open the Profile panel.
|
|
347
|
+
const reopenProfile = () => own.defer(() => {
|
|
348
|
+
void Promise.resolve(openProfilePicker({ returnTo }))
|
|
349
|
+
.catch((e) => store.pushNotice(`profile panel failed: ${e?.message || e}`, 'error'));
|
|
350
|
+
});
|
|
301
351
|
const cycleLanguage = (direction = 1) => {
|
|
302
352
|
const idx = Math.max(0, languages.findIndex((lang) => lang.id === currentLangId));
|
|
303
353
|
const next = languages[(idx + direction + languages.length) % languages.length];
|
|
304
|
-
|
|
305
|
-
store.
|
|
306
|
-
store.pushNotice(`
|
|
307
|
-
|
|
308
|
-
store.pushNotice(`profile update failed: ${e?.message || e}`, 'error');
|
|
309
|
-
}
|
|
310
|
-
openProfilePicker({ returnTo });
|
|
354
|
+
void Promise.resolve(store.setProfile?.({ language: next.id }))
|
|
355
|
+
.then(() => store.pushNotice(`Language set to ${next.label}`, 'info'))
|
|
356
|
+
.catch((e) => store.pushNotice(`profile update failed: ${e?.message || e}`, 'error'))
|
|
357
|
+
.finally(reopenProfile());
|
|
311
358
|
};
|
|
312
359
|
const cycleExperienceLevel = (direction = 1) => {
|
|
313
360
|
const idx = experienceLevels.findIndex((level) => level.id === currentExperienceLevelId);
|
|
@@ -315,21 +362,17 @@ export function createMaintenancePickers({
|
|
|
315
362
|
? (direction < 0 ? experienceLevels.length - 1 : 0)
|
|
316
363
|
: (idx + direction + experienceLevels.length) % experienceLevels.length;
|
|
317
364
|
const next = experienceLevels[nextIdx];
|
|
318
|
-
|
|
319
|
-
store.
|
|
320
|
-
store.pushNotice(`
|
|
321
|
-
|
|
322
|
-
store.pushNotice(`profile update failed: ${e?.message || e}`, 'error');
|
|
323
|
-
}
|
|
324
|
-
openProfilePicker({ returnTo });
|
|
365
|
+
void Promise.resolve(store.setProfile?.({ experienceLevel: next.id }))
|
|
366
|
+
.then(() => store.pushNotice(`Experience level set to ${next.label}`, 'info'))
|
|
367
|
+
.catch((e) => store.pushNotice(`profile update failed: ${e?.message || e}`, 'error'))
|
|
368
|
+
.finally(reopenProfile());
|
|
325
369
|
};
|
|
370
|
+
if (!own.owns()) return;
|
|
326
371
|
setProviderPrompt(null);
|
|
327
|
-
setChannelPrompt(null);
|
|
328
|
-
setHookPrompt(null);
|
|
329
372
|
setSettingsPrompt(null);
|
|
330
|
-
|
|
373
|
+
own.context(null);
|
|
331
374
|
closeUsagePanel();
|
|
332
|
-
|
|
375
|
+
own.paint({
|
|
333
376
|
title: 'Profile',
|
|
334
377
|
description: 'How the assistant addresses you, adapts terminology, and chooses its response language.',
|
|
335
378
|
help: '↑/↓ Select · ←/→ Change · Enter Edit · Esc Close',
|
|
@@ -369,7 +412,7 @@ export function createMaintenancePickers({
|
|
|
369
412
|
},
|
|
370
413
|
onSelect: (_value, item) => {
|
|
371
414
|
if (item?._action === 'title') {
|
|
372
|
-
|
|
415
|
+
own.close();
|
|
373
416
|
setSettingsPrompt({
|
|
374
417
|
kind: 'profile-title',
|
|
375
418
|
label: 'Profile · Title',
|
|
@@ -382,7 +425,7 @@ export function createMaintenancePickers({
|
|
|
382
425
|
}
|
|
383
426
|
},
|
|
384
427
|
onCancel: () => {
|
|
385
|
-
|
|
428
|
+
own.close();
|
|
386
429
|
if (returnTo) returnTo();
|
|
387
430
|
},
|
|
388
431
|
});
|
|
@@ -35,7 +35,7 @@ export function messageSelectorLabel(text, width = 56) {
|
|
|
35
35
|
export function useMessageSelector({
|
|
36
36
|
store,
|
|
37
37
|
state,
|
|
38
|
-
|
|
38
|
+
surface,
|
|
39
39
|
setPromptDraftOverride,
|
|
40
40
|
syncPromptLayoutRows,
|
|
41
41
|
showPromptHint,
|
|
@@ -69,14 +69,15 @@ export function useMessageSelector({
|
|
|
69
69
|
showPromptHint('No message to jump back to.', 'info');
|
|
70
70
|
return false;
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
const own = surface.claim();
|
|
73
|
+
own.paint({
|
|
73
74
|
_kind: 'message-selector',
|
|
74
75
|
title: 'Jump back to a message',
|
|
75
76
|
description: 'Rewind the conversation to a previous prompt and edit it.',
|
|
76
77
|
initialIndex: rows.length - 1,
|
|
77
78
|
items: rows.map((row) => ({ value: row.id, label: messageSelectorLabel(row.text) })),
|
|
78
79
|
onSelect: (value) => {
|
|
79
|
-
|
|
80
|
+
own.close();
|
|
80
81
|
let restored;
|
|
81
82
|
try {
|
|
82
83
|
restored = store.rewindToItem?.(value);
|
|
@@ -94,10 +95,10 @@ export function useMessageSelector({
|
|
|
94
95
|
}
|
|
95
96
|
applyRewind(restored);
|
|
96
97
|
},
|
|
97
|
-
onCancel: () =>
|
|
98
|
+
onCancel: () => own.close(),
|
|
98
99
|
});
|
|
99
100
|
return true;
|
|
100
|
-
}, [store, state.items,
|
|
101
|
+
}, [store, state.items, surface, applyRewind, showPromptHint]);
|
|
101
102
|
|
|
102
103
|
return { hasUserMessages, openMessageSelector };
|
|
103
104
|
}
|
|
@@ -31,10 +31,8 @@ const MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
31
31
|
export function createModelPicker({
|
|
32
32
|
store,
|
|
33
33
|
getState,
|
|
34
|
-
|
|
34
|
+
surface,
|
|
35
35
|
setProviderPrompt,
|
|
36
|
-
setChannelPrompt,
|
|
37
|
-
setHookPrompt,
|
|
38
36
|
setSettingsPrompt,
|
|
39
37
|
providerModelsCacheRef,
|
|
40
38
|
webSearchModelsCacheRef,
|
|
@@ -46,9 +44,14 @@ export function createModelPicker({
|
|
|
46
44
|
let providerModelsTtlRefreshPromise = null;
|
|
47
45
|
const openModelPicker = async (options = {}) => {
|
|
48
46
|
const state = getState();
|
|
47
|
+
// Surface claim for this picker (panel-surface.mjs): every paint — the
|
|
48
|
+
// loading frame, the provider list, the per-provider model list — proves
|
|
49
|
+
// ownership through it, and each paint re-arms it because a paint can
|
|
50
|
+
// itself be the handover. modelPickerRequestRef only orders concurrent
|
|
51
|
+
// OPENS; it cannot see an Esc that closed this one, so a model load
|
|
52
|
+
// settling afterwards used to repaint the dismissed picker.
|
|
53
|
+
const own = surface.claim();
|
|
49
54
|
setProviderPrompt(null);
|
|
50
|
-
setChannelPrompt(null);
|
|
51
|
-
setHookPrompt(null);
|
|
52
55
|
setSettingsPrompt(null);
|
|
53
56
|
modelPickerRequestRef.current += 1;
|
|
54
57
|
let modelPickerClosed = false;
|
|
@@ -59,8 +62,9 @@ export function createModelPicker({
|
|
|
59
62
|
const cancelModelPicker = () => {
|
|
60
63
|
modelPickerClosed = true;
|
|
61
64
|
if (returnTo) returnTo();
|
|
62
|
-
else
|
|
65
|
+
else own.close();
|
|
63
66
|
};
|
|
67
|
+
const paintModelPicker = (panel) => own.paint(panel);
|
|
64
68
|
const cacheRef = options.cacheRef === 'webSearch' ? webSearchModelsCacheRef : providerModelsCacheRef;
|
|
65
69
|
const loadModels = typeof options.loadModels === 'function' ? options.loadModels : store.listProviderModels;
|
|
66
70
|
let providerModels = Array.isArray(cacheRef.current.models)
|
|
@@ -69,7 +73,7 @@ export function createModelPicker({
|
|
|
69
73
|
let refreshModelsPromise = null;
|
|
70
74
|
let renderedQuickModels = false;
|
|
71
75
|
if (!providerModels.length || options.refreshModels === true) {
|
|
72
|
-
|
|
76
|
+
paintModelPicker({
|
|
73
77
|
title: options.title || 'Model',
|
|
74
78
|
description: options.loadingDescription || 'Loading models...',
|
|
75
79
|
help: returnTo ? `↑/↓ Select · Enter Open · Esc ${returnLabel}` : '↑/↓ Select · Enter Open · Esc Back',
|
|
@@ -108,6 +112,9 @@ export function createModelPicker({
|
|
|
108
112
|
|
|
109
113
|
if (!providerModels || providerModels.length === 0) {
|
|
110
114
|
store.pushNotice(options.emptyNotice || 'no provider models available; open /providers to sign in', 'warn');
|
|
115
|
+
// Delegation is a paint by proxy: the empty-catalog fallback opens
|
|
116
|
+
// Providers, so it must prove ownership exactly like paintModelPicker.
|
|
117
|
+
if (!own.owns()) return;
|
|
111
118
|
void openProviderSetupPicker({
|
|
112
119
|
title: 'Providers',
|
|
113
120
|
continueLabel: 'Back to model setup',
|
|
@@ -375,18 +382,28 @@ export function createModelPicker({
|
|
|
375
382
|
if (typeof options.onImmediateSelect === 'function') {
|
|
376
383
|
options.onImmediateSelect(routeInput, selected, effort);
|
|
377
384
|
} else {
|
|
378
|
-
|
|
385
|
+
own.paint(null);
|
|
379
386
|
}
|
|
387
|
+
// Post-ack delegation (return to the caller's panel), bound to the
|
|
388
|
+
// claim AFTER this keypress's own navigation: an Esc between the
|
|
389
|
+
// save and its ack must cancel the hop, not paint over the surface
|
|
390
|
+
// the user moved to.
|
|
391
|
+
const afterSelect = own.defer(() => {
|
|
392
|
+
if (typeof options.onAfterSelect === 'function') options.onAfterSelect();
|
|
393
|
+
});
|
|
380
394
|
void savePromise
|
|
381
395
|
.then((result) => {
|
|
382
396
|
if (result) clearModelCaches('all');
|
|
383
|
-
|
|
397
|
+
afterSelect();
|
|
384
398
|
return result;
|
|
385
399
|
})
|
|
386
400
|
.catch((e) => store.pushNotice(`Couldn’t save model: ${e?.message || e}`, 'error'));
|
|
387
401
|
return;
|
|
388
402
|
}
|
|
389
|
-
|
|
403
|
+
own.paint(null);
|
|
404
|
+
const afterSelect = own.defer(() => {
|
|
405
|
+
if (typeof options.onAfterSelect === 'function') options.onAfterSelect();
|
|
406
|
+
});
|
|
390
407
|
void store.setRoute(routeInput)
|
|
391
408
|
.then((ok) => {
|
|
392
409
|
if (ok) clearModelCaches('provider');
|
|
@@ -396,7 +413,7 @@ export function createModelPicker({
|
|
|
396
413
|
: 'Model switch is already running',
|
|
397
414
|
ok ? 'info' : 'warn',
|
|
398
415
|
);
|
|
399
|
-
if (ok
|
|
416
|
+
if (ok) afterSelect();
|
|
400
417
|
})
|
|
401
418
|
.catch((e) => store.pushNotice(`Couldn’t switch model: ${e?.message || e}`, 'error'));
|
|
402
419
|
};
|
|
@@ -408,7 +425,7 @@ export function createModelPicker({
|
|
|
408
425
|
(item) => item._provider === activeRoute?.provider && item._modelId === activeRoute?.model,
|
|
409
426
|
),
|
|
410
427
|
);
|
|
411
|
-
|
|
428
|
+
paintModelPicker({
|
|
412
429
|
title: providerDisplayName(provider),
|
|
413
430
|
description: options.modelDescription || 'Select a model. Adjust Effort with ←/→.',
|
|
414
431
|
footer: (item) => modelFooter(item?._model),
|
|
@@ -460,7 +477,7 @@ export function createModelPicker({
|
|
|
460
477
|
0,
|
|
461
478
|
providerItems.findIndex((item) => item._provider === providerHighlight),
|
|
462
479
|
);
|
|
463
|
-
|
|
480
|
+
paintModelPicker({
|
|
464
481
|
title: options.title || 'Model',
|
|
465
482
|
description: options.providerDescription || 'Choose a provider.',
|
|
466
483
|
help: returnTo ? `↑/↓ Select · Enter Open · Esc ${returnLabel}` : '↑/↓ Select · Enter Open · Esc Back',
|
|
@@ -19,10 +19,8 @@ import {
|
|
|
19
19
|
|
|
20
20
|
export function createOnboardingSteps({
|
|
21
21
|
store,
|
|
22
|
-
|
|
22
|
+
surface,
|
|
23
23
|
setProviderPrompt,
|
|
24
|
-
setChannelPrompt,
|
|
25
|
-
setHookPrompt,
|
|
26
24
|
setSettingsPrompt,
|
|
27
25
|
setOnboardingActive,
|
|
28
26
|
onboardingRef,
|
|
@@ -85,6 +83,11 @@ export function createOnboardingSteps({
|
|
|
85
83
|
|
|
86
84
|
const openOnboardingAuthStep = async () => {
|
|
87
85
|
prefetchOnboardingStep2();
|
|
86
|
+
// Surface claim (panel-surface.mjs): the provider-setup read below runs
|
|
87
|
+
// before anything paints, and the wizard is reachable while a normal
|
|
88
|
+
// surface is on screen (/onboarding, Step-1 re-entry), so Esc during the
|
|
89
|
+
// wait must leave that surface alone instead of dropping Step 1 on it.
|
|
90
|
+
const own = surface.claim();
|
|
88
91
|
// Load the provider setup BEFORE opening the picker so Step 1 renders the
|
|
89
92
|
// real list in one frame instead of flashing the "Checking Providers"
|
|
90
93
|
// placeholder (that swap is what looked like a jump on entry). On failure,
|
|
@@ -93,6 +96,7 @@ export function createOnboardingSteps({
|
|
|
93
96
|
try {
|
|
94
97
|
preloadedSetup = await store.getProviderSetup?.();
|
|
95
98
|
} catch { /* openProviderSetupPicker will show its loading frame + error. */ }
|
|
99
|
+
if (!own.owns()) return;
|
|
96
100
|
void openProviderSetupPicker({
|
|
97
101
|
title: 'First Run · Step 1/4 · Provider Auth',
|
|
98
102
|
returnTo: () => openOnboardingAuthStep(),
|
|
@@ -133,7 +137,7 @@ export function createOnboardingSteps({
|
|
|
133
137
|
const webSearchRoute = onboardingRef.current.webSearchRoute || null;
|
|
134
138
|
const overrides = onboardingRef.current.agentRoutes || {};
|
|
135
139
|
const hasOverrides = Object.keys(overrides).length > 0;
|
|
136
|
-
|
|
140
|
+
surface.claim().close();
|
|
137
141
|
setOnboardingActive(false);
|
|
138
142
|
const done = () => store.pushNotice('First-run setup complete.', 'info');
|
|
139
143
|
const failed = (e) => store.pushNotice(`Couldn’t save setup: ${e?.message || e}`, 'error');
|
|
@@ -172,6 +176,7 @@ export function createOnboardingSteps({
|
|
|
172
176
|
// the plain model list is shown. Selecting Main Model updates defaultRoute;
|
|
173
177
|
// agents that have no explicit override keep inheriting it.
|
|
174
178
|
const openOnboardingRoleModelPicker = async (target) => {
|
|
179
|
+
const own = surface.claim();
|
|
175
180
|
const isLead = target === 'lead';
|
|
176
181
|
const isWebSearch = target === 'webSearch';
|
|
177
182
|
// Web Search uses the web-search-capable model list; lead/agent use provider models.
|
|
@@ -186,14 +191,15 @@ export function createOnboardingSteps({
|
|
|
186
191
|
models = normalizeModelOptions(webSearchModels || []);
|
|
187
192
|
if (models.length === 0) {
|
|
188
193
|
store.pushNotice('no native web-search models available; connect OpenAI, Grok, Gemini, or Anthropic', 'warn');
|
|
189
|
-
|
|
194
|
+
// Fallback delegation is a paint by proxy: prove ownership first.
|
|
195
|
+
if (own.owns()) void openOnboardingWorkflowStep();
|
|
190
196
|
return;
|
|
191
197
|
}
|
|
192
198
|
} else {
|
|
193
199
|
models = normalizeModelOptions(onboardingRef.current.providerModels || []);
|
|
194
200
|
if (models.length === 0) {
|
|
195
201
|
store.pushNotice('no provider models available; open /providers to sign in', 'warn');
|
|
196
|
-
openOnboardingAuthStep();
|
|
202
|
+
if (own.owns()) openOnboardingAuthStep();
|
|
197
203
|
return;
|
|
198
204
|
}
|
|
199
205
|
}
|
|
@@ -245,7 +251,8 @@ export function createOnboardingSteps({
|
|
|
245
251
|
: isWebSearch
|
|
246
252
|
? 'Web Search'
|
|
247
253
|
: (onboardingRef.current.agents || []).find((a) => a.id === target)?.label || target;
|
|
248
|
-
|
|
254
|
+
if (!own.owns()) return;
|
|
255
|
+
own.paint({
|
|
249
256
|
title: `First Run · ${label}`,
|
|
250
257
|
description: isLead
|
|
251
258
|
? 'Pick the main model. Agents inherit this unless individually changed.'
|
|
@@ -294,6 +301,7 @@ export function createOnboardingSteps({
|
|
|
294
301
|
};
|
|
295
302
|
|
|
296
303
|
const openOnboardingWorkflowStep = async () => {
|
|
304
|
+
const own = surface.claim();
|
|
297
305
|
if (!Array.isArray(onboardingRef.current.providerModels) || onboardingRef.current.providerModels.length === 0) {
|
|
298
306
|
try {
|
|
299
307
|
onboardingRef.current.providerModels = await store.listProviderModels();
|
|
@@ -308,7 +316,9 @@ export function createOnboardingSteps({
|
|
|
308
316
|
onboardingRef.current.defaultRoute = null;
|
|
309
317
|
onboardingRef.current.agentRoutes = {};
|
|
310
318
|
store.pushNotice('no provider models available; open /providers to sign in', 'warn');
|
|
311
|
-
|
|
319
|
+
// Step 2's empty-model fallback hands the surface to Step 1 after an
|
|
320
|
+
// await: same ownership rule as its own paint below.
|
|
321
|
+
if (own.owns()) openOnboardingAuthStep();
|
|
312
322
|
return;
|
|
313
323
|
}
|
|
314
324
|
// Main Model stays unset until the user picks one; no auto-recommendation.
|
|
@@ -326,11 +336,10 @@ export function createOnboardingSteps({
|
|
|
326
336
|
const webSearchRoute = onboardingRef.current.webSearchRoute || null;
|
|
327
337
|
const overrides = onboardingRef.current.agentRoutes || {};
|
|
328
338
|
const agents = onboardingRef.current.agents || [];
|
|
339
|
+
if (!own.owns()) return;
|
|
329
340
|
setProviderPrompt(null);
|
|
330
|
-
setChannelPrompt(null);
|
|
331
|
-
setHookPrompt(null);
|
|
332
341
|
setSettingsPrompt(null);
|
|
333
|
-
|
|
342
|
+
own.paint({
|
|
334
343
|
title: 'First Run · Step 2/4 · Models',
|
|
335
344
|
description: 'Set the Main Model; each agent inherits it unless changed.',
|
|
336
345
|
indexMode: 'always',
|
|
@@ -384,7 +393,7 @@ export function createOnboardingSteps({
|
|
|
384
393
|
}
|
|
385
394
|
},
|
|
386
395
|
onCancel: () => {
|
|
387
|
-
|
|
396
|
+
own.close();
|
|
388
397
|
onboardingWarnReopen();
|
|
389
398
|
},
|
|
390
399
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Panel/prompt supersession epoch.
|
|
2
|
+
//
|
|
3
|
+
// Module-global on purpose: the TUI runs ONE App tree and every picker/prompt
|
|
4
|
+
// factory is re-created on each render, so this ownership marker cannot live in
|
|
5
|
+
// a factory closure.
|
|
6
|
+
//
|
|
7
|
+
// Anything that hands surface ownership back to the user — closing a picker
|
|
8
|
+
// (Esc/cancel), cancelling a text-entry prompt, or issuing a NEWER submit of
|
|
9
|
+
// the same prompt — supersedes every deferred paint issued before it. Async
|
|
10
|
+
// work captures currentPanelEpoch() when the USER ACTION starts and re-checks
|
|
11
|
+
// isPanelEpochCurrent(token) immediately before it opens, refreshes, or closes
|
|
12
|
+
// a surface. That single check kills both failure modes:
|
|
13
|
+
// - a daemon write settling after Esc re-opening the dismissed panel, and
|
|
14
|
+
// - an older ACK closing (or claiming success for) a panel that meanwhile
|
|
15
|
+
// holds newer user input.
|
|
16
|
+
let panelEpoch = 0;
|
|
17
|
+
|
|
18
|
+
export function currentPanelEpoch() {
|
|
19
|
+
return panelEpoch;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Marks every in-flight deferred paint as superseded and returns the new token
|
|
23
|
+
// for the action performing the supersession.
|
|
24
|
+
export function supersedePanelEpoch() {
|
|
25
|
+
panelEpoch += 1;
|
|
26
|
+
return panelEpoch;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isPanelEpochCurrent(token) {
|
|
30
|
+
return token === panelEpoch;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Identity of the SURFACE a panel object stands for. A rebuild of the same
|
|
34
|
+
// surface (light Settings refresh, toggle-driven MCP re-render) keeps this
|
|
35
|
+
// value stable; a different panel reports a different identity.
|
|
36
|
+
export function panelIdentity(panel) {
|
|
37
|
+
if (!panel || typeof panel !== 'object') return null;
|
|
38
|
+
return panel._kind || panel.title || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// True when `next` takes the surface away from a DIFFERENT panel that was open:
|
|
42
|
+
// a close (open → null) or a replacement by another panel. Both hand the
|
|
43
|
+
// surface to something the user is looking at now, so deferred paints issued
|
|
44
|
+
// before it are stale. Not a handover:
|
|
45
|
+
// - null → anything: nothing owned the surface, and the in-flight write of a
|
|
46
|
+
// text-entry prompt currently on screen must stay valid, and
|
|
47
|
+
// - a rebuild of the same panel: its own deferred refresh must still land.
|
|
48
|
+
export function shouldSupersedePanelEpoch(previous, next) {
|
|
49
|
+
if (!previous || typeof previous !== 'object') return false;
|
|
50
|
+
if (!next || typeof next !== 'object') return true;
|
|
51
|
+
if (previous === next) return false;
|
|
52
|
+
return panelIdentity(previous) !== panelIdentity(next);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Ownership is CLAIMED, never asserted by hand: panel-surface.mjs owns the
|
|
56
|
+
// sinks and hands out claims that carry these tokens (see that module's header).
|