mixdog 0.9.85 → 0.9.87
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/README.md +7 -1
- package/package.json +4 -3
- package/src/defaults/agents.json +12 -0
- package/src/defaults/skills/setup/SKILL.md +44 -12
- package/src/help.mjs +40 -9
- package/src/lib/keychain-cjs.cjs +11 -1
- package/src/rules/agent/43-title-agent.md +22 -0
- package/src/rules/shared/01-tool.md +3 -3
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +84 -3
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +125 -16
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +73 -45
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +15 -18
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +135 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +31 -12
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +45 -6
- package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/token-bpe.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/token-native.mjs +186 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +15 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +286 -0
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +152 -18
- package/src/runtime/agent/orchestrator/tools/token-binary-fetcher.mjs +201 -0
- package/src/runtime/agent/orchestrator/tools/token-manifest.json +26 -0
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +3 -3
- package/src/runtime/media/adapters/gemini-video.mjs +1 -1
- package/src/runtime/media/renditions.mjs +1 -1
- package/src/runtime/media/store.mjs +1 -1
- package/src/runtime/memory/lib/memory-action-handlers.mjs +4 -1
- package/src/runtime/memory/tool-defs.mjs +19 -3
- package/src/runtime/shared/atomic-file.mjs +25 -14
- package/src/runtime/shared/automation-attachments.mjs +3 -3
- package/src/runtime/shared/child-guardian.mjs +15 -5
- package/src/runtime/shared/child-spawn-gate.mjs +6 -12
- package/src/runtime/shared/resource-admission.mjs +7 -3
- package/src/runtime/shared/turn-snapshot.mjs +0 -3
- package/src/session-runtime/lifecycle-api.mjs +49 -9
- package/src/session-runtime/prewarm.mjs +35 -0
- package/src/session-runtime/provider-auth-api.mjs +8 -0
- package/src/session-runtime/provider-usage.mjs +30 -5
- package/src/session-runtime/remote-control.mjs +8 -47
- package/src/session-runtime/remote-transcript.mjs +3 -7
- package/src/session-runtime/route-preparation.mjs +24 -0
- package/src/session-runtime/runtime-core.mjs +286 -548
- package/src/session-runtime/session-lifecycle.mjs +378 -0
- package/src/session-runtime/session-turn-api.mjs +9 -2
- package/src/session-runtime/workflow-agents-api.mjs +3 -0
- package/src/standalone/agent-shard/shard-child.mjs +300 -0
- package/src/standalone/agent-shard/shard-pool.mjs +443 -0
- package/src/standalone/agent-tool/job-views.mjs +329 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +629 -0
- package/src/standalone/agent-tool/tag-registry.mjs +340 -0
- package/src/standalone/agent-tool.mjs +112 -941
- package/src/standalone/channel-daemon-transport.mjs +126 -185
- package/src/standalone/channel-daemon.mjs +9 -10
- package/src/standalone/usage-dashboard.mjs +23 -1
- package/src/tui/App.jsx +362 -2569
- package/src/tui/app/app-view.jsx +504 -0
- package/src/tui/app/channel-pickers.mjs +3 -8
- package/src/tui/app/create-app-pickers.mjs +313 -0
- package/src/tui/app/prompt-submit.mjs +501 -0
- package/src/tui/app/route-pickers.mjs +117 -152
- package/src/tui/app/settings-picker.mjs +90 -89
- package/src/tui/app/shell-layout.mjs +563 -0
- package/src/tui/app/slash-commands.mjs +7 -7
- package/src/tui/app/slash-dispatch.mjs +0 -45
- package/src/tui/app/usage-context-panels.mjs +288 -0
- package/src/tui/app/use-copy-selection.mjs +56 -0
- package/src/tui/app/use-global-key-input.mjs +151 -0
- package/src/tui/app/use-pasted-buffers.mjs +115 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +167 -0
- package/src/tui/app/use-prompt-hint.mjs +58 -0
- package/src/tui/app/use-prompt-queue-history.mjs +100 -0
- package/src/tui/app/use-terminal-chrome.mjs +72 -0
- package/src/tui/app/use-transcript-activity.mjs +131 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +96 -0
- package/src/tui/components/StatusLine.jsx +1 -1
- package/src/tui/components/tool-execution/ResultBody.jsx +1 -1
- package/src/tui/dist/index.mjs +9857 -9159
- package/src/tui/engine/session-api-ext.mjs +47 -9
- package/src/tui/engine.mjs +7 -1
- package/src/tui/figures.mjs +0 -1
- package/src/ui/statusline-format.mjs +0 -1
- package/src/hooks/lib/permission-evaluator.cjs +0 -24
- package/src/lib/config-cjs.cjs +0 -61
- package/src/runtime/shared/launcher-control.mjs +0 -258
- package/src/runtime/shared/workspace-router.mjs +0 -259
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
// Shell layout derivation, extracted from App.jsx. Pure render-phase math:
|
|
2
|
+
// given the terminal size and every surface that reserves rows (panels,
|
|
3
|
+
// prompt box, queued band, welcome banner, hints), compute the transcript
|
|
4
|
+
// viewport height and the whole bottom-cluster row budget. Runs once per
|
|
5
|
+
// render; the few ref writes mirror the previous inline behavior.
|
|
6
|
+
import { isLiveSpinnerMetaVisible } from './live-spinner-visibility.mjs';
|
|
7
|
+
import { isCompletedTranscriptTailAppendedThisCommit } from './live-spinner-visibility.mjs';
|
|
8
|
+
import { promptContentRows, wrappedDetailRows, queuedBandRows } from './text-layout.mjs';
|
|
9
|
+
|
|
10
|
+
export function computeShellLayout({
|
|
11
|
+
providerPrompt,
|
|
12
|
+
channelPrompt,
|
|
13
|
+
hookPrompt,
|
|
14
|
+
settingsPrompt,
|
|
15
|
+
panelTransitionEpoch,
|
|
16
|
+
panelInkMaskEpoch,
|
|
17
|
+
toolApproval,
|
|
18
|
+
picker,
|
|
19
|
+
contextPanel,
|
|
20
|
+
usagePanel,
|
|
21
|
+
slashPaletteOpen,
|
|
22
|
+
tuiReady,
|
|
23
|
+
state,
|
|
24
|
+
resizeState,
|
|
25
|
+
frameColumns,
|
|
26
|
+
promptHint,
|
|
27
|
+
promptHintTone,
|
|
28
|
+
textEntryLayoutRows,
|
|
29
|
+
onboardingActive,
|
|
30
|
+
conditionalWelcomePromptHint,
|
|
31
|
+
welcomePromptHintDismissed,
|
|
32
|
+
welcomePromptHintRef,
|
|
33
|
+
welcomePromptHintVisibleRef,
|
|
34
|
+
panelTransitionRef,
|
|
35
|
+
projectBootInputLatchRef,
|
|
36
|
+
promptLayoutValueRef,
|
|
37
|
+
promptContentColumns,
|
|
38
|
+
transcriptBottomSlackRowsRef,
|
|
39
|
+
transcriptViewportRef,
|
|
40
|
+
frameRowsRef,
|
|
41
|
+
promptBoxRectRef,
|
|
42
|
+
panelCloseInkMaskRowsRef,
|
|
43
|
+
CORE_MULTILINE_TEXT_ENTRY_KINDS,
|
|
44
|
+
panelSignatureFlags,
|
|
45
|
+
panelKindSignature,
|
|
46
|
+
isInstantPanelCloseTransition,
|
|
47
|
+
PANEL_LAYOUT_SIG,
|
|
48
|
+
}) {
|
|
49
|
+
// ── Transcript viewport height ──────────────────────────────────────────
|
|
50
|
+
// ROOT-CAUSE FIX: the transcript must live in a box with an EXPLICIT numeric
|
|
51
|
+
// height + overflow:hidden so ink's renderer actually clips off-screen rows
|
|
52
|
+
// (render-node-to-output.js → output.clip uses the box's computed height). An
|
|
53
|
+
// unbounded negative-margin column inside a flexGrow box let stale rows
|
|
54
|
+
// overprint newer ones across incremental redraws. We reserve the rows the
|
|
55
|
+
// bottom cluster needs and give the transcript everything above it.
|
|
56
|
+
//
|
|
57
|
+
// viewportHeight = rows
|
|
58
|
+
// − welcome header (empty transcript only)
|
|
59
|
+
// − live status (thinking / spinner / TurnDone)
|
|
60
|
+
// − queued prompts (marginTop 1 + N rows, only when queued)
|
|
61
|
+
// − prompt meta (spinner / transient message / queued)
|
|
62
|
+
// − input box (2 border + wrapped content)
|
|
63
|
+
// − statusline (reserved L1 + L2 + outer gap; total 3 rows)
|
|
64
|
+
//
|
|
65
|
+
// Every sibling outside the viewport must be accounted for here; otherwise
|
|
66
|
+
// the total tree height exceeds the terminal and the input box gets pushed.
|
|
67
|
+
const textEntryPrompt = providerPrompt || channelPrompt || hookPrompt || settingsPrompt;
|
|
68
|
+
const hasTextEntryPrompt = !!textEntryPrompt;
|
|
69
|
+
const hasFloatingPanel = !!(toolApproval || picker || contextPanel || usagePanel || slashPaletteOpen || hasTextEntryPrompt);
|
|
70
|
+
const expandedOptionPanel = !!(toolApproval || picker || contextPanel || usagePanel || hasTextEntryPrompt);
|
|
71
|
+
const panelTransitionForBoot = panelTransitionRef.current;
|
|
72
|
+
if (panelTransitionForBoot.signature.includes('picker:project') && !picker) {
|
|
73
|
+
projectBootInputLatchRef.current = true;
|
|
74
|
+
}
|
|
75
|
+
const bootSettling = !tuiReady && state.items.length === 0 && !hasFloatingPanel && !projectBootInputLatchRef.current;
|
|
76
|
+
// Project selection (initial-entry experience) keeps the welcome banner
|
|
77
|
+
// visible above the picker / path-entry prompt, unlike other floating panels.
|
|
78
|
+
const projectSelectionActive = picker?.kind === 'project'
|
|
79
|
+
|| settingsPrompt?.kind === 'project-new'
|
|
80
|
+
|| settingsPrompt?.kind === 'project-create-confirm'
|
|
81
|
+
|| settingsPrompt?.kind === 'project-rename';
|
|
82
|
+
// Slash search floats above the normal prompt. Actual option panels own the
|
|
83
|
+
// prompt/status area, so they hide those rows and expand into that space.
|
|
84
|
+
const inputBoxHidden = expandedOptionPanel || bootSettling;
|
|
85
|
+
const liveSpinner = state.spinner?.active ? state.spinner : (state.commandStatus?.active ? state.commandStatus : null);
|
|
86
|
+
// Command-status spinner (auto-clear/compact/etc.) is NOT part of the
|
|
87
|
+
// spinner → TurnDone handoff: it typically starts while the transcript tail
|
|
88
|
+
// is already a done row (idle session), so the done-at-tail suppression
|
|
89
|
+
// below must never hide it — that read as a frozen UI during auto-clear.
|
|
90
|
+
const liveSpinnerIsCommand = !state.spinner?.active && !!state.commandStatus?.active;
|
|
91
|
+
const latestToast = state.toasts?.length ? state.toasts[state.toasts.length - 1] : null;
|
|
92
|
+
const toastHint = latestToast ? latestToast.text : '';
|
|
93
|
+
const progressHint = state.progressHint || null;
|
|
94
|
+
const inputHint = promptHint || toastHint || (progressHint?.text || '');
|
|
95
|
+
const inputHintTone = promptHint
|
|
96
|
+
? promptHintTone
|
|
97
|
+
: (latestToast?.tone || progressHint?.tone || 'info');
|
|
98
|
+
const latestTranscriptItem = state.items[state.items.length - 1] || null;
|
|
99
|
+
// Bottom meta band ownership is LIVE-SPINNER ONLY. A finished turn's done row
|
|
100
|
+
// (turndone/statusdone) is a normal transcript item and flows into scrollback
|
|
101
|
+
// like anything else, so the area directly above the prompt is CLEAR when the
|
|
102
|
+
// user is idle. Earlier this row was pinned in the meta band until the next
|
|
103
|
+
// transcript item was appended (to dodge an autowrap overprint/bleed), which
|
|
104
|
+
// left the completed status row stuck above the prompt while the user typed or
|
|
105
|
+
// sat idle. That bleed is now fixed at the source by the tool-output width
|
|
106
|
+
// clamp, so the pin is no longer needed. Kept as a named null const so the
|
|
107
|
+
// downstream meta-band/hint logic collapses cleanly to the spinner-only path.
|
|
108
|
+
const latestDoneItem = null;
|
|
109
|
+
const SCROLL_HINT_ROWS = 0;
|
|
110
|
+
const LIVE_STATUS_ROWS = 0;
|
|
111
|
+
// The standalone prompt box is 2 border rows + the wrapped PromptInput body.
|
|
112
|
+
// The one-row scroll baseline gap ABOVE the prompt is owned by
|
|
113
|
+
// transcriptGuardRows, not the prompt box itself. That keeps the scroll
|
|
114
|
+
// reference at "textbox + 1" while the prompt/statusline bottom stays fixed.
|
|
115
|
+
//
|
|
116
|
+
// This must track the prompt draft's REAL wrapped height. Reserving a constant
|
|
117
|
+
// one-line prompt lets long/multiline input grow the bottom cluster after the
|
|
118
|
+
// transcript viewport has already claimed those rows, which makes transcript
|
|
119
|
+
// body text overprint the textbox or slash command window.
|
|
120
|
+
const currentPromptLayoutRows = promptContentRows(promptLayoutValueRef.current, promptContentColumns);
|
|
121
|
+
const promptInputRows = inputBoxHidden ? 0 : currentPromptLayoutRows;
|
|
122
|
+
const promptBoxRows = inputBoxHidden ? 0 : 2 + promptInputRows;
|
|
123
|
+
const STATUSLINE_ROWS = 3;
|
|
124
|
+
// Shared panel chrome math. Every floating panel follows the same vertical
|
|
125
|
+
// rhythm INSIDE its round border: title row, blank, description/hint row,
|
|
126
|
+
// blank, then content. That is 4 non-content rows; the round border adds 2
|
|
127
|
+
// more, so chrome reserves 6 rows total. Reserving the full chrome here (even
|
|
128
|
+
// for panels that omit the description) guarantees the bordered title can
|
|
129
|
+
// never be clipped off the top — content rows shrink first when the terminal
|
|
130
|
+
// is short, because the floating container clips from the top (flex-end).
|
|
131
|
+
const PANEL_MAX_VISIBLE = 8;
|
|
132
|
+
const PANEL_CHROME_ROWS = 6;
|
|
133
|
+
const PANEL_BASE_ROWS = PANEL_MAX_VISIBLE + PANEL_CHROME_ROWS;
|
|
134
|
+
const PICKER_CHROME_ROWS = PANEL_CHROME_ROWS;
|
|
135
|
+
// TextEntryPanel content is one prompt line (chrome + 1) for single-line
|
|
136
|
+
// prompts, or up to PANEL_MAX_VISIBLE wrapped rows for core memory add/edit.
|
|
137
|
+
// PLUS an optional wrapped detail block (blank spacer + N wrapped rows — e.g.
|
|
138
|
+
// the manual OAuth URL). The floating container clips from the TOP (flex-end +
|
|
139
|
+
// overflow hidden), so under-reserving here pushed the bordered title off
|
|
140
|
+
// the top of the panel. Width matches the panel interior: frame − 2 border
|
|
141
|
+
// − 2 paddingX, same wrap-ansi math ink uses for wrap="wrap".
|
|
142
|
+
const textEntryKind = String(textEntryPrompt?.kind || '');
|
|
143
|
+
const textEntryMultiline = CORE_MULTILINE_TEXT_ENTRY_KINDS.has(textEntryKind);
|
|
144
|
+
const textEntryDetailText = String(textEntryPrompt?.detail || '').trim();
|
|
145
|
+
const textEntryDetailRows = textEntryDetailText
|
|
146
|
+
? 1 + wrappedDetailRows(textEntryDetailText, Math.max(1, frameColumns - 4))
|
|
147
|
+
: 0;
|
|
148
|
+
const textEntryContentRows = textEntryMultiline ? textEntryLayoutRows : 1;
|
|
149
|
+
const TEXT_ENTRY_ROWS = PANEL_CHROME_ROWS + textEntryContentRows + textEntryDetailRows;
|
|
150
|
+
const OPTION_PANEL_EXTRA_ROWS = expandedOptionPanel ? 3 : 0;
|
|
151
|
+
const queuedVisible = !hasFloatingPanel && !inputBoxHidden && state.queued?.length > 0;
|
|
152
|
+
// While the slash palette is open it owns the area above the prompt, so the
|
|
153
|
+
// live spinner/meta row is suppressed entirely — no reservation and no render.
|
|
154
|
+
// Normalize the spinner → TurnDone handoff by making them occupy the SAME
|
|
155
|
+
// two-row slot. Completion appends turndone and clears spinner in one commit,
|
|
156
|
+
// so a completed row replaces the spinner slot with no transient jump. A
|
|
157
|
+
// statusdone can be emitted mid-turn (for example after compaction), so it
|
|
158
|
+
// must not suppress the still-active spinner.
|
|
159
|
+
const promptMetaVisible = isLiveSpinnerMetaVisible({
|
|
160
|
+
inputBoxHidden,
|
|
161
|
+
slashPaletteOpen,
|
|
162
|
+
liveSpinner,
|
|
163
|
+
liveSpinnerIsCommand,
|
|
164
|
+
latestTranscriptItem,
|
|
165
|
+
});
|
|
166
|
+
const promptMetaRows = promptMetaVisible ? 2 : 0;
|
|
167
|
+
// Toast/error text without a live spinner uses the existing transcript guard
|
|
168
|
+
// row directly above the prompt. Do NOT reserve another row here: that made a
|
|
169
|
+
// transient hint add a visible newline/prompt jump whenever no spinner was
|
|
170
|
+
// active.
|
|
171
|
+
const overlayHintRequested = !inputBoxHidden && !hasFloatingPanel && !liveSpinner && !!inputHint && !queuedVisible;
|
|
172
|
+
const overlayHintRows = 0;
|
|
173
|
+
// QueuedCommands renders each queued command at its FULL wrapped height
|
|
174
|
+
// (same content width the promoted transcript user row wraps at), pinned
|
|
175
|
+
// above the prompt box with no extra top-margin row. Reserving the true
|
|
176
|
+
// height keeps promotion from re-expanding the text mid-flight ("row jump").
|
|
177
|
+
// If the whole queue would eat too much of the frame, fall back to the old
|
|
178
|
+
// compact 1-row-per-entry truncation so the input box never leaves screen.
|
|
179
|
+
const queuedFullRows = queuedVisible
|
|
180
|
+
? state.queued.reduce(
|
|
181
|
+
(sum, item) => sum + queuedBandRows(String(item.displayText || item.text || ''), Math.max(1, frameColumns - 4)),
|
|
182
|
+
0,
|
|
183
|
+
)
|
|
184
|
+
: 0;
|
|
185
|
+
const queuedRowBudget = Math.max(3, Math.floor(resizeState.rows / 3));
|
|
186
|
+
const queuedCompact = queuedFullRows > queuedRowBudget;
|
|
187
|
+
const queuedRows = queuedVisible ? (queuedCompact ? state.queued.length : queuedFullRows) : 0;
|
|
188
|
+
const INPUT_BOX_ROWS = promptBoxRows + promptMetaRows + overlayHintRows;
|
|
189
|
+
// Welcome banner visibility (computed here, after the prompt/panel row math
|
|
190
|
+
// it depends on). The slash palette must NOT unmount the banner on the empty
|
|
191
|
+
// transcript: dropping the 11-row banner the moment '/' opened the palette
|
|
192
|
+
// (and remounting it on Esc) read as the whole top of the screen jumping on
|
|
193
|
+
// command-palette entry. Keep the banner mounted through a slash session
|
|
194
|
+
// whenever the terminal is tall enough to hold banner + palette + prompt +
|
|
195
|
+
// statusline at once; short terminals still yield the banner so the palette
|
|
196
|
+
// chrome never clips (the floating container clips from the top).
|
|
197
|
+
const WELCOME_BANNER_ROWS = 11;
|
|
198
|
+
const SLASH_PALETTE_ROWS = PANEL_MAX_VISIBLE + PANEL_CHROME_ROWS;
|
|
199
|
+
const slashKeepsWelcomeBanner = slashPaletteOpen
|
|
200
|
+
&& resizeState.rows >= WELCOME_BANNER_ROWS + SLASH_PALETTE_ROWS + INPUT_BOX_ROWS + STATUSLINE_ROWS + 1;
|
|
201
|
+
const showWelcomeBanner = (state.items.length === 0 && (!hasFloatingPanel || slashKeepsWelcomeBanner))
|
|
202
|
+
|| projectSelectionActive || onboardingActive;
|
|
203
|
+
const WELCOME_ROWS = showWelcomeBanner ? WELCOME_BANNER_ROWS : 0;
|
|
204
|
+
const baseReserve = WELCOME_ROWS + SCROLL_HINT_ROWS + LIVE_STATUS_ROWS + INPUT_BOX_ROWS + STATUSLINE_ROWS + queuedRows;
|
|
205
|
+
const maxFloatingPanelRows = Math.max(0, resizeState.rows - baseReserve - 1);
|
|
206
|
+
const desiredFloatingPanelRows = toolApproval
|
|
207
|
+
? PANEL_CHROME_ROWS + 2 + OPTION_PANEL_EXTRA_ROWS
|
|
208
|
+
: picker
|
|
209
|
+
? (picker.fillAvailable ? maxFloatingPanelRows : PANEL_BASE_ROWS + OPTION_PANEL_EXTRA_ROWS)
|
|
210
|
+
: contextPanel
|
|
211
|
+
? PANEL_BASE_ROWS + OPTION_PANEL_EXTRA_ROWS + 3
|
|
212
|
+
: usagePanel
|
|
213
|
+
? PANEL_BASE_ROWS + OPTION_PANEL_EXTRA_ROWS
|
|
214
|
+
: slashPaletteOpen
|
|
215
|
+
? PANEL_MAX_VISIBLE + PANEL_CHROME_ROWS
|
|
216
|
+
: hasTextEntryPrompt
|
|
217
|
+
? TEXT_ENTRY_ROWS
|
|
218
|
+
: 0;
|
|
219
|
+
const floatingPanelRows = desiredFloatingPanelRows > 0
|
|
220
|
+
? Math.min(desiredFloatingPanelRows, maxFloatingPanelRows)
|
|
221
|
+
: 0;
|
|
222
|
+
// Give the list every content row the panel exposes. The panel already grew
|
|
223
|
+
// by OPTION_PANEL_EXTRA_ROWS; previously that growth was subtracted back out
|
|
224
|
+
// here, so the rows leaked into an empty flexGrow gap instead of the list.
|
|
225
|
+
// Reserving only PICKER_CHROME_ROWS lets the list occupy the full interior
|
|
226
|
+
// (the footer's own reservation is handled inside Picker).
|
|
227
|
+
const pickerVisibleRows = picker
|
|
228
|
+
? Math.max(1, floatingPanelRows - PICKER_CHROME_ROWS)
|
|
229
|
+
: PANEL_MAX_VISIBLE;
|
|
230
|
+
const rawBottomReserve = baseReserve + floatingPanelRows;
|
|
231
|
+
const bottomClusterRows = INPUT_BOX_ROWS + STATUSLINE_ROWS + queuedRows + floatingPanelRows;
|
|
232
|
+
const panelLayoutSignature = [
|
|
233
|
+
toolApproval ? 'tool' : '',
|
|
234
|
+
picker ? `picker:${picker.kind || ''}:${picker.fillAvailable ? 'fill' : 'fit'}` : '',
|
|
235
|
+
contextPanel ? 'context' : '',
|
|
236
|
+
usagePanel ? 'usage' : '',
|
|
237
|
+
slashPaletteOpen ? 'slash' : '',
|
|
238
|
+
hasTextEntryPrompt ? `text:${textEntryPrompt?.kind || ''}` : '',
|
|
239
|
+
inputBoxHidden ? 'input-hidden' : 'input-visible',
|
|
240
|
+
floatingPanelRows,
|
|
241
|
+
promptBoxRows,
|
|
242
|
+
promptMetaRows,
|
|
243
|
+
queuedRows,
|
|
244
|
+
WELCOME_ROWS,
|
|
245
|
+
].join('|');
|
|
246
|
+
const panelTransition = panelTransitionRef.current;
|
|
247
|
+
const panelLayoutChanged = Boolean(panelTransition.signature && panelTransition.signature !== panelLayoutSignature);
|
|
248
|
+
let panelTransitionClearRows = 0;
|
|
249
|
+
let panelTransitionGuardRows = 0;
|
|
250
|
+
if (panelLayoutChanged) {
|
|
251
|
+
const panelShrinkRows = Math.max(0, panelTransition.reserve - bottomClusterRows);
|
|
252
|
+
const initialProjectEntryClose = state.items.length === 0;
|
|
253
|
+
// Prompt-row-only churn (promptBoxRows/promptMetaRows/queuedRows shifting
|
|
254
|
+
// while no panel opened/closed/changed kind and floatingPanelRows itself is
|
|
255
|
+
// unchanged — see PANEL_KIND_TOKEN_COUNT) must not fall into the clearRows
|
|
256
|
+
// + setTimeout recommit path below: that inserts a full extra blank row for
|
|
257
|
+
// one commit, which IS the newline-add/remove jolt while typing. Route a
|
|
258
|
+
// shrink here through the same one-commit ink-mask path as an instant panel
|
|
259
|
+
// close so the stale row is masked in the very commit it disappears; growth
|
|
260
|
+
// already needs no clearance (panelShrinkRows is 0 in that case).
|
|
261
|
+
const promptRowsOnlyChange = panelShrinkRows > 0
|
|
262
|
+
&& panelKindSignature(panelTransition.signature) === panelKindSignature(panelLayoutSignature);
|
|
263
|
+
// Turn-end spinner meta collapse: the 2-row live-spinner band disappears in
|
|
264
|
+
// the SAME commit the engine appends the turndone/statusdone tail (see
|
|
265
|
+
// engine.mjs runTurn — turndone + spinner:null land in one set()). That new
|
|
266
|
+
// done row already replaces the vacated height, so masking those rows blank
|
|
267
|
+
// for one commit only to clear them on the next commit IS the visible
|
|
268
|
+
// transcript bounce. Exempt exactly the meta-collapse rows from the ink mask
|
|
269
|
+
// when the done row is the transcript tail. Every other prompt-row-only
|
|
270
|
+
// shrink (typing newline removal, queued-row churn) AND the reclaimed/no-op
|
|
271
|
+
// path (engine.mjs skips turndone, so the completed-tail check stays false and no
|
|
272
|
+
// row replaces the height) keep the mask so they still reclaim smoothly.
|
|
273
|
+
const prevMetaRows = Number(String(panelTransition.signature).split('|')[PANEL_LAYOUT_SIG.PROMPT_META]) || 0;
|
|
274
|
+
const nextMetaRows = Number(String(panelLayoutSignature).split('|')[PANEL_LAYOUT_SIG.PROMPT_META]) || 0;
|
|
275
|
+
// Require the done row to have been appended in THIS commit (tail id changed
|
|
276
|
+
// since the last commit). A command spinner can leave a STALE done row at the
|
|
277
|
+
// tail and then clear without appending statusdone (e.g. /recall — see
|
|
278
|
+
// engine.mjs), collapsing the meta band with NO same-commit backfill; masking
|
|
279
|
+
// must stay on for that path or the vacated rows overpaint the stale row.
|
|
280
|
+
const doneTailAppendedThisCommit = isCompletedTranscriptTailAppendedThisCommit(
|
|
281
|
+
latestTranscriptItem,
|
|
282
|
+
panelTransition.tailId,
|
|
283
|
+
);
|
|
284
|
+
const spinnerMetaCollapseRows = doneTailAppendedThisCommit
|
|
285
|
+
? Math.max(0, prevMetaRows - nextMetaRows)
|
|
286
|
+
: 0;
|
|
287
|
+
// Queued-band promotion: drain() removes the queued band and appends the
|
|
288
|
+
// promoted user transcript row in the SAME commit (session-flow.mjs drain
|
|
289
|
+
// → pushUserOrSyntheticItem → runTurn spinner, one microtask flush). The
|
|
290
|
+
// new user row (full wrapped height + margin) already backfills the
|
|
291
|
+
// vacated band rows, so masking them blank for one commit only to drop
|
|
292
|
+
// the mask on the next commit made the whole transcript bounce down.
|
|
293
|
+
// Exempt exactly the vacated queued rows when a user row landed at the
|
|
294
|
+
// tail in this commit; queue edits/removals without a tail append (tail
|
|
295
|
+
// id unchanged, or non-user tail) keep the mask.
|
|
296
|
+
const prevQueuedSigRows = Number(String(panelTransition.signature).split('|')[PANEL_LAYOUT_SIG.QUEUED]) || 0;
|
|
297
|
+
const nextQueuedSigRows = Number(String(panelLayoutSignature).split('|')[PANEL_LAYOUT_SIG.QUEUED]) || 0;
|
|
298
|
+
const userTailAppendedThisCommit = latestTranscriptItem?.kind === 'user'
|
|
299
|
+
&& (latestTranscriptItem?.id ?? null) !== panelTransition.tailId;
|
|
300
|
+
const queuedPromoteCollapseRows = userTailAppendedThisCommit
|
|
301
|
+
? Math.max(0, prevQueuedSigRows - nextQueuedSigRows)
|
|
302
|
+
: 0;
|
|
303
|
+
const instantPanelClose = panelShrinkRows > 0
|
|
304
|
+
&& (promptRowsOnlyChange
|
|
305
|
+
|| isInstantPanelCloseTransition(panelTransition.signature, panelLayoutSignature, initialProjectEntryClose));
|
|
306
|
+
// Slash palette opening on the empty welcome screen: bottomReserve already
|
|
307
|
+
// grows to its final size in this same commit (floatingPanelRows reflects
|
|
308
|
+
// slashPaletteOpen immediately, no clearRows needed), but the renderer can
|
|
309
|
+
// still overpaint the just-vacated transcript row for one commit. Borrow
|
|
310
|
+
// the transitional guard-row mechanism (normally used for tall panel
|
|
311
|
+
// closes below) for exactly one commit on the open transition itself —
|
|
312
|
+
// this only carves an extra blank row out of transcriptContentHeight, it
|
|
313
|
+
// does not touch bottomReserve/floatingPanelRows/palette height.
|
|
314
|
+
const slashOpenOnEmptyTranscript = initialProjectEntryClose
|
|
315
|
+
&& !panelSignatureFlags(panelTransition.signature).slash
|
|
316
|
+
&& panelSignatureFlags(panelLayoutSignature).slash;
|
|
317
|
+
if (instantPanelClose) {
|
|
318
|
+
// Slash palette and initial project-entry closes land on the final bottom
|
|
319
|
+
// reserve in one commit. Paint reclaimed rows as a blank mask band below
|
|
320
|
+
// the transcript clip instead of inflating bottomReserve + reclaiming on
|
|
321
|
+
// the next tick. Subtract any turn-end spinner-meta rows that the same-
|
|
322
|
+
// commit done tail already backfills (spinnerMetaCollapseRows) so that
|
|
323
|
+
// transition masks nothing and does not bounce; same for queued-band
|
|
324
|
+
// rows backfilled by a just-promoted user row (queuedPromoteCollapseRows).
|
|
325
|
+
panelCloseInkMaskRowsRef.current = Math.max(0, panelShrinkRows - spinnerMetaCollapseRows - queuedPromoteCollapseRows);
|
|
326
|
+
panelTransition.clearRows = 0;
|
|
327
|
+
panelTransition.guardRows = 0;
|
|
328
|
+
panelTransition.epoch = panelTransitionEpoch;
|
|
329
|
+
} else if (slashOpenOnEmptyTranscript) {
|
|
330
|
+
panelTransitionClearRows = 0;
|
|
331
|
+
panelTransitionGuardRows = 1;
|
|
332
|
+
panelTransition.clearRows = 0;
|
|
333
|
+
panelTransition.guardRows = 1;
|
|
334
|
+
panelTransition.epoch = panelTransitionEpoch;
|
|
335
|
+
} else {
|
|
336
|
+
// Tall panel closes must land on the final bottom reserve in the same
|
|
337
|
+
// commit. Inflating bottomReserve with temporary clearance makes the
|
|
338
|
+
// transcript/prompt area move once, then snap back on the timer commit.
|
|
339
|
+
// Instead, keep the reclaimed rows inside the fixed viewport as a blank
|
|
340
|
+
// one-frame mask, matching the instant-close path above.
|
|
341
|
+
panelCloseInkMaskRowsRef.current = panelShrinkRows;
|
|
342
|
+
panelTransition.clearRows = 0;
|
|
343
|
+
panelTransition.guardRows = 0;
|
|
344
|
+
panelTransition.epoch = panelTransitionEpoch;
|
|
345
|
+
}
|
|
346
|
+
} else if (panelTransition.epoch === panelTransitionEpoch) {
|
|
347
|
+
panelTransitionClearRows = panelTransition.clearRows || 0;
|
|
348
|
+
panelTransitionGuardRows = panelTransition.guardRows || 0;
|
|
349
|
+
}
|
|
350
|
+
if (desiredFloatingPanelRows > 0) {
|
|
351
|
+
panelCloseInkMaskRowsRef.current = 0;
|
|
352
|
+
}
|
|
353
|
+
void panelInkMaskEpoch;
|
|
354
|
+
const panelCloseInkMaskRows = desiredFloatingPanelRows > 0 ? 0 : panelCloseInkMaskRowsRef.current;
|
|
355
|
+
const bottomReserve = rawBottomReserve + panelTransitionClearRows;
|
|
356
|
+
const viewportHeight = Math.max(1, resizeState.rows - bottomReserve);
|
|
357
|
+
// Keep one physical row between the transcript clip and the bottom cluster
|
|
358
|
+
// even when pinned to the live tail. Windows Terminal/conhost can still
|
|
359
|
+
// surface one clipped/off-by-one transcript row below the statusline during
|
|
360
|
+
// rapid tool-card updates; a permanent guard row makes that row blank instead
|
|
361
|
+
// of a tool header/detail.
|
|
362
|
+
const guardCapacityRows = Math.max(0, viewportHeight - 1);
|
|
363
|
+
const baseGuardRows = guardCapacityRows > 0 ? 1 : 0;
|
|
364
|
+
// ── Scroll-time overprint guard ───────────────────────────────────────────
|
|
365
|
+
// Wheel/manual scroll pushes the transcript column DOWN via a negative
|
|
366
|
+
// marginBottom (see the viewport render). Under conhost/Windows Terminal the
|
|
367
|
+
// incremental redraw can leave the row that slid past the clip edge painted
|
|
368
|
+
// OVER the bottom cluster (input box / statusline) for a frame — the reported
|
|
369
|
+
// "scrolled text shows on the statusline row" bug. One guard row is enough
|
|
370
|
+
// while pinned to the live tail, but during an active scroll the slid row can
|
|
371
|
+
// still bleed one line further, so widen the guard to TWO rows whenever the
|
|
372
|
+
// viewport is genuinely scrolled up. The extra blank row absorbs the stray
|
|
373
|
+
// paint instead of the statusline. Requires a viewport tall enough to spare
|
|
374
|
+
// the row, and never shrinks below the base guard.
|
|
375
|
+
// Gate on the same follow-aware basis the transcript window uses for
|
|
376
|
+
// renderScrollOffset (see use-transcript-window.mjs targetNearBottom): the
|
|
377
|
+
// live `scrollOffset` state can still read >0 for one frame after a wheel
|
|
378
|
+
// turn re-arms bottom-follow, while this same frame already renders with
|
|
379
|
+
// renderScrollOffset=0. Without this, the stale offset shrinks
|
|
380
|
+
// transcriptContentHeight by one row that never actually gets rendered,
|
|
381
|
+
// producing a one-row bounce that snaps back once state catches up.
|
|
382
|
+
// [2026-07-06] Scroll-time extra guard DISABLED: the widened (2-row) guard
|
|
383
|
+
// rendered as a visibly empty band between the transcript and the prompt box
|
|
384
|
+
// whenever the viewport was scrolled up (user-reported "bottom rows look
|
|
385
|
+
// blank while scrolling"). The base 1-row guard below stays; if the
|
|
386
|
+
// scrolled-row-over-statusline overpaint resurfaces, fix it in the renderer
|
|
387
|
+
// diff (clip/erase) instead of carving more blank viewport rows.
|
|
388
|
+
const scrollGuardRows = 0;
|
|
389
|
+
const transcriptGuardRows = Math.min(guardCapacityRows, baseGuardRows + panelTransitionGuardRows + scrollGuardRows);
|
|
390
|
+
// Welcome prompt hint: a one-row band rendered INSIDE the transcript
|
|
391
|
+
// viewport (as a sibling below the content clip), so it must be part of the
|
|
392
|
+
// viewport row accounting computed right below. Left unaccounted, the slash
|
|
393
|
+
// palette close commit on the empty welcome screen painted one extra
|
|
394
|
+
// physical row (hint reappears in the same commit as the close mask), so
|
|
395
|
+
// the prompt box + statusline dipped one row and snapped back on the
|
|
396
|
+
// mask-clear commit.
|
|
397
|
+
const welcomePromptHintText = conditionalWelcomePromptHint || welcomePromptHintRef.current || '';
|
|
398
|
+
const welcomePromptHintVisible = Boolean(
|
|
399
|
+
welcomePromptHintText
|
|
400
|
+
&& !welcomePromptHintDismissed
|
|
401
|
+
&& state.items.length === 0
|
|
402
|
+
&& !hasFloatingPanel
|
|
403
|
+
&& !inputBoxHidden
|
|
404
|
+
&& !queuedVisible
|
|
405
|
+
&& !liveSpinner
|
|
406
|
+
&& !inputHint
|
|
407
|
+
);
|
|
408
|
+
// Tiny terminals: guard rows can already consume all but one viewport row
|
|
409
|
+
// (guardCapacityRows = viewportHeight - 1). transcriptContentHeight clamps
|
|
410
|
+
// to >= 1, so an unconditional hint row would paint viewportHeight + 1 rows
|
|
411
|
+
// and push the prompt/statusline down. The hint yields unless at least one
|
|
412
|
+
// content row remains beside it.
|
|
413
|
+
const welcomePromptHintRows = welcomePromptHintVisible
|
|
414
|
+
&& (viewportHeight - transcriptGuardRows) >= 2 ? 1 : 0;
|
|
415
|
+
welcomePromptHintVisibleRef.current = welcomePromptHintRows > 0;
|
|
416
|
+
// Transient hint/error on the EMPTY transcript: the guard row sits directly
|
|
417
|
+
// above the prompt box, so painting the hint there hugs the textbox one row
|
|
418
|
+
// below where the live-spinner line renders. Carve one in-viewport row ABOVE
|
|
419
|
+
// the guard row instead so the hint's baseline matches the spinner row (two
|
|
420
|
+
// rows above the box, guard row stays blank as the spacer). This is an
|
|
421
|
+
// in-viewport carve like welcomePromptHintRows — bottomReserve is untouched,
|
|
422
|
+
// so the prompt box and statusline never move. Non-empty transcripts keep the
|
|
423
|
+
// existing attach-to-last-item / guard-row fallback placements.
|
|
424
|
+
const overlayHintBandRows = overlayHintRequested
|
|
425
|
+
&& state.items.length === 0
|
|
426
|
+
&& (viewportHeight - transcriptGuardRows - welcomePromptHintRows) >= 2 ? 1 : 0;
|
|
427
|
+
// Instant panel close (slash palette): the reclaimed rows stay blank for
|
|
428
|
+
// exactly one commit via panelCloseMaskRows. The mask MUST be part of this
|
|
429
|
+
// frame's row accounting — subtract it from the transcript content height
|
|
430
|
+
// and render it as a sibling band below the content clip (where the closed
|
|
431
|
+
// panel's ink was). Rendering it inside the scrolled transcript column made
|
|
432
|
+
// the painted column taller than the accounted viewport for one frame, so
|
|
433
|
+
// the prompt/statusline dropped a row and snapped back on the mask-clear
|
|
434
|
+
// commit (the "textbox dips when the slash palette closes" bug).
|
|
435
|
+
const panelCloseMaskRows = Math.min(
|
|
436
|
+
panelCloseInkMaskRows,
|
|
437
|
+
Math.max(0, viewportHeight - transcriptGuardRows - welcomePromptHintRows - overlayHintBandRows - 1),
|
|
438
|
+
);
|
|
439
|
+
const transcriptContentHeight = Math.max(
|
|
440
|
+
1,
|
|
441
|
+
viewportHeight - transcriptGuardRows - panelCloseMaskRows - welcomePromptHintRows - overlayHintBandRows,
|
|
442
|
+
);
|
|
443
|
+
// Keep the keyboard-selection edge step anchored to the base guard. This is
|
|
444
|
+
// not a follow threshold: any positive wheel target is a reading position;
|
|
445
|
+
// only the true bottom may auto-follow a live tail.
|
|
446
|
+
const transcriptBottomSlackRows = Math.max(0, baseGuardRows);
|
|
447
|
+
transcriptBottomSlackRowsRef.current = transcriptBottomSlackRows;
|
|
448
|
+
transcriptViewportRef.current = {
|
|
449
|
+
top: WELCOME_ROWS,
|
|
450
|
+
bottom: Math.max(WELCOME_ROWS, WELCOME_ROWS + transcriptContentHeight - 1),
|
|
451
|
+
};
|
|
452
|
+
// [mixdog] Keep the live terminal row count current for the mouse handler's
|
|
453
|
+
// region routing + status-band selection clip (see onData).
|
|
454
|
+
frameRowsRef.current = Math.max(1, Number(resizeState.rows) || 24);
|
|
455
|
+
// When the prompt box is hidden (floating panel / option panel owns the
|
|
456
|
+
// bottom area), drop its stale measured rect so the mouse handler does not
|
|
457
|
+
// route presses to a prompt box that is not on screen.
|
|
458
|
+
if (inputBoxHidden) promptBoxRectRef.current = null;
|
|
459
|
+
// Toast/error text has two mutually exclusive placements:
|
|
460
|
+
// - while a live status row exists (thinking/compacting/responding), attach it
|
|
461
|
+
// to that row so the bottom cluster reserves exactly one status band;
|
|
462
|
+
// - otherwise render it into the normal one-row gap above the prompt, replacing
|
|
463
|
+
// the blank spacer instead of reserving an extra row. This keeps late errors
|
|
464
|
+
// from pushing the prompt/statusline upward, and when thinking starts the
|
|
465
|
+
// hint moves into the live row on the same render instead of double-painting.
|
|
466
|
+
// Transient hint placement while no spinner owns the band is resolved after
|
|
467
|
+
// transcript windowing (see overlayHintOnLastItem / overlayHintFallbackRow).
|
|
468
|
+
const spinnerHintWidth = inputHint
|
|
469
|
+
? Math.max(1, Math.min(Math.max(1, frameColumns - 4), Math.max(12, Math.floor(frameColumns * 0.42))))
|
|
470
|
+
: 0;
|
|
471
|
+
// When no live spinner owns a status band, the transient hint/error is drawn
|
|
472
|
+
// into the existing transcript guard row directly above the prompt. Mirror the
|
|
473
|
+
// spinner-row placement: a fixed-width right slot, not a full-width left box.
|
|
474
|
+
const guardHintWidth = inputHint
|
|
475
|
+
? Math.max(1, Math.min(Math.max(1, frameColumns - 4), Math.max(12, Math.floor(frameColumns * 0.42))))
|
|
476
|
+
: 0;
|
|
477
|
+
const transientStatusWidth = liveSpinner ? spinnerHintWidth : guardHintWidth;
|
|
478
|
+
const promptSpinnerColumns = liveSpinner && inputHint
|
|
479
|
+
? Math.max(1, frameColumns - spinnerHintWidth - 1)
|
|
480
|
+
: frameColumns;
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
textEntryPrompt,
|
|
484
|
+
hasTextEntryPrompt,
|
|
485
|
+
hasFloatingPanel,
|
|
486
|
+
expandedOptionPanel,
|
|
487
|
+
panelTransitionForBoot,
|
|
488
|
+
bootSettling,
|
|
489
|
+
projectSelectionActive,
|
|
490
|
+
inputBoxHidden,
|
|
491
|
+
liveSpinner,
|
|
492
|
+
liveSpinnerIsCommand,
|
|
493
|
+
latestToast,
|
|
494
|
+
toastHint,
|
|
495
|
+
progressHint,
|
|
496
|
+
inputHint,
|
|
497
|
+
inputHintTone,
|
|
498
|
+
latestTranscriptItem,
|
|
499
|
+
latestDoneItem,
|
|
500
|
+
SCROLL_HINT_ROWS,
|
|
501
|
+
LIVE_STATUS_ROWS,
|
|
502
|
+
currentPromptLayoutRows,
|
|
503
|
+
promptInputRows,
|
|
504
|
+
promptBoxRows,
|
|
505
|
+
STATUSLINE_ROWS,
|
|
506
|
+
PANEL_MAX_VISIBLE,
|
|
507
|
+
PANEL_CHROME_ROWS,
|
|
508
|
+
PANEL_BASE_ROWS,
|
|
509
|
+
PICKER_CHROME_ROWS,
|
|
510
|
+
textEntryKind,
|
|
511
|
+
textEntryMultiline,
|
|
512
|
+
textEntryDetailText,
|
|
513
|
+
textEntryDetailRows,
|
|
514
|
+
textEntryContentRows,
|
|
515
|
+
TEXT_ENTRY_ROWS,
|
|
516
|
+
OPTION_PANEL_EXTRA_ROWS,
|
|
517
|
+
queuedVisible,
|
|
518
|
+
promptMetaVisible,
|
|
519
|
+
promptMetaRows,
|
|
520
|
+
overlayHintRequested,
|
|
521
|
+
overlayHintRows,
|
|
522
|
+
queuedFullRows,
|
|
523
|
+
queuedRowBudget,
|
|
524
|
+
queuedCompact,
|
|
525
|
+
queuedRows,
|
|
526
|
+
INPUT_BOX_ROWS,
|
|
527
|
+
WELCOME_BANNER_ROWS,
|
|
528
|
+
SLASH_PALETTE_ROWS,
|
|
529
|
+
slashKeepsWelcomeBanner,
|
|
530
|
+
showWelcomeBanner,
|
|
531
|
+
WELCOME_ROWS,
|
|
532
|
+
baseReserve,
|
|
533
|
+
maxFloatingPanelRows,
|
|
534
|
+
desiredFloatingPanelRows,
|
|
535
|
+
floatingPanelRows,
|
|
536
|
+
pickerVisibleRows,
|
|
537
|
+
rawBottomReserve,
|
|
538
|
+
bottomClusterRows,
|
|
539
|
+
panelLayoutSignature,
|
|
540
|
+
panelTransition,
|
|
541
|
+
panelLayoutChanged,
|
|
542
|
+
panelTransitionClearRows,
|
|
543
|
+
panelTransitionGuardRows,
|
|
544
|
+
panelCloseInkMaskRows,
|
|
545
|
+
bottomReserve,
|
|
546
|
+
viewportHeight,
|
|
547
|
+
guardCapacityRows,
|
|
548
|
+
baseGuardRows,
|
|
549
|
+
scrollGuardRows,
|
|
550
|
+
transcriptGuardRows,
|
|
551
|
+
welcomePromptHintText,
|
|
552
|
+
welcomePromptHintVisible,
|
|
553
|
+
welcomePromptHintRows,
|
|
554
|
+
overlayHintBandRows,
|
|
555
|
+
panelCloseMaskRows,
|
|
556
|
+
transcriptContentHeight,
|
|
557
|
+
transcriptBottomSlackRows,
|
|
558
|
+
spinnerHintWidth,
|
|
559
|
+
guardHintWidth,
|
|
560
|
+
transientStatusWidth,
|
|
561
|
+
promptSpinnerColumns,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export const SLASH_COMMANDS = [
|
|
7
7
|
{ name: 'clear', usage: '/clear', aliases: ['new'], aliasUsage: ['new'], description: 'Start a fresh chat' },
|
|
8
|
-
{ name: 'project', usage: '/project', aliases: ['projects'], aliasUsage: ['projects'], showAliasUsage: false, description: 'Switch working directory (project)' },
|
|
8
|
+
{ name: 'project', usage: '/project', aliases: ['projects'], aliasUsage: ['projects'], showAliasUsage: false, params: '[path]', description: 'Switch working directory (project)' },
|
|
9
9
|
{ name: 'compact', usage: '/compact', description: 'Compact older conversation context' },
|
|
10
|
-
{ name: 'autoclear', usage: '/autoclear', description: 'Reduce cache-miss cost after long idle gaps' },
|
|
11
|
-
{ name: 'resume', usage: '/resume', description: 'Resume a saved chat' },
|
|
10
|
+
{ name: 'autoclear', usage: '/autoclear', params: '[on|off|duration]', description: 'Reduce cache-miss cost after long idle gaps' },
|
|
11
|
+
{ name: 'resume', usage: '/resume', params: '[id]', description: 'Resume a saved chat' },
|
|
12
12
|
{ name: 'context', usage: '/context', description: 'Show current context surface' },
|
|
13
13
|
{ name: 'usage', usage: '/usage', params: '[refresh]', description: 'Show total provider quota / balance' },
|
|
14
|
-
{ name: 'model', usage: '/model', description: 'Switch model for subsequent turns' },
|
|
14
|
+
{ name: 'model', usage: '/model', params: '[name|refresh]', description: 'Switch model for subsequent turns' },
|
|
15
15
|
{ name: 'search', usage: '/search', description: 'Set the web search provider/model' },
|
|
16
|
-
{ name: 'workflow', usage: '/workflow', description: 'Switch the active workflow' },
|
|
16
|
+
{ name: 'workflow', usage: '/workflow', params: '[name]', description: 'Switch the active workflow' },
|
|
17
17
|
{ name: 'outputstyle', usage: '/OutputStyle', aliases: ['output-style', 'style'], aliasUsage: ['style'], showAliasUsage: false, params: '[name]', description: 'Switch Lead output style' },
|
|
18
18
|
{ name: 'theme', usage: '/theme', params: '[id]', description: 'Change the TUI color theme' },
|
|
19
|
-
{ name: 'agents', usage: '/agents', description: 'Show available workflow agents' },
|
|
19
|
+
{ name: 'agents', usage: '/agents', params: '[refresh]', description: 'Show available workflow agents' },
|
|
20
20
|
{ name: 'effort', usage: '/effort', params: '[level]', description: 'Set reasoning effort for the current model' },
|
|
21
21
|
{ name: 'fast', usage: '/fast', params: '[on|off]', description: 'Toggle Fast mode for the current model' },
|
|
22
22
|
{ name: 'mcp', usage: '/mcp', description: 'Manage MCP servers and tools' },
|
|
23
23
|
{ name: 'skills', usage: '/skills', description: 'Choose a skill for the next request' },
|
|
24
|
-
{ name: 'memory', usage: '/memory', description: 'List and edit core memories' },
|
|
24
|
+
{ name: 'memory', usage: '/memory', params: '[status|core ...]', description: 'List and edit core memories' },
|
|
25
25
|
{ name: 'plugins', usage: '/plugins', description: 'Manage local plugin integrations' },
|
|
26
26
|
{ name: 'hooks', usage: '/hooks', description: 'Manage before-tool hook rules and events' },
|
|
27
27
|
{ name: 'providers', usage: '/providers', description: 'Manage auth, API keys, OAuth, and local endpoints' },
|