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,504 @@
|
|
|
1
|
+
// App view tree, extracted from App.jsx. Pure JSX assembly: the prompt
|
|
2
|
+
// input control and the full shell layout (welcome banner, transcript
|
|
3
|
+
// viewport, floating panels, prompt cluster, statusline). All data and
|
|
4
|
+
// handlers arrive via one ctx object from App().
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { isCompletedTranscriptTailAppendedThisCommit } from './live-spinner-visibility.mjs';
|
|
7
|
+
import { Box, Text } from 'ink';
|
|
8
|
+
import { theme, surfaceBackground } from '../theme.mjs';
|
|
9
|
+
import { centerLine, promptStatusColor, toolApprovalDescription } from './app-format.mjs';
|
|
10
|
+
import { TRANSCRIPT_WINDOW_TAIL_OVERSCAN_ROWS } from './transcript-window.mjs';
|
|
11
|
+
import { localPackageVersion } from '../../runtime/shared/update-checker.mjs';
|
|
12
|
+
import { Spinner } from '../components/Spinner.jsx';
|
|
13
|
+
import { StatusLine } from '../components/StatusLine.jsx';
|
|
14
|
+
import { PromptInput } from '../components/PromptInput.jsx';
|
|
15
|
+
import { QueuedCommands } from '../components/QueuedCommands.jsx';
|
|
16
|
+
import { Picker } from '../components/Picker.jsx';
|
|
17
|
+
import { SlashCommandPalette } from '../components/SlashCommandPalette.jsx';
|
|
18
|
+
import { ContextPanel } from '../components/ContextPanel.jsx';
|
|
19
|
+
import { UsagePanel } from '../components/UsagePanel.jsx';
|
|
20
|
+
import { TextEntryPanel } from '../components/TextEntryPanel.jsx';
|
|
21
|
+
import { Item } from '../components/TranscriptItem.jsx';
|
|
22
|
+
export function renderAppView(ctx) {
|
|
23
|
+
const { PANEL_MAX_VISIBLE, acceptSlashPalette, activeSlashQuery, activeTools, agentRevision, cancelChannelPrompt, cancelHookPrompt, cancelProviderPrompt, cancelSettingsPrompt, cancelSlashPalette, channelPrompt, clearPromptHint, completeSlashPalette, contextPanel, cycleWorkflowFromPrompt, exiting, expandedOptionPanel, floatingPanelRows, frameColumns, gridSelectionActiveRef, guardHintWidth, handlePromptEscape, handlePromptHistoryNavigate, handlePromptInterrupt, handlePromptPaste, hookPrompt, initialStatusLine, inputBoxHidden, inputHint, inputHintTone, liveSpinner, onPromptDraftChange, onSubmit, overlayHintAttachItemIndex, overlayHintBandRows, overlayHintFallbackRow, overlayHintOnLastItem, panelCloseMaskRows, panelInkMaskEpoch, panelTransitionClearRows, panelTransitionEpoch, picker, pickerOpenedFromEnterRef, pickerOpenedFromEnterTimerRef, pickerVisibleRows, promptBoxRectRef, promptBoxRows, promptDraft, promptDraftOverride, promptMetaVisible, promptMouseSelectionRef, promptSelectionRef, promptSpinnerColumns, promptValueRef, providerPrompt, queuedCompact, queuedVisible, renderedTranscriptItems, resizeEpoch, resizeState, restoreQueuedToPrompt, setPicker, setSlashIndex, setTextEntryLayoutRows, settingsPrompt, showWelcomeBanner, slashCommands, slashIndex, slashPaletteOpen, state, statuslineStats, store, toolApproval, toolOutputExpanded, transcriptContentHeight, transcriptGuardRows, transcriptMeasureRef, transcriptTailPinned, transcriptWindow, transientStatusWidth, tuiReady, usagePanel, viewportHeight, welcomePromptHintRows, welcomePromptHintText } = ctx; /* DESTRUCTURE */
|
|
24
|
+
const promptInputControl = (
|
|
25
|
+
<PromptInput
|
|
26
|
+
onSubmit={onSubmit}
|
|
27
|
+
disabled={exiting || !!picker || !!toolApproval || !tuiReady}
|
|
28
|
+
onDraftChange={onPromptDraftChange}
|
|
29
|
+
interruptActive={state.busy}
|
|
30
|
+
onInterrupt={handlePromptInterrupt}
|
|
31
|
+
initialValue={promptDraft}
|
|
32
|
+
draftOverride={promptDraftOverride}
|
|
33
|
+
valueRef={promptValueRef}
|
|
34
|
+
selectionRef={promptSelectionRef}
|
|
35
|
+
boxRectRef={promptBoxRectRef}
|
|
36
|
+
mouseSelectionRef={promptMouseSelectionRef}
|
|
37
|
+
suppressShiftNavRef={gridSelectionActiveRef}
|
|
38
|
+
hint=""
|
|
39
|
+
hintTone={inputHintTone}
|
|
40
|
+
mask={false}
|
|
41
|
+
onEscape={handlePromptEscape}
|
|
42
|
+
onTab={cycleWorkflowFromPrompt}
|
|
43
|
+
onPasteText={handlePromptPaste}
|
|
44
|
+
onHistoryNavigate={handlePromptHistoryNavigate}
|
|
45
|
+
// Palette stays MOUNTED with 0 matches (stable height, no flicker), but
|
|
46
|
+
// key capture (Enter/arrows/Esc routing) only engages when a command can
|
|
47
|
+
// actually be accepted — otherwise Enter must submit the raw text as
|
|
48
|
+
// before instead of dead-ending in the palette accept path.
|
|
49
|
+
commandPaletteActive={slashPaletteOpen && slashCommands.length > 0}
|
|
50
|
+
onCommandPaletteNavigate={(direction) => {
|
|
51
|
+
setSlashIndex((index) => {
|
|
52
|
+
const total = slashCommands.length;
|
|
53
|
+
if (total === 0) return 0;
|
|
54
|
+
if (direction === 'home') return 0;
|
|
55
|
+
if (direction === 'end') return total - 1;
|
|
56
|
+
const step = direction === 'left'
|
|
57
|
+
? -1
|
|
58
|
+
: direction === 'right'
|
|
59
|
+
? 1
|
|
60
|
+
: Number(direction) || 0;
|
|
61
|
+
if (step === 1 || step === -1) return (index + step + total) % total;
|
|
62
|
+
return Math.max(0, Math.min(total - 1, index + step));
|
|
63
|
+
});
|
|
64
|
+
}}
|
|
65
|
+
onCommandPaletteAccept={acceptSlashPalette}
|
|
66
|
+
onCommandPaletteCancel={cancelSlashPalette}
|
|
67
|
+
onCommandPaletteComplete={completeSlashPalette}
|
|
68
|
+
onRestoreQueued={(currentText) => restoreQueuedToPrompt({ restoreDraft: true, showHint: false, currentText })}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
// Fullscreen layout: a full-height column (height = terminal rows) pins the
|
|
74
|
+
// input cluster + statusline to the physical bottom (flexShrink={0}), while
|
|
75
|
+
// the transcript fills the space above and is bottom-aligned so messages
|
|
76
|
+
// stack up from just over the input. A top flexGrow spacer sinks the whole
|
|
77
|
+
// stack to the bottom; the transcript itself is a fixed-height clipping
|
|
78
|
+
// viewport (see viewportHeight above).
|
|
79
|
+
<Box flexDirection="column" width={frameColumns} height={resizeState.rows} backgroundColor={surfaceBackground()}>
|
|
80
|
+
{/* Empty-transcript header stays outside the bottom-anchored viewport and
|
|
81
|
+
has its own reserved rows, so it cannot steal space from the input. */}
|
|
82
|
+
{showWelcomeBanner ? (
|
|
83
|
+
<Box flexDirection="column" height={7} flexShrink={0} marginTop={3} marginBottom={1} backgroundColor={surfaceBackground()}>
|
|
84
|
+
<Text color={theme.text} bold>{centerLine('███╗ ███╗██╗██╗ ██╗██████╗ ██████╗ ██████╗ ', frameColumns)}</Text>
|
|
85
|
+
<Text color={theme.text} bold>{centerLine('████╗ ████║██║╚██╗██╔╝██╔══██╗██╔═══██╗██╔════╝ ', frameColumns)}</Text>
|
|
86
|
+
<Text color={theme.logo ?? theme.claude} bold>{centerLine('██╔████╔██║██║ ╚███╔╝ ██║ ██║██║ ██║██║ ███╗', frameColumns)}</Text>
|
|
87
|
+
<Text color={theme.logo ?? theme.claude} bold>{centerLine('██║╚██╔╝██║██║ ██╔██╗ ██║ ██║██║ ██║██║ ██║', frameColumns)}</Text>
|
|
88
|
+
<Text color={theme.logo ?? theme.claude} bold>{centerLine('██║ ╚═╝ ██║██║██╔╝ ██╗██████╔╝╚██████╔╝╚██████╔╝', frameColumns)}</Text>
|
|
89
|
+
<Box height={1} flexShrink={0} />
|
|
90
|
+
<Text color={theme.inactive}>{centerLine(`mixdog coding agent · v${localPackageVersion()} · ${state.cwd}`, frameColumns, 4)}</Text>
|
|
91
|
+
</Box>
|
|
92
|
+
) : null}
|
|
93
|
+
|
|
94
|
+
{/* Transcript viewport — a BOUNDED, fixed-height clipping box. The explicit
|
|
95
|
+
numeric height + overflow:hidden is what lets ink actually slice the
|
|
96
|
+
off-screen rows (output.clip in render-node-to-output.js), so older
|
|
97
|
+
rows can never overprint newer ones. justifyContent flex-end keeps the
|
|
98
|
+
newest content pinned to the bottom edge; older content overflows the
|
|
99
|
+
TOP and is clipped. flexShrink lets it yield rows to the live status /
|
|
100
|
+
a multi-line input rather than overflow the screen. */}
|
|
101
|
+
<Box
|
|
102
|
+
flexDirection="column"
|
|
103
|
+
width="100%"
|
|
104
|
+
height={viewportHeight}
|
|
105
|
+
flexGrow={0}
|
|
106
|
+
flexShrink={1}
|
|
107
|
+
overflow="hidden"
|
|
108
|
+
justifyContent="flex-end"
|
|
109
|
+
>
|
|
110
|
+
<Box
|
|
111
|
+
flexDirection="column"
|
|
112
|
+
width="100%"
|
|
113
|
+
height={transcriptContentHeight}
|
|
114
|
+
flexShrink={0}
|
|
115
|
+
overflow="hidden"
|
|
116
|
+
justifyContent="flex-end"
|
|
117
|
+
>
|
|
118
|
+
{/* Wheel scroll: with the viewport bottom-anchored (flex-end), a NEGATIVE
|
|
119
|
+
marginBottom pushes the transcript column DOWN past the bottom edge,
|
|
120
|
+
bringing older content above the window into view (overflow hidden
|
|
121
|
+
clips the newest rows that slide below). 0 = newest content pinned to
|
|
122
|
+
the bottom. (marginTop has no effect under flex-end — the bottom edge
|
|
123
|
+
stays fixed — so the scroll axis here is marginBottom, not marginTop.)
|
|
124
|
+
scrollOffset is clamped ≥ 0 by the wheel handler; a new turn snaps it
|
|
125
|
+
back to 0. */}
|
|
126
|
+
<Box flexDirection="column" width="100%" flexShrink={0} marginBottom={-transcriptWindow.effectiveScrollOffset}>
|
|
127
|
+
{/*
|
|
128
|
+
* Transcript windowing: render only the rows around the viewport rather
|
|
129
|
+
* than the full state.items list. A cheap bottom spacer preserves the
|
|
130
|
+
* same scroll coordinate when the visible window is in older history;
|
|
131
|
+
* items above the window are off-screen and omitted entirely.
|
|
132
|
+
* MAX cap: TRANSCRIPT_WINDOW_MAX_ITEMS items (env MIXDOG_TUI_TRANSCRIPT_WINDOW_ITEMS).
|
|
133
|
+
* OVERSCAN: TRANSCRIPT_WINDOW_OVERSCAN_ROWS extra rows above the viewport so
|
|
134
|
+
* fast wheel scrolls don't show a blank gap before re-render.
|
|
135
|
+
*/}
|
|
136
|
+
{renderedTranscriptItems.map((item, i, arr) => {
|
|
137
|
+
const measureRef = transcriptMeasureRef(item);
|
|
138
|
+
const attachOverlayHint = overlayHintOnLastItem && i === overlayHintAttachItemIndex;
|
|
139
|
+
const itemNode = (
|
|
140
|
+
<Item
|
|
141
|
+
item={item}
|
|
142
|
+
prevKind={i > 0
|
|
143
|
+
? arr[i - 1].kind
|
|
144
|
+
: (state.transcriptViewItems || state.items)[transcriptWindow.startIndex - 1]?.kind ?? null}
|
|
145
|
+
columns={frameColumns}
|
|
146
|
+
toolOutputExpanded={toolOutputExpanded}
|
|
147
|
+
rightMessage={attachOverlayHint ? inputHint : ''}
|
|
148
|
+
rightTone={attachOverlayHint ? inputHintTone : 'info'}
|
|
149
|
+
rightMessageWidth={attachOverlayHint ? (guardHintWidth || transientStatusWidth || 24) : 24}
|
|
150
|
+
themeEpoch={state.themeEpoch || 0}
|
|
151
|
+
streamingWindowRows={transcriptTailPinned && item.id === state.streamingTail?.id
|
|
152
|
+
? transcriptContentHeight + TRANSCRIPT_WINDOW_TAIL_OVERSCAN_ROWS
|
|
153
|
+
: 0}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
// When measured-rows is on, wrap each row in a zero-cost flex column
|
|
157
|
+
// whose ref exposes the row's REAL Yoga height to the harvest effect.
|
|
158
|
+
// The wrapper adds no rows of its own (it shrink-wraps the child) and
|
|
159
|
+
// is omitted entirely when the feature is disabled so the default
|
|
160
|
+
// render tree is byte-for-byte unchanged on the off path.
|
|
161
|
+
return measureRef ? (
|
|
162
|
+
<Box key={item.id} ref={measureRef} flexDirection="column" flexShrink={0}>
|
|
163
|
+
{itemNode}
|
|
164
|
+
</Box>
|
|
165
|
+
) : (
|
|
166
|
+
<React.Fragment key={item.id}>{itemNode}</React.Fragment>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
{transcriptWindow.bottomSpacerRows > 0 ? (
|
|
170
|
+
<Box height={transcriptWindow.bottomSpacerRows} flexShrink={0} />
|
|
171
|
+
) : null}
|
|
172
|
+
</Box>
|
|
173
|
+
</Box>
|
|
174
|
+
{welcomePromptHintRows > 0 ? (
|
|
175
|
+
<Box height={1} flexShrink={0} width="100%" overflow="hidden">
|
|
176
|
+
<Text color={theme.inactive} wrap="truncate">{centerLine(welcomePromptHintText, frameColumns, 2)}</Text>
|
|
177
|
+
</Box>
|
|
178
|
+
) : null}
|
|
179
|
+
{panelCloseMaskRows > 0 ? (
|
|
180
|
+
<Box
|
|
181
|
+
height={panelCloseMaskRows}
|
|
182
|
+
flexShrink={0}
|
|
183
|
+
width="100%"
|
|
184
|
+
overflow="hidden"
|
|
185
|
+
backgroundColor={surfaceBackground()}
|
|
186
|
+
/>
|
|
187
|
+
) : null}
|
|
188
|
+
{overlayHintBandRows > 0 ? (
|
|
189
|
+
<Box height={1} flexShrink={0} backgroundColor={surfaceBackground()} flexDirection="row" width="100%" overflow="hidden">
|
|
190
|
+
<Box flexGrow={1} flexShrink={1} overflow="hidden" />
|
|
191
|
+
<Box flexShrink={0} width={guardHintWidth || 1} marginLeft={1} marginRight={1} justifyContent="flex-end" overflow="hidden">
|
|
192
|
+
<Text color={promptStatusColor(inputHintTone)} wrap="truncate">{inputHint}</Text>
|
|
193
|
+
</Box>
|
|
194
|
+
</Box>
|
|
195
|
+
) : null}
|
|
196
|
+
{transcriptGuardRows > 0 ? (
|
|
197
|
+
<Box height={transcriptGuardRows} flexShrink={0} backgroundColor={surfaceBackground()} flexDirection="row" width="100%" overflow="hidden">
|
|
198
|
+
<Box flexGrow={1} flexShrink={1} overflow="hidden" />
|
|
199
|
+
{overlayHintFallbackRow && overlayHintBandRows === 0 ? (
|
|
200
|
+
<Box flexShrink={0} width={guardHintWidth || 1} marginLeft={1} marginRight={1} justifyContent="flex-end" overflow="hidden">
|
|
201
|
+
<Text color={promptStatusColor(inputHintTone)} wrap="truncate">{inputHint}</Text>
|
|
202
|
+
</Box>
|
|
203
|
+
) : null}
|
|
204
|
+
</Box>
|
|
205
|
+
) : null}
|
|
206
|
+
</Box>
|
|
207
|
+
|
|
208
|
+
{/* Live reasoning and transient status live just above the prompt: reasoning
|
|
209
|
+
on the left, short-lived copy/error/info messages on the right. */}
|
|
210
|
+
|
|
211
|
+
{/* Bottom bar — pinned to the physical bottom, never moves. Floating
|
|
212
|
+
panels use their actual rendered height and shrink before the prompt
|
|
213
|
+
can move; overflow is clipped from the top while the panel remains
|
|
214
|
+
bottom-aligned against the prompt. */}
|
|
215
|
+
<Box flexDirection="column" flexShrink={0} width="100%" backgroundColor={surfaceBackground()}>
|
|
216
|
+
{panelTransitionClearRows > 0 ? (
|
|
217
|
+
<Box height={panelTransitionClearRows} flexShrink={0} width="100%" overflow="hidden" backgroundColor={surfaceBackground()} />
|
|
218
|
+
) : null}
|
|
219
|
+
{floatingPanelRows > 0 ? (
|
|
220
|
+
<Box flexDirection="column" flexShrink={0} height={floatingPanelRows} overflow="hidden" justifyContent="flex-end" backgroundColor={surfaceBackground()}>
|
|
221
|
+
{toolApproval ? (
|
|
222
|
+
<Picker
|
|
223
|
+
items={[
|
|
224
|
+
{ value: 'deny', label: 'Deny', marker: '×', markerColor: theme.error, description: 'block this tool call' },
|
|
225
|
+
{ value: 'approve', label: 'Approve once', marker: '✓', markerColor: theme.success, description: 'run this tool call' },
|
|
226
|
+
]}
|
|
227
|
+
onSelect={(value) => {
|
|
228
|
+
store.resolveToolApproval?.(toolApproval.id, {
|
|
229
|
+
approved: value === 'approve',
|
|
230
|
+
reason: value === 'approve' ? 'approved by user' : 'denied by user',
|
|
231
|
+
});
|
|
232
|
+
}}
|
|
233
|
+
onCancel={() => {
|
|
234
|
+
store.resolveToolApproval?.(toolApproval.id, { approved: false, reason: 'denied by user' });
|
|
235
|
+
}}
|
|
236
|
+
onKey={(input) => {
|
|
237
|
+
const value = String(input || '').trim().toLowerCase();
|
|
238
|
+
if (value === 'a' || value === 'y') {
|
|
239
|
+
store.resolveToolApproval?.(toolApproval.id, { approved: true, reason: 'approved by user' });
|
|
240
|
+
} else if (value === 'd' || value === 'n') {
|
|
241
|
+
store.resolveToolApproval?.(toolApproval.id, { approved: false, reason: 'denied by user' });
|
|
242
|
+
}
|
|
243
|
+
}}
|
|
244
|
+
title="Tool approval"
|
|
245
|
+
description={toolApprovalDescription(toolApproval)}
|
|
246
|
+
help="↑/↓ Select · Enter Choose · a/y Approve · d/n/Esc Deny"
|
|
247
|
+
columns={frameColumns}
|
|
248
|
+
labelWidth={18}
|
|
249
|
+
initialIndex={0}
|
|
250
|
+
indexMode="never"
|
|
251
|
+
visibleCount={2}
|
|
252
|
+
fillHeight={expandedOptionPanel}
|
|
253
|
+
/>
|
|
254
|
+
) : picker ? (
|
|
255
|
+
<Picker
|
|
256
|
+
key={picker.pickerKey}
|
|
257
|
+
items={picker.items}
|
|
258
|
+
onSelect={(value, item) => {
|
|
259
|
+
pickerOpenedFromEnterRef.current = true;
|
|
260
|
+
if (pickerOpenedFromEnterTimerRef.current) {
|
|
261
|
+
clearTimeout(pickerOpenedFromEnterTimerRef.current);
|
|
262
|
+
pickerOpenedFromEnterTimerRef.current = null;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
if (picker.onSelect) picker.onSelect(value, item);
|
|
266
|
+
} finally {
|
|
267
|
+
pickerOpenedFromEnterTimerRef.current = setTimeout(() => {
|
|
268
|
+
pickerOpenedFromEnterRef.current = false;
|
|
269
|
+
pickerOpenedFromEnterTimerRef.current = null;
|
|
270
|
+
}, 3000);
|
|
271
|
+
}
|
|
272
|
+
}}
|
|
273
|
+
onCancel={() => {
|
|
274
|
+
if (picker.onCancel) picker.onCancel();
|
|
275
|
+
else {
|
|
276
|
+
setPicker(null);
|
|
277
|
+
clearPromptHint();
|
|
278
|
+
}
|
|
279
|
+
}}
|
|
280
|
+
onLeft={picker.onLeft}
|
|
281
|
+
onRight={picker.onRight}
|
|
282
|
+
onTab={picker.onTab}
|
|
283
|
+
onKey={picker.onKey}
|
|
284
|
+
onHighlight={picker.onHighlight}
|
|
285
|
+
title={picker.title}
|
|
286
|
+
description={picker.description}
|
|
287
|
+
footer={picker.footer}
|
|
288
|
+
footerGapRows={picker.footerGapRows}
|
|
289
|
+
help={picker.help}
|
|
290
|
+
columns={frameColumns}
|
|
291
|
+
labelWidth={picker.labelWidth}
|
|
292
|
+
metaWidth={picker.metaWidth}
|
|
293
|
+
initialIndex={picker.initialIndex}
|
|
294
|
+
indexMode={picker.indexMode}
|
|
295
|
+
visibleCount={pickerVisibleRows}
|
|
296
|
+
fillHeight={expandedOptionPanel}
|
|
297
|
+
themeEpoch={state.themeEpoch || 0}
|
|
298
|
+
confirmBar={picker.confirmBar}
|
|
299
|
+
/>
|
|
300
|
+
) : contextPanel ? (
|
|
301
|
+
<ContextPanel
|
|
302
|
+
rows={contextPanel.rows}
|
|
303
|
+
title={contextPanel.title}
|
|
304
|
+
detail={contextPanel.detail}
|
|
305
|
+
columns={frameColumns}
|
|
306
|
+
fillHeight={expandedOptionPanel}
|
|
307
|
+
/>
|
|
308
|
+
) : usagePanel ? (
|
|
309
|
+
<UsagePanel
|
|
310
|
+
dashboard={usagePanel}
|
|
311
|
+
columns={frameColumns}
|
|
312
|
+
fillHeight={expandedOptionPanel}
|
|
313
|
+
panelRows={floatingPanelRows}
|
|
314
|
+
/>
|
|
315
|
+
) : slashPaletteOpen ? (
|
|
316
|
+
<SlashCommandPalette
|
|
317
|
+
commands={slashCommands}
|
|
318
|
+
selectedIndex={slashIndex}
|
|
319
|
+
title="Commands"
|
|
320
|
+
columns={frameColumns}
|
|
321
|
+
query={activeSlashQuery}
|
|
322
|
+
/>
|
|
323
|
+
) : providerPrompt ? (
|
|
324
|
+
<TextEntryPanel
|
|
325
|
+
title={providerPrompt.kind === 'api-key'
|
|
326
|
+
? `${providerPrompt.mode === 'replace' ? 'Replace' : 'Set'} API key · ${providerPrompt.label}`
|
|
327
|
+
: providerPrompt.kind === 'oauth-code'
|
|
328
|
+
? providerPrompt.label
|
|
329
|
+
: providerPrompt.kind === 'openai-usage-session'
|
|
330
|
+
? 'OpenAI Usage · Session Key'
|
|
331
|
+
: `Base URL · ${providerPrompt.label}`}
|
|
332
|
+
hint={providerPrompt.kind === 'api-key'
|
|
333
|
+
? [
|
|
334
|
+
providerPrompt.envName ? `Env: ${providerPrompt.envName}` : '',
|
|
335
|
+
providerPrompt.source ? `Current: ${providerPrompt.source}` : '',
|
|
336
|
+
'Stored in the OS keychain.',
|
|
337
|
+
].filter(Boolean).join(' · ')
|
|
338
|
+
: providerPrompt.kind === 'oauth-code'
|
|
339
|
+
? (providerPrompt.hint || 'Paste the browser code.')
|
|
340
|
+
: providerPrompt.kind === 'openai-usage-session'
|
|
341
|
+
? 'Paste an OpenAI dashboard/session key for the undocumented credit lookup. It is stored in the OS keychain.'
|
|
342
|
+
: `Default: ${providerPrompt.defaultURL}`}
|
|
343
|
+
detail={providerPrompt.detail || ''}
|
|
344
|
+
mask={providerPrompt.kind === 'api-key' || providerPrompt.kind === 'openai-usage-session'}
|
|
345
|
+
columns={frameColumns}
|
|
346
|
+
actionLabel={providerPrompt.kind === 'oauth-code' ? 'continue' : 'save'}
|
|
347
|
+
promptLabel={providerPrompt.kind === 'api-key'
|
|
348
|
+
? 'API key > '
|
|
349
|
+
: providerPrompt.kind === 'oauth-code'
|
|
350
|
+
? 'Paste code here if prompted > '
|
|
351
|
+
: providerPrompt.kind === 'openai-usage-session'
|
|
352
|
+
? 'Session key > '
|
|
353
|
+
: 'Base URL > '}
|
|
354
|
+
onSubmit={onSubmit}
|
|
355
|
+
onCancel={cancelProviderPrompt}
|
|
356
|
+
/>
|
|
357
|
+
) : channelPrompt ? (
|
|
358
|
+
<TextEntryPanel
|
|
359
|
+
title={channelPrompt.label}
|
|
360
|
+
hint={channelPrompt.hint || 'Save channel setting.'}
|
|
361
|
+
mask={channelPrompt.kind === 'discord-token' || channelPrompt.kind === 'telegram-token' || channelPrompt.kind === 'webhook-token'}
|
|
362
|
+
columns={frameColumns}
|
|
363
|
+
promptLabel="Value > "
|
|
364
|
+
onSubmit={onSubmit}
|
|
365
|
+
onCancel={cancelChannelPrompt}
|
|
366
|
+
/>
|
|
367
|
+
) : hookPrompt ? (
|
|
368
|
+
<TextEntryPanel
|
|
369
|
+
title={hookPrompt.label}
|
|
370
|
+
hint={hookPrompt.hint || 'Save hook setting.'}
|
|
371
|
+
columns={frameColumns}
|
|
372
|
+
promptLabel="Value > "
|
|
373
|
+
onSubmit={onSubmit}
|
|
374
|
+
onCancel={cancelHookPrompt}
|
|
375
|
+
/>
|
|
376
|
+
) : settingsPrompt ? (
|
|
377
|
+
<TextEntryPanel
|
|
378
|
+
title={settingsPrompt.label}
|
|
379
|
+
hint={settingsPrompt.hint || 'Save setting.'}
|
|
380
|
+
columns={frameColumns}
|
|
381
|
+
initialValue={settingsPrompt.initialValue || ''}
|
|
382
|
+
multiline={settingsPrompt.kind === 'core-add' || settingsPrompt.kind === 'core-edit'}
|
|
383
|
+
maxContentRows={PANEL_MAX_VISIBLE}
|
|
384
|
+
onContentRowsChange={setTextEntryLayoutRows}
|
|
385
|
+
actionLabel={settingsPrompt.kind === 'skill-use'
|
|
386
|
+
? 'run'
|
|
387
|
+
: settingsPrompt.kind === 'autoclear-provider'
|
|
388
|
+
? 'save'
|
|
389
|
+
: settingsPrompt.kind === 'project-new'
|
|
390
|
+
? 'open'
|
|
391
|
+
: settingsPrompt.kind === 'project-create-confirm'
|
|
392
|
+
? 'confirm'
|
|
393
|
+
: settingsPrompt.kind === 'project-rename'
|
|
394
|
+
? 'rename'
|
|
395
|
+
: settingsPrompt.kind === 'core-add'
|
|
396
|
+
? 'add'
|
|
397
|
+
: settingsPrompt.kind === 'core-edit'
|
|
398
|
+
? 'save'
|
|
399
|
+
: settingsPrompt.kind === 'core-delete-confirm'
|
|
400
|
+
? 'confirm'
|
|
401
|
+
: 'save'}
|
|
402
|
+
promptLabel={settingsPrompt.kind === 'skill-use'
|
|
403
|
+
? 'Command > '
|
|
404
|
+
: settingsPrompt.kind === 'autoclear-provider'
|
|
405
|
+
? 'Duration > '
|
|
406
|
+
: settingsPrompt.kind === 'project-new'
|
|
407
|
+
? 'Path > '
|
|
408
|
+
: settingsPrompt.kind === 'project-create-confirm'
|
|
409
|
+
? 'Create? (y/n) > '
|
|
410
|
+
: settingsPrompt.kind === 'project-rename'
|
|
411
|
+
? 'Name > '
|
|
412
|
+
: settingsPrompt.kind === 'core-add'
|
|
413
|
+
? 'Sentence > '
|
|
414
|
+
: settingsPrompt.kind === 'core-edit'
|
|
415
|
+
? 'Sentence > '
|
|
416
|
+
: settingsPrompt.kind === 'core-delete-confirm'
|
|
417
|
+
? 'Delete? (y/n) > '
|
|
418
|
+
: 'Value > '}
|
|
419
|
+
onSubmit={onSubmit}
|
|
420
|
+
onCancel={cancelSettingsPrompt}
|
|
421
|
+
/>
|
|
422
|
+
) : null}
|
|
423
|
+
</Box>
|
|
424
|
+
) : null}
|
|
425
|
+
{!inputBoxHidden ? (
|
|
426
|
+
<>
|
|
427
|
+
{promptMetaVisible ? (
|
|
428
|
+
<>
|
|
429
|
+
<Box
|
|
430
|
+
marginTop={0}
|
|
431
|
+
marginBottom={0}
|
|
432
|
+
height={1}
|
|
433
|
+
width="100%"
|
|
434
|
+
flexDirection="row"
|
|
435
|
+
backgroundColor={surfaceBackground()}
|
|
436
|
+
>
|
|
437
|
+
<Box flexGrow={1} flexShrink={1} overflow="hidden">
|
|
438
|
+
{liveSpinner ? (
|
|
439
|
+
<Spinner
|
|
440
|
+
verb={liveSpinner.verb}
|
|
441
|
+
startedAt={liveSpinner.startedAt}
|
|
442
|
+
outputTokens={liveSpinner?.outputTokens ?? liveSpinner?.tokens ?? 0}
|
|
443
|
+
thinking={!!(state.thinking || liveSpinner?.thinking)}
|
|
444
|
+
thinkingActiveSince={liveSpinner?.thinkingSegmentStartedAt ?? 0}
|
|
445
|
+
mode={liveSpinner?.mode || 'responding'}
|
|
446
|
+
columns={promptSpinnerColumns}
|
|
447
|
+
marginTop={0}
|
|
448
|
+
/>
|
|
449
|
+
) : null}
|
|
450
|
+
</Box>
|
|
451
|
+
{inputHint ? (
|
|
452
|
+
<Box flexShrink={0} width={transientStatusWidth || 1} marginLeft={1} marginRight={1} justifyContent="flex-end" overflow="hidden">
|
|
453
|
+
<Text color={promptStatusColor(inputHintTone)} wrap="truncate">{inputHint}</Text>
|
|
454
|
+
</Box>
|
|
455
|
+
) : null}
|
|
456
|
+
</Box>
|
|
457
|
+
<Box height={1} width="100%" backgroundColor={surfaceBackground()} />
|
|
458
|
+
</>
|
|
459
|
+
) : null}
|
|
460
|
+
{queuedVisible ? (
|
|
461
|
+
<QueuedCommands queued={state.queued} columns={frameColumns} compact={queuedCompact} />
|
|
462
|
+
) : null}
|
|
463
|
+
<Box
|
|
464
|
+
marginTop={0}
|
|
465
|
+
width="100%"
|
|
466
|
+
height={promptBoxRows}
|
|
467
|
+
flexShrink={0}
|
|
468
|
+
borderStyle="round"
|
|
469
|
+
borderColor={theme.promptBorder}
|
|
470
|
+
backgroundColor={surfaceBackground()}
|
|
471
|
+
paddingX={1}
|
|
472
|
+
>
|
|
473
|
+
{promptInputControl}
|
|
474
|
+
</Box>
|
|
475
|
+
</>
|
|
476
|
+
) : null}
|
|
477
|
+
<StatusLine
|
|
478
|
+
sessionId={state.sessionId}
|
|
479
|
+
clientHostPid={state.ownerClientHostPid || state.clientHostPid}
|
|
480
|
+
provider={state.provider}
|
|
481
|
+
model={state.model}
|
|
482
|
+
effort={state.effort}
|
|
483
|
+
fast={state.fast}
|
|
484
|
+
cwd={state.cwd}
|
|
485
|
+
stats={statuslineStats}
|
|
486
|
+
contextWindow={state.contextWindow}
|
|
487
|
+
displayContextWindow={state.displayContextWindow}
|
|
488
|
+
compactBoundaryTokens={state.compactBoundaryTokens}
|
|
489
|
+
autoCompactTokenLimit={state.autoCompactTokenLimit}
|
|
490
|
+
rawContextWindow={state.rawContextWindow}
|
|
491
|
+
resizeEpoch={resizeEpoch}
|
|
492
|
+
agentRevision={agentRevision}
|
|
493
|
+
agentWorkers={state.agentWorkers}
|
|
494
|
+
agentJobs={state.agentJobs}
|
|
495
|
+
activeTools={activeTools}
|
|
496
|
+
initialLine={initialStatusLine}
|
|
497
|
+
workflow={state.workflow}
|
|
498
|
+
remoteEnabled={state.remoteEnabled === true}
|
|
499
|
+
themeEpoch={state.themeEpoch || 0}
|
|
500
|
+
/>
|
|
501
|
+
</Box>
|
|
502
|
+
</Box>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
@@ -309,12 +309,13 @@ export function createChannelPickers({
|
|
|
309
309
|
value: 'webhook-endpoint',
|
|
310
310
|
label: 'Webhook endpoint',
|
|
311
311
|
// Relay tunnel: public exposure is automatic, so the endpoint is
|
|
312
|
-
// "On" whenever the webhook server itself is enabled.
|
|
312
|
+
// "On" whenever the webhook server itself is enabled. Info-only row:
|
|
313
|
+
// endpoint management lives in the desktop Webhooks page.
|
|
313
314
|
meta: setup.webhook?.enabled === false ? 'Off' : 'On',
|
|
314
315
|
description: setup.webhook?.publicUrl
|
|
315
316
|
? setup.webhook.publicUrl
|
|
316
317
|
: 'Mixdog relay tunnel — URL assigned on first run',
|
|
317
|
-
_action:
|
|
318
|
+
_action: null,
|
|
318
319
|
},
|
|
319
320
|
];
|
|
320
321
|
|
|
@@ -361,12 +362,6 @@ export function createChannelPickers({
|
|
|
361
362
|
});
|
|
362
363
|
return;
|
|
363
364
|
}
|
|
364
|
-
if (item._action === 'webhook-endpoint') {
|
|
365
|
-
void openChannelSetupPicker('webhook-endpoint', {
|
|
366
|
-
...(options.onboarding ? { onboarding: true, confirmBar: options.confirmBar || null } : {}),
|
|
367
|
-
returnTo: () => reopenRoot({ highlightValue: 'webhook-endpoint' }),
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
365
|
} catch (e) {
|
|
371
366
|
store.pushNotice(`channels update failed: ${e?.message || e}`, 'error');
|
|
372
367
|
}
|