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
|
@@ -5,8 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
import { parseToolArgs } from '../runtime/shared/tool-surface.mjs';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Anchor to the orchestrator's actual denial envelope:
|
|
9
|
+
// `Error: tool "<name>" denied by hook[: ...]`
|
|
10
|
+
// `Error: tool "<name>" denied by hook: approval required but no approval UI is available`
|
|
11
|
+
// Matching the bare phrase anywhere in the body produced FALSE POSITIVES when a
|
|
12
|
+
// normal (successful) tool result merely CONTAINED the text — e.g. a read/grep
|
|
13
|
+
// over this very file or the hook source. The denial is always an Error
|
|
14
|
+
// envelope emitted at the START of the result, so anchor on that prefix.
|
|
15
|
+
const HOOK_DENIAL_RE = /^Error:\s*tool\s*"[^"]*"\s*denied by hook\b/im;
|
|
16
|
+
const APPROVAL_UNAVAILABLE_RE = /denied by hook:\s*approval required but no approval UI is available/i;
|
|
10
17
|
|
|
11
18
|
export function toolItemResultText(item) {
|
|
12
19
|
if (!item) return '';
|
|
@@ -26,6 +33,10 @@ export function isHookApprovalDenialToolResult(text) {
|
|
|
26
33
|
|
|
27
34
|
export function isHookApprovalDenialToolItem(item) {
|
|
28
35
|
if (!item || item.kind !== 'tool') return false;
|
|
36
|
+
// A genuine hook denial is always surfaced as an ERROR result. Gating on
|
|
37
|
+
// isError stops a successful result whose body happens to contain the denial
|
|
38
|
+
// phrase (e.g. reading the hook source) from rendering a phantom "Denied".
|
|
39
|
+
if (!item.isError) return false;
|
|
29
40
|
return isHookApprovalDenialToolResult(toolItemResultText(item));
|
|
30
41
|
}
|
|
31
42
|
|
package/src/ui/markdown.mjs
CHANGED
|
@@ -137,7 +137,7 @@ function renderLine(line, width) {
|
|
|
137
137
|
// Blockquote (possibly nested).
|
|
138
138
|
const q = /^(\s*>+)\s?(.*)$/.exec(line);
|
|
139
139
|
if (q) {
|
|
140
|
-
return dim(
|
|
140
|
+
return dim('│ ') + italic(renderInline(q[2]));
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// Bullet list.
|
package/src/ui/model-display.mjs
CHANGED
|
@@ -74,13 +74,13 @@ export function canonicalModelDisplay(model, provider) {
|
|
|
74
74
|
return `Grok ${grok[1].split('-').map(titleModelPart).filter(Boolean).join(' ')}`;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
const claudeLegacy = raw.match(/^claude-(\d+)(?:-(\d+))?-(opus|sonnet|haiku)(?:-|$)/i);
|
|
77
|
+
const claudeLegacy = raw.match(/^claude-(\d+)(?:-(\d+))?-(opus|sonnet|haiku|fable)(?:-|$)/i);
|
|
78
78
|
if (claudeLegacy) {
|
|
79
79
|
const version = `${claudeLegacy[1]}${claudeLegacy[2] ? `.${claudeLegacy[2]}` : ''}`;
|
|
80
80
|
return `Claude ${titleModelPart(claudeLegacy[3])} ${version}`;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const claude = raw.match(/^claude-(opus|sonnet|haiku)-(.+)$/i);
|
|
83
|
+
const claude = raw.match(/^claude-(opus|sonnet|haiku|fable)-(.+)$/i);
|
|
84
84
|
if (claude) {
|
|
85
85
|
return `Claude ${titleModelPart(claude[1])} ${claude[2].replace(/-/g, '.')}`;
|
|
86
86
|
}
|
package/src/ui/statusline.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { bold, colorEnabled, rgb } from './ansi.mjs';
|
|
|
16
16
|
import { displayModelName, shortenModelName } from './model-display.mjs';
|
|
17
17
|
import { createSessionStats } from './session-stats.mjs';
|
|
18
18
|
import { forEachSessionRuntime } from '../runtime/agent/orchestrator/session/manager.mjs';
|
|
19
|
-
import {
|
|
19
|
+
import { listHiddenAgentNames } from '../runtime/agent/orchestrator/internal-agents.mjs';
|
|
20
20
|
import { getModelMetadataSync } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
|
|
21
21
|
import { readCachedOAuthUsageSnapshot } from '../runtime/agent/orchestrator/providers/oauth-usage.mjs';
|
|
22
22
|
import { readCachedOpenCodeGoUsageSnapshot } from '../runtime/agent/orchestrator/providers/opencode-go-usage.mjs';
|
|
@@ -61,11 +61,11 @@ const L2_SPINNER_FRAME_MS = 120;
|
|
|
61
61
|
// replaces them. Snapshots from a previous launch stay hidden during boot so the
|
|
62
62
|
// statusline starts empty until the current session captures usage once.
|
|
63
63
|
const STATUSLINE_PROCESS_STARTED_AT_MS = Date.now() - Math.floor((Number(process.uptime?.()) || 0) * 1000);
|
|
64
|
-
const
|
|
64
|
+
const DEFAULT_HIDDEN_STATUSLINE_AGENTS = Object.freeze(['explorer', 'cycle1-agent', 'cycle2-agent', 'cycle3-agent', 'scheduler-task', 'webhook-handler']);
|
|
65
65
|
let _shellJobsSegmentCache = { ownerPid: 0, at: 0, value: { count: 0, elapsedLabel: '' } };
|
|
66
66
|
let _gatewayQuotaStatusCache = { key: '', at: 0, value: null };
|
|
67
67
|
let _fallbackQuotaStatusCache = { key: '', at: 0, value: null };
|
|
68
|
-
let
|
|
68
|
+
let _hiddenStatuslineAgents = null;
|
|
69
69
|
// Option A boot gate: the L1 usage/quota segment stays fully empty until THIS
|
|
70
70
|
// process has captured its FIRST confirmed (current-process) OAuth usage
|
|
71
71
|
// snapshot for THAT provider. The latch is monotonic PER PROVIDER — once a
|
|
@@ -211,7 +211,7 @@ function resolveContextUsedPct({
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
function normalizeAgentWorkerForStatusline(worker = {}) {
|
|
214
|
-
const tag = String(worker.tag || worker.
|
|
214
|
+
const tag = String(worker.tag || worker.agent || worker.name || '').trim();
|
|
215
215
|
if (!tag) return null;
|
|
216
216
|
const statusText = String(worker.stage || worker.status || '').toLowerCase();
|
|
217
217
|
const status = isTerminalBridgeStatus(statusText) ? 'idle' : 'running';
|
|
@@ -219,7 +219,7 @@ function normalizeAgentWorkerForStatusline(worker = {}) {
|
|
|
219
219
|
tag,
|
|
220
220
|
status,
|
|
221
221
|
startedAtMs: timeMs(worker.startedAt || worker.startTime || worker.createdAt),
|
|
222
|
-
|
|
222
|
+
agent: worker.agent || null,
|
|
223
223
|
stage: worker.stage || worker.status || null,
|
|
224
224
|
sessionId: worker.sessionId || null,
|
|
225
225
|
provider: worker.provider || null,
|
|
@@ -231,7 +231,7 @@ function normalizeAgentJobForStatusline(job = {}) {
|
|
|
231
231
|
const statusText = String(job.status || job.stage || '').toLowerCase();
|
|
232
232
|
if (!statusText) return null;
|
|
233
233
|
const taskId = String(job.task_id || job.taskId || '').trim();
|
|
234
|
-
const tag = String(job.tag || job.
|
|
234
|
+
const tag = String(job.tag || job.agent || job.type || taskId || '').trim();
|
|
235
235
|
if (!tag && !taskId) return null;
|
|
236
236
|
const startedAtMs = timeMs(job.startedAt);
|
|
237
237
|
const finishedAtMs = timeMs(job.finishedAt);
|
|
@@ -243,7 +243,7 @@ function normalizeAgentJobForStatusline(job = {}) {
|
|
|
243
243
|
finalStatus: statusText,
|
|
244
244
|
startedAtMs,
|
|
245
245
|
finishedAtMs,
|
|
246
|
-
|
|
246
|
+
agent: job.agent || null,
|
|
247
247
|
stage: job.stage || job.workerStatus || job.status || null,
|
|
248
248
|
sessionId: job.sessionId || null,
|
|
249
249
|
provider: job.provider || null,
|
|
@@ -256,7 +256,7 @@ function normalizeAgentJobForStatusline(job = {}) {
|
|
|
256
256
|
taskId,
|
|
257
257
|
status: 'running',
|
|
258
258
|
startedAtMs,
|
|
259
|
-
|
|
259
|
+
agent: job.agent || null,
|
|
260
260
|
stage: job.stage || job.workerStatus || job.status || null,
|
|
261
261
|
sessionId: job.sessionId || null,
|
|
262
262
|
provider: job.provider || null,
|
|
@@ -387,10 +387,9 @@ function renderNativeStatusline({
|
|
|
387
387
|
|
|
388
388
|
const agentPayload = agentStatuslinePayload([
|
|
389
389
|
...(Array.isArray(agentWorkers) ? agentWorkers : []),
|
|
390
|
-
...
|
|
390
|
+
...activeHiddenAgentWorkers({ sessionId, clientHostPid }),
|
|
391
391
|
], agentJobs);
|
|
392
|
-
const {
|
|
393
|
-
if (maintenance.length) addL1(maintenance.join(' '));
|
|
392
|
+
const { runningWorkers } = classifyAgentWorkers(agentPayload.workers);
|
|
394
393
|
const shellStatus = shellJobsStatus({ clientHostPid });
|
|
395
394
|
|
|
396
395
|
const spinnerNow = Date.now();
|
|
@@ -688,30 +687,30 @@ function classifyAgentWorkers(workers = []) {
|
|
|
688
687
|
}
|
|
689
688
|
|
|
690
689
|
function hiddenWorkerLabel(worker = {}) {
|
|
691
|
-
const
|
|
690
|
+
const agent = String(worker?.agent || '').trim();
|
|
692
691
|
const tag = String(worker?.tag || '').trim();
|
|
693
|
-
return maintenanceLabel(
|
|
692
|
+
return maintenanceLabel(agent) || (!agent ? maintenanceLabel(tag) : '');
|
|
694
693
|
}
|
|
695
694
|
|
|
696
|
-
function
|
|
697
|
-
if (
|
|
698
|
-
const
|
|
695
|
+
function hiddenStatuslineAgents() {
|
|
696
|
+
if (_hiddenStatuslineAgents) return _hiddenStatuslineAgents;
|
|
697
|
+
const agents = new Set(DEFAULT_HIDDEN_STATUSLINE_AGENTS);
|
|
699
698
|
try {
|
|
700
|
-
for (const
|
|
701
|
-
const clean = String(
|
|
702
|
-
if (clean)
|
|
699
|
+
for (const agent of listHiddenAgentNames()) {
|
|
700
|
+
const clean = String(agent || '').trim();
|
|
701
|
+
if (clean) agents.add(clean);
|
|
703
702
|
}
|
|
704
703
|
} catch {}
|
|
705
|
-
|
|
706
|
-
return
|
|
704
|
+
_hiddenStatuslineAgents = agents;
|
|
705
|
+
return agents;
|
|
707
706
|
}
|
|
708
707
|
|
|
709
708
|
function isActiveHiddenStatus(statusText) {
|
|
710
709
|
return /^(connecting|requesting|streaming|tool_running|running)$/i.test(String(statusText || '').trim());
|
|
711
710
|
}
|
|
712
711
|
|
|
713
|
-
function
|
|
714
|
-
const
|
|
712
|
+
function activeHiddenAgentWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
|
|
713
|
+
const agents = hiddenStatuslineAgents();
|
|
715
714
|
const ownerPid = positiveInt(clientHostPid);
|
|
716
715
|
const ownerSessionId = String(sessionId || '').trim();
|
|
717
716
|
const rows = [];
|
|
@@ -720,8 +719,8 @@ function activeHiddenRoleWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
|
|
|
720
719
|
if (!entry || entry.closed === true) continue;
|
|
721
720
|
const session = entry.session || null;
|
|
722
721
|
if (!session || session.closed === true) continue;
|
|
723
|
-
const
|
|
724
|
-
if (!
|
|
722
|
+
const agent = String(session?.agent || '').trim();
|
|
723
|
+
if (!agent || !agents.has(agent)) continue;
|
|
725
724
|
const id = session?.id || runtimeSessionId || null;
|
|
726
725
|
if (ownerSessionId && id === ownerSessionId) continue;
|
|
727
726
|
const sessionOwnerId = String(session?.ownerSessionId || '').trim();
|
|
@@ -732,8 +731,8 @@ function activeHiddenRoleWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
|
|
|
732
731
|
const status = String(session?.status || stage || '').trim().toLowerCase();
|
|
733
732
|
if (!isActiveHiddenStatus(stage || status)) continue;
|
|
734
733
|
rows.push({
|
|
735
|
-
tag: String(session?.agentTag || `${
|
|
736
|
-
|
|
734
|
+
tag: String(session?.agentTag || `${agent}:${id || rows.length}`).trim(),
|
|
735
|
+
agent,
|
|
737
736
|
status: 'running',
|
|
738
737
|
stage: stage || status || 'running',
|
|
739
738
|
sessionId: id,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
readLatestGatewayHostRoute,
|
|
12
12
|
readGatewaySessionRoute,
|
|
13
13
|
} from '../src/gateway/session-routes.mjs';
|
|
14
|
+
import { compactBoundaryDenominator } from '../src/gateway/route-meta.mjs';
|
|
14
15
|
|
|
15
16
|
function positiveInt(value) {
|
|
16
17
|
const n = parseInt(String(value || ''), 10);
|
|
@@ -278,16 +279,8 @@ function resolveStatusAutoCompactTokenLimit(configured, active = {}, lastCompact
|
|
|
278
279
|
export const _resolveStatusAutoCompactTokenLimit = resolveStatusAutoCompactTokenLimit;
|
|
279
280
|
|
|
280
281
|
export function compactBoundaryForStatus(routeInfo = {}, compact = null) {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
const budgetWindow = num(compact?.budgetWindow, 0);
|
|
284
|
-
const rawContextWindow = num(routeInfo?.rawContextWindow ?? compact?.rawContextWindow, 0);
|
|
285
|
-
if (compactLimit > 0 && contextWindow > 0) return Math.min(compactLimit, contextWindow);
|
|
286
|
-
if (compactLimit > 0) return compactLimit;
|
|
287
|
-
if (budgetWindow > 0 && contextWindow > 0) return Math.min(budgetWindow, contextWindow);
|
|
288
|
-
if (budgetWindow > 0) return budgetWindow;
|
|
289
|
-
if (contextWindow > 0) return contextWindow;
|
|
290
|
-
return rawContextWindow > 0 ? rawContextWindow : null;
|
|
282
|
+
const n = compactBoundaryDenominator(routeInfo, compact);
|
|
283
|
+
return n > 0 ? n : null;
|
|
291
284
|
}
|
|
292
285
|
|
|
293
286
|
function sessionIdFromTranscriptPath(transcriptPath) {
|
|
@@ -378,8 +371,8 @@ function displayForModel(provider, model, info) {
|
|
|
378
371
|
const display = cleanString(info?.display) || cleanString(info?.displayName) || cleanString(info?.name);
|
|
379
372
|
if (display) return display;
|
|
380
373
|
if (provider === 'anthropic-oauth') {
|
|
381
|
-
const m = String(model || '').match(/^claude-(opus|sonnet|haiku)-(\d+)
|
|
382
|
-
if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}
|
|
374
|
+
const m = String(model || '').match(/^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-(\d+))?/i);
|
|
375
|
+
if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}${m[3] ? `.${m[3]}` : ''}`;
|
|
383
376
|
}
|
|
384
377
|
const raw = cleanString(model);
|
|
385
378
|
const gpt = raw.match(/^gpt[-_](.+)$/i);
|
|
@@ -119,7 +119,7 @@ export function resolveClaudeModelAlias(model) {
|
|
|
119
119
|
if (!raw) return null;
|
|
120
120
|
const stripped = stripClaudeContextSuffix(raw);
|
|
121
121
|
if (/^claude-/i.test(stripped)) return stripped;
|
|
122
|
-
const family = stripped.match(/^(opus|sonnet|haiku)(?:[-_].*)?$/i)?.[1]?.toLowerCase();
|
|
122
|
+
const family = stripped.match(/^(opus|sonnet|haiku|fable)(?:[-_].*)?$/i)?.[1]?.toLowerCase();
|
|
123
123
|
if (family) return familyDefaultModel(family);
|
|
124
124
|
return null;
|
|
125
125
|
}
|
|
@@ -128,11 +128,11 @@ export function displayClaudeModel(model, raw = '', display = '') {
|
|
|
128
128
|
const explicit = cleanString(display);
|
|
129
129
|
if (explicit) return explicit;
|
|
130
130
|
const id = cleanString(model) || resolveClaudeModelAlias(raw) || cleanString(raw);
|
|
131
|
-
const m = id.match(/^claude-(opus|sonnet|haiku)-(\d+)
|
|
131
|
+
const m = id.match(/^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-(\d+))?(?:-\d{8})?$/i);
|
|
132
132
|
if (m) {
|
|
133
133
|
const family = `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()}`;
|
|
134
134
|
const suffix = /\[1m\]/i.test(raw) || contextWindowFromRawModel(raw, id) === 1000000 ? ' (1M context)' : '';
|
|
135
|
-
return `${family} ${m[2]}
|
|
135
|
+
return `${family} ${m[2]}${m[3] ? `.${m[3]}` : ''}${suffix}`;
|
|
136
136
|
}
|
|
137
137
|
return id || raw || '';
|
|
138
138
|
}
|
|
@@ -254,8 +254,8 @@ function displayForModel(provider, model, info) {
|
|
|
254
254
|
const display = cleanString(info?.display) || cleanString(info?.displayName) || cleanString(info?.name);
|
|
255
255
|
if (display) return display;
|
|
256
256
|
if (provider === 'anthropic-oauth') {
|
|
257
|
-
const m = String(model || '').match(/^claude-(opus|sonnet|haiku)-(\d+)
|
|
258
|
-
if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}
|
|
257
|
+
const m = String(model || '').match(/^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-(\d+))?/i);
|
|
258
|
+
if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}${m[3] ? `.${m[3]}` : ''}`;
|
|
259
259
|
}
|
|
260
260
|
return model || '';
|
|
261
261
|
}
|
|
@@ -343,25 +343,44 @@ export function readGatewayRouteInfo(seed = {}, providerObj = null) {
|
|
|
343
343
|
};
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
// Boundary denominator for status %, overflow checks, and gateway env window.
|
|
347
|
+
// Order: explicit boundary (routeInfo or compact) → contextWindow → budgetWindow
|
|
348
|
+
// → rawContextWindow. autoCompactTokenLimit / compactLimitTokens are triggers
|
|
349
|
+
// only and are never read here.
|
|
350
|
+
export function compactBoundaryDenominator(routeInfo = {}, compact = null) {
|
|
351
|
+
const fromRoute = num(
|
|
352
|
+
routeInfo?.compactBoundaryTokens
|
|
353
|
+
?? routeInfo?.compact_boundary_tokens
|
|
354
|
+
?? routeInfo?.boundaryTokens,
|
|
355
|
+
0,
|
|
356
|
+
);
|
|
357
|
+
const fromCompact = num(
|
|
358
|
+
compact?.boundaryTokens
|
|
359
|
+
?? compact?.compactBoundaryTokens
|
|
360
|
+
?? compact?.compact_boundary_tokens,
|
|
361
|
+
0,
|
|
362
|
+
);
|
|
363
|
+
const boundaryTokens = fromRoute > 0 ? fromRoute : fromCompact;
|
|
348
364
|
const contextWindow = num(routeInfo?.contextWindow ?? compact?.contextWindow, 0);
|
|
349
365
|
const budgetWindow = num(compact?.budgetWindow, 0);
|
|
350
366
|
const rawContextWindow = num(routeInfo?.rawContextWindow ?? compact?.rawContextWindow, 0);
|
|
351
|
-
if (
|
|
352
|
-
if (compactLimit > 0) return compactLimit;
|
|
353
|
-
if (budgetWindow > 0 && contextWindow > 0) return Math.min(budgetWindow, contextWindow);
|
|
354
|
-
if (budgetWindow > 0) return budgetWindow;
|
|
367
|
+
if (boundaryTokens > 0) return boundaryTokens;
|
|
355
368
|
if (contextWindow > 0) return contextWindow;
|
|
369
|
+
if (budgetWindow > 0) return budgetWindow;
|
|
356
370
|
return rawContextWindow > 0 ? rawContextWindow : 0;
|
|
357
371
|
}
|
|
358
372
|
|
|
373
|
+
function compactBoundaryForRoute(routeInfo = {}, compact = null) {
|
|
374
|
+
return compactBoundaryDenominator(routeInfo, compact);
|
|
375
|
+
}
|
|
376
|
+
|
|
359
377
|
// Single source of truth for the value synced to host env
|
|
360
378
|
// CLAUDE_CODE_AUTO_COMPACT_WINDOW. BOTH the --enable-time write
|
|
361
379
|
// (scripts/gateway-model.mjs) and the runtime sync (src/gateway/server.mjs)
|
|
362
380
|
// must derive the window the SAME way, or they disagree on the value written to
|
|
363
|
-
// settings.json.
|
|
364
|
-
//
|
|
381
|
+
// settings.json. Denominator order: compactBoundaryTokens / boundaryTokens (route
|
|
382
|
+
// or compact) → contextWindow → budgetWindow → rawContextWindow. Never
|
|
383
|
+
// autoCompactTokenLimit or compactLimitTokens (trigger-only). Returns a positive
|
|
365
384
|
// integer, or null when unknown.
|
|
366
385
|
export function autoCompactWindowForRoute(routeInfo) {
|
|
367
386
|
const n = compactBoundaryForRoute(routeInfo);
|
|
@@ -428,12 +447,7 @@ function usageCostUsd(routeInfo, usage) {
|
|
|
428
447
|
}
|
|
429
448
|
|
|
430
449
|
function contextUsageBoundary(routeInfo, compact = null) {
|
|
431
|
-
|
|
432
|
-
const budgetWindow = num(compact?.budgetWindow, 0);
|
|
433
|
-
const rawContextWindow = num(routeInfo?.rawContextWindow ?? compact?.rawContextWindow, 0);
|
|
434
|
-
if (contextWindow > 0) return contextWindow;
|
|
435
|
-
if (budgetWindow > 0) return budgetWindow;
|
|
436
|
-
return rawContextWindow > 0 ? rawContextWindow : compactBoundaryForRoute(routeInfo, compact);
|
|
450
|
+
return compactBoundaryDenominator(routeInfo, compact);
|
|
437
451
|
}
|
|
438
452
|
|
|
439
453
|
export function summarizeGatewayUsage(routeInfo, providerOut, compact = null, durationMs = null) {
|
|
@@ -1,14 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: default
|
|
3
|
+
name: Default
|
|
4
|
+
description: "Default agent workflow — fan-out parallel delegation across independent scopes."
|
|
5
|
+
agents: worker, heavy-worker, reviewer, debugger, maintainer
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# Default Workflow
|
|
2
9
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
|
|
11
|
+
- Lead handles directly: simple 1–2 step work, plus coordination, pre-planning,
|
|
12
|
+
config changes, and final git deployment.
|
|
13
|
+
- Worker: implementation that takes several steps.
|
|
14
|
+
- Heavy Worker: high-complexity, multi-step implementation.
|
|
15
|
+
- Reviewer: verify implementation scopes (diff, regressions, missing checks).
|
|
16
|
+
- Debugger: deploy for very high complexity, or when root-causing has already
|
|
17
|
+
failed at least once.
|
|
18
|
+
|
|
19
|
+
1. Plan — discuss the request with the user, form a plan, and wait for approval.
|
|
20
|
+
2. Delegate — split into the maximum number of independent scopes.
|
|
21
|
+
- PARALLEL across independent scopes by default (implementation, analysis,
|
|
22
|
+
review, debugging split alike). Spawn every scope in the SAME turn. Shared
|
|
23
|
+
or cross-cutting code does NOT justify merging: split per path and verify
|
|
24
|
+
the shared parts yourself. The only single scope is a genuinely
|
|
25
|
+
inseparable dependency — then state it.
|
|
26
|
+
- SEQUENTIAL within a single complex scope — split it into ordered steps
|
|
27
|
+
rather than handing it off in one shot, with a build/test-green gate
|
|
28
|
+
between steps.
|
|
29
|
+
- Reuse the existing agent (same tag) for follow-up on the same scope; spawn
|
|
30
|
+
new only for a separate scope. Write briefs per the lead-tool brief
|
|
31
|
+
contract (token-optimized labeled fragments).
|
|
32
|
+
- After spawning async agent(s), END THE TURN — do not poll, guess, or start
|
|
33
|
+
dependent work until the completion notification resumes you (status/read
|
|
34
|
+
are manual recovery only). Then wait and continue automatically.
|
|
35
|
+
3. Review — pair one reviewer 1:1 with each implementation scope, spawned in the
|
|
36
|
+
same turn. Cross-check implementation and review results yourself before
|
|
37
|
+
acting on them. Send fixes back to the original scope and repeat until clean.
|
|
38
|
+
Skip review only for simple, low-risk tasks.
|
|
39
|
+
4. Report — deliver the final report: synthesize results (outcome + key
|
|
40
|
+
evidence; never forward raw agent output), state the final state, and ask the
|
|
41
|
+
user whether to ship/deploy when relevant.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sequential
|
|
3
|
+
name: Sequential
|
|
4
|
+
description: "Sequential workflow — delegates 1:1 with no fan-out; one agent at a time."
|
|
5
|
+
agents: worker, heavy-worker, reviewer, debugger, maintainer
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Sequential Workflow
|
|
9
|
+
|
|
10
|
+
Unlike Default, which fans out independent scopes in parallel, this mode runs
|
|
11
|
+
with no fan-out: it delegates one scope at a time, 1:1, and only moves to the
|
|
12
|
+
next after the current one completes.
|
|
13
|
+
|
|
14
|
+
Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
|
|
15
|
+
- Lead handles directly: simple 1–2 step work, plus coordination, pre-planning,
|
|
16
|
+
config changes, and final git deployment.
|
|
17
|
+
- Worker: implementation that takes several steps.
|
|
18
|
+
- Heavy Worker: high-complexity, multi-step implementation.
|
|
19
|
+
- Reviewer: verify implementation scopes (diff, regressions, missing checks).
|
|
20
|
+
- Debugger: deploy for very high complexity, or when root-causing has already
|
|
21
|
+
failed at least once.
|
|
22
|
+
|
|
23
|
+
1. Plan — discuss the request with the user, form a plan, and wait for approval.
|
|
24
|
+
2. Delegate — split into ordered scopes and hand them off ONE AT A TIME.
|
|
25
|
+
- NO PARALLEL, NO FAN-OUT. Even when scopes are independent, spawn exactly
|
|
26
|
+
one agent per turn; never spawn multiple agents in the same turn. Wait for
|
|
27
|
+
the current scope's completion notification before spawning the next scope
|
|
28
|
+
(1:1 sequential).
|
|
29
|
+
- SEQUENTIAL within a single complex scope — split it into ordered steps
|
|
30
|
+
rather than handing it off in one shot, with a build/test-green gate
|
|
31
|
+
between steps.
|
|
32
|
+
- Reuse the existing agent (same tag) for follow-up on the same scope; spawn
|
|
33
|
+
new only for a separate scope. Write briefs per the lead-tool brief
|
|
34
|
+
contract (token-optimized labeled fragments).
|
|
35
|
+
- After spawning an async agent, END THE TURN — do not poll, guess, or start
|
|
36
|
+
dependent or subsequent work until the completion notification resumes you
|
|
37
|
+
(status/read are manual recovery only). Then wait and continue
|
|
38
|
+
automatically.
|
|
39
|
+
3. Review — after each implementation scope completes, pair one reviewer 1:1
|
|
40
|
+
with that scope (spawned on its own turn, not batched). Cross-check
|
|
41
|
+
implementation and review results yourself before acting on them. Send fixes
|
|
42
|
+
back to the original scope and repeat until clean. Skip review only for
|
|
43
|
+
simple, low-risk tasks.
|
|
44
|
+
4. Report — deliver the final report: synthesize results (outcome + key
|
|
45
|
+
evidence; never forward raw agent output), state the final state, and ask the
|
|
46
|
+
user whether to ship/deploy when relevant.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* display-width.js — [mixdog fork] vendored copy of the shared display-width
|
|
3
|
+
* policy from src/tui/display-width.mjs.
|
|
4
|
+
*
|
|
5
|
+
* MUST STAY IN SYNC with src/tui/display-width.mjs. ink is aliased as an
|
|
6
|
+
* external bundle (see scripts/build-tui.mjs), so it cannot cleanly import from
|
|
7
|
+
* src; the small policy is replicated here verbatim instead. If the problem
|
|
8
|
+
* ranges or the WT_SESSION/MIXDOG_TUI_AMBIGUOUS_WIDE gate change there, change
|
|
9
|
+
* them here too — ink's MEASUREMENT must agree with OUR wrap/row math or the
|
|
10
|
+
* overlap gets worse, not better.
|
|
11
|
+
*
|
|
12
|
+
* Policy: widen ONLY U+2460–U+24FF (enclosed alphanumerics / circled digits)
|
|
13
|
+
* and U+2190–U+21FF (arrows) to 2 cells when ON. NEVER widen box-drawing
|
|
14
|
+
* (U+2500–U+257F), block elements, or other ambiguous glyphs. ON by default
|
|
15
|
+
* only under Windows Terminal (WT_SESSION); MIXDOG_TUI_AMBIGUOUS_WIDE='1'/'0'
|
|
16
|
+
* overrides and wins. OFF ⇒ identical to plain string-width.
|
|
17
|
+
*/
|
|
18
|
+
import stringWidth from 'string-width';
|
|
19
|
+
|
|
20
|
+
function isProblemCodePoint(cp) {
|
|
21
|
+
return (cp >= 0x2460 && cp <= 0x24ff) || (cp >= 0x2190 && cp <= 0x21ff);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveAmbiguousWidePolicy(env = process.env) {
|
|
25
|
+
const override = env?.MIXDOG_TUI_AMBIGUOUS_WIDE;
|
|
26
|
+
if (override === '1')
|
|
27
|
+
return true;
|
|
28
|
+
if (override === '0')
|
|
29
|
+
return false;
|
|
30
|
+
return Boolean(env?.WT_SESSION);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Resolved once at module load (matches src/tui/display-width.mjs).
|
|
34
|
+
export const AMBIGUOUS_WIDE = resolveAmbiguousWidePolicy();
|
|
35
|
+
|
|
36
|
+
export function displayWidthWith(str, wide) {
|
|
37
|
+
const s = String(str ?? '');
|
|
38
|
+
const base = stringWidth(s);
|
|
39
|
+
if (!wide)
|
|
40
|
+
return base;
|
|
41
|
+
let extra = 0;
|
|
42
|
+
for (const ch of s) {
|
|
43
|
+
const cp = ch.codePointAt(0);
|
|
44
|
+
if (isProblemCodePoint(cp) && stringWidth(ch) === 1)
|
|
45
|
+
extra += 1;
|
|
46
|
+
}
|
|
47
|
+
return base + extra;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Visible display width under the resolved policy (ink-side stringWidth). */
|
|
51
|
+
export function displayStringWidth(str) {
|
|
52
|
+
return displayWidthWith(str, AMBIGUOUS_WIDE);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** widest-line equivalent under the policy. */
|
|
56
|
+
export function displayWidestLine(text) {
|
|
57
|
+
let lineWidth = 0;
|
|
58
|
+
for (const line of String(text ?? '').split('\n')) {
|
|
59
|
+
lineWidth = Math.max(lineWidth, displayStringWidth(line));
|
|
60
|
+
}
|
|
61
|
+
return lineWidth;
|
|
62
|
+
}
|