@bubblebrain-ai/bubble 0.0.10 → 0.0.12
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/dist/agent.d.ts +1 -0
- package/dist/agent.js +6 -2
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +31 -3
- package/dist/feedback/collect.d.ts +7 -0
- package/dist/feedback/collect.js +119 -0
- package/dist/feedback/config.d.ts +14 -0
- package/dist/feedback/config.js +16 -0
- package/dist/feedback/redact.d.ts +1 -0
- package/dist/feedback/redact.js +25 -0
- package/dist/feedback/submit.d.ts +6 -0
- package/dist/feedback/submit.js +43 -0
- package/dist/feedback/types.d.ts +22 -0
- package/dist/feishu/agent-host/approval-card.d.ts +11 -0
- package/dist/feishu/agent-host/approval-card.js +46 -0
- package/dist/feishu/agent-host/approval-ui.d.ts +59 -0
- package/dist/feishu/agent-host/approval-ui.js +214 -0
- package/dist/feishu/agent-host/run-driver.d.ts +51 -0
- package/dist/feishu/agent-host/run-driver.js +302 -0
- package/dist/feishu/agent-host/runtime-deps.d.ts +33 -0
- package/dist/feishu/agent-host/runtime-deps.js +8 -0
- package/dist/feishu/card/budget.d.ts +40 -0
- package/dist/feishu/card/budget.js +134 -0
- package/dist/feishu/card/renderer.d.ts +29 -0
- package/dist/feishu/card/renderer.js +245 -0
- package/dist/feishu/card/run-state-types.d.ts +49 -0
- package/dist/feishu/card/run-state-types.js +15 -0
- package/dist/feishu/card/run-state.d.ts +21 -0
- package/dist/feishu/card/run-state.js +217 -0
- package/dist/feishu/channel/channel.d.ts +52 -0
- package/dist/feishu/channel/channel.js +74 -0
- package/dist/feishu/config.d.ts +24 -0
- package/dist/feishu/config.js +97 -0
- package/dist/feishu/format.d.ts +6 -0
- package/dist/feishu/format.js +14 -0
- package/dist/feishu/index.d.ts +4 -0
- package/dist/feishu/index.js +4 -0
- package/dist/feishu/logger.d.ts +31 -0
- package/dist/feishu/logger.js +62 -0
- package/dist/feishu/paths.d.ts +12 -0
- package/dist/feishu/paths.js +38 -0
- package/dist/feishu/process-registry.d.ts +29 -0
- package/dist/feishu/process-registry.js +90 -0
- package/dist/feishu/router/commands.d.ts +38 -0
- package/dist/feishu/router/commands.js +286 -0
- package/dist/feishu/router/event-router.d.ts +40 -0
- package/dist/feishu/router/event-router.js +208 -0
- package/dist/feishu/router/whitelist.d.ts +23 -0
- package/dist/feishu/router/whitelist.js +20 -0
- package/dist/feishu/runtime/active-runs.d.ts +32 -0
- package/dist/feishu/runtime/active-runs.js +84 -0
- package/dist/feishu/runtime/pending-queue.d.ts +36 -0
- package/dist/feishu/runtime/pending-queue.js +98 -0
- package/dist/feishu/runtime/process-pool.d.ts +29 -0
- package/dist/feishu/runtime/process-pool.js +49 -0
- package/dist/feishu/schema.d.ts +17 -0
- package/dist/feishu/schema.js +252 -0
- package/dist/feishu/scope/scope-registry.d.ts +39 -0
- package/dist/feishu/scope/scope-registry.js +148 -0
- package/dist/feishu/scope/session-binder.d.ts +44 -0
- package/dist/feishu/scope/session-binder.js +100 -0
- package/dist/feishu/scope/session-store.d.ts +24 -0
- package/dist/feishu/scope/session-store.js +73 -0
- package/dist/feishu/secrets.d.ts +37 -0
- package/dist/feishu/secrets.js +129 -0
- package/dist/feishu/serve.d.ts +12 -0
- package/dist/feishu/serve.js +288 -0
- package/dist/feishu/types.d.ts +75 -0
- package/dist/feishu/types.js +23 -0
- package/dist/feishu/wizard.d.ts +24 -0
- package/dist/feishu/wizard.js +121 -0
- package/dist/main.js +98 -32
- package/dist/model-catalog.js +3 -0
- package/dist/prompt/compose.js +3 -3
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.js +1 -1
- package/dist/provider-openai-codex.d.ts +8 -1
- package/dist/provider-openai-codex.js +33 -9
- package/dist/provider.d.ts +2 -0
- package/dist/session-title.d.ts +16 -0
- package/dist/session-title.js +134 -0
- package/dist/session-types.d.ts +5 -0
- package/dist/session.d.ts +16 -0
- package/dist/session.js +154 -2
- package/dist/skills/invocation.js +0 -18
- package/dist/skills/registry.d.ts +1 -0
- package/dist/skills/registry.js +2 -0
- package/dist/slash-commands/commands.js +15 -22
- package/dist/slash-commands/feishu.d.ts +17 -0
- package/dist/slash-commands/feishu.js +400 -0
- package/dist/slash-commands/registry.js +1 -1
- package/dist/slash-commands/types.d.ts +3 -1
- package/dist/text-display.d.ts +3 -0
- package/dist/text-display.js +25 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -1
- package/dist/tools/skill-search.d.ts +10 -0
- package/dist/tools/skill-search.js +134 -0
- package/dist/tools/skill.js +1 -4
- package/dist/tui-ink/app.js +265 -118
- package/dist/tui-ink/code-highlight.js +2 -3
- package/dist/tui-ink/detect-theme.d.ts +1 -18
- package/dist/tui-ink/detect-theme.js +1 -37
- package/dist/tui-ink/display-history.d.ts +20 -3
- package/dist/tui-ink/display-history.js +26 -27
- package/dist/tui-ink/feedback-dialog.d.ts +19 -0
- package/dist/tui-ink/feedback-dialog.js +123 -0
- package/dist/tui-ink/feishu-setup-picker.d.ts +5 -0
- package/dist/tui-ink/feishu-setup-picker.js +261 -0
- package/dist/tui-ink/input-box.d.ts +25 -1
- package/dist/tui-ink/input-box.js +132 -11
- package/dist/tui-ink/input-history.js +3 -5
- package/dist/tui-ink/markdown.d.ts +32 -0
- package/dist/tui-ink/markdown.js +111 -4
- package/dist/tui-ink/message-list.d.ts +1 -6
- package/dist/tui-ink/message-list.js +86 -34
- package/dist/tui-ink/model-picker.d.ts +18 -0
- package/dist/tui-ink/model-picker.js +81 -27
- package/dist/tui-ink/run-session-picker.d.ts +10 -0
- package/dist/tui-ink/run-session-picker.js +22 -0
- package/dist/tui-ink/run.js +7 -2
- package/dist/tui-ink/session-picker.d.ts +10 -0
- package/dist/tui-ink/session-picker.js +110 -0
- package/dist/tui-ink/terminal-mouse.d.ts +4 -0
- package/dist/tui-ink/terminal-mouse.js +23 -0
- package/dist/tui-ink/theme.js +2 -2
- package/dist/tui-ink/trace-groups.js +25 -2
- package/dist/tui-ink/welcome.js +2 -4
- package/package.json +4 -5
- package/dist/tui/clipboard.d.ts +0 -1
- package/dist/tui/clipboard.js +0 -53
- package/dist/tui/display-history.d.ts +0 -44
- package/dist/tui/display-history.js +0 -243
- package/dist/tui/escape-confirmation.d.ts +0 -15
- package/dist/tui/escape-confirmation.js +0 -30
- package/dist/tui/file-mentions.d.ts +0 -29
- package/dist/tui/file-mentions.js +0 -174
- package/dist/tui/global-key-router.d.ts +0 -3
- package/dist/tui/global-key-router.js +0 -87
- package/dist/tui/image-paste.d.ts +0 -95
- package/dist/tui/image-paste.js +0 -505
- package/dist/tui/markdown-inline.d.ts +0 -22
- package/dist/tui/markdown-inline.js +0 -68
- package/dist/tui/markdown-theme-rules.d.ts +0 -23
- package/dist/tui/markdown-theme-rules.js +0 -164
- package/dist/tui/markdown-theme.d.ts +0 -5
- package/dist/tui/markdown-theme.js +0 -27
- package/dist/tui/opencode-spinner.d.ts +0 -21
- package/dist/tui/opencode-spinner.js +0 -216
- package/dist/tui/prompt-keybindings.d.ts +0 -42
- package/dist/tui/prompt-keybindings.js +0 -35
- package/dist/tui/recent-activity.d.ts +0 -8
- package/dist/tui/recent-activity.js +0 -71
- package/dist/tui/render-signature.d.ts +0 -1
- package/dist/tui/render-signature.js +0 -7
- package/dist/tui/run.d.ts +0 -38
- package/dist/tui/run.js +0 -6996
- package/dist/tui/sidebar-mcp.d.ts +0 -31
- package/dist/tui/sidebar-mcp.js +0 -62
- package/dist/tui/sidebar-state.d.ts +0 -12
- package/dist/tui/sidebar-state.js +0 -69
- package/dist/tui/streaming-tool-args.d.ts +0 -15
- package/dist/tui/streaming-tool-args.js +0 -30
- package/dist/tui/tool-renderers/fallback.d.ts +0 -2
- package/dist/tui/tool-renderers/fallback.js +0 -75
- package/dist/tui/tool-renderers/registry.d.ts +0 -3
- package/dist/tui/tool-renderers/registry.js +0 -11
- package/dist/tui/tool-renderers/subagent.d.ts +0 -2
- package/dist/tui/tool-renderers/subagent.js +0 -114
- package/dist/tui/tool-renderers/types.d.ts +0 -36
- package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
- package/dist/tui/tool-renderers/write-preview.js +0 -30
- package/dist/tui/tool-renderers/write.d.ts +0 -6
- package/dist/tui/tool-renderers/write.js +0 -88
- /package/dist/{tui/tool-renderers → feedback}/types.js +0 -0
package/dist/tui-ink/app.js
CHANGED
|
@@ -4,11 +4,12 @@ import { Box, Text, useApp, useInput } from "ink";
|
|
|
4
4
|
import { AgentAbortError } from "../agent.js";
|
|
5
5
|
import { registry as slashRegistry } from "../slash-commands/index.js";
|
|
6
6
|
import { UserConfig, maskKey } from "../config.js";
|
|
7
|
-
import { InputBox } from "./input-box.js";
|
|
7
|
+
import { createPastedContentMarker, InputBox, isCtrlCInput, shouldCollapsePastedContent, } from "./input-box.js";
|
|
8
8
|
import { MessageList } from "./message-list.js";
|
|
9
|
-
import { appendTextPart, appendToolPart, compactDisplayMessages, contentFromParts, nextDisplayMessageKey, snapshotDisplayParts, toolCallsFromParts, } from "./display-history.js";
|
|
9
|
+
import { appendTextPart, appendToolPart, compactDisplayMessages, contentFromParts, latestCompactionSummary, nextDisplayMessageKey, snapshotDisplayParts, toolCallsFromParts, } from "./display-history.js";
|
|
10
10
|
import { paletteFor, ThemeProvider, useTheme } from "./theme.js";
|
|
11
11
|
import { ModelPicker, ProviderPicker, KeyPicker, SkillPicker } from "./model-picker.js";
|
|
12
|
+
import { FeishuSetupPicker } from "./feishu-setup-picker.js";
|
|
12
13
|
import { BUILTIN_PROVIDERS, ProviderRegistry, displayModel, isUserVisibleProvider } from "../provider-registry.js";
|
|
13
14
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
14
15
|
import { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingLevel } from "../provider-transform.js";
|
|
@@ -23,6 +24,9 @@ import { PlanConfirm } from "./plan-confirm.js";
|
|
|
23
24
|
import { ApprovalDialog } from "./approval/approval-dialog.js";
|
|
24
25
|
import { getNextPermissionMode } from "../permission/mode.js";
|
|
25
26
|
import { QuestionDialog } from "./question-dialog.js";
|
|
27
|
+
import { FeedbackDialog } from "./feedback-dialog.js";
|
|
28
|
+
import { collectFeedback } from "../feedback/collect.js";
|
|
29
|
+
import { hasTerminalMouseSequence } from "./terminal-mouse.js";
|
|
26
30
|
import os from "node:os";
|
|
27
31
|
import { existsSync } from "node:fs";
|
|
28
32
|
import { join } from "node:path";
|
|
@@ -61,7 +65,9 @@ function reconstructDisplayMessages(agentMessages) {
|
|
|
61
65
|
result.push({
|
|
62
66
|
key: nextDisplayMessageKey("user"),
|
|
63
67
|
role: "user",
|
|
64
|
-
content: typeof m.content === "string"
|
|
68
|
+
content: typeof m.content === "string"
|
|
69
|
+
? (shouldCollapsePastedContent(m.content) ? createPastedContentMarker(m.content) : m.content)
|
|
70
|
+
: "(multimedia)",
|
|
65
71
|
});
|
|
66
72
|
}
|
|
67
73
|
else if (m.role === "assistant") {
|
|
@@ -174,6 +180,76 @@ function withMessageKey(message) {
|
|
|
174
180
|
const prefix = message.role === "user" ? "user" : message.role === "error" ? "err" : "asst";
|
|
175
181
|
return { ...message, key: nextDisplayMessageKey(prefix) };
|
|
176
182
|
}
|
|
183
|
+
const STREAMING_STATIC_FLUSH_MIN_CHARS = 5000;
|
|
184
|
+
const STREAMING_STATIC_FLUSH_TARGET_CHARS = 3600;
|
|
185
|
+
const STREAMING_STATIC_FLUSH_MIN_TAIL = 700;
|
|
186
|
+
function findStreamingStaticFlushIndex(content) {
|
|
187
|
+
if (content.length < STREAMING_STATIC_FLUSH_MIN_CHARS)
|
|
188
|
+
return -1;
|
|
189
|
+
const upper = Math.min(STREAMING_STATIC_FLUSH_TARGET_CHARS, content.length - STREAMING_STATIC_FLUSH_MIN_TAIL);
|
|
190
|
+
if (upper <= 0)
|
|
191
|
+
return -1;
|
|
192
|
+
const search = content.slice(0, upper);
|
|
193
|
+
const paragraphBreak = search.lastIndexOf("\n\n");
|
|
194
|
+
if (paragraphBreak >= STREAMING_STATIC_FLUSH_TARGET_CHARS / 2) {
|
|
195
|
+
return paragraphBreak + 2;
|
|
196
|
+
}
|
|
197
|
+
const lineBreak = search.lastIndexOf("\n");
|
|
198
|
+
if (lineBreak >= STREAMING_STATIC_FLUSH_TARGET_CHARS / 2) {
|
|
199
|
+
return lineBreak + 1;
|
|
200
|
+
}
|
|
201
|
+
return -1;
|
|
202
|
+
}
|
|
203
|
+
function cloneDisplayPart(part) {
|
|
204
|
+
if (part.type === "text") {
|
|
205
|
+
return { type: "text", content: part.content };
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
type: "tools",
|
|
209
|
+
toolCalls: part.toolCalls.map((toolCall) => ({
|
|
210
|
+
...toolCall,
|
|
211
|
+
args: { ...toolCall.args },
|
|
212
|
+
})),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function splitDisplayPartsAtTextOffset(parts, offset) {
|
|
216
|
+
const flushedParts = [];
|
|
217
|
+
const remainingParts = [];
|
|
218
|
+
let remainingOffset = Math.max(0, offset);
|
|
219
|
+
let reachedTail = false;
|
|
220
|
+
for (const part of parts) {
|
|
221
|
+
if (part.type === "text") {
|
|
222
|
+
if (!reachedTail && remainingOffset >= part.content.length) {
|
|
223
|
+
if (part.content)
|
|
224
|
+
flushedParts.push(cloneDisplayPart(part));
|
|
225
|
+
remainingOffset -= part.content.length;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (!reachedTail && remainingOffset > 0) {
|
|
229
|
+
const head = part.content.slice(0, remainingOffset);
|
|
230
|
+
const tail = part.content.slice(remainingOffset);
|
|
231
|
+
if (head)
|
|
232
|
+
flushedParts.push({ type: "text", content: head });
|
|
233
|
+
if (tail)
|
|
234
|
+
remainingParts.push({ type: "text", content: tail });
|
|
235
|
+
remainingOffset = 0;
|
|
236
|
+
reachedTail = true;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
remainingParts.push(cloneDisplayPart(part));
|
|
240
|
+
reachedTail = true;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!reachedTail && remainingOffset > 0) {
|
|
244
|
+
flushedParts.push(cloneDisplayPart(part));
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
remainingParts.push(cloneDisplayPart(part));
|
|
248
|
+
reachedTail = true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { flushedParts, remainingParts };
|
|
252
|
+
}
|
|
177
253
|
export function App({ agent, args, sessionManager, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, bypassEnabled, onExit }) {
|
|
178
254
|
const [themeMode, setThemeMode] = useState(initialThemeMode ?? "auto");
|
|
179
255
|
// `detectedTheme` is captured once at startup in main.ts. We keep it in state
|
|
@@ -205,45 +281,52 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
205
281
|
const [pendingPlan, setPendingPlan] = useState(null);
|
|
206
282
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
207
283
|
const [pendingQuestion, setPendingQuestion] = useState(null);
|
|
284
|
+
const [pendingFeedback, setPendingFeedback] = useState(null);
|
|
208
285
|
const [pickerMode, setPickerMode] = useState(null);
|
|
286
|
+
const [cursorResetEpoch, setCursorResetEpoch] = useState(0);
|
|
287
|
+
const [composerDraft, setComposerDraft] = useState(null);
|
|
209
288
|
const [keyProviderId, setKeyProviderId] = useState(null);
|
|
210
289
|
const [verboseTrace, setVerboseTrace] = useState(false);
|
|
211
290
|
const startedWithVisibleHistoryRef = useRef(messages.some((message) => message.syntheticKind !== "ui_summary"));
|
|
212
291
|
const { columns: terminalColumns } = useTerminalSize();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const previousColumnsRef = useRef(null);
|
|
292
|
+
const showWelcome = shouldShowWelcomeBanner({
|
|
293
|
+
messages,
|
|
294
|
+
startedWithVisibleHistory: startedWithVisibleHistoryRef.current,
|
|
295
|
+
});
|
|
296
|
+
const activeAbortRef = useRef(null);
|
|
297
|
+
const exitRequestedRef = useRef(false);
|
|
298
|
+
const sessionStartRef = useRef(Date.now());
|
|
299
|
+
const previousTerminalColumnsRef = useRef(null);
|
|
222
300
|
useEffect(() => {
|
|
223
|
-
if (
|
|
224
|
-
|
|
301
|
+
if (previousTerminalColumnsRef.current === null) {
|
|
302
|
+
previousTerminalColumnsRef.current = terminalColumns;
|
|
225
303
|
return;
|
|
226
304
|
}
|
|
227
|
-
if (
|
|
305
|
+
if (previousTerminalColumnsRef.current === terminalColumns)
|
|
228
306
|
return;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
307
|
+
previousTerminalColumnsRef.current = terminalColumns;
|
|
308
|
+
// This follows Gemini CLI's normal terminal-buffer strategy: after a
|
|
309
|
+
// resize, the previous live Ink frame may have wrapped at the old width,
|
|
310
|
+
// so cursor-up based repaint can leave stale progress frames behind.
|
|
311
|
+
// Debounce resize storms, then clear and replay Static at the settled width.
|
|
312
|
+
const timer = setTimeout(() => {
|
|
313
|
+
if (exitRequestedRef.current)
|
|
314
|
+
return;
|
|
315
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
316
|
+
setClearEpoch((epoch) => epoch + 1);
|
|
317
|
+
}, 300);
|
|
318
|
+
return () => clearTimeout(timer);
|
|
232
319
|
}, [terminalColumns]);
|
|
233
|
-
const activeAbortRef = useRef(null);
|
|
234
|
-
const exitRequestedRef = useRef(false);
|
|
235
|
-
const sessionStartRef = useRef(Date.now());
|
|
236
320
|
// Set true the moment /quit is invoked so we can hide dynamic UI (composer,
|
|
237
321
|
// waiting indicator, footer) before Ink snapshots its final frame into the
|
|
238
322
|
// shell scrollback. Without this, the last visible "> " input row stays
|
|
239
323
|
// glued to the bottom of the terminal after exit.
|
|
240
324
|
const [isExiting, setIsExiting] = useState(false);
|
|
241
|
-
// 1Hz tick
|
|
242
|
-
//
|
|
243
|
-
// don't churn renders at idle.
|
|
325
|
+
// 1Hz tick keeps the composer activity indicator animated while the agent is
|
|
326
|
+
// running without churning renders at idle.
|
|
244
327
|
const [nowTick, setNowTick] = useState(() => Date.now());
|
|
245
|
-
// Timestamp of when the current agent run started
|
|
246
|
-
//
|
|
328
|
+
// Timestamp of when the current agent run started. Used only for the final
|
|
329
|
+
// per-task duration summary.
|
|
247
330
|
const runStartRef = useRef(null);
|
|
248
331
|
// Mark the moment the run started; flips back to null in the finally block.
|
|
249
332
|
useEffect(() => {
|
|
@@ -376,11 +459,16 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
376
459
|
thinkingLevel: overrides?.thinkingLevel ?? agent.thinking,
|
|
377
460
|
mode: overrides?.mode ?? agent.mode,
|
|
378
461
|
workingDir: args.cwd,
|
|
379
|
-
skills: safeSkillRegistry?.summaries() ?? [],
|
|
380
462
|
}));
|
|
381
463
|
}, [agent, args.cwd, safeRegistry, safeSkillRegistry]);
|
|
382
464
|
useInput((input, key) => {
|
|
383
|
-
if (
|
|
465
|
+
if (isCtrlCInput(input, key)) {
|
|
466
|
+
requestExit();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (pendingPlan || pendingApproval || pendingQuestion || pendingFeedback)
|
|
470
|
+
return;
|
|
471
|
+
if (hasTerminalMouseSequence(input))
|
|
384
472
|
return;
|
|
385
473
|
if (key.ctrl && input === "o" && !pickerMode) {
|
|
386
474
|
setVerboseTrace((v) => !v);
|
|
@@ -401,7 +489,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
401
489
|
rebuildSystemPrompt({ thinkingLevel: nextLevel });
|
|
402
490
|
userConfig.setDefaultThinkingLevel(nextLevel);
|
|
403
491
|
setThinkingLevel(nextLevel);
|
|
404
|
-
sessionManager?.
|
|
492
|
+
sessionManager?.updateMetadata({ model: agent.model, thinkingLevel: nextLevel, reasoningEffort: nextLevel });
|
|
405
493
|
sessionManager?.appendMarker("thinking_level_switch", nextLevel);
|
|
406
494
|
return;
|
|
407
495
|
}
|
|
@@ -429,13 +517,11 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
429
517
|
updateDisplayMessages((prev) => [...prev, withMessageKey({ role, content })]);
|
|
430
518
|
}, [updateDisplayMessages]);
|
|
431
519
|
const clearMessages = useCallback(() => {
|
|
432
|
-
|
|
433
|
-
//
|
|
434
|
-
// them — emptying the React state alone leaves the old output visible.
|
|
435
|
-
// Wipe screen + scrollback (xterm \x1b[3J) and bump the epoch below so
|
|
436
|
-
// Static remounts with a fresh internal cursor.
|
|
520
|
+
// Static history is already written to terminal scrollback, so clearing
|
|
521
|
+
// React state alone would leave old rows visible.
|
|
437
522
|
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
438
|
-
|
|
523
|
+
setMessages([]);
|
|
524
|
+
setClearEpoch((epoch) => epoch + 1);
|
|
439
525
|
}, []);
|
|
440
526
|
const openPicker = useCallback((mode, providerId) => {
|
|
441
527
|
if (mode === "key") {
|
|
@@ -443,6 +529,24 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
443
529
|
}
|
|
444
530
|
setPickerMode(mode);
|
|
445
531
|
}, []);
|
|
532
|
+
const closePicker = useCallback(() => {
|
|
533
|
+
setPickerMode(null);
|
|
534
|
+
setCursorResetEpoch((epoch) => epoch + 1);
|
|
535
|
+
}, []);
|
|
536
|
+
const fillComposer = useCallback((text) => {
|
|
537
|
+
setComposerDraft((current) => ({
|
|
538
|
+
text,
|
|
539
|
+
epoch: (current?.epoch ?? 0) + 1,
|
|
540
|
+
}));
|
|
541
|
+
}, []);
|
|
542
|
+
const clearComposerDraft = useCallback(() => {
|
|
543
|
+
setComposerDraft(null);
|
|
544
|
+
}, []);
|
|
545
|
+
const openFeedback = useCallback((initialDescription) => {
|
|
546
|
+
const base = collectFeedback(agent, { description: "" });
|
|
547
|
+
const { description: _drop, ...rest } = base;
|
|
548
|
+
setPendingFeedback({ base: rest, initialDescription });
|
|
549
|
+
}, [agent]);
|
|
446
550
|
const handleModelSelect = useCallback((model) => {
|
|
447
551
|
const run = async () => {
|
|
448
552
|
agent.model = model;
|
|
@@ -454,7 +558,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
454
558
|
const provider = safeRegistry.getConfigured().find((item) => item.id === providerId);
|
|
455
559
|
if (!provider?.apiKey || !createProvider) {
|
|
456
560
|
addMessage("error", `Provider ${providerId} is not configured or has no active credentials.`);
|
|
457
|
-
|
|
561
|
+
closePicker();
|
|
458
562
|
return;
|
|
459
563
|
}
|
|
460
564
|
const modelId = model.includes(":") ? model.split(":").slice(1).join(":") : model;
|
|
@@ -468,17 +572,16 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
468
572
|
configuredModelId: model,
|
|
469
573
|
thinkingLevel: agent.thinking,
|
|
470
574
|
workingDir: args.cwd,
|
|
471
|
-
skills: safeSkillRegistry?.summaries() ?? [],
|
|
472
575
|
}));
|
|
473
576
|
userConfig.pushRecentModel(model);
|
|
474
577
|
setThinkingLevel(agent.thinking);
|
|
475
|
-
sessionManager?.
|
|
578
|
+
sessionManager?.updateMetadata({ model, thinkingLevel: agent.thinking, reasoningEffort: agent.thinking });
|
|
476
579
|
sessionManager?.appendMarker("model_switch", model);
|
|
477
580
|
addMessage("assistant", `Model switched to ${displayModel(model)}.`);
|
|
478
|
-
|
|
581
|
+
closePicker();
|
|
479
582
|
};
|
|
480
583
|
void run();
|
|
481
|
-
}, [agent, addMessage, sessionManager, userConfig, safeRegistry, createProvider]);
|
|
584
|
+
}, [agent, addMessage, closePicker, sessionManager, userConfig, safeRegistry, createProvider]);
|
|
482
585
|
const handleProviderSelect = useCallback(async (providerId) => {
|
|
483
586
|
await safeRegistry.prepareProvider(providerId);
|
|
484
587
|
const configured = safeRegistry.getConfigured();
|
|
@@ -486,7 +589,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
486
589
|
const builtin = BUILTIN_PROVIDERS.find((x) => x.id === providerId);
|
|
487
590
|
if (!p && !builtin) {
|
|
488
591
|
addMessage("error", `Provider ${providerId} not found.`);
|
|
489
|
-
|
|
592
|
+
closePicker();
|
|
490
593
|
return;
|
|
491
594
|
}
|
|
492
595
|
if (!p?.apiKey) {
|
|
@@ -502,21 +605,21 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
502
605
|
agent.setProvider(createProvider(providerId, p.apiKey, p.baseURL));
|
|
503
606
|
agent.providerId = providerId;
|
|
504
607
|
addMessage("assistant", `Switched to provider ${p.name}. Use /model to pick a model.`);
|
|
505
|
-
|
|
506
|
-
}, [addMessage, agent, createProvider, safeRegistry]);
|
|
608
|
+
closePicker();
|
|
609
|
+
}, [addMessage, agent, closePicker, createProvider, safeRegistry]);
|
|
507
610
|
const handleProviderAddSelect = useCallback((providerId) => {
|
|
508
611
|
const ok = safeRegistry.addProvider(providerId, "");
|
|
509
612
|
if (!ok) {
|
|
510
613
|
addMessage("error", `Provider ${providerId} could not be added.`);
|
|
511
|
-
|
|
614
|
+
closePicker();
|
|
512
615
|
return;
|
|
513
616
|
}
|
|
514
617
|
safeRegistry.setDefault(providerId);
|
|
515
618
|
setKeyProviderId(providerId);
|
|
516
619
|
setPickerMode("key");
|
|
517
|
-
}, [addMessage, safeRegistry]);
|
|
620
|
+
}, [addMessage, closePicker, safeRegistry]);
|
|
518
621
|
const handleLoginProviderSelect = useCallback(async (providerId) => {
|
|
519
|
-
|
|
622
|
+
closePicker();
|
|
520
623
|
const command = `/login ${providerId}`;
|
|
521
624
|
const { handled, result } = await slashRegistry.execute(command, {
|
|
522
625
|
agent,
|
|
@@ -529,6 +632,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
529
632
|
throw new Error("Provider creation not available");
|
|
530
633
|
}),
|
|
531
634
|
openPicker,
|
|
635
|
+
openFeedback,
|
|
532
636
|
registry: safeRegistry,
|
|
533
637
|
skillRegistry: safeSkillRegistry,
|
|
534
638
|
bashAllowlist,
|
|
@@ -546,9 +650,9 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
546
650
|
if (handled && result) {
|
|
547
651
|
addMessage("assistant", result);
|
|
548
652
|
}
|
|
549
|
-
}, [agent, addMessage, clearMessages, createProvider, exit, openPicker, safeRegistry, sessionManager]);
|
|
653
|
+
}, [agent, addMessage, clearMessages, closePicker, createProvider, exit, openPicker, safeRegistry, sessionManager]);
|
|
550
654
|
const handleLogoutProviderSelect = useCallback(async (providerId) => {
|
|
551
|
-
|
|
655
|
+
closePicker();
|
|
552
656
|
const command = `/logout ${providerId}`;
|
|
553
657
|
const { handled, result } = await slashRegistry.execute(command, {
|
|
554
658
|
agent,
|
|
@@ -561,6 +665,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
561
665
|
throw new Error("Provider creation not available");
|
|
562
666
|
}),
|
|
563
667
|
openPicker,
|
|
668
|
+
openFeedback,
|
|
564
669
|
registry: safeRegistry,
|
|
565
670
|
skillRegistry: safeSkillRegistry,
|
|
566
671
|
bashAllowlist,
|
|
@@ -578,12 +683,12 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
578
683
|
if (handled && result) {
|
|
579
684
|
addMessage("assistant", result);
|
|
580
685
|
}
|
|
581
|
-
}, [agent, addMessage, clearMessages, createProvider, exit, openPicker, safeRegistry, sessionManager]);
|
|
686
|
+
}, [agent, addMessage, clearMessages, closePicker, createProvider, exit, openPicker, safeRegistry, sessionManager]);
|
|
582
687
|
const handleKeySubmit = useCallback((key) => {
|
|
583
688
|
const targetId = keyProviderId || safeRegistry.getDefault()?.id;
|
|
584
689
|
if (!targetId) {
|
|
585
690
|
addMessage("error", "No provider selected.");
|
|
586
|
-
|
|
691
|
+
closePicker();
|
|
587
692
|
setKeyProviderId(null);
|
|
588
693
|
return;
|
|
589
694
|
}
|
|
@@ -594,12 +699,13 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
594
699
|
agent.providerId = targetId;
|
|
595
700
|
}
|
|
596
701
|
addMessage("assistant", `API key updated for ${p?.name || targetId} to ${maskKey(key)}.`);
|
|
597
|
-
|
|
702
|
+
closePicker();
|
|
598
703
|
setKeyProviderId(null);
|
|
599
|
-
}, [addMessage, agent, createProvider, keyProviderId, safeRegistry]);
|
|
704
|
+
}, [addMessage, agent, closePicker, createProvider, keyProviderId, safeRegistry]);
|
|
600
705
|
const handleSubmit = useCallback(async (payload) => {
|
|
601
706
|
const normalized = typeof payload === "string" ? { text: payload, images: [] } : payload;
|
|
602
707
|
const input = normalized.text;
|
|
708
|
+
const displayInput = normalized.displayText ?? input;
|
|
603
709
|
const images = normalized.images;
|
|
604
710
|
if (!input.trim() && images.length === 0)
|
|
605
711
|
return;
|
|
@@ -642,7 +748,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
642
748
|
!!assistantReasoning ||
|
|
643
749
|
toolCalls.length > 0 ||
|
|
644
750
|
assistantParts.length > 0);
|
|
645
|
-
const commitAssistantMessage = () => {
|
|
751
|
+
const commitAssistantMessage = (taskElapsedMs) => {
|
|
646
752
|
if (!hasAssistantOutput())
|
|
647
753
|
return;
|
|
648
754
|
const currentParts = snapshotDisplayParts(assistantParts);
|
|
@@ -665,6 +771,9 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
665
771
|
if (currentParts.length > 0) {
|
|
666
772
|
msg.parts = currentParts;
|
|
667
773
|
}
|
|
774
|
+
if (taskElapsedMs !== undefined && Number.isFinite(taskElapsedMs) && taskElapsedMs > 0) {
|
|
775
|
+
msg.taskElapsedMs = taskElapsedMs;
|
|
776
|
+
}
|
|
668
777
|
updateDisplayMessages((prev) => [...prev, msg]);
|
|
669
778
|
};
|
|
670
779
|
const clearAssistantStream = () => {
|
|
@@ -677,14 +786,54 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
677
786
|
toolCalls.length = 0;
|
|
678
787
|
assistantParts.length = 0;
|
|
679
788
|
};
|
|
789
|
+
const flushAssistantStaticChunk = () => {
|
|
790
|
+
if (toolCalls.some((toolCall) => toolCall.result === undefined)) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
const splitIndex = findStreamingStaticFlushIndex(assistantContent);
|
|
794
|
+
if (splitIndex <= 0)
|
|
795
|
+
return false;
|
|
796
|
+
const { flushedParts, remainingParts } = splitDisplayPartsAtTextOffset(assistantParts, splitIndex);
|
|
797
|
+
const flushedContent = contentFromParts(flushedParts);
|
|
798
|
+
const flushedToolCalls = toolCallsFromParts(flushedParts);
|
|
799
|
+
if (!flushedContent && flushedToolCalls.length === 0)
|
|
800
|
+
return false;
|
|
801
|
+
const msg = {
|
|
802
|
+
key: nextDisplayMessageKey("asst"),
|
|
803
|
+
role: "assistant",
|
|
804
|
+
content: flushedContent,
|
|
805
|
+
};
|
|
806
|
+
if (assistantReasoning) {
|
|
807
|
+
msg.reasoning = assistantReasoning;
|
|
808
|
+
assistantReasoning = "";
|
|
809
|
+
setStreamingReasoning("");
|
|
810
|
+
}
|
|
811
|
+
if (flushedToolCalls.length > 0) {
|
|
812
|
+
msg.toolCalls = flushedToolCalls;
|
|
813
|
+
}
|
|
814
|
+
if (flushedParts.length > 0) {
|
|
815
|
+
msg.parts = flushedParts;
|
|
816
|
+
}
|
|
817
|
+
updateDisplayMessages((prev) => [...prev, msg]);
|
|
818
|
+
assistantParts.splice(0, assistantParts.length, ...remainingParts);
|
|
819
|
+
assistantContent = contentFromParts(assistantParts);
|
|
820
|
+
const remainingToolCalls = toolCallsFromParts(assistantParts);
|
|
821
|
+
toolCalls.splice(0, toolCalls.length, ...remainingToolCalls);
|
|
822
|
+
setStreamingContent(assistantContent);
|
|
823
|
+
setStreamingTools([...toolCalls]);
|
|
824
|
+
syncStreamingParts();
|
|
825
|
+
return true;
|
|
826
|
+
};
|
|
680
827
|
try {
|
|
681
828
|
for await (const event of agent.run(actualInput, args.cwd, { abortSignal: abortController.signal })) {
|
|
682
829
|
switch (event.type) {
|
|
683
830
|
case "text_delta":
|
|
684
831
|
assistantContent += event.content;
|
|
685
832
|
appendTextPart(assistantParts, event.content);
|
|
686
|
-
|
|
687
|
-
|
|
833
|
+
if (!flushAssistantStaticChunk()) {
|
|
834
|
+
setStreamingContent(assistantContent);
|
|
835
|
+
syncStreamingParts();
|
|
836
|
+
}
|
|
688
837
|
break;
|
|
689
838
|
case "reasoning_delta":
|
|
690
839
|
assistantReasoning += event.content;
|
|
@@ -798,7 +947,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
798
947
|
syncStreamingParts();
|
|
799
948
|
break;
|
|
800
949
|
}
|
|
801
|
-
commitAssistantMessage();
|
|
950
|
+
commitAssistantMessage(runStartRef.current ? Date.now() - runStartRef.current : undefined);
|
|
802
951
|
clearAssistantStream();
|
|
803
952
|
break;
|
|
804
953
|
}
|
|
@@ -833,7 +982,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
833
982
|
};
|
|
834
983
|
// Slash commands and skill invocations drop any attached images —
|
|
835
984
|
// they're meant for pure command routing.
|
|
836
|
-
if (
|
|
985
|
+
if (displayInput.startsWith("/")) {
|
|
837
986
|
// Fast-path `/quit` and `/exit` before slash-registry / skill
|
|
838
987
|
// resolution. This guarantees a literal "/quit" always exits even if
|
|
839
988
|
// a skill or alias of the same name is later registered. The
|
|
@@ -846,7 +995,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
846
995
|
}
|
|
847
996
|
const skillInvocation = parseSkillInvocation(input, safeSkillRegistry);
|
|
848
997
|
if (skillInvocation) {
|
|
849
|
-
await runAgentInput(skillInvocation.actualPrompt,
|
|
998
|
+
await runAgentInput(skillInvocation.actualPrompt, displayInput);
|
|
850
999
|
return;
|
|
851
1000
|
}
|
|
852
1001
|
const { handled, result, inject } = await slashRegistry.execute(input, {
|
|
@@ -860,6 +1009,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
860
1009
|
throw new Error("Provider creation not available");
|
|
861
1010
|
}),
|
|
862
1011
|
openPicker,
|
|
1012
|
+
openFeedback,
|
|
863
1013
|
registry: safeRegistry,
|
|
864
1014
|
skillRegistry: safeSkillRegistry,
|
|
865
1015
|
bashAllowlist,
|
|
@@ -879,10 +1029,27 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
879
1029
|
setPermissionMode(agent.mode);
|
|
880
1030
|
}
|
|
881
1031
|
if (result) {
|
|
882
|
-
|
|
1032
|
+
// `/compact` rewrites agent.messages, so the Ink transcript needs to
|
|
1033
|
+
// be rebuilt from the new agent state before appending the summary
|
|
1034
|
+
// card; otherwise the pre-compaction history would keep rendering.
|
|
1035
|
+
if (result.startsWith("✓ Compaction complete")) {
|
|
1036
|
+
const summary = latestCompactionSummary(agent.messages);
|
|
1037
|
+
updateDisplayMessages(() => [
|
|
1038
|
+
...reconstructDisplayMessages(agent.messages),
|
|
1039
|
+
{
|
|
1040
|
+
role: "assistant",
|
|
1041
|
+
content: result,
|
|
1042
|
+
syntheticKind: "ui_compact_summary",
|
|
1043
|
+
compactionSummary: summary,
|
|
1044
|
+
},
|
|
1045
|
+
]);
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
addMessage("assistant", result);
|
|
1049
|
+
}
|
|
883
1050
|
}
|
|
884
1051
|
if (inject) {
|
|
885
|
-
await runAgentInput(inject,
|
|
1052
|
+
await runAgentInput(inject, displayInput);
|
|
886
1053
|
}
|
|
887
1054
|
return;
|
|
888
1055
|
}
|
|
@@ -903,7 +1070,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
903
1070
|
})),
|
|
904
1071
|
]
|
|
905
1072
|
: expansion.text;
|
|
906
|
-
await runAgentInput(agentInput,
|
|
1073
|
+
await runAgentInput(agentInput, displayInput, images.map((img) => ({ filename: img.filename, bytes: img.bytes })));
|
|
907
1074
|
}, [addMessage, agent, args.cwd, openPicker, createProvider, safeRegistry, safeSkillRegistry, updateDisplayMessages]);
|
|
908
1075
|
const currentProviderId = agent.providerId || safeRegistry.getDefault()?.id;
|
|
909
1076
|
const keyTarget = keyProviderId
|
|
@@ -924,15 +1091,11 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
924
1091
|
return null;
|
|
925
1092
|
})()
|
|
926
1093
|
: null;
|
|
927
|
-
const showWelcome = shouldShowWelcomeBanner({
|
|
928
|
-
messages,
|
|
929
|
-
startedWithVisibleHistory: startedWithVisibleHistoryRef.current,
|
|
930
|
-
});
|
|
931
1094
|
const mcpStates = mcpManager?.getStates() ?? [];
|
|
932
1095
|
const mcpConnectedCount = mcpStates.filter((state) => state.status.kind === "connected").length;
|
|
933
1096
|
const hasAgentsFile = useMemo(() => existsSync(join(args.cwd, "AGENTS.md")) || existsSync(join(args.cwd, ".bubble", "AGENTS.md")), [args.cwd]);
|
|
934
1097
|
const welcomeBannerNode = showWelcome ? (_jsx(WelcomeBanner, { terminalColumns: terminalColumns, modelLabel: agent.model ? displayModel(agent.model) : undefined, cwd: friendlyCwd(args.cwd), tips: buildTips(agent, safeRegistry), skillsCount: safeSkillRegistry.summaries().length, mcpConnectedCount: mcpConnectedCount, mcpTotalCount: mcpStates.length, hasAgentsFile: hasAgentsFile })) : null;
|
|
935
|
-
return (_jsx(ThemeProvider, { value: palette, children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column",
|
|
1098
|
+
return (_jsx(ThemeProvider, { value: palette, children: _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingTop: 1, flexShrink: 0, children: [_jsx(MessageList, { messages: messages, streamingContent: streamingContent, streamingReasoning: streamingReasoning, streamingTools: streamingTools, streamingParts: streamingParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: approvalHint, nowTick: nowTick, welcomeBanner: welcomeBannerNode }, clearEpoch), pickerMode === "model" && (_jsx(ModelPicker, { registry: safeRegistry, current: agent.model, recent: userConfig.getRecentModels(), onSelect: handleModelSelect, onCancel: closePicker })), pickerMode === "provider" && (_jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
936
1099
|
.filter((p) => isUserVisibleProvider(p.id))
|
|
937
1100
|
.map((p) => {
|
|
938
1101
|
const configured = safeRegistry.getConfigured().find((item) => item.id === p.id);
|
|
@@ -942,45 +1105,25 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
942
1105
|
name: `${p.name} [${configuredLabel}]`,
|
|
943
1106
|
enabled: true,
|
|
944
1107
|
};
|
|
945
|
-
}), current: currentProviderId, onSelect: handleProviderSelect, onCancel:
|
|
1108
|
+
}), current: currentProviderId, onSelect: handleProviderSelect, onCancel: closePicker })), pickerMode === "provider-add" && (_jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
946
1109
|
.filter((p) => isUserVisibleProvider(p.id))
|
|
947
|
-
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleProviderAddSelect, onCancel:
|
|
1110
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleProviderAddSelect, onCancel: closePicker, title: "Add Provider" })), pickerMode === "login" && (_jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
948
1111
|
.filter((p) => isUserVisibleProvider(p.id) && safeRegistry.supportsOAuth(p.id))
|
|
949
|
-
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLoginProviderSelect, onCancel:
|
|
1112
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLoginProviderSelect, onCancel: closePicker, title: "Select Login Provider" })), pickerMode === "logout" && (_jsx(ProviderPicker, { providers: safeRegistry.getConfigured()
|
|
950
1113
|
.filter((p) => safeRegistry.getAuthStorage().has(p.id))
|
|
951
|
-
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLogoutProviderSelect, onCancel:
|
|
952
|
-
|
|
1114
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLogoutProviderSelect, onCancel: closePicker, title: "Select Logout Provider" })), pickerMode === "key" && keyTarget && (_jsx(KeyPicker, { providerName: keyTarget.name, onSubmit: handleKeySubmit, onCancel: () => {
|
|
1115
|
+
closePicker();
|
|
953
1116
|
setKeyProviderId(null);
|
|
954
|
-
} })), pickerMode === "skill" && (_jsx(SkillPicker, { skills: safeSkillRegistry.summaries(), onSelect:
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
throw new Error("Provider creation not available");
|
|
965
|
-
}),
|
|
966
|
-
openPicker,
|
|
967
|
-
registry: safeRegistry,
|
|
968
|
-
skillRegistry: safeSkillRegistry,
|
|
969
|
-
bashAllowlist,
|
|
970
|
-
settingsManager,
|
|
971
|
-
lspService,
|
|
972
|
-
mcpManager,
|
|
973
|
-
flushMemory,
|
|
974
|
-
runMemoryCompaction,
|
|
975
|
-
runMemorySummary,
|
|
976
|
-
runMemoryRefresh,
|
|
977
|
-
getThemeMode: () => themeMode,
|
|
978
|
-
getResolvedTheme: () => themeResolved,
|
|
979
|
-
setThemeMode: applyThemeMode,
|
|
980
|
-
});
|
|
981
|
-
if (handled && result)
|
|
982
|
-
addMessage("assistant", result);
|
|
983
|
-
}, onCancel: () => setPickerMode(null) }))] }), todos.length > 0 && !pickerMode && !pendingPlan && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(TodosPanel, { todos: todos, terminalColumns: terminalColumns }) })), pendingPlan && !pickerMode && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(PlanConfirm, { initialPlan: pendingPlan.plan, onApprove: (finalPlan) => {
|
|
1117
|
+
} })), pickerMode === "skill" && (_jsx(SkillPicker, { skills: safeSkillRegistry.summaries(), onSelect: (name) => {
|
|
1118
|
+
fillComposer(`/${name} `);
|
|
1119
|
+
closePicker();
|
|
1120
|
+
}, onCancel: closePicker })), pickerMode === "feishu-setup" && (_jsx(FeishuSetupPicker, { onComplete: (summary) => {
|
|
1121
|
+
closePicker();
|
|
1122
|
+
addMessage("assistant", summary);
|
|
1123
|
+
}, onCancel: () => {
|
|
1124
|
+
closePicker();
|
|
1125
|
+
addMessage("assistant", "已取消 Feishu setup。");
|
|
1126
|
+
} }))] }), todos.length > 0 && !pickerMode && !pendingPlan && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(TodosPanel, { todos: todos, terminalColumns: terminalColumns }) })), pendingPlan && !pickerMode && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(PlanConfirm, { initialPlan: pendingPlan.plan, onApprove: (finalPlan) => {
|
|
984
1127
|
const resolve = pendingPlan.resolve;
|
|
985
1128
|
setPendingPlan(null);
|
|
986
1129
|
resolve({ action: "approve", plan: finalPlan });
|
|
@@ -994,22 +1137,29 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
994
1137
|
resolve(decision);
|
|
995
1138
|
}, onAllowBashPrefix: (prefix) => {
|
|
996
1139
|
bashAllowlist?.add(prefix);
|
|
997
|
-
} }) })), pendingQuestion && !pickerMode && !pendingPlan && !pendingApproval && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(QuestionDialog, { request: pendingQuestion, onSubmit: (answers) => {
|
|
1140
|
+
} }) })), pendingQuestion && !pickerMode && !pendingPlan && !pendingApproval && !pendingFeedback && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(QuestionDialog, { request: pendingQuestion, onSubmit: (answers) => {
|
|
998
1141
|
questionController?.reply(pendingQuestion.id, answers);
|
|
999
1142
|
setPendingQuestion(null);
|
|
1000
1143
|
}, onCancel: () => {
|
|
1001
1144
|
questionController?.reject(pendingQuestion.id);
|
|
1002
1145
|
setPendingQuestion(null);
|
|
1003
|
-
} }) })),
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1146
|
+
} }) })), pendingFeedback && !pickerMode && !pendingPlan && !pendingApproval && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(FeedbackDialog, { base: pendingFeedback.base, initialDescription: pendingFeedback.initialDescription, onDismiss: () => setPendingFeedback(null), onResult: (result) => {
|
|
1147
|
+
if (result.kind === "success") {
|
|
1148
|
+
addMessage("assistant", `Feedback submitted: ${result.url}`);
|
|
1149
|
+
}
|
|
1150
|
+
else if (result.kind === "error") {
|
|
1151
|
+
addMessage("error", `Feedback failed: ${result.message}`);
|
|
1152
|
+
}
|
|
1153
|
+
} }) })), !isExiting && isRunning && !pickerMode && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && (_jsx(Box, { paddingX: 1, paddingBottom: 1, flexShrink: 0, children: _jsx(WaitingIndicator, { tools: streamingTools, hasStreamingText: streamingContent.length > 0, hasStreamingReasoning: streamingReasoning.length > 0, streamedChars: streamingContent.length + streamingReasoning.length, nowTick: nowTick }) })), !isExiting && !pickerMode && (_jsx(Box, { paddingBottom: 1, flexShrink: 0, children: _jsx(InputBox, { onSubmit: handleSubmit, disabled: isRunning || !!pendingPlan || !!pendingApproval || !!pendingQuestion || !!pendingFeedback, cursorResetEpoch: cursorResetEpoch, draftText: composerDraft?.text, draftEpoch: composerDraft?.epoch, onDraftApplied: clearComposerDraft, skillRegistry: safeSkillRegistry, terminalColumns: terminalColumns, cwd: args.cwd }) })), !isExiting && (_jsx(Box, { flexShrink: 0, children: _jsx(FooterBar, { data: buildFooterData({
|
|
1154
|
+
cwd: args.cwd,
|
|
1155
|
+
providerId: agent.providerId || safeRegistry.getDefault()?.id || "unknown",
|
|
1156
|
+
model: displayModel(agent.model) || "no model",
|
|
1157
|
+
thinkingLevel,
|
|
1158
|
+
showThinking: getAvailableThinkingLevels(agent.providerId, agent.apiModel).length > 2,
|
|
1159
|
+
mode: permissionMode,
|
|
1160
|
+
usageTotals,
|
|
1161
|
+
verboseTrace,
|
|
1162
|
+
}) }) }))] }) }));
|
|
1013
1163
|
}
|
|
1014
1164
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
1015
1165
|
const GENERIC_PHRASES = [
|
|
@@ -1074,7 +1224,8 @@ function formatTokensApprox(chars) {
|
|
|
1074
1224
|
return `${(tokens / 1000).toFixed(1)}k`;
|
|
1075
1225
|
return `${Math.round(tokens / 1000)}k`;
|
|
1076
1226
|
}
|
|
1077
|
-
function WaitingIndicator({ tools, hasStreamingText, hasStreamingReasoning, streamedChars,
|
|
1227
|
+
function WaitingIndicator({ tools, hasStreamingText, hasStreamingReasoning, streamedChars, nowTick, }) {
|
|
1228
|
+
void nowTick;
|
|
1078
1229
|
const theme = useTheme();
|
|
1079
1230
|
const [frameIndex, setFrameIndex] = useState(0);
|
|
1080
1231
|
const [idlePhrase, setIdlePhrase] = useState(() => GENERIC_PHRASES[0]);
|
|
@@ -1120,10 +1271,6 @@ function WaitingIndicator({ tools, hasStreamingText, hasStreamingReasoning, stre
|
|
|
1120
1271
|
else {
|
|
1121
1272
|
phrase = idlePhrase;
|
|
1122
1273
|
}
|
|
1123
|
-
const elapsedSec = runStartedAt
|
|
1124
|
-
? Math.max(0, Math.floor((nowTick - runStartedAt) / 1000))
|
|
1125
|
-
: 0;
|
|
1126
|
-
const elapsedText = elapsedSec > 0 ? `${elapsedSec}s` : "0s";
|
|
1127
1274
|
const tokenText = streamedChars > 0 ? `↓${formatTokensApprox(streamedChars)} tok` : "";
|
|
1128
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: SPINNER_FRAMES[frameIndex] }), _jsxs(Text, { color: theme.muted, children: [" ", phrase, " "] }), _jsxs(Text, { color: theme.muted, dimColor: true, children: ["(",
|
|
1275
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: SPINNER_FRAMES[frameIndex] }), _jsxs(Text, { color: theme.muted, children: [" ", phrase, " "] }), _jsxs(Text, { color: theme.muted, dimColor: true, children: ["(", tokenText ? `${tokenText} · ` : "", "esc\u00B7esc to interrupt)"] })] }));
|
|
1129
1276
|
}
|