@bubblebrain-ai/bubble 0.0.12 → 0.0.14
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/execution-governor.js +1 -1
- package/dist/agent/input-controller.d.ts +11 -0
- package/dist/agent/input-controller.js +30 -0
- package/dist/agent/tool-intent.js +1 -0
- package/dist/agent.d.ts +8 -4
- package/dist/agent.js +623 -312
- package/dist/approval/controller.d.ts +1 -0
- package/dist/approval/controller.js +20 -3
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +14 -1
- package/dist/context/compact.js +9 -3
- package/dist/context/projector.js +27 -12
- package/dist/debug-trace.d.ts +27 -0
- package/dist/debug-trace.js +385 -0
- package/dist/feishu/agent-host/approval-card.js +9 -0
- package/dist/feishu/serve.js +7 -1
- package/dist/main.js +86 -9
- package/dist/model-catalog.js +1 -0
- package/dist/orchestrator/default-hooks.js +19 -8
- package/dist/orchestrator/hooks.d.ts +1 -0
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.d.ts +5 -6
- package/dist/prompt/reminders.js +8 -9
- package/dist/prompt/runtime.js +2 -2
- package/dist/provider-openai-codex.d.ts +7 -0
- package/dist/provider-openai-codex.js +265 -124
- package/dist/provider-registry.d.ts +2 -0
- package/dist/provider-registry.js +58 -9
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +5 -1
- package/dist/session-log.js +13 -1
- package/dist/slash-commands/commands.js +39 -0
- package/dist/slash-commands/types.d.ts +12 -0
- package/dist/stats/usage.d.ts +52 -0
- package/dist/stats/usage.js +414 -0
- package/dist/tools/apply-patch.d.ts +9 -0
- package/dist/tools/apply-patch.js +330 -0
- package/dist/tools/bash.js +205 -44
- package/dist/tools/edit-apply.d.ts +5 -2
- package/dist/tools/edit-apply.js +221 -31
- package/dist/tools/edit.js +12 -3
- package/dist/tools/file-mutation-queue.d.ts +1 -0
- package/dist/tools/file-mutation-queue.js +12 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch-apply.d.ts +41 -0
- package/dist/tools/patch-apply.js +312 -0
- package/dist/tools/server-manager.d.ts +36 -0
- package/dist/tools/server-manager.js +234 -0
- package/dist/tools/server.d.ts +6 -0
- package/dist/tools/server.js +245 -0
- package/dist/tools/write.d.ts +3 -6
- package/dist/tools/write.js +26 -46
- package/dist/tui/clipboard.d.ts +1 -0
- package/dist/tui/clipboard.js +53 -0
- package/dist/tui/detect-theme.d.ts +2 -0
- package/dist/tui/detect-theme.js +87 -0
- package/dist/tui/display-history.d.ts +63 -0
- package/dist/tui/display-history.js +306 -0
- package/dist/tui/edit-diff.d.ts +11 -0
- package/dist/tui/edit-diff.js +57 -0
- package/dist/tui/escape-confirmation.d.ts +15 -0
- package/dist/tui/escape-confirmation.js +30 -0
- package/dist/tui/file-mentions.d.ts +29 -0
- package/dist/tui/file-mentions.js +174 -0
- package/dist/tui/global-key-router.d.ts +3 -0
- package/dist/tui/global-key-router.js +87 -0
- package/dist/tui/image-paste.d.ts +95 -0
- package/dist/tui/image-paste.js +505 -0
- package/dist/tui/input-history.d.ts +16 -0
- package/dist/tui/input-history.js +79 -0
- package/dist/tui/markdown-inline.d.ts +22 -0
- package/dist/tui/markdown-inline.js +68 -0
- package/dist/tui/markdown-theme-rules.d.ts +23 -0
- package/dist/tui/markdown-theme-rules.js +164 -0
- package/dist/tui/markdown-theme.d.ts +5 -0
- package/dist/tui/markdown-theme.js +27 -0
- package/dist/tui/model-picker-data.d.ts +10 -0
- package/dist/tui/model-picker-data.js +32 -0
- package/dist/tui/opencode-spinner.d.ts +22 -0
- package/dist/tui/opencode-spinner.js +216 -0
- package/dist/tui/prompt-keybindings.d.ts +42 -0
- package/dist/tui/prompt-keybindings.js +35 -0
- package/dist/tui/recent-activity.d.ts +8 -0
- package/dist/tui/recent-activity.js +71 -0
- package/dist/tui/render-signature.d.ts +1 -0
- package/dist/tui/render-signature.js +7 -0
- package/dist/tui/run.d.ts +45 -0
- package/dist/tui/run.js +9359 -0
- package/dist/tui/session-display.d.ts +6 -0
- package/dist/tui/session-display.js +12 -0
- package/dist/tui/sidebar-mcp.d.ts +31 -0
- package/dist/tui/sidebar-mcp.js +62 -0
- package/dist/tui/sidebar-state.d.ts +12 -0
- package/dist/tui/sidebar-state.js +69 -0
- package/dist/tui/streaming-tool-args.d.ts +15 -0
- package/dist/tui/streaming-tool-args.js +30 -0
- package/dist/tui/tool-renderers/fallback.d.ts +2 -0
- package/dist/tui/tool-renderers/fallback.js +75 -0
- package/dist/tui/tool-renderers/registry.d.ts +3 -0
- package/dist/tui/tool-renderers/registry.js +11 -0
- package/dist/tui/tool-renderers/subagent.d.ts +2 -0
- package/dist/tui/tool-renderers/subagent.js +135 -0
- package/dist/tui/tool-renderers/types.d.ts +36 -0
- package/dist/tui/tool-renderers/types.js +1 -0
- package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
- package/dist/tui/tool-renderers/write-preview.js +32 -0
- package/dist/tui/tool-renderers/write.d.ts +6 -0
- package/dist/tui/tool-renderers/write.js +88 -0
- package/dist/tui/trace-groups.d.ts +27 -0
- package/dist/tui/trace-groups.js +419 -0
- package/dist/tui/wordmark.d.ts +15 -0
- package/dist/tui/wordmark.js +179 -0
- package/dist/tui-ink/app.js +45 -9
- package/dist/tui-ink/approval/approval-dialog.js +7 -1
- package/dist/tui-ink/display-history.d.ts +1 -0
- package/dist/tui-ink/display-history.js +5 -4
- package/dist/tui-ink/message-list.js +23 -9
- package/dist/tui-ink/theme.d.ts +3 -9
- package/dist/tui-ink/theme.js +39 -45
- package/dist/tui-ink/trace-groups.js +1 -1
- package/dist/tui-ink/welcome.js +22 -78
- package/dist/tui-opentui/app.d.ts +54 -0
- package/dist/tui-opentui/app.js +1365 -0
- package/dist/tui-opentui/approval/approval-dialog.d.ts +15 -0
- package/dist/tui-opentui/approval/approval-dialog.js +145 -0
- package/dist/tui-opentui/approval/diff-view.d.ts +9 -0
- package/dist/tui-opentui/approval/diff-view.js +43 -0
- package/dist/tui-opentui/approval/select.d.ts +37 -0
- package/dist/tui-opentui/approval/select.js +91 -0
- package/dist/tui-opentui/detect-theme.d.ts +2 -0
- package/dist/tui-opentui/detect-theme.js +87 -0
- package/dist/tui-opentui/display-history.d.ts +56 -0
- package/dist/tui-opentui/display-history.js +130 -0
- package/dist/tui-opentui/edit-diff.d.ts +11 -0
- package/dist/tui-opentui/edit-diff.js +57 -0
- package/dist/tui-opentui/feedback-dialog.d.ts +21 -0
- package/dist/tui-opentui/feedback-dialog.js +164 -0
- package/dist/tui-opentui/feishu-setup-picker.d.ts +7 -0
- package/dist/tui-opentui/feishu-setup-picker.js +272 -0
- package/dist/tui-opentui/file-mentions.d.ts +29 -0
- package/dist/tui-opentui/file-mentions.js +174 -0
- package/dist/tui-opentui/footer.d.ts +26 -0
- package/dist/tui-opentui/footer.js +40 -0
- package/dist/tui-opentui/image-paste.d.ts +54 -0
- package/dist/tui-opentui/image-paste.js +288 -0
- package/dist/tui-opentui/input-box.d.ts +34 -0
- package/dist/tui-opentui/input-box.js +471 -0
- package/dist/tui-opentui/input-history.d.ts +16 -0
- package/dist/tui-opentui/input-history.js +79 -0
- package/dist/tui-opentui/markdown.d.ts +66 -0
- package/dist/tui-opentui/markdown.js +127 -0
- package/dist/tui-opentui/message-list.d.ts +31 -0
- package/dist/tui-opentui/message-list.js +128 -0
- package/dist/tui-opentui/model-picker.d.ts +63 -0
- package/dist/tui-opentui/model-picker.js +450 -0
- package/dist/tui-opentui/plan-confirm.d.ts +9 -0
- package/dist/tui-opentui/plan-confirm.js +124 -0
- package/dist/tui-opentui/question-dialog.d.ts +10 -0
- package/dist/tui-opentui/question-dialog.js +110 -0
- package/dist/tui-opentui/recent-activity.d.ts +8 -0
- package/dist/tui-opentui/recent-activity.js +71 -0
- package/dist/tui-opentui/run-session-picker.d.ts +10 -0
- package/dist/tui-opentui/run-session-picker.js +28 -0
- package/dist/tui-opentui/run.d.ts +38 -0
- package/dist/tui-opentui/run.js +48 -0
- package/dist/tui-opentui/session-picker.d.ts +12 -0
- package/dist/tui-opentui/session-picker.js +120 -0
- package/dist/tui-opentui/theme.d.ts +89 -0
- package/dist/tui-opentui/theme.js +157 -0
- package/dist/tui-opentui/todos.d.ts +9 -0
- package/dist/tui-opentui/todos.js +45 -0
- package/dist/tui-opentui/trace-groups.d.ts +27 -0
- package/dist/tui-opentui/trace-groups.js +419 -0
- package/dist/tui-opentui/use-terminal-size.d.ts +4 -0
- package/dist/tui-opentui/use-terminal-size.js +5 -0
- package/dist/tui-opentui/welcome.d.ts +25 -0
- package/dist/tui-opentui/welcome.js +77 -0
- package/dist/types.d.ts +36 -2
- package/package.json +5 -1
package/dist/tui-ink/app.js
CHANGED
|
@@ -180,9 +180,40 @@ function withMessageKey(message) {
|
|
|
180
180
|
const prefix = message.role === "user" ? "user" : message.role === "error" ? "err" : "asst";
|
|
181
181
|
return { ...message, key: nextDisplayMessageKey(prefix) };
|
|
182
182
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
// Keep the live (non-Static) region small so non-GPU terminals (xterm.js DOM
|
|
184
|
+
// renderer, ssh into a basic terminal, tmux without GPU) don't flicker when
|
|
185
|
+
// Ink re-reconciles the streaming block on every token. Flushing earlier and
|
|
186
|
+
// in smaller chunks shifts most of the answer into terminal scrollback, where
|
|
187
|
+
// it's a one-time write that doesn't get re-rendered.
|
|
188
|
+
const STREAMING_STATIC_FLUSH_MIN_CHARS = 600;
|
|
189
|
+
const STREAMING_STATIC_FLUSH_TARGET_CHARS = 400;
|
|
190
|
+
const STREAMING_STATIC_FLUSH_MIN_TAIL = 120;
|
|
191
|
+
/**
|
|
192
|
+
* True iff `prefix` ends inside an open ```/~~~ fenced code block. Splitting
|
|
193
|
+
* the streaming buffer at such a point would let the flushed half render
|
|
194
|
+
* without its closing fence — `MarkdownContent` would then treat the body as
|
|
195
|
+
* plain prose and the trailing half would render as an isolated code block
|
|
196
|
+
* with no opener. Fence delimiters of different families don't close each
|
|
197
|
+
* other (a `~~~` inside a ``` block is just text). We use a permissive
|
|
198
|
+
* "line starts with three or more of the same char" rule, ignoring the info
|
|
199
|
+
* string — that's enough to spot when we're mid-block.
|
|
200
|
+
*/
|
|
201
|
+
function endsInsideUnclosedCodeFence(prefix) {
|
|
202
|
+
let openMarker = null;
|
|
203
|
+
for (const rawLine of prefix.split("\n")) {
|
|
204
|
+
const line = rawLine.replace(/^ {0,3}/, "");
|
|
205
|
+
if (openMarker === null) {
|
|
206
|
+
if (line.startsWith("```"))
|
|
207
|
+
openMarker = "`";
|
|
208
|
+
else if (line.startsWith("~~~"))
|
|
209
|
+
openMarker = "~";
|
|
210
|
+
}
|
|
211
|
+
else if (line.startsWith(openMarker.repeat(3))) {
|
|
212
|
+
openMarker = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return openMarker !== null;
|
|
216
|
+
}
|
|
186
217
|
function findStreamingStaticFlushIndex(content) {
|
|
187
218
|
if (content.length < STREAMING_STATIC_FLUSH_MIN_CHARS)
|
|
188
219
|
return -1;
|
|
@@ -192,12 +223,20 @@ function findStreamingStaticFlushIndex(content) {
|
|
|
192
223
|
const search = content.slice(0, upper);
|
|
193
224
|
const paragraphBreak = search.lastIndexOf("\n\n");
|
|
194
225
|
if (paragraphBreak >= STREAMING_STATIC_FLUSH_TARGET_CHARS / 2) {
|
|
195
|
-
|
|
226
|
+
const splitIndex = paragraphBreak + 2;
|
|
227
|
+
if (!endsInsideUnclosedCodeFence(content.slice(0, splitIndex))) {
|
|
228
|
+
return splitIndex;
|
|
229
|
+
}
|
|
196
230
|
}
|
|
197
231
|
const lineBreak = search.lastIndexOf("\n");
|
|
198
232
|
if (lineBreak >= STREAMING_STATIC_FLUSH_TARGET_CHARS / 2) {
|
|
199
|
-
|
|
233
|
+
const splitIndex = lineBreak + 1;
|
|
234
|
+
if (!endsInsideUnclosedCodeFence(content.slice(0, splitIndex))) {
|
|
235
|
+
return splitIndex;
|
|
236
|
+
}
|
|
200
237
|
}
|
|
238
|
+
// Inside an open code fence: hold off flushing until the closing fence
|
|
239
|
+
// arrives. The live region grows a bit, but Markdown rendering stays correct.
|
|
201
240
|
return -1;
|
|
202
241
|
}
|
|
203
242
|
function cloneDisplayPart(part) {
|
|
@@ -957,10 +996,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
957
996
|
catch (err) {
|
|
958
997
|
commitAssistantMessage();
|
|
959
998
|
if (err instanceof AgentAbortError || err?.name === "AbortError") {
|
|
960
|
-
updateDisplayMessages((
|
|
961
|
-
...prev,
|
|
962
|
-
withMessageKey({ role: "assistant", content: "Cancelled." }),
|
|
963
|
-
]);
|
|
999
|
+
updateDisplayMessages(() => reconstructDisplayMessages(agent.messages));
|
|
964
1000
|
}
|
|
965
1001
|
else {
|
|
966
1002
|
updateDisplayMessages((prev) => [
|
|
@@ -53,7 +53,7 @@ function buildOptions(request) {
|
|
|
53
53
|
},
|
|
54
54
|
];
|
|
55
55
|
}
|
|
56
|
-
// edit / write
|
|
56
|
+
// edit / write / patch
|
|
57
57
|
return [
|
|
58
58
|
{ id: "yes", label: "Yes", allowAmend: true, amendPlaceholder: "and tell Claude what to do next" },
|
|
59
59
|
{
|
|
@@ -69,6 +69,8 @@ function dialogTitle(req) {
|
|
|
69
69
|
switch (req.type) {
|
|
70
70
|
case "edit":
|
|
71
71
|
return "Edit file";
|
|
72
|
+
case "patch":
|
|
73
|
+
return "Apply patch";
|
|
72
74
|
case "write":
|
|
73
75
|
return req.fileExists ? "Overwrite file" : "Create file";
|
|
74
76
|
case "bash":
|
|
@@ -81,6 +83,8 @@ function dialogQuestion(req) {
|
|
|
81
83
|
switch (req.type) {
|
|
82
84
|
case "edit":
|
|
83
85
|
return `Do you want to make this edit to ${basename(req.path)}?`;
|
|
86
|
+
case "patch":
|
|
87
|
+
return `Do you want to apply this patch to ${req.paths.length} file${req.paths.length === 1 ? "" : "s"}?`;
|
|
84
88
|
case "write":
|
|
85
89
|
return `Do you want to ${req.fileExists ? "overwrite" : "create"} ${basename(req.path)}?`;
|
|
86
90
|
case "bash":
|
|
@@ -99,6 +103,8 @@ function RequestPreview({ request }) {
|
|
|
99
103
|
return _jsx(BashPreview, { command: request.command, cwd: request.cwd });
|
|
100
104
|
case "edit":
|
|
101
105
|
return _jsx(DiffView, { diff: request.diff });
|
|
106
|
+
case "patch":
|
|
107
|
+
return _jsx(DiffView, { diff: request.diff });
|
|
102
108
|
case "write":
|
|
103
109
|
return _jsx(WritePreview, { path: request.path, content: request.content });
|
|
104
110
|
}
|
|
@@ -46,7 +46,6 @@ export function toolCallsFromParts(parts) {
|
|
|
46
46
|
const FULL_DETAIL_WINDOW = 24;
|
|
47
47
|
const MAX_OLD_CONTENT_CHARS = 1200;
|
|
48
48
|
const MAX_OLD_REASONING_CHARS = 600;
|
|
49
|
-
const MAX_OLD_TOOL_RESULT_CHARS = 800;
|
|
50
49
|
export function compactDisplayMessages(messages) {
|
|
51
50
|
if (messages.length === 0) {
|
|
52
51
|
return messages;
|
|
@@ -97,11 +96,13 @@ function compactDisplayPart(part) {
|
|
|
97
96
|
};
|
|
98
97
|
}
|
|
99
98
|
function compactToolCall(toolCall) {
|
|
99
|
+
if (toolCall.result === undefined) {
|
|
100
|
+
return toolCall;
|
|
101
|
+
}
|
|
100
102
|
return {
|
|
101
103
|
...toolCall,
|
|
102
|
-
result:
|
|
103
|
-
|
|
104
|
-
: toolCall.result,
|
|
104
|
+
result: undefined,
|
|
105
|
+
resultCollapsed: true,
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
108
|
const PREVIOUS_SUMMARY_PREFIX = /^Previous conversation summary:\s*\n?([\s\S]*)$/;
|
|
@@ -59,7 +59,15 @@ function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, v
|
|
|
59
59
|
const visibleParts = deferredParts.length > 0
|
|
60
60
|
? deferredParts
|
|
61
61
|
: fallbackStreamingParts(deferredContent, tools);
|
|
62
|
-
return (_jsxs(Box, { flexDirection: "column", children: [deferredReasoning && verboseTrace && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: deferredReasoning }) })), visibleParts.length > 0 && (
|
|
62
|
+
return (_jsxs(Box, { flexDirection: "column", children: [deferredReasoning && verboseTrace && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: deferredReasoning }) })), visibleParts.length > 0 && (
|
|
63
|
+
// marginTop intentionally 0: this Box only mounts on the first non-empty
|
|
64
|
+
// streaming frame, so a marginTop=1 here would visibly insert a blank
|
|
65
|
+
// line under the user message right at that moment (the "spinner sits
|
|
66
|
+
// close, then content appears with a sudden gap, then spinner slides
|
|
67
|
+
// down" effect users perceive as flicker on the DOM xterm renderer).
|
|
68
|
+
// marginBottom=1 stays so streamed text doesn't collide with the
|
|
69
|
+
// WaitingIndicator rendered below.
|
|
70
|
+
_jsx(Box, { marginTop: 0, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
|
|
63
71
|
}
|
|
64
72
|
function MessageParts({ parts, terminalColumns, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
|
|
65
73
|
const lastToolsPartIndex = findLastToolsPartIndex(parts);
|
|
@@ -97,8 +105,8 @@ function ToolsPart({ toolCalls, terminalColumns, verboseTrace, pendingApproval,
|
|
|
97
105
|
}
|
|
98
106
|
const lastIdx = toolCalls.length - 1;
|
|
99
107
|
return (_jsx(Box, { flexDirection: "column", children: toolCalls.map((tc, idx) => {
|
|
100
|
-
const isWaitingApproval = tc
|
|
101
|
-
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: tc
|
|
108
|
+
const isWaitingApproval = isToolPending(tc) && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
|
|
109
|
+
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose: verboseTrace, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
|
|
102
110
|
}) }));
|
|
103
111
|
}
|
|
104
112
|
function fallbackStreamingParts(content, tools) {
|
|
@@ -184,7 +192,7 @@ function findActiveTraceGroup(groups, pendingApproval) {
|
|
|
184
192
|
return undefined;
|
|
185
193
|
}
|
|
186
194
|
function isTraceGroupWaitingForApproval(group, pendingApproval) {
|
|
187
|
-
return !!pendingApproval && group.raw.some((tool) => tool
|
|
195
|
+
return !!pendingApproval && group.raw.some((tool) => isToolPending(tool) && approvalMatchesTool(pendingApproval, tool));
|
|
188
196
|
}
|
|
189
197
|
function approvalMatchesTool(hint, tc) {
|
|
190
198
|
if (hint.toolName !== tc.name)
|
|
@@ -194,6 +202,9 @@ function approvalMatchesTool(hint, tc) {
|
|
|
194
202
|
}
|
|
195
203
|
return !hint.path || hint.path === tc.args.path;
|
|
196
204
|
}
|
|
205
|
+
function isToolPending(tool) {
|
|
206
|
+
return tool.result === undefined && tool.resultCollapsed !== true;
|
|
207
|
+
}
|
|
197
208
|
function ReasoningTraceBlock({ reasoning }) {
|
|
198
209
|
const theme = useTheme();
|
|
199
210
|
const lines = React.useMemo(() => reasoning.split("\n").filter((l) => l.trim() !== ""), [reasoning]);
|
|
@@ -272,6 +283,8 @@ function getToolHeader(toolCall) {
|
|
|
272
283
|
}
|
|
273
284
|
}
|
|
274
285
|
function summarizeToolResult(tc) {
|
|
286
|
+
if (tc.resultCollapsed)
|
|
287
|
+
return tc.isError ? "error output collapsed" : "result collapsed";
|
|
275
288
|
if (tc.result === undefined)
|
|
276
289
|
return "pending";
|
|
277
290
|
const raw = tc.result.replace(/\r\n/g, "\n");
|
|
@@ -433,7 +446,7 @@ function ToolCallDisplay({ toolCall, isStreaming, verbose, terminalColumns, show
|
|
|
433
446
|
summary = "⏸ waiting for approval";
|
|
434
447
|
summaryColor = theme.warning;
|
|
435
448
|
}
|
|
436
|
-
else if (toolCall
|
|
449
|
+
else if (isToolPending(toolCall) && toolCall.startedAt) {
|
|
437
450
|
void nowTick;
|
|
438
451
|
summary = "running";
|
|
439
452
|
summaryColor = theme.toolPending;
|
|
@@ -446,18 +459,19 @@ function ToolCallDisplay({ toolCall, isStreaming, verbose, terminalColumns, show
|
|
|
446
459
|
summaryColor = theme.success;
|
|
447
460
|
}
|
|
448
461
|
const editDetails = getEditDiffDetails(toolCall);
|
|
449
|
-
const isEditDiff = editDetails !== null && toolCall.result !== undefined;
|
|
462
|
+
const isEditDiff = editDetails !== null && toolCall.result !== undefined && !toolCall.resultCollapsed;
|
|
463
|
+
const showSummary = !toolCall.resultCollapsed || waitingApproval;
|
|
450
464
|
// Only show the file preview once the tool actually executed. During the
|
|
451
465
|
// streaming-args phase, args.content is incomplete and re-rendering the
|
|
452
466
|
// entire body per delta both looks chaotic and breaks on partial escapes.
|
|
453
|
-
const isWritePreview = toolCall.name === "write" && !toolCall.isError && toolCall.result !== undefined;
|
|
454
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: bulletColor, children: [glyph, " "] }), _jsx(Text, { bold: true, color: theme.toolName, children: name }), header && _jsxs(Text, { color: theme.muted, children: ["(", header, ")"] })] }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: summaryColor, children: ["\u23BF ", summary] }) }), toolCall.isError && toolCall.result && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: toolCall.result.replace(/\r\n/g, "\n").split("\n").slice(0, 6).map((line, i) => (_jsx(Text, { color: theme.error, children: line }, i))) })), isEditDiff && (_jsx(DiffBlock, { diff: editDetails.diff, terminalColumns: terminalColumns, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), isWritePreview && (_jsx(WritePreview, { content: String(toolCall.args.content || ""), maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), !toolCall.isError && !isEditDiff && !isWritePreview && highlighted && (_jsx(OutputPreview, { text: highlighted, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint }))] }));
|
|
467
|
+
const isWritePreview = toolCall.name === "write" && !toolCall.isError && toolCall.result !== undefined && !toolCall.resultCollapsed;
|
|
468
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: bulletColor, children: [glyph, " "] }), _jsx(Text, { bold: true, color: theme.toolName, children: name }), header && _jsxs(Text, { color: theme.muted, children: ["(", header, ")"] })] }), showSummary && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: summaryColor, children: ["\u23BF ", summary] }) })), toolCall.isError && toolCall.result && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: toolCall.result.replace(/\r\n/g, "\n").split("\n").slice(0, 6).map((line, i) => (_jsx(Text, { color: theme.error, children: line }, i))) })), isEditDiff && (_jsx(DiffBlock, { diff: editDetails.diff, terminalColumns: terminalColumns, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), isWritePreview && (_jsx(WritePreview, { content: String(toolCall.args.content || ""), maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), !toolCall.isError && !isEditDiff && !isWritePreview && highlighted && (_jsx(OutputPreview, { text: highlighted, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint }))] }));
|
|
455
469
|
}
|
|
456
470
|
function SubagentToolDisplay({ toolCall, verbose, terminalColumns, compactTop, }) {
|
|
457
471
|
const theme = useTheme();
|
|
458
472
|
const subagents = subagentsFrom(toolCall);
|
|
459
473
|
const hasError = toolCall.isError || subagents.some((subagent) => (subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled"));
|
|
460
|
-
const bulletColor = hasError ? theme.error : toolCall
|
|
474
|
+
const bulletColor = hasError ? theme.error : isToolPending(toolCall) ? theme.toolPending : theme.user;
|
|
461
475
|
const detailWidth = Math.max(24, terminalColumns - 10);
|
|
462
476
|
const rows = verbose ? sortSubagents(subagents) : sortSubagents(subagents).slice(0, 4);
|
|
463
477
|
const omitted = Math.max(0, subagents.length - rows.length);
|
package/dist/tui-ink/theme.d.ts
CHANGED
|
@@ -49,15 +49,9 @@ export interface Theme {
|
|
|
49
49
|
}
|
|
50
50
|
export declare const darkTheme: Theme;
|
|
51
51
|
/**
|
|
52
|
-
* Light palette
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* effective.
|
|
56
|
-
* 2. Specific hex values are used wherever the dark palette assumed a dark
|
|
57
|
-
* background (notably accent/code/trace colors and message bubbles).
|
|
58
|
-
* Each hex was picked to clear WCAG AA contrast (4.5:1) against a near-
|
|
59
|
-
* white background (#fafafa) or, when applicable, against the explicit
|
|
60
|
-
* surface color in the same palette (e.g. diffAddFg vs diffAdd).
|
|
52
|
+
* Light palette aligned with the restored OpenTUI runtime: paper-neutral
|
|
53
|
+
* surfaces, blue focus/user rails, warm command accent, and semantic tool
|
|
54
|
+
* colors tuned for readable contrast on a light terminal background.
|
|
61
55
|
*/
|
|
62
56
|
export declare const lightTheme: Theme;
|
|
63
57
|
export declare const ThemeProvider: import("react").Provider<Theme>;
|
package/dist/tui-ink/theme.js
CHANGED
|
@@ -47,53 +47,47 @@ export const darkTheme = {
|
|
|
47
47
|
diffRemoveFg: "#F48771",
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
50
|
-
* Light palette
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* effective.
|
|
54
|
-
* 2. Specific hex values are used wherever the dark palette assumed a dark
|
|
55
|
-
* background (notably accent/code/trace colors and message bubbles).
|
|
56
|
-
* Each hex was picked to clear WCAG AA contrast (4.5:1) against a near-
|
|
57
|
-
* white background (#fafafa) or, when applicable, against the explicit
|
|
58
|
-
* surface color in the same palette (e.g. diffAddFg vs diffAdd).
|
|
50
|
+
* Light palette aligned with the restored OpenTUI runtime: paper-neutral
|
|
51
|
+
* surfaces, blue focus/user rails, warm command accent, and semantic tool
|
|
52
|
+
* colors tuned for readable contrast on a light terminal background.
|
|
59
53
|
*/
|
|
60
54
|
export const lightTheme = {
|
|
61
|
-
user: "
|
|
62
|
-
agent: "
|
|
63
|
-
error: "
|
|
64
|
-
warning: "#
|
|
65
|
-
success: "
|
|
66
|
-
accent: "#
|
|
67
|
-
border: "
|
|
68
|
-
borderActive: "#
|
|
69
|
-
inputBorder: "#
|
|
70
|
-
inputBorderDisabled: "#
|
|
71
|
-
inputBg: "#
|
|
72
|
-
inputBgDisabled: "#
|
|
73
|
-
inputText: "#
|
|
74
|
-
inputPlaceholder: "#
|
|
75
|
-
muted: "
|
|
76
|
-
dim: "
|
|
77
|
-
thinking: "
|
|
78
|
-
thinkingDim: "
|
|
79
|
-
toolName: "#
|
|
80
|
-
toolResult: "
|
|
81
|
-
toolError: "
|
|
82
|
-
toolPending: "#
|
|
83
|
-
code: "#
|
|
84
|
-
traceAction: "#
|
|
85
|
-
traceCount: "#
|
|
86
|
-
traceDetail: "
|
|
87
|
-
traceCommand: "#
|
|
88
|
-
tracePending: "#
|
|
89
|
-
userMessageBorder: "#
|
|
90
|
-
userMessageBg: "#
|
|
91
|
-
userMessageText: "#
|
|
92
|
-
userRail: "#
|
|
93
|
-
diffAdd: "#
|
|
94
|
-
diffRemove: "#
|
|
95
|
-
diffAddFg: "#
|
|
96
|
-
diffRemoveFg: "#
|
|
55
|
+
user: "#356FD2",
|
|
56
|
+
agent: "#171717",
|
|
57
|
+
error: "#B62633",
|
|
58
|
+
warning: "#8B4A00",
|
|
59
|
+
success: "#2F7D4A",
|
|
60
|
+
accent: "#8B4A00",
|
|
61
|
+
border: "#B9BDB8",
|
|
62
|
+
borderActive: "#356FD2",
|
|
63
|
+
inputBorder: "#356FD2",
|
|
64
|
+
inputBorderDisabled: "#D7DAD4",
|
|
65
|
+
inputBg: "#F1F3F0",
|
|
66
|
+
inputBgDisabled: "#E6E8E3",
|
|
67
|
+
inputText: "#171717",
|
|
68
|
+
inputPlaceholder: "#6F7377",
|
|
69
|
+
muted: "#6F7377",
|
|
70
|
+
dim: "#8B9094",
|
|
71
|
+
thinking: "#5F666D",
|
|
72
|
+
thinkingDim: "#8B9094",
|
|
73
|
+
toolName: "#495057",
|
|
74
|
+
toolResult: "#171717",
|
|
75
|
+
toolError: "#B62633",
|
|
76
|
+
toolPending: "#8B4A00",
|
|
77
|
+
code: "#2F7D4A",
|
|
78
|
+
traceAction: "#8B4A00",
|
|
79
|
+
traceCount: "#6F7377",
|
|
80
|
+
traceDetail: "#8B9094",
|
|
81
|
+
traceCommand: "#257E8A",
|
|
82
|
+
tracePending: "#8B4A00",
|
|
83
|
+
userMessageBorder: "#356FD2",
|
|
84
|
+
userMessageBg: "#F1F3F0",
|
|
85
|
+
userMessageText: "#234B93",
|
|
86
|
+
userRail: "#356FD2",
|
|
87
|
+
diffAdd: "#D7E8D8",
|
|
88
|
+
diffRemove: "#F7DADC",
|
|
89
|
+
diffAddFg: "#173D2D",
|
|
90
|
+
diffRemoveFg: "#5D1922",
|
|
97
91
|
};
|
|
98
92
|
const ThemeContext = createContext(darkTheme);
|
|
99
93
|
export const ThemeProvider = ThemeContext.Provider;
|
|
@@ -323,7 +323,7 @@ function isFailedSubagent(subagent) {
|
|
|
323
323
|
return subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled";
|
|
324
324
|
}
|
|
325
325
|
function isToolPending(tool) {
|
|
326
|
-
return tool.result === undefined;
|
|
326
|
+
return tool.result === undefined && tool.resultCollapsed !== true;
|
|
327
327
|
}
|
|
328
328
|
function isDirectoryLikeGlob(pattern) {
|
|
329
329
|
const normalized = pattern.trim();
|
package/dist/tui-ink/welcome.js
CHANGED
|
@@ -3,80 +3,10 @@ import React from "react";
|
|
|
3
3
|
import { Box, Text } from "ink";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { useTheme } from "./theme.js";
|
|
6
|
+
import { BUBBLE_COMPACT_WORDMARK, BUBBLE_WORDMARK, bubbleWordmarkLineText, bubbleWordmarkMaxWidth, } from "../tui/wordmark.js";
|
|
6
7
|
const require = createRequire(import.meta.url);
|
|
7
8
|
const PACKAGE_VERSION = readPackageVersion();
|
|
8
|
-
const
|
|
9
|
-
[
|
|
10
|
-
"██████ ",
|
|
11
|
-
"██ ██",
|
|
12
|
-
"██ ██",
|
|
13
|
-
"██████ ",
|
|
14
|
-
"██ ██",
|
|
15
|
-
"██ ██",
|
|
16
|
-
"██████ ",
|
|
17
|
-
],
|
|
18
|
-
[
|
|
19
|
-
"██ ██",
|
|
20
|
-
"██ ██",
|
|
21
|
-
"██ ██",
|
|
22
|
-
"██ ██",
|
|
23
|
-
"██ ██",
|
|
24
|
-
"██ ██",
|
|
25
|
-
" █████ ",
|
|
26
|
-
],
|
|
27
|
-
[
|
|
28
|
-
"██████ ",
|
|
29
|
-
"██ ██",
|
|
30
|
-
"██ ██",
|
|
31
|
-
"██████ ",
|
|
32
|
-
"██ ██",
|
|
33
|
-
"██ ██",
|
|
34
|
-
"██████ ",
|
|
35
|
-
],
|
|
36
|
-
[
|
|
37
|
-
"██████ ",
|
|
38
|
-
"██ ██",
|
|
39
|
-
"██ ██",
|
|
40
|
-
"██████ ",
|
|
41
|
-
"██ ██",
|
|
42
|
-
"██ ██",
|
|
43
|
-
"██████ ",
|
|
44
|
-
],
|
|
45
|
-
[
|
|
46
|
-
"██ ",
|
|
47
|
-
"██ ",
|
|
48
|
-
"██ ",
|
|
49
|
-
"██ ",
|
|
50
|
-
"██ ",
|
|
51
|
-
"██ ",
|
|
52
|
-
"███████",
|
|
53
|
-
],
|
|
54
|
-
[
|
|
55
|
-
"███████",
|
|
56
|
-
"██ ",
|
|
57
|
-
"██ ",
|
|
58
|
-
"██████ ",
|
|
59
|
-
"██ ",
|
|
60
|
-
"██ ",
|
|
61
|
-
"███████",
|
|
62
|
-
],
|
|
63
|
-
];
|
|
64
|
-
/**
|
|
65
|
-
* Derive a 6-step logo gradient from the active theme tokens so the banner
|
|
66
|
-
* stays readable on both dark and light backgrounds.
|
|
67
|
-
*/
|
|
68
|
-
function logoColors(theme) {
|
|
69
|
-
return [
|
|
70
|
-
theme.userMessageText,
|
|
71
|
-
theme.userMessageText,
|
|
72
|
-
theme.inputBorder,
|
|
73
|
-
theme.inputBorder,
|
|
74
|
-
theme.traceCommand,
|
|
75
|
-
theme.traceCommand,
|
|
76
|
-
];
|
|
77
|
-
}
|
|
78
|
-
const COMPACT_LOGO = ["B", "U", "B", "B", "L", "E"];
|
|
79
|
-
const WIDE_LOGO_MIN_WIDTH = 52;
|
|
9
|
+
const WIDE_LOGO_MIN_WIDTH = bubbleWordmarkMaxWidth(BUBBLE_WORDMARK) + 4;
|
|
80
10
|
export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
|
|
81
11
|
// Keep banner visibility tied to the initial history, not transient overlays,
|
|
82
12
|
// so opening and closing a picker does not move it in the transcript.
|
|
@@ -96,18 +26,32 @@ export function WelcomeBanner({ terminalColumns, modelLabel, cwd, tips, skillsCo
|
|
|
96
26
|
: "Type / for commands and @ to reference files";
|
|
97
27
|
const modelLine = modelLabel ? `${modelLabel}${cwd ? ` · ${cwd}` : ""}` : cwd;
|
|
98
28
|
return (_jsxs(Box, { width: effectiveWidth, flexDirection: "column", alignItems: "center", marginBottom: 1, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: useWideLogo
|
|
99
|
-
?
|
|
29
|
+
? BUBBLE_WORDMARK.map((line, rowIndex) => (_jsx(LogoRow, { line: line }, `logo-row-${rowIndex}`)))
|
|
100
30
|
: _jsx(CompactLogo, {}) }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { bold: true, color: theme.muted, children: PACKAGE_VERSION }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, color: theme.userMessageText, children: "TIP: " }), _jsx(Text, { bold: true, color: theme.userMessageText, children: tip })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "shift+tab to cycle modes \u00B7 ctrl+r for reasoning \u00B7 ctrl+o for trace" }) }), modelLine && (_jsx(Box, { children: _jsx(Text, { color: theme.muted, children: truncateToWidth(modelLine, effectiveWidth - 4) }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(StatusItem, { label: "Skills", count: skillsCount, ok: skillsCount > 0 }), _jsx(Text, { color: theme.muted, children: " " }), _jsx(StatusItem, { label: "MCPs", count: mcpConnectedCount, total: mcpTotalCount, ok: mcpTotalCount === 0 || mcpConnectedCount === mcpTotalCount }), _jsx(Text, { color: theme.muted, children: " " }), _jsx(StatusItem, { label: "AGENTS.md", ok: hasAgentsFile })] })] }));
|
|
101
31
|
}
|
|
102
|
-
function LogoRow({
|
|
32
|
+
function LogoRow({ line }) {
|
|
103
33
|
const theme = useTheme();
|
|
104
|
-
|
|
105
|
-
|
|
34
|
+
if (!line.segments) {
|
|
35
|
+
return _jsx(Text, { bold: true, color: logoColor(theme, line.tone ?? "caption"), children: line.text ?? "" });
|
|
36
|
+
}
|
|
37
|
+
return (_jsx(Box, { children: line.segments.map((segment, index) => (_jsx(React.Fragment, { children: _jsx(Text, { bold: true, color: logoColor(theme, segment.tone), children: segment.text }) }, `${index}-${segment.text}`))) }));
|
|
106
38
|
}
|
|
107
39
|
function CompactLogo() {
|
|
108
40
|
const theme = useTheme();
|
|
109
|
-
const
|
|
110
|
-
|
|
41
|
+
const line = BUBBLE_COMPACT_WORDMARK[0];
|
|
42
|
+
if (!line?.segments) {
|
|
43
|
+
return _jsx(Text, { bold: true, color: theme.warning, children: bubbleWordmarkLineText(line ?? { text: "" }) });
|
|
44
|
+
}
|
|
45
|
+
return (_jsx(Box, { children: line.segments.map((segment, index) => (_jsx(Text, { bold: true, color: logoColor(theme, segment.tone), children: segment.text }, `${segment.text}-${index}`))) }));
|
|
46
|
+
}
|
|
47
|
+
function logoColor(theme, tone) {
|
|
48
|
+
switch (tone) {
|
|
49
|
+
case "brand": return theme.warning;
|
|
50
|
+
case "ink": return theme.userMessageText;
|
|
51
|
+
case "stone": return theme.muted;
|
|
52
|
+
case "soft": return theme.dim;
|
|
53
|
+
case "caption": return theme.muted;
|
|
54
|
+
}
|
|
111
55
|
}
|
|
112
56
|
function StatusItem({ label, count, total, ok, }) {
|
|
113
57
|
const theme = useTheme();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/react */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { type Agent } from "../agent.js";
|
|
4
|
+
import type { CliArgs } from "../cli.js";
|
|
5
|
+
import type { SessionManager } from "../session.js";
|
|
6
|
+
import type { PlanDecision, Provider } from "../types.js";
|
|
7
|
+
import { type ResolvedTheme, type ThemeMode } from "./theme.js";
|
|
8
|
+
import { ProviderRegistry } from "../provider-registry.js";
|
|
9
|
+
import { SkillRegistry } from "../skills/registry.js";
|
|
10
|
+
import type { ApprovalDecision, ApprovalRequest } from "../approval/types.js";
|
|
11
|
+
import type { BashAllowlist } from "../approval/session-cache.js";
|
|
12
|
+
import type { SettingsManager } from "../permissions/settings.js";
|
|
13
|
+
import type { McpManager } from "../mcp/manager.js";
|
|
14
|
+
import type { LspService } from "../lsp/index.js";
|
|
15
|
+
import type { QuestionController } from "../question/index.js";
|
|
16
|
+
import type { MemoryScope } from "../memory/index.js";
|
|
17
|
+
export interface PlanHandlerRef {
|
|
18
|
+
current?: (plan: string) => Promise<PlanDecision>;
|
|
19
|
+
}
|
|
20
|
+
export interface ApprovalHandlerRef {
|
|
21
|
+
current?: (req: ApprovalRequest) => Promise<ApprovalDecision>;
|
|
22
|
+
}
|
|
23
|
+
interface AppProps {
|
|
24
|
+
agent: Agent;
|
|
25
|
+
args: CliArgs;
|
|
26
|
+
sessionManager?: SessionManager;
|
|
27
|
+
createProvider?: (providerId: string, apiKey: string, baseURL: string) => Provider;
|
|
28
|
+
registry?: ProviderRegistry;
|
|
29
|
+
skillRegistry?: SkillRegistry;
|
|
30
|
+
planHandlerRef?: PlanHandlerRef;
|
|
31
|
+
approvalHandlerRef?: ApprovalHandlerRef;
|
|
32
|
+
questionController?: QuestionController;
|
|
33
|
+
bashAllowlist?: BashAllowlist;
|
|
34
|
+
settingsManager?: SettingsManager;
|
|
35
|
+
lspService?: LspService;
|
|
36
|
+
mcpManager?: McpManager;
|
|
37
|
+
themeMode?: ThemeMode;
|
|
38
|
+
themeOverrides?: Record<string, string>;
|
|
39
|
+
detectedTheme?: ResolvedTheme;
|
|
40
|
+
onThemeModeChange?: (mode: ThemeMode) => void;
|
|
41
|
+
flushMemory?: () => Promise<void>;
|
|
42
|
+
runMemoryCompaction?: () => Promise<string>;
|
|
43
|
+
runMemorySummary?: (scope?: MemoryScope) => Promise<string>;
|
|
44
|
+
runMemoryRefresh?: (scope?: MemoryScope) => Promise<string>;
|
|
45
|
+
/** Whether the bypassPermissions mode is reachable via Shift+Tab cycling. */
|
|
46
|
+
bypassEnabled?: boolean;
|
|
47
|
+
onExit?: (summary: ExitSummary) => void;
|
|
48
|
+
}
|
|
49
|
+
export interface ExitSummary {
|
|
50
|
+
/** Wall-clock duration of the session, in milliseconds. */
|
|
51
|
+
wallMs: number;
|
|
52
|
+
}
|
|
53
|
+
export declare 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 }: AppProps): React.ReactNode;
|
|
54
|
+
export {};
|