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
package/src/tui/dist/index.mjs
CHANGED
|
@@ -27,10 +27,6 @@ var __commonJS = (cb, mod) => function __require2() {
|
|
|
27
27
|
throw mod = 0, e;
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
-
var __export = (target, all) => {
|
|
31
|
-
for (var name in all)
|
|
32
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
33
|
-
};
|
|
34
30
|
var __copyProps = (to, from, except, desc) => {
|
|
35
31
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
36
32
|
for (let key of __getOwnPropNames(from))
|
|
@@ -210,11 +206,11 @@ function cleanPathText(value) {
|
|
|
210
206
|
}
|
|
211
207
|
return text;
|
|
212
208
|
}
|
|
213
|
-
function mimeForPath(
|
|
214
|
-
return IMAGE_MIME[extname(String(
|
|
209
|
+
function mimeForPath(path2) {
|
|
210
|
+
return IMAGE_MIME[extname(String(path2 || "")).toLowerCase()] || null;
|
|
215
211
|
}
|
|
216
212
|
function execFileBuffer(cmd, args, options = {}) {
|
|
217
|
-
return new Promise((
|
|
213
|
+
return new Promise((resolve4) => {
|
|
218
214
|
execFile(cmd, args, {
|
|
219
215
|
windowsHide: true,
|
|
220
216
|
encoding: "buffer",
|
|
@@ -222,7 +218,7 @@ function execFileBuffer(cmd, args, options = {}) {
|
|
|
222
218
|
timeout: 5e3,
|
|
223
219
|
...options
|
|
224
220
|
}, (error, stdout2, stderr) => {
|
|
225
|
-
|
|
221
|
+
resolve4({ ok: !error, code: error?.code ?? 0, stdout: stdout2, stderr, error });
|
|
226
222
|
});
|
|
227
223
|
});
|
|
228
224
|
}
|
|
@@ -419,16 +415,16 @@ var init_paste_attachments = __esm({
|
|
|
419
415
|
// src/runtime/shared/atomic-file.mjs
|
|
420
416
|
import {
|
|
421
417
|
closeSync,
|
|
422
|
-
existsSync as
|
|
418
|
+
existsSync as existsSync3,
|
|
423
419
|
fsyncSync,
|
|
424
420
|
linkSync,
|
|
425
|
-
mkdirSync as
|
|
421
|
+
mkdirSync as mkdirSync2,
|
|
426
422
|
openSync,
|
|
427
|
-
readFileSync as
|
|
428
|
-
renameSync
|
|
429
|
-
statSync
|
|
423
|
+
readFileSync as readFileSync2,
|
|
424
|
+
renameSync,
|
|
425
|
+
statSync,
|
|
430
426
|
unlinkSync as unlinkSync2,
|
|
431
|
-
writeFileSync as
|
|
427
|
+
writeFileSync as writeFileSync2
|
|
432
428
|
} from "fs";
|
|
433
429
|
import {
|
|
434
430
|
link as linkAsync,
|
|
@@ -439,11 +435,41 @@ import {
|
|
|
439
435
|
unlink as unlinkAsync,
|
|
440
436
|
writeFile as writeFileAsync
|
|
441
437
|
} from "node:fs/promises";
|
|
442
|
-
import { dirname as
|
|
438
|
+
import { dirname as dirname3, basename as basename3, join as join3 } from "path";
|
|
443
439
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
444
440
|
import { execFile as execFile2, execFileSync } from "child_process";
|
|
445
441
|
import { promisify } from "util";
|
|
446
442
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
443
|
+
function _drainAdaptiveFileIo() {
|
|
444
|
+
while (_ioActive < _ioLimit && _ioQueue.length > 0) {
|
|
445
|
+
const item = _ioQueue.shift();
|
|
446
|
+
_ioActive += 1;
|
|
447
|
+
const startedAt = Date.now();
|
|
448
|
+
Promise.resolve().then(item.run).then(item.resolve, item.reject).finally(() => {
|
|
449
|
+
const elapsed = Date.now() - startedAt;
|
|
450
|
+
_ioActive = Math.max(0, _ioActive - 1);
|
|
451
|
+
if (elapsed >= 250) {
|
|
452
|
+
_ioLimit = Math.max(1, Math.ceil(_ioLimit / 2));
|
|
453
|
+
_ioFastStreak = 0;
|
|
454
|
+
} else if (elapsed <= 40 && _ioLimit < _ioCeiling) {
|
|
455
|
+
_ioFastStreak += 1;
|
|
456
|
+
if (_ioFastStreak >= _ioLimit * 4) {
|
|
457
|
+
_ioLimit += 1;
|
|
458
|
+
_ioFastStreak = 0;
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
_ioFastStreak = 0;
|
|
462
|
+
}
|
|
463
|
+
_drainAdaptiveFileIo();
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function _runAdaptiveFileIo(run) {
|
|
468
|
+
return new Promise((resolve4, reject) => {
|
|
469
|
+
_ioQueue.push({ run, resolve: resolve4, reject });
|
|
470
|
+
_drainAdaptiveFileIo();
|
|
471
|
+
});
|
|
472
|
+
}
|
|
447
473
|
function sleepSync(ms) {
|
|
448
474
|
try {
|
|
449
475
|
const buf = new SharedArrayBuffer(4);
|
|
@@ -456,7 +482,7 @@ function sleepSync(ms) {
|
|
|
456
482
|
}
|
|
457
483
|
function _describeLockHolder(lockPath) {
|
|
458
484
|
try {
|
|
459
|
-
const st =
|
|
485
|
+
const st = statSync(lockPath);
|
|
460
486
|
const owner = _readLockOwner(lockPath);
|
|
461
487
|
const ageMs = Math.max(0, Math.round(Date.now() - st.mtimeMs));
|
|
462
488
|
const live = owner.pid === null ? "unknown" : _ownerIsLive(owner) ? "live" : "dead";
|
|
@@ -466,12 +492,28 @@ function _describeLockHolder(lockPath) {
|
|
|
466
492
|
return "holder unknown (lock file unreadable/absent)";
|
|
467
493
|
}
|
|
468
494
|
}
|
|
495
|
+
function _reportLockWait(lockPath, waitedMs, mode) {
|
|
496
|
+
if (waitedMs < LOCK_WAIT_WARN_MS) return;
|
|
497
|
+
const now = Date.now();
|
|
498
|
+
if (now - (_lockWaitWarnedAt.get(lockPath) || 0) < LOCK_WAIT_WARN_INTERVAL_MS) return;
|
|
499
|
+
_lockWaitWarnedAt.set(lockPath, now);
|
|
500
|
+
if (_lockWaitWarnedAt.size > 64) {
|
|
501
|
+
for (const [path2, at] of _lockWaitWarnedAt) {
|
|
502
|
+
if (now - at >= LOCK_WAIT_WARN_INTERVAL_MS * 6) _lockWaitWarnedAt.delete(path2);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
process.stderr.write(`[atomic-file] ${mode} lock wait ${waitedMs}ms: ${lockPath}
|
|
507
|
+
`);
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
}
|
|
469
511
|
function renameWithRetrySync(src, dst, opts = {}) {
|
|
470
512
|
const backoffs = Array.isArray(opts.backoffs) && opts.backoffs.length > 0 ? opts.backoffs : DEFAULT_BACKOFFS_MS;
|
|
471
513
|
let lastErr = null;
|
|
472
514
|
for (let attempt = 0; attempt <= backoffs.length; attempt++) {
|
|
473
515
|
try {
|
|
474
|
-
|
|
516
|
+
renameSync(src, dst);
|
|
475
517
|
return true;
|
|
476
518
|
} catch (err) {
|
|
477
519
|
lastErr = err;
|
|
@@ -500,7 +542,7 @@ async function renameWithRetry(src, dst, opts = {}) {
|
|
|
500
542
|
}
|
|
501
543
|
function _readLockOwner(lockPath) {
|
|
502
544
|
try {
|
|
503
|
-
const raw =
|
|
545
|
+
const raw = readFileSync2(lockPath, "utf8");
|
|
504
546
|
const parts = String(raw).trim().split(/\s+/);
|
|
505
547
|
const pid = Number.parseInt(parts[0], 10);
|
|
506
548
|
return {
|
|
@@ -529,7 +571,7 @@ function _lockOwnedBySelf(lockPath) {
|
|
|
529
571
|
function _tryReclaimStaleLock(lockPath, staleMs) {
|
|
530
572
|
let initial;
|
|
531
573
|
try {
|
|
532
|
-
initial =
|
|
574
|
+
initial = statSync(lockPath);
|
|
533
575
|
} catch {
|
|
534
576
|
return false;
|
|
535
577
|
}
|
|
@@ -542,7 +584,7 @@ function _tryReclaimStaleLock(lockPath, staleMs) {
|
|
|
542
584
|
try {
|
|
543
585
|
let cur;
|
|
544
586
|
try {
|
|
545
|
-
cur =
|
|
587
|
+
cur = statSync(lockPath);
|
|
546
588
|
} catch {
|
|
547
589
|
return false;
|
|
548
590
|
}
|
|
@@ -569,7 +611,7 @@ function _tryAcquireReclaimGuard(lockPath, staleMs) {
|
|
|
569
611
|
`;
|
|
570
612
|
const stagedPath = `${guardPath}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
|
|
571
613
|
try {
|
|
572
|
-
|
|
614
|
+
writeFileSync2(stagedPath, token, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
573
615
|
linkSync(stagedPath, guardPath);
|
|
574
616
|
unlinkSync2(stagedPath);
|
|
575
617
|
return { guardPath };
|
|
@@ -591,8 +633,8 @@ function _releaseReclaimGuard(reclaim) {
|
|
|
591
633
|
}
|
|
592
634
|
}
|
|
593
635
|
function _asyncSleep(ms) {
|
|
594
|
-
return new Promise((
|
|
595
|
-
setTimeout(
|
|
636
|
+
return new Promise((resolve4) => {
|
|
637
|
+
setTimeout(resolve4, Math.max(1, Number(ms) || 1));
|
|
596
638
|
});
|
|
597
639
|
}
|
|
598
640
|
async function withFileLock(lockPath, fn, opts = {}) {
|
|
@@ -618,7 +660,8 @@ async function _withOsFileLock(lockPath, fn, opts = {}) {
|
|
|
618
660
|
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : DEFAULT_LOCK_TIMEOUT_MS;
|
|
619
661
|
const staleMs = Number.isFinite(opts.staleMs) ? opts.staleMs : 3e4;
|
|
620
662
|
const deadline = Date.now() + timeoutMs;
|
|
621
|
-
|
|
663
|
+
mkdirSync2(dirname3(lockPath), { recursive: true });
|
|
664
|
+
const waitStartedAt = Date.now();
|
|
622
665
|
let attempt = 0;
|
|
623
666
|
let lastErr = null;
|
|
624
667
|
while (true) {
|
|
@@ -646,10 +689,11 @@ async function _withOsFileLock(lockPath, fn, opts = {}) {
|
|
|
646
689
|
continue;
|
|
647
690
|
}
|
|
648
691
|
try {
|
|
649
|
-
|
|
692
|
+
writeFileSync2(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}
|
|
650
693
|
`, "utf8");
|
|
651
694
|
} catch {
|
|
652
695
|
}
|
|
696
|
+
_reportLockWait(lockPath, Date.now() - waitStartedAt, "async");
|
|
653
697
|
_osHeldPaths.add(lockPath);
|
|
654
698
|
try {
|
|
655
699
|
if (opts.secret === true) await _enforceOwnerOnlyAclWin32Async(lockPath);
|
|
@@ -677,8 +721,8 @@ function _icaclsPrincipal(principal) {
|
|
|
677
721
|
async function _resolveCurrentUserPrincipalAsync() {
|
|
678
722
|
const systemRoot = process.env.SystemRoot || process.env.windir;
|
|
679
723
|
if (systemRoot) {
|
|
680
|
-
const whoami =
|
|
681
|
-
if (
|
|
724
|
+
const whoami = join3(systemRoot, "System32", "whoami.exe");
|
|
725
|
+
if (existsSync3(whoami)) {
|
|
682
726
|
try {
|
|
683
727
|
const { stdout: stdout2 } = await _execFileAsync(whoami, ["/user", "/fo", "csv", "/nh"], {
|
|
684
728
|
encoding: "utf8",
|
|
@@ -702,8 +746,8 @@ async function _enforceOwnerOnlyAclWin32Async(targetPath) {
|
|
|
702
746
|
err.code = "EACLNOROOT";
|
|
703
747
|
throw err;
|
|
704
748
|
}
|
|
705
|
-
const icacls =
|
|
706
|
-
if (!
|
|
749
|
+
const icacls = join3(systemRoot, "System32", "icacls.exe");
|
|
750
|
+
if (!existsSync3(icacls)) {
|
|
707
751
|
const err = new Error(`icacls.exe not found at ${icacls}; refusing to leave secret world-readable`);
|
|
708
752
|
err.code = "EACLNOICACLS";
|
|
709
753
|
throw err;
|
|
@@ -722,9 +766,9 @@ async function _enforceOwnerOnlyAclWin32Async(targetPath) {
|
|
|
722
766
|
}
|
|
723
767
|
async function writeFileAtomicAsync(filePath, data, opts = {}) {
|
|
724
768
|
const run = async () => {
|
|
725
|
-
const dir =
|
|
769
|
+
const dir = dirname3(filePath);
|
|
726
770
|
await mkdirAsync(dir, { recursive: true });
|
|
727
|
-
const tmp =
|
|
771
|
+
const tmp = join3(dir, `.${basename3(filePath)}.${randomBytes2(12).toString("hex")}.tmp`);
|
|
728
772
|
try {
|
|
729
773
|
const writeOpts = { encoding: opts.encoding || "utf8", flag: "wx", mode: opts.mode !== void 0 ? opts.mode : 384 };
|
|
730
774
|
await writeFileAsync(tmp, data, writeOpts);
|
|
@@ -785,18 +829,20 @@ async function writeFileAtomicAsync(filePath, data, opts = {}) {
|
|
|
785
829
|
throw err;
|
|
786
830
|
}
|
|
787
831
|
};
|
|
788
|
-
|
|
789
|
-
return withFileLock(`${filePath}.lock`, run, opts);
|
|
790
|
-
}
|
|
791
|
-
return run();
|
|
832
|
+
return _runAdaptiveFileIo(() => opts.lock === true ? withFileLock(`${filePath}.lock`, run, opts) : run());
|
|
792
833
|
}
|
|
793
834
|
function writeJsonAtomicAsync(filePath, value, opts = {}) {
|
|
794
835
|
return writeFileAtomicAsync(filePath, JSON.stringify(value, null, opts.compact ? 0 : 2) + "\n", opts);
|
|
795
836
|
}
|
|
796
|
-
var _execFileAsync, RETRY_CODES, LOCK_WAIT_CODES, DEFAULT_BACKOFFS_MS, _configuredLockTimeoutMs, DEFAULT_LOCK_TIMEOUT_MS, _OWNER_TOKEN, _osHeldPaths, _lockQueues, _heldLockPaths, _cachedUserSid;
|
|
837
|
+
var _execFileAsync, _ioCeiling, _ioLimit, _ioActive, _ioFastStreak, _ioQueue, RETRY_CODES, LOCK_WAIT_CODES, DEFAULT_BACKOFFS_MS, _configuredLockTimeoutMs, DEFAULT_LOCK_TIMEOUT_MS, _OWNER_TOKEN, _osHeldPaths, LOCK_WAIT_WARN_MS, LOCK_WAIT_WARN_INTERVAL_MS, _lockWaitWarnedAt, _lockQueues, _heldLockPaths, _cachedUserSid;
|
|
797
838
|
var init_atomic_file = __esm({
|
|
798
839
|
"src/runtime/shared/atomic-file.mjs"() {
|
|
799
840
|
_execFileAsync = promisify(execFile2);
|
|
841
|
+
_ioCeiling = Math.max(1, Math.floor(Number(process.env.MIXDOG_FILE_IO_MAX_CONCURRENCY) || 16));
|
|
842
|
+
_ioLimit = Math.min(4, _ioCeiling);
|
|
843
|
+
_ioActive = 0;
|
|
844
|
+
_ioFastStreak = 0;
|
|
845
|
+
_ioQueue = [];
|
|
800
846
|
RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "EEXIST"]);
|
|
801
847
|
LOCK_WAIT_CODES = /* @__PURE__ */ new Set(["EEXIST", "EPERM", "EACCES", "EBUSY"]);
|
|
802
848
|
DEFAULT_BACKOFFS_MS = Object.freeze([25, 50, 100, 200, 400, 800, 1200, 1600]);
|
|
@@ -804,6 +850,9 @@ var init_atomic_file = __esm({
|
|
|
804
850
|
DEFAULT_LOCK_TIMEOUT_MS = Number.isFinite(_configuredLockTimeoutMs) && _configuredLockTimeoutMs >= 0 ? _configuredLockTimeoutMs : 2e3;
|
|
805
851
|
_OWNER_TOKEN = randomBytes2(12).toString("hex");
|
|
806
852
|
_osHeldPaths = /* @__PURE__ */ new Set();
|
|
853
|
+
LOCK_WAIT_WARN_MS = 500;
|
|
854
|
+
LOCK_WAIT_WARN_INTERVAL_MS = 1e4;
|
|
855
|
+
_lockWaitWarnedAt = /* @__PURE__ */ new Map();
|
|
807
856
|
_lockQueues = /* @__PURE__ */ new Map();
|
|
808
857
|
_heldLockPaths = new AsyncLocalStorage();
|
|
809
858
|
_cachedUserSid = null;
|
|
@@ -813,13 +862,13 @@ var init_atomic_file = __esm({
|
|
|
813
862
|
// src/runtime/shared/user-data-guard.mjs
|
|
814
863
|
import {
|
|
815
864
|
copyFileSync,
|
|
816
|
-
existsSync as
|
|
817
|
-
mkdirSync as
|
|
865
|
+
existsSync as existsSync4,
|
|
866
|
+
mkdirSync as mkdirSync3,
|
|
818
867
|
readdirSync,
|
|
819
|
-
readFileSync as
|
|
868
|
+
readFileSync as readFileSync3,
|
|
820
869
|
rmSync,
|
|
821
|
-
statSync as
|
|
822
|
-
writeFileSync as
|
|
870
|
+
statSync as statSync2,
|
|
871
|
+
writeFileSync as writeFileSync3
|
|
823
872
|
} from "fs";
|
|
824
873
|
import {
|
|
825
874
|
copyFile as copyFileP,
|
|
@@ -828,14 +877,14 @@ import {
|
|
|
828
877
|
rm as rmP,
|
|
829
878
|
stat as statP
|
|
830
879
|
} from "fs/promises";
|
|
831
|
-
import { dirname as
|
|
832
|
-
import { homedir as
|
|
880
|
+
import { dirname as dirname4, join as join4, resolve as resolve2 } from "path";
|
|
881
|
+
import { homedir as homedir2 } from "os";
|
|
833
882
|
import { createHash } from "crypto";
|
|
834
883
|
function mixdogConfigBaseDir() {
|
|
835
|
-
return process.env.MIXDOG_CONFIG_DIR ||
|
|
884
|
+
return process.env.MIXDOG_CONFIG_DIR || join4(homedir2(), ".mixdog");
|
|
836
885
|
}
|
|
837
886
|
function getBackupRoot() {
|
|
838
|
-
return process.env.MIXDOG_USER_DATA_BACKUP_ROOT ||
|
|
887
|
+
return process.env.MIXDOG_USER_DATA_BACKUP_ROOT || join4(mixdogConfigBaseDir(), "backups", "user-data");
|
|
839
888
|
}
|
|
840
889
|
function stamp() {
|
|
841
890
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -845,13 +894,13 @@ function safeReason(reason) {
|
|
|
845
894
|
}
|
|
846
895
|
function initMarkerPath(dataDir) {
|
|
847
896
|
const id = createHash("sha256").update(String(dataDir || "unknown")).digest("hex").slice(0, 16);
|
|
848
|
-
return
|
|
897
|
+
return join4(getBackupRoot(), `.initialized-${id}.json`);
|
|
849
898
|
}
|
|
850
899
|
function isPlainObject(value) {
|
|
851
900
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
852
901
|
}
|
|
853
902
|
function hasUserDataInitMarker(dataDir) {
|
|
854
|
-
return
|
|
903
|
+
return existsSync4(initMarkerPath(dataDir));
|
|
855
904
|
}
|
|
856
905
|
function isStructurallyCompleteMixdogConfigBackup(parsed) {
|
|
857
906
|
if (!isPlainObject(parsed)) return false;
|
|
@@ -868,10 +917,10 @@ function loadLatestMixdogConfigFromBackup(_dataDir) {
|
|
|
868
917
|
return null;
|
|
869
918
|
}
|
|
870
919
|
for (const name of entries) {
|
|
871
|
-
const cfgPath =
|
|
872
|
-
if (!
|
|
920
|
+
const cfgPath = join4(root, name, "mixdog-config.json");
|
|
921
|
+
if (!existsSync4(cfgPath)) continue;
|
|
873
922
|
try {
|
|
874
|
-
const parsed = JSON.parse(
|
|
923
|
+
const parsed = JSON.parse(readFileSync3(cfgPath, "utf8"));
|
|
875
924
|
if (isStructurallyCompleteMixdogConfigBackup(parsed)) return parsed;
|
|
876
925
|
} catch {
|
|
877
926
|
continue;
|
|
@@ -881,8 +930,8 @@ function loadLatestMixdogConfigFromBackup(_dataDir) {
|
|
|
881
930
|
}
|
|
882
931
|
function markUserDataInitialized(dataDir) {
|
|
883
932
|
try {
|
|
884
|
-
|
|
885
|
-
|
|
933
|
+
mkdirSync3(getBackupRoot(), { recursive: true });
|
|
934
|
+
writeFileSync3(initMarkerPath(dataDir), JSON.stringify({
|
|
886
935
|
dataDir,
|
|
887
936
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
888
937
|
}, null, 2) + "\n", "utf8");
|
|
@@ -893,12 +942,12 @@ async function copyTreeAsync(src, dst, copied) {
|
|
|
893
942
|
const st = await statP(src);
|
|
894
943
|
if (st.isDirectory()) {
|
|
895
944
|
for (const name of await readdirP(src)) {
|
|
896
|
-
await copyTreeAsync(
|
|
945
|
+
await copyTreeAsync(join4(src, name), join4(dst, name), copied);
|
|
897
946
|
}
|
|
898
947
|
return;
|
|
899
948
|
}
|
|
900
949
|
if (!st.isFile()) return;
|
|
901
|
-
await mkdirP(
|
|
950
|
+
await mkdirP(dirname4(dst), { recursive: true });
|
|
902
951
|
await copyFileP(src, dst);
|
|
903
952
|
copied.push(dst);
|
|
904
953
|
}
|
|
@@ -911,7 +960,7 @@ async function pruneBackupsAsync(keep = 40) {
|
|
|
911
960
|
}
|
|
912
961
|
for (const name of entries.slice(keep)) {
|
|
913
962
|
try {
|
|
914
|
-
await rmP(
|
|
963
|
+
await rmP(join4(getBackupRoot(), name), { recursive: true, force: true });
|
|
915
964
|
} catch {
|
|
916
965
|
}
|
|
917
966
|
}
|
|
@@ -920,16 +969,16 @@ async function backupUserDataAsync(dataDir, reason = "snapshot") {
|
|
|
920
969
|
if (process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "1" || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "true") {
|
|
921
970
|
return { dir: null, copied: [] };
|
|
922
971
|
}
|
|
923
|
-
if (!dataDir || !
|
|
924
|
-
const backupDir =
|
|
972
|
+
if (!dataDir || !existsSync4(dataDir)) return { dir: null, copied: [] };
|
|
973
|
+
const backupDir = join4(getBackupRoot(), `${stamp()}-${safeReason(reason)}`);
|
|
925
974
|
const copied = [];
|
|
926
975
|
for (const rel of USER_DATA_FILES) {
|
|
927
|
-
const src =
|
|
928
|
-
if (
|
|
976
|
+
const src = join4(dataDir, rel);
|
|
977
|
+
if (existsSync4(src)) await copyTreeAsync(src, join4(backupDir, rel), copied);
|
|
929
978
|
}
|
|
930
979
|
for (const rel of USER_DATA_DIRS) {
|
|
931
|
-
const src =
|
|
932
|
-
if (
|
|
980
|
+
const src = join4(dataDir, rel);
|
|
981
|
+
if (existsSync4(src)) await copyTreeAsync(src, join4(backupDir, rel), copied);
|
|
933
982
|
}
|
|
934
983
|
if (copied.length > 0) {
|
|
935
984
|
markUserDataInitialized(dataDir);
|
|
@@ -980,8 +1029,8 @@ var init_provider_api_key = __esm({
|
|
|
980
1029
|
});
|
|
981
1030
|
|
|
982
1031
|
// src/runtime/shared/config.mjs
|
|
983
|
-
import { readFileSync as
|
|
984
|
-
import { join as
|
|
1032
|
+
import { readFileSync as readFileSync4, statSync as statSync3, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
|
|
1033
|
+
import { join as join5, dirname as dirname5 } from "path";
|
|
985
1034
|
import { createRequire as createRequire3 } from "module";
|
|
986
1035
|
function invalidateConfigReadCache() {
|
|
987
1036
|
_configReadCache = null;
|
|
@@ -993,7 +1042,7 @@ function readConfigRawCached() {
|
|
|
993
1042
|
return _configReadCache.raw;
|
|
994
1043
|
}
|
|
995
1044
|
try {
|
|
996
|
-
const st2 =
|
|
1045
|
+
const st2 = statSync3(CONFIG_PATH);
|
|
997
1046
|
if (st2.mtimeMs === _configReadCache.mtimeMs && st2.size === _configReadCache.size) {
|
|
998
1047
|
_configReadCache.atMs = now;
|
|
999
1048
|
return _configReadCache.raw;
|
|
@@ -1003,7 +1052,7 @@ function readConfigRawCached() {
|
|
|
1003
1052
|
}
|
|
1004
1053
|
let st = null;
|
|
1005
1054
|
try {
|
|
1006
|
-
st =
|
|
1055
|
+
st = statSync3(CONFIG_PATH);
|
|
1007
1056
|
} catch (err) {
|
|
1008
1057
|
if (err.code === "ENOENT") {
|
|
1009
1058
|
_configReadCache = null;
|
|
@@ -1015,7 +1064,7 @@ function readConfigRawCached() {
|
|
|
1015
1064
|
}
|
|
1016
1065
|
let raw;
|
|
1017
1066
|
try {
|
|
1018
|
-
raw =
|
|
1067
|
+
raw = readFileSync4(CONFIG_PATH, "utf8");
|
|
1019
1068
|
} catch (err) {
|
|
1020
1069
|
if (err.code === "ENOENT") {
|
|
1021
1070
|
_configReadCache = null;
|
|
@@ -1031,21 +1080,100 @@ function readConfigRawCached() {
|
|
|
1031
1080
|
function isPlainObject2(value) {
|
|
1032
1081
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1033
1082
|
}
|
|
1083
|
+
function hasOwn(value, key) {
|
|
1084
|
+
return Object.prototype.hasOwnProperty.call(value || {}, key);
|
|
1085
|
+
}
|
|
1086
|
+
function canonicalizeStoredChannelsConfig(value = {}) {
|
|
1087
|
+
const next = isPlainObject2(value) ? { ...value } : {};
|
|
1088
|
+
delete next.promptInjection;
|
|
1089
|
+
delete next.schedules;
|
|
1090
|
+
delete next.nonInteractive;
|
|
1091
|
+
delete next.interactive;
|
|
1092
|
+
const channel = isPlainObject2(next.channel) ? { ...next.channel } : {};
|
|
1093
|
+
next.provider = next.provider === "telegram" || next.backend === "telegram" ? "telegram" : "discord";
|
|
1094
|
+
delete next.backend;
|
|
1095
|
+
delete channel.channelId;
|
|
1096
|
+
next.channel = channel;
|
|
1097
|
+
return next;
|
|
1098
|
+
}
|
|
1099
|
+
function canonicalizeUnifiedConfig(value = {}) {
|
|
1100
|
+
const next = isPlainObject2(value) ? { ...value } : {};
|
|
1101
|
+
const hadAgent = isPlainObject2(next.agent);
|
|
1102
|
+
const agent = hadAgent ? { ...next.agent } : {};
|
|
1103
|
+
if (!String(next.outputStyle || "").trim() && String(agent.outputStyle || "").trim()) {
|
|
1104
|
+
next.outputStyle = agent.outputStyle;
|
|
1105
|
+
}
|
|
1106
|
+
delete agent.outputStyle;
|
|
1107
|
+
for (const key of ["autoClear", "compaction", "shell"]) {
|
|
1108
|
+
if (!isPlainObject2(agent[key]) && isPlainObject2(next[key])) agent[key] = next[key];
|
|
1109
|
+
delete next[key];
|
|
1110
|
+
}
|
|
1111
|
+
if (isPlainObject2(next.mcpServers)) {
|
|
1112
|
+
agent.mcpServers = {
|
|
1113
|
+
...next.mcpServers,
|
|
1114
|
+
...isPlainObject2(agent.mcpServers) ? agent.mcpServers : {}
|
|
1115
|
+
};
|
|
1116
|
+
delete next.mcpServers;
|
|
1117
|
+
}
|
|
1118
|
+
if (!isPlainObject2(agent.searchRoute) && isPlainObject2(agent.search)) {
|
|
1119
|
+
agent.searchRoute = agent.search;
|
|
1120
|
+
}
|
|
1121
|
+
delete agent.search;
|
|
1122
|
+
delete agent.capabilities;
|
|
1123
|
+
const modules = isPlainObject2(agent.modules) ? { ...agent.modules } : {};
|
|
1124
|
+
if (hasOwn(modules, "memory")) {
|
|
1125
|
+
const legacyMemory = modules.memory;
|
|
1126
|
+
const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
|
|
1127
|
+
if (!explicitRecap) {
|
|
1128
|
+
const enabled = isPlainObject2(legacyMemory) ? legacyMemory.enabled !== false : legacyMemory !== false;
|
|
1129
|
+
agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled };
|
|
1130
|
+
}
|
|
1131
|
+
delete modules.memory;
|
|
1132
|
+
agent.modules = modules;
|
|
1133
|
+
}
|
|
1134
|
+
if (isPlainObject2(next.memory)) {
|
|
1135
|
+
const memory = { ...next.memory };
|
|
1136
|
+
const user = isPlainObject2(memory.user) ? { ...memory.user } : {};
|
|
1137
|
+
const legacyTitle = String(user.title || "").trim();
|
|
1138
|
+
if (legacyTitle && !String(agent.profile?.title || "").trim()) {
|
|
1139
|
+
agent.profile = { ...isPlainObject2(agent.profile) ? agent.profile : {}, title: legacyTitle };
|
|
1140
|
+
}
|
|
1141
|
+
delete user.title;
|
|
1142
|
+
if (Object.keys(user).length) memory.user = user;
|
|
1143
|
+
else delete memory.user;
|
|
1144
|
+
if (hasOwn(memory, "enabled")) {
|
|
1145
|
+
const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
|
|
1146
|
+
if (!explicitRecap) {
|
|
1147
|
+
agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled: memory.enabled !== false };
|
|
1148
|
+
}
|
|
1149
|
+
delete memory.enabled;
|
|
1150
|
+
}
|
|
1151
|
+
if (Object.keys(memory).length) next.memory = memory;
|
|
1152
|
+
else delete next.memory;
|
|
1153
|
+
}
|
|
1154
|
+
if (isPlainObject2(next.channels)) {
|
|
1155
|
+
next.channels = canonicalizeStoredChannelsConfig(next.channels);
|
|
1156
|
+
}
|
|
1157
|
+
delete next.search;
|
|
1158
|
+
delete next.capabilities;
|
|
1159
|
+
if (hadAgent || Object.keys(agent).length) next.agent = agent;
|
|
1160
|
+
return next;
|
|
1161
|
+
}
|
|
1034
1162
|
function stripGeneratedMarker(data) {
|
|
1035
1163
|
if (!isPlainObject2(data) || !Object.prototype.hasOwnProperty.call(data, GENERATED_KEY)) return data;
|
|
1036
1164
|
const { [GENERATED_KEY]: _generated, ...rest } = data;
|
|
1037
1165
|
return rest;
|
|
1038
1166
|
}
|
|
1039
|
-
function readJsonFile(
|
|
1167
|
+
function readJsonFile(path2) {
|
|
1040
1168
|
let raw;
|
|
1041
|
-
if (
|
|
1169
|
+
if (path2 === CONFIG_PATH) {
|
|
1042
1170
|
raw = readConfigRawCached();
|
|
1043
1171
|
} else {
|
|
1044
1172
|
try {
|
|
1045
|
-
raw =
|
|
1173
|
+
raw = readFileSync4(path2, "utf8");
|
|
1046
1174
|
} catch (err) {
|
|
1047
1175
|
if (err.code === "ENOENT") return null;
|
|
1048
|
-
process.stderr.write(`[config] readJsonFile: unexpected read error for ${
|
|
1176
|
+
process.stderr.write(`[config] readJsonFile: unexpected read error for ${path2}: ${err.message}
|
|
1049
1177
|
`);
|
|
1050
1178
|
throw err;
|
|
1051
1179
|
}
|
|
@@ -1054,11 +1182,11 @@ function readJsonFile(path3) {
|
|
|
1054
1182
|
try {
|
|
1055
1183
|
return JSON.parse(raw);
|
|
1056
1184
|
} catch (err) {
|
|
1057
|
-
if (
|
|
1185
|
+
if (path2 === CONFIG_PATH) {
|
|
1058
1186
|
invalidateConfigReadCache();
|
|
1059
|
-
const corrupt = `${
|
|
1187
|
+
const corrupt = `${path2}.corrupt-${Date.now()}`;
|
|
1060
1188
|
try {
|
|
1061
|
-
renameWithRetrySync(
|
|
1189
|
+
renameWithRetrySync(path2, corrupt);
|
|
1062
1190
|
} catch {
|
|
1063
1191
|
}
|
|
1064
1192
|
process.stderr.write(`[config] mixdog-config.json is malformed (${err.message}). Renamed to ${corrupt}. Restore it or delete to start fresh.
|
|
@@ -1069,12 +1197,12 @@ function readJsonFile(path3) {
|
|
|
1069
1197
|
}
|
|
1070
1198
|
function readAll() {
|
|
1071
1199
|
const parsed = readJsonFile(CONFIG_PATH);
|
|
1072
|
-
if (parsed != null) return parsed;
|
|
1200
|
+
if (parsed != null) return canonicalizeUnifiedConfig(parsed);
|
|
1073
1201
|
if (hasUserDataInitMarker(DATA_DIR)) {
|
|
1074
1202
|
const restored = loadLatestMixdogConfigFromBackup(DATA_DIR);
|
|
1075
1203
|
if (restored && isPlainObject2(restored)) {
|
|
1076
1204
|
process.stderr.write("[config] read: restored mixdog-config.json from latest user-data backup (missing after init)\n");
|
|
1077
|
-
return restored;
|
|
1205
|
+
return canonicalizeUnifiedConfig(restored);
|
|
1078
1206
|
}
|
|
1079
1207
|
}
|
|
1080
1208
|
return {};
|
|
@@ -1082,7 +1210,7 @@ function readAll() {
|
|
|
1082
1210
|
function quarantineMalformedConfig(parseErr) {
|
|
1083
1211
|
const corrupt = `${CONFIG_PATH}.corrupt-${Date.now()}`;
|
|
1084
1212
|
try {
|
|
1085
|
-
if (
|
|
1213
|
+
if (existsSync5(CONFIG_PATH)) renameWithRetrySync(CONFIG_PATH, corrupt);
|
|
1086
1214
|
} catch {
|
|
1087
1215
|
}
|
|
1088
1216
|
process.stderr.write(
|
|
@@ -1104,7 +1232,7 @@ function restoreAllForRmWOrThrow(reason) {
|
|
|
1104
1232
|
function readAllForRmW() {
|
|
1105
1233
|
let raw;
|
|
1106
1234
|
try {
|
|
1107
|
-
raw =
|
|
1235
|
+
raw = readFileSync4(CONFIG_PATH, "utf8");
|
|
1108
1236
|
} catch (err) {
|
|
1109
1237
|
if (err.code === "ENOENT") {
|
|
1110
1238
|
if (hasUserDataInitMarker(DATA_DIR)) {
|
|
@@ -1128,16 +1256,16 @@ function readAllForRmW() {
|
|
|
1128
1256
|
function readSection(section) {
|
|
1129
1257
|
return stripGeneratedMarker(readAll()[section] ?? null) ?? {};
|
|
1130
1258
|
}
|
|
1131
|
-
async function writeJsonFileAsync(
|
|
1132
|
-
|
|
1133
|
-
if (
|
|
1259
|
+
async function writeJsonFileAsync(path2, data) {
|
|
1260
|
+
mkdirSync4(dirname5(path2), { recursive: true, mode: 448 });
|
|
1261
|
+
if (path2 === CONFIG_PATH) {
|
|
1134
1262
|
try {
|
|
1135
1263
|
await backupUserDataAsync(DATA_DIR, "pre-config-write");
|
|
1136
1264
|
} catch {
|
|
1137
1265
|
}
|
|
1138
1266
|
}
|
|
1139
|
-
await writeJsonAtomicAsync(
|
|
1140
|
-
if (
|
|
1267
|
+
await writeJsonAtomicAsync(path2, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
|
|
1268
|
+
if (path2 === CONFIG_PATH) {
|
|
1141
1269
|
invalidateConfigReadCache();
|
|
1142
1270
|
try {
|
|
1143
1271
|
markUserDataInitialized(DATA_DIR);
|
|
@@ -1150,10 +1278,10 @@ async function writeJsonFileAsync(path3, data) {
|
|
|
1150
1278
|
}
|
|
1151
1279
|
}
|
|
1152
1280
|
async function writeAllAsync(data) {
|
|
1153
|
-
await writeJsonFileAsync(CONFIG_PATH, data);
|
|
1281
|
+
await writeJsonFileAsync(CONFIG_PATH, canonicalizeUnifiedConfig(data));
|
|
1154
1282
|
}
|
|
1155
1283
|
function withConfigLockAsync(fn) {
|
|
1156
|
-
return withFileLock(`${CONFIG_PATH}.lock`, fn
|
|
1284
|
+
return withFileLock(`${CONFIG_PATH}.lock`, fn);
|
|
1157
1285
|
}
|
|
1158
1286
|
function trackConfigWrite(work) {
|
|
1159
1287
|
let promise;
|
|
@@ -1177,7 +1305,7 @@ function updateSectionAsync(section, updater) {
|
|
|
1177
1305
|
await writeAllAsync(all);
|
|
1178
1306
|
}));
|
|
1179
1307
|
}
|
|
1180
|
-
var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites,
|
|
1308
|
+
var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites, SECRET_ACCOUNTS;
|
|
1181
1309
|
var init_config = __esm({
|
|
1182
1310
|
"src/runtime/shared/config.mjs"() {
|
|
1183
1311
|
init_plugin_paths();
|
|
@@ -1193,965 +1321,21 @@ var init_config = __esm({
|
|
|
1193
1321
|
invalidateSecretCache: _invalidateSecretCache
|
|
1194
1322
|
} = _require("../../lib/keychain-cjs.cjs"));
|
|
1195
1323
|
DATA_DIR = resolvePluginData();
|
|
1196
|
-
CONFIG_PATH =
|
|
1324
|
+
CONFIG_PATH = join5(DATA_DIR, "mixdog-config.json");
|
|
1197
1325
|
GENERATED_KEY = "_generated";
|
|
1198
1326
|
_configReadCache = null;
|
|
1199
|
-
CONFIG_READ_TTL_MS = (() => {
|
|
1200
|
-
const n = Number(process.env.MIXDOG_CONFIG_READ_TTL_MS);
|
|
1201
|
-
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
1202
|
-
})();
|
|
1203
|
-
_pendingConfigWrites = /* @__PURE__ */ new Set();
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
});
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
|
|
1215
|
-
// src/standalone/engine-daemon-protocol.mjs
|
|
1216
|
-
var ENGINE_DAEMON_PROTOCOL;
|
|
1217
|
-
var init_engine_daemon_protocol = __esm({
|
|
1218
|
-
"src/standalone/engine-daemon-protocol.mjs"() {
|
|
1219
|
-
ENGINE_DAEMON_PROTOCOL = 3;
|
|
1220
|
-
}
|
|
1221
|
-
});
|
|
1222
|
-
|
|
1223
|
-
// src/standalone/engine-daemon-local-bridge.mjs
|
|
1224
|
-
function engineDaemonLocalBridge() {
|
|
1225
|
-
return globalThis[LOCAL_ENGINE_BRIDGE_KEY] ?? null;
|
|
1226
|
-
}
|
|
1227
|
-
var LOCAL_ENGINE_BRIDGE_KEY;
|
|
1228
|
-
var init_engine_daemon_local_bridge = __esm({
|
|
1229
|
-
"src/standalone/engine-daemon-local-bridge.mjs"() {
|
|
1230
|
-
LOCAL_ENGINE_BRIDGE_KEY = /* @__PURE__ */ Symbol.for("mixdog.engine-daemon.local-bridge.v1");
|
|
1231
|
-
}
|
|
1232
|
-
});
|
|
1233
|
-
|
|
1234
|
-
// src/standalone/engine-daemon-client.mjs
|
|
1235
|
-
var engine_daemon_client_exports = {};
|
|
1236
|
-
__export(engine_daemon_client_exports, {
|
|
1237
|
-
attachEngineDaemon: () => attachEngineDaemon,
|
|
1238
|
-
callDaemonSession: () => callDaemonSession,
|
|
1239
|
-
createRemoteEngineSession: () => createRemoteEngineSession,
|
|
1240
|
-
engineDaemonDiscoveryPath: () => engineDaemonDiscoveryPath,
|
|
1241
|
-
ensureEngineDaemon: () => ensureEngineDaemon,
|
|
1242
|
-
negotiateEngineDaemon: () => negotiateEngineDaemon,
|
|
1243
|
-
probeEngineDaemonHealth: () => probeEngineDaemonHealth,
|
|
1244
|
-
readEngineDaemonDiscovery: () => readEngineDaemonDiscovery,
|
|
1245
|
-
shutdownEngineDaemon: () => shutdownEngineDaemon
|
|
1246
|
-
});
|
|
1247
|
-
import http from "node:http";
|
|
1248
|
-
import os from "node:os";
|
|
1249
|
-
import path2 from "node:path";
|
|
1250
|
-
import { fork } from "node:child_process";
|
|
1251
|
-
import { readFileSync as readFileSync7 } from "node:fs";
|
|
1252
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1253
|
-
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1254
|
-
function runtimeRoot() {
|
|
1255
|
-
return process.env.MIXDOG_RUNTIME_ROOT ? path2.resolve(process.env.MIXDOG_RUNTIME_ROOT) : path2.join(os.tmpdir(), "mixdog");
|
|
1256
|
-
}
|
|
1257
|
-
function engineDaemonDiscoveryPath() {
|
|
1258
|
-
return path2.join(runtimeRoot(), "engine-daemon.json");
|
|
1259
|
-
}
|
|
1260
|
-
function daemonEntry() {
|
|
1261
|
-
return fileURLToPath3(new URL("./backend-daemon.mjs", import.meta.url));
|
|
1262
|
-
}
|
|
1263
|
-
function request({
|
|
1264
|
-
port,
|
|
1265
|
-
token,
|
|
1266
|
-
method = "GET",
|
|
1267
|
-
path: urlPath,
|
|
1268
|
-
body = null,
|
|
1269
|
-
timeoutMs = 12e4,
|
|
1270
|
-
control = false
|
|
1271
|
-
}) {
|
|
1272
|
-
return new Promise((resolve5, reject) => {
|
|
1273
|
-
const payload = body ? JSON.stringify(body) : null;
|
|
1274
|
-
const req = http.request({
|
|
1275
|
-
hostname: "127.0.0.1",
|
|
1276
|
-
port,
|
|
1277
|
-
path: urlPath,
|
|
1278
|
-
method,
|
|
1279
|
-
agent: control ? daemonControlAgent : daemonCallAgent,
|
|
1280
|
-
headers: {
|
|
1281
|
-
"X-Mixdog-Daemon-Token": token,
|
|
1282
|
-
...payload ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) } : {}
|
|
1283
|
-
},
|
|
1284
|
-
timeout: timeoutMs
|
|
1285
|
-
}, (res) => {
|
|
1286
|
-
let data = "";
|
|
1287
|
-
res.setEncoding("utf8");
|
|
1288
|
-
res.on("data", (chunk) => {
|
|
1289
|
-
data += chunk;
|
|
1290
|
-
});
|
|
1291
|
-
res.on("end", () => {
|
|
1292
|
-
let parsed = null;
|
|
1293
|
-
try {
|
|
1294
|
-
parsed = data ? JSON.parse(data) : {};
|
|
1295
|
-
} catch {
|
|
1296
|
-
parsed = null;
|
|
1297
|
-
}
|
|
1298
|
-
if (res.statusCode && res.statusCode >= 400) {
|
|
1299
|
-
const err = new Error(parsed?.error || data || `HTTP ${res.statusCode}`);
|
|
1300
|
-
err.statusCode = res.statusCode;
|
|
1301
|
-
reject(err);
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
resolve5(parsed ?? {});
|
|
1305
|
-
});
|
|
1306
|
-
});
|
|
1307
|
-
req.on("timeout", () => {
|
|
1308
|
-
req.destroy(new Error(`engine daemon timeout: ${method} ${urlPath}`));
|
|
1309
|
-
});
|
|
1310
|
-
req.on("error", reject);
|
|
1311
|
-
if (payload) req.write(payload);
|
|
1312
|
-
req.end();
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
|
-
async function probeEngineDaemonHealth({ port, token, timeoutMs = 800 } = {}) {
|
|
1316
|
-
try {
|
|
1317
|
-
const health = await request({ port, token, path: "/health", timeoutMs, control: true });
|
|
1318
|
-
return health?.status === "ok" ? health : null;
|
|
1319
|
-
} catch {
|
|
1320
|
-
return null;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
function readEngineDaemonDiscovery(discoveryPath = engineDaemonDiscoveryPath()) {
|
|
1324
|
-
try {
|
|
1325
|
-
const parsed = JSON.parse(readFileSync7(discoveryPath, "utf8"));
|
|
1326
|
-
if (!parsed?.port || !parsed?.token || !parsed?.pid) return null;
|
|
1327
|
-
return parsed;
|
|
1328
|
-
} catch {
|
|
1329
|
-
return null;
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
function delay(ms) {
|
|
1333
|
-
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
1334
|
-
}
|
|
1335
|
-
function negotiateEngineDaemon(health) {
|
|
1336
|
-
const protocol = Number(health?.protocol) || 0;
|
|
1337
|
-
if (protocol > ENGINE_DAEMON_PROTOCOL) return "defer";
|
|
1338
|
-
if (protocol < ENGINE_DAEMON_PROTOCOL) return "restart";
|
|
1339
|
-
return "ok";
|
|
1340
|
-
}
|
|
1341
|
-
async function waitForDaemonExit(discovery, timeoutMs = 5e3) {
|
|
1342
|
-
const deadline = Date.now() + timeoutMs;
|
|
1343
|
-
while (Date.now() < deadline) {
|
|
1344
|
-
const health = await probeEngineDaemonHealth({ port: discovery.port, token: discovery.token, timeoutMs: 400 });
|
|
1345
|
-
if (!health || Number(health.pid) !== Number(discovery.pid)) return true;
|
|
1346
|
-
await delay(150);
|
|
1347
|
-
}
|
|
1348
|
-
return false;
|
|
1349
|
-
}
|
|
1350
|
-
function spawnDaemonCandidate({ cwd, log }) {
|
|
1351
|
-
return new Promise((resolve5) => {
|
|
1352
|
-
let settled = false;
|
|
1353
|
-
const done = () => {
|
|
1354
|
-
if (!settled) {
|
|
1355
|
-
settled = true;
|
|
1356
|
-
resolve5();
|
|
1357
|
-
}
|
|
1358
|
-
};
|
|
1359
|
-
let child;
|
|
1360
|
-
try {
|
|
1361
|
-
child = fork(daemonEntry(), [], {
|
|
1362
|
-
cwd,
|
|
1363
|
-
stdio: ["ignore", "ignore", "pipe", "ipc"],
|
|
1364
|
-
detached: process.platform !== "win32",
|
|
1365
|
-
windowsHide: true,
|
|
1366
|
-
env: {
|
|
1367
|
-
...process.env,
|
|
1368
|
-
MIXDOG_ENGINE_DAEMON_HOST: "1",
|
|
1369
|
-
// Engine-only spawn: the backend stays dormant on the channels side
|
|
1370
|
-
// until a channels client registers.
|
|
1371
|
-
MIXDOG_DAEMON_SPAWNED_FOR: "engine",
|
|
1372
|
-
MIXDOG_SUPERVISOR_PID: process.env.MIXDOG_SUPERVISOR_PID || String(process.pid)
|
|
1373
|
-
}
|
|
1374
|
-
});
|
|
1375
|
-
} catch (err) {
|
|
1376
|
-
log(`daemon spawn failed: ${err?.message || err}`);
|
|
1377
|
-
done();
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
const mirror = (chunk) => {
|
|
1381
|
-
const text = String(chunk || "").trimEnd();
|
|
1382
|
-
if (text) log(text);
|
|
1383
|
-
};
|
|
1384
|
-
child.stderr?.on("data", mirror);
|
|
1385
|
-
child.once("message", (msg) => {
|
|
1386
|
-
if (msg?.type !== "ready") return;
|
|
1387
|
-
try {
|
|
1388
|
-
child.stderr?.off?.("data", mirror);
|
|
1389
|
-
} catch {
|
|
1390
|
-
}
|
|
1391
|
-
try {
|
|
1392
|
-
child.disconnect?.();
|
|
1393
|
-
} catch {
|
|
1394
|
-
}
|
|
1395
|
-
try {
|
|
1396
|
-
child.unref?.();
|
|
1397
|
-
} catch {
|
|
1398
|
-
}
|
|
1399
|
-
try {
|
|
1400
|
-
child.stderr?.unref?.();
|
|
1401
|
-
} catch {
|
|
1402
|
-
}
|
|
1403
|
-
done();
|
|
1404
|
-
});
|
|
1405
|
-
child.once("exit", done);
|
|
1406
|
-
child.once("error", (err) => {
|
|
1407
|
-
log(`daemon spawn error: ${err?.message || err}`);
|
|
1408
|
-
done();
|
|
1409
|
-
});
|
|
1410
|
-
const timer2 = setTimeout(done, 3e4);
|
|
1411
|
-
timer2.unref?.();
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
async function ensureEngineDaemon({ cwd = process.cwd(), log = () => {
|
|
1415
|
-
}, attempts = 5 } = {}) {
|
|
1416
|
-
let upgradeBlocked = null;
|
|
1417
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1418
|
-
const discovery = readEngineDaemonDiscovery();
|
|
1419
|
-
if (discovery) {
|
|
1420
|
-
const health = await probeEngineDaemonHealth({ port: discovery.port, token: discovery.token });
|
|
1421
|
-
if (health && Number(health.pid) === Number(discovery.pid)) {
|
|
1422
|
-
const verdict = negotiateEngineDaemon(health);
|
|
1423
|
-
if (verdict === "ok") return discovery;
|
|
1424
|
-
if (verdict === "defer") {
|
|
1425
|
-
const err = new Error(
|
|
1426
|
-
`engine daemon ${health.version || "?"} (protocol ${health.protocol ?? 0}) is newer than this client`
|
|
1427
|
-
);
|
|
1428
|
-
err.daemonNewerThanClient = true;
|
|
1429
|
-
throw err;
|
|
1430
|
-
}
|
|
1431
|
-
log(`stopping older engine daemon ${health.version || "?"} (protocol ${health.protocol ?? 0})`);
|
|
1432
|
-
await shutdownEngineDaemon(discovery);
|
|
1433
|
-
upgradeBlocked = await waitForDaemonExit(discovery) ? null : health;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
await spawnDaemonCandidate({ cwd, log });
|
|
1437
|
-
await delay(100);
|
|
1438
|
-
}
|
|
1439
|
-
if (upgradeBlocked) {
|
|
1440
|
-
const err = new Error(
|
|
1441
|
-
`an older Mixdog backend (protocol ${upgradeBlocked.protocol ?? 0}) is still serving live clients \u2014 close the running Mixdog windows and terminals, then start again`
|
|
1442
|
-
);
|
|
1443
|
-
err.daemonUpgradeBlocked = true;
|
|
1444
|
-
throw err;
|
|
1445
|
-
}
|
|
1446
|
-
throw new Error("engine daemon is unavailable");
|
|
1447
|
-
}
|
|
1448
|
-
async function attachEngineDaemon({
|
|
1449
|
-
discovery,
|
|
1450
|
-
leadPid = process.pid,
|
|
1451
|
-
cwd = process.cwd(),
|
|
1452
|
-
lifecycle = true,
|
|
1453
|
-
registrationId = randomUUID2(),
|
|
1454
|
-
onFrame = () => {
|
|
1455
|
-
},
|
|
1456
|
-
onFatal = () => {
|
|
1457
|
-
},
|
|
1458
|
-
log = () => {
|
|
1459
|
-
}
|
|
1460
|
-
} = {}) {
|
|
1461
|
-
if (!discovery?.port || !discovery?.token) throw new Error("daemon discovery {port, token} required");
|
|
1462
|
-
const { port, token: serverToken } = discovery;
|
|
1463
|
-
const stableRegistrationId = String(registrationId || randomUUID2());
|
|
1464
|
-
let reg = null;
|
|
1465
|
-
let registrationError = null;
|
|
1466
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1467
|
-
try {
|
|
1468
|
-
reg = await request({
|
|
1469
|
-
port,
|
|
1470
|
-
token: serverToken,
|
|
1471
|
-
method: "POST",
|
|
1472
|
-
path: "/client/register",
|
|
1473
|
-
body: {
|
|
1474
|
-
leadPid,
|
|
1475
|
-
cwd,
|
|
1476
|
-
lifecycle: lifecycle !== false,
|
|
1477
|
-
registrationId: stableRegistrationId
|
|
1478
|
-
},
|
|
1479
|
-
timeoutMs: 3e3,
|
|
1480
|
-
control: true
|
|
1481
|
-
});
|
|
1482
|
-
break;
|
|
1483
|
-
} catch (error) {
|
|
1484
|
-
registrationError = error;
|
|
1485
|
-
if (Number(error?.statusCode) >= 400 && Number(error?.statusCode) < 500) throw error;
|
|
1486
|
-
if (attempt < 2) await delay(100 * (attempt + 1));
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
if (!reg) throw registrationError || new Error("engine daemon registration failed");
|
|
1490
|
-
const clientToken = reg?.token;
|
|
1491
|
-
if (!clientToken) throw new Error("engine daemon register returned no client token");
|
|
1492
|
-
let closed = false;
|
|
1493
|
-
let streamRequest = null;
|
|
1494
|
-
function openStream() {
|
|
1495
|
-
if (closed) return;
|
|
1496
|
-
const req = http.request({
|
|
1497
|
-
hostname: "127.0.0.1",
|
|
1498
|
-
port,
|
|
1499
|
-
path: `/events?token=${encodeURIComponent(clientToken)}&server_token=${encodeURIComponent(serverToken)}`,
|
|
1500
|
-
method: "GET",
|
|
1501
|
-
headers: { Accept: "text/event-stream", "X-Mixdog-Daemon-Token": serverToken }
|
|
1502
|
-
}, (res) => {
|
|
1503
|
-
if (req !== streamRequest || closed) {
|
|
1504
|
-
res.resume();
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
if (res.statusCode !== 200) {
|
|
1508
|
-
res.resume();
|
|
1509
|
-
if (!closed) {
|
|
1510
|
-
try {
|
|
1511
|
-
onFatal(`bad sse status ${res.statusCode}`);
|
|
1512
|
-
} catch {
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
res.setEncoding("utf8");
|
|
1518
|
-
let buffer = "";
|
|
1519
|
-
res.on("data", (chunk) => {
|
|
1520
|
-
if (req !== streamRequest || closed) return;
|
|
1521
|
-
buffer += chunk;
|
|
1522
|
-
let index;
|
|
1523
|
-
while ((index = buffer.indexOf("\n\n")) >= 0) {
|
|
1524
|
-
const raw = buffer.slice(0, index);
|
|
1525
|
-
buffer = buffer.slice(index + 2);
|
|
1526
|
-
for (const line of raw.split("\n")) {
|
|
1527
|
-
if (!line.startsWith("data:")) continue;
|
|
1528
|
-
const json = line.slice(5).trim();
|
|
1529
|
-
if (!json) continue;
|
|
1530
|
-
let frame = null;
|
|
1531
|
-
try {
|
|
1532
|
-
frame = JSON.parse(json);
|
|
1533
|
-
} catch {
|
|
1534
|
-
continue;
|
|
1535
|
-
}
|
|
1536
|
-
try {
|
|
1537
|
-
onFrame(frame);
|
|
1538
|
-
} catch (err) {
|
|
1539
|
-
log(`onFrame threw: ${err?.message || err}`);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
});
|
|
1544
|
-
const lost = (reason) => {
|
|
1545
|
-
if (req !== streamRequest || closed) return;
|
|
1546
|
-
try {
|
|
1547
|
-
onFatal(reason);
|
|
1548
|
-
} catch {
|
|
1549
|
-
}
|
|
1550
|
-
};
|
|
1551
|
-
res.on("end", () => lost("sse ended"));
|
|
1552
|
-
res.on("error", () => lost("sse error"));
|
|
1553
|
-
});
|
|
1554
|
-
req.on("error", () => {
|
|
1555
|
-
if (req === streamRequest && !closed) {
|
|
1556
|
-
try {
|
|
1557
|
-
onFatal("sse request error");
|
|
1558
|
-
} catch {
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
});
|
|
1562
|
-
streamRequest = req;
|
|
1563
|
-
req.end();
|
|
1564
|
-
}
|
|
1565
|
-
openStream();
|
|
1566
|
-
async function call(name, args = {}, { timeoutMs = 3e5, callId = null } = {}) {
|
|
1567
|
-
let out;
|
|
1568
|
-
try {
|
|
1569
|
-
out = await request({
|
|
1570
|
-
port,
|
|
1571
|
-
token: serverToken,
|
|
1572
|
-
method: "POST",
|
|
1573
|
-
path: "/call",
|
|
1574
|
-
body: { token: clientToken, name, args: args || {}, ...callId ? { callId } : {} },
|
|
1575
|
-
timeoutMs
|
|
1576
|
-
});
|
|
1577
|
-
} catch (err) {
|
|
1578
|
-
err.daemonTransportError = true;
|
|
1579
|
-
throw err;
|
|
1580
|
-
}
|
|
1581
|
-
if (out && out.error) throw new Error(out.error);
|
|
1582
|
-
return out?.result;
|
|
1583
|
-
}
|
|
1584
|
-
async function close(reason = "client close") {
|
|
1585
|
-
if (closed) return;
|
|
1586
|
-
closed = true;
|
|
1587
|
-
try {
|
|
1588
|
-
streamRequest?.destroy?.();
|
|
1589
|
-
} catch {
|
|
1590
|
-
}
|
|
1591
|
-
try {
|
|
1592
|
-
await request({
|
|
1593
|
-
port,
|
|
1594
|
-
token: serverToken,
|
|
1595
|
-
method: "POST",
|
|
1596
|
-
path: "/client/deregister",
|
|
1597
|
-
body: { token: clientToken },
|
|
1598
|
-
timeoutMs: 1500,
|
|
1599
|
-
control: true
|
|
1600
|
-
});
|
|
1601
|
-
} catch {
|
|
1602
|
-
}
|
|
1603
|
-
log(`detached (${reason})`);
|
|
1604
|
-
}
|
|
1605
|
-
return { call, close, clientToken, port, pid: Number(discovery.pid) };
|
|
1606
|
-
}
|
|
1607
|
-
async function shutdownEngineDaemon(discovery = readEngineDaemonDiscovery()) {
|
|
1608
|
-
if (!discovery?.port) return false;
|
|
1609
|
-
try {
|
|
1610
|
-
await request({
|
|
1611
|
-
port: discovery.port,
|
|
1612
|
-
token: discovery.token,
|
|
1613
|
-
method: "POST",
|
|
1614
|
-
path: "/shutdown",
|
|
1615
|
-
body: {},
|
|
1616
|
-
timeoutMs: 3e3,
|
|
1617
|
-
control: true
|
|
1618
|
-
});
|
|
1619
|
-
return true;
|
|
1620
|
-
} catch {
|
|
1621
|
-
return false;
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
function addSessionView(views, sessionId, view) {
|
|
1625
|
-
let bucket = views.get(sessionId);
|
|
1626
|
-
if (!bucket) {
|
|
1627
|
-
bucket = /* @__PURE__ */ new Set();
|
|
1628
|
-
views.set(sessionId, bucket);
|
|
1629
|
-
}
|
|
1630
|
-
bucket.add(view);
|
|
1631
|
-
}
|
|
1632
|
-
function removeSessionView(views, sessionId, view) {
|
|
1633
|
-
const bucket = views.get(sessionId);
|
|
1634
|
-
if (!bucket) return;
|
|
1635
|
-
bucket.delete(view);
|
|
1636
|
-
if (bucket.size === 0) views.delete(sessionId);
|
|
1637
|
-
}
|
|
1638
|
-
function sessionViewCount(views, sessionId) {
|
|
1639
|
-
return views.get(sessionId)?.size ?? 0;
|
|
1640
|
-
}
|
|
1641
|
-
function applyStatePatch(state, patch) {
|
|
1642
|
-
const next = { ...state, ...patch.set || {} };
|
|
1643
|
-
if (patch.itemsAppend) {
|
|
1644
|
-
const base = Array.isArray(state.items) ? state.items : [];
|
|
1645
|
-
next.items = base.slice(0, patch.itemsAppend.from).concat(patch.itemsAppend.values || []);
|
|
1646
|
-
}
|
|
1647
|
-
for (const key of patch.remove || []) delete next[key];
|
|
1648
|
-
return next;
|
|
1649
|
-
}
|
|
1650
|
-
function scheduleReattach({ cwd, log }) {
|
|
1651
|
-
if (reattachTimer || liveViews.size === 0) return;
|
|
1652
|
-
reattachTimer = setTimeout(() => {
|
|
1653
|
-
reattachTimer = null;
|
|
1654
|
-
if (liveViews.size === 0) return;
|
|
1655
|
-
void ensureSharedAttachment({ cwd, log }).catch((err) => {
|
|
1656
|
-
log(`backend daemon re-attach failed: ${err?.message || err}`);
|
|
1657
|
-
scheduleReattach({ cwd, log });
|
|
1658
|
-
});
|
|
1659
|
-
}, 500);
|
|
1660
|
-
reattachTimer.unref?.();
|
|
1661
|
-
}
|
|
1662
|
-
async function ensureSharedAttachment({ cwd, log }) {
|
|
1663
|
-
if (shared?.client) return shared;
|
|
1664
|
-
if (shared?.pending) return shared.pending;
|
|
1665
|
-
const pending = (async () => {
|
|
1666
|
-
const localBridge = process.env.MIXDOG_ENGINE_DAEMON_HOST === "1" ? engineDaemonLocalBridge() : null;
|
|
1667
|
-
const discovery = localBridge ? { pid: process.pid, local: true } : await ensureEngineDaemon({ cwd, log });
|
|
1668
|
-
const views = /* @__PURE__ */ new Map();
|
|
1669
|
-
const state = { client: null, views, discovery, refs: liveViews.size, log, cwd };
|
|
1670
|
-
const attachmentOptions = {
|
|
1671
|
-
cwd,
|
|
1672
|
-
log,
|
|
1673
|
-
onFrame: (frame) => {
|
|
1674
|
-
const bucket = views.get(String(frame?.sessionId || ""));
|
|
1675
|
-
if (!bucket) return;
|
|
1676
|
-
for (const view of [...bucket]) {
|
|
1677
|
-
if (frame.type === "session-state") view.applyFrame(frame);
|
|
1678
|
-
else if (frame.type === "session-gone") view.applyGone(frame.reason || "session unloaded");
|
|
1679
|
-
}
|
|
1680
|
-
},
|
|
1681
|
-
onFatal: (reason) => {
|
|
1682
|
-
log(`backend daemon attachment lost (${reason})`);
|
|
1683
|
-
if (shared === state) shared = null;
|
|
1684
|
-
for (const bucket of views.values()) {
|
|
1685
|
-
for (const view of [...bucket]) view.applyDetached(reason);
|
|
1686
|
-
}
|
|
1687
|
-
scheduleReattach({ cwd: state.cwd, log });
|
|
1688
|
-
}
|
|
1689
|
-
};
|
|
1690
|
-
state.client = localBridge ? await localBridge.attach(attachmentOptions) : await attachEngineDaemon({ discovery, ...attachmentOptions });
|
|
1691
|
-
shared = state;
|
|
1692
|
-
for (const view of liveViews) {
|
|
1693
|
-
addSessionView(views, view.sessionId(), view);
|
|
1694
|
-
void view.recover(state).catch((err) => {
|
|
1695
|
-
log(`session ${view.sessionId()} recovery failed: ${err?.message || err}`);
|
|
1696
|
-
});
|
|
1697
|
-
}
|
|
1698
|
-
return state;
|
|
1699
|
-
})();
|
|
1700
|
-
shared = { pending };
|
|
1701
|
-
try {
|
|
1702
|
-
return await pending;
|
|
1703
|
-
} catch (err) {
|
|
1704
|
-
shared = null;
|
|
1705
|
-
throw err;
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
async function createRemoteEngineSession(options = {}) {
|
|
1709
|
-
const log = typeof options.log === "function" ? options.log : () => {
|
|
1710
|
-
};
|
|
1711
|
-
const cwd = options.cwd || process.cwd();
|
|
1712
|
-
const openParams = {
|
|
1713
|
-
cwd,
|
|
1714
|
-
provider: options.provider,
|
|
1715
|
-
model: options.model,
|
|
1716
|
-
toolMode: options.toolMode || "full",
|
|
1717
|
-
remote: options.remote === true,
|
|
1718
|
-
desktopSession: options.desktopSession ?? null
|
|
1719
|
-
};
|
|
1720
|
-
let attachment = await ensureSharedAttachment({ cwd, log });
|
|
1721
|
-
const created = await attachment.client.call("session.create", openParams, {
|
|
1722
|
-
callId: `session-create:${process.pid}:${randomUUID2()}`
|
|
1723
|
-
});
|
|
1724
|
-
let sessionId = String(created?.sessionId || "");
|
|
1725
|
-
if (!sessionId) throw new Error("session.create returned no sessionId");
|
|
1726
|
-
let state = created?.full ?? {};
|
|
1727
|
-
let revision = Number(created?.revision) || 0;
|
|
1728
|
-
let sync = created?.sync ?? {};
|
|
1729
|
-
let disposed = false;
|
|
1730
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1731
|
-
function emit() {
|
|
1732
|
-
for (const listener of [...listeners]) {
|
|
1733
|
-
try {
|
|
1734
|
-
listener();
|
|
1735
|
-
} catch (err) {
|
|
1736
|
-
log(`session listener threw: ${err?.message || err}`);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
function applyBody(body) {
|
|
1741
|
-
if (disposed || !body) return false;
|
|
1742
|
-
if (body.full !== void 0 && body.full !== null) {
|
|
1743
|
-
state = body.full;
|
|
1744
|
-
revision = Number(body.revision) || revision;
|
|
1745
|
-
emit();
|
|
1746
|
-
return true;
|
|
1747
|
-
}
|
|
1748
|
-
if (body.patch) {
|
|
1749
|
-
if (Number(body.revision) === revision && Number(body.baseRevision) === revision - 1) return true;
|
|
1750
|
-
if (Number(body.baseRevision) !== revision) return false;
|
|
1751
|
-
state = applyStatePatch(state, body.patch);
|
|
1752
|
-
revision = Number(body.revision) || revision;
|
|
1753
|
-
emit();
|
|
1754
|
-
return true;
|
|
1755
|
-
}
|
|
1756
|
-
if (Number.isFinite(Number(body.revision))) {
|
|
1757
|
-
revision = Number(body.revision);
|
|
1758
|
-
return true;
|
|
1759
|
-
}
|
|
1760
|
-
return false;
|
|
1761
|
-
}
|
|
1762
|
-
let resyncing = false;
|
|
1763
|
-
function resync(reason) {
|
|
1764
|
-
if (disposed || resyncing) return;
|
|
1765
|
-
resyncing = true;
|
|
1766
|
-
void attachment.client.call("session.read", {
|
|
1767
|
-
sessionId,
|
|
1768
|
-
open: openParams,
|
|
1769
|
-
baseRevision: revision
|
|
1770
|
-
}).then((result) => {
|
|
1771
|
-
if (disposed) return;
|
|
1772
|
-
if (result?.sync) sync = result.sync;
|
|
1773
|
-
if (!applyBody(result) && result?.revision !== void 0) {
|
|
1774
|
-
log(`session ${sessionId} resync returned an unusable body`);
|
|
1775
|
-
}
|
|
1776
|
-
}).catch((err) => {
|
|
1777
|
-
log(`session ${sessionId} resync after ${reason} failed: ${err?.message || err}`);
|
|
1778
|
-
}).finally(() => {
|
|
1779
|
-
resyncing = false;
|
|
1780
|
-
});
|
|
1781
|
-
}
|
|
1782
|
-
let recoveryPromise = null;
|
|
1783
|
-
let view;
|
|
1784
|
-
async function recover(nextAttachment) {
|
|
1785
|
-
if (disposed) return;
|
|
1786
|
-
if (recoveryPromise) return recoveryPromise;
|
|
1787
|
-
recoveryPromise = (async () => {
|
|
1788
|
-
const previousAttachment = attachment;
|
|
1789
|
-
if (previousAttachment !== nextAttachment) {
|
|
1790
|
-
removeSessionView(previousAttachment.views, sessionId, view);
|
|
1791
|
-
attachment = nextAttachment;
|
|
1792
|
-
}
|
|
1793
|
-
let result;
|
|
1794
|
-
try {
|
|
1795
|
-
result = await attachment.client.call("session.subscribe", {
|
|
1796
|
-
sessionId,
|
|
1797
|
-
open: openParams,
|
|
1798
|
-
baseRevision: revision
|
|
1799
|
-
});
|
|
1800
|
-
} catch {
|
|
1801
|
-
result = await attachment.client.call("session.create", {
|
|
1802
|
-
...openParams,
|
|
1803
|
-
sessionId
|
|
1804
|
-
}, { callId: `session-recreate:${sessionId}` });
|
|
1805
|
-
}
|
|
1806
|
-
addSessionView(attachment.views, sessionId, view);
|
|
1807
|
-
if (result?.sync) sync = result.sync;
|
|
1808
|
-
if (!applyBody(result) && result?.revision !== void 0) resync("recovery body gap");
|
|
1809
|
-
log(`session ${sessionId} projection recovered`);
|
|
1810
|
-
})();
|
|
1811
|
-
try {
|
|
1812
|
-
return await recoveryPromise;
|
|
1813
|
-
} finally {
|
|
1814
|
-
recoveryPromise = null;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
view = {
|
|
1818
|
-
sessionId: () => sessionId,
|
|
1819
|
-
applyFrame(frame) {
|
|
1820
|
-
if (disposed) return;
|
|
1821
|
-
if (!applyBody(frame)) resync("revision gap");
|
|
1822
|
-
},
|
|
1823
|
-
applyGone(reason) {
|
|
1824
|
-
if (disposed) return;
|
|
1825
|
-
void recover(attachment).catch((err) => {
|
|
1826
|
-
log(`session ${sessionId} reload after ${reason} failed: ${err?.message || err}`);
|
|
1827
|
-
});
|
|
1828
|
-
},
|
|
1829
|
-
applyDetached(reason) {
|
|
1830
|
-
log(`session ${sessionId} projection detached (${reason})`);
|
|
1831
|
-
},
|
|
1832
|
-
recover
|
|
1833
|
-
};
|
|
1834
|
-
addSessionView(attachment.views, sessionId, view);
|
|
1835
|
-
attachment.refs += 1;
|
|
1836
|
-
liveViews.add(view);
|
|
1837
|
-
async function sendCall(route, payload, callId) {
|
|
1838
|
-
const send = () => attachment.client.call(route, payload, { callId });
|
|
1839
|
-
try {
|
|
1840
|
-
return await send();
|
|
1841
|
-
} catch (err) {
|
|
1842
|
-
const recoverable = err?.daemonTransportError || /unknown client token/i.test(String(err?.message || ""));
|
|
1843
|
-
if (!recoverable || disposed) throw err;
|
|
1844
|
-
if (shared === attachment) shared = null;
|
|
1845
|
-
const next = await ensureSharedAttachment({ cwd, log });
|
|
1846
|
-
await recover(next);
|
|
1847
|
-
return await send();
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
async function applyResult(result, reason) {
|
|
1851
|
-
const nextSessionId = String(result?.sessionId || sessionId);
|
|
1852
|
-
if (nextSessionId && nextSessionId !== sessionId) {
|
|
1853
|
-
const previousSessionId = sessionId;
|
|
1854
|
-
addSessionView(attachment.views, nextSessionId, view);
|
|
1855
|
-
removeSessionView(attachment.views, previousSessionId, view);
|
|
1856
|
-
sessionId = nextSessionId;
|
|
1857
|
-
}
|
|
1858
|
-
if (result?.sync) sync = result.sync;
|
|
1859
|
-
if (!applyBody(result) && result?.revision !== void 0) resync(`${reason} body gap`);
|
|
1860
|
-
return result;
|
|
1861
|
-
}
|
|
1862
|
-
let callChain = Promise.resolve();
|
|
1863
|
-
function serialize(task) {
|
|
1864
|
-
const run = callChain.then(task);
|
|
1865
|
-
callChain = run.then(() => {
|
|
1866
|
-
}, () => {
|
|
1867
|
-
});
|
|
1868
|
-
return run;
|
|
1869
|
-
}
|
|
1870
|
-
function remoteCall(method, args, callOptions = {}) {
|
|
1871
|
-
return serialize(async () => {
|
|
1872
|
-
if (disposed) throw new Error("This session view is disposed.");
|
|
1873
|
-
const stableCallId = typeof callOptions.callId === "string" && callOptions.callId.trim() ? callOptions.callId.trim() : randomUUID2();
|
|
1874
|
-
const result = await sendCall("session.invoke", {
|
|
1875
|
-
sessionId,
|
|
1876
|
-
method,
|
|
1877
|
-
args,
|
|
1878
|
-
open: openParams,
|
|
1879
|
-
baseRevision: revision
|
|
1880
|
-
}, stableCallId);
|
|
1881
|
-
await applyResult(result, method);
|
|
1882
|
-
return result?.value ?? null;
|
|
1883
|
-
});
|
|
1884
|
-
}
|
|
1885
|
-
function dispatch(method, args) {
|
|
1886
|
-
void remoteCall(method, args).catch((err) => {
|
|
1887
|
-
log(`session ${method} failed: ${err?.message || err}`);
|
|
1888
|
-
});
|
|
1889
|
-
}
|
|
1890
|
-
async function rebindTo(result, previousSessionId) {
|
|
1891
|
-
const nextSessionId = String(result?.sessionId || "");
|
|
1892
|
-
if (!nextSessionId) throw new Error("session route returned no sessionId");
|
|
1893
|
-
const lastLocalView = sessionViewCount(attachment.views, previousSessionId) <= 1;
|
|
1894
|
-
addSessionView(attachment.views, nextSessionId, view);
|
|
1895
|
-
removeSessionView(attachment.views, previousSessionId, view);
|
|
1896
|
-
sessionId = nextSessionId;
|
|
1897
|
-
if (result?.sync) sync = result.sync;
|
|
1898
|
-
if (!applyBody(result) && result?.revision !== void 0) resync("session rebind body gap");
|
|
1899
|
-
if (previousSessionId && previousSessionId !== nextSessionId && lastLocalView) {
|
|
1900
|
-
try {
|
|
1901
|
-
await attachment.client.call("session.unsubscribe", { sessionId: previousSessionId });
|
|
1902
|
-
} catch (err) {
|
|
1903
|
-
log(`session ${previousSessionId} unsubscribe failed: ${err?.message || err}`);
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
return nextSessionId;
|
|
1907
|
-
}
|
|
1908
|
-
async function createReservedSession() {
|
|
1909
|
-
return serialize(async () => {
|
|
1910
|
-
if (disposed) throw new Error("This session view is disposed.");
|
|
1911
|
-
const previousSessionId = sessionId;
|
|
1912
|
-
const result = await sendCall(
|
|
1913
|
-
"session.create",
|
|
1914
|
-
openParams,
|
|
1915
|
-
`session-create:${process.pid}:${randomUUID2()}`
|
|
1916
|
-
);
|
|
1917
|
-
return await rebindTo(result, previousSessionId);
|
|
1918
|
-
});
|
|
1919
|
-
}
|
|
1920
|
-
async function resumeSession(targetSessionId, resumeOptions) {
|
|
1921
|
-
const target = String(targetSessionId || "");
|
|
1922
|
-
if (!target) return false;
|
|
1923
|
-
return serialize(async () => {
|
|
1924
|
-
if (disposed) throw new Error("This session view is disposed.");
|
|
1925
|
-
if (target === sessionId) {
|
|
1926
|
-
const result2 = await sendCall("session.read", {
|
|
1927
|
-
sessionId,
|
|
1928
|
-
open: openParams,
|
|
1929
|
-
baseRevision: revision
|
|
1930
|
-
}, randomUUID2());
|
|
1931
|
-
await applyResult(result2, "resume");
|
|
1932
|
-
return true;
|
|
1933
|
-
}
|
|
1934
|
-
const previousSessionId = sessionId;
|
|
1935
|
-
const result = await sendCall("session.subscribe", {
|
|
1936
|
-
sessionId: target,
|
|
1937
|
-
open: { ...openParams, resumeOptions: resumeOptions || void 0 },
|
|
1938
|
-
baseRevision: null
|
|
1939
|
-
}, randomUUID2());
|
|
1940
|
-
await rebindTo(result, previousSessionId);
|
|
1941
|
-
return true;
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
1944
|
-
let submitSeq = 0;
|
|
1945
|
-
async function submitAsync(prompt, options2 = {}) {
|
|
1946
|
-
if (disposed) throw new Error("This session view is disposed.");
|
|
1947
|
-
const submissionId = String(options2?.id || "").trim() || `session-submit-${process.pid}-${Date.now()}-${submitSeq += 1}`;
|
|
1948
|
-
return serialize(async () => {
|
|
1949
|
-
const result = await sendCall("session.submit", {
|
|
1950
|
-
sessionId,
|
|
1951
|
-
prompt,
|
|
1952
|
-
options: { ...options2 || {}, id: submissionId },
|
|
1953
|
-
open: openParams,
|
|
1954
|
-
baseRevision: revision
|
|
1955
|
-
}, `session-submit:${sessionId}:${submissionId}`);
|
|
1956
|
-
await applyResult(result, "submit");
|
|
1957
|
-
return result?.accepted === true;
|
|
1958
|
-
});
|
|
1959
|
-
}
|
|
1960
|
-
const base = {
|
|
1961
|
-
isRemoteEngine: true,
|
|
1962
|
-
get disposedView() {
|
|
1963
|
-
return disposed;
|
|
1964
|
-
},
|
|
1965
|
-
getState: () => state,
|
|
1966
|
-
subscribe(listener) {
|
|
1967
|
-
listeners.add(listener);
|
|
1968
|
-
return () => listeners.delete(listener);
|
|
1969
|
-
},
|
|
1970
|
-
listSessions(options2) {
|
|
1971
|
-
if (options2?.refreshFromStorage) dispatch("listSessions", [options2]);
|
|
1972
|
-
return Array.isArray(sync.sessions) ? sync.sessions : [];
|
|
1973
|
-
},
|
|
1974
|
-
sessionStoreDir() {
|
|
1975
|
-
return typeof sync.sessionStoreDir === "string" ? sync.sessionStoreDir : null;
|
|
1976
|
-
},
|
|
1977
|
-
async newSession() {
|
|
1978
|
-
await createReservedSession();
|
|
1979
|
-
return true;
|
|
1980
|
-
},
|
|
1981
|
-
resume: resumeSession,
|
|
1982
|
-
async prefetchSession(targetSessionId) {
|
|
1983
|
-
const target = String(targetSessionId || "");
|
|
1984
|
-
if (!target) return false;
|
|
1985
|
-
await callDaemonSession({
|
|
1986
|
-
sessionId: target,
|
|
1987
|
-
method: "read",
|
|
1988
|
-
open: openParams,
|
|
1989
|
-
cwd,
|
|
1990
|
-
log,
|
|
1991
|
-
attempts: 1
|
|
1992
|
-
});
|
|
1993
|
-
return true;
|
|
1994
|
-
},
|
|
1995
|
-
submitAsync,
|
|
1996
|
-
submit(prompt, options2) {
|
|
1997
|
-
if (disposed) return false;
|
|
1998
|
-
void submitAsync(prompt, options2).catch((err) => {
|
|
1999
|
-
log(`legacy submit failed: ${err?.message || err}`);
|
|
2000
|
-
});
|
|
2001
|
-
return true;
|
|
2002
|
-
},
|
|
2003
|
-
abort() {
|
|
2004
|
-
void serialize(async () => {
|
|
2005
|
-
const result = await sendCall("session.abort", {
|
|
2006
|
-
sessionId,
|
|
2007
|
-
open: openParams,
|
|
2008
|
-
baseRevision: revision
|
|
2009
|
-
}, randomUUID2());
|
|
2010
|
-
await applyResult(result, "abort");
|
|
2011
|
-
}).catch((err) => log(`session abort failed: ${err?.message || err}`));
|
|
2012
|
-
return true;
|
|
2013
|
-
},
|
|
2014
|
-
resolveToolApproval(id, decision) {
|
|
2015
|
-
void serialize(async () => {
|
|
2016
|
-
const result = await sendCall("session.approve", {
|
|
2017
|
-
sessionId,
|
|
2018
|
-
approvalId: id,
|
|
2019
|
-
decision,
|
|
2020
|
-
open: openParams,
|
|
2021
|
-
baseRevision: revision
|
|
2022
|
-
}, randomUUID2());
|
|
2023
|
-
await applyResult(result, "approval");
|
|
2024
|
-
}).catch((err) => log(`session approval failed: ${err?.message || err}`));
|
|
2025
|
-
return true;
|
|
2026
|
-
},
|
|
2027
|
-
async dispose(reason = "view dispose") {
|
|
2028
|
-
if (disposed) return;
|
|
2029
|
-
disposed = true;
|
|
2030
|
-
liveViews.delete(view);
|
|
2031
|
-
const releasedSessionId = sessionId;
|
|
2032
|
-
const lastLocalView = sessionViewCount(attachment.views, releasedSessionId) <= 1;
|
|
2033
|
-
if (lastLocalView) {
|
|
2034
|
-
try {
|
|
2035
|
-
await attachment.client.call("session.unsubscribe", { sessionId: releasedSessionId });
|
|
2036
|
-
} catch (err) {
|
|
2037
|
-
log(`session ${releasedSessionId} unsubscribe failed: ${err?.message || err}`);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
removeSessionView(attachment.views, releasedSessionId, view);
|
|
2041
|
-
listeners.clear();
|
|
2042
|
-
attachment.refs = Math.max(0, attachment.refs - 1);
|
|
2043
|
-
if (attachment.refs === 0 && liveViews.size === 0) {
|
|
2044
|
-
try {
|
|
2045
|
-
await attachment.client.close(reason);
|
|
2046
|
-
} catch {
|
|
2047
|
-
}
|
|
2048
|
-
try {
|
|
2049
|
-
daemonCallAgent.destroy();
|
|
2050
|
-
} catch {
|
|
2051
|
-
}
|
|
2052
|
-
try {
|
|
2053
|
-
daemonControlAgent.destroy();
|
|
2054
|
-
} catch {
|
|
2055
|
-
}
|
|
2056
|
-
if (shared === attachment) shared = null;
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
};
|
|
2060
|
-
return new Proxy(base, {
|
|
2061
|
-
get(target, property, receiver) {
|
|
2062
|
-
if (property in target || LOCAL_ENGINE_KEYS.has(property)) {
|
|
2063
|
-
return Reflect.get(target, property, receiver);
|
|
2064
|
-
}
|
|
2065
|
-
if (typeof property === "symbol") return void 0;
|
|
2066
|
-
return (...args) => remoteCall(property, args);
|
|
2067
|
-
},
|
|
2068
|
-
has(target, property) {
|
|
2069
|
-
return typeof property === "string" ? true : Reflect.has(target, property);
|
|
2070
|
-
}
|
|
2071
|
-
});
|
|
2072
|
-
}
|
|
2073
|
-
async function callDaemonSession({
|
|
2074
|
-
sessionId,
|
|
2075
|
-
method,
|
|
2076
|
-
args = [],
|
|
2077
|
-
open: openHints = {},
|
|
2078
|
-
cwd = process.cwd(),
|
|
2079
|
-
log = () => {
|
|
2080
|
-
},
|
|
2081
|
-
attempts = 3
|
|
2082
|
-
} = {}) {
|
|
2083
|
-
const id = String(sessionId || "");
|
|
2084
|
-
if (!id) throw new TypeError("sessionId is required");
|
|
2085
|
-
const name = String(method || "");
|
|
2086
|
-
if (!name) throw new TypeError("method is required");
|
|
2087
|
-
let route = "session.invoke";
|
|
2088
|
-
let payload = { sessionId: id, method: name, args, open: openHints || {} };
|
|
2089
|
-
if (name === "submit") {
|
|
2090
|
-
route = "session.submit";
|
|
2091
|
-
payload = {
|
|
2092
|
-
sessionId: id,
|
|
2093
|
-
prompt: args?.[0],
|
|
2094
|
-
options: args?.[1] || {},
|
|
2095
|
-
open: openHints || {}
|
|
2096
|
-
};
|
|
2097
|
-
} else if (name === "abort") {
|
|
2098
|
-
route = "session.abort";
|
|
2099
|
-
payload = { sessionId: id, open: openHints || {} };
|
|
2100
|
-
} else if (name === "resolveToolApproval") {
|
|
2101
|
-
route = "session.approve";
|
|
2102
|
-
payload = {
|
|
2103
|
-
sessionId: id,
|
|
2104
|
-
approvalId: args?.[0],
|
|
2105
|
-
decision: args?.[1],
|
|
2106
|
-
open: openHints || {}
|
|
2107
|
-
};
|
|
2108
|
-
} else if (name === "read") {
|
|
2109
|
-
route = "session.read";
|
|
2110
|
-
payload = { sessionId: id, open: openHints || {}, baseRevision: null };
|
|
2111
|
-
}
|
|
2112
|
-
const submissionId = name === "submit" ? String(args?.[1]?.id || "").trim() : "";
|
|
2113
|
-
const callId = submissionId ? `session-submit:${id}:${submissionId}` : randomUUID2();
|
|
2114
|
-
let lastError = null;
|
|
2115
|
-
for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
|
|
2116
|
-
const attachment = await ensureSharedAttachment({ cwd, log });
|
|
2117
|
-
try {
|
|
2118
|
-
const result = await attachment.client.call(route, payload, { callId });
|
|
2119
|
-
if (name === "submit") return { ...result, value: result?.accepted === true };
|
|
2120
|
-
if (name === "abort") return { ...result, value: result?.aborted === true };
|
|
2121
|
-
if (name === "resolveToolApproval") return { ...result, value: result?.approved === true };
|
|
2122
|
-
return result;
|
|
2123
|
-
} catch (err) {
|
|
2124
|
-
if (!err?.daemonTransportError) throw err;
|
|
2125
|
-
lastError = err;
|
|
2126
|
-
if (shared === attachment) shared = null;
|
|
2127
|
-
await delay(200 * (attempt + 1));
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
throw lastError || new Error("engine daemon session call failed");
|
|
2131
|
-
}
|
|
2132
|
-
var daemonCallAgent, daemonControlAgent, shared, liveViews, reattachTimer, LOCAL_ENGINE_KEYS;
|
|
2133
|
-
var init_engine_daemon_client = __esm({
|
|
2134
|
-
"src/standalone/engine-daemon-client.mjs"() {
|
|
2135
|
-
init_engine_daemon_protocol();
|
|
2136
|
-
init_engine_daemon_local_bridge();
|
|
2137
|
-
daemonCallAgent = new http.Agent({ keepAlive: true, keepAliveMsecs: 5e3, maxSockets: 8 });
|
|
2138
|
-
daemonControlAgent = new http.Agent({ keepAlive: true, keepAliveMsecs: 5e3, maxSockets: 4 });
|
|
2139
|
-
shared = null;
|
|
2140
|
-
liveViews = /* @__PURE__ */ new Set();
|
|
2141
|
-
reattachTimer = null;
|
|
2142
|
-
LOCAL_ENGINE_KEYS = /* @__PURE__ */ new Set([
|
|
2143
|
-
"getState",
|
|
2144
|
-
"subscribe",
|
|
2145
|
-
"dispose",
|
|
2146
|
-
"isRemoteEngine",
|
|
2147
|
-
"disposedView",
|
|
2148
|
-
"then",
|
|
2149
|
-
"catch",
|
|
2150
|
-
"finally",
|
|
2151
|
-
"toJSON",
|
|
2152
|
-
"inspect",
|
|
2153
|
-
Symbol.toStringTag
|
|
2154
|
-
]);
|
|
1327
|
+
CONFIG_READ_TTL_MS = (() => {
|
|
1328
|
+
const n = Number(process.env.MIXDOG_CONFIG_READ_TTL_MS);
|
|
1329
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
1330
|
+
})();
|
|
1331
|
+
_pendingConfigWrites = /* @__PURE__ */ new Set();
|
|
1332
|
+
SECRET_ACCOUNTS = Object.freeze({
|
|
1333
|
+
discordToken: "discord.token",
|
|
1334
|
+
telegramToken: "telegram.token",
|
|
1335
|
+
agentApiKey: (provider) => `agent.${provider}.apiKey`,
|
|
1336
|
+
openaiUsageSessionKey: "agent.openai.usageSessionKey",
|
|
1337
|
+
opencodeGoAuthCookie: "agent.opencode-go.authCookie"
|
|
1338
|
+
});
|
|
2155
1339
|
}
|
|
2156
1340
|
});
|
|
2157
1341
|
|
|
@@ -2160,7 +1344,7 @@ var require_mixdog_debug = __commonJS({
|
|
|
2160
1344
|
"src/lib/mixdog-debug.cjs"(exports, module) {
|
|
2161
1345
|
"use strict";
|
|
2162
1346
|
var fs = __require("fs");
|
|
2163
|
-
var
|
|
1347
|
+
var path2 = __require("path");
|
|
2164
1348
|
function isTruthyEnv(value) {
|
|
2165
1349
|
if (value == null || value === "") return false;
|
|
2166
1350
|
const s = String(value).trim().toLowerCase();
|
|
@@ -2173,12 +1357,12 @@ var require_mixdog_debug = __commonJS({
|
|
|
2173
1357
|
function _detectFromSourceCheckout() {
|
|
2174
1358
|
if (_cachedFromSource !== null) return _cachedFromSource;
|
|
2175
1359
|
try {
|
|
2176
|
-
const moduleDir = module && module.filename ?
|
|
1360
|
+
const moduleDir = module && module.filename ? path2.dirname(module.filename) : process.cwd();
|
|
2177
1361
|
const roots = [
|
|
2178
|
-
|
|
1362
|
+
path2.resolve(moduleDir, "..", ".."),
|
|
2179
1363
|
process.cwd()
|
|
2180
1364
|
];
|
|
2181
|
-
_cachedFromSource = roots.some((root) => fs.existsSync(
|
|
1365
|
+
_cachedFromSource = roots.some((root) => fs.existsSync(path2.join(root, ".git")));
|
|
2182
1366
|
} catch {
|
|
2183
1367
|
_cachedFromSource = false;
|
|
2184
1368
|
}
|
|
@@ -2217,8 +1401,7 @@ var require_mixdog_debug = __commonJS({
|
|
|
2217
1401
|
"webhook.log",
|
|
2218
1402
|
"perf.log",
|
|
2219
1403
|
"tool-events.log",
|
|
2220
|
-
"
|
|
2221
|
-
"channels-worker-standalone.log",
|
|
1404
|
+
"daemon.log",
|
|
2222
1405
|
"mixdog-tui.stderr.log"
|
|
2223
1406
|
]);
|
|
2224
1407
|
var STALE_PLUGIN_LOG_SIBLING_RE = [
|
|
@@ -2244,7 +1427,7 @@ var require_mixdog_debug = __commonJS({
|
|
|
2244
1427
|
}
|
|
2245
1428
|
const candidates = [];
|
|
2246
1429
|
for (const e of entries) {
|
|
2247
|
-
const p =
|
|
1430
|
+
const p = path2.join(dataDir, e.name);
|
|
2248
1431
|
try {
|
|
2249
1432
|
const st = fs.statSync(p);
|
|
2250
1433
|
if (now - st.mtimeMs < minAgeMs) continue;
|
|
@@ -2285,7 +1468,7 @@ var require_mixdog_debug = __commonJS({
|
|
|
2285
1468
|
function appendSessionStartCriticalLog(dataDir, line) {
|
|
2286
1469
|
if (!dataDir || !line) return;
|
|
2287
1470
|
try {
|
|
2288
|
-
const p =
|
|
1471
|
+
const p = path2.join(dataDir, SESSION_START_CRITICAL_LOG);
|
|
2289
1472
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
2290
1473
|
rotateBoundedLog2(p, SESSION_START_CRITICAL_MAX_BYTES, SESSION_START_CRITICAL_KEEP_BYTES);
|
|
2291
1474
|
fs.appendFileSync(p, line.endsWith("\n") ? line : `${line}
|
|
@@ -2312,14 +1495,14 @@ var require_mixdog_debug = __commonJS({
|
|
|
2312
1495
|
// src/tui/index.jsx
|
|
2313
1496
|
import React22 from "react";
|
|
2314
1497
|
import { render } from "../../../vendor/ink/build/index.js";
|
|
2315
|
-
import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as
|
|
1498
|
+
import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync6, openSync as openSync2, readSync } from "node:fs";
|
|
2316
1499
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
2317
|
-
import { dirname as
|
|
1500
|
+
import { dirname as dirname7, join as join8 } from "node:path";
|
|
2318
1501
|
import { performance as performance3 } from "node:perf_hooks";
|
|
2319
1502
|
import { format } from "node:util";
|
|
2320
1503
|
|
|
2321
1504
|
// src/tui/App.jsx
|
|
2322
|
-
import React21, { useState as useState11, useCallback as
|
|
1505
|
+
import React21, { useState as useState11, useCallback as useCallback13, useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useMemo as useMemo5, useRef as useRef14 } from "react";
|
|
2323
1506
|
import { Box as Box19, Text as Text21, useApp, useInput as useInput6, useStdin as useStdin3, useStdout as useStdout2 } from "../../../vendor/ink/build/index.js";
|
|
2324
1507
|
|
|
2325
1508
|
// src/tui/themes/base.mjs
|
|
@@ -3828,9 +3011,9 @@ var AGENT_RESPONSE_MARKER = RIGHT_ARROW;
|
|
|
3828
3011
|
var RESULT_GUTTER = ` ${RESULT_GUTTER_GLYPH} `;
|
|
3829
3012
|
var RESULT_GUTTER_CONT = ` ${RESULT_GUTTER_CONT_GLYPH} `;
|
|
3830
3013
|
|
|
3831
|
-
// src/tui/hooks/
|
|
3014
|
+
// src/tui/hooks/useSession.mjs
|
|
3832
3015
|
import { useSyncExternalStore } from "react";
|
|
3833
|
-
function
|
|
3016
|
+
function useSession(store) {
|
|
3834
3017
|
return useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
3835
3018
|
}
|
|
3836
3019
|
|
|
@@ -3992,8 +3175,8 @@ function parseToolArgs(args) {
|
|
|
3992
3175
|
}
|
|
3993
3176
|
return { value: args };
|
|
3994
3177
|
}
|
|
3995
|
-
function displayToolPath(
|
|
3996
|
-
const text = String(
|
|
3178
|
+
function displayToolPath(path2) {
|
|
3179
|
+
const text = String(path2 ?? "");
|
|
3997
3180
|
return text.replace(/\\/g, "/").split("/").filter(Boolean).at(-1) || text;
|
|
3998
3181
|
}
|
|
3999
3182
|
function compactParts(parts) {
|
|
@@ -4187,16 +3370,16 @@ function looksLikeZeroResultText(text) {
|
|
|
4187
3370
|
return /^\(?\s*(?:no|0)\s+(?:fuzzy\s+)?(?:match(?:es)?|results?|files?|entries|candidates?|hits?)\b/i.test(trimmed) || /^\(?\s*(?:empty|none)\s*\)?$/i.test(trimmed) || /^no\s+\S+\s+(?:found|matched)\b/i.test(trimmed);
|
|
4188
3371
|
}
|
|
4189
3372
|
function splitPathAndDelta(value, explicitDelta = "") {
|
|
4190
|
-
let
|
|
3373
|
+
let path2 = String(value ?? "").trim();
|
|
4191
3374
|
let delta = String(explicitDelta ?? "").trim();
|
|
4192
3375
|
if (!delta) {
|
|
4193
|
-
const sep =
|
|
3376
|
+
const sep = path2.lastIndexOf(" \u2014 ");
|
|
4194
3377
|
if (sep !== -1) {
|
|
4195
|
-
delta =
|
|
4196
|
-
|
|
3378
|
+
delta = path2.slice(sep + 3).trim();
|
|
3379
|
+
path2 = path2.slice(0, sep).trim();
|
|
4197
3380
|
}
|
|
4198
3381
|
}
|
|
4199
|
-
return { path:
|
|
3382
|
+
return { path: path2, delta };
|
|
4200
3383
|
}
|
|
4201
3384
|
function parseLineDelta(delta) {
|
|
4202
3385
|
const totals = { added: 0, removed: 0, seen: false };
|
|
@@ -4230,14 +3413,14 @@ function summarizeUpdateResult(text, args) {
|
|
|
4230
3413
|
for (const line of String(text ?? "").split("\n")) {
|
|
4231
3414
|
const ok = /^\s*OK\s+(modify|add|delete|create)\s+(.+?)\s*$/i.exec(line);
|
|
4232
3415
|
if (ok) {
|
|
4233
|
-
const { path:
|
|
4234
|
-
changed.push({ action: ok[1].toLowerCase(), path:
|
|
3416
|
+
const { path: path2, delta } = splitPathAndDelta(ok[2]);
|
|
3417
|
+
changed.push({ action: ok[1].toLowerCase(), path: path2, delta });
|
|
4235
3418
|
continue;
|
|
4236
3419
|
}
|
|
4237
3420
|
const edited = /^\s*(Edited|Created|Updated|Wrote):\s+(.+?)(?:\s+\(([^)]+)\))?\s*$/i.exec(line);
|
|
4238
3421
|
if (edited) {
|
|
4239
|
-
const { path:
|
|
4240
|
-
changed.push({ action: edited[1].toLowerCase(), path:
|
|
3422
|
+
const { path: path2, delta } = splitPathAndDelta(edited[2], edited[3] || "");
|
|
3423
|
+
changed.push({ action: edited[1].toLowerCase(), path: path2, delta });
|
|
4241
3424
|
}
|
|
4242
3425
|
}
|
|
4243
3426
|
if (changed.length === 1) {
|
|
@@ -4580,6 +3763,32 @@ function summarizeAgentSurfaceBrief(name, args, resultText, { isError = false, i
|
|
|
4580
3763
|
}
|
|
4581
3764
|
|
|
4582
3765
|
// src/runtime/shared/tool-surface.mjs
|
|
3766
|
+
function patchOperationProfile(args = {}) {
|
|
3767
|
+
const a = parseToolArgs(args);
|
|
3768
|
+
const patchText = String(a.patch ?? "");
|
|
3769
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3770
|
+
const add = (kind, count = 1) => {
|
|
3771
|
+
counts.set(kind, Number(counts.get(kind) || 0) + Math.max(1, Number(count || 1)));
|
|
3772
|
+
};
|
|
3773
|
+
for (const line of patchText.split("\n")) {
|
|
3774
|
+
const match = /^\*\*\*\s+(Update|Add|Delete) File:\s+.+\s*$/i.exec(line);
|
|
3775
|
+
if (!match) continue;
|
|
3776
|
+
add(match[1].toLowerCase());
|
|
3777
|
+
}
|
|
3778
|
+
if (counts.size > 0) return counts;
|
|
3779
|
+
const gitSections = patchText.split(/(?=^diff --git )/m).filter((section) => /^diff --git /m.test(section));
|
|
3780
|
+
const unifiedSections = gitSections.length > 0 ? gitSections : /^---\s+.+\n\+\+\+\s+.+$/m.test(patchText) ? [patchText] : [];
|
|
3781
|
+
for (const section of unifiedSections) {
|
|
3782
|
+
if (/^new file mode /m.test(section) || /^---\s+\/dev\/null(?:\s|$)/m.test(section)) add("add");
|
|
3783
|
+
else if (/^deleted file mode /m.test(section) || /^\+\+\+\s+\/dev\/null(?:\s|$)/m.test(section)) add("delete");
|
|
3784
|
+
else add("update");
|
|
3785
|
+
}
|
|
3786
|
+
if (counts.size > 0) return counts;
|
|
3787
|
+
if (a.old_string === "") add("add");
|
|
3788
|
+
else if (a.new_string === "" && a.old_string != null) add("delete");
|
|
3789
|
+
else add("update", patchFileCount(a) || 1);
|
|
3790
|
+
return counts;
|
|
3791
|
+
}
|
|
4583
3792
|
function displayToolName(name, args = {}) {
|
|
4584
3793
|
if (isExternalMcpToolName(name)) {
|
|
4585
3794
|
const mcp = parseMcpToolName(name);
|
|
@@ -4594,7 +3803,11 @@ function displayToolName(name, args = {}) {
|
|
|
4594
3803
|
case "apply_patch": {
|
|
4595
3804
|
const parsed = parseToolArgs(args);
|
|
4596
3805
|
if (parsed && parsed.dry_run === true) return "Check";
|
|
4597
|
-
|
|
3806
|
+
const operations = patchOperationProfile(parsed);
|
|
3807
|
+
if (operations.size > 1) return "Change";
|
|
3808
|
+
if (operations.has("add")) return "Create";
|
|
3809
|
+
if (operations.has("delete")) return "Delete";
|
|
3810
|
+
return "Update";
|
|
4598
3811
|
}
|
|
4599
3812
|
case "shell":
|
|
4600
3813
|
case "bash":
|
|
@@ -4925,6 +4138,28 @@ function unitDescriptor(category, overrides = {}) {
|
|
|
4925
4138
|
function queryCount(args, ...keys) {
|
|
4926
4139
|
return collectionCount(...keys.map((key) => args?.[key]));
|
|
4927
4140
|
}
|
|
4141
|
+
function patchMutationUnits(args = {}) {
|
|
4142
|
+
const a = parseToolArgs(args);
|
|
4143
|
+
if (a.dry_run === true) {
|
|
4144
|
+
return [unitDescriptor("Patch", {
|
|
4145
|
+
count: patchFileCount(a) || 1,
|
|
4146
|
+
active: "Checking",
|
|
4147
|
+
done: "Checked",
|
|
4148
|
+
noun: "file"
|
|
4149
|
+
})];
|
|
4150
|
+
}
|
|
4151
|
+
const copy = {
|
|
4152
|
+
add: { active: "Creating", done: "Created" },
|
|
4153
|
+
delete: { active: "Deleting", done: "Deleted" },
|
|
4154
|
+
update: { active: "Editing", done: "Edited" }
|
|
4155
|
+
};
|
|
4156
|
+
return [...patchOperationProfile(a)].map(([kind, count]) => unitDescriptor("Patch", {
|
|
4157
|
+
count,
|
|
4158
|
+
active: copy[kind]?.active || "Editing",
|
|
4159
|
+
done: copy[kind]?.done || "Edited",
|
|
4160
|
+
noun: "file"
|
|
4161
|
+
}));
|
|
4162
|
+
}
|
|
4928
4163
|
function toolWorkUnit(name, args = {}, category = "") {
|
|
4929
4164
|
const a = parseToolArgs(args);
|
|
4930
4165
|
const normalized = normalizeToolName(name);
|
|
@@ -4944,26 +4179,12 @@ function toolWorkUnit(name, args = {}, category = "") {
|
|
|
4944
4179
|
case "read_mcp_resource":
|
|
4945
4180
|
return unitDescriptor("Read", { count: queryCount(a, "uri", "uris") || 1, noun: "resource" });
|
|
4946
4181
|
case "apply_patch": {
|
|
4947
|
-
const
|
|
4948
|
-
|
|
4949
|
-
const deleting = !creating && a.new_string === "" && a.old_string != null || /^\*\*\*\s+Delete File:/mi.test(patchText);
|
|
4950
|
-
if (a.dry_run === true) {
|
|
4951
|
-
return unitDescriptor("Patch", {
|
|
4952
|
-
count: patchFileCount(a) || 1,
|
|
4953
|
-
active: "Checking",
|
|
4954
|
-
done: "Checked",
|
|
4955
|
-
noun: "file"
|
|
4956
|
-
});
|
|
4957
|
-
}
|
|
4182
|
+
const units = patchMutationUnits(a);
|
|
4183
|
+
if (units.length === 1) return units[0];
|
|
4958
4184
|
return unitDescriptor("Patch", {
|
|
4959
|
-
count:
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
// real command, its output and its failure banner (turn.mjs
|
|
4963
|
-
// postShellTwins). Repeating "& verifying" in the header declared the
|
|
4964
|
-
// verify phase twice and truncated the label on narrow panes.
|
|
4965
|
-
active: creating ? "Creating" : deleting ? "Deleting" : "Editing",
|
|
4966
|
-
done: creating ? "Created" : deleting ? "Deleted" : "Edited",
|
|
4185
|
+
count: units.reduce((total, unit) => total + unit.count, 0),
|
|
4186
|
+
active: "Changing",
|
|
4187
|
+
done: "Changed",
|
|
4967
4188
|
noun: "file"
|
|
4968
4189
|
});
|
|
4969
4190
|
}
|
|
@@ -5179,9 +4400,9 @@ function reconcileTimer() {
|
|
|
5179
4400
|
clearTimeout(timer);
|
|
5180
4401
|
timer = null;
|
|
5181
4402
|
}
|
|
5182
|
-
const
|
|
5183
|
-
if (
|
|
5184
|
-
timer = setTimeout(onTick,
|
|
4403
|
+
const delay = nextDelayMs(Date.now());
|
|
4404
|
+
if (delay == null) return;
|
|
4405
|
+
timer = setTimeout(onTick, delay);
|
|
5185
4406
|
timer.unref?.();
|
|
5186
4407
|
}
|
|
5187
4408
|
function onTick() {
|
|
@@ -5411,6 +4632,16 @@ var SPINNER_VERBS = [
|
|
|
5411
4632
|
"Zigzagging"
|
|
5412
4633
|
];
|
|
5413
4634
|
var SPINNER_VERB_ROTATE_MS = 3e4;
|
|
4635
|
+
var SPINNER_VERB_CHANGE_ODDS = 0.5;
|
|
4636
|
+
function boundarySwaps(anchor, boundary) {
|
|
4637
|
+
let h = Math.floor(anchor / 1e3) + Math.imul(boundary, 2654435761) | 0;
|
|
4638
|
+
h = Math.imul(h ^ h >>> 16, 569420461);
|
|
4639
|
+
h = Math.imul(h ^ h >>> 15, 1935289751);
|
|
4640
|
+
h = (h ^ h >>> 15) >>> 0;
|
|
4641
|
+
return h / 4294967296 < SPINNER_VERB_CHANGE_ODDS;
|
|
4642
|
+
}
|
|
4643
|
+
var MAX_COUNTED_BOUNDARIES = 4096;
|
|
4644
|
+
var MAX_HELD_BOUNDARIES = 3;
|
|
5414
4645
|
var SPINNER_MODE_OVERRIDE_VERBS = {
|
|
5415
4646
|
compacting: "Compacting conversation",
|
|
5416
4647
|
"auto-clear": "Auto-clearing conversation",
|
|
@@ -5420,14 +4651,24 @@ function spinnerVerbFor(startedAt = 0, now = 0, pool = SPINNER_VERBS) {
|
|
|
5420
4651
|
const list = Array.isArray(pool) && pool.length ? pool : SPINNER_VERBS;
|
|
5421
4652
|
const anchor = Math.max(0, Number(startedAt) || 0);
|
|
5422
4653
|
const elapsed = anchor > 0 ? Math.max(0, (Number(now) || 0) - anchor) : 0;
|
|
5423
|
-
const
|
|
5424
|
-
const
|
|
4654
|
+
const boundaries = Math.floor(elapsed / SPINNER_VERB_ROTATE_MS);
|
|
4655
|
+
const counted = Math.min(boundaries, MAX_COUNTED_BOUNDARIES);
|
|
4656
|
+
let swaps = boundaries - counted;
|
|
4657
|
+
let held = 0;
|
|
4658
|
+
for (let boundary = 1; boundary <= counted; boundary += 1) {
|
|
4659
|
+
if (held >= MAX_HELD_BOUNDARIES || boundarySwaps(anchor, boundary)) {
|
|
4660
|
+
swaps += 1;
|
|
4661
|
+
held = 0;
|
|
4662
|
+
} else {
|
|
4663
|
+
held += 1;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
const seed = Math.floor(anchor / 1e3) + swaps;
|
|
5425
4667
|
return list[(seed * 7 + 3) % list.length];
|
|
5426
4668
|
}
|
|
5427
4669
|
var SPINNER_FRAMES = ["\u25C7", "\u25C6", "\u25C8", "\u25C6", "\u25C7"];
|
|
5428
4670
|
|
|
5429
4671
|
// src/tui/spinner-meta.mjs
|
|
5430
|
-
var SHOW_TOKENS_AFTER_MS = 3e4;
|
|
5431
4672
|
var compactNumberFormatter = new Intl.NumberFormat("en-US", {
|
|
5432
4673
|
notation: "compact",
|
|
5433
4674
|
maximumFractionDigits: 1,
|
|
@@ -5462,7 +4703,10 @@ function buildSpinnerMeta({
|
|
|
5462
4703
|
const thinkingText = spinnerThinkingLabel({ thinking, thinkingSince, thinkingMs, effort });
|
|
5463
4704
|
return {
|
|
5464
4705
|
tokensText: tokens > 0 ? `${formatSpinnerTokens(tokens)} tokens` : "",
|
|
5465
|
-
|
|
4706
|
+
// The count appears with the FIRST token (user: 30초가 아니라 바로).
|
|
4707
|
+
// Elapsed was never gated, so the byline used to grow a second field
|
|
4708
|
+
// mid-turn for no reason the reader could see; now both arrive together.
|
|
4709
|
+
showTokens: tokens > 0,
|
|
5466
4710
|
thinkingText,
|
|
5467
4711
|
thinkingActive: Boolean(thinking || Number(thinkingSince) > 0)
|
|
5468
4712
|
};
|
|
@@ -6001,8 +5245,8 @@ function hasActiveStatuslineWork(line, agentWorkers = [], agentJobs = [], active
|
|
|
6001
5245
|
function bootFullRenderEligible(mountAtMs, line, agentWorkers = [], agentJobs = [], activeTools = null) {
|
|
6002
5246
|
const elapsed = Date.now() - mountAtMs;
|
|
6003
5247
|
const active = hasActiveStatuslineWork(line, agentWorkers, agentJobs, activeTools);
|
|
6004
|
-
const
|
|
6005
|
-
return elapsed >=
|
|
5248
|
+
const delay = active ? STATUSLINE_BOOT_FULL_DELAY_ACTIVE_MS : STATUSLINE_BOOT_FULL_DELAY_MS;
|
|
5249
|
+
return elapsed >= delay;
|
|
6006
5250
|
}
|
|
6007
5251
|
function canAttemptBootFullRender(nextAttemptAtMs = 0) {
|
|
6008
5252
|
return Date.now() >= nextAttemptAtMs;
|
|
@@ -6775,15 +6019,15 @@ function singleTrailingLineBreakPrefix(text) {
|
|
|
6775
6019
|
function draftStateEqual(a, b) {
|
|
6776
6020
|
return a.value === b.value && a.cursor === b.cursor && a.selectionAnchor === b.selectionAnchor;
|
|
6777
6021
|
}
|
|
6778
|
-
var
|
|
6022
|
+
var MODIFIED_ENTER_NEWLINE = 1 | 2 | 4;
|
|
6779
6023
|
function isModifiedEnterSequence(input) {
|
|
6780
6024
|
const text = String(input ?? "");
|
|
6781
6025
|
const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
|
|
6782
6026
|
if (!body) return false;
|
|
6783
6027
|
const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
|
|
6784
|
-
if (kitty) return (Number(kitty[1]) - 1 &
|
|
6028
|
+
if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0;
|
|
6785
6029
|
const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
|
|
6786
|
-
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 &
|
|
6030
|
+
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0);
|
|
6787
6031
|
}
|
|
6788
6032
|
function isAnyModifiedEnterSequence(input) {
|
|
6789
6033
|
const text = String(input ?? "");
|
|
@@ -6796,9 +6040,6 @@ function isAnyModifiedEnterSequence(input) {
|
|
|
6796
6040
|
}
|
|
6797
6041
|
|
|
6798
6042
|
// src/tui/components/prompt-input/immediate-render.mjs
|
|
6799
|
-
function suppressed(value) {
|
|
6800
|
-
return typeof value === "function" ? value() === true : value === true;
|
|
6801
|
-
}
|
|
6802
6043
|
function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
|
|
6803
6044
|
if (!throttle || throttle.timer === null) return false;
|
|
6804
6045
|
clearTimer(throttle.timer);
|
|
@@ -6807,7 +6048,6 @@ function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
|
|
|
6807
6048
|
}
|
|
6808
6049
|
function schedulePromptImmediateFlush({
|
|
6809
6050
|
throttle,
|
|
6810
|
-
isSuppressed = false,
|
|
6811
6051
|
flush,
|
|
6812
6052
|
intervalMs = 16,
|
|
6813
6053
|
now = Date.now,
|
|
@@ -6818,10 +6058,6 @@ function schedulePromptImmediateFlush({
|
|
|
6818
6058
|
if (!throttle || typeof flush !== "function") {
|
|
6819
6059
|
throw new TypeError("schedulePromptImmediateFlush requires throttle state and a flush function");
|
|
6820
6060
|
}
|
|
6821
|
-
if (suppressed(isSuppressed)) {
|
|
6822
|
-
cancelPromptImmediateFlush(throttle, clearTimer);
|
|
6823
|
-
return false;
|
|
6824
|
-
}
|
|
6825
6061
|
const current = now();
|
|
6826
6062
|
const elapsed = current - throttle.lastAt;
|
|
6827
6063
|
if (elapsed >= intervalMs) {
|
|
@@ -6833,13 +6069,35 @@ function schedulePromptImmediateFlush({
|
|
|
6833
6069
|
if (throttle.timer !== null) return false;
|
|
6834
6070
|
throttle.timer = setTimer(() => {
|
|
6835
6071
|
throttle.timer = null;
|
|
6836
|
-
if (suppressed(isSuppressed)) return;
|
|
6837
6072
|
throttle.lastAt = now();
|
|
6838
6073
|
flush();
|
|
6839
6074
|
}, intervalMs - elapsed);
|
|
6840
6075
|
return true;
|
|
6841
6076
|
}
|
|
6842
6077
|
|
|
6078
|
+
// src/tui/components/prompt-input/escape-policy.mjs
|
|
6079
|
+
var PROMPT_ESCAPE_CLEAR_WINDOW_MS = 800;
|
|
6080
|
+
function classifyPromptEscape({
|
|
6081
|
+
interruptActive = false,
|
|
6082
|
+
hasQueuedMessages = false,
|
|
6083
|
+
hasMessages = false,
|
|
6084
|
+
value = "",
|
|
6085
|
+
lastClearPressAt = 0,
|
|
6086
|
+
now = Date.now()
|
|
6087
|
+
} = {}) {
|
|
6088
|
+
if (interruptActive) return { action: "interrupt", nextClearPressAt: 0 };
|
|
6089
|
+
if (hasQueuedMessages) return { action: "restore-queue", nextClearPressAt: 0 };
|
|
6090
|
+
const current = Number(now);
|
|
6091
|
+
const previous = Number(lastClearPressAt);
|
|
6092
|
+
const repeated = Number.isFinite(current) && Number.isFinite(previous) && previous > 0 && current >= previous && current - previous <= PROMPT_ESCAPE_CLEAR_WINDOW_MS;
|
|
6093
|
+
const armed = Number.isFinite(current) && current > 0 ? current : 1;
|
|
6094
|
+
if (String(value ?? "") === "") {
|
|
6095
|
+
if (!hasMessages) return { action: "idle", nextClearPressAt: 0 };
|
|
6096
|
+
return repeated ? { action: "message-selector", nextClearPressAt: 0 } : { action: "arm-select", nextClearPressAt: armed };
|
|
6097
|
+
}
|
|
6098
|
+
return repeated ? { action: "clear", nextClearPressAt: 0 } : { action: "arm-clear", nextClearPressAt: armed };
|
|
6099
|
+
}
|
|
6100
|
+
|
|
6843
6101
|
// src/tui/components/PromptInput.jsx
|
|
6844
6102
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
6845
6103
|
var IME_LEFT_GUARD_COLUMNS = 0;
|
|
@@ -6862,6 +6120,7 @@ function PromptInput({
|
|
|
6862
6120
|
interruptActive = false,
|
|
6863
6121
|
onInterrupt,
|
|
6864
6122
|
commandPaletteActive = false,
|
|
6123
|
+
commandPaletteOpen = commandPaletteActive,
|
|
6865
6124
|
mask = false,
|
|
6866
6125
|
hint = "",
|
|
6867
6126
|
hintTone = "info",
|
|
@@ -6874,6 +6133,8 @@ function PromptInput({
|
|
|
6874
6133
|
onCommandPaletteCancel,
|
|
6875
6134
|
onCommandPaletteComplete,
|
|
6876
6135
|
onRestoreQueued,
|
|
6136
|
+
hasQueuedMessages = false,
|
|
6137
|
+
hasMessages = false,
|
|
6877
6138
|
onHistoryNavigate,
|
|
6878
6139
|
onPasteText,
|
|
6879
6140
|
selectionRef,
|
|
@@ -6888,10 +6149,10 @@ function PromptInput({
|
|
|
6888
6149
|
});
|
|
6889
6150
|
const [, bumpCursorAnchorEpoch] = useState3(0);
|
|
6890
6151
|
const draftRef = useRef4(draft);
|
|
6891
|
-
const interruptActiveRef = useRef4(interruptActive);
|
|
6892
6152
|
const lastReportedValueRef = useRef4(draft.value);
|
|
6893
6153
|
const pasteGenerationRef = useRef4(0);
|
|
6894
6154
|
const submitGateRef = useRef4(false);
|
|
6155
|
+
const escapeClearAtRef = useRef4(0);
|
|
6895
6156
|
if (valueRef) valueRef.current = draftRef.current.value;
|
|
6896
6157
|
const { isRawModeSupported } = useStdin();
|
|
6897
6158
|
const boxRef = useRef4(null);
|
|
@@ -6903,7 +6164,6 @@ function PromptInput({
|
|
|
6903
6164
|
const undoRef = useRef4({ past: [], future: [], lastPushAt: 0, lastValue: null });
|
|
6904
6165
|
const { value, cursor } = draft;
|
|
6905
6166
|
draftRef.current = draft;
|
|
6906
|
-
interruptActiveRef.current = interruptActive;
|
|
6907
6167
|
if (selectionRef) {
|
|
6908
6168
|
const range = selectionRange(draft);
|
|
6909
6169
|
selectionRef.current = range ? { range, text: mask ? "" : draft.value.slice(range.start, range.end) } : null;
|
|
@@ -6928,7 +6188,6 @@ function PromptInput({
|
|
|
6928
6188
|
const scheduleImmediateFlush = () => {
|
|
6929
6189
|
schedulePromptImmediateFlush({
|
|
6930
6190
|
throttle: flushThrottleRef.current,
|
|
6931
|
-
isSuppressed: () => interruptActiveRef.current,
|
|
6932
6191
|
flush: flushImmediate
|
|
6933
6192
|
});
|
|
6934
6193
|
};
|
|
@@ -6936,6 +6195,7 @@ function PromptInput({
|
|
|
6936
6195
|
cancelPromptImmediateFlush(flushThrottleRef.current);
|
|
6937
6196
|
}, []);
|
|
6938
6197
|
const commitDraft = (next, options = {}) => {
|
|
6198
|
+
escapeClearAtRef.current = 0;
|
|
6939
6199
|
const sameDraft = draftStateEqual(draftRef.current, next);
|
|
6940
6200
|
if (!options.keepPreferredColumn) preferredColumnRef.current = null;
|
|
6941
6201
|
if (sameDraft) {
|
|
@@ -7144,8 +6404,12 @@ function PromptInput({
|
|
|
7144
6404
|
commitDraft(moveCursor(current, moved.cursor, { extend }), { keepPreferredColumn: true });
|
|
7145
6405
|
return true;
|
|
7146
6406
|
};
|
|
7147
|
-
const restoreQueuedToDraft = () => {
|
|
7148
|
-
return onRestoreQueued?.(
|
|
6407
|
+
const restoreQueuedToDraft = ({ showHint = false } = {}) => {
|
|
6408
|
+
return onRestoreQueued?.({
|
|
6409
|
+
restoreDraft: true,
|
|
6410
|
+
showHint,
|
|
6411
|
+
currentText: draftRef.current.value
|
|
6412
|
+
}) === true;
|
|
7149
6413
|
};
|
|
7150
6414
|
const applyHistoryNavigation = (direction, meta = {}) => {
|
|
7151
6415
|
const nextValue = onHistoryNavigate?.(direction, draftRef.current.value, meta);
|
|
@@ -7237,6 +6501,7 @@ function PromptInput({
|
|
|
7237
6501
|
if (disabled) return;
|
|
7238
6502
|
const rawInput = String(input ?? "");
|
|
7239
6503
|
const inputKey = rawInput.toLowerCase();
|
|
6504
|
+
if (!key.escape) escapeClearAtRef.current = 0;
|
|
7240
6505
|
const rawShiftArrowForGrid = rawInput === "\x1B[1;2A" || rawInput === "\x1B[a" || rawInput === "[1;2A" || rawInput === "\x1B[1;2B" || rawInput === "\x1B[b" || rawInput === "[1;2B" || rawInput === "\x1B[1;2C" || rawInput === "\x1B[c" || rawInput === "[1;2C" || rawInput === "\x1B[1;2D" || rawInput === "\x1B[d" || rawInput === "[1;2D" || rawInput === "\x1B[1;6A" || rawInput === "[1;6A" || rawInput === "\x1B[1;6B" || rawInput === "[1;6B" || rawInput === "\x1B[1;6C" || rawInput === "[1;6C" || rawInput === "\x1B[1;6D" || rawInput === "[1;6D";
|
|
7241
6506
|
const gridSelectionActive = typeof suppressShiftNavRef === "function" ? suppressShiftNavRef() : typeof suppressShiftNavRef?.current === "function" ? suppressShiftNavRef.current() : Boolean(suppressShiftNavRef?.current);
|
|
7242
6507
|
if (gridSelectionActive) {
|
|
@@ -7264,13 +6529,13 @@ function PromptInput({
|
|
|
7264
6529
|
const lineBreakIndex = rawInput.search(/[\r\n]/);
|
|
7265
6530
|
const rawEnter = rawInput === "\r" || rawInput === "\n" || rawInput === "\r\n";
|
|
7266
6531
|
const trailingEnterPrefix = singleTrailingLineBreakPrefix(rawInput);
|
|
7267
|
-
const
|
|
7268
|
-
const modifiedLineBreak = key.shift || key.meta || key.ctrl ||
|
|
6532
|
+
const rawModifiedEnter = isModifiedEnterSequence(rawInput);
|
|
6533
|
+
const modifiedLineBreak = key.shift || key.meta || key.ctrl || rawModifiedEnter;
|
|
7269
6534
|
if (rawInput === "\n" && !key.return || key.ctrl && inputKey === "j") {
|
|
7270
6535
|
updateDraft((d) => replaceSelection(d, "\n"));
|
|
7271
6536
|
return;
|
|
7272
6537
|
}
|
|
7273
|
-
if (!
|
|
6538
|
+
if (!rawModifiedEnter && isAnyModifiedEnterSequence(rawInput)) {
|
|
7274
6539
|
return;
|
|
7275
6540
|
}
|
|
7276
6541
|
const pasteFallback = lineBreakIndex !== -1 && trailingEnterPrefix === null && !rawEnter && (rawInput.length > 1 || !key.return);
|
|
@@ -7287,7 +6552,7 @@ function PromptInput({
|
|
|
7287
6552
|
submitEnterChunk(trailingEnterPrefix);
|
|
7288
6553
|
return;
|
|
7289
6554
|
}
|
|
7290
|
-
if (
|
|
6555
|
+
if (rawModifiedEnter) {
|
|
7291
6556
|
updateDraft((d) => replaceSelection(d, "\n"));
|
|
7292
6557
|
return;
|
|
7293
6558
|
}
|
|
@@ -7340,12 +6605,10 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7340
6605
|
if (!moveDraftVertically(-1, { extend: true })) {
|
|
7341
6606
|
updateDraft((d) => moveCursor(d, 0, { extend: true }));
|
|
7342
6607
|
}
|
|
7343
|
-
} else {
|
|
7344
|
-
const
|
|
7345
|
-
if (!
|
|
7346
|
-
|
|
7347
|
-
} else if (!moveDraftVertically(-1, { extend: false })) {
|
|
7348
|
-
applyHistoryNavigation("up", { emptyDraft: false });
|
|
6608
|
+
} else if (!moveDraftVertically(-1, { extend: false })) {
|
|
6609
|
+
const emptyDraft = String(draftRef.current.value || "").length === 0;
|
|
6610
|
+
if (!hasQueuedMessages || !restoreQueuedToDraft()) {
|
|
6611
|
+
applyHistoryNavigation("up", { emptyDraft });
|
|
7349
6612
|
}
|
|
7350
6613
|
}
|
|
7351
6614
|
}
|
|
@@ -7360,7 +6623,7 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7360
6623
|
updateDraft((d) => moveCursor(d, d.value.length, { extend: true }));
|
|
7361
6624
|
}
|
|
7362
6625
|
} else if (!moveDraftVertically(1, { extend: false })) {
|
|
7363
|
-
applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").
|
|
6626
|
+
applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").length === 0 });
|
|
7364
6627
|
}
|
|
7365
6628
|
}
|
|
7366
6629
|
return;
|
|
@@ -7392,9 +6655,8 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7392
6655
|
if (onTab?.(draftRef.current.value) === true) return;
|
|
7393
6656
|
}
|
|
7394
6657
|
if (key.escape) {
|
|
7395
|
-
if (
|
|
6658
|
+
if (commandPaletteOpen) {
|
|
7396
6659
|
onCommandPaletteCancel?.(draftRef.current.value);
|
|
7397
|
-
commitDraft({ value: "", cursor: 0, selectionAnchor: null });
|
|
7398
6660
|
return;
|
|
7399
6661
|
}
|
|
7400
6662
|
if (selectionRange(draftRef.current)) {
|
|
@@ -7405,19 +6667,48 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7405
6667
|
if (onEscape?.(currentValue, { phase: "before" }) === true) {
|
|
7406
6668
|
return;
|
|
7407
6669
|
}
|
|
7408
|
-
|
|
6670
|
+
let escape = classifyPromptEscape({
|
|
6671
|
+
interruptActive,
|
|
6672
|
+
hasQueuedMessages,
|
|
6673
|
+
hasMessages,
|
|
6674
|
+
value: currentValue,
|
|
6675
|
+
lastClearPressAt: escapeClearAtRef.current
|
|
6676
|
+
});
|
|
6677
|
+
if (escape.action === "restore-queue") {
|
|
6678
|
+
if (restoreQueuedToDraft()) {
|
|
6679
|
+
escapeClearAtRef.current = 0;
|
|
6680
|
+
return;
|
|
6681
|
+
}
|
|
6682
|
+
escape = classifyPromptEscape({
|
|
6683
|
+
interruptActive,
|
|
6684
|
+
hasMessages,
|
|
6685
|
+
value: currentValue,
|
|
6686
|
+
lastClearPressAt: escapeClearAtRef.current
|
|
6687
|
+
});
|
|
6688
|
+
}
|
|
6689
|
+
escapeClearAtRef.current = escape.nextClearPressAt;
|
|
6690
|
+
if (escape.action === "interrupt") {
|
|
6691
|
+
const restoredText = onInterrupt?.(currentValue);
|
|
6692
|
+
if (!currentValue && typeof restoredText === "string") {
|
|
6693
|
+
commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
|
|
6694
|
+
}
|
|
6695
|
+
return;
|
|
6696
|
+
}
|
|
6697
|
+
if (escape.action === "arm-clear") {
|
|
6698
|
+
onEscape?.(currentValue, { phase: "clear-arm" });
|
|
6699
|
+
return;
|
|
6700
|
+
}
|
|
6701
|
+
if (escape.action === "clear") {
|
|
7409
6702
|
onEscape?.(currentValue, { phase: "clear" });
|
|
7410
6703
|
commitDraft({ value: "", cursor: 0, selectionAnchor: null });
|
|
7411
6704
|
return;
|
|
7412
6705
|
}
|
|
7413
|
-
if (
|
|
7414
|
-
|
|
7415
|
-
if (typeof restoredText === "string") {
|
|
7416
|
-
commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
|
|
7417
|
-
}
|
|
6706
|
+
if (escape.action === "arm-select") {
|
|
6707
|
+
onEscape?.("", { phase: "select-arm" });
|
|
7418
6708
|
return;
|
|
7419
6709
|
}
|
|
7420
|
-
if (
|
|
6710
|
+
if (escape.action === "message-selector") {
|
|
6711
|
+
onEscape?.("", { phase: "select" });
|
|
7421
6712
|
return;
|
|
7422
6713
|
}
|
|
7423
6714
|
onEscape?.("", { phase: "empty" });
|
|
@@ -8254,11 +7545,11 @@ function ContextUsageView({ detail, columns }) {
|
|
|
8254
7545
|
const usage = detail?.usage || {};
|
|
8255
7546
|
const compaction = detail?.compaction || {};
|
|
8256
7547
|
const messages = detail?.messages || {};
|
|
8257
|
-
const
|
|
7548
|
+
const request = detail?.request || {};
|
|
8258
7549
|
const lastApi = detail?.lastApi || {};
|
|
8259
7550
|
const cache = detail?.cache || {};
|
|
8260
7551
|
const semantic = messages.semantic || {};
|
|
8261
|
-
const schema =
|
|
7552
|
+
const schema = request.toolSchemaBreakdown || {};
|
|
8262
7553
|
const usedTokens = finiteNumber(usage.usedTokens);
|
|
8263
7554
|
const windowTokens = finiteNumber(usage.windowTokens);
|
|
8264
7555
|
const freeTokens = windowTokens ? Math.max(0, windowTokens - usedTokens) : finiteNumber(usage.freeTokens);
|
|
@@ -8839,15 +8130,15 @@ function singleTrailingLineBreakPrefix2(text) {
|
|
|
8839
8130
|
const prefix = normalized.slice(0, -1);
|
|
8840
8131
|
return prefix.includes("\n") ? null : prefix;
|
|
8841
8132
|
}
|
|
8842
|
-
var
|
|
8133
|
+
var MODIFIED_ENTER_SHIFT_OR_CTRL = 1 | 4;
|
|
8843
8134
|
function isModifiedEnterSequence2(input) {
|
|
8844
8135
|
const text = String(input ?? "");
|
|
8845
8136
|
const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
|
|
8846
8137
|
if (!body) return false;
|
|
8847
8138
|
const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
|
|
8848
|
-
if (kitty) return (Number(kitty[1]) - 1 &
|
|
8139
|
+
if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0;
|
|
8849
8140
|
const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
|
|
8850
|
-
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 &
|
|
8141
|
+
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0);
|
|
8851
8142
|
}
|
|
8852
8143
|
function isAnyModifiedEnterSequence2(input) {
|
|
8853
8144
|
const text = String(input ?? "");
|
|
@@ -9211,169 +8502,17 @@ function TextEntryPanel({
|
|
|
9211
8502
|
// src/tui/App.jsx
|
|
9212
8503
|
init_paste_attachments();
|
|
9213
8504
|
|
|
9214
|
-
// src/standalone/projects.mjs
|
|
9215
|
-
import { homedir as homedir2 } from "node:os";
|
|
9216
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9217
|
-
import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
|
|
9218
|
-
var MIXDOG_HOME = process.env.MIXDOG_HOME || join3(homedir2(), ".mixdog");
|
|
9219
|
-
var PROJECTS_FILE = process.env.MIXDOG_PROJECTS_FILE ? resolve2(process.env.MIXDOG_PROJECTS_FILE) : join3(MIXDOG_HOME, "projects.json");
|
|
9220
|
-
function toAbsolute(rawPath) {
|
|
9221
|
-
const text = String(rawPath || "").trim();
|
|
9222
|
-
if (!text) return "";
|
|
9223
|
-
return isAbsolute2(text) ? resolve2(text) : resolve2(process.cwd(), text);
|
|
9224
|
-
}
|
|
9225
|
-
function normalizeKey(absPath) {
|
|
9226
|
-
const text = String(absPath || "");
|
|
9227
|
-
return process.platform === "win32" ? text.replace(/[\\/]+$/, "").toLowerCase() : text.replace(/\/+$/, "");
|
|
9228
|
-
}
|
|
9229
|
-
function readStore() {
|
|
9230
|
-
try {
|
|
9231
|
-
if (!existsSync3(PROJECTS_FILE)) return { projects: [] };
|
|
9232
|
-
const raw = readFileSync2(PROJECTS_FILE, "utf8");
|
|
9233
|
-
const parsed = JSON.parse(raw);
|
|
9234
|
-
const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
|
|
9235
|
-
return {
|
|
9236
|
-
projects: projects.filter((entry) => entry && typeof entry.path === "string" && entry.path.trim()).map((entry) => ({
|
|
9237
|
-
name: String(entry.name || basename3(entry.path) || entry.path),
|
|
9238
|
-
path: String(entry.path),
|
|
9239
|
-
addedAt: Number(entry.addedAt) || 0,
|
|
9240
|
-
...Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}
|
|
9241
|
-
}))
|
|
9242
|
-
};
|
|
9243
|
-
} catch {
|
|
9244
|
-
return { projects: [] };
|
|
9245
|
-
}
|
|
9246
|
-
}
|
|
9247
|
-
function writeStore(store) {
|
|
9248
|
-
try {
|
|
9249
|
-
mkdirSync2(dirname3(PROJECTS_FILE), { recursive: true });
|
|
9250
|
-
const tmp = `${PROJECTS_FILE}.${process.pid}.tmp`;
|
|
9251
|
-
writeFileSync2(tmp, `${JSON.stringify(store, null, 2)}
|
|
9252
|
-
`, "utf8");
|
|
9253
|
-
renameSync(tmp, PROJECTS_FILE);
|
|
9254
|
-
} catch {
|
|
9255
|
-
}
|
|
9256
|
-
}
|
|
9257
|
-
function ensureProjectIdMarker(absPath, name) {
|
|
9258
|
-
try {
|
|
9259
|
-
if (process.env.MIXDOG_DISABLE_PROJECT_MARKERS === "1") return;
|
|
9260
|
-
if (!absPath) return;
|
|
9261
|
-
try {
|
|
9262
|
-
if (!statSync(absPath).isDirectory()) return;
|
|
9263
|
-
} catch {
|
|
9264
|
-
return;
|
|
9265
|
-
}
|
|
9266
|
-
const markerPath = join3(absPath, ".mixdog", "project.id");
|
|
9267
|
-
if (existsSync3(markerPath)) return;
|
|
9268
|
-
const value = String(name || basename3(absPath) || "").trim();
|
|
9269
|
-
if (!value || value.toLowerCase() === "common") return;
|
|
9270
|
-
const markerDir = join3(absPath, ".mixdog");
|
|
9271
|
-
mkdirSync2(markerDir, { recursive: true });
|
|
9272
|
-
const tmp = `${markerPath}.${process.pid}.tmp`;
|
|
9273
|
-
writeFileSync2(tmp, `${value}
|
|
9274
|
-
`, "utf8");
|
|
9275
|
-
renameSync(tmp, markerPath);
|
|
9276
|
-
} catch {
|
|
9277
|
-
}
|
|
9278
|
-
}
|
|
9279
|
-
function projectRecency(entry) {
|
|
9280
|
-
const last = Number(entry?.lastSelectedAt);
|
|
9281
|
-
if (Number.isFinite(last) && last > 0) return last;
|
|
9282
|
-
return Number(entry?.addedAt) || 0;
|
|
9283
|
-
}
|
|
9284
|
-
function compareProjects(a, b) {
|
|
9285
|
-
const byRecency = projectRecency(b) - projectRecency(a);
|
|
9286
|
-
if (byRecency !== 0) return byRecency;
|
|
9287
|
-
const byName = String(a.name).localeCompare(String(b.name));
|
|
9288
|
-
if (byName !== 0) return byName;
|
|
9289
|
-
return String(a.path).localeCompare(String(b.path));
|
|
9290
|
-
}
|
|
9291
|
-
function listProjects() {
|
|
9292
|
-
const { projects } = readStore();
|
|
9293
|
-
return projects.slice().sort(compareProjects);
|
|
9294
|
-
}
|
|
9295
|
-
function addProject(rawPath) {
|
|
9296
|
-
const absPath = toAbsolute(rawPath);
|
|
9297
|
-
if (!absPath) return null;
|
|
9298
|
-
const store = readStore();
|
|
9299
|
-
const key = normalizeKey(absPath);
|
|
9300
|
-
const existing = store.projects.find((entry2) => normalizeKey(entry2.path) === key);
|
|
9301
|
-
if (existing) {
|
|
9302
|
-
ensureProjectIdMarker(absPath, existing.name);
|
|
9303
|
-
return existing;
|
|
9304
|
-
}
|
|
9305
|
-
const entry = {
|
|
9306
|
-
name: basename3(absPath) || absPath,
|
|
9307
|
-
path: absPath,
|
|
9308
|
-
addedAt: Date.now()
|
|
9309
|
-
};
|
|
9310
|
-
store.projects.push(entry);
|
|
9311
|
-
writeStore(store);
|
|
9312
|
-
ensureProjectIdMarker(absPath, entry.name);
|
|
9313
|
-
return entry;
|
|
9314
|
-
}
|
|
9315
|
-
function touchProjectSelected(rawPath) {
|
|
9316
|
-
const absPath = toAbsolute(rawPath);
|
|
9317
|
-
if (!absPath) return null;
|
|
9318
|
-
const store = readStore();
|
|
9319
|
-
const key = normalizeKey(absPath);
|
|
9320
|
-
const entry = store.projects.find((item) => normalizeKey(item.path) === key);
|
|
9321
|
-
if (!entry) return null;
|
|
9322
|
-
entry.lastSelectedAt = Date.now();
|
|
9323
|
-
writeStore(store);
|
|
9324
|
-
ensureProjectIdMarker(absPath, entry.name);
|
|
9325
|
-
return entry;
|
|
9326
|
-
}
|
|
9327
|
-
function renameProject(rawPath, nextName) {
|
|
9328
|
-
const absPath = toAbsolute(rawPath);
|
|
9329
|
-
if (!absPath) return null;
|
|
9330
|
-
const store = readStore();
|
|
9331
|
-
const key = normalizeKey(absPath);
|
|
9332
|
-
const entry = store.projects.find((item) => normalizeKey(item.path) === key);
|
|
9333
|
-
if (!entry) return null;
|
|
9334
|
-
const trimmed = String(nextName || "").trim();
|
|
9335
|
-
entry.name = trimmed || basename3(absPath) || absPath;
|
|
9336
|
-
writeStore(store);
|
|
9337
|
-
return entry;
|
|
9338
|
-
}
|
|
9339
|
-
function pathExists(rawPath) {
|
|
9340
|
-
const absPath = toAbsolute(rawPath);
|
|
9341
|
-
return !!absPath && existsSync3(absPath);
|
|
9342
|
-
}
|
|
9343
|
-
function isDirectory(rawPath) {
|
|
9344
|
-
const absPath = toAbsolute(rawPath);
|
|
9345
|
-
if (!absPath) return false;
|
|
9346
|
-
try {
|
|
9347
|
-
return statSync(absPath).isDirectory();
|
|
9348
|
-
} catch {
|
|
9349
|
-
return false;
|
|
9350
|
-
}
|
|
9351
|
-
}
|
|
9352
|
-
function ensureDir(rawPath) {
|
|
9353
|
-
const absPath = toAbsolute(rawPath);
|
|
9354
|
-
if (!absPath) return "";
|
|
9355
|
-
try {
|
|
9356
|
-
mkdirSync2(absPath, { recursive: true });
|
|
9357
|
-
return absPath;
|
|
9358
|
-
} catch {
|
|
9359
|
-
return "";
|
|
9360
|
-
}
|
|
9361
|
-
}
|
|
9362
|
-
function resolveProjectPath(rawPath) {
|
|
9363
|
-
return toAbsolute(rawPath);
|
|
9364
|
-
}
|
|
9365
|
-
|
|
9366
8505
|
// src/standalone/folder-dialog.mjs
|
|
9367
8506
|
import { spawn } from "node:child_process";
|
|
9368
8507
|
import path from "node:path";
|
|
9369
8508
|
var DIALOG_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
9370
8509
|
function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
|
|
9371
|
-
return new Promise((
|
|
8510
|
+
return new Promise((resolve4) => {
|
|
9372
8511
|
let child;
|
|
9373
8512
|
try {
|
|
9374
8513
|
child = spawn(cmd, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
9375
8514
|
} catch (error) {
|
|
9376
|
-
|
|
8515
|
+
resolve4({ ok: false, code: -1, stdout: "", error });
|
|
9377
8516
|
return;
|
|
9378
8517
|
}
|
|
9379
8518
|
let stdout2 = "";
|
|
@@ -9385,7 +8524,7 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
|
|
|
9385
8524
|
child.kill();
|
|
9386
8525
|
} catch {
|
|
9387
8526
|
}
|
|
9388
|
-
|
|
8527
|
+
resolve4({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
|
|
9389
8528
|
}, timeoutMs);
|
|
9390
8529
|
timer2.unref?.();
|
|
9391
8530
|
child.stdout.on("data", (chunk) => {
|
|
@@ -9395,13 +8534,13 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
|
|
|
9395
8534
|
if (settled) return;
|
|
9396
8535
|
settled = true;
|
|
9397
8536
|
clearTimeout(timer2);
|
|
9398
|
-
|
|
8537
|
+
resolve4({ ok: false, code: -1, stdout: "", error });
|
|
9399
8538
|
});
|
|
9400
8539
|
child.on("close", (code) => {
|
|
9401
8540
|
if (settled) return;
|
|
9402
8541
|
settled = true;
|
|
9403
8542
|
clearTimeout(timer2);
|
|
9404
|
-
|
|
8543
|
+
resolve4({ ok: code === 0, code, stdout: stdout2 });
|
|
9405
8544
|
});
|
|
9406
8545
|
});
|
|
9407
8546
|
}
|
|
@@ -9409,10 +8548,10 @@ function spawnFailed(result) {
|
|
|
9409
8548
|
return !!result && result.code === -1;
|
|
9410
8549
|
}
|
|
9411
8550
|
function commandExists(cmd) {
|
|
9412
|
-
return new Promise((
|
|
8551
|
+
return new Promise((resolve4) => {
|
|
9413
8552
|
const probe = process.platform === "win32" ? spawn("where", [cmd], { stdio: "ignore", windowsHide: true }) : spawn("which", [cmd], { stdio: "ignore" });
|
|
9414
|
-
probe.on("error", () =>
|
|
9415
|
-
probe.on("close", (code) =>
|
|
8553
|
+
probe.on("error", () => resolve4(false));
|
|
8554
|
+
probe.on("close", (code) => resolve4(code === 0));
|
|
9416
8555
|
});
|
|
9417
8556
|
}
|
|
9418
8557
|
function psSingleQuoted(value) {
|
|
@@ -9615,9 +8754,9 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
|
|
|
9615
8754
|
powershellScript(title, resolvedInitial)
|
|
9616
8755
|
]);
|
|
9617
8756
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9618
|
-
const
|
|
9619
|
-
if (!result.ok && !
|
|
9620
|
-
return { available: true, path:
|
|
8757
|
+
const path2 = String(result.stdout || "").trim();
|
|
8758
|
+
if (!result.ok && !path2) return { available: false, path: null };
|
|
8759
|
+
return { available: true, path: path2 || null };
|
|
9621
8760
|
}
|
|
9622
8761
|
if (platform2 === "darwin") {
|
|
9623
8762
|
const safeTitle = String(title).replace(/"/g, '\\"');
|
|
@@ -9625,8 +8764,8 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
|
|
|
9625
8764
|
POSIX path of f`;
|
|
9626
8765
|
const result = await runCapture("osascript", ["-e", script]);
|
|
9627
8766
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9628
|
-
const
|
|
9629
|
-
return { available: true, path:
|
|
8767
|
+
const path2 = String(result.stdout || "").trim();
|
|
8768
|
+
return { available: true, path: path2 || null };
|
|
9630
8769
|
}
|
|
9631
8770
|
if (await commandExists("zenity")) {
|
|
9632
8771
|
const result = await runCapture("zenity", [
|
|
@@ -9635,16 +8774,16 @@ POSIX path of f`;
|
|
|
9635
8774
|
`--title=${title}`
|
|
9636
8775
|
]);
|
|
9637
8776
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9638
|
-
const
|
|
9639
|
-
if (!result.ok && !
|
|
9640
|
-
return { available: true, path:
|
|
8777
|
+
const path2 = String(result.stdout || "").trim();
|
|
8778
|
+
if (!result.ok && !path2) return { available: true, path: null };
|
|
8779
|
+
return { available: true, path: path2 || null };
|
|
9641
8780
|
}
|
|
9642
8781
|
if (await commandExists("kdialog")) {
|
|
9643
8782
|
const result = await runCapture("kdialog", ["--getexistingdirectory", "."]);
|
|
9644
8783
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9645
|
-
const
|
|
9646
|
-
if (!result.ok && !
|
|
9647
|
-
return { available: true, path:
|
|
8784
|
+
const path2 = String(result.stdout || "").trim();
|
|
8785
|
+
if (!result.ok && !path2) return { available: true, path: null };
|
|
8786
|
+
return { available: true, path: path2 || null };
|
|
9648
8787
|
}
|
|
9649
8788
|
return { available: false, path: null };
|
|
9650
8789
|
}
|
|
@@ -9967,13 +9106,13 @@ function nativeClipboardCommand(text) {
|
|
|
9967
9106
|
function copyToClipboard(text) {
|
|
9968
9107
|
const value = String(text ?? "");
|
|
9969
9108
|
const wroteOsc52 = writeOsc52Clipboard(value);
|
|
9970
|
-
return new Promise((
|
|
9109
|
+
return new Promise((resolve4, reject) => {
|
|
9971
9110
|
const { cmd, args, input } = nativeClipboardCommand(value);
|
|
9972
9111
|
let child;
|
|
9973
9112
|
try {
|
|
9974
9113
|
child = spawn2(cmd, args, { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
|
|
9975
9114
|
} catch (e) {
|
|
9976
|
-
if (wroteOsc52)
|
|
9115
|
+
if (wroteOsc52) resolve4();
|
|
9977
9116
|
else reject(e);
|
|
9978
9117
|
return;
|
|
9979
9118
|
}
|
|
@@ -9982,7 +9121,7 @@ function copyToClipboard(text) {
|
|
|
9982
9121
|
});
|
|
9983
9122
|
if (!wroteOsc52) {
|
|
9984
9123
|
child.on("close", (code) => {
|
|
9985
|
-
if (code === 0)
|
|
9124
|
+
if (code === 0) resolve4();
|
|
9986
9125
|
else reject(new Error(`${cmd} exited with code ${code}`));
|
|
9987
9126
|
});
|
|
9988
9127
|
}
|
|
@@ -9991,7 +9130,7 @@ function copyToClipboard(text) {
|
|
|
9991
9130
|
child.stdin.end(input);
|
|
9992
9131
|
if (wroteOsc52) {
|
|
9993
9132
|
child.unref?.();
|
|
9994
|
-
|
|
9133
|
+
resolve4();
|
|
9995
9134
|
}
|
|
9996
9135
|
});
|
|
9997
9136
|
}
|
|
@@ -10168,7 +9307,7 @@ var DIFF_STAT_SUMMARY_RE = /^\s*\d+\s+files?\s+changed\b/;
|
|
|
10168
9307
|
function colorizeDiffStatLine(line, c) {
|
|
10169
9308
|
const m = DIFF_STAT_FILE_RE.exec(line);
|
|
10170
9309
|
if (m) {
|
|
10171
|
-
const [, indent,
|
|
9310
|
+
const [, indent, path2, sep, tail] = m;
|
|
10172
9311
|
const countMatch = /^(\d+)(\s*)([+\-]*)\s*$/.exec(tail);
|
|
10173
9312
|
let coloredTail;
|
|
10174
9313
|
if (countMatch) {
|
|
@@ -10179,7 +9318,7 @@ function colorizeDiffStatLine(line, c) {
|
|
|
10179
9318
|
} else {
|
|
10180
9319
|
coloredTail = c.diffContext(tail);
|
|
10181
9320
|
}
|
|
10182
|
-
return `${indent}${c.diffContext(
|
|
9321
|
+
return `${indent}${c.diffContext(path2)}${c.diffContext(sep)}${coloredTail}`;
|
|
10183
9322
|
}
|
|
10184
9323
|
return c.diffContext(line);
|
|
10185
9324
|
}
|
|
@@ -12447,6 +11586,16 @@ var TRANSCRIPT_WINDOW_TAIL_MAX_ITEMS = positiveIntEnv("MIXDOG_TUI_TRANSCRIPT_WIN
|
|
|
12447
11586
|
var SELECTION_PAINT_INTERVAL_MS = positiveIntEnv("MIXDOG_TUI_SELECTION_PAINT_MS", 24);
|
|
12448
11587
|
var SCROLL_COALESCE_MS = positiveIntEnv("MIXDOG_TUI_SCROLL_COALESCE_MS", 16);
|
|
12449
11588
|
var PROMPT_HISTORY_LIMIT = 50;
|
|
11589
|
+
function accumulateDirectionalScrollDelta(state, deltaRows) {
|
|
11590
|
+
const delta = Number(deltaRows) || 0;
|
|
11591
|
+
if (!delta) return false;
|
|
11592
|
+
const direction = Math.sign(delta);
|
|
11593
|
+
const reversed = Boolean(state.direction && state.direction !== direction);
|
|
11594
|
+
if (reversed) state.pendingRows = 0;
|
|
11595
|
+
state.direction = direction;
|
|
11596
|
+
state.pendingRows += delta;
|
|
11597
|
+
return reversed;
|
|
11598
|
+
}
|
|
12450
11599
|
function boolEnvDefaultTrue(name) {
|
|
12451
11600
|
const raw = process.env[name];
|
|
12452
11601
|
if (raw == null) return true;
|
|
@@ -12784,6 +11933,17 @@ function transcriptRowAt(values, index) {
|
|
|
12784
11933
|
if (!values || index < 0 || index >= values.length) return 0;
|
|
12785
11934
|
return typeof values.atIndex === "function" ? values.atIndex(index) : Number(values[index]) || 0;
|
|
12786
11935
|
}
|
|
11936
|
+
function transcriptSwapReturnsToTail({
|
|
11937
|
+
count = 0,
|
|
11938
|
+
previousCount = 0,
|
|
11939
|
+
firstId = null,
|
|
11940
|
+
previousFirstId = null
|
|
11941
|
+
} = {}) {
|
|
11942
|
+
if (previousCount === 0) return true;
|
|
11943
|
+
if (count < previousCount) return true;
|
|
11944
|
+
if (count === 0) return false;
|
|
11945
|
+
return firstId != null && previousFirstId != null && firstId !== previousFirstId;
|
|
11946
|
+
}
|
|
12787
11947
|
function appendTranscriptRow(prefix, tailValue) {
|
|
12788
11948
|
return Object.freeze({
|
|
12789
11949
|
length: prefix.length + 1,
|
|
@@ -13378,7 +12538,7 @@ function useMouseInput({
|
|
|
13378
12538
|
// src/tui/app/use-transcript-scroll.mjs
|
|
13379
12539
|
import { useCallback as useCallback3, useEffect as useEffect8, useLayoutEffect as useLayoutEffect3, useRef as useRef8 } from "react";
|
|
13380
12540
|
|
|
13381
|
-
// src/tui/
|
|
12541
|
+
// src/tui/session/render-timing.mjs
|
|
13382
12542
|
var RENDER_ACK_HANG_GUARD_MS = 250;
|
|
13383
12543
|
var RENDER_SETTLE_IDLE_MS = 64;
|
|
13384
12544
|
var TUI_RENDER_FPS = 120;
|
|
@@ -13397,7 +12557,7 @@ var notifyRenderFrame = (seq = ++renderAckSeq) => {
|
|
|
13397
12557
|
pendingRenderAcks = [];
|
|
13398
12558
|
for (const ack of acks) ack(seq);
|
|
13399
12559
|
};
|
|
13400
|
-
var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((
|
|
12560
|
+
var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve4) => {
|
|
13401
12561
|
const minSeq = renderAckSeq;
|
|
13402
12562
|
let remainingFrames = Math.max(1, Math.floor(Number(frames) || 1));
|
|
13403
12563
|
let settled = false;
|
|
@@ -13409,7 +12569,7 @@ var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve5) => {
|
|
|
13409
12569
|
if (timer2) clearTimeout(timer2);
|
|
13410
12570
|
const idx = pendingRenderAcks.indexOf(onFrame);
|
|
13411
12571
|
if (idx !== -1) pendingRenderAcks.splice(idx, 1);
|
|
13412
|
-
|
|
12572
|
+
resolve4();
|
|
13413
12573
|
};
|
|
13414
12574
|
const onTimeout = () => {
|
|
13415
12575
|
if (settled) return;
|
|
@@ -13470,7 +12630,7 @@ function useTranscriptScroll({
|
|
|
13470
12630
|
const scrollAnimationRef = useRef8(null);
|
|
13471
12631
|
const pendingReadbackRowsRef = useRef8(0);
|
|
13472
12632
|
const selectionPaintRef = useRef8({ t: 0, rect: null, pending: null, timer: null });
|
|
13473
|
-
const scrollCoalesceRef = useRef8({ pendingRows: 0, timer: null });
|
|
12633
|
+
const scrollCoalesceRef = useRef8({ pendingRows: 0, direction: 0, timer: null });
|
|
13474
12634
|
const selectionTextCaptureRef = useRef8(0);
|
|
13475
12635
|
const stitchBufferRef = useRef8(/* @__PURE__ */ new Map());
|
|
13476
12636
|
const stitchHarvestTimerRef = useRef8(null);
|
|
@@ -13761,6 +12921,7 @@ function useTranscriptScroll({
|
|
|
13761
12921
|
if (coalesceState.timer) clearTimeout(coalesceState.timer);
|
|
13762
12922
|
coalesceState.timer = null;
|
|
13763
12923
|
coalesceState.pendingRows = 0;
|
|
12924
|
+
coalesceState.direction = 0;
|
|
13764
12925
|
}, []);
|
|
13765
12926
|
const scrollTranscriptRows = useCallback3((deltaRows, options = {}) => {
|
|
13766
12927
|
const maxTarget = Math.max(0, Number(maxScrollRowsRef.current) || 0);
|
|
@@ -13893,13 +13054,18 @@ function useTranscriptScroll({
|
|
|
13893
13054
|
});
|
|
13894
13055
|
const queueScrollCoalesced = useCallback3((deltaRows) => {
|
|
13895
13056
|
const state = scrollCoalesceRef.current;
|
|
13896
|
-
state
|
|
13057
|
+
const reversed = accumulateDirectionalScrollDelta(state, deltaRows);
|
|
13058
|
+
if (reversed && state.timer) {
|
|
13059
|
+
clearTimeout(state.timer);
|
|
13060
|
+
state.timer = null;
|
|
13061
|
+
}
|
|
13897
13062
|
if (state.timer) return;
|
|
13898
13063
|
const rows = state.pendingRows;
|
|
13899
13064
|
state.pendingRows = 0;
|
|
13900
13065
|
scrollTranscriptRows(rows);
|
|
13901
13066
|
state.timer = setTimeout(() => {
|
|
13902
13067
|
state.timer = null;
|
|
13068
|
+
state.direction = 0;
|
|
13903
13069
|
if (state.pendingRows !== 0) {
|
|
13904
13070
|
const remaining = state.pendingRows;
|
|
13905
13071
|
state.pendingRows = 0;
|
|
@@ -14121,17 +13287,18 @@ function usePromptHint() {
|
|
|
14121
13287
|
const [promptHintTone, setPromptHintTone] = useState8("info");
|
|
14122
13288
|
const promptHintTimerRef = useRef10(null);
|
|
14123
13289
|
const promptHintActiveRef = useRef10(false);
|
|
14124
|
-
const showPromptHint = useCallback5((text, tone = "info") => {
|
|
13290
|
+
const showPromptHint = useCallback5((text, tone = "info", dismissMs = PROMPT_HINT_DISMISS_MS) => {
|
|
14125
13291
|
if (promptHintTimerRef.current) clearTimeout(promptHintTimerRef.current);
|
|
14126
13292
|
promptHintActiveRef.current = true;
|
|
14127
13293
|
setPromptHint(String(text || ""));
|
|
14128
13294
|
setPromptHintTone(tone);
|
|
13295
|
+
const timeoutMs = Number(dismissMs) > 0 ? Number(dismissMs) : PROMPT_HINT_DISMISS_MS;
|
|
14129
13296
|
promptHintTimerRef.current = setTimeout(() => {
|
|
14130
13297
|
promptHintTimerRef.current = null;
|
|
14131
13298
|
promptHintActiveRef.current = false;
|
|
14132
13299
|
setPromptHint("");
|
|
14133
13300
|
setPromptHintTone("info");
|
|
14134
|
-
},
|
|
13301
|
+
}, timeoutMs);
|
|
14135
13302
|
}, []);
|
|
14136
13303
|
const showSelectionCopyHint = useCallback5(
|
|
14137
13304
|
(text, tone = "plain") => showPromptHint(text, tone),
|
|
@@ -14204,11 +13371,11 @@ function compactJson(value, max = 180) {
|
|
|
14204
13371
|
if (!text) return "";
|
|
14205
13372
|
return stringWidth8(text) > max ? `${text.slice(0, Math.max(1, max - 1))}\u2026` : text;
|
|
14206
13373
|
}
|
|
14207
|
-
function toolApprovalDescription(
|
|
14208
|
-
const surface = formatToolSurface(
|
|
14209
|
-
const summary = surface?.summary ? `${surface.label ||
|
|
14210
|
-
const reason = clean(
|
|
14211
|
-
const args = compactJson(
|
|
13374
|
+
function toolApprovalDescription(request = {}) {
|
|
13375
|
+
const surface = formatToolSurface(request.name, request.args);
|
|
13376
|
+
const summary = surface?.summary ? `${surface.label || request.name} (${surface.summary})` : surface?.label || request.name || "tool";
|
|
13377
|
+
const reason = clean(request.reason) || "Hook requested approval.";
|
|
13378
|
+
const args = compactJson(request.args);
|
|
14212
13379
|
return [
|
|
14213
13380
|
summary ? `Tool: ${summary}` : "",
|
|
14214
13381
|
reason ? `Reason: ${reason}` : "",
|
|
@@ -14515,8 +13682,8 @@ function createUsageContextPanels({
|
|
|
14515
13682
|
const context = contextStatusValue || {};
|
|
14516
13683
|
const usage = context.usage || {};
|
|
14517
13684
|
const messages = context.messages || {};
|
|
14518
|
-
const
|
|
14519
|
-
const schemaBreakdown =
|
|
13685
|
+
const request = context.request || {};
|
|
13686
|
+
const schemaBreakdown = request.toolSchemaBreakdown || {};
|
|
14520
13687
|
const schemaTokensFor = (buckets) => buckets.reduce(
|
|
14521
13688
|
(sum, bucket) => sum + Number(schemaBreakdown?.[bucket]?.tokens || 0),
|
|
14522
13689
|
0
|
|
@@ -14602,7 +13769,7 @@ function createUsageContextPanels({
|
|
|
14602
13769
|
{
|
|
14603
13770
|
value: "request",
|
|
14604
13771
|
label: "Request overhead",
|
|
14605
|
-
description: `${fmt(
|
|
13772
|
+
description: `${fmt(request.requestOverheadTokens)} framing \xB7 ${fmt(request.reserveTokens)} reserve incl. tools`,
|
|
14606
13773
|
_action: "request"
|
|
14607
13774
|
},
|
|
14608
13775
|
{
|
|
@@ -14678,9 +13845,9 @@ function createUsageContextPanels({
|
|
|
14678
13845
|
results: messages.toolResultCount
|
|
14679
13846
|
},
|
|
14680
13847
|
request: {
|
|
14681
|
-
toolSchemaBreakdown:
|
|
14682
|
-
overheadTokens:
|
|
14683
|
-
reserveTokens:
|
|
13848
|
+
toolSchemaBreakdown: request.toolSchemaBreakdown,
|
|
13849
|
+
overheadTokens: request.requestOverheadTokens,
|
|
13850
|
+
reserveTokens: request.reserveTokens
|
|
14684
13851
|
},
|
|
14685
13852
|
lastApi: {
|
|
14686
13853
|
contextTokens: usage.lastContextTokens,
|
|
@@ -14746,10 +13913,17 @@ function createPromptSubmit({
|
|
|
14746
13913
|
pastedImagesRef,
|
|
14747
13914
|
pastedTextsRef
|
|
14748
13915
|
}) {
|
|
13916
|
+
const serviceCall = (name, ...args) => {
|
|
13917
|
+
const target = store?.[name];
|
|
13918
|
+
if (typeof target !== "function") {
|
|
13919
|
+
return Promise.reject(new TypeError(`project service method ${name} is unavailable`));
|
|
13920
|
+
}
|
|
13921
|
+
return Promise.resolve(target.apply(store, args));
|
|
13922
|
+
};
|
|
14749
13923
|
const submitPrompt = (prompt, options) => {
|
|
14750
13924
|
if (typeof store.submitAsync !== "function") return store.submit(prompt, options);
|
|
14751
13925
|
void Promise.resolve(store.submitAsync(prompt, options)).then((accepted) => {
|
|
14752
|
-
if (accepted === false) store.pushNotice("prompt was not accepted by the session
|
|
13926
|
+
if (accepted === false) store.pushNotice("prompt was not accepted by the session service", "error");
|
|
14753
13927
|
}).catch((error) => {
|
|
14754
13928
|
store.pushNotice(`prompt submit failed: ${error?.message || error}`, "error");
|
|
14755
13929
|
});
|
|
@@ -14875,7 +14049,7 @@ function createPromptSubmit({
|
|
|
14875
14049
|
const channelId = isPipe ? parts[1] : parts[0];
|
|
14876
14050
|
Promise.resolve(store.setChannel({
|
|
14877
14051
|
channelId,
|
|
14878
|
-
|
|
14052
|
+
provider: channelPrompt.provider
|
|
14879
14053
|
})).then(() => resumeAfterChannelPrompt(channelPrompt)).catch((e) => {
|
|
14880
14054
|
store.pushNotice(`channel save failed: ${e?.message || e}`, "error");
|
|
14881
14055
|
});
|
|
@@ -14919,9 +14093,14 @@ function createPromptSubmit({
|
|
|
14919
14093
|
store.pushNotice("working directory path is required", "warn");
|
|
14920
14094
|
return false;
|
|
14921
14095
|
}
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14096
|
+
void serviceCall("setCwd", commandText, {
|
|
14097
|
+
message: `Project set: ${projectNameFromPath(commandText)}`
|
|
14098
|
+
}).then(() => {
|
|
14099
|
+
setSettingsPrompt(null);
|
|
14100
|
+
void openSettingsPicker();
|
|
14101
|
+
}).catch((error) => {
|
|
14102
|
+
store.pushNotice(`project switch failed: ${error?.message || error}`, "error");
|
|
14103
|
+
});
|
|
14925
14104
|
return true;
|
|
14926
14105
|
}
|
|
14927
14106
|
if (settingsPrompt.kind === "project-new") {
|
|
@@ -14929,21 +14108,25 @@ function createPromptSubmit({
|
|
|
14929
14108
|
store.pushNotice("project path is required", "warn");
|
|
14930
14109
|
return false;
|
|
14931
14110
|
}
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14111
|
+
void serviceCall("inspectProjectPath", commandText).then((result) => {
|
|
14112
|
+
const path2 = String(result?.path || commandText);
|
|
14113
|
+
if (result?.directory === true) {
|
|
14114
|
+
setSettingsPrompt(null);
|
|
14115
|
+
void registerProject(path2);
|
|
14116
|
+
return;
|
|
14117
|
+
}
|
|
14118
|
+
if (result?.exists === true) {
|
|
14119
|
+
store.pushNotice(`${path2} is not a directory`, "warn");
|
|
14120
|
+
return;
|
|
14121
|
+
}
|
|
14122
|
+
setSettingsPrompt({
|
|
14123
|
+
kind: "project-create-confirm",
|
|
14124
|
+
label: "New project \xB7 Create folder?",
|
|
14125
|
+
hint: `${path2} does not exist. Type "y" to create it, or anything else to cancel.`,
|
|
14126
|
+
pendingPath: path2
|
|
14127
|
+
});
|
|
14128
|
+
}).catch((error) => {
|
|
14129
|
+
store.pushNotice(`project path check failed: ${error?.message || error}`, "error");
|
|
14947
14130
|
});
|
|
14948
14131
|
return true;
|
|
14949
14132
|
}
|
|
@@ -14951,14 +14134,13 @@ function createPromptSubmit({
|
|
|
14951
14134
|
const pendingPath = String(settingsPrompt.pendingPath || "");
|
|
14952
14135
|
const answer = String(commandText || "").trim().toLowerCase();
|
|
14953
14136
|
if (answer === "y" || answer === "yes") {
|
|
14954
|
-
|
|
14955
|
-
if (!created) {
|
|
14956
|
-
store.pushNotice(`could not create folder: ${pendingPath}`, "error");
|
|
14137
|
+
void serviceCall("ensureProjectDirectory", pendingPath).then((created) => {
|
|
14957
14138
|
setSettingsPrompt(null);
|
|
14958
|
-
|
|
14959
|
-
}
|
|
14960
|
-
|
|
14961
|
-
|
|
14139
|
+
void registerProject(created || pendingPath);
|
|
14140
|
+
}).catch((error) => {
|
|
14141
|
+
store.pushNotice(`could not create folder: ${error?.message || error}`, "error");
|
|
14142
|
+
setSettingsPrompt(null);
|
|
14143
|
+
});
|
|
14962
14144
|
return true;
|
|
14963
14145
|
}
|
|
14964
14146
|
setSettingsPrompt(null);
|
|
@@ -14967,16 +14149,15 @@ function createPromptSubmit({
|
|
|
14967
14149
|
}
|
|
14968
14150
|
if (settingsPrompt.kind === "project-rename") {
|
|
14969
14151
|
const targetPath = String(settingsPrompt.projectPath || "");
|
|
14970
|
-
|
|
14971
|
-
const updated = renameProject(targetPath, commandText);
|
|
14152
|
+
void serviceCall("renameProject", targetPath, commandText).then((updated) => {
|
|
14972
14153
|
if (updated) {
|
|
14973
14154
|
store.pushNotice(`project renamed to "${updated.name}"`, "info");
|
|
14974
14155
|
}
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
}
|
|
14978
|
-
|
|
14979
|
-
|
|
14156
|
+
setSettingsPrompt(null);
|
|
14157
|
+
void openProjectPicker();
|
|
14158
|
+
}).catch((error) => {
|
|
14159
|
+
store.pushNotice(`rename failed: ${error?.message || error}`, "error");
|
|
14160
|
+
});
|
|
14980
14161
|
return true;
|
|
14981
14162
|
}
|
|
14982
14163
|
if (settingsPrompt.kind === "system-shell") {
|
|
@@ -16960,7 +16141,7 @@ var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpa
|
|
|
16960
16141
|
// src/tui/app/app-view.jsx
|
|
16961
16142
|
import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
16962
16143
|
function renderAppView(ctx) {
|
|
16963
|
-
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;
|
|
16144
|
+
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;
|
|
16964
16145
|
const promptInputControl = /* @__PURE__ */ jsx20(
|
|
16965
16146
|
PromptInput,
|
|
16966
16147
|
{
|
|
@@ -16984,6 +16165,7 @@ function renderAppView(ctx) {
|
|
|
16984
16165
|
onPasteText: handlePromptPaste,
|
|
16985
16166
|
onHistoryNavigate: handlePromptHistoryNavigate,
|
|
16986
16167
|
commandPaletteActive: slashPaletteOpen && slashCommands.length > 0,
|
|
16168
|
+
commandPaletteOpen: slashPaletteOpen,
|
|
16987
16169
|
onCommandPaletteNavigate: (direction) => {
|
|
16988
16170
|
setSlashIndex((index) => {
|
|
16989
16171
|
const total = slashCommands.length;
|
|
@@ -16998,7 +16180,9 @@ function renderAppView(ctx) {
|
|
|
16998
16180
|
onCommandPaletteAccept: acceptSlashPalette,
|
|
16999
16181
|
onCommandPaletteCancel: cancelSlashPalette,
|
|
17000
16182
|
onCommandPaletteComplete: completeSlashPalette,
|
|
17001
|
-
onRestoreQueued:
|
|
16183
|
+
onRestoreQueued: restoreQueuedToPrompt,
|
|
16184
|
+
hasQueuedMessages: Array.isArray(state.queued) && state.queued.length > 0,
|
|
16185
|
+
hasMessages: hasUserMessages
|
|
17002
16186
|
}
|
|
17003
16187
|
);
|
|
17004
16188
|
return (
|
|
@@ -17448,10 +16632,8 @@ function usePromptDraftFlow({
|
|
|
17448
16632
|
return token ? `/${token} ` : void 0;
|
|
17449
16633
|
}, [slashCommands, slashIndex]);
|
|
17450
16634
|
const cancelSlashPalette = useCallback8((value = "") => {
|
|
17451
|
-
setSlashDismissedFor("");
|
|
17452
|
-
|
|
17453
|
-
setPromptDraftOverride({ id: Date.now(), value: "" });
|
|
17454
|
-
}, []);
|
|
16635
|
+
setSlashDismissedFor(String(value ?? ""));
|
|
16636
|
+
}, [setSlashDismissedFor]);
|
|
17455
16637
|
return {
|
|
17456
16638
|
onPromptDraftChange,
|
|
17457
16639
|
cancelProviderPrompt,
|
|
@@ -17694,10 +16876,10 @@ function createResumePicker({
|
|
|
17694
16876
|
formatSessionUpdatedAt: formatSessionUpdatedAt2,
|
|
17695
16877
|
formatSessionMessageCount: formatSessionMessageCount2
|
|
17696
16878
|
}) {
|
|
17697
|
-
const openResumePicker = () => {
|
|
16879
|
+
const openResumePicker = async () => {
|
|
17698
16880
|
let sessions;
|
|
17699
16881
|
try {
|
|
17700
|
-
sessions = store.listSessions({ refreshFromStorage: true });
|
|
16882
|
+
sessions = await store.listSessions({ refreshFromStorage: true });
|
|
17701
16883
|
} catch (e) {
|
|
17702
16884
|
store.pushNotice(`could not list saved chats: ${e?.message || e}`, "error");
|
|
17703
16885
|
return;
|
|
@@ -17762,16 +16944,16 @@ function createCoreMemoryPicker({
|
|
|
17762
16944
|
onCancel: closeMemoryCorePicker
|
|
17763
16945
|
});
|
|
17764
16946
|
void store.memoryControl?.({ action: "core", op: "list", project_id: "*" }, { silent: true }).then(async (result) => {
|
|
17765
|
-
const
|
|
17766
|
-
const
|
|
16947
|
+
const recap = await store.getRecapSettings?.() || { enabled: true };
|
|
16948
|
+
const recapOn = recap.enabled !== false;
|
|
17767
16949
|
const coreRows = parseMemoryCoreRows2(result);
|
|
17768
16950
|
const rows = [
|
|
17769
16951
|
{
|
|
17770
|
-
value: "
|
|
17771
|
-
label: "
|
|
17772
|
-
meta:
|
|
17773
|
-
description:
|
|
17774
|
-
_action: "toggle-
|
|
16952
|
+
value: "recap-toggle",
|
|
16953
|
+
label: "Recap",
|
|
16954
|
+
meta: recapOn ? "On" : "Off",
|
|
16955
|
+
description: recapOn ? "Background session recaps on" : "Background recaps off; core memory remains available",
|
|
16956
|
+
_action: "toggle-recap"
|
|
17775
16957
|
},
|
|
17776
16958
|
{ value: "core-add", label: "Add Memory", description: "store a new curated memory sentence", _action: "add-core" },
|
|
17777
16959
|
{
|
|
@@ -17793,17 +16975,17 @@ function createCoreMemoryPicker({
|
|
|
17793
16975
|
labelWidth: 12,
|
|
17794
16976
|
footer: (item) => item && item._action === "core-entry" ? item._summary || item._element || "" : "",
|
|
17795
16977
|
onSelect: (_value, item) => {
|
|
17796
|
-
if (item?._action === "toggle-
|
|
16978
|
+
if (item?._action === "toggle-recap") toggleRecapEnabled();
|
|
17797
16979
|
else if (item?._action === "add-core") beginAddCoreMemory();
|
|
17798
16980
|
else if (item?._action === "core-list") openCoreMemoryListPicker(item._rows);
|
|
17799
16981
|
else if (item?._line) store.pushNotice(item._line, "info");
|
|
17800
16982
|
},
|
|
17801
16983
|
// Toggle rows also respond to ←/→ (matches settings/channels UX).
|
|
17802
16984
|
onLeft: (item) => {
|
|
17803
|
-
if (item?._action === "toggle-
|
|
16985
|
+
if (item?._action === "toggle-recap") toggleRecapEnabled();
|
|
17804
16986
|
},
|
|
17805
16987
|
onRight: (item) => {
|
|
17806
|
-
if (item?._action === "toggle-
|
|
16988
|
+
if (item?._action === "toggle-recap") toggleRecapEnabled();
|
|
17807
16989
|
},
|
|
17808
16990
|
onCancel: closeMemoryCorePicker
|
|
17809
16991
|
});
|
|
@@ -17844,12 +17026,12 @@ function createCoreMemoryPicker({
|
|
|
17844
17026
|
store.pushNotice(`core memory failed: ${e?.message || e}`, "error");
|
|
17845
17027
|
});
|
|
17846
17028
|
};
|
|
17847
|
-
const
|
|
17848
|
-
const
|
|
17849
|
-
void Promise.resolve(store.
|
|
17850
|
-
if (!next) store.pushNotice("
|
|
17851
|
-
else store.pushNotice(`
|
|
17852
|
-
}).catch((e) => store.pushNotice(`
|
|
17029
|
+
const toggleRecapEnabled = async () => {
|
|
17030
|
+
const recap = await store.getRecapSettings?.() || { enabled: true };
|
|
17031
|
+
void Promise.resolve(store.setRecapEnabled?.(!(recap.enabled !== false))).then((next) => {
|
|
17032
|
+
if (!next) store.pushNotice("recap setting is busy", "warn");
|
|
17033
|
+
else store.pushNotice(`Recap ${next.enabled ? "on" : "off"}`, "info");
|
|
17034
|
+
}).catch((e) => store.pushNotice(`recap toggle failed: ${e?.message || e}`, "error")).finally(() => openMemoryCorePicker());
|
|
17853
17035
|
};
|
|
17854
17036
|
const openCoreEntryActionsPicker = (entryItem) => {
|
|
17855
17037
|
setPicker({
|
|
@@ -19107,7 +18289,6 @@ function createOnboardingSteps({
|
|
|
19107
18289
|
if (defaultRoute) {
|
|
19108
18290
|
void store.completeOnboarding?.({
|
|
19109
18291
|
defaultRoute,
|
|
19110
|
-
defaultProvider: defaultRoute.provider,
|
|
19111
18292
|
...hasOverrides ? { agentRoutes: { ...overrides } } : {},
|
|
19112
18293
|
...searchRoute ? { searchRoute } : {}
|
|
19113
18294
|
}).then(done).catch(failed);
|
|
@@ -19433,7 +18614,7 @@ function createChannelPickers({
|
|
|
19433
18614
|
onboardingWarnReopen
|
|
19434
18615
|
}) {
|
|
19435
18616
|
let pickerGen = 0;
|
|
19436
|
-
const openChannelTypeActionsPicker = async (
|
|
18617
|
+
const openChannelTypeActionsPicker = async (provider, options = {}) => {
|
|
19437
18618
|
pickerGen += 1;
|
|
19438
18619
|
const parentReturn = typeof options.returnTo === "function" ? options.returnTo : () => openChannelSettingTypePicker();
|
|
19439
18620
|
setProviderPrompt(null);
|
|
@@ -19447,23 +18628,23 @@ function createChannelPickers({
|
|
|
19447
18628
|
store.pushNotice(`channels failed: ${e?.message || e}`, "error");
|
|
19448
18629
|
return;
|
|
19449
18630
|
}
|
|
19450
|
-
const isTelegram =
|
|
19451
|
-
const
|
|
18631
|
+
const isTelegram = provider === "telegram";
|
|
18632
|
+
const activeProvider = setup.provider || "discord";
|
|
19452
18633
|
const tokenDescription = isTelegram ? `${setup.telegram?.status ?? "Off"}${setup.telegram?.problem ? " \xB7 Invalid" : ""}` : `${setup.discord.status}${setup.discord.problem ? " \xB7 Invalid" : ""}`;
|
|
19453
18634
|
const mainEntry = setup.channel || {};
|
|
19454
|
-
const mainTarget = isTelegram ? mainEntry?.telegramChatId || (
|
|
18635
|
+
const mainTarget = isTelegram ? mainEntry?.telegramChatId || (activeProvider === "telegram" ? mainEntry?.channelId : "") : mainEntry?.discordChannelId || (activeProvider === "discord" ? mainEntry?.channelId : "");
|
|
19455
18636
|
const mainDescription = isTelegram ? mainTarget ? `Chat ID ${mainTarget}` : "Not set \xB7 Enter Telegram chat ID" : mainTarget ? `Channel ID ${mainTarget}` : "Not set \xB7 Enter Discord channel ID";
|
|
19456
18637
|
const openChannelPrompt = (prompt) => {
|
|
19457
18638
|
setPicker(null);
|
|
19458
18639
|
setContextPanel(null);
|
|
19459
18640
|
setChannelPrompt({
|
|
19460
18641
|
...prompt,
|
|
19461
|
-
afterSave: () => openChannelTypeActionsPicker(
|
|
18642
|
+
afterSave: () => openChannelTypeActionsPicker(provider, options)
|
|
19462
18643
|
});
|
|
19463
18644
|
};
|
|
19464
18645
|
setPicker({
|
|
19465
18646
|
title: isTelegram ? "Telegram" : "Discord",
|
|
19466
|
-
description:
|
|
18647
|
+
description: activeProvider === provider ? "Active channel type \xB7 token and main target" : "Token and main target settings",
|
|
19467
18648
|
help: "\u2191/\u2193 Select \xB7 Enter Edit \xB7 Esc Back",
|
|
19468
18649
|
indexMode: "always",
|
|
19469
18650
|
labelWidth: 18,
|
|
@@ -19502,7 +18683,7 @@ function createChannelPickers({
|
|
|
19502
18683
|
if (item._action === "main-target") {
|
|
19503
18684
|
openChannelPrompt({
|
|
19504
18685
|
kind: "channel-add",
|
|
19505
|
-
|
|
18686
|
+
provider,
|
|
19506
18687
|
label: isTelegram ? "Main chat" : "Main channel",
|
|
19507
18688
|
hint: isTelegram ? "Paste the Telegram chat ID." : "Paste the Discord channel ID."
|
|
19508
18689
|
});
|
|
@@ -19532,15 +18713,15 @@ function createChannelPickers({
|
|
|
19532
18713
|
store.pushNotice(`channels failed: ${e?.message || e}`, "error");
|
|
19533
18714
|
return;
|
|
19534
18715
|
}
|
|
19535
|
-
const
|
|
18716
|
+
const activeProvider = setup.provider || "discord";
|
|
19536
18717
|
const mainEntry = setup.channel || {};
|
|
19537
|
-
const typeDescription = (
|
|
19538
|
-
const selected =
|
|
19539
|
-
const hasToken =
|
|
19540
|
-
const hasTarget =
|
|
18718
|
+
const typeDescription = (provider) => {
|
|
18719
|
+
const selected = activeProvider === provider;
|
|
18720
|
+
const hasToken = provider === "telegram" ? setup.telegram?.authenticated === true : setup.discord?.authenticated === true;
|
|
18721
|
+
const hasTarget = provider === "telegram" ? Boolean(mainEntry?.telegramChatId || activeProvider === "telegram" && mainEntry?.channelId) : Boolean(mainEntry?.discordChannelId || activeProvider === "discord" && mainEntry?.channelId);
|
|
19541
18722
|
const needs = [
|
|
19542
18723
|
...hasToken ? [] : ["token"],
|
|
19543
|
-
...hasTarget ? [] : [
|
|
18724
|
+
...hasTarget ? [] : [provider === "telegram" ? "chat ID" : "channel ID"]
|
|
19544
18725
|
];
|
|
19545
18726
|
return [
|
|
19546
18727
|
...selected ? ["Selected"] : [],
|
|
@@ -19549,7 +18730,7 @@ function createChannelPickers({
|
|
|
19549
18730
|
};
|
|
19550
18731
|
setPicker({
|
|
19551
18732
|
title: "Channel Type Settings",
|
|
19552
|
-
description: "Choose a type. Selected is the active
|
|
18733
|
+
description: "Choose a type. Selected is the active provider; Ready means token and main target are set.",
|
|
19553
18734
|
help: "\u2191/\u2193 Select \xB7 Enter Open \xB7 Esc Back",
|
|
19554
18735
|
indexMode: "always",
|
|
19555
18736
|
labelWidth: 18,
|
|
@@ -19558,20 +18739,20 @@ function createChannelPickers({
|
|
|
19558
18739
|
value: "discord",
|
|
19559
18740
|
label: "Discord",
|
|
19560
18741
|
description: typeDescription("discord"),
|
|
19561
|
-
|
|
18742
|
+
_provider: "discord"
|
|
19562
18743
|
},
|
|
19563
18744
|
{
|
|
19564
18745
|
value: "telegram",
|
|
19565
18746
|
label: "Telegram",
|
|
19566
18747
|
description: typeDescription("telegram"),
|
|
19567
|
-
|
|
18748
|
+
_provider: "telegram"
|
|
19568
18749
|
}
|
|
19569
18750
|
],
|
|
19570
18751
|
onSelect: (value, item) => {
|
|
19571
|
-
const
|
|
19572
|
-
if (!
|
|
18752
|
+
const provider = item?._provider || (value === "telegram" ? "telegram" : value === "discord" ? "discord" : null);
|
|
18753
|
+
if (!provider) return;
|
|
19573
18754
|
setPicker(null);
|
|
19574
|
-
openChannelTypeActionsPicker(
|
|
18755
|
+
openChannelTypeActionsPicker(provider, {
|
|
19575
18756
|
returnTo: () => openChannelSettingTypePicker(options)
|
|
19576
18757
|
});
|
|
19577
18758
|
},
|
|
@@ -19601,8 +18782,8 @@ function createChannelPickers({
|
|
|
19601
18782
|
setChannelPrompt(prompt);
|
|
19602
18783
|
};
|
|
19603
18784
|
const worker = await store.getChannelWorkerStatus?.();
|
|
19604
|
-
const
|
|
19605
|
-
const
|
|
18785
|
+
const activeProvider = setup.provider === "telegram" ? "telegram" : "discord";
|
|
18786
|
+
const providerLabel = activeProvider === "telegram" ? "Telegram" : "Discord";
|
|
19606
18787
|
const remoteEnabled = await store.isRemoteEnabled?.() === true;
|
|
19607
18788
|
const boolLabel = (enabled) => enabled ? "On" : "Off";
|
|
19608
18789
|
const reopenRoot = (extra = {}) => {
|
|
@@ -19612,23 +18793,23 @@ function createChannelPickers({
|
|
|
19612
18793
|
const applyRemoteRuntime = (highlightValue = "remote-runtime") => {
|
|
19613
18794
|
void Promise.resolve(store.toggleRemote?.()).then((enabled) => store.pushNotice(enabled === true ? "Remote mode ON" : "Remote mode OFF", "info")).catch((e) => store.pushNotice(`Remote toggle failed: ${e?.message || e}`, "error")).finally(() => reopenRoot({ highlightValue }));
|
|
19614
18795
|
};
|
|
19615
|
-
const
|
|
19616
|
-
const
|
|
19617
|
-
const currentIndex = Math.max(0,
|
|
19618
|
-
const chosen =
|
|
19619
|
-
if (chosen ===
|
|
19620
|
-
reopenRoot({ highlightValue,
|
|
18796
|
+
const cycleChannelProvider = (direction = 1, highlightValue = "channel-provider") => {
|
|
18797
|
+
const providers = ["discord", "telegram"];
|
|
18798
|
+
const currentIndex = Math.max(0, providers.indexOf(activeProvider));
|
|
18799
|
+
const chosen = providers[(currentIndex + direction + providers.length) % providers.length];
|
|
18800
|
+
if (chosen === activeProvider) {
|
|
18801
|
+
reopenRoot({ highlightValue, providerOverride: activeProvider });
|
|
19621
18802
|
return;
|
|
19622
18803
|
}
|
|
19623
18804
|
try {
|
|
19624
|
-
store.
|
|
18805
|
+
store.setChannelProvider(chosen);
|
|
19625
18806
|
const label = chosen === "telegram" ? "Telegram" : "Discord";
|
|
19626
18807
|
const restartHint = remoteEnabled || worker?.running ? `Channel set to ${label}. Restart remote to apply.` : `Channel set to ${label}.`;
|
|
19627
18808
|
store.pushNotice(restartHint, "info");
|
|
19628
18809
|
} catch (e) {
|
|
19629
|
-
store.pushNotice(`channel
|
|
18810
|
+
store.pushNotice(`channel provider failed: ${e?.message || e}`, "error");
|
|
19630
18811
|
}
|
|
19631
|
-
reopenRoot({ highlightValue,
|
|
18812
|
+
reopenRoot({ highlightValue, providerOverride: chosen });
|
|
19632
18813
|
};
|
|
19633
18814
|
const applyVoice = (highlightValue = "voice") => {
|
|
19634
18815
|
if (isVoiceInstallBusy()) {
|
|
@@ -19649,11 +18830,11 @@ function createChannelPickers({
|
|
|
19649
18830
|
_action: "remote-runtime"
|
|
19650
18831
|
},
|
|
19651
18832
|
{
|
|
19652
|
-
value: "channel-
|
|
18833
|
+
value: "channel-provider",
|
|
19653
18834
|
label: "Channel",
|
|
19654
|
-
meta:
|
|
18835
|
+
meta: providerLabel,
|
|
19655
18836
|
description: "Select Discord or Telegram",
|
|
19656
|
-
_action: "channel-
|
|
18837
|
+
_action: "channel-provider"
|
|
19657
18838
|
},
|
|
19658
18839
|
{
|
|
19659
18840
|
value: "channel-setting",
|
|
@@ -19688,18 +18869,18 @@ function createChannelPickers({
|
|
|
19688
18869
|
indexMode: "always",
|
|
19689
18870
|
labelWidth: 18,
|
|
19690
18871
|
metaWidth: 12,
|
|
19691
|
-
pickerKey: `channels:${
|
|
18872
|
+
pickerKey: `channels:${activeProvider}:${remoteEnabled ? "on" : "off"}:${options.highlightValue || "root"}`,
|
|
19692
18873
|
initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
|
|
19693
18874
|
items,
|
|
19694
18875
|
confirmBar: options.confirmBar || null,
|
|
19695
18876
|
onLeft: options.confirmBar ? void 0 : (item) => {
|
|
19696
18877
|
if (item?._action === "remote-runtime") applyRemoteRuntime(item.value);
|
|
19697
|
-
else if (item?._action === "channel-
|
|
18878
|
+
else if (item?._action === "channel-provider") cycleChannelProvider(-1, item.value);
|
|
19698
18879
|
else if (item?._action === "voice") applyVoice(item.value);
|
|
19699
18880
|
},
|
|
19700
18881
|
onRight: options.confirmBar ? void 0 : (item) => {
|
|
19701
18882
|
if (item?._action === "remote-runtime") applyRemoteRuntime(item.value);
|
|
19702
|
-
else if (item?._action === "channel-
|
|
18883
|
+
else if (item?._action === "channel-provider") cycleChannelProvider(1, item.value);
|
|
19703
18884
|
else if (item?._action === "voice") applyVoice(item.value);
|
|
19704
18885
|
},
|
|
19705
18886
|
onSelect: (_value, item) => {
|
|
@@ -19708,8 +18889,8 @@ function createChannelPickers({
|
|
|
19708
18889
|
applyRemoteRuntime(item.value);
|
|
19709
18890
|
return;
|
|
19710
18891
|
}
|
|
19711
|
-
if (item._action === "channel-
|
|
19712
|
-
|
|
18892
|
+
if (item._action === "channel-provider") {
|
|
18893
|
+
cycleChannelProvider(1, item.value);
|
|
19713
18894
|
return;
|
|
19714
18895
|
}
|
|
19715
18896
|
if (item._action === "voice") {
|
|
@@ -19788,7 +18969,7 @@ function createProviderSetupPicker({
|
|
|
19788
18969
|
}
|
|
19789
18970
|
});
|
|
19790
18971
|
try {
|
|
19791
|
-
await new Promise((
|
|
18972
|
+
await new Promise((resolve4) => setTimeout(resolve4, 0));
|
|
19792
18973
|
setup = await store.getProviderSetup();
|
|
19793
18974
|
} catch (e) {
|
|
19794
18975
|
store.pushNotice(`providers failed: ${e?.message || e}`, "error");
|
|
@@ -20327,7 +19508,7 @@ function createModelPicker({
|
|
|
20327
19508
|
items: [],
|
|
20328
19509
|
onCancel: cancelModelPicker
|
|
20329
19510
|
});
|
|
20330
|
-
await new Promise((
|
|
19511
|
+
await new Promise((resolve4) => setTimeout(resolve4, 0));
|
|
20331
19512
|
try {
|
|
20332
19513
|
if (options.refreshModels !== true && options.cacheRef !== "search") {
|
|
20333
19514
|
refreshModelsPromise = Promise.resolve(loadModels({ force: false }));
|
|
@@ -20682,7 +19863,7 @@ function createRoutePickers({
|
|
|
20682
19863
|
const items = agents.map((agent) => ({
|
|
20683
19864
|
value: agent.id,
|
|
20684
19865
|
label: agent.label,
|
|
20685
|
-
metaParts: agentModelParts2(routeOverrides[agent.id] || agent.route),
|
|
19866
|
+
metaParts: routeOverrides[agent.id] || agent.route ? agentModelParts2(routeOverrides[agent.id] || agent.route) : [{ text: "(follows main)", width: 17 }, { text: "", width: 6 }, { text: "", width: 4 }],
|
|
20686
19867
|
description: agent.description || agent.definition?.description || "",
|
|
20687
19868
|
_agent: agent
|
|
20688
19869
|
}));
|
|
@@ -20909,7 +20090,7 @@ function createSettingsPicker({
|
|
|
20909
20090
|
const snapshot = await store.getSettingsSnapshot?.({ heavy: !heavyCache }) || {};
|
|
20910
20091
|
const autoClear = snapshot.autoClear || {};
|
|
20911
20092
|
const compaction = snapshot.compaction || {};
|
|
20912
|
-
const
|
|
20093
|
+
const recap = snapshot.recap || { enabled: true };
|
|
20913
20094
|
const channels = snapshot.channels || { enabled: true };
|
|
20914
20095
|
const systemShell = snapshot.systemShell || { source: "auto", command: "", effective: "" };
|
|
20915
20096
|
const outputStyle = snapshot.outputStyle || {};
|
|
@@ -20919,17 +20100,17 @@ function createSettingsPicker({
|
|
|
20919
20100
|
const plugins = heavyCache ? heavyCache.plugins : snapshot.plugins || { count: 0 };
|
|
20920
20101
|
const skills = heavyCache ? heavyCache.skills : snapshot.skills || { count: 0 };
|
|
20921
20102
|
const channelWorker = snapshot.channelWorker || null;
|
|
20922
|
-
const
|
|
20923
|
-
const
|
|
20924
|
-
settingsHeavyCacheRef.current = { mcp, hooks, plugins, skills,
|
|
20925
|
-
const
|
|
20103
|
+
const channelProvider = heavyCache ? heavyCache.channelProvider || "discord" : snapshot.channelProvider || "discord";
|
|
20104
|
+
const effectiveChannelProvider = overrides && Object.prototype.hasOwnProperty.call(overrides, "channelProvider") ? overrides.channelProvider : channelProvider;
|
|
20105
|
+
settingsHeavyCacheRef.current = { mcp, hooks, plugins, skills, channelProvider: effectiveChannelProvider };
|
|
20106
|
+
const channelProviderLabel = effectiveChannelProvider === "telegram" ? "Telegram" : "Discord";
|
|
20926
20107
|
const remoteEnabled = snapshot.remoteEnabled === true;
|
|
20927
20108
|
const remoteRuntimeDescription = channelWorker?.running ? `runtime running \xB7 pid ${channelWorker.pid}` : "Stopped. Manual ON claims remote from any other session.";
|
|
20928
20109
|
const compactTypeLabel = "Fast-track (fixed)";
|
|
20929
20110
|
const outputStyleLabel = outputStyle?.current?.label || outputStyle?.current?.id || outputStyle?.configured || "Default";
|
|
20930
20111
|
const workflowLabel = workflowDisplayName2(workflow);
|
|
20931
20112
|
const boolLabel = (enabled) => enabled ? "On" : "Off";
|
|
20932
|
-
const compactTypeDescription =
|
|
20113
|
+
const compactTypeDescription = "Uses Memory recall to rebuild context faster on large histories.";
|
|
20933
20114
|
const applyAutoClear = (patch = {}) => {
|
|
20934
20115
|
void Promise.resolve(store.setAutoClear?.(patch)).then((next) => {
|
|
20935
20116
|
if (!next) store.pushNotice("autoclear unavailable", "warn");
|
|
@@ -21033,23 +20214,23 @@ function createSettingsPicker({
|
|
|
21033
20214
|
void openSettingsPicker({ light: true });
|
|
21034
20215
|
});
|
|
21035
20216
|
};
|
|
21036
|
-
const
|
|
21037
|
-
const
|
|
21038
|
-
const currentIndex = Math.max(0,
|
|
21039
|
-
const chosen =
|
|
21040
|
-
if (chosen ===
|
|
21041
|
-
openSettingsPicker({ light: true, overrides: {
|
|
20217
|
+
const cycleChannelProvider = (direction = 1) => {
|
|
20218
|
+
const providers = ["discord", "telegram"];
|
|
20219
|
+
const currentIndex = Math.max(0, providers.indexOf(effectiveChannelProvider));
|
|
20220
|
+
const chosen = providers[(currentIndex + direction + providers.length) % providers.length];
|
|
20221
|
+
if (chosen === effectiveChannelProvider) {
|
|
20222
|
+
openSettingsPicker({ light: true, overrides: { channelProvider: effectiveChannelProvider } });
|
|
21042
20223
|
return;
|
|
21043
20224
|
}
|
|
21044
20225
|
try {
|
|
21045
|
-
store.
|
|
20226
|
+
store.setChannelProvider(chosen);
|
|
21046
20227
|
const label = chosen === "telegram" ? "Telegram" : "Discord";
|
|
21047
20228
|
const restartHint = remoteEnabled || channelWorker?.running ? `Channel set to ${label}. Restart remote to apply.` : `Channel set to ${label}.`;
|
|
21048
20229
|
store.pushNotice(restartHint, "info");
|
|
21049
20230
|
} catch (e) {
|
|
21050
|
-
store.pushNotice(`channel
|
|
20231
|
+
store.pushNotice(`channel provider failed: ${e?.message || e}`, "error");
|
|
21051
20232
|
}
|
|
21052
|
-
openSettingsPicker({ light: true, overrides: {
|
|
20233
|
+
openSettingsPicker({ light: true, overrides: { channelProvider: chosen } });
|
|
21053
20234
|
};
|
|
21054
20235
|
const items = [
|
|
21055
20236
|
{
|
|
@@ -21130,8 +20311,8 @@ function createSettingsPicker({
|
|
|
21130
20311
|
{
|
|
21131
20312
|
value: "memory",
|
|
21132
20313
|
label: "Memory",
|
|
21133
|
-
meta: boolLabel(
|
|
21134
|
-
description:
|
|
20314
|
+
meta: `Recap ${boolLabel(recap.enabled !== false)}`,
|
|
20315
|
+
description: recap.enabled === false ? "Background recap off. Core memories and on-demand recall stay available." : "Background recap and core memories.",
|
|
21135
20316
|
_action: "memory"
|
|
21136
20317
|
},
|
|
21137
20318
|
{
|
|
@@ -21174,11 +20355,11 @@ function createSettingsPicker({
|
|
|
21174
20355
|
_action: "channels"
|
|
21175
20356
|
},
|
|
21176
20357
|
{
|
|
21177
|
-
value: "channel-
|
|
20358
|
+
value: "channel-provider",
|
|
21178
20359
|
label: "Channel",
|
|
21179
|
-
meta:
|
|
20360
|
+
meta: channelProviderLabel,
|
|
21180
20361
|
description: "Left/Right or Enter changes channel type (Discord or Telegram).",
|
|
21181
|
-
_action: "channel-
|
|
20362
|
+
_action: "channel-provider"
|
|
21182
20363
|
},
|
|
21183
20364
|
{
|
|
21184
20365
|
value: "channel-setting",
|
|
@@ -21232,7 +20413,7 @@ function createSettingsPicker({
|
|
|
21232
20413
|
else if (item?._action === "autocompact") applyCompaction({ auto: !(compaction.auto !== false) });
|
|
21233
20414
|
else if (item?._action === "channels") applyChannels(!(channels.enabled !== false));
|
|
21234
20415
|
else if (item?._action === "remote-runtime") applyRemoteRuntime();
|
|
21235
|
-
else if (item?._action === "channel-
|
|
20416
|
+
else if (item?._action === "channel-provider") cycleChannelProvider(-1);
|
|
21236
20417
|
else if (item?._action === "output-style") cycleOutputStyle(-1);
|
|
21237
20418
|
else if (item?._action === "theme") cycleTheme(-1);
|
|
21238
20419
|
else if (item?._action === "workflow") cycleWorkflow(-1);
|
|
@@ -21242,7 +20423,7 @@ function createSettingsPicker({
|
|
|
21242
20423
|
else if (item?._action === "autocompact") applyCompaction({ auto: !(compaction.auto !== false) });
|
|
21243
20424
|
else if (item?._action === "channels") applyChannels(!(channels.enabled !== false));
|
|
21244
20425
|
else if (item?._action === "remote-runtime") applyRemoteRuntime();
|
|
21245
|
-
else if (item?._action === "channel-
|
|
20426
|
+
else if (item?._action === "channel-provider") cycleChannelProvider(1);
|
|
21246
20427
|
else if (item?._action === "output-style") cycleOutputStyle(1);
|
|
21247
20428
|
else if (item?._action === "theme") cycleTheme(1);
|
|
21248
20429
|
else if (item?._action === "workflow") cycleWorkflow(1);
|
|
@@ -21253,7 +20434,7 @@ function createSettingsPicker({
|
|
|
21253
20434
|
else if (item._action === "autocompact") applyCompaction({ auto: !(compaction.auto !== false) });
|
|
21254
20435
|
else if (item._action === "channels") applyChannels(!(channels.enabled !== false));
|
|
21255
20436
|
else if (item._action === "remote-runtime") applyRemoteRuntime();
|
|
21256
|
-
else if (item._action === "channel-
|
|
20437
|
+
else if (item._action === "channel-provider") cycleChannelProvider(1);
|
|
21257
20438
|
else if (item._action === "channel-setting") openChannelSettingTypePicker({ returnTo: openSettingsPicker });
|
|
21258
20439
|
else if (item._action === "output-style") openOutputStylePicker({ returnTo: openSettingsPicker });
|
|
21259
20440
|
else if (item._action === "theme") openThemePicker({ returnTo: openSettingsPicker });
|
|
@@ -21898,6 +21079,13 @@ function createAppPickers({
|
|
|
21898
21079
|
|
|
21899
21080
|
// src/tui/app/use-prompt-queue-history.mjs
|
|
21900
21081
|
import { useCallback as useCallback9, useMemo as useMemo2 } from "react";
|
|
21082
|
+
|
|
21083
|
+
// src/tui/components/prompt-input/restore-policy.mjs
|
|
21084
|
+
function mergeQueuedRestoreText(queuedText = "", currentText = "") {
|
|
21085
|
+
return [String(queuedText ?? ""), String(currentText ?? "")].filter((text) => text.trim()).join("\n");
|
|
21086
|
+
}
|
|
21087
|
+
|
|
21088
|
+
// src/tui/app/use-prompt-queue-history.mjs
|
|
21901
21089
|
function usePromptQueueHistory({
|
|
21902
21090
|
store,
|
|
21903
21091
|
state,
|
|
@@ -21930,8 +21118,8 @@ function usePromptQueueHistory({
|
|
|
21930
21118
|
let timer2 = null;
|
|
21931
21119
|
Promise.race([
|
|
21932
21120
|
Promise.resolve(store.dispose?.("cli-react-exit", { detach: true })),
|
|
21933
|
-
new Promise((
|
|
21934
|
-
timer2 = setTimeout(
|
|
21121
|
+
new Promise((resolve4) => {
|
|
21122
|
+
timer2 = setTimeout(resolve4, 350);
|
|
21935
21123
|
})
|
|
21936
21124
|
]).finally(() => {
|
|
21937
21125
|
if (timer2) clearTimeout(timer2);
|
|
@@ -21951,8 +21139,12 @@ function usePromptQueueHistory({
|
|
|
21951
21139
|
if (restoreDraft) {
|
|
21952
21140
|
if (restored2.pastedImages) installPastedImages(restored2.pastedImages, { merge: true });
|
|
21953
21141
|
if (restored2.pastedTexts) installPastedTexts(restored2.pastedTexts, { merge: true });
|
|
21954
|
-
|
|
21955
|
-
|
|
21142
|
+
const restoredText = mergeQueuedRestoreText(
|
|
21143
|
+
restored2.text,
|
|
21144
|
+
promptValueRef.current ?? currentText
|
|
21145
|
+
);
|
|
21146
|
+
syncPromptLayoutRows(restoredText);
|
|
21147
|
+
setPromptDraftOverride({ id: Date.now(), value: restoredText });
|
|
21956
21148
|
}
|
|
21957
21149
|
if (showHint) {
|
|
21958
21150
|
showPromptHint(`restored ${restored2.count} queued message${restored2.count === 1 ? "" : "s"}`, "info");
|
|
@@ -21961,7 +21153,7 @@ function usePromptQueueHistory({
|
|
|
21961
21153
|
}
|
|
21962
21154
|
return true;
|
|
21963
21155
|
};
|
|
21964
|
-
const restored = store.restoreQueued?.(
|
|
21156
|
+
const restored = store.restoreQueued?.("");
|
|
21965
21157
|
if (restored && typeof restored.then === "function") {
|
|
21966
21158
|
const queuedCount = Array.isArray(state?.queued) ? state.queued.length : 0;
|
|
21967
21159
|
void Promise.resolve(restored).then(apply).catch(() => {
|
|
@@ -21993,6 +21185,88 @@ function usePromptQueueHistory({
|
|
|
21993
21185
|
return { requestExit, restoreQueuedToPrompt, recentPromptHistory, resetPromptHistoryNav };
|
|
21994
21186
|
}
|
|
21995
21187
|
|
|
21188
|
+
// src/tui/app/message-selector.mjs
|
|
21189
|
+
import { useCallback as useCallback10, useMemo as useMemo3 } from "react";
|
|
21190
|
+
var MAX_SELECTABLE_MESSAGES = 20;
|
|
21191
|
+
function selectableUserItems(items, limit = MAX_SELECTABLE_MESSAGES) {
|
|
21192
|
+
const source = Array.isArray(items) ? items : [];
|
|
21193
|
+
const rows = [];
|
|
21194
|
+
for (let index = source.length - 1; index >= 0 && rows.length < limit; index -= 1) {
|
|
21195
|
+
const item = source[index];
|
|
21196
|
+
if (item?.kind !== "user") continue;
|
|
21197
|
+
if (item?.id == null) continue;
|
|
21198
|
+
const text = String(item.text || "").trim();
|
|
21199
|
+
if (!text) continue;
|
|
21200
|
+
rows.push({ id: String(item.id), text });
|
|
21201
|
+
}
|
|
21202
|
+
return rows.reverse();
|
|
21203
|
+
}
|
|
21204
|
+
function messageSelectorLabel(text, width = 56) {
|
|
21205
|
+
const firstLine = String(text || "").split("\n").map((line) => line.trim()).find(Boolean) || "";
|
|
21206
|
+
if (firstLine.length <= width) return firstLine;
|
|
21207
|
+
return `${firstLine.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
21208
|
+
}
|
|
21209
|
+
function useMessageSelector({
|
|
21210
|
+
store,
|
|
21211
|
+
state,
|
|
21212
|
+
setPicker,
|
|
21213
|
+
setPromptDraftOverride,
|
|
21214
|
+
syncPromptLayoutRows,
|
|
21215
|
+
showPromptHint,
|
|
21216
|
+
clearPromptHint
|
|
21217
|
+
}) {
|
|
21218
|
+
const hasUserMessages = useMemo3(
|
|
21219
|
+
() => (Array.isArray(state.items) ? state.items : []).some(
|
|
21220
|
+
(item) => item?.kind === "user" && String(item?.text || "").trim()
|
|
21221
|
+
),
|
|
21222
|
+
[state.items]
|
|
21223
|
+
);
|
|
21224
|
+
const applyRewind = useCallback10((restored) => {
|
|
21225
|
+
const text = String(restored?.text || "");
|
|
21226
|
+
if (!text) {
|
|
21227
|
+
showPromptHint("Could not restore that message.", "error");
|
|
21228
|
+
return false;
|
|
21229
|
+
}
|
|
21230
|
+
clearPromptHint();
|
|
21231
|
+
syncPromptLayoutRows(text);
|
|
21232
|
+
setPromptDraftOverride({ id: Date.now(), value: text });
|
|
21233
|
+
return true;
|
|
21234
|
+
}, [clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride, showPromptHint]);
|
|
21235
|
+
const openMessageSelector = useCallback10(() => {
|
|
21236
|
+
const items = store.getState?.().items ?? state.items;
|
|
21237
|
+
const rows = selectableUserItems(items);
|
|
21238
|
+
if (rows.length === 0) {
|
|
21239
|
+
showPromptHint("No message to jump back to.", "info");
|
|
21240
|
+
return false;
|
|
21241
|
+
}
|
|
21242
|
+
setPicker({
|
|
21243
|
+
_kind: "message-selector",
|
|
21244
|
+
title: "Jump back to a message",
|
|
21245
|
+
description: "Rewind the conversation to a previous prompt and edit it.",
|
|
21246
|
+
initialIndex: rows.length - 1,
|
|
21247
|
+
items: rows.map((row) => ({ value: row.id, label: messageSelectorLabel(row.text) })),
|
|
21248
|
+
onSelect: (value) => {
|
|
21249
|
+
setPicker(null);
|
|
21250
|
+
let restored;
|
|
21251
|
+
try {
|
|
21252
|
+
restored = store.rewindToItem?.(value);
|
|
21253
|
+
} catch (error) {
|
|
21254
|
+
showPromptHint(`rewind failed: ${error?.message || error}`, "error");
|
|
21255
|
+
return;
|
|
21256
|
+
}
|
|
21257
|
+
if (restored && typeof restored.then === "function") {
|
|
21258
|
+
void Promise.resolve(restored).then(applyRewind).catch((error) => showPromptHint(`rewind failed: ${error?.message || error}`, "error"));
|
|
21259
|
+
return;
|
|
21260
|
+
}
|
|
21261
|
+
applyRewind(restored);
|
|
21262
|
+
},
|
|
21263
|
+
onCancel: () => setPicker(null)
|
|
21264
|
+
});
|
|
21265
|
+
return true;
|
|
21266
|
+
}, [store, state.items, setPicker, applyRewind, showPromptHint]);
|
|
21267
|
+
return { hasUserMessages, openMessageSelector };
|
|
21268
|
+
}
|
|
21269
|
+
|
|
21996
21270
|
// src/tui/app/use-terminal-chrome.mjs
|
|
21997
21271
|
import { useEffect as useEffect11 } from "react";
|
|
21998
21272
|
function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState }) {
|
|
@@ -22042,7 +21316,7 @@ function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState
|
|
|
22042
21316
|
}
|
|
22043
21317
|
|
|
22044
21318
|
// src/tui/app/use-transcript-window.mjs
|
|
22045
|
-
import { useCallback as
|
|
21319
|
+
import { useCallback as useCallback11, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useMemo as useMemo4, useRef as useRef12, useState as useState10 } from "react";
|
|
22046
21320
|
var HARVEST_BUMP_STREAK_LIMIT = 10;
|
|
22047
21321
|
var TUI_DEBUG = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ""));
|
|
22048
21322
|
var harvestBreakerLogged = false;
|
|
@@ -22133,7 +21407,7 @@ function useTranscriptWindow({
|
|
|
22133
21407
|
useLayoutEffect4(() => {
|
|
22134
21408
|
if (scrollOffset !== 0) setScrollOffset(0);
|
|
22135
21409
|
}, [normalizedSessionKey, setScrollOffset]);
|
|
22136
|
-
const transcriptMeasureRef =
|
|
21410
|
+
const transcriptMeasureRef = useCallback11((item) => {
|
|
22137
21411
|
if (!TRANSCRIPT_MEASURED_ROWS || !item || item.id == null) return void 0;
|
|
22138
21412
|
if (shouldSuppressFullyFailedToolItem(item)) {
|
|
22139
21413
|
transcriptMeasuredRowsCache.delete(item);
|
|
@@ -22157,8 +21431,6 @@ function useTranscriptWindow({
|
|
|
22157
21431
|
return fn;
|
|
22158
21432
|
}, []);
|
|
22159
21433
|
const streamingTailItem = streamingTail?.kind === "assistant" && streamingTail.streaming ? streamingTail : null;
|
|
22160
|
-
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
22161
|
-
const transcriptPinnedForStreaming = followingRef.current || scrolledUpRowsForPin === 0;
|
|
22162
21434
|
const tailRows = streamingTailItem ? estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded) : 0;
|
|
22163
21435
|
const tailSig = streamingTailItem ? `${streamingTailItem.id}:${tailRows}` : "_";
|
|
22164
21436
|
const revision = Math.max(0, Number(structureRevision) || 0);
|
|
@@ -22174,13 +21446,8 @@ function useTranscriptWindow({
|
|
|
22174
21446
|
revision,
|
|
22175
21447
|
streamingTailItem
|
|
22176
21448
|
);
|
|
22177
|
-
const
|
|
22178
|
-
const
|
|
22179
|
-
const prevEstimateGeometry = transcriptGeomRef.current?.suppressMeasuredRowHeights === true;
|
|
22180
|
-
const hasStreamingReadingAnchor = !!transcriptAnchorRef.current || transcriptAnchorDirtyRef.current;
|
|
22181
|
-
const bottomPinnedForMeasure = transcriptPinnedForStreaming;
|
|
22182
|
-
const suppressMeasuredRowHeights = bottomPinnedForMeasure || transcriptStreamingActive && (scrolledUpForStreamingMeasure && hasStreamingReadingAnchor || scrolledUpForStreamingMeasure && prevEstimateGeometry && !transcriptPinnedForStreaming);
|
|
22183
|
-
const transcriptRowIndex = useMemo3(() => buildTranscriptRowIndexIncremental(transcriptItems, {
|
|
21449
|
+
const suppressMeasuredRowHeights = false;
|
|
21450
|
+
const transcriptRowIndex = useMemo4(() => buildTranscriptRowIndexIncremental(transcriptItems, {
|
|
22184
21451
|
columns: frameColumns,
|
|
22185
21452
|
toolOutputExpanded,
|
|
22186
21453
|
suppressMeasuredRowHeights,
|
|
@@ -22279,7 +21546,7 @@ function useTranscriptWindow({
|
|
|
22279
21546
|
contentHeight: lockViewRows,
|
|
22280
21547
|
floatingPanelRows: Number(floatingPanelRows) || 0
|
|
22281
21548
|
};
|
|
22282
|
-
const transcriptWindow =
|
|
21549
|
+
const transcriptWindow = useMemo4(() => transcriptRenderWindow(transcriptItems, {
|
|
22283
21550
|
scrollOffset: renderScrollOffset,
|
|
22284
21551
|
viewportHeight: transcriptContentHeight,
|
|
22285
21552
|
columns: frameColumns,
|
|
@@ -22461,7 +21728,7 @@ function useTranscriptWindow({
|
|
|
22461
21728
|
const currentPosition = Math.max(0, Number(scrollPositionRef.current) || 0);
|
|
22462
21729
|
const currentOffset = Math.max(0, Number(scrollOffset) || 0);
|
|
22463
21730
|
const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
|
|
22464
|
-
if (!followingRef.current && rowDelta
|
|
21731
|
+
if (!followingRef.current && rowDelta >= 0 && currentTarget === 0) {
|
|
22465
21732
|
followingRef.current = true;
|
|
22466
21733
|
}
|
|
22467
21734
|
const nearBottom = followingRef.current;
|
|
@@ -22514,6 +21781,16 @@ function useTranscriptWindow({
|
|
|
22514
21781
|
maxRows
|
|
22515
21782
|
});
|
|
22516
21783
|
if (desired == null) {
|
|
21784
|
+
if (rowDelta < 0 || currentTarget > maxRows) {
|
|
21785
|
+
stopSmoothScroll();
|
|
21786
|
+
transcriptAnchorRef.current = null;
|
|
21787
|
+
transcriptAnchorDirtyRef.current = false;
|
|
21788
|
+
scrollTargetRef.current = 0;
|
|
21789
|
+
scrollPositionRef.current = 0;
|
|
21790
|
+
followingRef.current = true;
|
|
21791
|
+
if (currentOffset !== 0) setScrollOffset(0);
|
|
21792
|
+
return;
|
|
21793
|
+
}
|
|
22517
21794
|
transcriptAnchorDirtyRef.current = true;
|
|
22518
21795
|
return;
|
|
22519
21796
|
}
|
|
@@ -22560,6 +21837,11 @@ function useTranscriptWindow({
|
|
|
22560
21837
|
}, [themeEpoch, withSelectionClip, paintSelectionRect]);
|
|
22561
21838
|
useEffect12(() => {
|
|
22562
21839
|
const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
|
|
21840
|
+
if (maxRows === 0 && !followingRef.current) {
|
|
21841
|
+
transcriptAnchorRef.current = null;
|
|
21842
|
+
transcriptAnchorDirtyRef.current = false;
|
|
21843
|
+
followingRef.current = true;
|
|
21844
|
+
}
|
|
22563
21845
|
if (scrollTargetRef.current <= maxRows && scrollPositionRef.current <= maxRows && scrollOffset <= maxRows) return;
|
|
22564
21846
|
stopSmoothScroll();
|
|
22565
21847
|
const next = Math.max(0, Math.min(maxRows, scrollTargetRef.current));
|
|
@@ -22589,42 +21871,49 @@ function createProjectPicker({
|
|
|
22589
21871
|
setSettingsPrompt,
|
|
22590
21872
|
setContextPanel,
|
|
22591
21873
|
closeUsagePanel,
|
|
22592
|
-
listProjects: listProjects2,
|
|
22593
|
-
addProject: addProject2,
|
|
22594
|
-
touchProjectSelected: touchProjectSelected2,
|
|
22595
|
-
resolveProjectPath: resolveProjectPath2,
|
|
22596
21874
|
projectNameFromPath: projectNameFromPath3,
|
|
22597
21875
|
pickFolder: pickFolder2
|
|
22598
21876
|
}) {
|
|
22599
|
-
const
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
} catch {
|
|
22604
|
-
projects = [];
|
|
21877
|
+
const serviceCall = (name, ...args) => {
|
|
21878
|
+
const target = store?.[name];
|
|
21879
|
+
if (typeof target !== "function") {
|
|
21880
|
+
return Promise.reject(new TypeError(`project service method ${name} is unavailable`));
|
|
22605
21881
|
}
|
|
21882
|
+
return Promise.resolve(target.apply(store, args));
|
|
21883
|
+
};
|
|
21884
|
+
const buildProjectPickerState = ({
|
|
21885
|
+
initialEntry = false,
|
|
21886
|
+
projects = [],
|
|
21887
|
+
loading = false,
|
|
21888
|
+
requestId = null
|
|
21889
|
+
} = {}) => {
|
|
22606
21890
|
const currentPath = String(state.cwd || process.cwd() || "");
|
|
22607
21891
|
const items = [];
|
|
22608
|
-
|
|
22609
|
-
|
|
21892
|
+
if (!loading) {
|
|
21893
|
+
for (const project of projects) {
|
|
21894
|
+
if (!project?.path) continue;
|
|
21895
|
+
items.push({
|
|
21896
|
+
value: project.path,
|
|
21897
|
+
label: project.name || project.path,
|
|
21898
|
+
meta: project.path,
|
|
21899
|
+
_project: project
|
|
21900
|
+
});
|
|
21901
|
+
}
|
|
22610
21902
|
items.push({
|
|
22611
|
-
value:
|
|
22612
|
-
label:
|
|
22613
|
-
meta:
|
|
22614
|
-
|
|
21903
|
+
value: "__use_current__",
|
|
21904
|
+
label: "Current Path",
|
|
21905
|
+
meta: currentPath,
|
|
21906
|
+
_action: "current"
|
|
22615
21907
|
});
|
|
22616
21908
|
}
|
|
22617
|
-
items.push({
|
|
22618
|
-
value: "__use_current__",
|
|
22619
|
-
label: "Current Path",
|
|
22620
|
-
meta: currentPath,
|
|
22621
|
-
_action: "current"
|
|
22622
|
-
});
|
|
22623
21909
|
return {
|
|
22624
21910
|
kind: "project",
|
|
21911
|
+
_kind: "project",
|
|
21912
|
+
_projectRequestId: requestId,
|
|
21913
|
+
_projectInitialPending: loading && initialEntry,
|
|
22625
21914
|
title: "Project",
|
|
22626
|
-
description: "Choose a project.",
|
|
22627
|
-
help: initialEntry ? "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename" : "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename \xB7 Esc Back",
|
|
21915
|
+
description: loading ? "Loading projects from the session\u2026" : "Choose a project.",
|
|
21916
|
+
help: loading ? "Waiting for the project service\u2026" : initialEntry ? "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename" : "\u2191/\u2193 Select \xB7 Enter Open \xB7 c Create \xB7 r Rename \xB7 Esc Back",
|
|
22628
21917
|
indexMode: "always",
|
|
22629
21918
|
labelWidth: 18,
|
|
22630
21919
|
metaWidth: 40,
|
|
@@ -22635,20 +21924,11 @@ function createProjectPicker({
|
|
|
22635
21924
|
return;
|
|
22636
21925
|
}
|
|
22637
21926
|
if (item?._action === "current") {
|
|
22638
|
-
|
|
22639
|
-
try {
|
|
22640
|
-
store.setCwd?.(currentPath, {
|
|
22641
|
-
notice: !initialEntry,
|
|
22642
|
-
message: `Project set: ${projectNameFromPath3(currentPath)}`
|
|
22643
|
-
});
|
|
22644
|
-
} catch (e) {
|
|
22645
|
-
store.pushNotice(`project switch failed: ${e?.message || e}`, "error");
|
|
22646
|
-
}
|
|
21927
|
+
void enterProject(currentPath, { notice: !initialEntry, register: false });
|
|
22647
21928
|
return;
|
|
22648
21929
|
}
|
|
22649
|
-
setPicker(null);
|
|
22650
21930
|
const project = item?._project;
|
|
22651
|
-
if (project?.path) enterProject(project.path, { notice: !initialEntry });
|
|
21931
|
+
if (project?.path) void enterProject(project.path, { notice: !initialEntry });
|
|
22652
21932
|
},
|
|
22653
21933
|
onKey: (input, _key, item) => {
|
|
22654
21934
|
if (input === "c" || input === "C") {
|
|
@@ -22707,40 +21987,44 @@ function createProjectPicker({
|
|
|
22707
21987
|
openProjectPicker();
|
|
22708
21988
|
return;
|
|
22709
21989
|
}
|
|
22710
|
-
registerProject(result.path);
|
|
21990
|
+
void registerProject(result.path);
|
|
22711
21991
|
}).catch(() => {
|
|
22712
21992
|
beginNewProjectManual();
|
|
22713
21993
|
});
|
|
22714
21994
|
};
|
|
22715
|
-
const registerProject = (rawPath) => {
|
|
22716
|
-
const
|
|
22717
|
-
if (!
|
|
21995
|
+
const registerProject = async (rawPath) => {
|
|
21996
|
+
const path2 = String(rawPath || "").trim();
|
|
21997
|
+
if (!path2) {
|
|
22718
21998
|
store.pushNotice("project path is required", "warn");
|
|
22719
|
-
return;
|
|
21999
|
+
return false;
|
|
22720
22000
|
}
|
|
22721
22001
|
try {
|
|
22722
|
-
const project =
|
|
22002
|
+
const project = await serviceCall("addProject", path2);
|
|
22723
22003
|
if (project?.name) store.pushNotice(`project added: ${project.name}`, "info");
|
|
22724
|
-
openProjectPicker();
|
|
22004
|
+
await openProjectPicker();
|
|
22005
|
+
return true;
|
|
22725
22006
|
} catch (e) {
|
|
22726
22007
|
store.pushNotice(`project add failed: ${e?.message || e}`, "error");
|
|
22008
|
+
return false;
|
|
22727
22009
|
}
|
|
22728
22010
|
};
|
|
22729
|
-
const enterProject = (rawPath, options = {}) => {
|
|
22730
|
-
const
|
|
22731
|
-
if (!
|
|
22011
|
+
const enterProject = async (rawPath, options = {}) => {
|
|
22012
|
+
const path2 = String(rawPath || "").trim();
|
|
22013
|
+
if (!path2) {
|
|
22732
22014
|
store.pushNotice("project path is required", "warn");
|
|
22733
|
-
return;
|
|
22015
|
+
return false;
|
|
22734
22016
|
}
|
|
22017
|
+
setPicker(null);
|
|
22735
22018
|
try {
|
|
22736
|
-
|
|
22019
|
+
const resolved = await serviceCall("setCwd", path2, {
|
|
22737
22020
|
notice: options?.notice !== false,
|
|
22738
|
-
message: `Project set: ${projectNameFromPath3(
|
|
22021
|
+
message: `Project set: ${projectNameFromPath3(path2)}`
|
|
22739
22022
|
});
|
|
22740
|
-
|
|
22741
|
-
|
|
22023
|
+
if (options?.register !== false) await serviceCall("addProject", resolved || path2);
|
|
22024
|
+
return true;
|
|
22742
22025
|
} catch (e) {
|
|
22743
22026
|
store.pushNotice(`project switch failed: ${e?.message || e}`, "error");
|
|
22027
|
+
return false;
|
|
22744
22028
|
}
|
|
22745
22029
|
};
|
|
22746
22030
|
const beginRenameProject = (project) => {
|
|
@@ -22759,14 +22043,32 @@ function createProjectPicker({
|
|
|
22759
22043
|
initialValue: project.name || ""
|
|
22760
22044
|
});
|
|
22761
22045
|
};
|
|
22762
|
-
const openProjectPicker = () => {
|
|
22046
|
+
const openProjectPicker = async ({ initialEntry = false } = {}) => {
|
|
22763
22047
|
setProviderPrompt(null);
|
|
22764
22048
|
setChannelPrompt(null);
|
|
22765
22049
|
setHookPrompt(null);
|
|
22766
22050
|
setSettingsPrompt(null);
|
|
22767
22051
|
setContextPanel(null);
|
|
22768
22052
|
closeUsagePanel();
|
|
22769
|
-
|
|
22053
|
+
const requestId = /* @__PURE__ */ Symbol("project-picker-request");
|
|
22054
|
+
setPicker(buildProjectPickerState({
|
|
22055
|
+
initialEntry,
|
|
22056
|
+
loading: true,
|
|
22057
|
+
requestId
|
|
22058
|
+
}));
|
|
22059
|
+
try {
|
|
22060
|
+
const projects = await serviceCall("listProjects");
|
|
22061
|
+
setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({
|
|
22062
|
+
initialEntry,
|
|
22063
|
+
projects: Array.isArray(projects) ? projects : [],
|
|
22064
|
+
requestId
|
|
22065
|
+
}) : current);
|
|
22066
|
+
return projects;
|
|
22067
|
+
} catch (error) {
|
|
22068
|
+
setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({ initialEntry, projects: [], requestId }) : current);
|
|
22069
|
+
store.pushNotice(`project list failed: ${error?.message || error}`, "error");
|
|
22070
|
+
return [];
|
|
22071
|
+
}
|
|
22770
22072
|
};
|
|
22771
22073
|
return {
|
|
22772
22074
|
buildProjectPickerState,
|
|
@@ -22782,20 +22084,20 @@ function createProjectPicker({
|
|
|
22782
22084
|
// src/tui/app/use-prompt-handlers.mjs
|
|
22783
22085
|
init_paste_attachments();
|
|
22784
22086
|
init_paste_attachments();
|
|
22785
|
-
import { useCallback as
|
|
22087
|
+
import { useCallback as useCallback12, useEffect as useEffect13, useRef as useRef13 } from "react";
|
|
22786
22088
|
|
|
22787
22089
|
// src/tui/prompt-history-store.mjs
|
|
22788
|
-
import { chmodSync, existsSync as
|
|
22090
|
+
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
22789
22091
|
init_plugin_paths();
|
|
22790
|
-
import { dirname as
|
|
22092
|
+
import { dirname as dirname6, join as join6, resolve as resolve3 } from "node:path";
|
|
22791
22093
|
var PROMPT_HISTORY_LIMIT2 = 50;
|
|
22792
22094
|
function promptHistoryKey2(value) {
|
|
22793
22095
|
return String(value || "").trim().replace(/\s+/g, " ");
|
|
22794
22096
|
}
|
|
22795
22097
|
function readEntries(filePath) {
|
|
22796
|
-
if (!filePath || !
|
|
22098
|
+
if (!filePath || !existsSync6(filePath)) return [];
|
|
22797
22099
|
try {
|
|
22798
|
-
const parsed = JSON.parse(
|
|
22100
|
+
const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
|
|
22799
22101
|
const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
|
|
22800
22102
|
return entries.map((entry) => String(entry || "").trim()).filter((entry) => promptHistoryKey2(entry));
|
|
22801
22103
|
} catch {
|
|
@@ -22809,10 +22111,10 @@ function buildPayload(entries) {
|
|
|
22809
22111
|
function writeEntriesSync(filePath, entries) {
|
|
22810
22112
|
if (!filePath) return false;
|
|
22811
22113
|
try {
|
|
22812
|
-
|
|
22114
|
+
mkdirSync5(dirname6(filePath), { recursive: true, mode: 448 });
|
|
22813
22115
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
22814
|
-
|
|
22815
|
-
|
|
22116
|
+
writeFileSync4(tmp, buildPayload(entries), { encoding: "utf8", mode: 384 });
|
|
22117
|
+
renameSync2(tmp, filePath);
|
|
22816
22118
|
try {
|
|
22817
22119
|
chmodSync(filePath, 384);
|
|
22818
22120
|
} catch {
|
|
@@ -22849,6 +22151,13 @@ function flushPromptHistory() {
|
|
|
22849
22151
|
}
|
|
22850
22152
|
process.once("exit", flushPromptHistory);
|
|
22851
22153
|
|
|
22154
|
+
// src/tui/components/prompt-input/interrupt-policy.mjs
|
|
22155
|
+
function promptInterruptRestoreText(result, currentText = "") {
|
|
22156
|
+
if (!result || result?.aborted === false) return "";
|
|
22157
|
+
if (String(currentText ?? "") !== "") return "";
|
|
22158
|
+
return String(result?.restoreText || "").trim();
|
|
22159
|
+
}
|
|
22160
|
+
|
|
22852
22161
|
// src/tui/app/use-prompt-handlers.mjs
|
|
22853
22162
|
function usePromptHandlers({
|
|
22854
22163
|
store,
|
|
@@ -22873,6 +22182,7 @@ function usePromptHandlers({
|
|
|
22873
22182
|
recentPromptHistory,
|
|
22874
22183
|
resetPromptHistoryNav,
|
|
22875
22184
|
restoreQueuedToPrompt,
|
|
22185
|
+
openMessageSelector,
|
|
22876
22186
|
usagePanel,
|
|
22877
22187
|
closeUsagePanel,
|
|
22878
22188
|
contextPanel,
|
|
@@ -22884,7 +22194,9 @@ function usePromptHandlers({
|
|
|
22884
22194
|
clearPastedTextsSnapshot,
|
|
22885
22195
|
registerPastedText
|
|
22886
22196
|
}) {
|
|
22887
|
-
const
|
|
22197
|
+
const interruptGenerationRef = useRef13(0);
|
|
22198
|
+
const pendingInterruptRestoreRef = useRef13(null);
|
|
22199
|
+
const handlePromptPaste = useCallback12((text, meta = {}) => {
|
|
22888
22200
|
const source = String(meta?.source || "paste");
|
|
22889
22201
|
const value = String(text ?? "");
|
|
22890
22202
|
const processText = (raw, returnRaw = false) => {
|
|
@@ -22946,7 +22258,7 @@ function usePromptHandlers({
|
|
|
22946
22258
|
}
|
|
22947
22259
|
return processText(value);
|
|
22948
22260
|
}, [registerPastedImage, registerPastedText, showPromptHint, state.cwd]);
|
|
22949
|
-
const handlePromptHistoryNavigate =
|
|
22261
|
+
const handlePromptHistoryNavigate = useCallback12((direction, currentText = "", meta = {}) => {
|
|
22950
22262
|
const currentValue = String(currentText || "");
|
|
22951
22263
|
const currentKey = promptHistoryKey2(currentValue);
|
|
22952
22264
|
const nav = promptHistoryNavRef.current || { active: false, index: -1, seed: "", lastValue: "" };
|
|
@@ -22993,7 +22305,7 @@ function usePromptHandlers({
|
|
|
22993
22305
|
promptHistoryDraftChangeRef.current = true;
|
|
22994
22306
|
return nextValue;
|
|
22995
22307
|
}, [recentPromptHistory, resetPromptHistoryNav, clearPromptHint]);
|
|
22996
|
-
const handlePromptEscape =
|
|
22308
|
+
const handlePromptEscape = useCallback12((text = "", meta = {}) => {
|
|
22997
22309
|
if (usagePanel) {
|
|
22998
22310
|
closeUsagePanel();
|
|
22999
22311
|
return true;
|
|
@@ -23002,8 +22314,28 @@ function usePromptHandlers({
|
|
|
23002
22314
|
setContextPanel(null);
|
|
23003
22315
|
return true;
|
|
23004
22316
|
}
|
|
22317
|
+
if (meta.phase === "clear-arm") {
|
|
22318
|
+
showPromptHint("Esc again to clear", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
|
|
22319
|
+
return true;
|
|
22320
|
+
}
|
|
22321
|
+
if (meta.phase === "select-arm") {
|
|
22322
|
+
showPromptHint("Esc again to pick a message", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
|
|
22323
|
+
return true;
|
|
22324
|
+
}
|
|
22325
|
+
if (meta.phase === "select") {
|
|
22326
|
+
clearPromptHint();
|
|
22327
|
+
if (restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text })) return true;
|
|
22328
|
+
return openMessageSelector?.() === true;
|
|
22329
|
+
}
|
|
23005
22330
|
if (meta.phase === "clear") {
|
|
22331
|
+
try {
|
|
22332
|
+
const remembered = store.rememberPromptHistory?.(text);
|
|
22333
|
+
if (remembered?.catch) void remembered.catch(() => {
|
|
22334
|
+
});
|
|
22335
|
+
} catch {
|
|
22336
|
+
}
|
|
23006
22337
|
clearPastedImagesSnapshot();
|
|
22338
|
+
clearPastedTextsSnapshot();
|
|
23007
22339
|
clearPromptHint();
|
|
23008
22340
|
return false;
|
|
23009
22341
|
}
|
|
@@ -23011,21 +22343,57 @@ function usePromptHandlers({
|
|
|
23011
22343
|
return restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text });
|
|
23012
22344
|
}
|
|
23013
22345
|
return false;
|
|
23014
|
-
}, [contextPanel, usagePanel, closeUsagePanel, restoreQueuedToPrompt, clearPromptHint, clearPastedImagesSnapshot, store]);
|
|
23015
|
-
const
|
|
23016
|
-
|
|
23017
|
-
if (
|
|
23018
|
-
|
|
23019
|
-
if (
|
|
23020
|
-
if (result?.
|
|
23021
|
-
if (result?.
|
|
23022
|
-
const restoreText = String(result?.restoreText || "").trim();
|
|
23023
|
-
if (!restoreText) return void 0;
|
|
23024
|
-
const existingText = String(currentText || "").trim();
|
|
23025
|
-
const nextText = [restoreText, existingText].filter(Boolean).join("\n");
|
|
22346
|
+
}, [contextPanel, usagePanel, closeUsagePanel, restoreQueuedToPrompt, openMessageSelector, showPromptHint, clearPromptHint, clearPastedImagesSnapshot, clearPastedTextsSnapshot, store]);
|
|
22347
|
+
const commitAsyncInterruptRestore = useCallback12((pending) => {
|
|
22348
|
+
if (!pending || pending.generation !== interruptGenerationRef.current) return true;
|
|
22349
|
+
if (store.getState?.().busy) return false;
|
|
22350
|
+
const restoreText = promptInterruptRestoreText(pending.result, promptValueRef.current);
|
|
22351
|
+
if (!restoreText) return true;
|
|
22352
|
+
if (pending.result?.pastedImages) installPastedImages(pending.result.pastedImages, { merge: true });
|
|
22353
|
+
if (pending.result?.pastedTexts) installPastedTexts(pending.result.pastedTexts, { merge: true });
|
|
23026
22354
|
clearPromptHint();
|
|
23027
|
-
|
|
23028
|
-
|
|
22355
|
+
syncPromptLayoutRows(restoreText);
|
|
22356
|
+
setPromptDraftOverride({ id: Date.now(), value: restoreText });
|
|
22357
|
+
return true;
|
|
22358
|
+
}, [store, promptValueRef, installPastedImages, installPastedTexts, clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride]);
|
|
22359
|
+
useEffect13(() => {
|
|
22360
|
+
const pending = pendingInterruptRestoreRef.current;
|
|
22361
|
+
if (!pending) return;
|
|
22362
|
+
if (commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = null;
|
|
22363
|
+
}, [state.busy, commitAsyncInterruptRestore]);
|
|
22364
|
+
const handlePromptInterrupt = useCallback12((currentText = "") => {
|
|
22365
|
+
const generation = ++interruptGenerationRef.current;
|
|
22366
|
+
pendingInterruptRestoreRef.current = null;
|
|
22367
|
+
const applyResult = (result2, draftText, asyncResult = false) => {
|
|
22368
|
+
if (generation !== interruptGenerationRef.current || result2?.aborted === false) return void 0;
|
|
22369
|
+
if (result2?.discardPastedImages) clearPastedImagesSnapshot(result2.discardPastedImages);
|
|
22370
|
+
if (result2?.discardPastedTexts) clearPastedTextsSnapshot(result2.discardPastedTexts);
|
|
22371
|
+
if (asyncResult) {
|
|
22372
|
+
const pending = { generation, result: result2 };
|
|
22373
|
+
if (!commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = pending;
|
|
22374
|
+
return void 0;
|
|
22375
|
+
}
|
|
22376
|
+
const restoreText = promptInterruptRestoreText(result2, draftText);
|
|
22377
|
+
if (!restoreText) return void 0;
|
|
22378
|
+
if (result2?.pastedImages) installPastedImages(result2.pastedImages, { merge: true });
|
|
22379
|
+
if (result2?.pastedTexts) installPastedTexts(result2.pastedTexts, { merge: true });
|
|
22380
|
+
clearPromptHint();
|
|
22381
|
+
return restoreText;
|
|
22382
|
+
};
|
|
22383
|
+
let result;
|
|
22384
|
+
try {
|
|
22385
|
+
const options = { restorePrompt: String(currentText ?? "") === "" };
|
|
22386
|
+
result = typeof store.abortAsync === "function" ? store.abortAsync(options) : store.abort?.(options);
|
|
22387
|
+
} catch (error) {
|
|
22388
|
+
store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error");
|
|
22389
|
+
return void 0;
|
|
22390
|
+
}
|
|
22391
|
+
if (result && typeof result.then === "function") {
|
|
22392
|
+
void Promise.resolve(result).then((resolved) => applyResult(resolved, promptValueRef.current, true)).catch((error) => store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error"));
|
|
22393
|
+
return void 0;
|
|
22394
|
+
}
|
|
22395
|
+
return applyResult(result, currentText, false);
|
|
22396
|
+
}, [store, commitAsyncInterruptRestore, clearPromptHint, installPastedImages, installPastedTexts, clearPastedImagesSnapshot, clearPastedTextsSnapshot]);
|
|
23029
22397
|
return {
|
|
23030
22398
|
handlePromptPaste,
|
|
23031
22399
|
handlePromptHistoryNavigate,
|
|
@@ -23084,7 +22452,7 @@ function resolveOnboardingCompleted(store, onboardingCompleted) {
|
|
|
23084
22452
|
}
|
|
23085
22453
|
}
|
|
23086
22454
|
function App({ store, initialStatusLine = "", forceOnboarding = false, onboardingCompleted = void 0 }) {
|
|
23087
|
-
const state =
|
|
22455
|
+
const state = useSession(store);
|
|
23088
22456
|
const [toolOutputExpanded, setToolOutputExpanded] = useState11(false);
|
|
23089
22457
|
const [onboardingActive, setOnboardingActive] = useState11(false);
|
|
23090
22458
|
const { exit } = useApp();
|
|
@@ -23092,30 +22460,31 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23092
22460
|
const { stdout: stdout2 } = useStdout2();
|
|
23093
22461
|
const [exiting, setExiting] = useState11(false);
|
|
23094
22462
|
const [tuiReady, setTuiReady] = useState11(false);
|
|
23095
|
-
const exitRequestedRef =
|
|
22463
|
+
const exitRequestedRef = useRef14(false);
|
|
23096
22464
|
const [resizeState, setResizeState] = useState11(() => ({ ...terminalSize(stdout2), epoch: 0 }));
|
|
23097
22465
|
const [panelTransitionEpoch, setPanelTransitionEpoch] = useState11(0);
|
|
23098
22466
|
const [panelInkMaskEpoch, setPanelInkMaskEpoch] = useState11(0);
|
|
23099
22467
|
const rightSafetyColumns = 1;
|
|
23100
22468
|
const frameColumns = Math.max(1, resizeState.columns - rightSafetyColumns);
|
|
23101
22469
|
const [scrollOffset, setScrollOffset] = useState11(0);
|
|
23102
|
-
const scrollPositionRef =
|
|
23103
|
-
const scrollTargetRef =
|
|
23104
|
-
const maxScrollRowsRef =
|
|
23105
|
-
const transcriptBottomSlackRowsRef =
|
|
23106
|
-
const transcriptAnchorRef =
|
|
23107
|
-
const transcriptAnchorDirtyRef =
|
|
23108
|
-
const transcriptGeomRef =
|
|
22470
|
+
const scrollPositionRef = useRef14(0);
|
|
22471
|
+
const scrollTargetRef = useRef14(0);
|
|
22472
|
+
const maxScrollRowsRef = useRef14(0);
|
|
22473
|
+
const transcriptBottomSlackRowsRef = useRef14(0);
|
|
22474
|
+
const transcriptAnchorRef = useRef14(null);
|
|
22475
|
+
const transcriptAnchorDirtyRef = useRef14(false);
|
|
22476
|
+
const transcriptGeomRef = useRef14({ prefixRows: null, totalRows: 0, viewRows: 1 });
|
|
23109
22477
|
const [measuredRowsVersion, setMeasuredRowsVersion] = useState11(0);
|
|
23110
|
-
const followingRef =
|
|
23111
|
-
const lastItemsCountRef =
|
|
23112
|
-
const
|
|
23113
|
-
const
|
|
23114
|
-
const
|
|
22478
|
+
const followingRef = useRef14(false);
|
|
22479
|
+
const lastItemsCountRef = useRef14(0);
|
|
22480
|
+
const lastFirstItemIdRef = useRef14(null);
|
|
22481
|
+
const pickerOpenedFromEnterRef = useRef14(false);
|
|
22482
|
+
const pickerOpenedFromEnterTimerRef = useRef14(null);
|
|
22483
|
+
const projectPickerRef = useRef14(null);
|
|
23115
22484
|
const buildProjectPickerState = (opts) => projectPickerRef.current.buildProjectPickerState(opts);
|
|
23116
22485
|
const [picker, setPickerState] = useState11(null);
|
|
23117
|
-
const livePickerRef =
|
|
23118
|
-
const setPicker =
|
|
22486
|
+
const livePickerRef = useRef14(null);
|
|
22487
|
+
const setPicker = useCallback13((next) => {
|
|
23119
22488
|
livePickerRef.current = typeof next === "function" ? next(livePickerRef.current) : next;
|
|
23120
22489
|
setPickerState((prev) => {
|
|
23121
22490
|
const resolved = typeof next === "function" ? next(prev) : next;
|
|
@@ -23136,14 +22505,14 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23136
22505
|
livePickerRef.current = picker;
|
|
23137
22506
|
const [contextPanel, setContextPanel] = useState11(null);
|
|
23138
22507
|
const [usagePanel, setUsagePanel] = useState11(null);
|
|
23139
|
-
const usageRequestRef =
|
|
23140
|
-
const settingsHeavyCacheRef =
|
|
23141
|
-
const closeUsagePanel =
|
|
22508
|
+
const usageRequestRef = useRef14(0);
|
|
22509
|
+
const settingsHeavyCacheRef = useRef14(null);
|
|
22510
|
+
const closeUsagePanel = useCallback13(() => {
|
|
23142
22511
|
usageRequestRef.current += 1;
|
|
23143
22512
|
setUsagePanel(null);
|
|
23144
22513
|
}, []);
|
|
23145
22514
|
const [providerPrompt, setProviderPrompt] = useState11(null);
|
|
23146
|
-
const oauthSubmitRef =
|
|
22515
|
+
const oauthSubmitRef = useRef14(false);
|
|
23147
22516
|
const [channelPrompt, setChannelPrompt] = useState11(null);
|
|
23148
22517
|
const [hookPrompt, setHookPrompt] = useState11(null);
|
|
23149
22518
|
const [settingsPrompt, setSettingsPrompt] = useState11(null);
|
|
@@ -23157,22 +22526,22 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23157
22526
|
setSettingsPrompt,
|
|
23158
22527
|
setContextPanel,
|
|
23159
22528
|
closeUsagePanel,
|
|
23160
|
-
listProjects,
|
|
23161
|
-
addProject,
|
|
23162
|
-
touchProjectSelected,
|
|
23163
|
-
resolveProjectPath,
|
|
23164
22529
|
projectNameFromPath,
|
|
23165
22530
|
pickFolder
|
|
23166
22531
|
});
|
|
23167
22532
|
projectPickerRef.current = projectPicker;
|
|
23168
|
-
const initialPickerBuiltRef =
|
|
22533
|
+
const initialPickerBuiltRef = useRef14(false);
|
|
23169
22534
|
if (!initialPickerBuiltRef.current) {
|
|
23170
22535
|
initialPickerBuiltRef.current = true;
|
|
23171
22536
|
const onboardingOwnsScreen = !resolveOnboardingCompleted(store, onboardingCompleted) || forceOnboarding;
|
|
23172
22537
|
if (!onboardingOwnsScreen && state.items.length === 0) {
|
|
23173
|
-
setPicker(projectPicker.buildProjectPickerState({ initialEntry: true }));
|
|
22538
|
+
setPicker(projectPicker.buildProjectPickerState({ initialEntry: true, loading: true }));
|
|
23174
22539
|
}
|
|
23175
22540
|
}
|
|
22541
|
+
useEffect14(() => {
|
|
22542
|
+
if (livePickerRef.current?._projectInitialPending !== true) return;
|
|
22543
|
+
void projectPickerRef.current?.openProjectPicker({ initialEntry: true });
|
|
22544
|
+
}, [store]);
|
|
23176
22545
|
const {
|
|
23177
22546
|
beginNewProject,
|
|
23178
22547
|
registerProject,
|
|
@@ -23181,7 +22550,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23181
22550
|
openProjectPicker
|
|
23182
22551
|
} = projectPicker;
|
|
23183
22552
|
const [disabledSkills, setDisabledSkillsInner] = useState11(() => /* @__PURE__ */ new Set());
|
|
23184
|
-
|
|
22553
|
+
useEffect14(() => {
|
|
23185
22554
|
let alive = true;
|
|
23186
22555
|
void Promise.resolve(store.getDisabledSkills?.()).then((result) => {
|
|
23187
22556
|
if (!alive) return;
|
|
@@ -23193,7 +22562,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23193
22562
|
alive = false;
|
|
23194
22563
|
};
|
|
23195
22564
|
}, [store]);
|
|
23196
|
-
const setDisabledSkills =
|
|
22565
|
+
const setDisabledSkills = useCallback13((next) => {
|
|
23197
22566
|
setDisabledSkillsInner((current) => {
|
|
23198
22567
|
const base = current instanceof Set ? current : new Set(current);
|
|
23199
22568
|
const set = typeof next === "function" ? next(base) : next instanceof Set ? next : new Set(next);
|
|
@@ -23208,7 +22577,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23208
22577
|
const toolApproval = state.toolApproval || null;
|
|
23209
22578
|
const [promptDraft, setPromptDraft] = useState11("");
|
|
23210
22579
|
const [promptDraftOverride, setPromptDraftOverride] = useState11(null);
|
|
23211
|
-
const promptLayoutValueRef =
|
|
22580
|
+
const promptLayoutValueRef = useRef14("");
|
|
23212
22581
|
const [, setPromptLayoutRows] = useState11(1);
|
|
23213
22582
|
const [textEntryLayoutRows, setTextEntryLayoutRows] = useState11(1);
|
|
23214
22583
|
const {
|
|
@@ -23225,12 +22594,12 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23225
22594
|
clearPastedTextsSnapshot,
|
|
23226
22595
|
registerPastedText
|
|
23227
22596
|
} = usePastedBuffers();
|
|
23228
|
-
const promptValueRef =
|
|
23229
|
-
const promptSelectionRef =
|
|
23230
|
-
const promptBoxRectRef =
|
|
23231
|
-
const promptMouseSelectionRef =
|
|
23232
|
-
const promptHistoryNavRef =
|
|
23233
|
-
const promptHistoryDraftChangeRef =
|
|
22597
|
+
const promptValueRef = useRef14("");
|
|
22598
|
+
const promptSelectionRef = useRef14(null);
|
|
22599
|
+
const promptBoxRectRef = useRef14(null);
|
|
22600
|
+
const promptMouseSelectionRef = useRef14(null);
|
|
22601
|
+
const promptHistoryNavRef = useRef14({ active: false, index: -1, seed: "", lastValue: "" });
|
|
22602
|
+
const promptHistoryDraftChangeRef = useRef14(false);
|
|
23234
22603
|
const {
|
|
23235
22604
|
promptHint,
|
|
23236
22605
|
promptHintTone,
|
|
@@ -23240,7 +22609,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23240
22609
|
showSelectionCopyHint,
|
|
23241
22610
|
clearPromptHint
|
|
23242
22611
|
} = usePromptHint();
|
|
23243
|
-
const toastErrorSignature =
|
|
22612
|
+
const toastErrorSignature = useMemo5(() => (state.toasts || []).filter((toast) => toast?.tone === "error").map((toast) => `${toast.id || ""}:${toast.text || ""}`).join("|"), [state.toasts]);
|
|
23244
22613
|
const {
|
|
23245
22614
|
welcomePromptHintDismissed,
|
|
23246
22615
|
conditionalWelcomePromptHint,
|
|
@@ -23250,16 +22619,16 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23250
22619
|
} = useWelcomePromptHint({ store, state, toastErrorSignature });
|
|
23251
22620
|
const [slashIndex, setSlashIndex] = useState11(0);
|
|
23252
22621
|
const [slashDismissedFor, setSlashDismissedFor] = useState11("");
|
|
23253
|
-
const slashPaletteRef =
|
|
23254
|
-
const workflowTabCycleRef =
|
|
23255
|
-
const scrollFocusRef =
|
|
23256
|
-
const onboardingStartedRef =
|
|
23257
|
-
const onboardingRef =
|
|
23258
|
-
const providerModelsCacheRef =
|
|
23259
|
-
const searchModelsCacheRef =
|
|
23260
|
-
const modelPickerRequestRef =
|
|
23261
|
-
const onboardingPrefetchSeqRef =
|
|
23262
|
-
const clearModelCaches =
|
|
22622
|
+
const slashPaletteRef = useRef14({ open: false, count: 0 });
|
|
22623
|
+
const workflowTabCycleRef = useRef14({ pending: false, lastAt: 0 });
|
|
22624
|
+
const scrollFocusRef = useRef14({});
|
|
22625
|
+
const onboardingStartedRef = useRef14(false);
|
|
22626
|
+
const onboardingRef = useRef14({ defaultRoute: null, searchRoute: null, agentRoutes: {}, agents: [], providerModels: [] });
|
|
22627
|
+
const providerModelsCacheRef = useRef14({ models: null, at: 0 });
|
|
22628
|
+
const searchModelsCacheRef = useRef14({ models: null, at: 0 });
|
|
22629
|
+
const modelPickerRequestRef = useRef14(0);
|
|
22630
|
+
const onboardingPrefetchSeqRef = useRef14(0);
|
|
22631
|
+
const clearModelCaches = useCallback13((scope = "all") => {
|
|
23263
22632
|
if (scope === "all" || scope === "provider") {
|
|
23264
22633
|
providerModelsCacheRef.current = { models: null, at: 0 };
|
|
23265
22634
|
onboardingRef.current.providerModels = [];
|
|
@@ -23269,7 +22638,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23269
22638
|
searchModelsCacheRef.current = { models: null, at: 0 };
|
|
23270
22639
|
}
|
|
23271
22640
|
}, []);
|
|
23272
|
-
|
|
22641
|
+
useEffect14(() => {
|
|
23273
22642
|
let alive = true;
|
|
23274
22643
|
const timer2 = setTimeout(async () => {
|
|
23275
22644
|
const seq = onboardingPrefetchSeqRef.current;
|
|
@@ -23354,24 +22723,24 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23354
22723
|
openUsagePanel: (...a) => openUsagePanel(...a),
|
|
23355
22724
|
openContextPicker: (...a) => openContextPicker(...a)
|
|
23356
22725
|
});
|
|
23357
|
-
const dragRef =
|
|
23358
|
-
const transcriptViewportRef =
|
|
23359
|
-
const panelTransitionRef =
|
|
23360
|
-
const panelCloseInkMaskRowsRef =
|
|
23361
|
-
const projectBootInputLatchRef =
|
|
23362
|
-
const frameRowsRef =
|
|
22726
|
+
const dragRef = useRef14({ anchor: null, anchorScroll: 0, last: null, active: false, rect: null, region: null, anchorSpan: null });
|
|
22727
|
+
const transcriptViewportRef = useRef14({ top: 0, bottom: 0 });
|
|
22728
|
+
const panelTransitionRef = useRef14({ signature: "", reserve: 0, clearRows: 0, guardRows: 0, epoch: 0 });
|
|
22729
|
+
const panelCloseInkMaskRowsRef = useRef14(0);
|
|
22730
|
+
const projectBootInputLatchRef = useRef14(false);
|
|
22731
|
+
const frameRowsRef = useRef14(24);
|
|
23363
22732
|
const STATUSLINE_BAND_ROWS = 3;
|
|
23364
22733
|
const promptContentColumns = Math.max(1, frameColumns - 4);
|
|
23365
|
-
const syncPromptLayoutRows =
|
|
22734
|
+
const syncPromptLayoutRows = useCallback13((value) => {
|
|
23366
22735
|
const text = String(value ?? "");
|
|
23367
22736
|
promptLayoutValueRef.current = text;
|
|
23368
22737
|
const nextRows = promptContentRows(text, promptContentColumns);
|
|
23369
22738
|
setPromptLayoutRows((prev) => prev === nextRows ? prev : nextRows);
|
|
23370
22739
|
}, [promptContentColumns]);
|
|
23371
|
-
|
|
22740
|
+
useEffect14(() => {
|
|
23372
22741
|
syncPromptLayoutRows(promptLayoutValueRef.current);
|
|
23373
22742
|
}, [syncPromptLayoutRows]);
|
|
23374
|
-
|
|
22743
|
+
useEffect14(() => {
|
|
23375
22744
|
const kind = String(settingsPrompt?.kind || "");
|
|
23376
22745
|
if (!CORE_MULTILINE_TEXT_ENTRY_KINDS.has(kind)) {
|
|
23377
22746
|
setTextEntryLayoutRows(1);
|
|
@@ -23380,10 +22749,10 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23380
22749
|
const cols = Math.max(1, frameColumns - 4 - stringWidth11("Sentence > "));
|
|
23381
22750
|
setTextEntryLayoutRows(textEntryReservedRows(settingsPrompt?.initialValue, cols, 8));
|
|
23382
22751
|
}, [settingsPrompt?.kind, settingsPrompt?.initialValue, frameColumns]);
|
|
23383
|
-
const selectionLayoutRef =
|
|
23384
|
-
const selectionTextRef =
|
|
23385
|
-
const lastClickRef =
|
|
23386
|
-
|
|
22752
|
+
const selectionLayoutRef = useRef14(null);
|
|
22753
|
+
const selectionTextRef = useRef14("");
|
|
22754
|
+
const lastClickRef = useRef14({ x: -1, y: -1, t: 0, count: 0 });
|
|
22755
|
+
useEffect14(() => {
|
|
23387
22756
|
const timer2 = setTimeout(() => setTuiReady(true), 0);
|
|
23388
22757
|
return () => clearTimeout(timer2);
|
|
23389
22758
|
}, []);
|
|
@@ -23429,7 +22798,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23429
22798
|
getStitchedSelectionText,
|
|
23430
22799
|
showSelectionCopyHint
|
|
23431
22800
|
});
|
|
23432
|
-
|
|
22801
|
+
useEffect14(() => () => {
|
|
23433
22802
|
stopSmoothScroll();
|
|
23434
22803
|
}, [stopSmoothScroll]);
|
|
23435
22804
|
useMouseInput({
|
|
@@ -23463,12 +22832,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23463
22832
|
const count = state.items.length;
|
|
23464
22833
|
const previousCount = lastItemsCountRef.current;
|
|
23465
22834
|
lastItemsCountRef.current = count;
|
|
23466
|
-
|
|
22835
|
+
const firstId = count > 0 ? state.items[0]?.id ?? null : null;
|
|
22836
|
+
const previousFirstId = lastFirstItemIdRef.current;
|
|
22837
|
+
lastFirstItemIdRef.current = firstId;
|
|
22838
|
+
if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
|
|
23467
22839
|
resetTranscriptScroll();
|
|
23468
22840
|
return;
|
|
23469
22841
|
}
|
|
23470
22842
|
if (count === previousCount || dragRef.current.active) return;
|
|
23471
|
-
}, [state.items
|
|
22843
|
+
}, [state.items, resetTranscriptScroll]);
|
|
23472
22844
|
const {
|
|
23473
22845
|
requestExit,
|
|
23474
22846
|
restoreQueuedToPrompt,
|
|
@@ -23490,6 +22862,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23490
22862
|
setPromptDraftOverride,
|
|
23491
22863
|
promptHistoryNavRef
|
|
23492
22864
|
});
|
|
22865
|
+
const { hasUserMessages, openMessageSelector } = useMessageSelector({
|
|
22866
|
+
store,
|
|
22867
|
+
state,
|
|
22868
|
+
setPicker,
|
|
22869
|
+
setPromptDraftOverride,
|
|
22870
|
+
syncPromptLayoutRows,
|
|
22871
|
+
showPromptHint,
|
|
22872
|
+
clearPromptHint
|
|
22873
|
+
});
|
|
23493
22874
|
const {
|
|
23494
22875
|
handlePromptPaste,
|
|
23495
22876
|
handlePromptHistoryNavigate,
|
|
@@ -23515,6 +22896,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23515
22896
|
recentPromptHistory,
|
|
23516
22897
|
resetPromptHistoryNav,
|
|
23517
22898
|
restoreQueuedToPrompt,
|
|
22899
|
+
openMessageSelector,
|
|
23518
22900
|
usagePanel,
|
|
23519
22901
|
closeUsagePanel,
|
|
23520
22902
|
contextPanel,
|
|
@@ -23525,7 +22907,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23525
22907
|
clearPastedTextsSnapshot,
|
|
23526
22908
|
registerPastedText
|
|
23527
22909
|
});
|
|
23528
|
-
const toggleExpand =
|
|
22910
|
+
const toggleExpand = useCallback13(() => {
|
|
23529
22911
|
setToolOutputExpanded((expanded) => !expanded);
|
|
23530
22912
|
}, []);
|
|
23531
22913
|
useGlobalKeyInput({
|
|
@@ -23565,7 +22947,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23565
22947
|
setUsagePanel,
|
|
23566
22948
|
closeUsagePanel
|
|
23567
22949
|
});
|
|
23568
|
-
|
|
22950
|
+
useEffect14(() => {
|
|
23569
22951
|
if (contextPanel?.kind === "context") {
|
|
23570
22952
|
openContextPicker();
|
|
23571
22953
|
return;
|
|
@@ -23586,7 +22968,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23586
22968
|
state.cwd,
|
|
23587
22969
|
state.clientHostPid
|
|
23588
22970
|
]);
|
|
23589
|
-
|
|
22971
|
+
useEffect14(() => {
|
|
23590
22972
|
if (onboardingStartedRef.current) return void 0;
|
|
23591
22973
|
if (resolveOnboardingCompleted(store, onboardingCompleted) && !forceOnboarding) return void 0;
|
|
23592
22974
|
let canceled = false;
|
|
@@ -23647,7 +23029,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23647
23029
|
hookPrompt: !!hookPrompt,
|
|
23648
23030
|
settingsPrompt: !!settingsPrompt
|
|
23649
23031
|
};
|
|
23650
|
-
|
|
23032
|
+
useEffect14(() => {
|
|
23651
23033
|
setSlashIndex((index) => Math.min(index, Math.max(0, slashCommands.length - 1)));
|
|
23652
23034
|
}, [slashCommands.length, activeSlashQuery]);
|
|
23653
23035
|
const {
|
|
@@ -23750,7 +23132,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23750
23132
|
transcriptContentHeight,
|
|
23751
23133
|
transcriptBottomSlackRows
|
|
23752
23134
|
} = layout;
|
|
23753
|
-
|
|
23135
|
+
useEffect14(() => {
|
|
23754
23136
|
const transition = panelTransitionRef.current;
|
|
23755
23137
|
const pendingInkMask = panelCloseInkMaskRowsRef.current;
|
|
23756
23138
|
const hadTransitionClearance = panelTransitionClearRows > 0 || panelTransitionGuardRows > 0;
|
|
@@ -23767,7 +23149,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23767
23149
|
const timer2 = setTimeout(() => setPanelTransitionEpoch((epoch) => epoch + 1), 0);
|
|
23768
23150
|
return () => clearTimeout(timer2);
|
|
23769
23151
|
}, [panelLayoutSignature, bottomClusterRows, panelTransitionClearRows, panelTransitionGuardRows]);
|
|
23770
|
-
|
|
23152
|
+
useEffect14(() => {
|
|
23771
23153
|
panelTransitionRef.current.tailId = latestTranscriptItem?.id ?? null;
|
|
23772
23154
|
});
|
|
23773
23155
|
const {
|
|
@@ -23811,7 +23193,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23811
23193
|
measuredRowsVersion,
|
|
23812
23194
|
setMeasuredRowsVersion
|
|
23813
23195
|
});
|
|
23814
|
-
const cycleWorkflowFromPrompt =
|
|
23196
|
+
const cycleWorkflowFromPrompt = useCallback13(() => {
|
|
23815
23197
|
if (slashPaletteOpen || toolApproval || picker || settingsPrompt || providerPrompt || channelPrompt || hookPrompt || contextPanel || usagePanel) return true;
|
|
23816
23198
|
const repeatGuardMs = 300;
|
|
23817
23199
|
const cycleGuard = workflowTabCycleRef.current;
|
|
@@ -23871,6 +23253,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23871
23253
|
handlePromptHistoryNavigate,
|
|
23872
23254
|
handlePromptInterrupt,
|
|
23873
23255
|
handlePromptPaste,
|
|
23256
|
+
hasUserMessages,
|
|
23874
23257
|
hookPrompt,
|
|
23875
23258
|
initialStatusLine,
|
|
23876
23259
|
onPromptDraftChange,
|
|
@@ -23914,7 +23297,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23914
23297
|
});
|
|
23915
23298
|
}
|
|
23916
23299
|
|
|
23917
|
-
// src/tui/
|
|
23300
|
+
// src/tui/session/transcript-spill.mjs
|
|
23918
23301
|
import { randomUUID } from "node:crypto";
|
|
23919
23302
|
|
|
23920
23303
|
// src/session-runtime/session-text.mjs
|
|
@@ -23942,44 +23325,19 @@ var SYNTHETIC_SESSION_TEXT_PATTERNS = Object.freeze([
|
|
|
23942
23325
|
init_plugin_paths();
|
|
23943
23326
|
init_config();
|
|
23944
23327
|
|
|
23945
|
-
// src/runtime/agent
|
|
23946
|
-
var
|
|
23947
|
-
|
|
23948
|
-
|
|
23949
|
-
|
|
23950
|
-
|
|
23951
|
-
xai: {
|
|
23952
|
-
baseURL: "https://api.x.ai/v1",
|
|
23953
|
-
defaultModel: "grok-4.5"
|
|
23954
|
-
},
|
|
23955
|
-
// OpenCode Go - low-cost coding-model subscription gateway.
|
|
23956
|
-
"opencode-go": {
|
|
23957
|
-
baseURL: "https://opencode.ai/zen/go/v1",
|
|
23958
|
-
defaultModel: "glm-5.2"
|
|
23959
|
-
},
|
|
23960
|
-
ollama: {
|
|
23961
|
-
baseURL: "http://localhost:11434/v1",
|
|
23962
|
-
defaultModel: "llama3.3:latest"
|
|
23963
|
-
},
|
|
23964
|
-
lmstudio: {
|
|
23965
|
-
baseURL: "http://localhost:1234/v1",
|
|
23966
|
-
defaultModel: "default"
|
|
23967
|
-
}
|
|
23968
|
-
};
|
|
23328
|
+
// src/runtime/shared/agent-route-config.mjs
|
|
23329
|
+
var LEGACY_AGENT_ROUTE_SLOTS = Object.freeze({
|
|
23330
|
+
explore: Object.freeze({ workflow: "explorer", maintenance: "explore" }),
|
|
23331
|
+
maintainer: Object.freeze({ alias: "maintenance", workflow: "memory", maintenance: "memory" }),
|
|
23332
|
+
worker: Object.freeze({ workflow: "agent" })
|
|
23333
|
+
});
|
|
23969
23334
|
|
|
23970
23335
|
// src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs
|
|
23971
23336
|
init_plugin_paths();
|
|
23972
|
-
import { join as
|
|
23973
|
-
var ANTHROPIC_DEFAULT_CREDENTIALS_PATH =
|
|
23337
|
+
import { join as join7 } from "node:path";
|
|
23338
|
+
var ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join7(resolvePluginData(), "anthropic-oauth-credentials.json");
|
|
23974
23339
|
|
|
23975
23340
|
// src/runtime/agent/orchestrator/config.mjs
|
|
23976
|
-
var CONFIG_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
23977
|
-
...Object.keys(AGENT_PROVIDER_ENV),
|
|
23978
|
-
...Object.keys(OPENAI_COMPAT_PRESETS),
|
|
23979
|
-
"openai-oauth",
|
|
23980
|
-
"anthropic-oauth",
|
|
23981
|
-
"grok-oauth"
|
|
23982
|
-
]);
|
|
23983
23341
|
var MAINTENANCE_SLOTS = Object.freeze(["explore", "memory"]);
|
|
23984
23342
|
var PROFILE_LANGUAGES = Object.freeze([
|
|
23985
23343
|
{ id: "system", label: "System (locale)", prompt: null },
|
|
@@ -24038,44 +23396,41 @@ var AGENT_PROVIDER_ALIASES = Object.freeze({
|
|
|
24038
23396
|
"xai-api": "xai"
|
|
24039
23397
|
});
|
|
24040
23398
|
|
|
24041
|
-
// src/tui/
|
|
23399
|
+
// src/tui/session/boot-profile.mjs
|
|
24042
23400
|
import { performance as performance2 } from "node:perf_hooks";
|
|
24043
23401
|
var BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ""));
|
|
24044
23402
|
var BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance2.now());
|
|
24045
23403
|
|
|
24046
|
-
// src/tui/
|
|
23404
|
+
// src/tui/session/agent-job-feed.mjs
|
|
24047
23405
|
init_paste_attachments();
|
|
24048
23406
|
|
|
24049
23407
|
// src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs
|
|
24050
23408
|
var DELIVERED_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
24051
23409
|
|
|
24052
|
-
// src/tui/
|
|
23410
|
+
// src/tui/session/tui-steering-persist.mjs
|
|
24053
23411
|
init_plugin_paths();
|
|
24054
23412
|
init_atomic_file();
|
|
24055
23413
|
var STALE_STEERING_RESTORE_TTL_MS = 30 * 60 * 1e3;
|
|
24056
23414
|
var _persistChain = Promise.resolve();
|
|
24057
23415
|
|
|
23416
|
+
// src/tui/session/oauth-flows.mjs
|
|
23417
|
+
var DEFAULT_FLOW_TTL_MS = 10 * 60 * 1e3;
|
|
23418
|
+
|
|
24058
23419
|
// src/tui/app/doctor.mjs
|
|
24059
23420
|
init_plugin_paths();
|
|
24060
23421
|
|
|
24061
|
-
// src/tui/
|
|
24062
|
-
var MANUAL_ABORT_RECOVERY_MS = (() => {
|
|
24063
|
-
const v = Number(process.env.MIXDOG_MANUAL_ABORT_RECOVERY_MS);
|
|
24064
|
-
return Number.isFinite(v) && v > 0 ? v : 4e3;
|
|
24065
|
-
})();
|
|
24066
|
-
|
|
24067
|
-
// src/tui/engine/live-share.mjs
|
|
23422
|
+
// src/tui/session/live-share.mjs
|
|
24068
23423
|
var MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
24069
23424
|
|
|
24070
|
-
// src/tui/
|
|
23425
|
+
// src/tui/session/transcript-spill.mjs
|
|
24071
23426
|
var TUI_DEBUG2 = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ""));
|
|
24072
23427
|
var TRANSCRIPT_SPILL_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
24073
23428
|
var TRANSCRIPT_PROCESS_NONCE = randomUUID();
|
|
24074
23429
|
|
|
24075
|
-
// src/tui/
|
|
24076
|
-
async function
|
|
24077
|
-
const {
|
|
24078
|
-
return await
|
|
23430
|
+
// src/tui/session.mjs
|
|
23431
|
+
async function createSessionRuntime(options = {}) {
|
|
23432
|
+
const { createSession } = await import("../../standalone/session-client.mjs");
|
|
23433
|
+
return await createSession(options);
|
|
24079
23434
|
}
|
|
24080
23435
|
|
|
24081
23436
|
// src/tui/index.jsx
|
|
@@ -24281,8 +23636,8 @@ function waitWithTimeout(promise, ms) {
|
|
|
24281
23636
|
let timer2 = null;
|
|
24282
23637
|
return Promise.race([
|
|
24283
23638
|
Promise.resolve(promise).then(() => true),
|
|
24284
|
-
new Promise((
|
|
24285
|
-
timer2 = setTimeout(() =>
|
|
23639
|
+
new Promise((resolve4) => {
|
|
23640
|
+
timer2 = setTimeout(() => resolve4(false), ms);
|
|
24286
23641
|
})
|
|
24287
23642
|
]).finally(() => {
|
|
24288
23643
|
if (timer2) clearTimeout(timer2);
|
|
@@ -24301,7 +23656,7 @@ function scheduleHardExit(code = 0) {
|
|
|
24301
23656
|
timer2.unref?.();
|
|
24302
23657
|
}
|
|
24303
23658
|
function resolveTuiStderrLogPath() {
|
|
24304
|
-
return process.env.MIXDOG_TUI_STDERR_LOG ||
|
|
23659
|
+
return process.env.MIXDOG_TUI_STDERR_LOG || join8(process.env.MIXDOG_RUNTIME_ROOT || join8(tmpdir2(), "mixdog"), "mixdog-tui.stderr.log");
|
|
24305
23660
|
}
|
|
24306
23661
|
function ansiFg(rgb) {
|
|
24307
23662
|
const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ""));
|
|
@@ -24383,7 +23738,7 @@ function installTuiStderrGuard() {
|
|
|
24383
23738
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
24384
23739
|
const logPath = resolveTuiStderrLogPath();
|
|
24385
23740
|
try {
|
|
24386
|
-
|
|
23741
|
+
mkdirSync6(dirname7(logPath), { recursive: true });
|
|
24387
23742
|
} catch {
|
|
24388
23743
|
}
|
|
24389
23744
|
(0, import_mixdog_debug.rotateBoundedLog)(logPath, import_mixdog_debug.PLUGIN_LOG_MAX_BYTES, import_mixdog_debug.PLUGIN_LOG_KEEP_BYTES);
|
|
@@ -24477,7 +23832,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
24477
23832
|
process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1B[2J\x1B[H`);
|
|
24478
23833
|
process.stdout.write("\x1B[5 q");
|
|
24479
23834
|
process.on("exit", restoreTerminal);
|
|
24480
|
-
const storeOutcomePromise =
|
|
23835
|
+
const storeOutcomePromise = createSessionRuntime({ provider, model, toolMode, remote }).then((store2) => ({ store: store2, error: null }), (error) => ({ store: null, error }));
|
|
24481
23836
|
try {
|
|
24482
23837
|
await loadThemeSettingFromConfig();
|
|
24483
23838
|
} catch {
|
|
@@ -24491,10 +23846,10 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
24491
23846
|
if (outcome.error) throw outcome.error;
|
|
24492
23847
|
store = outcome.store;
|
|
24493
23848
|
try {
|
|
24494
|
-
touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
23849
|
+
await store.touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
24495
23850
|
} catch {
|
|
24496
23851
|
}
|
|
24497
|
-
if (store.
|
|
23852
|
+
if (store.isRemoteSession === true) {
|
|
24498
23853
|
const remoteSetTheme = store.setTheme;
|
|
24499
23854
|
store.listThemes = () => listThemes();
|
|
24500
23855
|
store.getTheme = () => getThemeSetting();
|