mixdog 0.9.47 → 0.9.49
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 +14 -4
- package/scripts/agent-parallel-smoke.mjs +4 -1
- package/scripts/agent-route-batch-test.mjs +40 -0
- package/scripts/code-graph-aggregate-cwd-test.mjs +154 -0
- package/scripts/code-graph-description-contract.mjs +185 -0
- package/scripts/code-graph-disk-hit-test.mjs +55 -0
- package/scripts/code-graph-dispatch-test.mjs +96 -0
- package/scripts/compact-pressure-test.mjs +40 -0
- package/scripts/deferred-tool-loading-test.mjs +233 -0
- package/scripts/execution-completion-dedup-test.mjs +48 -0
- package/scripts/explore-prompt-policy-test.mjs +88 -3
- package/scripts/live-worker-smoke.mjs +68 -16
- package/scripts/memory-core-input-test.mjs +33 -13
- package/scripts/native-edit-wire-test.mjs +152 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +294 -16
- package/scripts/patch-binary-cache-test.mjs +181 -0
- package/scripts/prompt-immediate-render-test.mjs +89 -0
- package/scripts/provider-toolcall-test.mjs +280 -15
- package/scripts/shell-failure-diagnostics-test.mjs +211 -0
- package/scripts/statusline-quota-hysteresis-test.mjs +26 -1
- package/scripts/streaming-tail-window-test.mjs +29 -0
- package/scripts/tool-failures.mjs +21 -3
- package/scripts/tool-smoke.mjs +263 -38
- package/scripts/tool-tui-presentation-test.mjs +17 -1
- package/scripts/tui-perf-run.ps1 +26 -0
- package/scripts/tui-transcript-perf-test.mjs +7 -1
- package/scripts/verify-release-assets-test.mjs +647 -0
- package/scripts/verify-release-assets.mjs +293 -0
- package/scripts/windows-hide-spawn-options-test.mjs +19 -0
- package/src/cli.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +34 -34
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +45 -38
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +13 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +25 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +69 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +12 -12
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +12 -7
- package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +77 -18
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +99 -24
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -11
- package/src/runtime/memory/tool-defs.mjs +1 -2
- package/src/session-runtime/context-status.mjs +25 -16
- package/src/session-runtime/model-route-api.mjs +2 -0
- package/src/session-runtime/runtime-core.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +4 -1
- package/src/session-runtime/tool-catalog.mjs +113 -19
- package/src/session-runtime/tool-defs.mjs +1 -0
- package/src/standalone/agent-tool/helpers.mjs +25 -6
- package/src/standalone/agent-tool.mjs +75 -41
- package/src/tui/App.jsx +4 -0
- package/src/tui/app/input-parsers.mjs +8 -9
- package/src/tui/components/Markdown.jsx +6 -1
- package/src/tui/components/Message.jsx +11 -2
- package/src/tui/components/PromptInput.jsx +19 -21
- package/src/tui/components/Spinner.jsx +4 -4
- package/src/tui/components/StatusLine.jsx +6 -6
- package/src/tui/components/TranscriptItem.jsx +2 -2
- package/src/tui/components/prompt-input/immediate-render.mjs +47 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +14 -9
- package/src/tui/dist/index.mjs +130 -45
- package/src/tui/engine/agent-job-feed.mjs +21 -2
- package/src/tui/engine/turn.mjs +12 -1
- package/src/tui/markdown/measure-rendered-rows.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +20 -0
- package/src/ui/statusline.mjs +5 -5
- package/src/vendor/statusline/src/gateway/session-routes.mjs +22 -10
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* is mode-driven: up while requesting, down otherwise. Input totals hidden.
|
|
16
16
|
* - elided duration formatting ("0:25" after 60s).
|
|
17
17
|
* - mode prop: 'responding' | 'thinking' | 'tool-use' | 'tool-input' |
|
|
18
|
-
* 'requesting' | 'compacting' | 'resuming' (default 'responding').
|
|
18
|
+
* 'requesting' | 'reconnecting' | 'compacting' | 'resuming' (default 'responding').
|
|
19
19
|
*/
|
|
20
20
|
import React, { useRef } from 'react';
|
|
21
21
|
import { Box, Text } from 'ink';
|
|
@@ -40,7 +40,7 @@ const SHOW_TOKENS_AFTER_MS = 30_000;
|
|
|
40
40
|
const THINKING_DELAY_MS = 3000;
|
|
41
41
|
|
|
42
42
|
// One-way shimmer. The tail runs past the final character before restarting.
|
|
43
|
-
const GLIMMER_SPEED_MS = { requesting: 70, compacting: 120, 'auto-clear': 120, resuming: 120, 'tool-use': 120, responding: 120, thinking: 120, 'tool-input': 120 };
|
|
43
|
+
const GLIMMER_SPEED_MS = { requesting: 70, reconnecting: 70, compacting: 120, 'auto-clear': 120, resuming: 120, 'tool-use': 120, responding: 120, thinking: 120, 'tool-input': 120 };
|
|
44
44
|
const GLIMMER_TRAIL = 4;
|
|
45
45
|
const THINKING_GLIMMER_SPEED_MS = 120;
|
|
46
46
|
const THINKING_GLIMMER_TRAIL = 4;
|
|
@@ -230,7 +230,7 @@ export function Spinner({ verb = 'Working', startedAt, outputTokens = 0, tokens
|
|
|
230
230
|
: theme.spinnerGlyph;
|
|
231
231
|
|
|
232
232
|
// --- Verb shimmer (traveling highlight) ---
|
|
233
|
-
if (!displayVerbRef.current) {
|
|
233
|
+
if (!displayVerbRef.current || displayVerbModeRef.current !== mode) {
|
|
234
234
|
displayVerbRef.current = stableModeVerb(mode, verb);
|
|
235
235
|
nextVerbCheckRef.current = nextVerbCheckAt(now);
|
|
236
236
|
}
|
|
@@ -240,7 +240,7 @@ export function Spinner({ verb = 'Working', startedAt, outputTokens = 0, tokens
|
|
|
240
240
|
nextVerbCheckRef.current = nextVerbCheckAt(now);
|
|
241
241
|
}
|
|
242
242
|
const displayVerb = displayVerbRef.current;
|
|
243
|
-
const messageText = `${displayVerb}…`;
|
|
243
|
+
const messageText = mode === 'reconnecting' ? displayVerb : `${displayVerb}…`;
|
|
244
244
|
const messageLen = messageText.length;
|
|
245
245
|
|
|
246
246
|
// Glimmer speed per mode.
|
|
@@ -152,18 +152,18 @@ function localContextPct({
|
|
|
152
152
|
compactBoundaryTokens = 0,
|
|
153
153
|
autoCompactTokenLimit = 0,
|
|
154
154
|
} = {}) {
|
|
155
|
-
const
|
|
155
|
+
const boundary = localNum(compactBoundaryTokens) > 0
|
|
156
156
|
? localNum(compactBoundaryTokens)
|
|
157
157
|
: (localNum(displayContextWindow) > 0
|
|
158
158
|
? localNum(displayContextWindow)
|
|
159
159
|
: (localNum(contextWindow) > 0
|
|
160
160
|
? localNum(contextWindow)
|
|
161
161
|
: (localNum(rawContextWindow) > 0 ? localNum(rawContextWindow) : 200_000)));
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
// The trigger is the denominator used by the pre-send auto-compact decision,
|
|
163
|
+
// so the local boot gauge reaches 100% at the exact same pressure.
|
|
164
|
+
const window = localNum(autoCompactTokenLimit) > 0
|
|
165
|
+
? localNum(autoCompactTokenLimit)
|
|
166
|
+
: boundary;
|
|
167
167
|
const s = stats && typeof stats === 'object' ? stats : {};
|
|
168
168
|
const source = String(s.currentContextSource || '').toLowerCase();
|
|
169
169
|
const estimated = localNum(s.currentEstimatedContextTokens);
|
|
@@ -61,7 +61,7 @@ export function ToolHookDenialCard({ item, columns = 80 }) {
|
|
|
61
61
|
// transcript row (which reads theme.* directly) to re-render. Threading the
|
|
62
62
|
// epoch through Item → AssistantMessage/UserMessage/ToolExecution breaks
|
|
63
63
|
// React.memo's shallow equality on a theme change without a broad refactor.
|
|
64
|
-
export const Item = React.memo(function Item({ item, prevKind, columns, toolOutputExpanded, rightMessage = '', rightTone = 'info', rightMessageWidth = 24, themeEpoch = 0 }) {
|
|
64
|
+
export const Item = React.memo(function Item({ item, prevKind, columns, toolOutputExpanded, rightMessage = '', rightTone = 'info', rightMessageWidth = 24, themeEpoch = 0, streamingWindowRows = 0 }) {
|
|
65
65
|
const hintOnTurnDoneRow = item.kind === 'turndone' || item.kind === 'statusdone';
|
|
66
66
|
let node = null;
|
|
67
67
|
switch (item.kind) {
|
|
@@ -69,7 +69,7 @@ export const Item = React.memo(function Item({ item, prevKind, columns, toolOutp
|
|
|
69
69
|
node = <UserMessage text={item.text} attached={prevKind === 'user'} columns={columns} themeEpoch={themeEpoch} />;
|
|
70
70
|
break;
|
|
71
71
|
case 'assistant':
|
|
72
|
-
node = <AssistantMessage text={item.text} streaming={item.streaming} columns={columns} themeEpoch={themeEpoch} assistantId={item.id} />;
|
|
72
|
+
node = <AssistantMessage text={item.text} streaming={item.streaming} columns={columns} themeEpoch={themeEpoch} assistantId={item.id} streamingWindowRows={streamingWindowRows} />;
|
|
73
73
|
break;
|
|
74
74
|
case 'tool': {
|
|
75
75
|
if (shouldSuppressFullyFailedToolItem(item)) return null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function suppressed(value) {
|
|
2
|
+
return typeof value === 'function' ? value() === true : value === true;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
|
|
6
|
+
if (!throttle || throttle.timer === null) return false;
|
|
7
|
+
clearTimer(throttle.timer);
|
|
8
|
+
throttle.timer = null;
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function schedulePromptImmediateFlush({
|
|
13
|
+
throttle,
|
|
14
|
+
isSuppressed = false,
|
|
15
|
+
flush,
|
|
16
|
+
intervalMs = 16,
|
|
17
|
+
now = Date.now,
|
|
18
|
+
enqueue = queueMicrotask,
|
|
19
|
+
setTimer = setTimeout,
|
|
20
|
+
clearTimer = clearTimeout,
|
|
21
|
+
}) {
|
|
22
|
+
if (!throttle || typeof flush !== 'function') {
|
|
23
|
+
throw new TypeError('schedulePromptImmediateFlush requires throttle state and a flush function');
|
|
24
|
+
}
|
|
25
|
+
if (suppressed(isSuppressed)) {
|
|
26
|
+
cancelPromptImmediateFlush(throttle, clearTimer);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const current = now();
|
|
31
|
+
const elapsed = current - throttle.lastAt;
|
|
32
|
+
if (elapsed >= intervalMs) {
|
|
33
|
+
cancelPromptImmediateFlush(throttle, clearTimer);
|
|
34
|
+
throttle.lastAt = current;
|
|
35
|
+
enqueue(flush);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (throttle.timer !== null) return false;
|
|
39
|
+
|
|
40
|
+
throttle.timer = setTimer(() => {
|
|
41
|
+
throttle.timer = null;
|
|
42
|
+
if (suppressed(isSuppressed)) return;
|
|
43
|
+
throttle.lastAt = now();
|
|
44
|
+
flush();
|
|
45
|
+
}, intervalMs - elapsed);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
@@ -353,19 +353,24 @@ export function toolSearchLoadedSummary(resultText) {
|
|
|
353
353
|
parsed = JSON.parse(String(resultText || ''));
|
|
354
354
|
} catch {
|
|
355
355
|
const text = String(resultText || '');
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
return [
|
|
356
|
+
const loaded = /^Loaded deferred tools:\s*(.+)$/m.exec(text)?.[1] || '';
|
|
357
|
+
const already = /^Already active:\s*(.+)$/m.exec(text)?.[1] || '';
|
|
358
|
+
return [
|
|
359
|
+
...(loaded ? [`Loaded: ${loaded}`] : []),
|
|
360
|
+
...(already ? [`Already active: ${already}`] : []),
|
|
361
|
+
].join(' · ');
|
|
359
362
|
}
|
|
360
363
|
const tools = parsed?.selected?.tools;
|
|
361
364
|
if (!tools || typeof tools !== 'object') return '';
|
|
362
|
-
const
|
|
363
|
-
...(Array.isArray(tools.added) ? tools.added : []),
|
|
364
|
-
...(Array.isArray(tools.already) ? tools.already : []),
|
|
365
|
-
]
|
|
365
|
+
const uniqueNames = (names) => [...new Set((Array.isArray(names) ? names : [])
|
|
366
366
|
.map((name) => String(name || '').trim())
|
|
367
|
-
.filter(Boolean);
|
|
368
|
-
|
|
367
|
+
.filter(Boolean))];
|
|
368
|
+
const loaded = uniqueNames(tools.added);
|
|
369
|
+
const already = uniqueNames(tools.already);
|
|
370
|
+
return [
|
|
371
|
+
...(loaded.length ? [`Loaded: ${loaded.join(', ')}`] : []),
|
|
372
|
+
...(already.length ? [`Already active: ${already.join(', ')}`] : []),
|
|
373
|
+
].join(' · ');
|
|
369
374
|
}
|
|
370
375
|
|
|
371
376
|
export function agentTerminalDetail(status, isError, elapsed, error = '') {
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -3512,7 +3512,7 @@ var STALL_TIMEOUT_MS = 3e3;
|
|
|
3512
3512
|
var STALL_FADE_MS = 2e3;
|
|
3513
3513
|
var SHOW_TOKENS_AFTER_MS = 3e4;
|
|
3514
3514
|
var THINKING_DELAY_MS = 3e3;
|
|
3515
|
-
var GLIMMER_SPEED_MS = { requesting: 70, compacting: 120, "auto-clear": 120, resuming: 120, "tool-use": 120, responding: 120, thinking: 120, "tool-input": 120 };
|
|
3515
|
+
var GLIMMER_SPEED_MS = { requesting: 70, reconnecting: 70, compacting: 120, "auto-clear": 120, resuming: 120, "tool-use": 120, responding: 120, thinking: 120, "tool-input": 120 };
|
|
3516
3516
|
var GLIMMER_TRAIL = 4;
|
|
3517
3517
|
var THINKING_GLIMMER_SPEED_MS = 120;
|
|
3518
3518
|
var THINKING_GLIMMER_TRAIL = 4;
|
|
@@ -3649,7 +3649,7 @@ function Spinner({ verb = "Working", startedAt, outputTokens = 0, tokens = 0, th
|
|
|
3649
3649
|
STALL_RGB,
|
|
3650
3650
|
stalledIntensity
|
|
3651
3651
|
)) : theme.spinnerGlyph;
|
|
3652
|
-
if (!displayVerbRef.current) {
|
|
3652
|
+
if (!displayVerbRef.current || displayVerbModeRef.current !== mode) {
|
|
3653
3653
|
displayVerbRef.current = stableModeVerb(mode, verb);
|
|
3654
3654
|
nextVerbCheckRef.current = nextVerbCheckAt(now);
|
|
3655
3655
|
}
|
|
@@ -3659,7 +3659,7 @@ function Spinner({ verb = "Working", startedAt, outputTokens = 0, tokens = 0, th
|
|
|
3659
3659
|
nextVerbCheckRef.current = nextVerbCheckAt(now);
|
|
3660
3660
|
}
|
|
3661
3661
|
const displayVerb = displayVerbRef.current;
|
|
3662
|
-
const messageText = `${displayVerb}\u2026`;
|
|
3662
|
+
const messageText = mode === "reconnecting" ? displayVerb : `${displayVerb}\u2026`;
|
|
3663
3663
|
const messageLen = messageText.length;
|
|
3664
3664
|
const glimmerSpeed = GLIMMER_SPEED_MS[mode] ?? 200;
|
|
3665
3665
|
const shimmerSpan = Math.max(1, messageLen + GLIMMER_TRAIL);
|
|
@@ -4057,8 +4057,8 @@ function localContextPct({
|
|
|
4057
4057
|
compactBoundaryTokens = 0,
|
|
4058
4058
|
autoCompactTokenLimit = 0
|
|
4059
4059
|
} = {}) {
|
|
4060
|
-
const
|
|
4061
|
-
const window =
|
|
4060
|
+
const boundary = localNum(compactBoundaryTokens) > 0 ? localNum(compactBoundaryTokens) : localNum(displayContextWindow) > 0 ? localNum(displayContextWindow) : localNum(contextWindow) > 0 ? localNum(contextWindow) : localNum(rawContextWindow) > 0 ? localNum(rawContextWindow) : 2e5;
|
|
4061
|
+
const window = localNum(autoCompactTokenLimit) > 0 ? localNum(autoCompactTokenLimit) : boundary;
|
|
4062
4062
|
const s = stats && typeof stats === "object" ? stats : {};
|
|
4063
4063
|
const source = String(s.currentContextSource || "").toLowerCase();
|
|
4064
4064
|
const estimated = localNum(s.currentEstimatedContextTokens);
|
|
@@ -4784,6 +4784,51 @@ function isAnyModifiedEnterSequence(input) {
|
|
|
4784
4784
|
return Boolean(modifyOtherKeys && Number(modifyOtherKeys[1]) - 1 !== 0);
|
|
4785
4785
|
}
|
|
4786
4786
|
|
|
4787
|
+
// src/tui/components/prompt-input/immediate-render.mjs
|
|
4788
|
+
function suppressed(value) {
|
|
4789
|
+
return typeof value === "function" ? value() === true : value === true;
|
|
4790
|
+
}
|
|
4791
|
+
function cancelPromptImmediateFlush(throttle, clearTimer = clearTimeout) {
|
|
4792
|
+
if (!throttle || throttle.timer === null) return false;
|
|
4793
|
+
clearTimer(throttle.timer);
|
|
4794
|
+
throttle.timer = null;
|
|
4795
|
+
return true;
|
|
4796
|
+
}
|
|
4797
|
+
function schedulePromptImmediateFlush({
|
|
4798
|
+
throttle,
|
|
4799
|
+
isSuppressed = false,
|
|
4800
|
+
flush,
|
|
4801
|
+
intervalMs = 16,
|
|
4802
|
+
now = Date.now,
|
|
4803
|
+
enqueue = queueMicrotask,
|
|
4804
|
+
setTimer = setTimeout,
|
|
4805
|
+
clearTimer = clearTimeout
|
|
4806
|
+
}) {
|
|
4807
|
+
if (!throttle || typeof flush !== "function") {
|
|
4808
|
+
throw new TypeError("schedulePromptImmediateFlush requires throttle state and a flush function");
|
|
4809
|
+
}
|
|
4810
|
+
if (suppressed(isSuppressed)) {
|
|
4811
|
+
cancelPromptImmediateFlush(throttle, clearTimer);
|
|
4812
|
+
return false;
|
|
4813
|
+
}
|
|
4814
|
+
const current = now();
|
|
4815
|
+
const elapsed = current - throttle.lastAt;
|
|
4816
|
+
if (elapsed >= intervalMs) {
|
|
4817
|
+
cancelPromptImmediateFlush(throttle, clearTimer);
|
|
4818
|
+
throttle.lastAt = current;
|
|
4819
|
+
enqueue(flush);
|
|
4820
|
+
return true;
|
|
4821
|
+
}
|
|
4822
|
+
if (throttle.timer !== null) return false;
|
|
4823
|
+
throttle.timer = setTimer(() => {
|
|
4824
|
+
throttle.timer = null;
|
|
4825
|
+
if (suppressed(isSuppressed)) return;
|
|
4826
|
+
throttle.lastAt = now();
|
|
4827
|
+
flush();
|
|
4828
|
+
}, intervalMs - elapsed);
|
|
4829
|
+
return true;
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4787
4832
|
// src/tui/components/PromptInput.jsx
|
|
4788
4833
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
4789
4834
|
var IME_LEFT_GUARD_COLUMNS = 0;
|
|
@@ -4832,6 +4877,7 @@ function PromptInput({
|
|
|
4832
4877
|
});
|
|
4833
4878
|
const [, bumpCursorAnchorEpoch] = useState3(0);
|
|
4834
4879
|
const draftRef = useRef4(draft);
|
|
4880
|
+
const interruptActiveRef = useRef4(interruptActive);
|
|
4835
4881
|
const lastReportedValueRef = useRef4(draft.value);
|
|
4836
4882
|
const pasteGenerationRef = useRef4(0);
|
|
4837
4883
|
if (valueRef) valueRef.current = draftRef.current.value;
|
|
@@ -4844,6 +4890,7 @@ function PromptInput({
|
|
|
4844
4890
|
const undoRef = useRef4({ past: [], future: [], lastPushAt: 0, lastValue: null });
|
|
4845
4891
|
const { value, cursor } = draft;
|
|
4846
4892
|
draftRef.current = draft;
|
|
4893
|
+
interruptActiveRef.current = interruptActive;
|
|
4847
4894
|
if (selectionRef) {
|
|
4848
4895
|
const range = selectionRange(draft);
|
|
4849
4896
|
selectionRef.current = range ? { range, text: mask ? "" : draft.value.slice(range.start, range.end) } : null;
|
|
@@ -4859,26 +4906,16 @@ function PromptInput({
|
|
|
4859
4906
|
node = node.parentNode;
|
|
4860
4907
|
}
|
|
4861
4908
|
};
|
|
4862
|
-
const FLUSH_INTERVAL_MS = 16;
|
|
4863
4909
|
const scheduleImmediateFlush = () => {
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
clearTimeout(t.timer);
|
|
4870
|
-
t.timer = null;
|
|
4871
|
-
}
|
|
4872
|
-
t.lastAt = now;
|
|
4873
|
-
queueMicrotask(flushImmediate);
|
|
4874
|
-
} else if (t.timer === null) {
|
|
4875
|
-
t.timer = setTimeout(() => {
|
|
4876
|
-
t.timer = null;
|
|
4877
|
-
t.lastAt = Date.now();
|
|
4878
|
-
flushImmediate();
|
|
4879
|
-
}, FLUSH_INTERVAL_MS - elapsed);
|
|
4880
|
-
}
|
|
4910
|
+
schedulePromptImmediateFlush({
|
|
4911
|
+
throttle: flushThrottleRef.current,
|
|
4912
|
+
isSuppressed: () => interruptActiveRef.current,
|
|
4913
|
+
flush: flushImmediate
|
|
4914
|
+
});
|
|
4881
4915
|
};
|
|
4916
|
+
useEffect3(() => () => {
|
|
4917
|
+
cancelPromptImmediateFlush(flushThrottleRef.current);
|
|
4918
|
+
}, []);
|
|
4882
4919
|
const commitDraft = (next, options = {}) => {
|
|
4883
4920
|
const sameDraft = draftStateEqual(draftRef.current, next);
|
|
4884
4921
|
if (!options.keepPreferredColumn) preferredColumnRef.current = null;
|
|
@@ -8114,14 +8151,14 @@ function parseMemoryCoreRows(text) {
|
|
|
8114
8151
|
currentProjectId = label === "COMMON" ? null : label;
|
|
8115
8152
|
return null;
|
|
8116
8153
|
}
|
|
8117
|
-
const match = raw.match(/^id=(\d+)\s
|
|
8154
|
+
const match = raw.match(/^id=(\d+)\s+(.+?)(?:\s+—\s+(.+))?$/);
|
|
8118
8155
|
if (match) {
|
|
8119
|
-
const [, id,
|
|
8156
|
+
const [, id, element, summary = ""] = match;
|
|
8120
8157
|
return {
|
|
8121
8158
|
value: `core-${id}`,
|
|
8122
8159
|
// Display is summary-first: session injection only ever uses the
|
|
8123
|
-
// summary sentence (buildSessionCoreMemoryPayload), so id/
|
|
8124
|
-
//
|
|
8160
|
+
// summary sentence (buildSessionCoreMemoryPayload), so id/element
|
|
8161
|
+
// are UI noise. The element stays in the hidden fields for
|
|
8125
8162
|
// edit/delete plumbing.
|
|
8126
8163
|
label: `#${id}`,
|
|
8127
8164
|
meta: currentProjectId || "",
|
|
@@ -8129,7 +8166,6 @@ function parseMemoryCoreRows(text) {
|
|
|
8129
8166
|
_line: raw,
|
|
8130
8167
|
_action: "core-entry",
|
|
8131
8168
|
_id: Number(id),
|
|
8132
|
-
_category: category,
|
|
8133
8169
|
_element: element,
|
|
8134
8170
|
_summary: summary || element,
|
|
8135
8171
|
_projectId: currentProjectId,
|
|
@@ -9293,6 +9329,24 @@ var STABLE_PREFIX_LRU_MAX = 32;
|
|
|
9293
9329
|
function streamingLayoutText(text) {
|
|
9294
9330
|
return String(text ?? "").replace(/^\n+|\n+$/g, "");
|
|
9295
9331
|
}
|
|
9332
|
+
function windowPlainStreamingText(text, columns, maxRows) {
|
|
9333
|
+
const value = streamingLayoutText(text);
|
|
9334
|
+
const rowBudget = Math.max(0, Math.floor(Number(maxRows) || 0));
|
|
9335
|
+
if (!value || rowBudget <= 0 || hasMarkdownSyntax(value)) return value;
|
|
9336
|
+
const lines = value.split("\n");
|
|
9337
|
+
const width = Math.max(1, Math.floor(Number(columns) || 80));
|
|
9338
|
+
let rows = 0;
|
|
9339
|
+
let start = lines.length;
|
|
9340
|
+
while (start > 0) {
|
|
9341
|
+
const line = lines[start - 1];
|
|
9342
|
+
const lineRows = Math.max(1, Math.ceil(displayWidth(line) / width));
|
|
9343
|
+
if (rows > 0 && rows + lineRows > rowBudget) break;
|
|
9344
|
+
rows += lineRows;
|
|
9345
|
+
start -= 1;
|
|
9346
|
+
if (rows >= rowBudget) break;
|
|
9347
|
+
}
|
|
9348
|
+
return start > 0 ? lines.slice(start).join("\n") : value;
|
|
9349
|
+
}
|
|
9296
9350
|
function isWhitespaceOnlyText(text) {
|
|
9297
9351
|
return !String(text ?? "").trim();
|
|
9298
9352
|
}
|
|
@@ -9532,7 +9586,7 @@ function measureStreamingMarkdownRenderedRows(text, columns, streamKey) {
|
|
|
9532
9586
|
if (!value) return 1;
|
|
9533
9587
|
const parts = resolveStreamingMarkdownParts(value, streamKey);
|
|
9534
9588
|
if (parts.plain) {
|
|
9535
|
-
return
|
|
9589
|
+
return estimateWrappedRowsFallback(parts.unstableForRender, columns, 3);
|
|
9536
9590
|
}
|
|
9537
9591
|
let rows = 0;
|
|
9538
9592
|
let childCount = 0;
|
|
@@ -18532,7 +18586,7 @@ function Markdown({ children, themeEpoch = 0, trimPartialFences = false, columns
|
|
|
18532
18586
|
function StreamingMarkdown({ children, themeEpoch = 0, columns, streamKey }) {
|
|
18533
18587
|
const parts = resolveStreamingMarkdownParts(children, streamKey);
|
|
18534
18588
|
if (parts.plain) {
|
|
18535
|
-
return /* @__PURE__ */ jsx13(
|
|
18589
|
+
return /* @__PURE__ */ jsx13(Text13, { color: theme.text, wrap: "wrap", children: parts.unstableForRender });
|
|
18536
18590
|
}
|
|
18537
18591
|
return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", gap: 1, children: [
|
|
18538
18592
|
parts.stablePrefix ? /* @__PURE__ */ jsx13(Markdown, { themeEpoch, columns, children: parts.stablePrefix }) : null,
|
|
@@ -18547,15 +18601,17 @@ var AssistantMessage = React14.memo(function AssistantMessage2({
|
|
|
18547
18601
|
streaming = false,
|
|
18548
18602
|
columns = 80,
|
|
18549
18603
|
themeEpoch = 0,
|
|
18550
|
-
assistantId
|
|
18604
|
+
assistantId,
|
|
18605
|
+
streamingWindowRows = 0
|
|
18551
18606
|
}) {
|
|
18552
18607
|
React14.useEffect(() => {
|
|
18553
18608
|
if (!streaming && assistantId) resetStreamingMarkdownStablePrefix(assistantId);
|
|
18554
18609
|
}, [streaming, assistantId]);
|
|
18555
18610
|
const bodyWidth = assistantBodyWidth(columns);
|
|
18611
|
+
const renderText = streaming && streamingWindowRows > 0 ? windowPlainStreamingText(text, bodyWidth, streamingWindowRows) : text;
|
|
18556
18612
|
return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "row", marginTop: 1, children: [
|
|
18557
18613
|
/* @__PURE__ */ jsx14(Box12, { flexShrink: 0, minWidth: 2, children: /* @__PURE__ */ jsx14(Text14, { color: theme.text, children: TURN_MARKER }) }),
|
|
18558
|
-
/* @__PURE__ */ jsx14(Box12, { flexDirection: "column", flexShrink: 0, width: bodyWidth, children: streaming ? /* @__PURE__ */ jsx14(StreamingMarkdown, { themeEpoch, columns: bodyWidth, streamKey: assistantId, children:
|
|
18614
|
+
/* @__PURE__ */ jsx14(Box12, { flexDirection: "column", flexShrink: 0, width: bodyWidth, children: streaming ? /* @__PURE__ */ jsx14(StreamingMarkdown, { themeEpoch, columns: bodyWidth, streamKey: assistantId, children: renderText }) : /* @__PURE__ */ jsx14(Markdown, { themeEpoch, columns: bodyWidth, children: text }) })
|
|
18559
18615
|
] });
|
|
18560
18616
|
});
|
|
18561
18617
|
var UserMessage = React14.memo(function UserMessage2({ text, attached = false, columns, themeEpoch = 0 }) {
|
|
@@ -19009,17 +19065,22 @@ function toolSearchLoadedSummary(resultText) {
|
|
|
19009
19065
|
parsed = JSON.parse(String(resultText || ""));
|
|
19010
19066
|
} catch {
|
|
19011
19067
|
const text = String(resultText || "");
|
|
19012
|
-
const
|
|
19013
|
-
|
|
19014
|
-
return [
|
|
19068
|
+
const loaded2 = /^Loaded deferred tools:\s*(.+)$/m.exec(text)?.[1] || "";
|
|
19069
|
+
const already2 = /^Already active:\s*(.+)$/m.exec(text)?.[1] || "";
|
|
19070
|
+
return [
|
|
19071
|
+
...loaded2 ? [`Loaded: ${loaded2}`] : [],
|
|
19072
|
+
...already2 ? [`Already active: ${already2}`] : []
|
|
19073
|
+
].join(" \xB7 ");
|
|
19015
19074
|
}
|
|
19016
19075
|
const tools = parsed?.selected?.tools;
|
|
19017
19076
|
if (!tools || typeof tools !== "object") return "";
|
|
19018
|
-
const
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
19022
|
-
|
|
19077
|
+
const uniqueNames = (names) => [...new Set((Array.isArray(names) ? names : []).map((name) => String(name || "").trim()).filter(Boolean))];
|
|
19078
|
+
const loaded = uniqueNames(tools.added);
|
|
19079
|
+
const already = uniqueNames(tools.already);
|
|
19080
|
+
return [
|
|
19081
|
+
...loaded.length ? [`Loaded: ${loaded.join(", ")}`] : [],
|
|
19082
|
+
...already.length ? [`Already active: ${already.join(", ")}`] : []
|
|
19083
|
+
].join(" \xB7 ");
|
|
19023
19084
|
}
|
|
19024
19085
|
function agentTerminalDetail(status, isError, elapsed, error = "") {
|
|
19025
19086
|
const failureDetail = isError && error ? backgroundTaskFailureStatusLabel(status, error, { surface: "agent" }) : "";
|
|
@@ -19424,7 +19485,7 @@ function ToolHookDenialCard({ item, columns = 80 }) {
|
|
|
19424
19485
|
] }) : null
|
|
19425
19486
|
] });
|
|
19426
19487
|
}
|
|
19427
|
-
var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpanded, rightMessage = "", rightTone = "info", rightMessageWidth = 24, themeEpoch = 0 }) {
|
|
19488
|
+
var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpanded, rightMessage = "", rightTone = "info", rightMessageWidth = 24, themeEpoch = 0, streamingWindowRows = 0 }) {
|
|
19428
19489
|
const hintOnTurnDoneRow = item.kind === "turndone" || item.kind === "statusdone";
|
|
19429
19490
|
let node = null;
|
|
19430
19491
|
switch (item.kind) {
|
|
@@ -19432,7 +19493,7 @@ var Item = React19.memo(function Item2({ item, prevKind, columns, toolOutputExpa
|
|
|
19432
19493
|
node = /* @__PURE__ */ jsx19(UserMessage, { text: item.text, attached: prevKind === "user", columns, themeEpoch });
|
|
19433
19494
|
break;
|
|
19434
19495
|
case "assistant":
|
|
19435
|
-
node = /* @__PURE__ */ jsx19(AssistantMessage, { text: item.text, streaming: item.streaming, columns, themeEpoch, assistantId: item.id });
|
|
19496
|
+
node = /* @__PURE__ */ jsx19(AssistantMessage, { text: item.text, streaming: item.streaming, columns, themeEpoch, assistantId: item.id, streamingWindowRows });
|
|
19436
19497
|
break;
|
|
19437
19498
|
case "tool": {
|
|
19438
19499
|
if (shouldSuppressFullyFailedToolItem(item)) return null;
|
|
@@ -21712,7 +21773,8 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
|
|
|
21712
21773
|
rightMessage: attachOverlayHint ? inputHint : "",
|
|
21713
21774
|
rightTone: attachOverlayHint ? inputHintTone : "info",
|
|
21714
21775
|
rightMessageWidth: attachOverlayHint ? guardHintWidth || transientStatusWidth || 24 : 24,
|
|
21715
|
-
themeEpoch: state.themeEpoch || 0
|
|
21776
|
+
themeEpoch: state.themeEpoch || 0,
|
|
21777
|
+
streamingWindowRows: transcriptTailPinned && item.id === state.streamingTail?.id ? transcriptContentHeight + TRANSCRIPT_WINDOW_TAIL_OVERSCAN_ROWS : 0
|
|
21716
21778
|
}
|
|
21717
21779
|
);
|
|
21718
21780
|
return measureRef ? /* @__PURE__ */ jsx20(Box18, { ref: measureRef, flexDirection: "column", flexShrink: 0, children: itemNode }, item.id) : /* @__PURE__ */ jsx20(React20.Fragment, { children: itemNode }, item.id);
|
|
@@ -23520,6 +23582,9 @@ function createAgentJobFeed({
|
|
|
23520
23582
|
const terminalExecutionNotificationKeys = /* @__PURE__ */ new Set();
|
|
23521
23583
|
const terminalExecutionResponseKeys = /* @__PURE__ */ new Set();
|
|
23522
23584
|
const executionNotificationKeys = /* @__PURE__ */ new Map();
|
|
23585
|
+
const AGENT_STATUS_COALESCE_MS = 16;
|
|
23586
|
+
let agentStatusRefreshTimer = null;
|
|
23587
|
+
let agentStatusRefreshForce = false;
|
|
23523
23588
|
const clearExecutionDedupState = () => {
|
|
23524
23589
|
displayedExecutionNotificationKeys.clear();
|
|
23525
23590
|
displayedExecutionResponseStates.clear();
|
|
@@ -23668,7 +23733,16 @@ function createAgentJobFeed({
|
|
|
23668
23733
|
if (!parsed?.taskId) return;
|
|
23669
23734
|
const status = String(parsed.status || "").toLowerCase();
|
|
23670
23735
|
const terminal = /^(completed|complete|done|success|succeeded|ok|failed|error|timeout|killed|cancelled|canceled|denied)$/.test(status);
|
|
23671
|
-
|
|
23736
|
+
agentStatusRefreshForce = agentStatusRefreshForce || terminal;
|
|
23737
|
+
if (agentStatusRefreshTimer) return;
|
|
23738
|
+
agentStatusRefreshTimer = setTimeout(() => {
|
|
23739
|
+
agentStatusRefreshTimer = null;
|
|
23740
|
+
if (getDisposed()) return;
|
|
23741
|
+
const force = agentStatusRefreshForce;
|
|
23742
|
+
agentStatusRefreshForce = false;
|
|
23743
|
+
set(agentStatusState(force ? { force: true } : void 0));
|
|
23744
|
+
}, AGENT_STATUS_COALESCE_MS);
|
|
23745
|
+
agentStatusRefreshTimer.unref?.();
|
|
23672
23746
|
}
|
|
23673
23747
|
function subscribeRuntimeNotifications() {
|
|
23674
23748
|
if (typeof runtime.onNotification !== "function") return null;
|
|
@@ -23780,6 +23854,9 @@ function createAgentJobFeed({
|
|
|
23780
23854
|
try {
|
|
23781
23855
|
unsubscribe?.();
|
|
23782
23856
|
} finally {
|
|
23857
|
+
if (agentStatusRefreshTimer) clearTimeout(agentStatusRefreshTimer);
|
|
23858
|
+
agentStatusRefreshTimer = null;
|
|
23859
|
+
agentStatusRefreshForce = false;
|
|
23783
23860
|
clearExecutionDedupState();
|
|
23784
23861
|
}
|
|
23785
23862
|
};
|
|
@@ -25460,8 +25537,9 @@ function createRunTurn(bag) {
|
|
|
25460
25537
|
label: compactEventLabel(event),
|
|
25461
25538
|
detail: compactEventDetail(event)
|
|
25462
25539
|
});
|
|
25540
|
+
syncContextStats({ allowEstimated: true });
|
|
25463
25541
|
},
|
|
25464
|
-
onStageChange: async (stage) => {
|
|
25542
|
+
onStageChange: async (stage, detail = null) => {
|
|
25465
25543
|
if (!markTurnProgress(`stage:${String(stage || "")}`)) return;
|
|
25466
25544
|
if (!getState().spinner) return;
|
|
25467
25545
|
const value = String(stage || "");
|
|
@@ -25484,6 +25562,13 @@ function createRunTurn(bag) {
|
|
|
25484
25562
|
await yieldToRenderer();
|
|
25485
25563
|
return;
|
|
25486
25564
|
}
|
|
25565
|
+
if (value === "reconnecting") {
|
|
25566
|
+
compactingActive = false;
|
|
25567
|
+
const retryVerb = String(detail?.message || "Reconnecting");
|
|
25568
|
+
set({ spinner: { ...getState().spinner, mode: "reconnecting", verb: retryVerb } });
|
|
25569
|
+
await yieldToRenderer();
|
|
25570
|
+
return;
|
|
25571
|
+
}
|
|
25487
25572
|
if (value === "requesting" || value === "streaming") compactingActive = false;
|
|
25488
25573
|
const mode = value === "requesting" ? "requesting" : value === "streaming" ? getState().spinner.thinking ? "thinking" : "responding" : null;
|
|
25489
25574
|
if (!mode || getState().spinner.mode === mode) return;
|
|
@@ -79,6 +79,9 @@ export function createAgentJobFeed({
|
|
|
79
79
|
const terminalExecutionNotificationKeys = new Set();
|
|
80
80
|
const terminalExecutionResponseKeys = new Set();
|
|
81
81
|
const executionNotificationKeys = new Map();
|
|
82
|
+
const AGENT_STATUS_COALESCE_MS = 16;
|
|
83
|
+
let agentStatusRefreshTimer = null;
|
|
84
|
+
let agentStatusRefreshForce = false;
|
|
82
85
|
|
|
83
86
|
const clearExecutionDedupState = () => {
|
|
84
87
|
displayedExecutionNotificationKeys.clear();
|
|
@@ -261,7 +264,16 @@ export function createAgentJobFeed({
|
|
|
261
264
|
if (!parsed?.taskId) return;
|
|
262
265
|
const status = String(parsed.status || '').toLowerCase();
|
|
263
266
|
const terminal = /^(completed|complete|done|success|succeeded|ok|failed|error|timeout|killed|cancelled|canceled|denied)$/.test(status);
|
|
264
|
-
|
|
267
|
+
agentStatusRefreshForce = agentStatusRefreshForce || terminal;
|
|
268
|
+
if (agentStatusRefreshTimer) return;
|
|
269
|
+
agentStatusRefreshTimer = setTimeout(() => {
|
|
270
|
+
agentStatusRefreshTimer = null;
|
|
271
|
+
if (getDisposed()) return;
|
|
272
|
+
const force = agentStatusRefreshForce;
|
|
273
|
+
agentStatusRefreshForce = false;
|
|
274
|
+
set(agentStatusState(force ? { force: true } : undefined));
|
|
275
|
+
}, AGENT_STATUS_COALESCE_MS);
|
|
276
|
+
agentStatusRefreshTimer.unref?.();
|
|
265
277
|
}
|
|
266
278
|
|
|
267
279
|
function subscribeRuntimeNotifications() {
|
|
@@ -399,7 +411,14 @@ export function createAgentJobFeed({
|
|
|
399
411
|
return true;
|
|
400
412
|
});
|
|
401
413
|
return () => {
|
|
402
|
-
try {
|
|
414
|
+
try {
|
|
415
|
+
unsubscribe?.();
|
|
416
|
+
} finally {
|
|
417
|
+
if (agentStatusRefreshTimer) clearTimeout(agentStatusRefreshTimer);
|
|
418
|
+
agentStatusRefreshTimer = null;
|
|
419
|
+
agentStatusRefreshForce = false;
|
|
420
|
+
clearExecutionDedupState();
|
|
421
|
+
}
|
|
403
422
|
};
|
|
404
423
|
}
|
|
405
424
|
|
package/src/tui/engine/turn.mjs
CHANGED
|
@@ -1007,8 +1007,12 @@ export function createRunTurn(bag) {
|
|
|
1007
1007
|
label: compactEventLabel(event),
|
|
1008
1008
|
detail: compactEventDetail(event),
|
|
1009
1009
|
});
|
|
1010
|
+
// Compaction itself remains owned by the pre-provider-send pass.
|
|
1011
|
+
// This event only refreshes the gauge from the already-mutated
|
|
1012
|
+
// transcript before another render can show stale pressure.
|
|
1013
|
+
syncContextStats({ allowEstimated: true });
|
|
1010
1014
|
},
|
|
1011
|
-
onStageChange: async (stage) => {
|
|
1015
|
+
onStageChange: async (stage, detail = null) => {
|
|
1012
1016
|
if (!markTurnProgress(`stage:${String(stage || '')}`)) return;
|
|
1013
1017
|
if (!getState().spinner) return;
|
|
1014
1018
|
const value = String(stage || '');
|
|
@@ -1031,6 +1035,13 @@ export function createRunTurn(bag) {
|
|
|
1031
1035
|
await yieldToRenderer();
|
|
1032
1036
|
return;
|
|
1033
1037
|
}
|
|
1038
|
+
if (value === 'reconnecting') {
|
|
1039
|
+
compactingActive = false;
|
|
1040
|
+
const retryVerb = String(detail?.message || 'Reconnecting');
|
|
1041
|
+
set({ spinner: { ...getState().spinner, mode: 'reconnecting', verb: retryVerb } });
|
|
1042
|
+
await yieldToRenderer();
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1034
1045
|
if (value === 'requesting' || value === 'streaming') compactingActive = false;
|
|
1035
1046
|
const mode = value === 'requesting'
|
|
1036
1047
|
? 'requesting'
|
|
@@ -67,7 +67,7 @@ export function measureStreamingMarkdownRenderedRows(text, columns, streamKey) {
|
|
|
67
67
|
if (!value) return 1;
|
|
68
68
|
const parts = resolveStreamingMarkdownParts(value, streamKey);
|
|
69
69
|
if (parts.plain) {
|
|
70
|
-
return
|
|
70
|
+
return estimateWrappedRowsFallback(parts.unstableForRender, columns, 3);
|
|
71
71
|
}
|
|
72
72
|
let rows = 0;
|
|
73
73
|
let childCount = 0;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { marked } from 'marked';
|
|
6
6
|
import { configureMarked, hasMarkdownSyntax } from './render-ansi.mjs';
|
|
7
7
|
import { trimPartialClosingFences, findOpenFenceStart } from './stream-fence.mjs';
|
|
8
|
+
import { displayWidth } from '../display-width.mjs';
|
|
8
9
|
|
|
9
10
|
const stablePrefixByStreamKey = new Map();
|
|
10
11
|
// Reuse the current normalized-text split across measure → render → harvest.
|
|
@@ -16,6 +17,25 @@ export function streamingLayoutText(text) {
|
|
|
16
17
|
return String(text ?? '').replace(/^\n+|\n+$/g, '');
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
export function windowPlainStreamingText(text, columns, maxRows) {
|
|
21
|
+
const value = streamingLayoutText(text);
|
|
22
|
+
const rowBudget = Math.max(0, Math.floor(Number(maxRows) || 0));
|
|
23
|
+
if (!value || rowBudget <= 0 || hasMarkdownSyntax(value)) return value;
|
|
24
|
+
const lines = value.split('\n');
|
|
25
|
+
const width = Math.max(1, Math.floor(Number(columns) || 80));
|
|
26
|
+
let rows = 0;
|
|
27
|
+
let start = lines.length;
|
|
28
|
+
while (start > 0) {
|
|
29
|
+
const line = lines[start - 1];
|
|
30
|
+
const lineRows = Math.max(1, Math.ceil(displayWidth(line) / width));
|
|
31
|
+
if (rows > 0 && rows + lineRows > rowBudget) break;
|
|
32
|
+
rows += lineRows;
|
|
33
|
+
start -= 1;
|
|
34
|
+
if (rows >= rowBudget) break;
|
|
35
|
+
}
|
|
36
|
+
return start > 0 ? lines.slice(start).join('\n') : value;
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
function isWhitespaceOnlyText(text) {
|
|
20
40
|
return !String(text ?? '').trim();
|
|
21
41
|
}
|
package/src/ui/statusline.mjs
CHANGED
|
@@ -189,7 +189,7 @@ function displayContextBoundary({
|
|
|
189
189
|
return modelContextWindow('', '', boundarySeed);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
function resolveContextUsedPct({
|
|
192
|
+
export function resolveContextUsedPct({
|
|
193
193
|
provider = '',
|
|
194
194
|
model = '',
|
|
195
195
|
stats = null,
|
|
@@ -210,10 +210,10 @@ function resolveContextUsedPct({
|
|
|
210
210
|
autoCompactTokenLimit,
|
|
211
211
|
compact,
|
|
212
212
|
});
|
|
213
|
-
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
213
|
+
const trigger = num(autoCompactTokenLimit);
|
|
214
|
+
// A resolved trigger means this is the runtime compaction gauge: keep both
|
|
215
|
+
// operands local so a gateway boundary percentage cannot replace it.
|
|
216
|
+
if (trigger > 0) return (numerator / trigger) * 100;
|
|
217
217
|
const gatewayRawPct = gatewayStatus?.contextUsedPct;
|
|
218
218
|
if (
|
|
219
219
|
gatewayStatus
|