mixdog 0.9.95 → 0.9.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/MIT.txt +56 -0
- package/LICENSES/codex-NOTICE.txt +6 -0
- package/NOTICE.md +46 -9
- package/README.md +0 -17
- package/package.json +16 -12
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +21 -29
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
- package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +34 -34
- package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +76 -248
- package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
- package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
- package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
- package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
- package/src/runtime/memory/lib/http-router.mjs +9 -6
- package/src/runtime/memory/lib/ko-morph.mjs +3 -20
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/agent-route-config.mjs +119 -0
- package/src/runtime/shared/atomic-file.mjs +80 -4
- package/src/runtime/shared/channel-notification-routing.mjs +2 -2
- package/src/runtime/shared/child-guardian.mjs +204 -108
- package/src/runtime/shared/child-spawn-gate.mjs +139 -72
- package/src/runtime/shared/config.mjs +120 -47
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +150 -100
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -27
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +47 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +63 -39
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +45 -17
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +96 -24
- package/src/session-runtime/settings-api.mjs +12 -24
- package/src/session-runtime/tool-defs.mjs +3 -3
- package/src/session-runtime/workflow-agents-api.mjs +55 -81
- package/src/session-runtime/workflow.mjs +14 -24
- package/src/standalone/agent-dispatch-broker.mjs +198 -0
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
- package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
- package/src/standalone/agent-tool/tool-def.mjs +0 -14
- package/src/standalone/channel-admin.mjs +53 -64
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/session-client.mjs +1134 -0
- package/src/standalone/session-protocol.mjs +192 -0
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +34 -21
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +3 -4
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +48 -43
- package/src/tui/app/resume-picker.mjs +2 -2
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker.mjs +31 -33
- package/src/tui/app/transcript-window.mjs +38 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
- package/src/tui/app/use-prompt-handlers.mjs +86 -21
- package/src/tui/app/use-prompt-hint.mjs +3 -2
- package/src/tui/app/use-prompt-queue-history.mjs +10 -3
- package/src/tui/app/use-transcript-scroll.mjs +10 -3
- package/src/tui/app/use-transcript-window.mjs +50 -17
- package/src/tui/components/PromptInput.jsx +71 -38
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
- package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
- package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
- package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
- package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +1031 -1676
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +12 -13
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +38 -4
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
- package/src/tui/{engine → session}/session-api.mjs +110 -93
- package/src/tui/{engine → session}/session-flow.mjs +8 -2
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +38 -176
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
- package/src/tui/session.mjs +22 -0
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-client.mjs +0 -894
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
- package/src/standalone/engine-daemon-protocol.mjs +0 -33
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* app/message-selector.mjs — Claude Code's "jump back to a previous message"
|
|
3
|
+
* list, opened by a double Escape from an EMPTY prompt while idle.
|
|
4
|
+
*
|
|
5
|
+
* Selecting a row rewinds the conversation to just before that prompt and
|
|
6
|
+
* drops its text back into the input box for editing: the transcript rows and
|
|
7
|
+
* the model-side history after it are undone, so the resubmit continues from
|
|
8
|
+
* exactly that point (engine side: store.rewindToItem).
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useMemo } from 'react';
|
|
11
|
+
|
|
12
|
+
export const MAX_SELECTABLE_MESSAGES = 20;
|
|
13
|
+
|
|
14
|
+
/** Newest-last list of rewindable user prompts, capped for the picker. */
|
|
15
|
+
export function selectableUserItems(items, limit = MAX_SELECTABLE_MESSAGES) {
|
|
16
|
+
const source = Array.isArray(items) ? items : [];
|
|
17
|
+
const rows = [];
|
|
18
|
+
for (let index = source.length - 1; index >= 0 && rows.length < limit; index -= 1) {
|
|
19
|
+
const item = source[index];
|
|
20
|
+
if (item?.kind !== 'user') continue;
|
|
21
|
+
if (item?.id == null) continue;
|
|
22
|
+
const text = String(item.text || '').trim();
|
|
23
|
+
if (!text) continue;
|
|
24
|
+
rows.push({ id: String(item.id), text });
|
|
25
|
+
}
|
|
26
|
+
return rows.reverse();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function messageSelectorLabel(text, width = 56) {
|
|
30
|
+
const firstLine = String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
31
|
+
if (firstLine.length <= width) return firstLine;
|
|
32
|
+
return `${firstLine.slice(0, Math.max(1, width - 1))}…`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useMessageSelector({
|
|
36
|
+
store,
|
|
37
|
+
state,
|
|
38
|
+
setPicker,
|
|
39
|
+
setPromptDraftOverride,
|
|
40
|
+
syncPromptLayoutRows,
|
|
41
|
+
showPromptHint,
|
|
42
|
+
clearPromptHint,
|
|
43
|
+
}) {
|
|
44
|
+
// Cheap enough to recompute only when the transcript array identity changes;
|
|
45
|
+
// the prompt asks for this on every Escape, never per keystroke.
|
|
46
|
+
const hasUserMessages = useMemo(
|
|
47
|
+
() => (Array.isArray(state.items) ? state.items : []).some(
|
|
48
|
+
(item) => item?.kind === 'user' && String(item?.text || '').trim(),
|
|
49
|
+
),
|
|
50
|
+
[state.items],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const applyRewind = useCallback((restored) => {
|
|
54
|
+
const text = String(restored?.text || '');
|
|
55
|
+
if (!text) {
|
|
56
|
+
showPromptHint('Could not restore that message.', 'error');
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
clearPromptHint();
|
|
60
|
+
syncPromptLayoutRows(text);
|
|
61
|
+
setPromptDraftOverride({ id: Date.now(), value: text });
|
|
62
|
+
return true;
|
|
63
|
+
}, [clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride, showPromptHint]);
|
|
64
|
+
|
|
65
|
+
const openMessageSelector = useCallback(() => {
|
|
66
|
+
const items = store.getState?.().items ?? state.items;
|
|
67
|
+
const rows = selectableUserItems(items);
|
|
68
|
+
if (rows.length === 0) {
|
|
69
|
+
showPromptHint('No message to jump back to.', 'info');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
setPicker({
|
|
73
|
+
_kind: 'message-selector',
|
|
74
|
+
title: 'Jump back to a message',
|
|
75
|
+
description: 'Rewind the conversation to a previous prompt and edit it.',
|
|
76
|
+
initialIndex: rows.length - 1,
|
|
77
|
+
items: rows.map((row) => ({ value: row.id, label: messageSelectorLabel(row.text) })),
|
|
78
|
+
onSelect: (value) => {
|
|
79
|
+
setPicker(null);
|
|
80
|
+
let restored;
|
|
81
|
+
try {
|
|
82
|
+
restored = store.rewindToItem?.(value);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
showPromptHint(`rewind failed: ${error?.message || error}`, 'error');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Daemon-backed stores answer asynchronously; a local store returns the
|
|
88
|
+
// payload directly. Both land in the same apply path.
|
|
89
|
+
if (restored && typeof restored.then === 'function') {
|
|
90
|
+
void Promise.resolve(restored)
|
|
91
|
+
.then(applyRewind)
|
|
92
|
+
.catch((error) => showPromptHint(`rewind failed: ${error?.message || error}`, 'error'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
applyRewind(restored);
|
|
96
|
+
},
|
|
97
|
+
onCancel: () => setPicker(null),
|
|
98
|
+
});
|
|
99
|
+
return true;
|
|
100
|
+
}, [store, state.items, setPicker, applyRewind, showPromptHint]);
|
|
101
|
+
|
|
102
|
+
return { hasUserMessages, openMessageSelector };
|
|
103
|
+
}
|
|
@@ -138,20 +138,19 @@ export function createOnboardingSteps({
|
|
|
138
138
|
const done = () => store.pushNotice('First-run setup complete.', 'info');
|
|
139
139
|
const failed = (e) => store.pushNotice(`Couldn’t save setup: ${e?.message || e}`, 'error');
|
|
140
140
|
// Branch 1 — Main Model set: full persist. Agents without an explicit
|
|
141
|
-
// override are sent; untouched agents are left out so the
|
|
141
|
+
// override are sent; untouched agents are left out so the session never
|
|
142
142
|
// overwrites them (they follow the Main Model dynamically at runtime).
|
|
143
143
|
if (defaultRoute) {
|
|
144
144
|
void store.completeOnboarding?.({
|
|
145
145
|
defaultRoute,
|
|
146
|
-
defaultProvider: defaultRoute.provider,
|
|
147
146
|
...(hasOverrides ? { agentRoutes: { ...overrides } } : {}),
|
|
148
147
|
...(searchRoute ? { searchRoute } : {}),
|
|
149
148
|
}).then(done).catch(failed);
|
|
150
149
|
return;
|
|
151
150
|
}
|
|
152
151
|
// Branch 2 — Main unset but some Search/agent picks exist: partial persist.
|
|
153
|
-
// Only the explicit overrides are sent (no defaultRoute
|
|
154
|
-
//
|
|
152
|
+
// Only the explicit overrides are sent (no defaultRoute); the
|
|
153
|
+
// session skips agents lacking a route and marks onboarding complete.
|
|
155
154
|
if (hasOverrides || searchRoute) {
|
|
156
155
|
void store.completeOnboarding?.({
|
|
157
156
|
...(hasOverrides ? { agentRoutes: { ...overrides } } : {}),
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Extracted from App.jsx behavior-preservingly. This cluster is ref/state
|
|
5
5
|
* coupled (it drives setPicker + a fan of prompt setters), so it's delivered as
|
|
6
|
-
* a dependency-injection factory rather than pure functions
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* (state, store, setPicker, the prompt setters, and the projects.mjs helpers)
|
|
10
|
-
* threaded in through the factory argument.
|
|
6
|
+
* a dependency-injection factory rather than pure functions. Project registry
|
|
7
|
+
* and path operations are daemon calls on `store`; the TUI retains only the
|
|
8
|
+
* OS-native folder chooser.
|
|
11
9
|
*/
|
|
12
10
|
export function createProjectPicker({
|
|
13
11
|
state,
|
|
@@ -19,44 +17,53 @@ export function createProjectPicker({
|
|
|
19
17
|
setSettingsPrompt,
|
|
20
18
|
setContextPanel,
|
|
21
19
|
closeUsagePanel,
|
|
22
|
-
listProjects,
|
|
23
|
-
addProject,
|
|
24
|
-
touchProjectSelected,
|
|
25
|
-
resolveProjectPath,
|
|
26
20
|
projectNameFromPath,
|
|
27
21
|
pickFolder,
|
|
28
22
|
}) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} catch {
|
|
34
|
-
projects = [];
|
|
23
|
+
const serviceCall = (name, ...args) => {
|
|
24
|
+
const target = store?.[name];
|
|
25
|
+
if (typeof target !== 'function') {
|
|
26
|
+
return Promise.reject(new TypeError(`project service method ${name} is unavailable`));
|
|
35
27
|
}
|
|
28
|
+
return Promise.resolve(target.apply(store, args));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const buildProjectPickerState = ({
|
|
32
|
+
initialEntry = false,
|
|
33
|
+
projects = [],
|
|
34
|
+
loading = false,
|
|
35
|
+
requestId = null,
|
|
36
|
+
} = {}) => {
|
|
36
37
|
const currentPath = String(state.cwd || process.cwd() || '');
|
|
37
38
|
const items = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
if (!loading) {
|
|
40
|
+
for (const project of projects) {
|
|
41
|
+
if (!project?.path) continue;
|
|
42
|
+
items.push({
|
|
43
|
+
value: project.path,
|
|
44
|
+
label: project.name || project.path,
|
|
45
|
+
meta: project.path,
|
|
46
|
+
_project: project,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Last row: implicit current-directory shortcut (not persisted).
|
|
41
50
|
items.push({
|
|
42
|
-
value:
|
|
43
|
-
label:
|
|
44
|
-
meta:
|
|
45
|
-
|
|
51
|
+
value: '__use_current__',
|
|
52
|
+
label: 'Current Path',
|
|
53
|
+
meta: currentPath,
|
|
54
|
+
_action: 'current',
|
|
46
55
|
});
|
|
47
56
|
}
|
|
48
|
-
// Last row: implicit current-directory shortcut (not persisted).
|
|
49
|
-
items.push({
|
|
50
|
-
value: '__use_current__',
|
|
51
|
-
label: 'Current Path',
|
|
52
|
-
meta: currentPath,
|
|
53
|
-
_action: 'current',
|
|
54
|
-
});
|
|
55
57
|
return {
|
|
56
58
|
kind: 'project',
|
|
59
|
+
_kind: 'project',
|
|
60
|
+
_projectRequestId: requestId,
|
|
61
|
+
_projectInitialPending: loading && initialEntry,
|
|
57
62
|
title: 'Project',
|
|
58
|
-
description: 'Choose a project.',
|
|
59
|
-
help:
|
|
63
|
+
description: loading ? 'Loading projects from the session…' : 'Choose a project.',
|
|
64
|
+
help: loading
|
|
65
|
+
? 'Waiting for the project service…'
|
|
66
|
+
: initialEntry
|
|
60
67
|
? '↑/↓ Select · Enter Open · c Create · r Rename'
|
|
61
68
|
: '↑/↓ Select · Enter Open · c Create · r Rename · Esc Back',
|
|
62
69
|
indexMode: 'always',
|
|
@@ -69,20 +76,11 @@ export function createProjectPicker({
|
|
|
69
76
|
return;
|
|
70
77
|
}
|
|
71
78
|
if (item?._action === 'current') {
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
store.setCwd?.(currentPath, {
|
|
75
|
-
notice: !initialEntry,
|
|
76
|
-
message: `Project set: ${projectNameFromPath(currentPath)}`,
|
|
77
|
-
});
|
|
78
|
-
} catch (e) {
|
|
79
|
-
store.pushNotice(`project switch failed: ${e?.message || e}`, 'error');
|
|
80
|
-
}
|
|
79
|
+
void enterProject(currentPath, { notice: !initialEntry, register: false });
|
|
81
80
|
return;
|
|
82
81
|
}
|
|
83
|
-
setPicker(null);
|
|
84
82
|
const project = item?._project;
|
|
85
|
-
if (project?.path) enterProject(project.path, { notice: !initialEntry });
|
|
83
|
+
if (project?.path) void enterProject(project.path, { notice: !initialEntry });
|
|
86
84
|
},
|
|
87
85
|
onKey: (input, _key, item) => {
|
|
88
86
|
if (input === 'c' || input === 'C') {
|
|
@@ -157,7 +155,7 @@ export function createProjectPicker({
|
|
|
157
155
|
openProjectPicker();
|
|
158
156
|
return;
|
|
159
157
|
}
|
|
160
|
-
registerProject(result.path);
|
|
158
|
+
void registerProject(result.path);
|
|
161
159
|
})
|
|
162
160
|
.catch(() => {
|
|
163
161
|
beginNewProjectManual();
|
|
@@ -165,39 +163,43 @@ export function createProjectPicker({
|
|
|
165
163
|
};
|
|
166
164
|
|
|
167
165
|
// Register a project in the picker list without switching this session's cwd.
|
|
168
|
-
const registerProject = (rawPath) => {
|
|
169
|
-
const path =
|
|
166
|
+
const registerProject = async (rawPath) => {
|
|
167
|
+
const path = String(rawPath || '').trim();
|
|
170
168
|
if (!path) {
|
|
171
169
|
store.pushNotice('project path is required', 'warn');
|
|
172
|
-
return;
|
|
170
|
+
return false;
|
|
173
171
|
}
|
|
174
172
|
try {
|
|
175
|
-
const project = addProject
|
|
173
|
+
const project = await serviceCall('addProject', path);
|
|
176
174
|
if (project?.name) store.pushNotice(`project added: ${project.name}`, 'info');
|
|
177
|
-
openProjectPicker();
|
|
175
|
+
await openProjectPicker();
|
|
176
|
+
return true;
|
|
178
177
|
} catch (e) {
|
|
179
178
|
store.pushNotice(`project add failed: ${e?.message || e}`, 'error');
|
|
179
|
+
return false;
|
|
180
180
|
}
|
|
181
181
|
};
|
|
182
182
|
|
|
183
183
|
// Switch the active working directory to a registered/created project path.
|
|
184
|
-
const enterProject = (rawPath, options = {}) => {
|
|
185
|
-
const path =
|
|
184
|
+
const enterProject = async (rawPath, options = {}) => {
|
|
185
|
+
const path = String(rawPath || '').trim();
|
|
186
186
|
if (!path) {
|
|
187
187
|
store.pushNotice('project path is required', 'warn');
|
|
188
|
-
return;
|
|
188
|
+
return false;
|
|
189
189
|
}
|
|
190
|
+
setPicker(null);
|
|
190
191
|
try {
|
|
191
192
|
// Switch cwd first; only persist the project once the runtime accepts it,
|
|
192
193
|
// so an invalid/missing path can never be written to projects.json.
|
|
193
|
-
|
|
194
|
+
const resolved = await serviceCall('setCwd', path, {
|
|
194
195
|
notice: options?.notice !== false,
|
|
195
196
|
message: `Project set: ${projectNameFromPath(path)}`,
|
|
196
197
|
});
|
|
197
|
-
addProject
|
|
198
|
-
|
|
198
|
+
if (options?.register !== false) await serviceCall('addProject', resolved || path);
|
|
199
|
+
return true;
|
|
199
200
|
} catch (e) {
|
|
200
201
|
store.pushNotice(`project switch failed: ${e?.message || e}`, 'error');
|
|
202
|
+
return false;
|
|
201
203
|
}
|
|
202
204
|
};
|
|
203
205
|
|
|
@@ -225,14 +227,36 @@ export function createProjectPicker({
|
|
|
225
227
|
// a Name column + Path column. The list always opens (even when empty) and
|
|
226
228
|
// lists registered projects first, then a trailing "Current Path" shortcut.
|
|
227
229
|
// Creating a new project is available via the picker-level c shortcut.
|
|
228
|
-
const openProjectPicker = () => {
|
|
230
|
+
const openProjectPicker = async ({ initialEntry = false } = {}) => {
|
|
229
231
|
setProviderPrompt(null);
|
|
230
232
|
setChannelPrompt(null);
|
|
231
233
|
setHookPrompt(null);
|
|
232
234
|
setSettingsPrompt(null);
|
|
233
235
|
setContextPanel(null);
|
|
234
236
|
closeUsagePanel();
|
|
235
|
-
|
|
237
|
+
const requestId = Symbol('project-picker-request');
|
|
238
|
+
setPicker(buildProjectPickerState({
|
|
239
|
+
initialEntry,
|
|
240
|
+
loading: true,
|
|
241
|
+
requestId,
|
|
242
|
+
}));
|
|
243
|
+
try {
|
|
244
|
+
const projects = await serviceCall('listProjects');
|
|
245
|
+
setPicker((current) => current?._projectRequestId === requestId
|
|
246
|
+
? buildProjectPickerState({
|
|
247
|
+
initialEntry,
|
|
248
|
+
projects: Array.isArray(projects) ? projects : [],
|
|
249
|
+
requestId,
|
|
250
|
+
})
|
|
251
|
+
: current);
|
|
252
|
+
return projects;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
setPicker((current) => current?._projectRequestId === requestId
|
|
255
|
+
? buildProjectPickerState({ initialEntry, projects: [], requestId })
|
|
256
|
+
: current);
|
|
257
|
+
store.pushNotice(`project list failed: ${error?.message || error}`, 'error');
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
236
260
|
};
|
|
237
261
|
|
|
238
262
|
return {
|
|
@@ -16,13 +16,6 @@ import {
|
|
|
16
16
|
imageReferenceIds,
|
|
17
17
|
pastedTextReferenceIds,
|
|
18
18
|
} from '../paste-attachments.mjs';
|
|
19
|
-
import {
|
|
20
|
-
ensureDir,
|
|
21
|
-
isDirectory,
|
|
22
|
-
pathExists,
|
|
23
|
-
renameProject,
|
|
24
|
-
resolveProjectPath,
|
|
25
|
-
} from '../../standalone/projects.mjs';
|
|
26
19
|
|
|
27
20
|
export function createPromptSubmit({
|
|
28
21
|
store,
|
|
@@ -56,10 +49,17 @@ export function createPromptSubmit({
|
|
|
56
49
|
pastedImagesRef,
|
|
57
50
|
pastedTextsRef,
|
|
58
51
|
}) {
|
|
52
|
+
const serviceCall = (name, ...args) => {
|
|
53
|
+
const target = store?.[name];
|
|
54
|
+
if (typeof target !== 'function') {
|
|
55
|
+
return Promise.reject(new TypeError(`project service method ${name} is unavailable`));
|
|
56
|
+
}
|
|
57
|
+
return Promise.resolve(target.apply(store, args));
|
|
58
|
+
};
|
|
59
59
|
const submitPrompt = (prompt, options) => {
|
|
60
60
|
if (typeof store.submitAsync !== 'function') return store.submit(prompt, options);
|
|
61
61
|
void Promise.resolve(store.submitAsync(prompt, options)).then((accepted) => {
|
|
62
|
-
if (accepted === false) store.pushNotice('prompt was not accepted by the session
|
|
62
|
+
if (accepted === false) store.pushNotice('prompt was not accepted by the session service', 'error');
|
|
63
63
|
}).catch((error) => {
|
|
64
64
|
store.pushNotice(`prompt submit failed: ${error?.message || error}`, 'error');
|
|
65
65
|
});
|
|
@@ -192,7 +192,7 @@ export function createPromptSubmit({
|
|
|
192
192
|
const channelId = isPipe ? parts[1] : parts[0];
|
|
193
193
|
Promise.resolve(store.setChannel({
|
|
194
194
|
channelId,
|
|
195
|
-
|
|
195
|
+
provider: channelPrompt.provider,
|
|
196
196
|
}))
|
|
197
197
|
.then(() => resumeAfterChannelPrompt(channelPrompt))
|
|
198
198
|
.catch((e) => {
|
|
@@ -240,9 +240,14 @@ export function createPromptSubmit({
|
|
|
240
240
|
store.pushNotice('working directory path is required', 'warn');
|
|
241
241
|
return false;
|
|
242
242
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
void serviceCall('setCwd', commandText, {
|
|
244
|
+
message: `Project set: ${projectNameFromPath(commandText)}`,
|
|
245
|
+
}).then(() => {
|
|
246
|
+
setSettingsPrompt(null);
|
|
247
|
+
void openSettingsPicker();
|
|
248
|
+
}).catch((error) => {
|
|
249
|
+
store.pushNotice(`project switch failed: ${error?.message || error}`, 'error');
|
|
250
|
+
});
|
|
246
251
|
return true;
|
|
247
252
|
}
|
|
248
253
|
if (settingsPrompt.kind === 'project-new') {
|
|
@@ -250,23 +255,25 @@ export function createPromptSubmit({
|
|
|
250
255
|
store.pushNotice('project path is required', 'warn');
|
|
251
256
|
return false;
|
|
252
257
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
258
|
+
void serviceCall('inspectProjectPath', commandText).then((result) => {
|
|
259
|
+
const path = String(result?.path || commandText);
|
|
260
|
+
if (result?.directory === true) {
|
|
261
|
+
setSettingsPrompt(null);
|
|
262
|
+
void registerProject(path);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (result?.exists === true) {
|
|
266
|
+
store.pushNotice(`${path} is not a directory`, 'warn');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
setSettingsPrompt({
|
|
270
|
+
kind: 'project-create-confirm',
|
|
271
|
+
label: 'New project · Create folder?',
|
|
272
|
+
hint: `${path} does not exist. Type "y" to create it, or anything else to cancel.`,
|
|
273
|
+
pendingPath: path,
|
|
274
|
+
});
|
|
275
|
+
}).catch((error) => {
|
|
276
|
+
store.pushNotice(`project path check failed: ${error?.message || error}`, 'error');
|
|
270
277
|
});
|
|
271
278
|
return true;
|
|
272
279
|
}
|
|
@@ -274,14 +281,13 @@ export function createPromptSubmit({
|
|
|
274
281
|
const pendingPath = String(settingsPrompt.pendingPath || '');
|
|
275
282
|
const answer = String(commandText || '').trim().toLowerCase();
|
|
276
283
|
if (answer === 'y' || answer === 'yes') {
|
|
277
|
-
|
|
278
|
-
if (!created) {
|
|
279
|
-
store.pushNotice(`could not create folder: ${pendingPath}`, 'error');
|
|
284
|
+
void serviceCall('ensureProjectDirectory', pendingPath).then((created) => {
|
|
280
285
|
setSettingsPrompt(null);
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
void registerProject(created || pendingPath);
|
|
287
|
+
}).catch((error) => {
|
|
288
|
+
store.pushNotice(`could not create folder: ${error?.message || error}`, 'error');
|
|
289
|
+
setSettingsPrompt(null);
|
|
290
|
+
});
|
|
285
291
|
return true;
|
|
286
292
|
}
|
|
287
293
|
setSettingsPrompt(null);
|
|
@@ -290,16 +296,15 @@ export function createPromptSubmit({
|
|
|
290
296
|
}
|
|
291
297
|
if (settingsPrompt.kind === 'project-rename') {
|
|
292
298
|
const targetPath = String(settingsPrompt.projectPath || '');
|
|
293
|
-
|
|
294
|
-
const updated = renameProject(targetPath, commandText);
|
|
299
|
+
void serviceCall('renameProject', targetPath, commandText).then((updated) => {
|
|
295
300
|
if (updated) {
|
|
296
301
|
store.pushNotice(`project renamed to "${updated.name}"`, 'info');
|
|
297
302
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
+
setSettingsPrompt(null);
|
|
304
|
+
void openProjectPicker();
|
|
305
|
+
}).catch((error) => {
|
|
306
|
+
store.pushNotice(`rename failed: ${error?.message || error}`, 'error');
|
|
307
|
+
});
|
|
303
308
|
return true;
|
|
304
309
|
}
|
|
305
310
|
if (settingsPrompt.kind === 'system-shell') {
|
|
@@ -13,14 +13,14 @@ export function createResumePicker({
|
|
|
13
13
|
formatSessionUpdatedAt,
|
|
14
14
|
formatSessionMessageCount,
|
|
15
15
|
}) {
|
|
16
|
-
const openResumePicker = () => {
|
|
16
|
+
const openResumePicker = async () => {
|
|
17
17
|
let sessions;
|
|
18
18
|
try {
|
|
19
19
|
// Terminal ↔ desktop interop: the summary cache is per-process, so a
|
|
20
20
|
// session created/updated by the desktop app (or another CLI) after this
|
|
21
21
|
// process's first listing would be invisible without an authoritative
|
|
22
22
|
// storage rescan on picker open.
|
|
23
|
-
sessions = store.listSessions({ refreshFromStorage: true });
|
|
23
|
+
sessions = await store.listSessions({ refreshFromStorage: true });
|
|
24
24
|
} catch (e) {
|
|
25
25
|
store.pushNotice(`could not list saved chats: ${e?.message || e}`, 'error');
|
|
26
26
|
return;
|
|
@@ -86,7 +86,9 @@ export function createRoutePickers({
|
|
|
86
86
|
const items = agents.map((agent) => ({
|
|
87
87
|
value: agent.id,
|
|
88
88
|
label: agent.label,
|
|
89
|
-
metaParts:
|
|
89
|
+
metaParts: routeOverrides[agent.id] || agent.route
|
|
90
|
+
? agentModelParts(routeOverrides[agent.id] || agent.route)
|
|
91
|
+
: [{ text: '(follows main)', width: 17 }, { text: '', width: 6 }, { text: '', width: 4 }],
|
|
90
92
|
description: agent.description || agent.definition?.description || '',
|
|
91
93
|
_agent: agent,
|
|
92
94
|
}));
|
|
@@ -53,7 +53,7 @@ export function createSettingsPicker({
|
|
|
53
53
|
const snapshot = (await store.getSettingsSnapshot?.({ heavy: !heavyCache })) || {};
|
|
54
54
|
const autoClear = snapshot.autoClear || {};
|
|
55
55
|
const compaction = snapshot.compaction || {};
|
|
56
|
-
const
|
|
56
|
+
const recap = snapshot.recap || { enabled: true };
|
|
57
57
|
const channels = snapshot.channels || { enabled: true };
|
|
58
58
|
const systemShell = snapshot.systemShell || { source: 'auto', command: '', effective: '' };
|
|
59
59
|
const outputStyle = snapshot.outputStyle || {};
|
|
@@ -63,19 +63,19 @@ export function createSettingsPicker({
|
|
|
63
63
|
const plugins = heavyCache ? heavyCache.plugins : (snapshot.plugins || { count: 0 });
|
|
64
64
|
const skills = heavyCache ? heavyCache.skills : (snapshot.skills || { count: 0 });
|
|
65
65
|
const channelWorker = snapshot.channelWorker || null;
|
|
66
|
-
const
|
|
67
|
-
? (heavyCache.
|
|
68
|
-
: (snapshot.
|
|
69
|
-
const
|
|
70
|
-
&& Object.prototype.hasOwnProperty.call(overrides, '
|
|
71
|
-
? overrides.
|
|
72
|
-
:
|
|
66
|
+
const channelProvider = heavyCache
|
|
67
|
+
? (heavyCache.channelProvider || 'discord')
|
|
68
|
+
: (snapshot.channelProvider || 'discord');
|
|
69
|
+
const effectiveChannelProvider = overrides
|
|
70
|
+
&& Object.prototype.hasOwnProperty.call(overrides, 'channelProvider')
|
|
71
|
+
? overrides.channelProvider
|
|
72
|
+
: channelProvider;
|
|
73
73
|
// Refresh the cache every build (light or full) so the next light
|
|
74
74
|
// refresh reuses whatever was most recently known, and so an
|
|
75
|
-
// optimistic override (e.g. channel
|
|
75
|
+
// optimistic override (e.g. channel provider cycle) sticks without
|
|
76
76
|
// re-running the heavy getter it came from.
|
|
77
|
-
settingsHeavyCacheRef.current = { mcp, hooks, plugins, skills,
|
|
78
|
-
const
|
|
77
|
+
settingsHeavyCacheRef.current = { mcp, hooks, plugins, skills, channelProvider: effectiveChannelProvider };
|
|
78
|
+
const channelProviderLabel = effectiveChannelProvider === 'telegram' ? 'Telegram' : 'Discord';
|
|
79
79
|
const remoteEnabled = snapshot.remoteEnabled === true;
|
|
80
80
|
const remoteRuntimeDescription = channelWorker?.running
|
|
81
81
|
? `runtime running · pid ${channelWorker.pid}`
|
|
@@ -84,9 +84,7 @@ export function createSettingsPicker({
|
|
|
84
84
|
const outputStyleLabel = outputStyle?.current?.label || outputStyle?.current?.id || outputStyle?.configured || 'Default';
|
|
85
85
|
const workflowLabel = workflowDisplayName(workflow);
|
|
86
86
|
const boolLabel = (enabled) => enabled ? 'On' : 'Off';
|
|
87
|
-
const compactTypeDescription =
|
|
88
|
-
? 'Injects raw transcript lines (memory off: no LLM chunking).'
|
|
89
|
-
: 'Uses Memory recall to rebuild context faster on large histories.';
|
|
87
|
+
const compactTypeDescription = 'Uses Memory recall to rebuild context faster on large histories.';
|
|
90
88
|
const applyAutoClear = (patch = {}) => {
|
|
91
89
|
void Promise.resolve(store.setAutoClear?.(patch))
|
|
92
90
|
.then((next) => {
|
|
@@ -200,25 +198,25 @@ export function createSettingsPicker({
|
|
|
200
198
|
.catch((e) => store.pushNotice(`Remote toggle failed: ${e?.message || e}`, 'error'))
|
|
201
199
|
.finally(() => { void openSettingsPicker({ light: true }); });
|
|
202
200
|
};
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const currentIndex = Math.max(0,
|
|
206
|
-
const chosen =
|
|
207
|
-
if (chosen ===
|
|
208
|
-
openSettingsPicker({ light: true, overrides: {
|
|
201
|
+
const cycleChannelProvider = (direction = 1) => {
|
|
202
|
+
const providers = ['discord', 'telegram'];
|
|
203
|
+
const currentIndex = Math.max(0, providers.indexOf(effectiveChannelProvider));
|
|
204
|
+
const chosen = providers[(currentIndex + direction + providers.length) % providers.length];
|
|
205
|
+
if (chosen === effectiveChannelProvider) {
|
|
206
|
+
openSettingsPicker({ light: true, overrides: { channelProvider: effectiveChannelProvider } });
|
|
209
207
|
return;
|
|
210
208
|
}
|
|
211
209
|
try {
|
|
212
|
-
store.
|
|
210
|
+
store.setChannelProvider(chosen);
|
|
213
211
|
const label = chosen === 'telegram' ? 'Telegram' : 'Discord';
|
|
214
212
|
const restartHint = (remoteEnabled || channelWorker?.running)
|
|
215
213
|
? `Channel set to ${label}. Restart remote to apply.`
|
|
216
214
|
: `Channel set to ${label}.`;
|
|
217
215
|
store.pushNotice(restartHint, 'info');
|
|
218
216
|
} catch (e) {
|
|
219
|
-
store.pushNotice(`channel
|
|
217
|
+
store.pushNotice(`channel provider failed: ${e?.message || e}`, 'error');
|
|
220
218
|
}
|
|
221
|
-
openSettingsPicker({ light: true, overrides: {
|
|
219
|
+
openSettingsPicker({ light: true, overrides: { channelProvider: chosen } });
|
|
222
220
|
};
|
|
223
221
|
// Row order groups by concern — routing/model first, then session
|
|
224
222
|
// behavior, integrations, channels/remote, system — and must stay in
|
|
@@ -304,10 +302,10 @@ export function createSettingsPicker({
|
|
|
304
302
|
{
|
|
305
303
|
value: 'memory',
|
|
306
304
|
label: 'Memory',
|
|
307
|
-
meta: boolLabel(
|
|
308
|
-
description:
|
|
309
|
-
? '
|
|
310
|
-
: '
|
|
305
|
+
meta: `Recap ${boolLabel(recap.enabled !== false)}`,
|
|
306
|
+
description: recap.enabled === false
|
|
307
|
+
? 'Background recap off. Core memories and on-demand recall stay available.'
|
|
308
|
+
: 'Background recap and core memories.',
|
|
311
309
|
_action: 'memory',
|
|
312
310
|
},
|
|
313
311
|
{
|
|
@@ -352,11 +350,11 @@ export function createSettingsPicker({
|
|
|
352
350
|
_action: 'channels',
|
|
353
351
|
},
|
|
354
352
|
{
|
|
355
|
-
value: 'channel-
|
|
353
|
+
value: 'channel-provider',
|
|
356
354
|
label: 'Channel',
|
|
357
|
-
meta:
|
|
355
|
+
meta: channelProviderLabel,
|
|
358
356
|
description: 'Left/Right or Enter changes channel type (Discord or Telegram).',
|
|
359
|
-
_action: 'channel-
|
|
357
|
+
_action: 'channel-provider',
|
|
360
358
|
},
|
|
361
359
|
{
|
|
362
360
|
value: 'channel-setting',
|
|
@@ -412,7 +410,7 @@ export function createSettingsPicker({
|
|
|
412
410
|
else if (item?._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
|
|
413
411
|
else if (item?._action === 'channels') applyChannels(!(channels.enabled !== false));
|
|
414
412
|
else if (item?._action === 'remote-runtime') applyRemoteRuntime();
|
|
415
|
-
else if (item?._action === 'channel-
|
|
413
|
+
else if (item?._action === 'channel-provider') cycleChannelProvider(-1);
|
|
416
414
|
else if (item?._action === 'output-style') cycleOutputStyle(-1);
|
|
417
415
|
else if (item?._action === 'theme') cycleTheme(-1);
|
|
418
416
|
else if (item?._action === 'workflow') cycleWorkflow(-1);
|
|
@@ -422,7 +420,7 @@ export function createSettingsPicker({
|
|
|
422
420
|
else if (item?._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
|
|
423
421
|
else if (item?._action === 'channels') applyChannels(!(channels.enabled !== false));
|
|
424
422
|
else if (item?._action === 'remote-runtime') applyRemoteRuntime();
|
|
425
|
-
else if (item?._action === 'channel-
|
|
423
|
+
else if (item?._action === 'channel-provider') cycleChannelProvider(1);
|
|
426
424
|
else if (item?._action === 'output-style') cycleOutputStyle(1);
|
|
427
425
|
else if (item?._action === 'theme') cycleTheme(1);
|
|
428
426
|
else if (item?._action === 'workflow') cycleWorkflow(1);
|
|
@@ -433,7 +431,7 @@ export function createSettingsPicker({
|
|
|
433
431
|
else if (item._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
|
|
434
432
|
else if (item._action === 'channels') applyChannels(!(channels.enabled !== false));
|
|
435
433
|
else if (item._action === 'remote-runtime') applyRemoteRuntime();
|
|
436
|
-
else if (item._action === 'channel-
|
|
434
|
+
else if (item._action === 'channel-provider') cycleChannelProvider(1);
|
|
437
435
|
else if (item._action === 'channel-setting') openChannelSettingTypePicker({ returnTo: openSettingsPicker });
|
|
438
436
|
else if (item._action === 'output-style') openOutputStylePicker({ returnTo: openSettingsPicker });
|
|
439
437
|
else if (item._action === 'theme') openThemePicker({ returnTo: openSettingsPicker });
|