mixdog 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +710 -318
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -2
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +332 -57
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +59 -32
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +78 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +202 -98
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +183 -20
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +15 -9
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1224
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/output-forwarder.mjs +80 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +184 -19
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2096 -732
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +83 -7
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +170 -98
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +159 -21
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6965 -2391
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +129 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
package/src/tui/index.jsx
CHANGED
|
@@ -6,19 +6,24 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react';
|
|
8
8
|
import { render } from 'ink';
|
|
9
|
-
import { createWriteStream, mkdirSync } from 'node:fs';
|
|
9
|
+
import { closeSync, constants as fsConstants, createWriteStream, mkdirSync, openSync, readSync } from 'node:fs';
|
|
10
10
|
import { tmpdir } from 'node:os';
|
|
11
11
|
import { dirname, join } from 'node:path';
|
|
12
12
|
import { performance } from 'node:perf_hooks';
|
|
13
13
|
import { App } from './App.jsx';
|
|
14
14
|
import { createEngineSession } from './engine.mjs';
|
|
15
15
|
import { installProcessSignalCleanup } from '../runtime/shared/process-shutdown.mjs';
|
|
16
|
+
import { touchUiHeartbeat } from '../runtime/channels/lib/runtime-paths.mjs';
|
|
16
17
|
import { emitTerminalBackground, loadThemeSettingFromConfig, theme } from './theme.mjs';
|
|
18
|
+
import { POP_KITTY, DISABLE_MODIFY_OTHER_KEYS } from './keyboard-protocol.mjs';
|
|
17
19
|
|
|
18
20
|
const TERMINAL_MODE_RESET = '\x1b[?1006l\x1b[?1005l\x1b[?1015l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?2004l\x1b[?25h';
|
|
19
21
|
const TERMINAL_OSC_RESET_BG = '\x1b]111\x07';
|
|
20
22
|
const TERMINAL_MODE_RESET_HIDDEN_CURSOR = TERMINAL_MODE_RESET.replace('\x1b[?25h', '\x1b[?25l');
|
|
21
23
|
const MOUSE_TRACKING_ON = '\x1b[?1000h\x1b[?1002h\x1b[?1006h';
|
|
24
|
+
// Keyboard-protocol teardown. App.jsx enables kitty + modifyOtherKeys
|
|
25
|
+
// synchronously at raw-mode-on (no query); here we just pop/disable them on
|
|
26
|
+
// exit. POP_KITTY / DISABLE_MODIFY_OTHER_KEYS come from keyboard-protocol.mjs.
|
|
22
27
|
const BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ''));
|
|
23
28
|
const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
|
|
24
29
|
const EXIT_WAIT_TIMEOUT_MS = positiveIntEnv('MIXDOG_TUI_EXIT_WAIT_MS', 2500);
|
|
@@ -136,6 +141,47 @@ function resolveTuiStderrLogPath() {
|
|
|
136
141
|
|| join(process.env.MIXDOG_RUNTIME_ROOT || join(tmpdir(), 'mixdog'), 'mixdog-tui.stderr.log');
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
/** Drain stdin so queued key/mouse bytes do not leak into the shell after exit. */
|
|
145
|
+
function drainStdin(stdin = process.stdin) {
|
|
146
|
+
if (!stdin.isTTY) return;
|
|
147
|
+
try {
|
|
148
|
+
while (stdin.read() !== null) {
|
|
149
|
+
/* discard */
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
/* stream may be destroyed */
|
|
153
|
+
}
|
|
154
|
+
if (process.platform === 'win32') return;
|
|
155
|
+
const tty = stdin;
|
|
156
|
+
const wasRaw = tty.isRaw === true;
|
|
157
|
+
let fd = -1;
|
|
158
|
+
try {
|
|
159
|
+
if (!wasRaw) tty.setRawMode?.(true);
|
|
160
|
+
fd = openSync('/dev/tty', fsConstants.O_RDONLY | fsConstants.O_NONBLOCK);
|
|
161
|
+
const buf = Buffer.alloc(1024);
|
|
162
|
+
for (let i = 0; i < 64; i++) {
|
|
163
|
+
if (readSync(fd, buf, 0, buf.length, null) <= 0) break;
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
/* EAGAIN, ENXIO, ENOENT, EBADF, EIO */
|
|
167
|
+
} finally {
|
|
168
|
+
if (fd >= 0) {
|
|
169
|
+
try {
|
|
170
|
+
closeSync(fd);
|
|
171
|
+
} catch {
|
|
172
|
+
/* ignore */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!wasRaw) {
|
|
176
|
+
try {
|
|
177
|
+
tty.setRawMode?.(false);
|
|
178
|
+
} catch {
|
|
179
|
+
/* TTY may be gone */
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
139
185
|
function installTuiStderrGuard() {
|
|
140
186
|
if (process.env.MIXDOG_TUI_ALLOW_STDERR === '1') return () => {};
|
|
141
187
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
@@ -177,9 +223,9 @@ function installTuiStderrGuard() {
|
|
|
177
223
|
};
|
|
178
224
|
}
|
|
179
225
|
|
|
180
|
-
export async function runTui({ provider, model, toolMode } = {}) {
|
|
226
|
+
export async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {}) {
|
|
181
227
|
const startedAt = performance.now();
|
|
182
|
-
bootProfile('run:start', { provider, model, toolMode });
|
|
228
|
+
bootProfile('run:start', { provider, model, toolMode, remote });
|
|
183
229
|
// The React/ink TUI needs a raw-mode-capable TTY (interactive input). In a
|
|
184
230
|
// pipe/redirect/CI, ink's input hooks throw — bail with a clear hint instead.
|
|
185
231
|
if (!process.stdin.isTTY) {
|
|
@@ -191,13 +237,19 @@ export async function runTui({ provider, model, toolMode } = {}) {
|
|
|
191
237
|
}
|
|
192
238
|
|
|
193
239
|
const restoreStderr = installTuiStderrGuard();
|
|
194
|
-
const restorePrimedInput = () =>
|
|
240
|
+
const restorePrimedInput = () => drainStdin(process.stdin);
|
|
195
241
|
let restored = false;
|
|
196
242
|
const restoreTerminal = () => {
|
|
197
243
|
if (restored) return;
|
|
198
244
|
restored = true;
|
|
199
245
|
restorePrimedInput();
|
|
200
|
-
try {
|
|
246
|
+
try {
|
|
247
|
+
process.stdout.write(
|
|
248
|
+
// Pop kitty + disable modifyOtherKeys BEFORE leaving the alt screen.
|
|
249
|
+
// Both are no-ops if nothing was enabled, so this is always safe.
|
|
250
|
+
`${TERMINAL_MODE_RESET}\x1b[0 q${POP_KITTY}${DISABLE_MODIFY_OTHER_KEYS}\x1b[?1049l${TERMINAL_MODE_RESET}${TERMINAL_OSC_RESET_BG}`,
|
|
251
|
+
);
|
|
252
|
+
} catch { /* ignore */ }
|
|
201
253
|
};
|
|
202
254
|
|
|
203
255
|
// Enter the alternate screen buffer before session/runtime boot so no stale
|
|
@@ -211,6 +263,10 @@ export async function runTui({ provider, model, toolMode } = {}) {
|
|
|
211
263
|
// fat block. PromptInput parks this hardware cursor at the insertion point via
|
|
212
264
|
// useCursor; the terminal also anchors IME composition to it.
|
|
213
265
|
process.stdout.write('\x1b[5 q'); // blinking bar
|
|
266
|
+
// NOTE: extended-keys enabling (kitty + modifyOtherKeys) is done by App.jsx's
|
|
267
|
+
// mount effect, SYNCHRONOUSLY at ink's raw-mode-on — no query, no round-trip —
|
|
268
|
+
// so the first Ctrl+Enter is already covered. Only teardown lives here (see
|
|
269
|
+
// restoreTerminal above).
|
|
214
270
|
|
|
215
271
|
process.on('exit', restoreTerminal);
|
|
216
272
|
|
|
@@ -223,7 +279,7 @@ export async function runTui({ provider, model, toolMode } = {}) {
|
|
|
223
279
|
|
|
224
280
|
let store;
|
|
225
281
|
try {
|
|
226
|
-
store = await createEngineSession({ provider, model, toolMode });
|
|
282
|
+
store = await createEngineSession({ provider, model, toolMode, remote });
|
|
227
283
|
bootProfile('store:ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
228
284
|
} catch (error) {
|
|
229
285
|
restoreTerminal();
|
|
@@ -260,11 +316,58 @@ export async function runTui({ provider, model, toolMode } = {}) {
|
|
|
260
316
|
},
|
|
261
317
|
});
|
|
262
318
|
|
|
319
|
+
// Zombie-Lead repro (2026-07-02): the render loop can wedge (all owning
|
|
320
|
+
// PIDs still alive) leaving active-instance.json's pid-only staleness
|
|
321
|
+
// check unable to detect it. Heartbeat every 30s so runtime-paths.mjs can
|
|
322
|
+
// fall back to a ui_heartbeat_at staleness check when pids look fine.
|
|
323
|
+
// Best-effort: a failed/late write just means the next tick catches up,
|
|
324
|
+
// and the timer is unref'd so it never keeps the process alive on its own.
|
|
325
|
+
const uiHeartbeatTimer = setInterval(() => {
|
|
326
|
+
try { touchUiHeartbeat(); } catch { /* ignore */ }
|
|
327
|
+
}, 30_000);
|
|
328
|
+
if (typeof uiHeartbeatTimer.unref === 'function') uiHeartbeatTimer.unref();
|
|
329
|
+
try { touchUiHeartbeat(); } catch { /* ignore */ }
|
|
330
|
+
|
|
331
|
+
// Zombie-Lead repro (2026-07-02): stdio can die (TTY hangup, EPIPE on a
|
|
332
|
+
// detached/piped stdout) without the process ever receiving SIGHUP/SIGTERM,
|
|
333
|
+
// leaving a zombie renderer looping forever with nothing left to draw to.
|
|
334
|
+
// Treat a dead stdio surface as fatal and route it through the same
|
|
335
|
+
// signalCleanup teardown used for signals.
|
|
336
|
+
// 'error' whitelist: only codes that mean the stream is truly gone.
|
|
337
|
+
// Transient EAGAIN (backpressure) or terminal-resize noise must NOT be
|
|
338
|
+
// treated as fatal here — only EPIPE / ERR_STREAM_DESTROYED / EIO.
|
|
339
|
+
const STDIO_DEATH_FATAL_CODES = new Set(['EPIPE', 'ERR_STREAM_DESTROYED', 'EIO']);
|
|
340
|
+
const stdioDeathListeners = [];
|
|
341
|
+
const registerStdioDeath = (stream, event, { requireCode = false } = {}) => {
|
|
342
|
+
if (!stream || typeof stream.on !== 'function') return;
|
|
343
|
+
const handler = (err) => {
|
|
344
|
+
if (requireCode && !(err && STDIO_DEATH_FATAL_CODES.has(err.code))) return;
|
|
345
|
+
void signalCleanup.run('stdio-dead', {
|
|
346
|
+
code: 1,
|
|
347
|
+
shouldExit: true,
|
|
348
|
+
error: err || new Error(`stdio ${event} (source: ${stream === process.stdin ? 'stdin' : stream === process.stdout ? 'stdout' : 'stderr'})`),
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
stream.on(event, handler);
|
|
352
|
+
stdioDeathListeners.push([stream, event, handler]);
|
|
353
|
+
};
|
|
354
|
+
// stdin end/close/error: TTY/pipe on the input side is gone — nothing left
|
|
355
|
+
// to read from, no point keeping the renderer alive.
|
|
356
|
+
registerStdioDeath(process.stdin, 'end');
|
|
357
|
+
registerStdioDeath(process.stdin, 'close');
|
|
358
|
+
registerStdioDeath(process.stdin, 'error', { requireCode: true });
|
|
359
|
+
// stdout/stderr: 'close' always means the fd is gone (fatal); 'error' is
|
|
360
|
+
// filtered to the whitelist above so resize/EAGAIN noise doesn't kill us.
|
|
361
|
+
registerStdioDeath(process.stdout, 'error', { requireCode: true });
|
|
362
|
+
registerStdioDeath(process.stdout, 'close');
|
|
363
|
+
registerStdioDeath(process.stderr, 'error', { requireCode: true });
|
|
364
|
+
registerStdioDeath(process.stderr, 'close');
|
|
365
|
+
|
|
263
366
|
// exitOnCtrlC:false — App handles Ctrl+C as an interrupt/line-clear so Ink
|
|
264
367
|
// does not exit abruptly. Explicit exits go through /exit or /quit so teardown
|
|
265
368
|
// still restores the cursor, mouse mode, and alternate screen cleanly.
|
|
266
369
|
try {
|
|
267
|
-
const instance = render(<App store={store} />, { exitOnCtrlC: false, maxFps: 120, onRender: makeRenderProfiler() });
|
|
370
|
+
const instance = render(<App store={store} forceOnboarding={forceOnboarding === true} />, { exitOnCtrlC: false, maxFps: 120, onRender: makeRenderProfiler() });
|
|
268
371
|
bootProfile('render:mounted', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
269
372
|
const { waitUntilExit } = instance;
|
|
270
373
|
// [mixdog fork] Hand the ink renderer's drag-selection setter to the store so
|
|
@@ -281,8 +384,15 @@ export async function runTui({ provider, model, toolMode } = {}) {
|
|
|
281
384
|
if (mouseTracking && typeof instance.getWordRectAt === 'function') {
|
|
282
385
|
store.getWordRectAt = instance.getWordRectAt;
|
|
283
386
|
}
|
|
387
|
+
if (mouseTracking && typeof instance.getLineRectAt === 'function') {
|
|
388
|
+
store.getLineRectAt = instance.getLineRectAt;
|
|
389
|
+
}
|
|
284
390
|
await waitUntilExit();
|
|
285
391
|
} finally {
|
|
392
|
+
clearInterval(uiHeartbeatTimer);
|
|
393
|
+
for (const [stream, event, handler] of stdioDeathListeners.splice(0)) {
|
|
394
|
+
try { stream.off(event, handler); } catch { /* ignore */ }
|
|
395
|
+
}
|
|
286
396
|
signalCleanup.uninstall();
|
|
287
397
|
try {
|
|
288
398
|
await disposeStoreOnce('cli-react-exit');
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/keyboard-protocol.mjs — terminal extended-keys capability gate.
|
|
3
|
+
*
|
|
4
|
+
* We do NOT query the terminal. Instead, mirroring Claude Code, we enable the
|
|
5
|
+
* kitty keyboard protocol AND xterm modifyOtherKeys SYNCHRONOUSLY at the moment
|
|
6
|
+
* raw mode turns on (see App.jsx's mount effect), gated by the allowlist below.
|
|
7
|
+
* Writing both enables unconditionally is safe — a terminal honors whichever it
|
|
8
|
+
* implements (Windows Terminal 1.24 has no kitty support but DOES honor
|
|
9
|
+
* modifyOtherKeys), and doing it before the first keypair is read removes the
|
|
10
|
+
* round-trip race that made the first Ctrl+Enter submit instead of inserting a
|
|
11
|
+
* newline.
|
|
12
|
+
*
|
|
13
|
+
* Enable / disable byte sequences (used by App.jsx enable + index.jsx teardown):
|
|
14
|
+
* ENABLE_KITTY_KEYBOARD \x1b[>1u push kitty flags=1 (disambiguate)
|
|
15
|
+
* ENABLE_MODIFY_OTHER_KEYS \x1b[>4;2m xterm modifyOtherKeys level 2
|
|
16
|
+
* POP_KITTY \x1b[<u pop the kitty stack entry
|
|
17
|
+
* DISABLE_MODIFY_OTHER_KEYS \x1b[>4;0m modifyOtherKeys off
|
|
18
|
+
*/
|
|
19
|
+
export const ENABLE_KITTY_KEYBOARD = '\x1b[>1u';
|
|
20
|
+
export const ENABLE_MODIFY_OTHER_KEYS = '\x1b[>4;2m';
|
|
21
|
+
export const POP_KITTY = '\x1b[<u';
|
|
22
|
+
export const DISABLE_MODIFY_OTHER_KEYS = '\x1b[>4;0m';
|
|
23
|
+
|
|
24
|
+
// Allowlist of terminals known to honor kitty and/or xterm modifyOtherKeys.
|
|
25
|
+
// Mirrors Claude Code's supportsExtendedKeys allowlist. VS Code's xterm.js
|
|
26
|
+
// integrated terminal mishandles these sequences, so it is excluded first.
|
|
27
|
+
// Honors the MIXDOG_TUI_EXTENDED_KEYS opt-out (=0) / override (=1).
|
|
28
|
+
export function supportsExtendedKeys() {
|
|
29
|
+
const raw = String(process.env.MIXDOG_TUI_EXTENDED_KEYS ?? '').trim();
|
|
30
|
+
if (/^(0|false|no|off)$/i.test(raw)) return false;
|
|
31
|
+
if (/^(1|true|yes|on)$/i.test(raw)) return true;
|
|
32
|
+
if (process.env.TERM_PROGRAM === 'vscode') return false;
|
|
33
|
+
if (process.env.WT_SESSION) return true; // Windows Terminal
|
|
34
|
+
if (process.env.TERM?.includes('kitty')) return true;
|
|
35
|
+
if (process.env.KITTY_WINDOW_ID) return true;
|
|
36
|
+
if (process.env.TERM_PROGRAM === 'WezTerm') return true;
|
|
37
|
+
if (process.env.TERM_PROGRAM === 'ghostty') return true;
|
|
38
|
+
if (process.env.TERM === 'xterm-ghostty') return true;
|
|
39
|
+
if (process.env.TMUX) return true;
|
|
40
|
+
if (process.env.TERM_PROGRAM === 'iTerm.app') return true;
|
|
41
|
+
return false;
|
|
42
|
+
}
|