mixdog 0.9.95 → 0.9.96
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/package.json +12 -9
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/shared/01-tool.md +20 -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/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +26 -11
- 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/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 +28 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -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 +1 -27
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- 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/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-reader.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -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 +118 -18
- 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/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +10 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +23 -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 +87 -15
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +2 -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/orchestrator.mjs +29 -1
- 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 +10 -10
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +56 -8
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +2 -2
- package/src/runtime/channels/lib/inbound-handler.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/memory/index.mjs +5 -38
- package/src/runtime/memory/lib/agent-ipc.mjs +145 -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 +12 -33
- 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 +8 -0
- 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-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-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/tool-defs.mjs +2 -2
- 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 +60 -31
- package/src/runtime/shared/config.mjs +130 -44
- package/src/runtime/shared/resource-admission.mjs +146 -97
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-surface.mjs +0 -5
- package/src/runtime/shared/turn-snapshot.mjs +31 -5
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/runtime-core.mjs +11 -11
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/session-lifecycle.mjs +9 -2
- package/src/session-runtime/session-turn-api.mjs +41 -9
- package/src/session-runtime/settings-api.mjs +5 -17
- 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/backend-daemon.mjs +97 -36
- package/src/standalone/channel-admin.mjs +12 -12
- package/src/standalone/channel-daemon-client.mjs +17 -1
- package/src/standalone/channel-daemon-transport.mjs +90 -8
- package/src/standalone/engine-daemon-client.mjs +380 -205
- package/src/standalone/engine-daemon-protocol.mjs +3 -4
- package/src/standalone/engine-daemon-service.mjs +378 -134
- package/src/standalone/engine-daemon-transport.mjs +268 -77
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/session-protocol.mjs +188 -0
- package/src/tui/App.jsx +30 -17
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +1 -2
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +46 -41
- 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 +6 -8
- 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 +9 -2
- 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/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/dist/index.mjs +920 -1592
- package/src/tui/engine/live-share.mjs +34 -0
- package/src/tui/engine/session-api-ext.mjs +1 -4
- package/src/tui/engine/session-api.mjs +103 -33
- package/src/tui/engine/session-flow.mjs +7 -1
- package/src/tui/engine/tool-card-results.mjs +4 -2
- package/src/tui/engine/tool-result-text.mjs +10 -0
- package/src/tui/engine/turn.mjs +9 -69
- package/src/tui/engine-local-session.mjs +47 -31
- package/src/tui/index.jsx +1 -2
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
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,112 @@ 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
|
+
const legacyId = String(channel.channelId || "").trim();
|
|
1094
|
+
const discordId = String(channel.discordChannelId || "").trim();
|
|
1095
|
+
const telegramId = String(channel.telegramChatId || "").trim();
|
|
1096
|
+
const backend = next.backend === "telegram" ? "telegram" : "discord";
|
|
1097
|
+
if (legacyId) {
|
|
1098
|
+
if (!discordId && !telegramId) {
|
|
1099
|
+
if (backend === "telegram") channel.telegramChatId = legacyId;
|
|
1100
|
+
else channel.discordChannelId = legacyId;
|
|
1101
|
+
delete channel.channelId;
|
|
1102
|
+
} else if (backend === "telegram" && telegramId || backend === "discord" && discordId || discordId && telegramId) {
|
|
1103
|
+
delete channel.channelId;
|
|
1104
|
+
}
|
|
1105
|
+
} else {
|
|
1106
|
+
delete channel.channelId;
|
|
1107
|
+
}
|
|
1108
|
+
next.channel = channel;
|
|
1109
|
+
return next;
|
|
1110
|
+
}
|
|
1111
|
+
function canonicalizeUnifiedConfig(value = {}) {
|
|
1112
|
+
const next = isPlainObject2(value) ? { ...value } : {};
|
|
1113
|
+
const hadAgent = isPlainObject2(next.agent);
|
|
1114
|
+
const agent = hadAgent ? { ...next.agent } : {};
|
|
1115
|
+
if (!String(next.outputStyle || "").trim() && String(agent.outputStyle || "").trim()) {
|
|
1116
|
+
next.outputStyle = agent.outputStyle;
|
|
1117
|
+
}
|
|
1118
|
+
delete agent.outputStyle;
|
|
1119
|
+
for (const key of ["autoClear", "compaction", "shell"]) {
|
|
1120
|
+
if (!isPlainObject2(agent[key]) && isPlainObject2(next[key])) agent[key] = next[key];
|
|
1121
|
+
delete next[key];
|
|
1122
|
+
}
|
|
1123
|
+
if (isPlainObject2(next.mcpServers)) {
|
|
1124
|
+
agent.mcpServers = {
|
|
1125
|
+
...next.mcpServers,
|
|
1126
|
+
...isPlainObject2(agent.mcpServers) ? agent.mcpServers : {}
|
|
1127
|
+
};
|
|
1128
|
+
delete next.mcpServers;
|
|
1129
|
+
}
|
|
1130
|
+
if (!isPlainObject2(agent.searchRoute) && isPlainObject2(agent.search)) {
|
|
1131
|
+
agent.searchRoute = agent.search;
|
|
1132
|
+
}
|
|
1133
|
+
delete agent.search;
|
|
1134
|
+
delete agent.capabilities;
|
|
1135
|
+
const modules = isPlainObject2(agent.modules) ? { ...agent.modules } : {};
|
|
1136
|
+
if (hasOwn(modules, "memory")) {
|
|
1137
|
+
const legacyMemory = modules.memory;
|
|
1138
|
+
const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
|
|
1139
|
+
if (!explicitRecap) {
|
|
1140
|
+
const enabled = isPlainObject2(legacyMemory) ? legacyMemory.enabled !== false : legacyMemory !== false;
|
|
1141
|
+
agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled };
|
|
1142
|
+
}
|
|
1143
|
+
delete modules.memory;
|
|
1144
|
+
agent.modules = modules;
|
|
1145
|
+
}
|
|
1146
|
+
if (isPlainObject2(next.memory)) {
|
|
1147
|
+
const memory = { ...next.memory };
|
|
1148
|
+
const user = isPlainObject2(memory.user) ? { ...memory.user } : {};
|
|
1149
|
+
const legacyTitle = String(user.title || "").trim();
|
|
1150
|
+
if (legacyTitle && !String(agent.profile?.title || "").trim()) {
|
|
1151
|
+
agent.profile = { ...isPlainObject2(agent.profile) ? agent.profile : {}, title: legacyTitle };
|
|
1152
|
+
}
|
|
1153
|
+
delete user.title;
|
|
1154
|
+
if (Object.keys(user).length) memory.user = user;
|
|
1155
|
+
else delete memory.user;
|
|
1156
|
+
if (hasOwn(memory, "enabled")) {
|
|
1157
|
+
const explicitRecap = isPlainObject2(agent.recap) && hasOwn(agent.recap, "enabled");
|
|
1158
|
+
if (!explicitRecap) {
|
|
1159
|
+
agent.recap = { ...isPlainObject2(agent.recap) ? agent.recap : {}, enabled: memory.enabled !== false };
|
|
1160
|
+
}
|
|
1161
|
+
delete memory.enabled;
|
|
1162
|
+
}
|
|
1163
|
+
if (Object.keys(memory).length) next.memory = memory;
|
|
1164
|
+
else delete next.memory;
|
|
1165
|
+
}
|
|
1166
|
+
if (isPlainObject2(next.channels)) {
|
|
1167
|
+
next.channels = canonicalizeStoredChannelsConfig(next.channels);
|
|
1168
|
+
}
|
|
1169
|
+
delete next.search;
|
|
1170
|
+
delete next.capabilities;
|
|
1171
|
+
if (hadAgent || Object.keys(agent).length) next.agent = agent;
|
|
1172
|
+
return next;
|
|
1173
|
+
}
|
|
1034
1174
|
function stripGeneratedMarker(data) {
|
|
1035
1175
|
if (!isPlainObject2(data) || !Object.prototype.hasOwnProperty.call(data, GENERATED_KEY)) return data;
|
|
1036
1176
|
const { [GENERATED_KEY]: _generated, ...rest } = data;
|
|
1037
1177
|
return rest;
|
|
1038
1178
|
}
|
|
1039
|
-
function readJsonFile(
|
|
1179
|
+
function readJsonFile(path2) {
|
|
1040
1180
|
let raw;
|
|
1041
|
-
if (
|
|
1181
|
+
if (path2 === CONFIG_PATH) {
|
|
1042
1182
|
raw = readConfigRawCached();
|
|
1043
1183
|
} else {
|
|
1044
1184
|
try {
|
|
1045
|
-
raw =
|
|
1185
|
+
raw = readFileSync4(path2, "utf8");
|
|
1046
1186
|
} catch (err) {
|
|
1047
1187
|
if (err.code === "ENOENT") return null;
|
|
1048
|
-
process.stderr.write(`[config] readJsonFile: unexpected read error for ${
|
|
1188
|
+
process.stderr.write(`[config] readJsonFile: unexpected read error for ${path2}: ${err.message}
|
|
1049
1189
|
`);
|
|
1050
1190
|
throw err;
|
|
1051
1191
|
}
|
|
@@ -1054,11 +1194,11 @@ function readJsonFile(path3) {
|
|
|
1054
1194
|
try {
|
|
1055
1195
|
return JSON.parse(raw);
|
|
1056
1196
|
} catch (err) {
|
|
1057
|
-
if (
|
|
1197
|
+
if (path2 === CONFIG_PATH) {
|
|
1058
1198
|
invalidateConfigReadCache();
|
|
1059
|
-
const corrupt = `${
|
|
1199
|
+
const corrupt = `${path2}.corrupt-${Date.now()}`;
|
|
1060
1200
|
try {
|
|
1061
|
-
renameWithRetrySync(
|
|
1201
|
+
renameWithRetrySync(path2, corrupt);
|
|
1062
1202
|
} catch {
|
|
1063
1203
|
}
|
|
1064
1204
|
process.stderr.write(`[config] mixdog-config.json is malformed (${err.message}). Renamed to ${corrupt}. Restore it or delete to start fresh.
|
|
@@ -1069,12 +1209,12 @@ function readJsonFile(path3) {
|
|
|
1069
1209
|
}
|
|
1070
1210
|
function readAll() {
|
|
1071
1211
|
const parsed = readJsonFile(CONFIG_PATH);
|
|
1072
|
-
if (parsed != null) return parsed;
|
|
1212
|
+
if (parsed != null) return canonicalizeUnifiedConfig(parsed);
|
|
1073
1213
|
if (hasUserDataInitMarker(DATA_DIR)) {
|
|
1074
1214
|
const restored = loadLatestMixdogConfigFromBackup(DATA_DIR);
|
|
1075
1215
|
if (restored && isPlainObject2(restored)) {
|
|
1076
1216
|
process.stderr.write("[config] read: restored mixdog-config.json from latest user-data backup (missing after init)\n");
|
|
1077
|
-
return restored;
|
|
1217
|
+
return canonicalizeUnifiedConfig(restored);
|
|
1078
1218
|
}
|
|
1079
1219
|
}
|
|
1080
1220
|
return {};
|
|
@@ -1082,7 +1222,7 @@ function readAll() {
|
|
|
1082
1222
|
function quarantineMalformedConfig(parseErr) {
|
|
1083
1223
|
const corrupt = `${CONFIG_PATH}.corrupt-${Date.now()}`;
|
|
1084
1224
|
try {
|
|
1085
|
-
if (
|
|
1225
|
+
if (existsSync5(CONFIG_PATH)) renameWithRetrySync(CONFIG_PATH, corrupt);
|
|
1086
1226
|
} catch {
|
|
1087
1227
|
}
|
|
1088
1228
|
process.stderr.write(
|
|
@@ -1104,7 +1244,7 @@ function restoreAllForRmWOrThrow(reason) {
|
|
|
1104
1244
|
function readAllForRmW() {
|
|
1105
1245
|
let raw;
|
|
1106
1246
|
try {
|
|
1107
|
-
raw =
|
|
1247
|
+
raw = readFileSync4(CONFIG_PATH, "utf8");
|
|
1108
1248
|
} catch (err) {
|
|
1109
1249
|
if (err.code === "ENOENT") {
|
|
1110
1250
|
if (hasUserDataInitMarker(DATA_DIR)) {
|
|
@@ -1128,16 +1268,16 @@ function readAllForRmW() {
|
|
|
1128
1268
|
function readSection(section) {
|
|
1129
1269
|
return stripGeneratedMarker(readAll()[section] ?? null) ?? {};
|
|
1130
1270
|
}
|
|
1131
|
-
async function writeJsonFileAsync(
|
|
1132
|
-
|
|
1133
|
-
if (
|
|
1271
|
+
async function writeJsonFileAsync(path2, data) {
|
|
1272
|
+
mkdirSync4(dirname5(path2), { recursive: true, mode: 448 });
|
|
1273
|
+
if (path2 === CONFIG_PATH) {
|
|
1134
1274
|
try {
|
|
1135
1275
|
await backupUserDataAsync(DATA_DIR, "pre-config-write");
|
|
1136
1276
|
} catch {
|
|
1137
1277
|
}
|
|
1138
1278
|
}
|
|
1139
|
-
await writeJsonAtomicAsync(
|
|
1140
|
-
if (
|
|
1279
|
+
await writeJsonAtomicAsync(path2, data, { lock: false, fsyncDir: true, mode: 384, secret: true });
|
|
1280
|
+
if (path2 === CONFIG_PATH) {
|
|
1141
1281
|
invalidateConfigReadCache();
|
|
1142
1282
|
try {
|
|
1143
1283
|
markUserDataInitialized(DATA_DIR);
|
|
@@ -1150,10 +1290,10 @@ async function writeJsonFileAsync(path3, data) {
|
|
|
1150
1290
|
}
|
|
1151
1291
|
}
|
|
1152
1292
|
async function writeAllAsync(data) {
|
|
1153
|
-
await writeJsonFileAsync(CONFIG_PATH, data);
|
|
1293
|
+
await writeJsonFileAsync(CONFIG_PATH, canonicalizeUnifiedConfig(data));
|
|
1154
1294
|
}
|
|
1155
1295
|
function withConfigLockAsync(fn) {
|
|
1156
|
-
return withFileLock(`${CONFIG_PATH}.lock`, fn
|
|
1296
|
+
return withFileLock(`${CONFIG_PATH}.lock`, fn);
|
|
1157
1297
|
}
|
|
1158
1298
|
function trackConfigWrite(work) {
|
|
1159
1299
|
let promise;
|
|
@@ -1177,981 +1317,37 @@ function updateSectionAsync(section, updater) {
|
|
|
1177
1317
|
await writeAllAsync(all);
|
|
1178
1318
|
}));
|
|
1179
1319
|
}
|
|
1180
|
-
var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites,
|
|
1320
|
+
var _require, _getSecret, _setSecret, _deleteSecret, _hasSecret, _invalidateSecretCache, DATA_DIR, CONFIG_PATH, GENERATED_KEY, _configReadCache, CONFIG_READ_TTL_MS, _pendingConfigWrites, SECRET_ACCOUNTS;
|
|
1181
1321
|
var init_config = __esm({
|
|
1182
1322
|
"src/runtime/shared/config.mjs"() {
|
|
1183
1323
|
init_plugin_paths();
|
|
1184
1324
|
init_atomic_file();
|
|
1185
1325
|
init_user_data_guard();
|
|
1186
|
-
init_provider_api_key();
|
|
1187
|
-
_require = createRequire3(import.meta.url);
|
|
1188
|
-
({
|
|
1189
|
-
getSecret: _getSecret,
|
|
1190
|
-
setSecret: _setSecret,
|
|
1191
|
-
deleteSecret: _deleteSecret,
|
|
1192
|
-
hasSecret: _hasSecret,
|
|
1193
|
-
invalidateSecretCache: _invalidateSecretCache
|
|
1194
|
-
} = _require("../../lib/keychain-cjs.cjs"));
|
|
1195
|
-
DATA_DIR = resolvePluginData();
|
|
1196
|
-
CONFIG_PATH =
|
|
1197
|
-
GENERATED_KEY = "_generated";
|
|
1198
|
-
_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
|
-
]);
|
|
1326
|
+
init_provider_api_key();
|
|
1327
|
+
_require = createRequire3(import.meta.url);
|
|
1328
|
+
({
|
|
1329
|
+
getSecret: _getSecret,
|
|
1330
|
+
setSecret: _setSecret,
|
|
1331
|
+
deleteSecret: _deleteSecret,
|
|
1332
|
+
hasSecret: _hasSecret,
|
|
1333
|
+
invalidateSecretCache: _invalidateSecretCache
|
|
1334
|
+
} = _require("../../lib/keychain-cjs.cjs"));
|
|
1335
|
+
DATA_DIR = resolvePluginData();
|
|
1336
|
+
CONFIG_PATH = join5(DATA_DIR, "mixdog-config.json");
|
|
1337
|
+
GENERATED_KEY = "_generated";
|
|
1338
|
+
_configReadCache = null;
|
|
1339
|
+
CONFIG_READ_TTL_MS = (() => {
|
|
1340
|
+
const n = Number(process.env.MIXDOG_CONFIG_READ_TTL_MS);
|
|
1341
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
1342
|
+
})();
|
|
1343
|
+
_pendingConfigWrites = /* @__PURE__ */ new Set();
|
|
1344
|
+
SECRET_ACCOUNTS = Object.freeze({
|
|
1345
|
+
discordToken: "discord.token",
|
|
1346
|
+
telegramToken: "telegram.token",
|
|
1347
|
+
agentApiKey: (provider) => `agent.${provider}.apiKey`,
|
|
1348
|
+
openaiUsageSessionKey: "agent.openai.usageSessionKey",
|
|
1349
|
+
opencodeGoAuthCookie: "agent.opencode-go.authCookie"
|
|
1350
|
+
});
|
|
2155
1351
|
}
|
|
2156
1352
|
});
|
|
2157
1353
|
|
|
@@ -2160,7 +1356,7 @@ var require_mixdog_debug = __commonJS({
|
|
|
2160
1356
|
"src/lib/mixdog-debug.cjs"(exports, module) {
|
|
2161
1357
|
"use strict";
|
|
2162
1358
|
var fs = __require("fs");
|
|
2163
|
-
var
|
|
1359
|
+
var path2 = __require("path");
|
|
2164
1360
|
function isTruthyEnv(value) {
|
|
2165
1361
|
if (value == null || value === "") return false;
|
|
2166
1362
|
const s = String(value).trim().toLowerCase();
|
|
@@ -2173,12 +1369,12 @@ var require_mixdog_debug = __commonJS({
|
|
|
2173
1369
|
function _detectFromSourceCheckout() {
|
|
2174
1370
|
if (_cachedFromSource !== null) return _cachedFromSource;
|
|
2175
1371
|
try {
|
|
2176
|
-
const moduleDir = module && module.filename ?
|
|
1372
|
+
const moduleDir = module && module.filename ? path2.dirname(module.filename) : process.cwd();
|
|
2177
1373
|
const roots = [
|
|
2178
|
-
|
|
1374
|
+
path2.resolve(moduleDir, "..", ".."),
|
|
2179
1375
|
process.cwd()
|
|
2180
1376
|
];
|
|
2181
|
-
_cachedFromSource = roots.some((root) => fs.existsSync(
|
|
1377
|
+
_cachedFromSource = roots.some((root) => fs.existsSync(path2.join(root, ".git")));
|
|
2182
1378
|
} catch {
|
|
2183
1379
|
_cachedFromSource = false;
|
|
2184
1380
|
}
|
|
@@ -2244,7 +1440,7 @@ var require_mixdog_debug = __commonJS({
|
|
|
2244
1440
|
}
|
|
2245
1441
|
const candidates = [];
|
|
2246
1442
|
for (const e of entries) {
|
|
2247
|
-
const p =
|
|
1443
|
+
const p = path2.join(dataDir, e.name);
|
|
2248
1444
|
try {
|
|
2249
1445
|
const st = fs.statSync(p);
|
|
2250
1446
|
if (now - st.mtimeMs < minAgeMs) continue;
|
|
@@ -2285,7 +1481,7 @@ var require_mixdog_debug = __commonJS({
|
|
|
2285
1481
|
function appendSessionStartCriticalLog(dataDir, line) {
|
|
2286
1482
|
if (!dataDir || !line) return;
|
|
2287
1483
|
try {
|
|
2288
|
-
const p =
|
|
1484
|
+
const p = path2.join(dataDir, SESSION_START_CRITICAL_LOG);
|
|
2289
1485
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
2290
1486
|
rotateBoundedLog2(p, SESSION_START_CRITICAL_MAX_BYTES, SESSION_START_CRITICAL_KEEP_BYTES);
|
|
2291
1487
|
fs.appendFileSync(p, line.endsWith("\n") ? line : `${line}
|
|
@@ -2312,14 +1508,14 @@ var require_mixdog_debug = __commonJS({
|
|
|
2312
1508
|
// src/tui/index.jsx
|
|
2313
1509
|
import React22 from "react";
|
|
2314
1510
|
import { render } from "../../../vendor/ink/build/index.js";
|
|
2315
|
-
import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as
|
|
1511
|
+
import { closeSync as closeSync2, constants as fsConstants, createWriteStream, mkdirSync as mkdirSync6, openSync as openSync2, readSync } from "node:fs";
|
|
2316
1512
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
2317
|
-
import { dirname as
|
|
1513
|
+
import { dirname as dirname7, join as join8 } from "node:path";
|
|
2318
1514
|
import { performance as performance3 } from "node:perf_hooks";
|
|
2319
1515
|
import { format } from "node:util";
|
|
2320
1516
|
|
|
2321
1517
|
// src/tui/App.jsx
|
|
2322
|
-
import React21, { useState as useState11, useCallback as
|
|
1518
|
+
import React21, { useState as useState11, useCallback as useCallback13, useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useMemo as useMemo5, useRef as useRef14 } from "react";
|
|
2323
1519
|
import { Box as Box19, Text as Text21, useApp, useInput as useInput6, useStdin as useStdin3, useStdout as useStdout2 } from "../../../vendor/ink/build/index.js";
|
|
2324
1520
|
|
|
2325
1521
|
// src/tui/themes/base.mjs
|
|
@@ -3992,8 +3188,8 @@ function parseToolArgs(args) {
|
|
|
3992
3188
|
}
|
|
3993
3189
|
return { value: args };
|
|
3994
3190
|
}
|
|
3995
|
-
function displayToolPath(
|
|
3996
|
-
const text = String(
|
|
3191
|
+
function displayToolPath(path2) {
|
|
3192
|
+
const text = String(path2 ?? "");
|
|
3997
3193
|
return text.replace(/\\/g, "/").split("/").filter(Boolean).at(-1) || text;
|
|
3998
3194
|
}
|
|
3999
3195
|
function compactParts(parts) {
|
|
@@ -4187,16 +3383,16 @@ function looksLikeZeroResultText(text) {
|
|
|
4187
3383
|
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
3384
|
}
|
|
4189
3385
|
function splitPathAndDelta(value, explicitDelta = "") {
|
|
4190
|
-
let
|
|
3386
|
+
let path2 = String(value ?? "").trim();
|
|
4191
3387
|
let delta = String(explicitDelta ?? "").trim();
|
|
4192
3388
|
if (!delta) {
|
|
4193
|
-
const sep =
|
|
3389
|
+
const sep = path2.lastIndexOf(" \u2014 ");
|
|
4194
3390
|
if (sep !== -1) {
|
|
4195
|
-
delta =
|
|
4196
|
-
|
|
3391
|
+
delta = path2.slice(sep + 3).trim();
|
|
3392
|
+
path2 = path2.slice(0, sep).trim();
|
|
4197
3393
|
}
|
|
4198
3394
|
}
|
|
4199
|
-
return { path:
|
|
3395
|
+
return { path: path2, delta };
|
|
4200
3396
|
}
|
|
4201
3397
|
function parseLineDelta(delta) {
|
|
4202
3398
|
const totals = { added: 0, removed: 0, seen: false };
|
|
@@ -4230,14 +3426,14 @@ function summarizeUpdateResult(text, args) {
|
|
|
4230
3426
|
for (const line of String(text ?? "").split("\n")) {
|
|
4231
3427
|
const ok = /^\s*OK\s+(modify|add|delete|create)\s+(.+?)\s*$/i.exec(line);
|
|
4232
3428
|
if (ok) {
|
|
4233
|
-
const { path:
|
|
4234
|
-
changed.push({ action: ok[1].toLowerCase(), path:
|
|
3429
|
+
const { path: path2, delta } = splitPathAndDelta(ok[2]);
|
|
3430
|
+
changed.push({ action: ok[1].toLowerCase(), path: path2, delta });
|
|
4235
3431
|
continue;
|
|
4236
3432
|
}
|
|
4237
3433
|
const edited = /^\s*(Edited|Created|Updated|Wrote):\s+(.+?)(?:\s+\(([^)]+)\))?\s*$/i.exec(line);
|
|
4238
3434
|
if (edited) {
|
|
4239
|
-
const { path:
|
|
4240
|
-
changed.push({ action: edited[1].toLowerCase(), path:
|
|
3435
|
+
const { path: path2, delta } = splitPathAndDelta(edited[2], edited[3] || "");
|
|
3436
|
+
changed.push({ action: edited[1].toLowerCase(), path: path2, delta });
|
|
4241
3437
|
}
|
|
4242
3438
|
}
|
|
4243
3439
|
if (changed.length === 1) {
|
|
@@ -4957,11 +4153,6 @@ function toolWorkUnit(name, args = {}, category = "") {
|
|
|
4957
4153
|
}
|
|
4958
4154
|
return unitDescriptor("Patch", {
|
|
4959
4155
|
count: patchFileCount(a) || 1,
|
|
4960
|
-
// post_shell is NOT named here: the turn engine already splits such a
|
|
4961
|
-
// call into this edit card plus a trailing Shell card that carries the
|
|
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
4156
|
active: creating ? "Creating" : deleting ? "Deleting" : "Editing",
|
|
4966
4157
|
done: creating ? "Created" : deleting ? "Deleted" : "Edited",
|
|
4967
4158
|
noun: "file"
|
|
@@ -5179,9 +4370,9 @@ function reconcileTimer() {
|
|
|
5179
4370
|
clearTimeout(timer);
|
|
5180
4371
|
timer = null;
|
|
5181
4372
|
}
|
|
5182
|
-
const
|
|
5183
|
-
if (
|
|
5184
|
-
timer = setTimeout(onTick,
|
|
4373
|
+
const delay = nextDelayMs(Date.now());
|
|
4374
|
+
if (delay == null) return;
|
|
4375
|
+
timer = setTimeout(onTick, delay);
|
|
5185
4376
|
timer.unref?.();
|
|
5186
4377
|
}
|
|
5187
4378
|
function onTick() {
|
|
@@ -5411,6 +4602,16 @@ var SPINNER_VERBS = [
|
|
|
5411
4602
|
"Zigzagging"
|
|
5412
4603
|
];
|
|
5413
4604
|
var SPINNER_VERB_ROTATE_MS = 3e4;
|
|
4605
|
+
var SPINNER_VERB_CHANGE_ODDS = 0.5;
|
|
4606
|
+
function boundarySwaps(anchor, boundary) {
|
|
4607
|
+
let h = Math.floor(anchor / 1e3) + Math.imul(boundary, 2654435761) | 0;
|
|
4608
|
+
h = Math.imul(h ^ h >>> 16, 569420461);
|
|
4609
|
+
h = Math.imul(h ^ h >>> 15, 1935289751);
|
|
4610
|
+
h = (h ^ h >>> 15) >>> 0;
|
|
4611
|
+
return h / 4294967296 < SPINNER_VERB_CHANGE_ODDS;
|
|
4612
|
+
}
|
|
4613
|
+
var MAX_COUNTED_BOUNDARIES = 4096;
|
|
4614
|
+
var MAX_HELD_BOUNDARIES = 3;
|
|
5414
4615
|
var SPINNER_MODE_OVERRIDE_VERBS = {
|
|
5415
4616
|
compacting: "Compacting conversation",
|
|
5416
4617
|
"auto-clear": "Auto-clearing conversation",
|
|
@@ -5420,14 +4621,24 @@ function spinnerVerbFor(startedAt = 0, now = 0, pool = SPINNER_VERBS) {
|
|
|
5420
4621
|
const list = Array.isArray(pool) && pool.length ? pool : SPINNER_VERBS;
|
|
5421
4622
|
const anchor = Math.max(0, Number(startedAt) || 0);
|
|
5422
4623
|
const elapsed = anchor > 0 ? Math.max(0, (Number(now) || 0) - anchor) : 0;
|
|
5423
|
-
const
|
|
5424
|
-
const
|
|
4624
|
+
const boundaries = Math.floor(elapsed / SPINNER_VERB_ROTATE_MS);
|
|
4625
|
+
const counted = Math.min(boundaries, MAX_COUNTED_BOUNDARIES);
|
|
4626
|
+
let swaps = boundaries - counted;
|
|
4627
|
+
let held = 0;
|
|
4628
|
+
for (let boundary = 1; boundary <= counted; boundary += 1) {
|
|
4629
|
+
if (held >= MAX_HELD_BOUNDARIES || boundarySwaps(anchor, boundary)) {
|
|
4630
|
+
swaps += 1;
|
|
4631
|
+
held = 0;
|
|
4632
|
+
} else {
|
|
4633
|
+
held += 1;
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
const seed = Math.floor(anchor / 1e3) + swaps;
|
|
5425
4637
|
return list[(seed * 7 + 3) % list.length];
|
|
5426
4638
|
}
|
|
5427
4639
|
var SPINNER_FRAMES = ["\u25C7", "\u25C6", "\u25C8", "\u25C6", "\u25C7"];
|
|
5428
4640
|
|
|
5429
4641
|
// src/tui/spinner-meta.mjs
|
|
5430
|
-
var SHOW_TOKENS_AFTER_MS = 3e4;
|
|
5431
4642
|
var compactNumberFormatter = new Intl.NumberFormat("en-US", {
|
|
5432
4643
|
notation: "compact",
|
|
5433
4644
|
maximumFractionDigits: 1,
|
|
@@ -5462,7 +4673,10 @@ function buildSpinnerMeta({
|
|
|
5462
4673
|
const thinkingText = spinnerThinkingLabel({ thinking, thinkingSince, thinkingMs, effort });
|
|
5463
4674
|
return {
|
|
5464
4675
|
tokensText: tokens > 0 ? `${formatSpinnerTokens(tokens)} tokens` : "",
|
|
5465
|
-
|
|
4676
|
+
// The count appears with the FIRST token (user: 30초가 아니라 바로).
|
|
4677
|
+
// Elapsed was never gated, so the byline used to grow a second field
|
|
4678
|
+
// mid-turn for no reason the reader could see; now both arrive together.
|
|
4679
|
+
showTokens: tokens > 0,
|
|
5466
4680
|
thinkingText,
|
|
5467
4681
|
thinkingActive: Boolean(thinking || Number(thinkingSince) > 0)
|
|
5468
4682
|
};
|
|
@@ -6001,8 +5215,8 @@ function hasActiveStatuslineWork(line, agentWorkers = [], agentJobs = [], active
|
|
|
6001
5215
|
function bootFullRenderEligible(mountAtMs, line, agentWorkers = [], agentJobs = [], activeTools = null) {
|
|
6002
5216
|
const elapsed = Date.now() - mountAtMs;
|
|
6003
5217
|
const active = hasActiveStatuslineWork(line, agentWorkers, agentJobs, activeTools);
|
|
6004
|
-
const
|
|
6005
|
-
return elapsed >=
|
|
5218
|
+
const delay = active ? STATUSLINE_BOOT_FULL_DELAY_ACTIVE_MS : STATUSLINE_BOOT_FULL_DELAY_MS;
|
|
5219
|
+
return elapsed >= delay;
|
|
6006
5220
|
}
|
|
6007
5221
|
function canAttemptBootFullRender(nextAttemptAtMs = 0) {
|
|
6008
5222
|
return Date.now() >= nextAttemptAtMs;
|
|
@@ -6775,15 +5989,15 @@ function singleTrailingLineBreakPrefix(text) {
|
|
|
6775
5989
|
function draftStateEqual(a, b) {
|
|
6776
5990
|
return a.value === b.value && a.cursor === b.cursor && a.selectionAnchor === b.selectionAnchor;
|
|
6777
5991
|
}
|
|
6778
|
-
var
|
|
5992
|
+
var MODIFIED_ENTER_NEWLINE = 1 | 2 | 4;
|
|
6779
5993
|
function isModifiedEnterSequence(input) {
|
|
6780
5994
|
const text = String(input ?? "");
|
|
6781
5995
|
const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
|
|
6782
5996
|
if (!body) return false;
|
|
6783
5997
|
const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
|
|
6784
|
-
if (kitty) return (Number(kitty[1]) - 1 &
|
|
5998
|
+
if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0;
|
|
6785
5999
|
const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
|
|
6786
|
-
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 &
|
|
6000
|
+
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_NEWLINE) !== 0);
|
|
6787
6001
|
}
|
|
6788
6002
|
function isAnyModifiedEnterSequence(input) {
|
|
6789
6003
|
const text = String(input ?? "");
|
|
@@ -6796,9 +6010,6 @@ function isAnyModifiedEnterSequence(input) {
|
|
|
6796
6010
|
}
|
|
6797
6011
|
|
|
6798
6012
|
// src/tui/components/prompt-input/immediate-render.mjs
|
|
6799
|
-
function suppressed(value) {
|
|
6800
|
-
return typeof value === "function" ? value() === true : value === true;
|
|
6801
|
-
}
|
|
6802
6013
|
function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
|
|
6803
6014
|
if (!throttle || throttle.timer === null) return false;
|
|
6804
6015
|
clearTimer(throttle.timer);
|
|
@@ -6807,7 +6018,6 @@ function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
|
|
|
6807
6018
|
}
|
|
6808
6019
|
function schedulePromptImmediateFlush({
|
|
6809
6020
|
throttle,
|
|
6810
|
-
isSuppressed = false,
|
|
6811
6021
|
flush,
|
|
6812
6022
|
intervalMs = 16,
|
|
6813
6023
|
now = Date.now,
|
|
@@ -6818,10 +6028,6 @@ function schedulePromptImmediateFlush({
|
|
|
6818
6028
|
if (!throttle || typeof flush !== "function") {
|
|
6819
6029
|
throw new TypeError("schedulePromptImmediateFlush requires throttle state and a flush function");
|
|
6820
6030
|
}
|
|
6821
|
-
if (suppressed(isSuppressed)) {
|
|
6822
|
-
cancelPromptImmediateFlush(throttle, clearTimer);
|
|
6823
|
-
return false;
|
|
6824
|
-
}
|
|
6825
6031
|
const current = now();
|
|
6826
6032
|
const elapsed = current - throttle.lastAt;
|
|
6827
6033
|
if (elapsed >= intervalMs) {
|
|
@@ -6833,13 +6039,35 @@ function schedulePromptImmediateFlush({
|
|
|
6833
6039
|
if (throttle.timer !== null) return false;
|
|
6834
6040
|
throttle.timer = setTimer(() => {
|
|
6835
6041
|
throttle.timer = null;
|
|
6836
|
-
if (suppressed(isSuppressed)) return;
|
|
6837
6042
|
throttle.lastAt = now();
|
|
6838
6043
|
flush();
|
|
6839
6044
|
}, intervalMs - elapsed);
|
|
6840
6045
|
return true;
|
|
6841
6046
|
}
|
|
6842
6047
|
|
|
6048
|
+
// src/tui/components/prompt-input/escape-policy.mjs
|
|
6049
|
+
var PROMPT_ESCAPE_CLEAR_WINDOW_MS = 800;
|
|
6050
|
+
function classifyPromptEscape({
|
|
6051
|
+
interruptActive = false,
|
|
6052
|
+
hasQueuedMessages = false,
|
|
6053
|
+
hasMessages = false,
|
|
6054
|
+
value = "",
|
|
6055
|
+
lastClearPressAt = 0,
|
|
6056
|
+
now = Date.now()
|
|
6057
|
+
} = {}) {
|
|
6058
|
+
if (interruptActive) return { action: "interrupt", nextClearPressAt: 0 };
|
|
6059
|
+
if (hasQueuedMessages) return { action: "restore-queue", nextClearPressAt: 0 };
|
|
6060
|
+
const current = Number(now);
|
|
6061
|
+
const previous = Number(lastClearPressAt);
|
|
6062
|
+
const repeated = Number.isFinite(current) && Number.isFinite(previous) && previous > 0 && current >= previous && current - previous <= PROMPT_ESCAPE_CLEAR_WINDOW_MS;
|
|
6063
|
+
const armed = Number.isFinite(current) && current > 0 ? current : 1;
|
|
6064
|
+
if (String(value ?? "") === "") {
|
|
6065
|
+
if (!hasMessages) return { action: "idle", nextClearPressAt: 0 };
|
|
6066
|
+
return repeated ? { action: "message-selector", nextClearPressAt: 0 } : { action: "arm-select", nextClearPressAt: armed };
|
|
6067
|
+
}
|
|
6068
|
+
return repeated ? { action: "clear", nextClearPressAt: 0 } : { action: "arm-clear", nextClearPressAt: armed };
|
|
6069
|
+
}
|
|
6070
|
+
|
|
6843
6071
|
// src/tui/components/PromptInput.jsx
|
|
6844
6072
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
6845
6073
|
var IME_LEFT_GUARD_COLUMNS = 0;
|
|
@@ -6862,6 +6090,7 @@ function PromptInput({
|
|
|
6862
6090
|
interruptActive = false,
|
|
6863
6091
|
onInterrupt,
|
|
6864
6092
|
commandPaletteActive = false,
|
|
6093
|
+
commandPaletteOpen = commandPaletteActive,
|
|
6865
6094
|
mask = false,
|
|
6866
6095
|
hint = "",
|
|
6867
6096
|
hintTone = "info",
|
|
@@ -6874,6 +6103,8 @@ function PromptInput({
|
|
|
6874
6103
|
onCommandPaletteCancel,
|
|
6875
6104
|
onCommandPaletteComplete,
|
|
6876
6105
|
onRestoreQueued,
|
|
6106
|
+
hasQueuedMessages = false,
|
|
6107
|
+
hasMessages = false,
|
|
6877
6108
|
onHistoryNavigate,
|
|
6878
6109
|
onPasteText,
|
|
6879
6110
|
selectionRef,
|
|
@@ -6888,10 +6119,10 @@ function PromptInput({
|
|
|
6888
6119
|
});
|
|
6889
6120
|
const [, bumpCursorAnchorEpoch] = useState3(0);
|
|
6890
6121
|
const draftRef = useRef4(draft);
|
|
6891
|
-
const interruptActiveRef = useRef4(interruptActive);
|
|
6892
6122
|
const lastReportedValueRef = useRef4(draft.value);
|
|
6893
6123
|
const pasteGenerationRef = useRef4(0);
|
|
6894
6124
|
const submitGateRef = useRef4(false);
|
|
6125
|
+
const escapeClearAtRef = useRef4(0);
|
|
6895
6126
|
if (valueRef) valueRef.current = draftRef.current.value;
|
|
6896
6127
|
const { isRawModeSupported } = useStdin();
|
|
6897
6128
|
const boxRef = useRef4(null);
|
|
@@ -6903,7 +6134,6 @@ function PromptInput({
|
|
|
6903
6134
|
const undoRef = useRef4({ past: [], future: [], lastPushAt: 0, lastValue: null });
|
|
6904
6135
|
const { value, cursor } = draft;
|
|
6905
6136
|
draftRef.current = draft;
|
|
6906
|
-
interruptActiveRef.current = interruptActive;
|
|
6907
6137
|
if (selectionRef) {
|
|
6908
6138
|
const range = selectionRange(draft);
|
|
6909
6139
|
selectionRef.current = range ? { range, text: mask ? "" : draft.value.slice(range.start, range.end) } : null;
|
|
@@ -6928,7 +6158,6 @@ function PromptInput({
|
|
|
6928
6158
|
const scheduleImmediateFlush = () => {
|
|
6929
6159
|
schedulePromptImmediateFlush({
|
|
6930
6160
|
throttle: flushThrottleRef.current,
|
|
6931
|
-
isSuppressed: () => interruptActiveRef.current,
|
|
6932
6161
|
flush: flushImmediate
|
|
6933
6162
|
});
|
|
6934
6163
|
};
|
|
@@ -6936,6 +6165,7 @@ function PromptInput({
|
|
|
6936
6165
|
cancelPromptImmediateFlush(flushThrottleRef.current);
|
|
6937
6166
|
}, []);
|
|
6938
6167
|
const commitDraft = (next, options = {}) => {
|
|
6168
|
+
escapeClearAtRef.current = 0;
|
|
6939
6169
|
const sameDraft = draftStateEqual(draftRef.current, next);
|
|
6940
6170
|
if (!options.keepPreferredColumn) preferredColumnRef.current = null;
|
|
6941
6171
|
if (sameDraft) {
|
|
@@ -7144,8 +6374,12 @@ function PromptInput({
|
|
|
7144
6374
|
commitDraft(moveCursor(current, moved.cursor, { extend }), { keepPreferredColumn: true });
|
|
7145
6375
|
return true;
|
|
7146
6376
|
};
|
|
7147
|
-
const restoreQueuedToDraft = () => {
|
|
7148
|
-
return onRestoreQueued?.(
|
|
6377
|
+
const restoreQueuedToDraft = ({ showHint = false } = {}) => {
|
|
6378
|
+
return onRestoreQueued?.({
|
|
6379
|
+
restoreDraft: true,
|
|
6380
|
+
showHint,
|
|
6381
|
+
currentText: draftRef.current.value
|
|
6382
|
+
}) === true;
|
|
7149
6383
|
};
|
|
7150
6384
|
const applyHistoryNavigation = (direction, meta = {}) => {
|
|
7151
6385
|
const nextValue = onHistoryNavigate?.(direction, draftRef.current.value, meta);
|
|
@@ -7237,6 +6471,7 @@ function PromptInput({
|
|
|
7237
6471
|
if (disabled) return;
|
|
7238
6472
|
const rawInput = String(input ?? "");
|
|
7239
6473
|
const inputKey = rawInput.toLowerCase();
|
|
6474
|
+
if (!key.escape) escapeClearAtRef.current = 0;
|
|
7240
6475
|
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
6476
|
const gridSelectionActive = typeof suppressShiftNavRef === "function" ? suppressShiftNavRef() : typeof suppressShiftNavRef?.current === "function" ? suppressShiftNavRef.current() : Boolean(suppressShiftNavRef?.current);
|
|
7242
6477
|
if (gridSelectionActive) {
|
|
@@ -7264,13 +6499,13 @@ function PromptInput({
|
|
|
7264
6499
|
const lineBreakIndex = rawInput.search(/[\r\n]/);
|
|
7265
6500
|
const rawEnter = rawInput === "\r" || rawInput === "\n" || rawInput === "\r\n";
|
|
7266
6501
|
const trailingEnterPrefix = singleTrailingLineBreakPrefix(rawInput);
|
|
7267
|
-
const
|
|
7268
|
-
const modifiedLineBreak = key.shift || key.meta || key.ctrl ||
|
|
6502
|
+
const rawModifiedEnter = isModifiedEnterSequence(rawInput);
|
|
6503
|
+
const modifiedLineBreak = key.shift || key.meta || key.ctrl || rawModifiedEnter;
|
|
7269
6504
|
if (rawInput === "\n" && !key.return || key.ctrl && inputKey === "j") {
|
|
7270
6505
|
updateDraft((d) => replaceSelection(d, "\n"));
|
|
7271
6506
|
return;
|
|
7272
6507
|
}
|
|
7273
|
-
if (!
|
|
6508
|
+
if (!rawModifiedEnter && isAnyModifiedEnterSequence(rawInput)) {
|
|
7274
6509
|
return;
|
|
7275
6510
|
}
|
|
7276
6511
|
const pasteFallback = lineBreakIndex !== -1 && trailingEnterPrefix === null && !rawEnter && (rawInput.length > 1 || !key.return);
|
|
@@ -7287,7 +6522,7 @@ function PromptInput({
|
|
|
7287
6522
|
submitEnterChunk(trailingEnterPrefix);
|
|
7288
6523
|
return;
|
|
7289
6524
|
}
|
|
7290
|
-
if (
|
|
6525
|
+
if (rawModifiedEnter) {
|
|
7291
6526
|
updateDraft((d) => replaceSelection(d, "\n"));
|
|
7292
6527
|
return;
|
|
7293
6528
|
}
|
|
@@ -7340,12 +6575,10 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7340
6575
|
if (!moveDraftVertically(-1, { extend: true })) {
|
|
7341
6576
|
updateDraft((d) => moveCursor(d, 0, { extend: true }));
|
|
7342
6577
|
}
|
|
7343
|
-
} else {
|
|
7344
|
-
const
|
|
7345
|
-
if (!
|
|
7346
|
-
|
|
7347
|
-
} else if (!moveDraftVertically(-1, { extend: false })) {
|
|
7348
|
-
applyHistoryNavigation("up", { emptyDraft: false });
|
|
6578
|
+
} else if (!moveDraftVertically(-1, { extend: false })) {
|
|
6579
|
+
const emptyDraft = String(draftRef.current.value || "").length === 0;
|
|
6580
|
+
if (!hasQueuedMessages || !restoreQueuedToDraft()) {
|
|
6581
|
+
applyHistoryNavigation("up", { emptyDraft });
|
|
7349
6582
|
}
|
|
7350
6583
|
}
|
|
7351
6584
|
}
|
|
@@ -7360,7 +6593,7 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7360
6593
|
updateDraft((d) => moveCursor(d, d.value.length, { extend: true }));
|
|
7361
6594
|
}
|
|
7362
6595
|
} else if (!moveDraftVertically(1, { extend: false })) {
|
|
7363
|
-
applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").
|
|
6596
|
+
applyHistoryNavigation("down", { emptyDraft: String(draftRef.current.value || "").length === 0 });
|
|
7364
6597
|
}
|
|
7365
6598
|
}
|
|
7366
6599
|
return;
|
|
@@ -7392,9 +6625,8 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7392
6625
|
if (onTab?.(draftRef.current.value) === true) return;
|
|
7393
6626
|
}
|
|
7394
6627
|
if (key.escape) {
|
|
7395
|
-
if (
|
|
6628
|
+
if (commandPaletteOpen) {
|
|
7396
6629
|
onCommandPaletteCancel?.(draftRef.current.value);
|
|
7397
|
-
commitDraft({ value: "", cursor: 0, selectionAnchor: null });
|
|
7398
6630
|
return;
|
|
7399
6631
|
}
|
|
7400
6632
|
if (selectionRange(draftRef.current)) {
|
|
@@ -7405,19 +6637,48 @@ ${d.value.slice(d.cursor)}`,
|
|
|
7405
6637
|
if (onEscape?.(currentValue, { phase: "before" }) === true) {
|
|
7406
6638
|
return;
|
|
7407
6639
|
}
|
|
7408
|
-
|
|
6640
|
+
let escape = classifyPromptEscape({
|
|
6641
|
+
interruptActive,
|
|
6642
|
+
hasQueuedMessages,
|
|
6643
|
+
hasMessages,
|
|
6644
|
+
value: currentValue,
|
|
6645
|
+
lastClearPressAt: escapeClearAtRef.current
|
|
6646
|
+
});
|
|
6647
|
+
if (escape.action === "restore-queue") {
|
|
6648
|
+
if (restoreQueuedToDraft()) {
|
|
6649
|
+
escapeClearAtRef.current = 0;
|
|
6650
|
+
return;
|
|
6651
|
+
}
|
|
6652
|
+
escape = classifyPromptEscape({
|
|
6653
|
+
interruptActive,
|
|
6654
|
+
hasMessages,
|
|
6655
|
+
value: currentValue,
|
|
6656
|
+
lastClearPressAt: escapeClearAtRef.current
|
|
6657
|
+
});
|
|
6658
|
+
}
|
|
6659
|
+
escapeClearAtRef.current = escape.nextClearPressAt;
|
|
6660
|
+
if (escape.action === "interrupt") {
|
|
6661
|
+
const restoredText = onInterrupt?.(currentValue);
|
|
6662
|
+
if (!currentValue && typeof restoredText === "string") {
|
|
6663
|
+
commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
|
|
6664
|
+
}
|
|
6665
|
+
return;
|
|
6666
|
+
}
|
|
6667
|
+
if (escape.action === "arm-clear") {
|
|
6668
|
+
onEscape?.(currentValue, { phase: "clear-arm" });
|
|
6669
|
+
return;
|
|
6670
|
+
}
|
|
6671
|
+
if (escape.action === "clear") {
|
|
7409
6672
|
onEscape?.(currentValue, { phase: "clear" });
|
|
7410
6673
|
commitDraft({ value: "", cursor: 0, selectionAnchor: null });
|
|
7411
6674
|
return;
|
|
7412
6675
|
}
|
|
7413
|
-
if (
|
|
7414
|
-
|
|
7415
|
-
if (typeof restoredText === "string") {
|
|
7416
|
-
commitDraft({ value: restoredText, cursor: restoredText.length, selectionAnchor: null });
|
|
7417
|
-
}
|
|
6676
|
+
if (escape.action === "arm-select") {
|
|
6677
|
+
onEscape?.("", { phase: "select-arm" });
|
|
7418
6678
|
return;
|
|
7419
6679
|
}
|
|
7420
|
-
if (
|
|
6680
|
+
if (escape.action === "message-selector") {
|
|
6681
|
+
onEscape?.("", { phase: "select" });
|
|
7421
6682
|
return;
|
|
7422
6683
|
}
|
|
7423
6684
|
onEscape?.("", { phase: "empty" });
|
|
@@ -8254,11 +7515,11 @@ function ContextUsageView({ detail, columns }) {
|
|
|
8254
7515
|
const usage = detail?.usage || {};
|
|
8255
7516
|
const compaction = detail?.compaction || {};
|
|
8256
7517
|
const messages = detail?.messages || {};
|
|
8257
|
-
const
|
|
7518
|
+
const request = detail?.request || {};
|
|
8258
7519
|
const lastApi = detail?.lastApi || {};
|
|
8259
7520
|
const cache = detail?.cache || {};
|
|
8260
7521
|
const semantic = messages.semantic || {};
|
|
8261
|
-
const schema =
|
|
7522
|
+
const schema = request.toolSchemaBreakdown || {};
|
|
8262
7523
|
const usedTokens = finiteNumber(usage.usedTokens);
|
|
8263
7524
|
const windowTokens = finiteNumber(usage.windowTokens);
|
|
8264
7525
|
const freeTokens = windowTokens ? Math.max(0, windowTokens - usedTokens) : finiteNumber(usage.freeTokens);
|
|
@@ -8839,15 +8100,15 @@ function singleTrailingLineBreakPrefix2(text) {
|
|
|
8839
8100
|
const prefix = normalized.slice(0, -1);
|
|
8840
8101
|
return prefix.includes("\n") ? null : prefix;
|
|
8841
8102
|
}
|
|
8842
|
-
var
|
|
8103
|
+
var MODIFIED_ENTER_SHIFT_OR_CTRL = 1 | 4;
|
|
8843
8104
|
function isModifiedEnterSequence2(input) {
|
|
8844
8105
|
const text = String(input ?? "");
|
|
8845
8106
|
const body = text.startsWith("\x1B[") ? text.slice(2) : text.startsWith("[") ? text.slice(1) : "";
|
|
8846
8107
|
if (!body) return false;
|
|
8847
8108
|
const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
|
|
8848
|
-
if (kitty) return (Number(kitty[1]) - 1 &
|
|
8109
|
+
if (kitty) return (Number(kitty[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0;
|
|
8849
8110
|
const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
|
|
8850
|
-
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 &
|
|
8111
|
+
return Boolean(modifyOtherKeys && (Number(modifyOtherKeys[1]) - 1 & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0);
|
|
8851
8112
|
}
|
|
8852
8113
|
function isAnyModifiedEnterSequence2(input) {
|
|
8853
8114
|
const text = String(input ?? "");
|
|
@@ -9211,169 +8472,17 @@ function TextEntryPanel({
|
|
|
9211
8472
|
// src/tui/App.jsx
|
|
9212
8473
|
init_paste_attachments();
|
|
9213
8474
|
|
|
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
8475
|
// src/standalone/folder-dialog.mjs
|
|
9367
8476
|
import { spawn } from "node:child_process";
|
|
9368
8477
|
import path from "node:path";
|
|
9369
8478
|
var DIALOG_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
9370
8479
|
function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
|
|
9371
|
-
return new Promise((
|
|
8480
|
+
return new Promise((resolve4) => {
|
|
9372
8481
|
let child;
|
|
9373
8482
|
try {
|
|
9374
8483
|
child = spawn(cmd, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
9375
8484
|
} catch (error) {
|
|
9376
|
-
|
|
8485
|
+
resolve4({ ok: false, code: -1, stdout: "", error });
|
|
9377
8486
|
return;
|
|
9378
8487
|
}
|
|
9379
8488
|
let stdout2 = "";
|
|
@@ -9385,7 +8494,7 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
|
|
|
9385
8494
|
child.kill();
|
|
9386
8495
|
} catch {
|
|
9387
8496
|
}
|
|
9388
|
-
|
|
8497
|
+
resolve4({ ok: false, code: -1, stdout: "", error: new Error("dialog timed out") });
|
|
9389
8498
|
}, timeoutMs);
|
|
9390
8499
|
timer2.unref?.();
|
|
9391
8500
|
child.stdout.on("data", (chunk) => {
|
|
@@ -9395,13 +8504,13 @@ function runCapture(cmd, args, { timeoutMs = DIALOG_TIMEOUT_MS } = {}) {
|
|
|
9395
8504
|
if (settled) return;
|
|
9396
8505
|
settled = true;
|
|
9397
8506
|
clearTimeout(timer2);
|
|
9398
|
-
|
|
8507
|
+
resolve4({ ok: false, code: -1, stdout: "", error });
|
|
9399
8508
|
});
|
|
9400
8509
|
child.on("close", (code) => {
|
|
9401
8510
|
if (settled) return;
|
|
9402
8511
|
settled = true;
|
|
9403
8512
|
clearTimeout(timer2);
|
|
9404
|
-
|
|
8513
|
+
resolve4({ ok: code === 0, code, stdout: stdout2 });
|
|
9405
8514
|
});
|
|
9406
8515
|
});
|
|
9407
8516
|
}
|
|
@@ -9409,10 +8518,10 @@ function spawnFailed(result) {
|
|
|
9409
8518
|
return !!result && result.code === -1;
|
|
9410
8519
|
}
|
|
9411
8520
|
function commandExists(cmd) {
|
|
9412
|
-
return new Promise((
|
|
8521
|
+
return new Promise((resolve4) => {
|
|
9413
8522
|
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) =>
|
|
8523
|
+
probe.on("error", () => resolve4(false));
|
|
8524
|
+
probe.on("close", (code) => resolve4(code === 0));
|
|
9416
8525
|
});
|
|
9417
8526
|
}
|
|
9418
8527
|
function psSingleQuoted(value) {
|
|
@@ -9615,9 +8724,9 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
|
|
|
9615
8724
|
powershellScript(title, resolvedInitial)
|
|
9616
8725
|
]);
|
|
9617
8726
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9618
|
-
const
|
|
9619
|
-
if (!result.ok && !
|
|
9620
|
-
return { available: true, path:
|
|
8727
|
+
const path2 = String(result.stdout || "").trim();
|
|
8728
|
+
if (!result.ok && !path2) return { available: false, path: null };
|
|
8729
|
+
return { available: true, path: path2 || null };
|
|
9621
8730
|
}
|
|
9622
8731
|
if (platform2 === "darwin") {
|
|
9623
8732
|
const safeTitle = String(title).replace(/"/g, '\\"');
|
|
@@ -9625,8 +8734,8 @@ async function pickFolder({ title = "Select a project folder", initialPath = ""
|
|
|
9625
8734
|
POSIX path of f`;
|
|
9626
8735
|
const result = await runCapture("osascript", ["-e", script]);
|
|
9627
8736
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9628
|
-
const
|
|
9629
|
-
return { available: true, path:
|
|
8737
|
+
const path2 = String(result.stdout || "").trim();
|
|
8738
|
+
return { available: true, path: path2 || null };
|
|
9630
8739
|
}
|
|
9631
8740
|
if (await commandExists("zenity")) {
|
|
9632
8741
|
const result = await runCapture("zenity", [
|
|
@@ -9635,16 +8744,16 @@ POSIX path of f`;
|
|
|
9635
8744
|
`--title=${title}`
|
|
9636
8745
|
]);
|
|
9637
8746
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9638
|
-
const
|
|
9639
|
-
if (!result.ok && !
|
|
9640
|
-
return { available: true, path:
|
|
8747
|
+
const path2 = String(result.stdout || "").trim();
|
|
8748
|
+
if (!result.ok && !path2) return { available: true, path: null };
|
|
8749
|
+
return { available: true, path: path2 || null };
|
|
9641
8750
|
}
|
|
9642
8751
|
if (await commandExists("kdialog")) {
|
|
9643
8752
|
const result = await runCapture("kdialog", ["--getexistingdirectory", "."]);
|
|
9644
8753
|
if (spawnFailed(result)) return { available: false, path: null };
|
|
9645
|
-
const
|
|
9646
|
-
if (!result.ok && !
|
|
9647
|
-
return { available: true, path:
|
|
8754
|
+
const path2 = String(result.stdout || "").trim();
|
|
8755
|
+
if (!result.ok && !path2) return { available: true, path: null };
|
|
8756
|
+
return { available: true, path: path2 || null };
|
|
9648
8757
|
}
|
|
9649
8758
|
return { available: false, path: null };
|
|
9650
8759
|
}
|
|
@@ -9967,13 +9076,13 @@ function nativeClipboardCommand(text) {
|
|
|
9967
9076
|
function copyToClipboard(text) {
|
|
9968
9077
|
const value = String(text ?? "");
|
|
9969
9078
|
const wroteOsc52 = writeOsc52Clipboard(value);
|
|
9970
|
-
return new Promise((
|
|
9079
|
+
return new Promise((resolve4, reject) => {
|
|
9971
9080
|
const { cmd, args, input } = nativeClipboardCommand(value);
|
|
9972
9081
|
let child;
|
|
9973
9082
|
try {
|
|
9974
9083
|
child = spawn2(cmd, args, { stdio: ["pipe", "ignore", "ignore"], windowsHide: true });
|
|
9975
9084
|
} catch (e) {
|
|
9976
|
-
if (wroteOsc52)
|
|
9085
|
+
if (wroteOsc52) resolve4();
|
|
9977
9086
|
else reject(e);
|
|
9978
9087
|
return;
|
|
9979
9088
|
}
|
|
@@ -9982,7 +9091,7 @@ function copyToClipboard(text) {
|
|
|
9982
9091
|
});
|
|
9983
9092
|
if (!wroteOsc52) {
|
|
9984
9093
|
child.on("close", (code) => {
|
|
9985
|
-
if (code === 0)
|
|
9094
|
+
if (code === 0) resolve4();
|
|
9986
9095
|
else reject(new Error(`${cmd} exited with code ${code}`));
|
|
9987
9096
|
});
|
|
9988
9097
|
}
|
|
@@ -9991,7 +9100,7 @@ function copyToClipboard(text) {
|
|
|
9991
9100
|
child.stdin.end(input);
|
|
9992
9101
|
if (wroteOsc52) {
|
|
9993
9102
|
child.unref?.();
|
|
9994
|
-
|
|
9103
|
+
resolve4();
|
|
9995
9104
|
}
|
|
9996
9105
|
});
|
|
9997
9106
|
}
|
|
@@ -10168,7 +9277,7 @@ var DIFF_STAT_SUMMARY_RE = /^\s*\d+\s+files?\s+changed\b/;
|
|
|
10168
9277
|
function colorizeDiffStatLine(line, c) {
|
|
10169
9278
|
const m = DIFF_STAT_FILE_RE.exec(line);
|
|
10170
9279
|
if (m) {
|
|
10171
|
-
const [, indent,
|
|
9280
|
+
const [, indent, path2, sep, tail] = m;
|
|
10172
9281
|
const countMatch = /^(\d+)(\s*)([+\-]*)\s*$/.exec(tail);
|
|
10173
9282
|
let coloredTail;
|
|
10174
9283
|
if (countMatch) {
|
|
@@ -10179,7 +9288,7 @@ function colorizeDiffStatLine(line, c) {
|
|
|
10179
9288
|
} else {
|
|
10180
9289
|
coloredTail = c.diffContext(tail);
|
|
10181
9290
|
}
|
|
10182
|
-
return `${indent}${c.diffContext(
|
|
9291
|
+
return `${indent}${c.diffContext(path2)}${c.diffContext(sep)}${coloredTail}`;
|
|
10183
9292
|
}
|
|
10184
9293
|
return c.diffContext(line);
|
|
10185
9294
|
}
|
|
@@ -12447,6 +11556,16 @@ var TRANSCRIPT_WINDOW_TAIL_MAX_ITEMS = positiveIntEnv("MIXDOG_TUI_TRANSCRIPT_WIN
|
|
|
12447
11556
|
var SELECTION_PAINT_INTERVAL_MS = positiveIntEnv("MIXDOG_TUI_SELECTION_PAINT_MS", 24);
|
|
12448
11557
|
var SCROLL_COALESCE_MS = positiveIntEnv("MIXDOG_TUI_SCROLL_COALESCE_MS", 16);
|
|
12449
11558
|
var PROMPT_HISTORY_LIMIT = 50;
|
|
11559
|
+
function accumulateDirectionalScrollDelta(state, deltaRows) {
|
|
11560
|
+
const delta = Number(deltaRows) || 0;
|
|
11561
|
+
if (!delta) return false;
|
|
11562
|
+
const direction = Math.sign(delta);
|
|
11563
|
+
const reversed = Boolean(state.direction && state.direction !== direction);
|
|
11564
|
+
if (reversed) state.pendingRows = 0;
|
|
11565
|
+
state.direction = direction;
|
|
11566
|
+
state.pendingRows += delta;
|
|
11567
|
+
return reversed;
|
|
11568
|
+
}
|
|
12450
11569
|
function boolEnvDefaultTrue(name) {
|
|
12451
11570
|
const raw = process.env[name];
|
|
12452
11571
|
if (raw == null) return true;
|
|
@@ -12784,6 +11903,17 @@ function transcriptRowAt(values, index) {
|
|
|
12784
11903
|
if (!values || index < 0 || index >= values.length) return 0;
|
|
12785
11904
|
return typeof values.atIndex === "function" ? values.atIndex(index) : Number(values[index]) || 0;
|
|
12786
11905
|
}
|
|
11906
|
+
function transcriptSwapReturnsToTail({
|
|
11907
|
+
count = 0,
|
|
11908
|
+
previousCount = 0,
|
|
11909
|
+
firstId = null,
|
|
11910
|
+
previousFirstId = null
|
|
11911
|
+
} = {}) {
|
|
11912
|
+
if (previousCount === 0) return true;
|
|
11913
|
+
if (count < previousCount) return true;
|
|
11914
|
+
if (count === 0) return false;
|
|
11915
|
+
return firstId != null && previousFirstId != null && firstId !== previousFirstId;
|
|
11916
|
+
}
|
|
12787
11917
|
function appendTranscriptRow(prefix, tailValue) {
|
|
12788
11918
|
return Object.freeze({
|
|
12789
11919
|
length: prefix.length + 1,
|
|
@@ -13397,7 +12527,7 @@ var notifyRenderFrame = (seq = ++renderAckSeq) => {
|
|
|
13397
12527
|
pendingRenderAcks = [];
|
|
13398
12528
|
for (const ack of acks) ack(seq);
|
|
13399
12529
|
};
|
|
13400
|
-
var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((
|
|
12530
|
+
var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve4) => {
|
|
13401
12531
|
const minSeq = renderAckSeq;
|
|
13402
12532
|
let remainingFrames = Math.max(1, Math.floor(Number(frames) || 1));
|
|
13403
12533
|
let settled = false;
|
|
@@ -13409,7 +12539,7 @@ var yieldToRenderer = ({ frames = 1 } = {}) => new Promise((resolve5) => {
|
|
|
13409
12539
|
if (timer2) clearTimeout(timer2);
|
|
13410
12540
|
const idx = pendingRenderAcks.indexOf(onFrame);
|
|
13411
12541
|
if (idx !== -1) pendingRenderAcks.splice(idx, 1);
|
|
13412
|
-
|
|
12542
|
+
resolve4();
|
|
13413
12543
|
};
|
|
13414
12544
|
const onTimeout = () => {
|
|
13415
12545
|
if (settled) return;
|
|
@@ -13470,7 +12600,7 @@ function useTranscriptScroll({
|
|
|
13470
12600
|
const scrollAnimationRef = useRef8(null);
|
|
13471
12601
|
const pendingReadbackRowsRef = useRef8(0);
|
|
13472
12602
|
const selectionPaintRef = useRef8({ t: 0, rect: null, pending: null, timer: null });
|
|
13473
|
-
const scrollCoalesceRef = useRef8({ pendingRows: 0, timer: null });
|
|
12603
|
+
const scrollCoalesceRef = useRef8({ pendingRows: 0, direction: 0, timer: null });
|
|
13474
12604
|
const selectionTextCaptureRef = useRef8(0);
|
|
13475
12605
|
const stitchBufferRef = useRef8(/* @__PURE__ */ new Map());
|
|
13476
12606
|
const stitchHarvestTimerRef = useRef8(null);
|
|
@@ -13761,6 +12891,7 @@ function useTranscriptScroll({
|
|
|
13761
12891
|
if (coalesceState.timer) clearTimeout(coalesceState.timer);
|
|
13762
12892
|
coalesceState.timer = null;
|
|
13763
12893
|
coalesceState.pendingRows = 0;
|
|
12894
|
+
coalesceState.direction = 0;
|
|
13764
12895
|
}, []);
|
|
13765
12896
|
const scrollTranscriptRows = useCallback3((deltaRows, options = {}) => {
|
|
13766
12897
|
const maxTarget = Math.max(0, Number(maxScrollRowsRef.current) || 0);
|
|
@@ -13893,13 +13024,18 @@ function useTranscriptScroll({
|
|
|
13893
13024
|
});
|
|
13894
13025
|
const queueScrollCoalesced = useCallback3((deltaRows) => {
|
|
13895
13026
|
const state = scrollCoalesceRef.current;
|
|
13896
|
-
state
|
|
13027
|
+
const reversed = accumulateDirectionalScrollDelta(state, deltaRows);
|
|
13028
|
+
if (reversed && state.timer) {
|
|
13029
|
+
clearTimeout(state.timer);
|
|
13030
|
+
state.timer = null;
|
|
13031
|
+
}
|
|
13897
13032
|
if (state.timer) return;
|
|
13898
13033
|
const rows = state.pendingRows;
|
|
13899
13034
|
state.pendingRows = 0;
|
|
13900
13035
|
scrollTranscriptRows(rows);
|
|
13901
13036
|
state.timer = setTimeout(() => {
|
|
13902
13037
|
state.timer = null;
|
|
13038
|
+
state.direction = 0;
|
|
13903
13039
|
if (state.pendingRows !== 0) {
|
|
13904
13040
|
const remaining = state.pendingRows;
|
|
13905
13041
|
state.pendingRows = 0;
|
|
@@ -14121,17 +13257,18 @@ function usePromptHint() {
|
|
|
14121
13257
|
const [promptHintTone, setPromptHintTone] = useState8("info");
|
|
14122
13258
|
const promptHintTimerRef = useRef10(null);
|
|
14123
13259
|
const promptHintActiveRef = useRef10(false);
|
|
14124
|
-
const showPromptHint = useCallback5((text, tone = "info") => {
|
|
13260
|
+
const showPromptHint = useCallback5((text, tone = "info", dismissMs = PROMPT_HINT_DISMISS_MS) => {
|
|
14125
13261
|
if (promptHintTimerRef.current) clearTimeout(promptHintTimerRef.current);
|
|
14126
13262
|
promptHintActiveRef.current = true;
|
|
14127
13263
|
setPromptHint(String(text || ""));
|
|
14128
13264
|
setPromptHintTone(tone);
|
|
13265
|
+
const timeoutMs = Number(dismissMs) > 0 ? Number(dismissMs) : PROMPT_HINT_DISMISS_MS;
|
|
14129
13266
|
promptHintTimerRef.current = setTimeout(() => {
|
|
14130
13267
|
promptHintTimerRef.current = null;
|
|
14131
13268
|
promptHintActiveRef.current = false;
|
|
14132
13269
|
setPromptHint("");
|
|
14133
13270
|
setPromptHintTone("info");
|
|
14134
|
-
},
|
|
13271
|
+
}, timeoutMs);
|
|
14135
13272
|
}, []);
|
|
14136
13273
|
const showSelectionCopyHint = useCallback5(
|
|
14137
13274
|
(text, tone = "plain") => showPromptHint(text, tone),
|
|
@@ -14204,11 +13341,11 @@ function compactJson(value, max = 180) {
|
|
|
14204
13341
|
if (!text) return "";
|
|
14205
13342
|
return stringWidth8(text) > max ? `${text.slice(0, Math.max(1, max - 1))}\u2026` : text;
|
|
14206
13343
|
}
|
|
14207
|
-
function toolApprovalDescription(
|
|
14208
|
-
const surface = formatToolSurface(
|
|
14209
|
-
const summary = surface?.summary ? `${surface.label ||
|
|
14210
|
-
const reason = clean(
|
|
14211
|
-
const args = compactJson(
|
|
13344
|
+
function toolApprovalDescription(request = {}) {
|
|
13345
|
+
const surface = formatToolSurface(request.name, request.args);
|
|
13346
|
+
const summary = surface?.summary ? `${surface.label || request.name} (${surface.summary})` : surface?.label || request.name || "tool";
|
|
13347
|
+
const reason = clean(request.reason) || "Hook requested approval.";
|
|
13348
|
+
const args = compactJson(request.args);
|
|
14212
13349
|
return [
|
|
14213
13350
|
summary ? `Tool: ${summary}` : "",
|
|
14214
13351
|
reason ? `Reason: ${reason}` : "",
|
|
@@ -14515,8 +13652,8 @@ function createUsageContextPanels({
|
|
|
14515
13652
|
const context = contextStatusValue || {};
|
|
14516
13653
|
const usage = context.usage || {};
|
|
14517
13654
|
const messages = context.messages || {};
|
|
14518
|
-
const
|
|
14519
|
-
const schemaBreakdown =
|
|
13655
|
+
const request = context.request || {};
|
|
13656
|
+
const schemaBreakdown = request.toolSchemaBreakdown || {};
|
|
14520
13657
|
const schemaTokensFor = (buckets) => buckets.reduce(
|
|
14521
13658
|
(sum, bucket) => sum + Number(schemaBreakdown?.[bucket]?.tokens || 0),
|
|
14522
13659
|
0
|
|
@@ -14602,7 +13739,7 @@ function createUsageContextPanels({
|
|
|
14602
13739
|
{
|
|
14603
13740
|
value: "request",
|
|
14604
13741
|
label: "Request overhead",
|
|
14605
|
-
description: `${fmt(
|
|
13742
|
+
description: `${fmt(request.requestOverheadTokens)} framing \xB7 ${fmt(request.reserveTokens)} reserve incl. tools`,
|
|
14606
13743
|
_action: "request"
|
|
14607
13744
|
},
|
|
14608
13745
|
{
|
|
@@ -14678,9 +13815,9 @@ function createUsageContextPanels({
|
|
|
14678
13815
|
results: messages.toolResultCount
|
|
14679
13816
|
},
|
|
14680
13817
|
request: {
|
|
14681
|
-
toolSchemaBreakdown:
|
|
14682
|
-
overheadTokens:
|
|
14683
|
-
reserveTokens:
|
|
13818
|
+
toolSchemaBreakdown: request.toolSchemaBreakdown,
|
|
13819
|
+
overheadTokens: request.requestOverheadTokens,
|
|
13820
|
+
reserveTokens: request.reserveTokens
|
|
14684
13821
|
},
|
|
14685
13822
|
lastApi: {
|
|
14686
13823
|
contextTokens: usage.lastContextTokens,
|
|
@@ -14746,6 +13883,13 @@ function createPromptSubmit({
|
|
|
14746
13883
|
pastedImagesRef,
|
|
14747
13884
|
pastedTextsRef
|
|
14748
13885
|
}) {
|
|
13886
|
+
const backendCall = (name, ...args) => {
|
|
13887
|
+
const target = store?.[name];
|
|
13888
|
+
if (typeof target !== "function") {
|
|
13889
|
+
return Promise.reject(new TypeError(`project backend method ${name} is unavailable`));
|
|
13890
|
+
}
|
|
13891
|
+
return Promise.resolve(target.apply(store, args));
|
|
13892
|
+
};
|
|
14749
13893
|
const submitPrompt = (prompt, options) => {
|
|
14750
13894
|
if (typeof store.submitAsync !== "function") return store.submit(prompt, options);
|
|
14751
13895
|
void Promise.resolve(store.submitAsync(prompt, options)).then((accepted) => {
|
|
@@ -14919,9 +14063,14 @@ function createPromptSubmit({
|
|
|
14919
14063
|
store.pushNotice("working directory path is required", "warn");
|
|
14920
14064
|
return false;
|
|
14921
14065
|
}
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14066
|
+
void backendCall("setCwd", commandText, {
|
|
14067
|
+
message: `Project set: ${projectNameFromPath(commandText)}`
|
|
14068
|
+
}).then(() => {
|
|
14069
|
+
setSettingsPrompt(null);
|
|
14070
|
+
void openSettingsPicker();
|
|
14071
|
+
}).catch((error) => {
|
|
14072
|
+
store.pushNotice(`project switch failed: ${error?.message || error}`, "error");
|
|
14073
|
+
});
|
|
14925
14074
|
return true;
|
|
14926
14075
|
}
|
|
14927
14076
|
if (settingsPrompt.kind === "project-new") {
|
|
@@ -14929,21 +14078,25 @@ function createPromptSubmit({
|
|
|
14929
14078
|
store.pushNotice("project path is required", "warn");
|
|
14930
14079
|
return false;
|
|
14931
14080
|
}
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14081
|
+
void backendCall("inspectProjectPath", commandText).then((result) => {
|
|
14082
|
+
const path2 = String(result?.path || commandText);
|
|
14083
|
+
if (result?.directory === true) {
|
|
14084
|
+
setSettingsPrompt(null);
|
|
14085
|
+
void registerProject(path2);
|
|
14086
|
+
return;
|
|
14087
|
+
}
|
|
14088
|
+
if (result?.exists === true) {
|
|
14089
|
+
store.pushNotice(`${path2} is not a directory`, "warn");
|
|
14090
|
+
return;
|
|
14091
|
+
}
|
|
14092
|
+
setSettingsPrompt({
|
|
14093
|
+
kind: "project-create-confirm",
|
|
14094
|
+
label: "New project \xB7 Create folder?",
|
|
14095
|
+
hint: `${path2} does not exist. Type "y" to create it, or anything else to cancel.`,
|
|
14096
|
+
pendingPath: path2
|
|
14097
|
+
});
|
|
14098
|
+
}).catch((error) => {
|
|
14099
|
+
store.pushNotice(`project path check failed: ${error?.message || error}`, "error");
|
|
14947
14100
|
});
|
|
14948
14101
|
return true;
|
|
14949
14102
|
}
|
|
@@ -14951,14 +14104,13 @@ function createPromptSubmit({
|
|
|
14951
14104
|
const pendingPath = String(settingsPrompt.pendingPath || "");
|
|
14952
14105
|
const answer = String(commandText || "").trim().toLowerCase();
|
|
14953
14106
|
if (answer === "y" || answer === "yes") {
|
|
14954
|
-
|
|
14955
|
-
if (!created) {
|
|
14956
|
-
store.pushNotice(`could not create folder: ${pendingPath}`, "error");
|
|
14107
|
+
void backendCall("ensureProjectDirectory", pendingPath).then((created) => {
|
|
14957
14108
|
setSettingsPrompt(null);
|
|
14958
|
-
|
|
14959
|
-
}
|
|
14960
|
-
|
|
14961
|
-
|
|
14109
|
+
void registerProject(created || pendingPath);
|
|
14110
|
+
}).catch((error) => {
|
|
14111
|
+
store.pushNotice(`could not create folder: ${error?.message || error}`, "error");
|
|
14112
|
+
setSettingsPrompt(null);
|
|
14113
|
+
});
|
|
14962
14114
|
return true;
|
|
14963
14115
|
}
|
|
14964
14116
|
setSettingsPrompt(null);
|
|
@@ -14967,16 +14119,15 @@ function createPromptSubmit({
|
|
|
14967
14119
|
}
|
|
14968
14120
|
if (settingsPrompt.kind === "project-rename") {
|
|
14969
14121
|
const targetPath = String(settingsPrompt.projectPath || "");
|
|
14970
|
-
|
|
14971
|
-
const updated = renameProject(targetPath, commandText);
|
|
14122
|
+
void backendCall("renameProject", targetPath, commandText).then((updated) => {
|
|
14972
14123
|
if (updated) {
|
|
14973
14124
|
store.pushNotice(`project renamed to "${updated.name}"`, "info");
|
|
14974
14125
|
}
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
}
|
|
14978
|
-
|
|
14979
|
-
|
|
14126
|
+
setSettingsPrompt(null);
|
|
14127
|
+
void openProjectPicker();
|
|
14128
|
+
}).catch((error) => {
|
|
14129
|
+
store.pushNotice(`rename failed: ${error?.message || error}`, "error");
|
|
14130
|
+
});
|
|
14980
14131
|
return true;
|
|
14981
14132
|
}
|
|
14982
14133
|
if (settingsPrompt.kind === "system-shell") {
|
|
@@ -16960,7 +16111,7 @@ var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpa
|
|
|
16960
16111
|
// src/tui/app/app-view.jsx
|
|
16961
16112
|
import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
16962
16113
|
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;
|
|
16114
|
+
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
16115
|
const promptInputControl = /* @__PURE__ */ jsx20(
|
|
16965
16116
|
PromptInput,
|
|
16966
16117
|
{
|
|
@@ -16984,6 +16135,7 @@ function renderAppView(ctx) {
|
|
|
16984
16135
|
onPasteText: handlePromptPaste,
|
|
16985
16136
|
onHistoryNavigate: handlePromptHistoryNavigate,
|
|
16986
16137
|
commandPaletteActive: slashPaletteOpen && slashCommands.length > 0,
|
|
16138
|
+
commandPaletteOpen: slashPaletteOpen,
|
|
16987
16139
|
onCommandPaletteNavigate: (direction) => {
|
|
16988
16140
|
setSlashIndex((index) => {
|
|
16989
16141
|
const total = slashCommands.length;
|
|
@@ -16998,7 +16150,9 @@ function renderAppView(ctx) {
|
|
|
16998
16150
|
onCommandPaletteAccept: acceptSlashPalette,
|
|
16999
16151
|
onCommandPaletteCancel: cancelSlashPalette,
|
|
17000
16152
|
onCommandPaletteComplete: completeSlashPalette,
|
|
17001
|
-
onRestoreQueued:
|
|
16153
|
+
onRestoreQueued: restoreQueuedToPrompt,
|
|
16154
|
+
hasQueuedMessages: Array.isArray(state.queued) && state.queued.length > 0,
|
|
16155
|
+
hasMessages: hasUserMessages
|
|
17002
16156
|
}
|
|
17003
16157
|
);
|
|
17004
16158
|
return (
|
|
@@ -17448,10 +16602,8 @@ function usePromptDraftFlow({
|
|
|
17448
16602
|
return token ? `/${token} ` : void 0;
|
|
17449
16603
|
}, [slashCommands, slashIndex]);
|
|
17450
16604
|
const cancelSlashPalette = useCallback8((value = "") => {
|
|
17451
|
-
setSlashDismissedFor("");
|
|
17452
|
-
|
|
17453
|
-
setPromptDraftOverride({ id: Date.now(), value: "" });
|
|
17454
|
-
}, []);
|
|
16605
|
+
setSlashDismissedFor(String(value ?? ""));
|
|
16606
|
+
}, [setSlashDismissedFor]);
|
|
17455
16607
|
return {
|
|
17456
16608
|
onPromptDraftChange,
|
|
17457
16609
|
cancelProviderPrompt,
|
|
@@ -17694,10 +16846,10 @@ function createResumePicker({
|
|
|
17694
16846
|
formatSessionUpdatedAt: formatSessionUpdatedAt2,
|
|
17695
16847
|
formatSessionMessageCount: formatSessionMessageCount2
|
|
17696
16848
|
}) {
|
|
17697
|
-
const openResumePicker = () => {
|
|
16849
|
+
const openResumePicker = async () => {
|
|
17698
16850
|
let sessions;
|
|
17699
16851
|
try {
|
|
17700
|
-
sessions = store.listSessions({ refreshFromStorage: true });
|
|
16852
|
+
sessions = await store.listSessions({ refreshFromStorage: true });
|
|
17701
16853
|
} catch (e) {
|
|
17702
16854
|
store.pushNotice(`could not list saved chats: ${e?.message || e}`, "error");
|
|
17703
16855
|
return;
|
|
@@ -17762,16 +16914,16 @@ function createCoreMemoryPicker({
|
|
|
17762
16914
|
onCancel: closeMemoryCorePicker
|
|
17763
16915
|
});
|
|
17764
16916
|
void store.memoryControl?.({ action: "core", op: "list", project_id: "*" }, { silent: true }).then(async (result) => {
|
|
17765
|
-
const
|
|
17766
|
-
const
|
|
16917
|
+
const recap = await store.getRecapSettings?.() || { enabled: true };
|
|
16918
|
+
const recapOn = recap.enabled !== false;
|
|
17767
16919
|
const coreRows = parseMemoryCoreRows2(result);
|
|
17768
16920
|
const rows = [
|
|
17769
16921
|
{
|
|
17770
|
-
value: "
|
|
17771
|
-
label: "
|
|
17772
|
-
meta:
|
|
17773
|
-
description:
|
|
17774
|
-
_action: "toggle-
|
|
16922
|
+
value: "recap-toggle",
|
|
16923
|
+
label: "Recap",
|
|
16924
|
+
meta: recapOn ? "On" : "Off",
|
|
16925
|
+
description: recapOn ? "Background session recaps on" : "Background recaps off; core memory remains available",
|
|
16926
|
+
_action: "toggle-recap"
|
|
17775
16927
|
},
|
|
17776
16928
|
{ value: "core-add", label: "Add Memory", description: "store a new curated memory sentence", _action: "add-core" },
|
|
17777
16929
|
{
|
|
@@ -17793,17 +16945,17 @@ function createCoreMemoryPicker({
|
|
|
17793
16945
|
labelWidth: 12,
|
|
17794
16946
|
footer: (item) => item && item._action === "core-entry" ? item._summary || item._element || "" : "",
|
|
17795
16947
|
onSelect: (_value, item) => {
|
|
17796
|
-
if (item?._action === "toggle-
|
|
16948
|
+
if (item?._action === "toggle-recap") toggleRecapEnabled();
|
|
17797
16949
|
else if (item?._action === "add-core") beginAddCoreMemory();
|
|
17798
16950
|
else if (item?._action === "core-list") openCoreMemoryListPicker(item._rows);
|
|
17799
16951
|
else if (item?._line) store.pushNotice(item._line, "info");
|
|
17800
16952
|
},
|
|
17801
16953
|
// Toggle rows also respond to ←/→ (matches settings/channels UX).
|
|
17802
16954
|
onLeft: (item) => {
|
|
17803
|
-
if (item?._action === "toggle-
|
|
16955
|
+
if (item?._action === "toggle-recap") toggleRecapEnabled();
|
|
17804
16956
|
},
|
|
17805
16957
|
onRight: (item) => {
|
|
17806
|
-
if (item?._action === "toggle-
|
|
16958
|
+
if (item?._action === "toggle-recap") toggleRecapEnabled();
|
|
17807
16959
|
},
|
|
17808
16960
|
onCancel: closeMemoryCorePicker
|
|
17809
16961
|
});
|
|
@@ -17844,12 +16996,12 @@ function createCoreMemoryPicker({
|
|
|
17844
16996
|
store.pushNotice(`core memory failed: ${e?.message || e}`, "error");
|
|
17845
16997
|
});
|
|
17846
16998
|
};
|
|
17847
|
-
const
|
|
17848
|
-
const
|
|
17849
|
-
void Promise.resolve(store.
|
|
17850
|
-
if (!next) store.pushNotice("
|
|
17851
|
-
else store.pushNotice(`
|
|
17852
|
-
}).catch((e) => store.pushNotice(`
|
|
16999
|
+
const toggleRecapEnabled = async () => {
|
|
17000
|
+
const recap = await store.getRecapSettings?.() || { enabled: true };
|
|
17001
|
+
void Promise.resolve(store.setRecapEnabled?.(!(recap.enabled !== false))).then((next) => {
|
|
17002
|
+
if (!next) store.pushNotice("recap setting is busy", "warn");
|
|
17003
|
+
else store.pushNotice(`Recap ${next.enabled ? "on" : "off"}`, "info");
|
|
17004
|
+
}).catch((e) => store.pushNotice(`recap toggle failed: ${e?.message || e}`, "error")).finally(() => openMemoryCorePicker());
|
|
17853
17005
|
};
|
|
17854
17006
|
const openCoreEntryActionsPicker = (entryItem) => {
|
|
17855
17007
|
setPicker({
|
|
@@ -19107,7 +18259,6 @@ function createOnboardingSteps({
|
|
|
19107
18259
|
if (defaultRoute) {
|
|
19108
18260
|
void store.completeOnboarding?.({
|
|
19109
18261
|
defaultRoute,
|
|
19110
|
-
defaultProvider: defaultRoute.provider,
|
|
19111
18262
|
...hasOverrides ? { agentRoutes: { ...overrides } } : {},
|
|
19112
18263
|
...searchRoute ? { searchRoute } : {}
|
|
19113
18264
|
}).then(done).catch(failed);
|
|
@@ -19788,7 +18939,7 @@ function createProviderSetupPicker({
|
|
|
19788
18939
|
}
|
|
19789
18940
|
});
|
|
19790
18941
|
try {
|
|
19791
|
-
await new Promise((
|
|
18942
|
+
await new Promise((resolve4) => setTimeout(resolve4, 0));
|
|
19792
18943
|
setup = await store.getProviderSetup();
|
|
19793
18944
|
} catch (e) {
|
|
19794
18945
|
store.pushNotice(`providers failed: ${e?.message || e}`, "error");
|
|
@@ -20327,7 +19478,7 @@ function createModelPicker({
|
|
|
20327
19478
|
items: [],
|
|
20328
19479
|
onCancel: cancelModelPicker
|
|
20329
19480
|
});
|
|
20330
|
-
await new Promise((
|
|
19481
|
+
await new Promise((resolve4) => setTimeout(resolve4, 0));
|
|
20331
19482
|
try {
|
|
20332
19483
|
if (options.refreshModels !== true && options.cacheRef !== "search") {
|
|
20333
19484
|
refreshModelsPromise = Promise.resolve(loadModels({ force: false }));
|
|
@@ -20682,7 +19833,7 @@ function createRoutePickers({
|
|
|
20682
19833
|
const items = agents.map((agent) => ({
|
|
20683
19834
|
value: agent.id,
|
|
20684
19835
|
label: agent.label,
|
|
20685
|
-
metaParts: agentModelParts2(routeOverrides[agent.id] || agent.route),
|
|
19836
|
+
metaParts: routeOverrides[agent.id] || agent.route ? agentModelParts2(routeOverrides[agent.id] || agent.route) : [{ text: "(follows main)", width: 17 }, { text: "", width: 6 }, { text: "", width: 4 }],
|
|
20686
19837
|
description: agent.description || agent.definition?.description || "",
|
|
20687
19838
|
_agent: agent
|
|
20688
19839
|
}));
|
|
@@ -20909,7 +20060,7 @@ function createSettingsPicker({
|
|
|
20909
20060
|
const snapshot = await store.getSettingsSnapshot?.({ heavy: !heavyCache }) || {};
|
|
20910
20061
|
const autoClear = snapshot.autoClear || {};
|
|
20911
20062
|
const compaction = snapshot.compaction || {};
|
|
20912
|
-
const
|
|
20063
|
+
const recap = snapshot.recap || { enabled: true };
|
|
20913
20064
|
const channels = snapshot.channels || { enabled: true };
|
|
20914
20065
|
const systemShell = snapshot.systemShell || { source: "auto", command: "", effective: "" };
|
|
20915
20066
|
const outputStyle = snapshot.outputStyle || {};
|
|
@@ -20929,7 +20080,7 @@ function createSettingsPicker({
|
|
|
20929
20080
|
const outputStyleLabel = outputStyle?.current?.label || outputStyle?.current?.id || outputStyle?.configured || "Default";
|
|
20930
20081
|
const workflowLabel = workflowDisplayName2(workflow);
|
|
20931
20082
|
const boolLabel = (enabled) => enabled ? "On" : "Off";
|
|
20932
|
-
const compactTypeDescription =
|
|
20083
|
+
const compactTypeDescription = "Uses Memory recall to rebuild context faster on large histories.";
|
|
20933
20084
|
const applyAutoClear = (patch = {}) => {
|
|
20934
20085
|
void Promise.resolve(store.setAutoClear?.(patch)).then((next) => {
|
|
20935
20086
|
if (!next) store.pushNotice("autoclear unavailable", "warn");
|
|
@@ -21130,8 +20281,8 @@ function createSettingsPicker({
|
|
|
21130
20281
|
{
|
|
21131
20282
|
value: "memory",
|
|
21132
20283
|
label: "Memory",
|
|
21133
|
-
meta: boolLabel(
|
|
21134
|
-
description:
|
|
20284
|
+
meta: `Recap ${boolLabel(recap.enabled !== false)}`,
|
|
20285
|
+
description: recap.enabled === false ? "Background recap off. Core memories and on-demand recall stay available." : "Background recap and core memories.",
|
|
21135
20286
|
_action: "memory"
|
|
21136
20287
|
},
|
|
21137
20288
|
{
|
|
@@ -21898,6 +21049,13 @@ function createAppPickers({
|
|
|
21898
21049
|
|
|
21899
21050
|
// src/tui/app/use-prompt-queue-history.mjs
|
|
21900
21051
|
import { useCallback as useCallback9, useMemo as useMemo2 } from "react";
|
|
21052
|
+
|
|
21053
|
+
// src/tui/components/prompt-input/restore-policy.mjs
|
|
21054
|
+
function mergeQueuedRestoreText(queuedText = "", currentText = "") {
|
|
21055
|
+
return [String(queuedText ?? ""), String(currentText ?? "")].filter((text) => text.trim()).join("\n");
|
|
21056
|
+
}
|
|
21057
|
+
|
|
21058
|
+
// src/tui/app/use-prompt-queue-history.mjs
|
|
21901
21059
|
function usePromptQueueHistory({
|
|
21902
21060
|
store,
|
|
21903
21061
|
state,
|
|
@@ -21930,8 +21088,8 @@ function usePromptQueueHistory({
|
|
|
21930
21088
|
let timer2 = null;
|
|
21931
21089
|
Promise.race([
|
|
21932
21090
|
Promise.resolve(store.dispose?.("cli-react-exit", { detach: true })),
|
|
21933
|
-
new Promise((
|
|
21934
|
-
timer2 = setTimeout(
|
|
21091
|
+
new Promise((resolve4) => {
|
|
21092
|
+
timer2 = setTimeout(resolve4, 350);
|
|
21935
21093
|
})
|
|
21936
21094
|
]).finally(() => {
|
|
21937
21095
|
if (timer2) clearTimeout(timer2);
|
|
@@ -21951,8 +21109,12 @@ function usePromptQueueHistory({
|
|
|
21951
21109
|
if (restoreDraft) {
|
|
21952
21110
|
if (restored2.pastedImages) installPastedImages(restored2.pastedImages, { merge: true });
|
|
21953
21111
|
if (restored2.pastedTexts) installPastedTexts(restored2.pastedTexts, { merge: true });
|
|
21954
|
-
|
|
21955
|
-
|
|
21112
|
+
const restoredText = mergeQueuedRestoreText(
|
|
21113
|
+
restored2.text,
|
|
21114
|
+
promptValueRef.current ?? currentText
|
|
21115
|
+
);
|
|
21116
|
+
syncPromptLayoutRows(restoredText);
|
|
21117
|
+
setPromptDraftOverride({ id: Date.now(), value: restoredText });
|
|
21956
21118
|
}
|
|
21957
21119
|
if (showHint) {
|
|
21958
21120
|
showPromptHint(`restored ${restored2.count} queued message${restored2.count === 1 ? "" : "s"}`, "info");
|
|
@@ -21961,7 +21123,7 @@ function usePromptQueueHistory({
|
|
|
21961
21123
|
}
|
|
21962
21124
|
return true;
|
|
21963
21125
|
};
|
|
21964
|
-
const restored = store.restoreQueued?.(
|
|
21126
|
+
const restored = store.restoreQueued?.("");
|
|
21965
21127
|
if (restored && typeof restored.then === "function") {
|
|
21966
21128
|
const queuedCount = Array.isArray(state?.queued) ? state.queued.length : 0;
|
|
21967
21129
|
void Promise.resolve(restored).then(apply).catch(() => {
|
|
@@ -21993,6 +21155,88 @@ function usePromptQueueHistory({
|
|
|
21993
21155
|
return { requestExit, restoreQueuedToPrompt, recentPromptHistory, resetPromptHistoryNav };
|
|
21994
21156
|
}
|
|
21995
21157
|
|
|
21158
|
+
// src/tui/app/message-selector.mjs
|
|
21159
|
+
import { useCallback as useCallback10, useMemo as useMemo3 } from "react";
|
|
21160
|
+
var MAX_SELECTABLE_MESSAGES = 20;
|
|
21161
|
+
function selectableUserItems(items, limit = MAX_SELECTABLE_MESSAGES) {
|
|
21162
|
+
const source = Array.isArray(items) ? items : [];
|
|
21163
|
+
const rows = [];
|
|
21164
|
+
for (let index = source.length - 1; index >= 0 && rows.length < limit; index -= 1) {
|
|
21165
|
+
const item = source[index];
|
|
21166
|
+
if (item?.kind !== "user") continue;
|
|
21167
|
+
if (item?.id == null) continue;
|
|
21168
|
+
const text = String(item.text || "").trim();
|
|
21169
|
+
if (!text) continue;
|
|
21170
|
+
rows.push({ id: String(item.id), text });
|
|
21171
|
+
}
|
|
21172
|
+
return rows.reverse();
|
|
21173
|
+
}
|
|
21174
|
+
function messageSelectorLabel(text, width = 56) {
|
|
21175
|
+
const firstLine = String(text || "").split("\n").map((line) => line.trim()).find(Boolean) || "";
|
|
21176
|
+
if (firstLine.length <= width) return firstLine;
|
|
21177
|
+
return `${firstLine.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
21178
|
+
}
|
|
21179
|
+
function useMessageSelector({
|
|
21180
|
+
store,
|
|
21181
|
+
state,
|
|
21182
|
+
setPicker,
|
|
21183
|
+
setPromptDraftOverride,
|
|
21184
|
+
syncPromptLayoutRows,
|
|
21185
|
+
showPromptHint,
|
|
21186
|
+
clearPromptHint
|
|
21187
|
+
}) {
|
|
21188
|
+
const hasUserMessages = useMemo3(
|
|
21189
|
+
() => (Array.isArray(state.items) ? state.items : []).some(
|
|
21190
|
+
(item) => item?.kind === "user" && String(item?.text || "").trim()
|
|
21191
|
+
),
|
|
21192
|
+
[state.items]
|
|
21193
|
+
);
|
|
21194
|
+
const applyRewind = useCallback10((restored) => {
|
|
21195
|
+
const text = String(restored?.text || "");
|
|
21196
|
+
if (!text) {
|
|
21197
|
+
showPromptHint("Could not restore that message.", "error");
|
|
21198
|
+
return false;
|
|
21199
|
+
}
|
|
21200
|
+
clearPromptHint();
|
|
21201
|
+
syncPromptLayoutRows(text);
|
|
21202
|
+
setPromptDraftOverride({ id: Date.now(), value: text });
|
|
21203
|
+
return true;
|
|
21204
|
+
}, [clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride, showPromptHint]);
|
|
21205
|
+
const openMessageSelector = useCallback10(() => {
|
|
21206
|
+
const items = store.getState?.().items ?? state.items;
|
|
21207
|
+
const rows = selectableUserItems(items);
|
|
21208
|
+
if (rows.length === 0) {
|
|
21209
|
+
showPromptHint("No message to jump back to.", "info");
|
|
21210
|
+
return false;
|
|
21211
|
+
}
|
|
21212
|
+
setPicker({
|
|
21213
|
+
_kind: "message-selector",
|
|
21214
|
+
title: "Jump back to a message",
|
|
21215
|
+
description: "Rewind the conversation to a previous prompt and edit it.",
|
|
21216
|
+
initialIndex: rows.length - 1,
|
|
21217
|
+
items: rows.map((row) => ({ value: row.id, label: messageSelectorLabel(row.text) })),
|
|
21218
|
+
onSelect: (value) => {
|
|
21219
|
+
setPicker(null);
|
|
21220
|
+
let restored;
|
|
21221
|
+
try {
|
|
21222
|
+
restored = store.rewindToItem?.(value);
|
|
21223
|
+
} catch (error) {
|
|
21224
|
+
showPromptHint(`rewind failed: ${error?.message || error}`, "error");
|
|
21225
|
+
return;
|
|
21226
|
+
}
|
|
21227
|
+
if (restored && typeof restored.then === "function") {
|
|
21228
|
+
void Promise.resolve(restored).then(applyRewind).catch((error) => showPromptHint(`rewind failed: ${error?.message || error}`, "error"));
|
|
21229
|
+
return;
|
|
21230
|
+
}
|
|
21231
|
+
applyRewind(restored);
|
|
21232
|
+
},
|
|
21233
|
+
onCancel: () => setPicker(null)
|
|
21234
|
+
});
|
|
21235
|
+
return true;
|
|
21236
|
+
}, [store, state.items, setPicker, applyRewind, showPromptHint]);
|
|
21237
|
+
return { hasUserMessages, openMessageSelector };
|
|
21238
|
+
}
|
|
21239
|
+
|
|
21996
21240
|
// src/tui/app/use-terminal-chrome.mjs
|
|
21997
21241
|
import { useEffect as useEffect11 } from "react";
|
|
21998
21242
|
function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState }) {
|
|
@@ -22042,7 +21286,7 @@ function useTerminalChrome({ stdout: stdout2, isRawModeSupported, setResizeState
|
|
|
22042
21286
|
}
|
|
22043
21287
|
|
|
22044
21288
|
// src/tui/app/use-transcript-window.mjs
|
|
22045
|
-
import { useCallback as
|
|
21289
|
+
import { useCallback as useCallback11, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useMemo as useMemo4, useRef as useRef12, useState as useState10 } from "react";
|
|
22046
21290
|
var HARVEST_BUMP_STREAK_LIMIT = 10;
|
|
22047
21291
|
var TUI_DEBUG = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ""));
|
|
22048
21292
|
var harvestBreakerLogged = false;
|
|
@@ -22133,7 +21377,7 @@ function useTranscriptWindow({
|
|
|
22133
21377
|
useLayoutEffect4(() => {
|
|
22134
21378
|
if (scrollOffset !== 0) setScrollOffset(0);
|
|
22135
21379
|
}, [normalizedSessionKey, setScrollOffset]);
|
|
22136
|
-
const transcriptMeasureRef =
|
|
21380
|
+
const transcriptMeasureRef = useCallback11((item) => {
|
|
22137
21381
|
if (!TRANSCRIPT_MEASURED_ROWS || !item || item.id == null) return void 0;
|
|
22138
21382
|
if (shouldSuppressFullyFailedToolItem(item)) {
|
|
22139
21383
|
transcriptMeasuredRowsCache.delete(item);
|
|
@@ -22157,8 +21401,6 @@ function useTranscriptWindow({
|
|
|
22157
21401
|
return fn;
|
|
22158
21402
|
}, []);
|
|
22159
21403
|
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
21404
|
const tailRows = streamingTailItem ? estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded) : 0;
|
|
22163
21405
|
const tailSig = streamingTailItem ? `${streamingTailItem.id}:${tailRows}` : "_";
|
|
22164
21406
|
const revision = Math.max(0, Number(structureRevision) || 0);
|
|
@@ -22174,13 +21416,8 @@ function useTranscriptWindow({
|
|
|
22174
21416
|
revision,
|
|
22175
21417
|
streamingTailItem
|
|
22176
21418
|
);
|
|
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, {
|
|
21419
|
+
const suppressMeasuredRowHeights = false;
|
|
21420
|
+
const transcriptRowIndex = useMemo4(() => buildTranscriptRowIndexIncremental(transcriptItems, {
|
|
22184
21421
|
columns: frameColumns,
|
|
22185
21422
|
toolOutputExpanded,
|
|
22186
21423
|
suppressMeasuredRowHeights,
|
|
@@ -22279,7 +21516,7 @@ function useTranscriptWindow({
|
|
|
22279
21516
|
contentHeight: lockViewRows,
|
|
22280
21517
|
floatingPanelRows: Number(floatingPanelRows) || 0
|
|
22281
21518
|
};
|
|
22282
|
-
const transcriptWindow =
|
|
21519
|
+
const transcriptWindow = useMemo4(() => transcriptRenderWindow(transcriptItems, {
|
|
22283
21520
|
scrollOffset: renderScrollOffset,
|
|
22284
21521
|
viewportHeight: transcriptContentHeight,
|
|
22285
21522
|
columns: frameColumns,
|
|
@@ -22461,7 +21698,7 @@ function useTranscriptWindow({
|
|
|
22461
21698
|
const currentPosition = Math.max(0, Number(scrollPositionRef.current) || 0);
|
|
22462
21699
|
const currentOffset = Math.max(0, Number(scrollOffset) || 0);
|
|
22463
21700
|
const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
|
|
22464
|
-
if (!followingRef.current && rowDelta
|
|
21701
|
+
if (!followingRef.current && rowDelta >= 0 && currentTarget === 0) {
|
|
22465
21702
|
followingRef.current = true;
|
|
22466
21703
|
}
|
|
22467
21704
|
const nearBottom = followingRef.current;
|
|
@@ -22514,6 +21751,16 @@ function useTranscriptWindow({
|
|
|
22514
21751
|
maxRows
|
|
22515
21752
|
});
|
|
22516
21753
|
if (desired == null) {
|
|
21754
|
+
if (rowDelta < 0 || currentTarget > maxRows) {
|
|
21755
|
+
stopSmoothScroll();
|
|
21756
|
+
transcriptAnchorRef.current = null;
|
|
21757
|
+
transcriptAnchorDirtyRef.current = false;
|
|
21758
|
+
scrollTargetRef.current = 0;
|
|
21759
|
+
scrollPositionRef.current = 0;
|
|
21760
|
+
followingRef.current = true;
|
|
21761
|
+
if (currentOffset !== 0) setScrollOffset(0);
|
|
21762
|
+
return;
|
|
21763
|
+
}
|
|
22517
21764
|
transcriptAnchorDirtyRef.current = true;
|
|
22518
21765
|
return;
|
|
22519
21766
|
}
|
|
@@ -22560,6 +21807,11 @@ function useTranscriptWindow({
|
|
|
22560
21807
|
}, [themeEpoch, withSelectionClip, paintSelectionRect]);
|
|
22561
21808
|
useEffect12(() => {
|
|
22562
21809
|
const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
|
|
21810
|
+
if (maxRows === 0 && !followingRef.current) {
|
|
21811
|
+
transcriptAnchorRef.current = null;
|
|
21812
|
+
transcriptAnchorDirtyRef.current = false;
|
|
21813
|
+
followingRef.current = true;
|
|
21814
|
+
}
|
|
22563
21815
|
if (scrollTargetRef.current <= maxRows && scrollPositionRef.current <= maxRows && scrollOffset <= maxRows) return;
|
|
22564
21816
|
stopSmoothScroll();
|
|
22565
21817
|
const next = Math.max(0, Math.min(maxRows, scrollTargetRef.current));
|
|
@@ -22589,42 +21841,49 @@ function createProjectPicker({
|
|
|
22589
21841
|
setSettingsPrompt,
|
|
22590
21842
|
setContextPanel,
|
|
22591
21843
|
closeUsagePanel,
|
|
22592
|
-
listProjects: listProjects2,
|
|
22593
|
-
addProject: addProject2,
|
|
22594
|
-
touchProjectSelected: touchProjectSelected2,
|
|
22595
|
-
resolveProjectPath: resolveProjectPath2,
|
|
22596
21844
|
projectNameFromPath: projectNameFromPath3,
|
|
22597
21845
|
pickFolder: pickFolder2
|
|
22598
21846
|
}) {
|
|
22599
|
-
const
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
} catch {
|
|
22604
|
-
projects = [];
|
|
21847
|
+
const backendCall = (name, ...args) => {
|
|
21848
|
+
const target = store?.[name];
|
|
21849
|
+
if (typeof target !== "function") {
|
|
21850
|
+
return Promise.reject(new TypeError(`project backend method ${name} is unavailable`));
|
|
22605
21851
|
}
|
|
21852
|
+
return Promise.resolve(target.apply(store, args));
|
|
21853
|
+
};
|
|
21854
|
+
const buildProjectPickerState = ({
|
|
21855
|
+
initialEntry = false,
|
|
21856
|
+
projects = [],
|
|
21857
|
+
loading = false,
|
|
21858
|
+
requestId = null
|
|
21859
|
+
} = {}) => {
|
|
22606
21860
|
const currentPath = String(state.cwd || process.cwd() || "");
|
|
22607
21861
|
const items = [];
|
|
22608
|
-
|
|
22609
|
-
|
|
21862
|
+
if (!loading) {
|
|
21863
|
+
for (const project of projects) {
|
|
21864
|
+
if (!project?.path) continue;
|
|
21865
|
+
items.push({
|
|
21866
|
+
value: project.path,
|
|
21867
|
+
label: project.name || project.path,
|
|
21868
|
+
meta: project.path,
|
|
21869
|
+
_project: project
|
|
21870
|
+
});
|
|
21871
|
+
}
|
|
22610
21872
|
items.push({
|
|
22611
|
-
value:
|
|
22612
|
-
label:
|
|
22613
|
-
meta:
|
|
22614
|
-
|
|
21873
|
+
value: "__use_current__",
|
|
21874
|
+
label: "Current Path",
|
|
21875
|
+
meta: currentPath,
|
|
21876
|
+
_action: "current"
|
|
22615
21877
|
});
|
|
22616
21878
|
}
|
|
22617
|
-
items.push({
|
|
22618
|
-
value: "__use_current__",
|
|
22619
|
-
label: "Current Path",
|
|
22620
|
-
meta: currentPath,
|
|
22621
|
-
_action: "current"
|
|
22622
|
-
});
|
|
22623
21879
|
return {
|
|
22624
21880
|
kind: "project",
|
|
21881
|
+
_kind: "project",
|
|
21882
|
+
_projectRequestId: requestId,
|
|
21883
|
+
_projectInitialPending: loading && initialEntry,
|
|
22625
21884
|
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",
|
|
21885
|
+
description: loading ? "Loading projects from the backend\u2026" : "Choose a project.",
|
|
21886
|
+
help: loading ? "Waiting for the project backend\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
21887
|
indexMode: "always",
|
|
22629
21888
|
labelWidth: 18,
|
|
22630
21889
|
metaWidth: 40,
|
|
@@ -22635,20 +21894,11 @@ function createProjectPicker({
|
|
|
22635
21894
|
return;
|
|
22636
21895
|
}
|
|
22637
21896
|
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
|
-
}
|
|
21897
|
+
void enterProject(currentPath, { notice: !initialEntry, register: false });
|
|
22647
21898
|
return;
|
|
22648
21899
|
}
|
|
22649
|
-
setPicker(null);
|
|
22650
21900
|
const project = item?._project;
|
|
22651
|
-
if (project?.path) enterProject(project.path, { notice: !initialEntry });
|
|
21901
|
+
if (project?.path) void enterProject(project.path, { notice: !initialEntry });
|
|
22652
21902
|
},
|
|
22653
21903
|
onKey: (input, _key, item) => {
|
|
22654
21904
|
if (input === "c" || input === "C") {
|
|
@@ -22707,40 +21957,44 @@ function createProjectPicker({
|
|
|
22707
21957
|
openProjectPicker();
|
|
22708
21958
|
return;
|
|
22709
21959
|
}
|
|
22710
|
-
registerProject(result.path);
|
|
21960
|
+
void registerProject(result.path);
|
|
22711
21961
|
}).catch(() => {
|
|
22712
21962
|
beginNewProjectManual();
|
|
22713
21963
|
});
|
|
22714
21964
|
};
|
|
22715
|
-
const registerProject = (rawPath) => {
|
|
22716
|
-
const
|
|
22717
|
-
if (!
|
|
21965
|
+
const registerProject = async (rawPath) => {
|
|
21966
|
+
const path2 = String(rawPath || "").trim();
|
|
21967
|
+
if (!path2) {
|
|
22718
21968
|
store.pushNotice("project path is required", "warn");
|
|
22719
|
-
return;
|
|
21969
|
+
return false;
|
|
22720
21970
|
}
|
|
22721
21971
|
try {
|
|
22722
|
-
const project =
|
|
21972
|
+
const project = await backendCall("addProject", path2);
|
|
22723
21973
|
if (project?.name) store.pushNotice(`project added: ${project.name}`, "info");
|
|
22724
|
-
openProjectPicker();
|
|
21974
|
+
await openProjectPicker();
|
|
21975
|
+
return true;
|
|
22725
21976
|
} catch (e) {
|
|
22726
21977
|
store.pushNotice(`project add failed: ${e?.message || e}`, "error");
|
|
21978
|
+
return false;
|
|
22727
21979
|
}
|
|
22728
21980
|
};
|
|
22729
|
-
const enterProject = (rawPath, options = {}) => {
|
|
22730
|
-
const
|
|
22731
|
-
if (!
|
|
21981
|
+
const enterProject = async (rawPath, options = {}) => {
|
|
21982
|
+
const path2 = String(rawPath || "").trim();
|
|
21983
|
+
if (!path2) {
|
|
22732
21984
|
store.pushNotice("project path is required", "warn");
|
|
22733
|
-
return;
|
|
21985
|
+
return false;
|
|
22734
21986
|
}
|
|
21987
|
+
setPicker(null);
|
|
22735
21988
|
try {
|
|
22736
|
-
|
|
21989
|
+
const resolved = await backendCall("setCwd", path2, {
|
|
22737
21990
|
notice: options?.notice !== false,
|
|
22738
|
-
message: `Project set: ${projectNameFromPath3(
|
|
21991
|
+
message: `Project set: ${projectNameFromPath3(path2)}`
|
|
22739
21992
|
});
|
|
22740
|
-
|
|
22741
|
-
|
|
21993
|
+
if (options?.register !== false) await backendCall("addProject", resolved || path2);
|
|
21994
|
+
return true;
|
|
22742
21995
|
} catch (e) {
|
|
22743
21996
|
store.pushNotice(`project switch failed: ${e?.message || e}`, "error");
|
|
21997
|
+
return false;
|
|
22744
21998
|
}
|
|
22745
21999
|
};
|
|
22746
22000
|
const beginRenameProject = (project) => {
|
|
@@ -22759,14 +22013,32 @@ function createProjectPicker({
|
|
|
22759
22013
|
initialValue: project.name || ""
|
|
22760
22014
|
});
|
|
22761
22015
|
};
|
|
22762
|
-
const openProjectPicker = () => {
|
|
22016
|
+
const openProjectPicker = async ({ initialEntry = false } = {}) => {
|
|
22763
22017
|
setProviderPrompt(null);
|
|
22764
22018
|
setChannelPrompt(null);
|
|
22765
22019
|
setHookPrompt(null);
|
|
22766
22020
|
setSettingsPrompt(null);
|
|
22767
22021
|
setContextPanel(null);
|
|
22768
22022
|
closeUsagePanel();
|
|
22769
|
-
|
|
22023
|
+
const requestId = /* @__PURE__ */ Symbol("project-picker-request");
|
|
22024
|
+
setPicker(buildProjectPickerState({
|
|
22025
|
+
initialEntry,
|
|
22026
|
+
loading: true,
|
|
22027
|
+
requestId
|
|
22028
|
+
}));
|
|
22029
|
+
try {
|
|
22030
|
+
const projects = await backendCall("listProjects");
|
|
22031
|
+
setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({
|
|
22032
|
+
initialEntry,
|
|
22033
|
+
projects: Array.isArray(projects) ? projects : [],
|
|
22034
|
+
requestId
|
|
22035
|
+
}) : current);
|
|
22036
|
+
return projects;
|
|
22037
|
+
} catch (error) {
|
|
22038
|
+
setPicker((current) => current?._projectRequestId === requestId ? buildProjectPickerState({ initialEntry, projects: [], requestId }) : current);
|
|
22039
|
+
store.pushNotice(`project list failed: ${error?.message || error}`, "error");
|
|
22040
|
+
return [];
|
|
22041
|
+
}
|
|
22770
22042
|
};
|
|
22771
22043
|
return {
|
|
22772
22044
|
buildProjectPickerState,
|
|
@@ -22782,20 +22054,20 @@ function createProjectPicker({
|
|
|
22782
22054
|
// src/tui/app/use-prompt-handlers.mjs
|
|
22783
22055
|
init_paste_attachments();
|
|
22784
22056
|
init_paste_attachments();
|
|
22785
|
-
import { useCallback as
|
|
22057
|
+
import { useCallback as useCallback12, useEffect as useEffect13, useRef as useRef13 } from "react";
|
|
22786
22058
|
|
|
22787
22059
|
// src/tui/prompt-history-store.mjs
|
|
22788
|
-
import { chmodSync, existsSync as
|
|
22060
|
+
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
22789
22061
|
init_plugin_paths();
|
|
22790
|
-
import { dirname as
|
|
22062
|
+
import { dirname as dirname6, join as join6, resolve as resolve3 } from "node:path";
|
|
22791
22063
|
var PROMPT_HISTORY_LIMIT2 = 50;
|
|
22792
22064
|
function promptHistoryKey2(value) {
|
|
22793
22065
|
return String(value || "").trim().replace(/\s+/g, " ");
|
|
22794
22066
|
}
|
|
22795
22067
|
function readEntries(filePath) {
|
|
22796
|
-
if (!filePath || !
|
|
22068
|
+
if (!filePath || !existsSync6(filePath)) return [];
|
|
22797
22069
|
try {
|
|
22798
|
-
const parsed = JSON.parse(
|
|
22070
|
+
const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
|
|
22799
22071
|
const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
|
|
22800
22072
|
return entries.map((entry) => String(entry || "").trim()).filter((entry) => promptHistoryKey2(entry));
|
|
22801
22073
|
} catch {
|
|
@@ -22809,10 +22081,10 @@ function buildPayload(entries) {
|
|
|
22809
22081
|
function writeEntriesSync(filePath, entries) {
|
|
22810
22082
|
if (!filePath) return false;
|
|
22811
22083
|
try {
|
|
22812
|
-
|
|
22084
|
+
mkdirSync5(dirname6(filePath), { recursive: true, mode: 448 });
|
|
22813
22085
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
22814
|
-
|
|
22815
|
-
|
|
22086
|
+
writeFileSync4(tmp, buildPayload(entries), { encoding: "utf8", mode: 384 });
|
|
22087
|
+
renameSync2(tmp, filePath);
|
|
22816
22088
|
try {
|
|
22817
22089
|
chmodSync(filePath, 384);
|
|
22818
22090
|
} catch {
|
|
@@ -22849,6 +22121,13 @@ function flushPromptHistory() {
|
|
|
22849
22121
|
}
|
|
22850
22122
|
process.once("exit", flushPromptHistory);
|
|
22851
22123
|
|
|
22124
|
+
// src/tui/components/prompt-input/interrupt-policy.mjs
|
|
22125
|
+
function promptInterruptRestoreText(result, currentText = "") {
|
|
22126
|
+
if (!result || result?.aborted === false) return "";
|
|
22127
|
+
if (String(currentText ?? "") !== "") return "";
|
|
22128
|
+
return String(result?.restoreText || "").trim();
|
|
22129
|
+
}
|
|
22130
|
+
|
|
22852
22131
|
// src/tui/app/use-prompt-handlers.mjs
|
|
22853
22132
|
function usePromptHandlers({
|
|
22854
22133
|
store,
|
|
@@ -22873,6 +22152,7 @@ function usePromptHandlers({
|
|
|
22873
22152
|
recentPromptHistory,
|
|
22874
22153
|
resetPromptHistoryNav,
|
|
22875
22154
|
restoreQueuedToPrompt,
|
|
22155
|
+
openMessageSelector,
|
|
22876
22156
|
usagePanel,
|
|
22877
22157
|
closeUsagePanel,
|
|
22878
22158
|
contextPanel,
|
|
@@ -22884,7 +22164,9 @@ function usePromptHandlers({
|
|
|
22884
22164
|
clearPastedTextsSnapshot,
|
|
22885
22165
|
registerPastedText
|
|
22886
22166
|
}) {
|
|
22887
|
-
const
|
|
22167
|
+
const interruptGenerationRef = useRef13(0);
|
|
22168
|
+
const pendingInterruptRestoreRef = useRef13(null);
|
|
22169
|
+
const handlePromptPaste = useCallback12((text, meta = {}) => {
|
|
22888
22170
|
const source = String(meta?.source || "paste");
|
|
22889
22171
|
const value = String(text ?? "");
|
|
22890
22172
|
const processText = (raw, returnRaw = false) => {
|
|
@@ -22946,7 +22228,7 @@ function usePromptHandlers({
|
|
|
22946
22228
|
}
|
|
22947
22229
|
return processText(value);
|
|
22948
22230
|
}, [registerPastedImage, registerPastedText, showPromptHint, state.cwd]);
|
|
22949
|
-
const handlePromptHistoryNavigate =
|
|
22231
|
+
const handlePromptHistoryNavigate = useCallback12((direction, currentText = "", meta = {}) => {
|
|
22950
22232
|
const currentValue = String(currentText || "");
|
|
22951
22233
|
const currentKey = promptHistoryKey2(currentValue);
|
|
22952
22234
|
const nav = promptHistoryNavRef.current || { active: false, index: -1, seed: "", lastValue: "" };
|
|
@@ -22993,7 +22275,7 @@ function usePromptHandlers({
|
|
|
22993
22275
|
promptHistoryDraftChangeRef.current = true;
|
|
22994
22276
|
return nextValue;
|
|
22995
22277
|
}, [recentPromptHistory, resetPromptHistoryNav, clearPromptHint]);
|
|
22996
|
-
const handlePromptEscape =
|
|
22278
|
+
const handlePromptEscape = useCallback12((text = "", meta = {}) => {
|
|
22997
22279
|
if (usagePanel) {
|
|
22998
22280
|
closeUsagePanel();
|
|
22999
22281
|
return true;
|
|
@@ -23002,8 +22284,28 @@ function usePromptHandlers({
|
|
|
23002
22284
|
setContextPanel(null);
|
|
23003
22285
|
return true;
|
|
23004
22286
|
}
|
|
22287
|
+
if (meta.phase === "clear-arm") {
|
|
22288
|
+
showPromptHint("Esc again to clear", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
|
|
22289
|
+
return true;
|
|
22290
|
+
}
|
|
22291
|
+
if (meta.phase === "select-arm") {
|
|
22292
|
+
showPromptHint("Esc again to pick a message", "plain", PROMPT_ESCAPE_CLEAR_WINDOW_MS);
|
|
22293
|
+
return true;
|
|
22294
|
+
}
|
|
22295
|
+
if (meta.phase === "select") {
|
|
22296
|
+
clearPromptHint();
|
|
22297
|
+
if (restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text })) return true;
|
|
22298
|
+
return openMessageSelector?.() === true;
|
|
22299
|
+
}
|
|
23005
22300
|
if (meta.phase === "clear") {
|
|
22301
|
+
try {
|
|
22302
|
+
const remembered = store.rememberPromptHistory?.(text);
|
|
22303
|
+
if (remembered?.catch) void remembered.catch(() => {
|
|
22304
|
+
});
|
|
22305
|
+
} catch {
|
|
22306
|
+
}
|
|
23006
22307
|
clearPastedImagesSnapshot();
|
|
22308
|
+
clearPastedTextsSnapshot();
|
|
23007
22309
|
clearPromptHint();
|
|
23008
22310
|
return false;
|
|
23009
22311
|
}
|
|
@@ -23011,21 +22313,57 @@ function usePromptHandlers({
|
|
|
23011
22313
|
return restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText: text });
|
|
23012
22314
|
}
|
|
23013
22315
|
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");
|
|
22316
|
+
}, [contextPanel, usagePanel, closeUsagePanel, restoreQueuedToPrompt, openMessageSelector, showPromptHint, clearPromptHint, clearPastedImagesSnapshot, clearPastedTextsSnapshot, store]);
|
|
22317
|
+
const commitAsyncInterruptRestore = useCallback12((pending) => {
|
|
22318
|
+
if (!pending || pending.generation !== interruptGenerationRef.current) return true;
|
|
22319
|
+
if (store.getState?.().busy) return false;
|
|
22320
|
+
const restoreText = promptInterruptRestoreText(pending.result, promptValueRef.current);
|
|
22321
|
+
if (!restoreText) return true;
|
|
22322
|
+
if (pending.result?.pastedImages) installPastedImages(pending.result.pastedImages, { merge: true });
|
|
22323
|
+
if (pending.result?.pastedTexts) installPastedTexts(pending.result.pastedTexts, { merge: true });
|
|
23026
22324
|
clearPromptHint();
|
|
23027
|
-
|
|
23028
|
-
|
|
22325
|
+
syncPromptLayoutRows(restoreText);
|
|
22326
|
+
setPromptDraftOverride({ id: Date.now(), value: restoreText });
|
|
22327
|
+
return true;
|
|
22328
|
+
}, [store, promptValueRef, installPastedImages, installPastedTexts, clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride]);
|
|
22329
|
+
useEffect13(() => {
|
|
22330
|
+
const pending = pendingInterruptRestoreRef.current;
|
|
22331
|
+
if (!pending) return;
|
|
22332
|
+
if (commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = null;
|
|
22333
|
+
}, [state.busy, commitAsyncInterruptRestore]);
|
|
22334
|
+
const handlePromptInterrupt = useCallback12((currentText = "") => {
|
|
22335
|
+
const generation = ++interruptGenerationRef.current;
|
|
22336
|
+
pendingInterruptRestoreRef.current = null;
|
|
22337
|
+
const applyResult = (result2, draftText, asyncResult = false) => {
|
|
22338
|
+
if (generation !== interruptGenerationRef.current || result2?.aborted === false) return void 0;
|
|
22339
|
+
if (result2?.discardPastedImages) clearPastedImagesSnapshot(result2.discardPastedImages);
|
|
22340
|
+
if (result2?.discardPastedTexts) clearPastedTextsSnapshot(result2.discardPastedTexts);
|
|
22341
|
+
if (asyncResult) {
|
|
22342
|
+
const pending = { generation, result: result2 };
|
|
22343
|
+
if (!commitAsyncInterruptRestore(pending)) pendingInterruptRestoreRef.current = pending;
|
|
22344
|
+
return void 0;
|
|
22345
|
+
}
|
|
22346
|
+
const restoreText = promptInterruptRestoreText(result2, draftText);
|
|
22347
|
+
if (!restoreText) return void 0;
|
|
22348
|
+
if (result2?.pastedImages) installPastedImages(result2.pastedImages, { merge: true });
|
|
22349
|
+
if (result2?.pastedTexts) installPastedTexts(result2.pastedTexts, { merge: true });
|
|
22350
|
+
clearPromptHint();
|
|
22351
|
+
return restoreText;
|
|
22352
|
+
};
|
|
22353
|
+
let result;
|
|
22354
|
+
try {
|
|
22355
|
+
const options = { restorePrompt: String(currentText ?? "") === "" };
|
|
22356
|
+
result = typeof store.abortAsync === "function" ? store.abortAsync(options) : store.abort?.(options);
|
|
22357
|
+
} catch (error) {
|
|
22358
|
+
store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error");
|
|
22359
|
+
return void 0;
|
|
22360
|
+
}
|
|
22361
|
+
if (result && typeof result.then === "function") {
|
|
22362
|
+
void Promise.resolve(result).then((resolved) => applyResult(resolved, promptValueRef.current, true)).catch((error) => store.pushNotice?.(`interrupt failed: ${error?.message || error}`, "error"));
|
|
22363
|
+
return void 0;
|
|
22364
|
+
}
|
|
22365
|
+
return applyResult(result, currentText, false);
|
|
22366
|
+
}, [store, commitAsyncInterruptRestore, clearPromptHint, installPastedImages, installPastedTexts, clearPastedImagesSnapshot, clearPastedTextsSnapshot]);
|
|
23029
22367
|
return {
|
|
23030
22368
|
handlePromptPaste,
|
|
23031
22369
|
handlePromptHistoryNavigate,
|
|
@@ -23092,30 +22430,31 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23092
22430
|
const { stdout: stdout2 } = useStdout2();
|
|
23093
22431
|
const [exiting, setExiting] = useState11(false);
|
|
23094
22432
|
const [tuiReady, setTuiReady] = useState11(false);
|
|
23095
|
-
const exitRequestedRef =
|
|
22433
|
+
const exitRequestedRef = useRef14(false);
|
|
23096
22434
|
const [resizeState, setResizeState] = useState11(() => ({ ...terminalSize(stdout2), epoch: 0 }));
|
|
23097
22435
|
const [panelTransitionEpoch, setPanelTransitionEpoch] = useState11(0);
|
|
23098
22436
|
const [panelInkMaskEpoch, setPanelInkMaskEpoch] = useState11(0);
|
|
23099
22437
|
const rightSafetyColumns = 1;
|
|
23100
22438
|
const frameColumns = Math.max(1, resizeState.columns - rightSafetyColumns);
|
|
23101
22439
|
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 =
|
|
22440
|
+
const scrollPositionRef = useRef14(0);
|
|
22441
|
+
const scrollTargetRef = useRef14(0);
|
|
22442
|
+
const maxScrollRowsRef = useRef14(0);
|
|
22443
|
+
const transcriptBottomSlackRowsRef = useRef14(0);
|
|
22444
|
+
const transcriptAnchorRef = useRef14(null);
|
|
22445
|
+
const transcriptAnchorDirtyRef = useRef14(false);
|
|
22446
|
+
const transcriptGeomRef = useRef14({ prefixRows: null, totalRows: 0, viewRows: 1 });
|
|
23109
22447
|
const [measuredRowsVersion, setMeasuredRowsVersion] = useState11(0);
|
|
23110
|
-
const followingRef =
|
|
23111
|
-
const lastItemsCountRef =
|
|
23112
|
-
const
|
|
23113
|
-
const
|
|
23114
|
-
const
|
|
22448
|
+
const followingRef = useRef14(false);
|
|
22449
|
+
const lastItemsCountRef = useRef14(0);
|
|
22450
|
+
const lastFirstItemIdRef = useRef14(null);
|
|
22451
|
+
const pickerOpenedFromEnterRef = useRef14(false);
|
|
22452
|
+
const pickerOpenedFromEnterTimerRef = useRef14(null);
|
|
22453
|
+
const projectPickerRef = useRef14(null);
|
|
23115
22454
|
const buildProjectPickerState = (opts) => projectPickerRef.current.buildProjectPickerState(opts);
|
|
23116
22455
|
const [picker, setPickerState] = useState11(null);
|
|
23117
|
-
const livePickerRef =
|
|
23118
|
-
const setPicker =
|
|
22456
|
+
const livePickerRef = useRef14(null);
|
|
22457
|
+
const setPicker = useCallback13((next) => {
|
|
23119
22458
|
livePickerRef.current = typeof next === "function" ? next(livePickerRef.current) : next;
|
|
23120
22459
|
setPickerState((prev) => {
|
|
23121
22460
|
const resolved = typeof next === "function" ? next(prev) : next;
|
|
@@ -23136,14 +22475,14 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23136
22475
|
livePickerRef.current = picker;
|
|
23137
22476
|
const [contextPanel, setContextPanel] = useState11(null);
|
|
23138
22477
|
const [usagePanel, setUsagePanel] = useState11(null);
|
|
23139
|
-
const usageRequestRef =
|
|
23140
|
-
const settingsHeavyCacheRef =
|
|
23141
|
-
const closeUsagePanel =
|
|
22478
|
+
const usageRequestRef = useRef14(0);
|
|
22479
|
+
const settingsHeavyCacheRef = useRef14(null);
|
|
22480
|
+
const closeUsagePanel = useCallback13(() => {
|
|
23142
22481
|
usageRequestRef.current += 1;
|
|
23143
22482
|
setUsagePanel(null);
|
|
23144
22483
|
}, []);
|
|
23145
22484
|
const [providerPrompt, setProviderPrompt] = useState11(null);
|
|
23146
|
-
const oauthSubmitRef =
|
|
22485
|
+
const oauthSubmitRef = useRef14(false);
|
|
23147
22486
|
const [channelPrompt, setChannelPrompt] = useState11(null);
|
|
23148
22487
|
const [hookPrompt, setHookPrompt] = useState11(null);
|
|
23149
22488
|
const [settingsPrompt, setSettingsPrompt] = useState11(null);
|
|
@@ -23157,22 +22496,22 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23157
22496
|
setSettingsPrompt,
|
|
23158
22497
|
setContextPanel,
|
|
23159
22498
|
closeUsagePanel,
|
|
23160
|
-
listProjects,
|
|
23161
|
-
addProject,
|
|
23162
|
-
touchProjectSelected,
|
|
23163
|
-
resolveProjectPath,
|
|
23164
22499
|
projectNameFromPath,
|
|
23165
22500
|
pickFolder
|
|
23166
22501
|
});
|
|
23167
22502
|
projectPickerRef.current = projectPicker;
|
|
23168
|
-
const initialPickerBuiltRef =
|
|
22503
|
+
const initialPickerBuiltRef = useRef14(false);
|
|
23169
22504
|
if (!initialPickerBuiltRef.current) {
|
|
23170
22505
|
initialPickerBuiltRef.current = true;
|
|
23171
22506
|
const onboardingOwnsScreen = !resolveOnboardingCompleted(store, onboardingCompleted) || forceOnboarding;
|
|
23172
22507
|
if (!onboardingOwnsScreen && state.items.length === 0) {
|
|
23173
|
-
setPicker(projectPicker.buildProjectPickerState({ initialEntry: true }));
|
|
22508
|
+
setPicker(projectPicker.buildProjectPickerState({ initialEntry: true, loading: true }));
|
|
23174
22509
|
}
|
|
23175
22510
|
}
|
|
22511
|
+
useEffect14(() => {
|
|
22512
|
+
if (livePickerRef.current?._projectInitialPending !== true) return;
|
|
22513
|
+
void projectPickerRef.current?.openProjectPicker({ initialEntry: true });
|
|
22514
|
+
}, [store]);
|
|
23176
22515
|
const {
|
|
23177
22516
|
beginNewProject,
|
|
23178
22517
|
registerProject,
|
|
@@ -23181,7 +22520,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23181
22520
|
openProjectPicker
|
|
23182
22521
|
} = projectPicker;
|
|
23183
22522
|
const [disabledSkills, setDisabledSkillsInner] = useState11(() => /* @__PURE__ */ new Set());
|
|
23184
|
-
|
|
22523
|
+
useEffect14(() => {
|
|
23185
22524
|
let alive = true;
|
|
23186
22525
|
void Promise.resolve(store.getDisabledSkills?.()).then((result) => {
|
|
23187
22526
|
if (!alive) return;
|
|
@@ -23193,7 +22532,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23193
22532
|
alive = false;
|
|
23194
22533
|
};
|
|
23195
22534
|
}, [store]);
|
|
23196
|
-
const setDisabledSkills =
|
|
22535
|
+
const setDisabledSkills = useCallback13((next) => {
|
|
23197
22536
|
setDisabledSkillsInner((current) => {
|
|
23198
22537
|
const base = current instanceof Set ? current : new Set(current);
|
|
23199
22538
|
const set = typeof next === "function" ? next(base) : next instanceof Set ? next : new Set(next);
|
|
@@ -23208,7 +22547,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23208
22547
|
const toolApproval = state.toolApproval || null;
|
|
23209
22548
|
const [promptDraft, setPromptDraft] = useState11("");
|
|
23210
22549
|
const [promptDraftOverride, setPromptDraftOverride] = useState11(null);
|
|
23211
|
-
const promptLayoutValueRef =
|
|
22550
|
+
const promptLayoutValueRef = useRef14("");
|
|
23212
22551
|
const [, setPromptLayoutRows] = useState11(1);
|
|
23213
22552
|
const [textEntryLayoutRows, setTextEntryLayoutRows] = useState11(1);
|
|
23214
22553
|
const {
|
|
@@ -23225,12 +22564,12 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23225
22564
|
clearPastedTextsSnapshot,
|
|
23226
22565
|
registerPastedText
|
|
23227
22566
|
} = usePastedBuffers();
|
|
23228
|
-
const promptValueRef =
|
|
23229
|
-
const promptSelectionRef =
|
|
23230
|
-
const promptBoxRectRef =
|
|
23231
|
-
const promptMouseSelectionRef =
|
|
23232
|
-
const promptHistoryNavRef =
|
|
23233
|
-
const promptHistoryDraftChangeRef =
|
|
22567
|
+
const promptValueRef = useRef14("");
|
|
22568
|
+
const promptSelectionRef = useRef14(null);
|
|
22569
|
+
const promptBoxRectRef = useRef14(null);
|
|
22570
|
+
const promptMouseSelectionRef = useRef14(null);
|
|
22571
|
+
const promptHistoryNavRef = useRef14({ active: false, index: -1, seed: "", lastValue: "" });
|
|
22572
|
+
const promptHistoryDraftChangeRef = useRef14(false);
|
|
23234
22573
|
const {
|
|
23235
22574
|
promptHint,
|
|
23236
22575
|
promptHintTone,
|
|
@@ -23240,7 +22579,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23240
22579
|
showSelectionCopyHint,
|
|
23241
22580
|
clearPromptHint
|
|
23242
22581
|
} = usePromptHint();
|
|
23243
|
-
const toastErrorSignature =
|
|
22582
|
+
const toastErrorSignature = useMemo5(() => (state.toasts || []).filter((toast) => toast?.tone === "error").map((toast) => `${toast.id || ""}:${toast.text || ""}`).join("|"), [state.toasts]);
|
|
23244
22583
|
const {
|
|
23245
22584
|
welcomePromptHintDismissed,
|
|
23246
22585
|
conditionalWelcomePromptHint,
|
|
@@ -23250,16 +22589,16 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23250
22589
|
} = useWelcomePromptHint({ store, state, toastErrorSignature });
|
|
23251
22590
|
const [slashIndex, setSlashIndex] = useState11(0);
|
|
23252
22591
|
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 =
|
|
22592
|
+
const slashPaletteRef = useRef14({ open: false, count: 0 });
|
|
22593
|
+
const workflowTabCycleRef = useRef14({ pending: false, lastAt: 0 });
|
|
22594
|
+
const scrollFocusRef = useRef14({});
|
|
22595
|
+
const onboardingStartedRef = useRef14(false);
|
|
22596
|
+
const onboardingRef = useRef14({ defaultRoute: null, searchRoute: null, agentRoutes: {}, agents: [], providerModels: [] });
|
|
22597
|
+
const providerModelsCacheRef = useRef14({ models: null, at: 0 });
|
|
22598
|
+
const searchModelsCacheRef = useRef14({ models: null, at: 0 });
|
|
22599
|
+
const modelPickerRequestRef = useRef14(0);
|
|
22600
|
+
const onboardingPrefetchSeqRef = useRef14(0);
|
|
22601
|
+
const clearModelCaches = useCallback13((scope = "all") => {
|
|
23263
22602
|
if (scope === "all" || scope === "provider") {
|
|
23264
22603
|
providerModelsCacheRef.current = { models: null, at: 0 };
|
|
23265
22604
|
onboardingRef.current.providerModels = [];
|
|
@@ -23269,7 +22608,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23269
22608
|
searchModelsCacheRef.current = { models: null, at: 0 };
|
|
23270
22609
|
}
|
|
23271
22610
|
}, []);
|
|
23272
|
-
|
|
22611
|
+
useEffect14(() => {
|
|
23273
22612
|
let alive = true;
|
|
23274
22613
|
const timer2 = setTimeout(async () => {
|
|
23275
22614
|
const seq = onboardingPrefetchSeqRef.current;
|
|
@@ -23354,24 +22693,24 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23354
22693
|
openUsagePanel: (...a) => openUsagePanel(...a),
|
|
23355
22694
|
openContextPicker: (...a) => openContextPicker(...a)
|
|
23356
22695
|
});
|
|
23357
|
-
const dragRef =
|
|
23358
|
-
const transcriptViewportRef =
|
|
23359
|
-
const panelTransitionRef =
|
|
23360
|
-
const panelCloseInkMaskRowsRef =
|
|
23361
|
-
const projectBootInputLatchRef =
|
|
23362
|
-
const frameRowsRef =
|
|
22696
|
+
const dragRef = useRef14({ anchor: null, anchorScroll: 0, last: null, active: false, rect: null, region: null, anchorSpan: null });
|
|
22697
|
+
const transcriptViewportRef = useRef14({ top: 0, bottom: 0 });
|
|
22698
|
+
const panelTransitionRef = useRef14({ signature: "", reserve: 0, clearRows: 0, guardRows: 0, epoch: 0 });
|
|
22699
|
+
const panelCloseInkMaskRowsRef = useRef14(0);
|
|
22700
|
+
const projectBootInputLatchRef = useRef14(false);
|
|
22701
|
+
const frameRowsRef = useRef14(24);
|
|
23363
22702
|
const STATUSLINE_BAND_ROWS = 3;
|
|
23364
22703
|
const promptContentColumns = Math.max(1, frameColumns - 4);
|
|
23365
|
-
const syncPromptLayoutRows =
|
|
22704
|
+
const syncPromptLayoutRows = useCallback13((value) => {
|
|
23366
22705
|
const text = String(value ?? "");
|
|
23367
22706
|
promptLayoutValueRef.current = text;
|
|
23368
22707
|
const nextRows = promptContentRows(text, promptContentColumns);
|
|
23369
22708
|
setPromptLayoutRows((prev) => prev === nextRows ? prev : nextRows);
|
|
23370
22709
|
}, [promptContentColumns]);
|
|
23371
|
-
|
|
22710
|
+
useEffect14(() => {
|
|
23372
22711
|
syncPromptLayoutRows(promptLayoutValueRef.current);
|
|
23373
22712
|
}, [syncPromptLayoutRows]);
|
|
23374
|
-
|
|
22713
|
+
useEffect14(() => {
|
|
23375
22714
|
const kind = String(settingsPrompt?.kind || "");
|
|
23376
22715
|
if (!CORE_MULTILINE_TEXT_ENTRY_KINDS.has(kind)) {
|
|
23377
22716
|
setTextEntryLayoutRows(1);
|
|
@@ -23380,10 +22719,10 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23380
22719
|
const cols = Math.max(1, frameColumns - 4 - stringWidth11("Sentence > "));
|
|
23381
22720
|
setTextEntryLayoutRows(textEntryReservedRows(settingsPrompt?.initialValue, cols, 8));
|
|
23382
22721
|
}, [settingsPrompt?.kind, settingsPrompt?.initialValue, frameColumns]);
|
|
23383
|
-
const selectionLayoutRef =
|
|
23384
|
-
const selectionTextRef =
|
|
23385
|
-
const lastClickRef =
|
|
23386
|
-
|
|
22722
|
+
const selectionLayoutRef = useRef14(null);
|
|
22723
|
+
const selectionTextRef = useRef14("");
|
|
22724
|
+
const lastClickRef = useRef14({ x: -1, y: -1, t: 0, count: 0 });
|
|
22725
|
+
useEffect14(() => {
|
|
23387
22726
|
const timer2 = setTimeout(() => setTuiReady(true), 0);
|
|
23388
22727
|
return () => clearTimeout(timer2);
|
|
23389
22728
|
}, []);
|
|
@@ -23429,7 +22768,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23429
22768
|
getStitchedSelectionText,
|
|
23430
22769
|
showSelectionCopyHint
|
|
23431
22770
|
});
|
|
23432
|
-
|
|
22771
|
+
useEffect14(() => () => {
|
|
23433
22772
|
stopSmoothScroll();
|
|
23434
22773
|
}, [stopSmoothScroll]);
|
|
23435
22774
|
useMouseInput({
|
|
@@ -23463,12 +22802,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23463
22802
|
const count = state.items.length;
|
|
23464
22803
|
const previousCount = lastItemsCountRef.current;
|
|
23465
22804
|
lastItemsCountRef.current = count;
|
|
23466
|
-
|
|
22805
|
+
const firstId = count > 0 ? state.items[0]?.id ?? null : null;
|
|
22806
|
+
const previousFirstId = lastFirstItemIdRef.current;
|
|
22807
|
+
lastFirstItemIdRef.current = firstId;
|
|
22808
|
+
if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
|
|
23467
22809
|
resetTranscriptScroll();
|
|
23468
22810
|
return;
|
|
23469
22811
|
}
|
|
23470
22812
|
if (count === previousCount || dragRef.current.active) return;
|
|
23471
|
-
}, [state.items
|
|
22813
|
+
}, [state.items, resetTranscriptScroll]);
|
|
23472
22814
|
const {
|
|
23473
22815
|
requestExit,
|
|
23474
22816
|
restoreQueuedToPrompt,
|
|
@@ -23490,6 +22832,15 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23490
22832
|
setPromptDraftOverride,
|
|
23491
22833
|
promptHistoryNavRef
|
|
23492
22834
|
});
|
|
22835
|
+
const { hasUserMessages, openMessageSelector } = useMessageSelector({
|
|
22836
|
+
store,
|
|
22837
|
+
state,
|
|
22838
|
+
setPicker,
|
|
22839
|
+
setPromptDraftOverride,
|
|
22840
|
+
syncPromptLayoutRows,
|
|
22841
|
+
showPromptHint,
|
|
22842
|
+
clearPromptHint
|
|
22843
|
+
});
|
|
23493
22844
|
const {
|
|
23494
22845
|
handlePromptPaste,
|
|
23495
22846
|
handlePromptHistoryNavigate,
|
|
@@ -23515,6 +22866,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23515
22866
|
recentPromptHistory,
|
|
23516
22867
|
resetPromptHistoryNav,
|
|
23517
22868
|
restoreQueuedToPrompt,
|
|
22869
|
+
openMessageSelector,
|
|
23518
22870
|
usagePanel,
|
|
23519
22871
|
closeUsagePanel,
|
|
23520
22872
|
contextPanel,
|
|
@@ -23525,7 +22877,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23525
22877
|
clearPastedTextsSnapshot,
|
|
23526
22878
|
registerPastedText
|
|
23527
22879
|
});
|
|
23528
|
-
const toggleExpand =
|
|
22880
|
+
const toggleExpand = useCallback13(() => {
|
|
23529
22881
|
setToolOutputExpanded((expanded) => !expanded);
|
|
23530
22882
|
}, []);
|
|
23531
22883
|
useGlobalKeyInput({
|
|
@@ -23565,7 +22917,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23565
22917
|
setUsagePanel,
|
|
23566
22918
|
closeUsagePanel
|
|
23567
22919
|
});
|
|
23568
|
-
|
|
22920
|
+
useEffect14(() => {
|
|
23569
22921
|
if (contextPanel?.kind === "context") {
|
|
23570
22922
|
openContextPicker();
|
|
23571
22923
|
return;
|
|
@@ -23586,7 +22938,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23586
22938
|
state.cwd,
|
|
23587
22939
|
state.clientHostPid
|
|
23588
22940
|
]);
|
|
23589
|
-
|
|
22941
|
+
useEffect14(() => {
|
|
23590
22942
|
if (onboardingStartedRef.current) return void 0;
|
|
23591
22943
|
if (resolveOnboardingCompleted(store, onboardingCompleted) && !forceOnboarding) return void 0;
|
|
23592
22944
|
let canceled = false;
|
|
@@ -23647,7 +22999,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23647
22999
|
hookPrompt: !!hookPrompt,
|
|
23648
23000
|
settingsPrompt: !!settingsPrompt
|
|
23649
23001
|
};
|
|
23650
|
-
|
|
23002
|
+
useEffect14(() => {
|
|
23651
23003
|
setSlashIndex((index) => Math.min(index, Math.max(0, slashCommands.length - 1)));
|
|
23652
23004
|
}, [slashCommands.length, activeSlashQuery]);
|
|
23653
23005
|
const {
|
|
@@ -23750,7 +23102,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23750
23102
|
transcriptContentHeight,
|
|
23751
23103
|
transcriptBottomSlackRows
|
|
23752
23104
|
} = layout;
|
|
23753
|
-
|
|
23105
|
+
useEffect14(() => {
|
|
23754
23106
|
const transition = panelTransitionRef.current;
|
|
23755
23107
|
const pendingInkMask = panelCloseInkMaskRowsRef.current;
|
|
23756
23108
|
const hadTransitionClearance = panelTransitionClearRows > 0 || panelTransitionGuardRows > 0;
|
|
@@ -23767,7 +23119,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23767
23119
|
const timer2 = setTimeout(() => setPanelTransitionEpoch((epoch) => epoch + 1), 0);
|
|
23768
23120
|
return () => clearTimeout(timer2);
|
|
23769
23121
|
}, [panelLayoutSignature, bottomClusterRows, panelTransitionClearRows, panelTransitionGuardRows]);
|
|
23770
|
-
|
|
23122
|
+
useEffect14(() => {
|
|
23771
23123
|
panelTransitionRef.current.tailId = latestTranscriptItem?.id ?? null;
|
|
23772
23124
|
});
|
|
23773
23125
|
const {
|
|
@@ -23811,7 +23163,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23811
23163
|
measuredRowsVersion,
|
|
23812
23164
|
setMeasuredRowsVersion
|
|
23813
23165
|
});
|
|
23814
|
-
const cycleWorkflowFromPrompt =
|
|
23166
|
+
const cycleWorkflowFromPrompt = useCallback13(() => {
|
|
23815
23167
|
if (slashPaletteOpen || toolApproval || picker || settingsPrompt || providerPrompt || channelPrompt || hookPrompt || contextPanel || usagePanel) return true;
|
|
23816
23168
|
const repeatGuardMs = 300;
|
|
23817
23169
|
const cycleGuard = workflowTabCycleRef.current;
|
|
@@ -23871,6 +23223,7 @@ function App({ store, initialStatusLine = "", forceOnboarding = false, onboardin
|
|
|
23871
23223
|
handlePromptHistoryNavigate,
|
|
23872
23224
|
handlePromptInterrupt,
|
|
23873
23225
|
handlePromptPaste,
|
|
23226
|
+
hasUserMessages,
|
|
23874
23227
|
hookPrompt,
|
|
23875
23228
|
initialStatusLine,
|
|
23876
23229
|
onPromptDraftChange,
|
|
@@ -23942,44 +23295,19 @@ var SYNTHETIC_SESSION_TEXT_PATTERNS = Object.freeze([
|
|
|
23942
23295
|
init_plugin_paths();
|
|
23943
23296
|
init_config();
|
|
23944
23297
|
|
|
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
|
-
};
|
|
23298
|
+
// src/runtime/shared/agent-route-config.mjs
|
|
23299
|
+
var LEGACY_AGENT_ROUTE_SLOTS = Object.freeze({
|
|
23300
|
+
explore: Object.freeze({ workflow: "explorer", maintenance: "explore" }),
|
|
23301
|
+
maintainer: Object.freeze({ alias: "maintenance", workflow: "memory", maintenance: "memory" }),
|
|
23302
|
+
worker: Object.freeze({ workflow: "agent" })
|
|
23303
|
+
});
|
|
23969
23304
|
|
|
23970
23305
|
// src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs
|
|
23971
23306
|
init_plugin_paths();
|
|
23972
|
-
import { join as
|
|
23973
|
-
var ANTHROPIC_DEFAULT_CREDENTIALS_PATH =
|
|
23307
|
+
import { join as join7 } from "node:path";
|
|
23308
|
+
var ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join7(resolvePluginData(), "anthropic-oauth-credentials.json");
|
|
23974
23309
|
|
|
23975
23310
|
// 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
23311
|
var MAINTENANCE_SLOTS = Object.freeze(["explore", "memory"]);
|
|
23984
23312
|
var PROFILE_LANGUAGES = Object.freeze([
|
|
23985
23313
|
{ id: "system", label: "System (locale)", prompt: null },
|
|
@@ -24074,8 +23402,8 @@ var TRANSCRIPT_PROCESS_NONCE = randomUUID();
|
|
|
24074
23402
|
|
|
24075
23403
|
// src/tui/engine.mjs
|
|
24076
23404
|
async function createEngineSession(options = {}) {
|
|
24077
|
-
const { createRemoteEngineSession
|
|
24078
|
-
return await
|
|
23405
|
+
const { createRemoteEngineSession } = await import("../../standalone/engine-daemon-client.mjs");
|
|
23406
|
+
return await createRemoteEngineSession(options);
|
|
24079
23407
|
}
|
|
24080
23408
|
|
|
24081
23409
|
// src/tui/index.jsx
|
|
@@ -24281,8 +23609,8 @@ function waitWithTimeout(promise, ms) {
|
|
|
24281
23609
|
let timer2 = null;
|
|
24282
23610
|
return Promise.race([
|
|
24283
23611
|
Promise.resolve(promise).then(() => true),
|
|
24284
|
-
new Promise((
|
|
24285
|
-
timer2 = setTimeout(() =>
|
|
23612
|
+
new Promise((resolve4) => {
|
|
23613
|
+
timer2 = setTimeout(() => resolve4(false), ms);
|
|
24286
23614
|
})
|
|
24287
23615
|
]).finally(() => {
|
|
24288
23616
|
if (timer2) clearTimeout(timer2);
|
|
@@ -24301,7 +23629,7 @@ function scheduleHardExit(code = 0) {
|
|
|
24301
23629
|
timer2.unref?.();
|
|
24302
23630
|
}
|
|
24303
23631
|
function resolveTuiStderrLogPath() {
|
|
24304
|
-
return process.env.MIXDOG_TUI_STDERR_LOG ||
|
|
23632
|
+
return process.env.MIXDOG_TUI_STDERR_LOG || join8(process.env.MIXDOG_RUNTIME_ROOT || join8(tmpdir2(), "mixdog"), "mixdog-tui.stderr.log");
|
|
24305
23633
|
}
|
|
24306
23634
|
function ansiFg(rgb) {
|
|
24307
23635
|
const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ""));
|
|
@@ -24383,7 +23711,7 @@ function installTuiStderrGuard() {
|
|
|
24383
23711
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
24384
23712
|
const logPath = resolveTuiStderrLogPath();
|
|
24385
23713
|
try {
|
|
24386
|
-
|
|
23714
|
+
mkdirSync6(dirname7(logPath), { recursive: true });
|
|
24387
23715
|
} catch {
|
|
24388
23716
|
}
|
|
24389
23717
|
(0, import_mixdog_debug.rotateBoundedLog)(logPath, import_mixdog_debug.PLUGIN_LOG_MAX_BYTES, import_mixdog_debug.PLUGIN_LOG_KEEP_BYTES);
|
|
@@ -24491,7 +23819,7 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
24491
23819
|
if (outcome.error) throw outcome.error;
|
|
24492
23820
|
store = outcome.store;
|
|
24493
23821
|
try {
|
|
24494
|
-
touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
23822
|
+
await store.touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
24495
23823
|
} catch {
|
|
24496
23824
|
}
|
|
24497
23825
|
if (store.isRemoteEngine === true) {
|