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,55 @@
|
|
|
1
|
+
// Wire identity shared by the daemon's session transport and its views. Kept dependency-free
|
|
2
|
+
// so both the host process and the client can read it without pulling in the
|
|
3
|
+
// other side's module graph.
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import {
|
|
7
|
+
SESSION_CONFIGURE_ACTIONS,
|
|
8
|
+
SESSION_READ_ACTIONS,
|
|
9
|
+
} from './session-protocol.mjs';
|
|
10
|
+
|
|
11
|
+
/** Compatibility generation, not an application or capability version.
|
|
12
|
+
* Increment only for a rare, fundamentally incompatible wire rewrite. */
|
|
13
|
+
export const SESSION_PROTOCOL = 1;
|
|
14
|
+
|
|
15
|
+
/** Monotonic API index inside protocol 1. Increment when actions or payload
|
|
16
|
+
* shapes change. Newer clients replace older daemons; older clients attach to
|
|
17
|
+
* newer daemons and are normalized at the session boundary. */
|
|
18
|
+
export const SESSION_REVISION = 1;
|
|
19
|
+
|
|
20
|
+
/** Diagnostic-only action-surface identity. It never advances the protocol. */
|
|
21
|
+
export const SESSION_CAPABILITY_FINGERPRINT = createHash('sha256')
|
|
22
|
+
.update(JSON.stringify({
|
|
23
|
+
read: SESSION_READ_ACTIONS,
|
|
24
|
+
configure: SESSION_CONFIGURE_ACTIONS,
|
|
25
|
+
}))
|
|
26
|
+
.digest('hex')
|
|
27
|
+
.slice(0, 16);
|
|
28
|
+
|
|
29
|
+
let cachedVersion = null;
|
|
30
|
+
|
|
31
|
+
function numericVersion(value) {
|
|
32
|
+
const match = String(value || '').match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
33
|
+
return match ? match.slice(1).map(Number) : [0, 0, 0];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function compareRuntimeVersions(left, right) {
|
|
37
|
+
const a = numericVersion(left);
|
|
38
|
+
const b = numericVersion(right);
|
|
39
|
+
for (let index = 0; index < 3; index += 1) {
|
|
40
|
+
if (a[index] !== b[index]) return a[index] - b[index];
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Build rank within one protocol generation. */
|
|
46
|
+
export function runtimeVersion() {
|
|
47
|
+
if (cachedVersion) return cachedVersion;
|
|
48
|
+
try {
|
|
49
|
+
const manifest = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
50
|
+
cachedVersion = String(manifest?.version || '0.0.0');
|
|
51
|
+
} catch {
|
|
52
|
+
cachedVersion = '0.0.0';
|
|
53
|
+
}
|
|
54
|
+
return cachedVersion;
|
|
55
|
+
}
|
package/src/tui/App.jsx
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* queued steering prompts + rounded prompt input (one cluster)
|
|
11
11
|
* statusline (vendored L1/L2)
|
|
12
12
|
*
|
|
13
|
-
* State comes from the
|
|
13
|
+
* State comes from the session store via useSession; submitting a line calls
|
|
14
14
|
* store.submit() (or handles a slash command locally). The whole tree is live
|
|
15
15
|
* (no <Static>): full-width bands and the native hardware caret both need real
|
|
16
16
|
* layout, which <Static> collapses. The terminal handles scrollback itself as
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import React, { useState, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
20
20
|
import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';
|
|
21
21
|
import { theme, surfaceBackground } from './theme.mjs';
|
|
22
|
-
import {
|
|
22
|
+
import { useSession } from './hooks/useSession.mjs';
|
|
23
23
|
import { classifyToolCategory } from '../runtime/shared/tool-surface.mjs';
|
|
24
24
|
import { localPackageVersion } from '../runtime/shared/update-checker.mjs';
|
|
25
25
|
import { Spinner } from './components/Spinner.jsx';
|
|
@@ -42,16 +42,6 @@ import {
|
|
|
42
42
|
splitPastedImagePathCandidates,
|
|
43
43
|
} from './paste-attachments.mjs';
|
|
44
44
|
import { formatDuration } from './time-format.mjs';
|
|
45
|
-
import {
|
|
46
|
-
listProjects,
|
|
47
|
-
addProject,
|
|
48
|
-
touchProjectSelected,
|
|
49
|
-
renameProject,
|
|
50
|
-
isDirectory,
|
|
51
|
-
pathExists,
|
|
52
|
-
ensureDir,
|
|
53
|
-
resolveProjectPath,
|
|
54
|
-
} from '../standalone/projects.mjs';
|
|
55
45
|
import { pickFolder } from '../standalone/folder-dialog.mjs';
|
|
56
46
|
import {
|
|
57
47
|
formatHookDenialDetail,
|
|
@@ -100,6 +90,7 @@ import { usePromptDraftFlow } from './app/use-prompt-draft-flow.mjs';
|
|
|
100
90
|
import { useTranscriptActivity } from './app/use-transcript-activity.mjs';
|
|
101
91
|
import { createAppPickers } from './app/create-app-pickers.mjs';
|
|
102
92
|
import { usePromptQueueHistory } from './app/use-prompt-queue-history.mjs';
|
|
93
|
+
import { useMessageSelector } from './app/message-selector.mjs';
|
|
103
94
|
import { useTerminalChrome } from './app/use-terminal-chrome.mjs';
|
|
104
95
|
import { useTranscriptWindow } from './app/use-transcript-window.mjs';
|
|
105
96
|
import {
|
|
@@ -120,6 +111,7 @@ import {
|
|
|
120
111
|
transcriptMeasuredRowsCache,
|
|
121
112
|
buildTranscriptRowIndex,
|
|
122
113
|
transcriptRenderWindow,
|
|
114
|
+
transcriptSwapReturnsToTail,
|
|
123
115
|
} from './app/transcript-window.mjs';
|
|
124
116
|
import {
|
|
125
117
|
SEARCH_DEFAULT_ROUTE,
|
|
@@ -265,7 +257,7 @@ function resolveOnboardingCompleted(store, onboardingCompleted) {
|
|
|
265
257
|
}
|
|
266
258
|
|
|
267
259
|
export function App({ store, initialStatusLine = '', forceOnboarding = false, onboardingCompleted = undefined }) {
|
|
268
|
-
const state =
|
|
260
|
+
const state = useSession(store);
|
|
269
261
|
const [toolOutputExpanded, setToolOutputExpanded] = useState(false);
|
|
270
262
|
// True for the entire first-run onboarding wizard (every step + nested depth)
|
|
271
263
|
// so the welcome banner stays reserved and the layout doesn't jump when the
|
|
@@ -326,6 +318,9 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
326
318
|
// same glide back to the bottom.
|
|
327
319
|
const followingRef = useRef(false);
|
|
328
320
|
const lastItemsCountRef = useRef(0);
|
|
321
|
+
// Head item of the last committed transcript: a bulk swap (session load /
|
|
322
|
+
// clear / compaction trim) changes it, a live append never does.
|
|
323
|
+
const lastFirstItemIdRef = useRef(null);
|
|
329
324
|
// picker = null | { type, title, items, onSelect }
|
|
330
325
|
// Rendered as an option panel attached directly above the bottom prompt.
|
|
331
326
|
const pickerOpenedFromEnterRef = useRef(false);
|
|
@@ -385,7 +380,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
385
380
|
const [usagePanel, setUsagePanel] = useState(null);
|
|
386
381
|
const usageRequestRef = useRef(0);
|
|
387
382
|
// Cache of the last computed heavy settings-picker status objects (MCP,
|
|
388
|
-
// hooks, plugins, skills, channel
|
|
383
|
+
// hooks, plugins, skills, channel provider). ←/→ cycle/toggle handlers in
|
|
389
384
|
// openSettingsPicker() pass { light: true } to reuse this cache instead of
|
|
390
385
|
// re-querying these heavy getters on every keystroke; only a full open
|
|
391
386
|
// (initial /config or Esc-return) recomputes them.
|
|
@@ -413,10 +408,6 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
413
408
|
setSettingsPrompt,
|
|
414
409
|
setContextPanel,
|
|
415
410
|
closeUsagePanel,
|
|
416
|
-
listProjects,
|
|
417
|
-
addProject,
|
|
418
|
-
touchProjectSelected,
|
|
419
|
-
resolveProjectPath,
|
|
420
411
|
projectNameFromPath,
|
|
421
412
|
pickFolder,
|
|
422
413
|
});
|
|
@@ -429,9 +420,13 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
429
420
|
initialPickerBuiltRef.current = true;
|
|
430
421
|
const onboardingOwnsScreen = !resolveOnboardingCompleted(store, onboardingCompleted) || forceOnboarding;
|
|
431
422
|
if (!onboardingOwnsScreen && state.items.length === 0) {
|
|
432
|
-
setPicker(projectPicker.buildProjectPickerState({ initialEntry: true }));
|
|
423
|
+
setPicker(projectPicker.buildProjectPickerState({ initialEntry: true, loading: true }));
|
|
433
424
|
}
|
|
434
425
|
}
|
|
426
|
+
useEffect(() => {
|
|
427
|
+
if (livePickerRef.current?._projectInitialPending !== true) return;
|
|
428
|
+
void projectPickerRef.current?.openProjectPicker({ initialEntry: true });
|
|
429
|
+
}, [store]);
|
|
435
430
|
const {
|
|
436
431
|
beginNewProject,
|
|
437
432
|
registerProject,
|
|
@@ -793,12 +788,17 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
793
788
|
const count = state.items.length;
|
|
794
789
|
const previousCount = lastItemsCountRef.current;
|
|
795
790
|
lastItemsCountRef.current = count;
|
|
796
|
-
|
|
791
|
+
const firstId = count > 0 ? (state.items[0]?.id ?? null) : null;
|
|
792
|
+
const previousFirstId = lastFirstItemIdRef.current;
|
|
793
|
+
lastFirstItemIdRef.current = firstId;
|
|
794
|
+
// A bulk swap (compaction included) is an explicit return to the tail: the
|
|
795
|
+
// rows the reader was anchored to no longer exist.
|
|
796
|
+
if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
|
|
797
797
|
resetTranscriptScroll();
|
|
798
798
|
return;
|
|
799
799
|
}
|
|
800
800
|
if (count === previousCount || dragRef.current.active) return;
|
|
801
|
-
}, [state.items
|
|
801
|
+
}, [state.items, resetTranscriptScroll]);
|
|
802
802
|
|
|
803
803
|
// Exit + queued-restore + prompt history: app/use-prompt-queue-history.mjs.
|
|
804
804
|
const {
|
|
@@ -823,6 +823,17 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
823
823
|
promptHistoryNavRef,
|
|
824
824
|
});
|
|
825
825
|
|
|
826
|
+
// Double-Esc message selector (Claude Code parity): app/message-selector.mjs.
|
|
827
|
+
const { hasUserMessages, openMessageSelector } = useMessageSelector({
|
|
828
|
+
store,
|
|
829
|
+
state,
|
|
830
|
+
setPicker,
|
|
831
|
+
setPromptDraftOverride,
|
|
832
|
+
syncPromptLayoutRows,
|
|
833
|
+
showPromptHint,
|
|
834
|
+
clearPromptHint,
|
|
835
|
+
});
|
|
836
|
+
|
|
826
837
|
// PROMPT HANDLER cluster extracted to app/use-prompt-handlers.mjs.
|
|
827
838
|
const {
|
|
828
839
|
handlePromptPaste,
|
|
@@ -849,6 +860,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
849
860
|
recentPromptHistory,
|
|
850
861
|
resetPromptHistoryNav,
|
|
851
862
|
restoreQueuedToPrompt,
|
|
863
|
+
openMessageSelector,
|
|
852
864
|
usagePanel,
|
|
853
865
|
closeUsagePanel,
|
|
854
866
|
contextPanel,
|
|
@@ -1257,6 +1269,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
|
|
|
1257
1269
|
handlePromptHistoryNavigate,
|
|
1258
1270
|
handlePromptInterrupt,
|
|
1259
1271
|
handlePromptPaste,
|
|
1272
|
+
hasUserMessages,
|
|
1260
1273
|
hookPrompt,
|
|
1261
1274
|
initialStatusLine,
|
|
1262
1275
|
onPromptDraftChange,
|
|
@@ -8,7 +8,7 @@ import stringWidth from 'string-width';
|
|
|
8
8
|
import { theme } from '../theme.mjs';
|
|
9
9
|
import { formatToolSurface } from '../../runtime/shared/tool-surface.mjs';
|
|
10
10
|
|
|
11
|
-
// SEARCH_DEFAULT marker — mirrors
|
|
11
|
+
// SEARCH_DEFAULT marker — mirrors search defaults SEARCH_DEFAULT_PROVIDER/MODEL
|
|
12
12
|
// (mixdog-session-runtime.mjs 1167-1168). A search route of {provider:'default',
|
|
13
13
|
// model:'default'} means "follow the Main Model" at runtime (nativeSearchRoutes).
|
|
14
14
|
export const SEARCH_DEFAULT_ROUTE = Object.freeze({ provider: 'default', model: 'default' });
|
package/src/tui/app/app-view.jsx
CHANGED
|
@@ -20,7 +20,7 @@ import { UsagePanel } from '../components/UsagePanel.jsx';
|
|
|
20
20
|
import { TextEntryPanel } from '../components/TextEntryPanel.jsx';
|
|
21
21
|
import { Item } from '../components/TranscriptItem.jsx';
|
|
22
22
|
export function renderAppView(ctx) {
|
|
23
|
-
const { PANEL_MAX_VISIBLE, acceptSlashPalette, activeSlashQuery, activeTools, agentRevision, cancelChannelPrompt, cancelHookPrompt, cancelProviderPrompt, cancelSettingsPrompt, cancelSlashPalette, channelPrompt, clearPromptHint, completeSlashPalette, contextPanel, cycleWorkflowFromPrompt, exiting, expandedOptionPanel, floatingPanelRows, frameColumns, gridSelectionActiveRef, guardHintWidth, handlePromptEscape, handlePromptHistoryNavigate, handlePromptInterrupt, handlePromptPaste, hookPrompt, initialStatusLine, inputBoxHidden, inputHint, inputHintTone, liveSpinner, onPromptDraftChange, onSubmit, overlayHintAttachItemIndex, overlayHintBandRows, overlayHintFallbackRow, overlayHintOnLastItem, panelCloseMaskRows, panelInkMaskEpoch, panelTransitionClearRows, panelTransitionEpoch, picker, pickerOpenedFromEnterRef, pickerOpenedFromEnterTimerRef, pickerVisibleRows, promptBoxRectRef, promptBoxRows, promptDraft, promptDraftOverride, promptMetaVisible, promptMouseSelectionRef, promptSelectionRef, promptSpinnerColumns, promptValueRef, providerPrompt, queuedCompact, queuedVisible, renderedTranscriptItems, resizeEpoch, resizeState, restoreQueuedToPrompt, setPicker, setSlashIndex, setTextEntryLayoutRows, settingsPrompt, showWelcomeBanner, slashCommands, slashIndex, slashPaletteOpen, state, statuslineStats, store, toolApproval, toolOutputExpanded, transcriptContentHeight, transcriptGuardRows, transcriptMeasureRef, transcriptTailPinned, transcriptWindow, transientStatusWidth, tuiReady, usagePanel, viewportHeight, welcomePromptHintRows, welcomePromptHintText } = ctx; /* DESTRUCTURE */
|
|
23
|
+
const { PANEL_MAX_VISIBLE, acceptSlashPalette, activeSlashQuery, activeTools, agentRevision, cancelChannelPrompt, cancelHookPrompt, cancelProviderPrompt, cancelSettingsPrompt, cancelSlashPalette, channelPrompt, clearPromptHint, completeSlashPalette, contextPanel, cycleWorkflowFromPrompt, exiting, expandedOptionPanel, floatingPanelRows, frameColumns, gridSelectionActiveRef, guardHintWidth, handlePromptEscape, handlePromptHistoryNavigate, handlePromptInterrupt, handlePromptPaste, hasUserMessages, hookPrompt, initialStatusLine, inputBoxHidden, inputHint, inputHintTone, liveSpinner, onPromptDraftChange, onSubmit, overlayHintAttachItemIndex, overlayHintBandRows, overlayHintFallbackRow, overlayHintOnLastItem, panelCloseMaskRows, panelInkMaskEpoch, panelTransitionClearRows, panelTransitionEpoch, picker, pickerOpenedFromEnterRef, pickerOpenedFromEnterTimerRef, pickerVisibleRows, promptBoxRectRef, promptBoxRows, promptDraft, promptDraftOverride, promptMetaVisible, promptMouseSelectionRef, promptSelectionRef, promptSpinnerColumns, promptValueRef, providerPrompt, queuedCompact, queuedVisible, renderedTranscriptItems, resizeEpoch, resizeState, restoreQueuedToPrompt, setPicker, setSlashIndex, setTextEntryLayoutRows, settingsPrompt, showWelcomeBanner, slashCommands, slashIndex, slashPaletteOpen, state, statuslineStats, store, toolApproval, toolOutputExpanded, transcriptContentHeight, transcriptGuardRows, transcriptMeasureRef, transcriptTailPinned, transcriptWindow, transientStatusWidth, tuiReady, usagePanel, viewportHeight, welcomePromptHintRows, welcomePromptHintText } = ctx; /* DESTRUCTURE */
|
|
24
24
|
const promptInputControl = (
|
|
25
25
|
<PromptInput
|
|
26
26
|
onSubmit={onSubmit}
|
|
@@ -47,6 +47,7 @@ export function renderAppView(ctx) {
|
|
|
47
47
|
// actually be accepted — otherwise Enter must submit the raw text as
|
|
48
48
|
// before instead of dead-ending in the palette accept path.
|
|
49
49
|
commandPaletteActive={slashPaletteOpen && slashCommands.length > 0}
|
|
50
|
+
commandPaletteOpen={slashPaletteOpen}
|
|
50
51
|
onCommandPaletteNavigate={(direction) => {
|
|
51
52
|
setSlashIndex((index) => {
|
|
52
53
|
const total = slashCommands.length;
|
|
@@ -65,7 +66,9 @@ export function renderAppView(ctx) {
|
|
|
65
66
|
onCommandPaletteAccept={acceptSlashPalette}
|
|
66
67
|
onCommandPaletteCancel={cancelSlashPalette}
|
|
67
68
|
onCommandPaletteComplete={completeSlashPalette}
|
|
68
|
-
onRestoreQueued={
|
|
69
|
+
onRestoreQueued={restoreQueuedToPrompt}
|
|
70
|
+
hasQueuedMessages={Array.isArray(state.queued) && state.queued.length > 0}
|
|
71
|
+
hasMessages={hasUserMessages}
|
|
69
72
|
/>
|
|
70
73
|
);
|
|
71
74
|
|
|
@@ -27,7 +27,7 @@ export function createChannelPickers({
|
|
|
27
27
|
// a long first-time install finishing later must not hijack whatever
|
|
28
28
|
// screen/picker the user has since navigated to.
|
|
29
29
|
let pickerGen = 0;
|
|
30
|
-
const openChannelTypeActionsPicker = async (
|
|
30
|
+
const openChannelTypeActionsPicker = async (provider, options = {}) => {
|
|
31
31
|
pickerGen += 1;
|
|
32
32
|
const parentReturn = typeof options.returnTo === 'function'
|
|
33
33
|
? options.returnTo
|
|
@@ -43,15 +43,15 @@ export function createChannelPickers({
|
|
|
43
43
|
store.pushNotice(`channels failed: ${e?.message || e}`, 'error');
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
const isTelegram =
|
|
47
|
-
const
|
|
46
|
+
const isTelegram = provider === 'telegram';
|
|
47
|
+
const activeProvider = setup.provider || 'discord';
|
|
48
48
|
const tokenDescription = isTelegram
|
|
49
49
|
? `${setup.telegram?.status ?? 'Off'}${setup.telegram?.problem ? ' · Invalid' : ''}`
|
|
50
50
|
: `${setup.discord.status}${setup.discord.problem ? ' · Invalid' : ''}`;
|
|
51
51
|
const mainEntry = setup.channel || {};
|
|
52
52
|
const mainTarget = isTelegram
|
|
53
|
-
? (mainEntry?.telegramChatId || (
|
|
54
|
-
: (mainEntry?.discordChannelId || (
|
|
53
|
+
? (mainEntry?.telegramChatId || (activeProvider === 'telegram' ? mainEntry?.channelId : ''))
|
|
54
|
+
: (mainEntry?.discordChannelId || (activeProvider === 'discord' ? mainEntry?.channelId : ''));
|
|
55
55
|
const mainDescription = isTelegram
|
|
56
56
|
? (mainTarget ? `Chat ID ${mainTarget}` : 'Not set · Enter Telegram chat ID')
|
|
57
57
|
: (mainTarget ? `Channel ID ${mainTarget}` : 'Not set · Enter Discord channel ID');
|
|
@@ -61,13 +61,13 @@ export function createChannelPickers({
|
|
|
61
61
|
setContextPanel(null);
|
|
62
62
|
setChannelPrompt({
|
|
63
63
|
...prompt,
|
|
64
|
-
afterSave: () => openChannelTypeActionsPicker(
|
|
64
|
+
afterSave: () => openChannelTypeActionsPicker(provider, options),
|
|
65
65
|
});
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
setPicker({
|
|
69
69
|
title: isTelegram ? 'Telegram' : 'Discord',
|
|
70
|
-
description:
|
|
70
|
+
description: activeProvider === provider
|
|
71
71
|
? 'Active channel type · token and main target'
|
|
72
72
|
: 'Token and main target settings',
|
|
73
73
|
help: '↑/↓ Select · Enter Edit · Esc Back',
|
|
@@ -108,7 +108,7 @@ export function createChannelPickers({
|
|
|
108
108
|
if (item._action === 'main-target') {
|
|
109
109
|
openChannelPrompt({
|
|
110
110
|
kind: 'channel-add',
|
|
111
|
-
|
|
111
|
+
provider,
|
|
112
112
|
label: isTelegram ? 'Main chat' : 'Main channel',
|
|
113
113
|
hint: isTelegram
|
|
114
114
|
? 'Paste the Telegram chat ID.'
|
|
@@ -140,19 +140,19 @@ export function createChannelPickers({
|
|
|
140
140
|
store.pushNotice(`channels failed: ${e?.message || e}`, 'error');
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
|
-
const
|
|
143
|
+
const activeProvider = setup.provider || 'discord';
|
|
144
144
|
const mainEntry = setup.channel || {};
|
|
145
|
-
const typeDescription = (
|
|
146
|
-
const selected =
|
|
147
|
-
const hasToken =
|
|
145
|
+
const typeDescription = (provider) => {
|
|
146
|
+
const selected = activeProvider === provider;
|
|
147
|
+
const hasToken = provider === 'telegram'
|
|
148
148
|
? setup.telegram?.authenticated === true
|
|
149
149
|
: setup.discord?.authenticated === true;
|
|
150
|
-
const hasTarget =
|
|
151
|
-
? Boolean(mainEntry?.telegramChatId || (
|
|
152
|
-
: Boolean(mainEntry?.discordChannelId || (
|
|
150
|
+
const hasTarget = provider === 'telegram'
|
|
151
|
+
? Boolean(mainEntry?.telegramChatId || (activeProvider === 'telegram' && mainEntry?.channelId))
|
|
152
|
+
: Boolean(mainEntry?.discordChannelId || (activeProvider === 'discord' && mainEntry?.channelId));
|
|
153
153
|
const needs = [
|
|
154
154
|
...(hasToken ? [] : ['token']),
|
|
155
|
-
...(hasTarget ? [] : [
|
|
155
|
+
...(hasTarget ? [] : [provider === 'telegram' ? 'chat ID' : 'channel ID']),
|
|
156
156
|
];
|
|
157
157
|
return [
|
|
158
158
|
...(selected ? ['Selected'] : []),
|
|
@@ -161,7 +161,7 @@ export function createChannelPickers({
|
|
|
161
161
|
};
|
|
162
162
|
setPicker({
|
|
163
163
|
title: 'Channel Type Settings',
|
|
164
|
-
description: 'Choose a type. Selected is the active
|
|
164
|
+
description: 'Choose a type. Selected is the active provider; Ready means token and main target are set.',
|
|
165
165
|
help: '↑/↓ Select · Enter Open · Esc Back',
|
|
166
166
|
indexMode: 'always',
|
|
167
167
|
labelWidth: 18,
|
|
@@ -170,20 +170,20 @@ export function createChannelPickers({
|
|
|
170
170
|
value: 'discord',
|
|
171
171
|
label: 'Discord',
|
|
172
172
|
description: typeDescription('discord'),
|
|
173
|
-
|
|
173
|
+
_provider: 'discord',
|
|
174
174
|
},
|
|
175
175
|
{
|
|
176
176
|
value: 'telegram',
|
|
177
177
|
label: 'Telegram',
|
|
178
178
|
description: typeDescription('telegram'),
|
|
179
|
-
|
|
179
|
+
_provider: 'telegram',
|
|
180
180
|
},
|
|
181
181
|
],
|
|
182
182
|
onSelect: (value, item) => {
|
|
183
|
-
const
|
|
184
|
-
if (!
|
|
183
|
+
const provider = item?._provider || (value === 'telegram' ? 'telegram' : value === 'discord' ? 'discord' : null);
|
|
184
|
+
if (!provider) return;
|
|
185
185
|
setPicker(null);
|
|
186
|
-
openChannelTypeActionsPicker(
|
|
186
|
+
openChannelTypeActionsPicker(provider, {
|
|
187
187
|
returnTo: () => openChannelSettingTypePicker(options),
|
|
188
188
|
});
|
|
189
189
|
},
|
|
@@ -218,8 +218,8 @@ export function createChannelPickers({
|
|
|
218
218
|
// Schedules/webhooks management is desktop-only (user decision): the TUI
|
|
219
219
|
// no longer carries their pickers or the webhook-endpoint info page.
|
|
220
220
|
const worker = await store.getChannelWorkerStatus?.();
|
|
221
|
-
const
|
|
222
|
-
const
|
|
221
|
+
const activeProvider = setup.provider === 'telegram' ? 'telegram' : 'discord';
|
|
222
|
+
const providerLabel = activeProvider === 'telegram' ? 'Telegram' : 'Discord';
|
|
223
223
|
const remoteEnabled = (await store.isRemoteEnabled?.()) === true;
|
|
224
224
|
const boolLabel = (enabled) => enabled ? 'On' : 'Off';
|
|
225
225
|
// Onboarding Step 5 reuses this root picker with a ConfirmBar. Reopens after
|
|
@@ -237,25 +237,25 @@ export function createChannelPickers({
|
|
|
237
237
|
.catch((e) => store.pushNotice(`Remote toggle failed: ${e?.message || e}`, 'error'))
|
|
238
238
|
.finally(() => reopenRoot({ highlightValue }));
|
|
239
239
|
};
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const currentIndex = Math.max(0,
|
|
243
|
-
const chosen =
|
|
244
|
-
if (chosen ===
|
|
245
|
-
reopenRoot({ highlightValue,
|
|
240
|
+
const cycleChannelProvider = (direction = 1, highlightValue = 'channel-provider') => {
|
|
241
|
+
const providers = ['discord', 'telegram'];
|
|
242
|
+
const currentIndex = Math.max(0, providers.indexOf(activeProvider));
|
|
243
|
+
const chosen = providers[(currentIndex + direction + providers.length) % providers.length];
|
|
244
|
+
if (chosen === activeProvider) {
|
|
245
|
+
reopenRoot({ highlightValue, providerOverride: activeProvider });
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
248
|
try {
|
|
249
|
-
store.
|
|
249
|
+
store.setChannelProvider(chosen);
|
|
250
250
|
const label = chosen === 'telegram' ? 'Telegram' : 'Discord';
|
|
251
251
|
const restartHint = (remoteEnabled || worker?.running)
|
|
252
252
|
? `Channel set to ${label}. Restart remote to apply.`
|
|
253
253
|
: `Channel set to ${label}.`;
|
|
254
254
|
store.pushNotice(restartHint, 'info');
|
|
255
255
|
} catch (e) {
|
|
256
|
-
store.pushNotice(`channel
|
|
256
|
+
store.pushNotice(`channel provider failed: ${e?.message || e}`, 'error');
|
|
257
257
|
}
|
|
258
|
-
reopenRoot({ highlightValue,
|
|
258
|
+
reopenRoot({ highlightValue, providerOverride: chosen });
|
|
259
259
|
};
|
|
260
260
|
// Voice toggle: enabling installs the managed whisper/ffmpeg runtime (first
|
|
261
261
|
// time only) then flips voice.enabled so the channels pipeline transcribes
|
|
@@ -285,11 +285,11 @@ export function createChannelPickers({
|
|
|
285
285
|
_action: 'remote-runtime',
|
|
286
286
|
},
|
|
287
287
|
{
|
|
288
|
-
value: 'channel-
|
|
288
|
+
value: 'channel-provider',
|
|
289
289
|
label: 'Channel',
|
|
290
|
-
meta:
|
|
290
|
+
meta: providerLabel,
|
|
291
291
|
description: 'Select Discord or Telegram',
|
|
292
|
-
_action: 'channel-
|
|
292
|
+
_action: 'channel-provider',
|
|
293
293
|
},
|
|
294
294
|
{
|
|
295
295
|
value: 'channel-setting',
|
|
@@ -329,18 +329,18 @@ export function createChannelPickers({
|
|
|
329
329
|
indexMode: 'always',
|
|
330
330
|
labelWidth: 18,
|
|
331
331
|
metaWidth: 12,
|
|
332
|
-
pickerKey: `channels:${
|
|
332
|
+
pickerKey: `channels:${activeProvider}:${remoteEnabled ? 'on' : 'off'}:${options.highlightValue || 'root'}`,
|
|
333
333
|
initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
|
|
334
334
|
items,
|
|
335
335
|
confirmBar: options.confirmBar || null,
|
|
336
336
|
onLeft: options.confirmBar ? undefined : (item) => {
|
|
337
337
|
if (item?._action === 'remote-runtime') applyRemoteRuntime(item.value);
|
|
338
|
-
else if (item?._action === 'channel-
|
|
338
|
+
else if (item?._action === 'channel-provider') cycleChannelProvider(-1, item.value);
|
|
339
339
|
else if (item?._action === 'voice') applyVoice(item.value);
|
|
340
340
|
},
|
|
341
341
|
onRight: options.confirmBar ? undefined : (item) => {
|
|
342
342
|
if (item?._action === 'remote-runtime') applyRemoteRuntime(item.value);
|
|
343
|
-
else if (item?._action === 'channel-
|
|
343
|
+
else if (item?._action === 'channel-provider') cycleChannelProvider(1, item.value);
|
|
344
344
|
else if (item?._action === 'voice') applyVoice(item.value);
|
|
345
345
|
},
|
|
346
346
|
onSelect: (_value, item) => {
|
|
@@ -349,8 +349,8 @@ export function createChannelPickers({
|
|
|
349
349
|
applyRemoteRuntime(item.value);
|
|
350
350
|
return;
|
|
351
351
|
}
|
|
352
|
-
if (item._action === 'channel-
|
|
353
|
-
|
|
352
|
+
if (item._action === 'channel-provider') {
|
|
353
|
+
cycleChannelProvider(1, item.value);
|
|
354
354
|
return;
|
|
355
355
|
}
|
|
356
356
|
if (item._action === 'voice') {
|
|
@@ -39,20 +39,20 @@ export function createCoreMemoryPicker({
|
|
|
39
39
|
});
|
|
40
40
|
void store.memoryControl?.({ action: 'core', op: 'list', project_id: '*' }, { silent: true })
|
|
41
41
|
.then(async (result) => {
|
|
42
|
-
//
|
|
42
|
+
// The recap toggle lives here too so /memory is a one-stop surface:
|
|
43
43
|
// toggle row + add row + curated entries.
|
|
44
|
-
const
|
|
45
|
-
const
|
|
44
|
+
const recap = (await store.getRecapSettings?.()) || { enabled: true };
|
|
45
|
+
const recapOn = recap.enabled !== false;
|
|
46
46
|
const coreRows = parseMemoryCoreRows(result);
|
|
47
47
|
const rows = [
|
|
48
48
|
{
|
|
49
|
-
value: '
|
|
50
|
-
label: '
|
|
51
|
-
meta:
|
|
52
|
-
description:
|
|
53
|
-
? 'Background
|
|
54
|
-
: 'Background memory
|
|
55
|
-
_action: 'toggle-
|
|
49
|
+
value: 'recap-toggle',
|
|
50
|
+
label: 'Recap',
|
|
51
|
+
meta: recapOn ? 'On' : 'Off',
|
|
52
|
+
description: recapOn
|
|
53
|
+
? 'Background session recaps on'
|
|
54
|
+
: 'Background recaps off; core memory remains available',
|
|
55
|
+
_action: 'toggle-recap',
|
|
56
56
|
},
|
|
57
57
|
{ value: 'core-add', label: 'Add Memory', description: 'store a new curated memory sentence', _action: 'add-core' },
|
|
58
58
|
{
|
|
@@ -76,17 +76,17 @@ export function createCoreMemoryPicker({
|
|
|
76
76
|
labelWidth: 12,
|
|
77
77
|
footer: (item) => (item && item._action === 'core-entry' ? (item._summary || item._element || '') : ''),
|
|
78
78
|
onSelect: (_value, item) => {
|
|
79
|
-
if (item?._action === 'toggle-
|
|
79
|
+
if (item?._action === 'toggle-recap') toggleRecapEnabled();
|
|
80
80
|
else if (item?._action === 'add-core') beginAddCoreMemory();
|
|
81
81
|
else if (item?._action === 'core-list') openCoreMemoryListPicker(item._rows);
|
|
82
82
|
else if (item?._line) store.pushNotice(item._line, 'info');
|
|
83
83
|
},
|
|
84
84
|
// Toggle rows also respond to ←/→ (matches settings/channels UX).
|
|
85
85
|
onLeft: (item) => {
|
|
86
|
-
if (item?._action === 'toggle-
|
|
86
|
+
if (item?._action === 'toggle-recap') toggleRecapEnabled();
|
|
87
87
|
},
|
|
88
88
|
onRight: (item) => {
|
|
89
|
-
if (item?._action === 'toggle-
|
|
89
|
+
if (item?._action === 'toggle-recap') toggleRecapEnabled();
|
|
90
90
|
},
|
|
91
91
|
onCancel: closeMemoryCorePicker,
|
|
92
92
|
});
|
|
@@ -137,14 +137,14 @@ export function createCoreMemoryPicker({
|
|
|
137
137
|
});
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
void Promise.resolve(store.
|
|
140
|
+
const toggleRecapEnabled = async () => {
|
|
141
|
+
const recap = (await store.getRecapSettings?.()) || { enabled: true };
|
|
142
|
+
void Promise.resolve(store.setRecapEnabled?.(!(recap.enabled !== false)))
|
|
143
143
|
.then((next) => {
|
|
144
|
-
if (!next) store.pushNotice('
|
|
145
|
-
else store.pushNotice(`
|
|
144
|
+
if (!next) store.pushNotice('recap setting is busy', 'warn');
|
|
145
|
+
else store.pushNotice(`Recap ${next.enabled ? 'on' : 'off'}`, 'info');
|
|
146
146
|
})
|
|
147
|
-
.catch((e) => store.pushNotice(`
|
|
147
|
+
.catch((e) => store.pushNotice(`recap toggle failed: ${e?.message || e}`, 'error'))
|
|
148
148
|
.finally(() => openMemoryCorePicker());
|
|
149
149
|
};
|
|
150
150
|
|
package/src/tui/app/doctor.mjs
CHANGED
|
@@ -138,18 +138,12 @@ export async function buildDoctorReport(runtime = {}, getState = () => ({})) {
|
|
|
138
138
|
row(failed.length || conn < conf ? 'warn' : 'ok', detail);
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
// 5. memory:
|
|
142
|
-
//
|
|
141
|
+
// 5. memory recap: background-cycle state. Memory ingest, on-demand recall,
|
|
142
|
+
// and curated core memory are always available.
|
|
143
143
|
await check('memory', async (row) => {
|
|
144
|
-
const
|
|
145
|
-
const enabled =
|
|
146
|
-
|
|
147
|
-
if (mem.backend) detail += ` · backend ${mem.backend}`;
|
|
148
|
-
const health = mem.backendHealth || mem.health;
|
|
149
|
-
if (health != null) {
|
|
150
|
-
detail += ` · ${typeof health === 'string' ? health : (health.ok ? 'healthy' : 'unhealthy')}`;
|
|
151
|
-
}
|
|
152
|
-
row(enabled ? 'ok' : 'warn', detail);
|
|
144
|
+
const recap = runtime.getRecapSettings?.() || {};
|
|
145
|
+
const enabled = recap.enabled !== false;
|
|
146
|
+
row('ok', `recap ${enabled ? 'enabled' : 'disabled'} · core memory available`);
|
|
153
147
|
});
|
|
154
148
|
|
|
155
149
|
// 6. channels: enabled + worker status + configured tokens (names only).
|
|
@@ -139,7 +139,7 @@ export function parseMemoryCandidateRows(text) {
|
|
|
139
139
|
// op:'candidates' returns this exact sentinel (index.mjs ~3158) when the
|
|
140
140
|
// resolved scope has none — treat as an empty list, not an inert text row.
|
|
141
141
|
if (!trimmed || /^core candidates:\s*none$/i.test(trimmed)) return [];
|
|
142
|
-
//
|
|
142
|
+
// Service row shape (index.mjs ~3164), one candidate per line, no group
|
|
143
143
|
// headers:
|
|
144
144
|
// id=<n> project=<COMMON|slug> score=<x.xx|-> <element> — <summary> (<reason>)
|
|
145
145
|
const rowPattern = /^id=(\d+)\s+project=(\S+)\s+score=(\S+)\s+(.+?)\s+—\s+(.+?)\s+\(([^)]*)\)$/;
|
|
@@ -171,7 +171,7 @@ export function parseMemoryCandidateRows(text) {
|
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
//
|
|
174
|
+
// Service "core" operation errors are flattened to plain text by store.memoryControl
|
|
175
175
|
// (isError is dropped -- see engine.mjs memoryControl / toolResponseText in
|
|
176
176
|
// mixdog-session-runtime.mjs). Success text always uses a past-tense verb
|
|
177
177
|
// ("core added/edited/deleted/promoted...", "core candidate dismissed...");
|