@bubblebrain-ai/bubble 0.0.9 → 0.0.11
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 +5 -0
- 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 +295 -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 +285 -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 +78 -29
- package/dist/model-catalog.js +3 -0
- package/dist/session.d.ts +11 -0
- package/dist/session.js +88 -2
- package/dist/slash-commands/commands.js +13 -0
- package/dist/slash-commands/feishu.d.ts +17 -0
- package/dist/slash-commands/feishu.js +400 -0
- package/dist/slash-commands/types.d.ts +3 -1
- package/dist/tui-ink/app.js +218 -60
- 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 +3 -0
- package/dist/tui-ink/input-box.js +27 -0
- 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 +85 -34
- package/dist/tui-ink/model-picker.js +1 -4
- 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 +112 -0
- package/dist/tui-ink/terminal-mouse.d.ts +4 -0
- package/dist/tui-ink/terminal-mouse.js +23 -0
- 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
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helpers for the TUI MCP sidebar widget.
|
|
3
|
-
*
|
|
4
|
-
* Kept separate from run.ts so they can be unit-tested without mounting the
|
|
5
|
-
* full opentui renderer. run.ts imports sidebarMcpRowsFromStates and
|
|
6
|
-
* renderMcpRowMarker for display; everything else stays in-TUI because it
|
|
7
|
-
* depends on theme / renderable construction.
|
|
8
|
-
*/
|
|
9
|
-
import type { McpServerState } from "../mcp/types.js";
|
|
10
|
-
export interface SidebarMcpRow {
|
|
11
|
-
name: string;
|
|
12
|
-
kind: "connected" | "failed" | "disabled";
|
|
13
|
-
label: string;
|
|
14
|
-
toolCount: number;
|
|
15
|
-
promptCount: number;
|
|
16
|
-
errorDetail?: string;
|
|
17
|
-
canReconnect: boolean;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Project raw McpServerState[] (from McpManager.getStates()) into the display
|
|
21
|
-
* shape consumed by the sidebar's row renderer. Deterministic and side-effect
|
|
22
|
-
* free — the single source of truth for what the widget shows per server.
|
|
23
|
-
*/
|
|
24
|
-
export declare function sidebarMcpRowsFromStates(states: McpServerState[]): SidebarMcpRow[];
|
|
25
|
-
/**
|
|
26
|
-
* Single-char status marker used at the start of each sidebar row.
|
|
27
|
-
* Mirrors opencode's convention so a connected row reads as a bullet, failed
|
|
28
|
-
* as a cross, disabled as a hollow circle. Colour is applied separately by
|
|
29
|
-
* the caller using theme mapping.
|
|
30
|
-
*/
|
|
31
|
-
export declare function renderMcpRowMarker(kind: SidebarMcpRow["kind"]): string;
|
package/dist/tui/sidebar-mcp.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helpers for the TUI MCP sidebar widget.
|
|
3
|
-
*
|
|
4
|
-
* Kept separate from run.ts so they can be unit-tested without mounting the
|
|
5
|
-
* full opentui renderer. run.ts imports sidebarMcpRowsFromStates and
|
|
6
|
-
* renderMcpRowMarker for display; everything else stays in-TUI because it
|
|
7
|
-
* depends on theme / renderable construction.
|
|
8
|
-
*/
|
|
9
|
-
const ERROR_LABEL_MAX = 32;
|
|
10
|
-
function truncateInline(s, n) {
|
|
11
|
-
return s.length <= n ? s : s.slice(0, n - 1) + "…";
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Project raw McpServerState[] (from McpManager.getStates()) into the display
|
|
15
|
-
* shape consumed by the sidebar's row renderer. Deterministic and side-effect
|
|
16
|
-
* free — the single source of truth for what the widget shows per server.
|
|
17
|
-
*/
|
|
18
|
-
export function sidebarMcpRowsFromStates(states) {
|
|
19
|
-
return states.map((state) => {
|
|
20
|
-
const kind = state.status.kind;
|
|
21
|
-
const toolCount = kind === "connected" ? state.status.tools.length : 0;
|
|
22
|
-
const promptCount = kind === "connected" ? state.status.prompts.length : 0;
|
|
23
|
-
const errorDetail = kind === "failed" ? state.status.error : undefined;
|
|
24
|
-
let label;
|
|
25
|
-
if (kind === "connected") {
|
|
26
|
-
const parts = [];
|
|
27
|
-
parts.push(`${toolCount} tool${toolCount === 1 ? "" : "s"}`);
|
|
28
|
-
if (promptCount > 0) {
|
|
29
|
-
parts.push(`${promptCount} prompt${promptCount === 1 ? "" : "s"}`);
|
|
30
|
-
}
|
|
31
|
-
label = parts.join(", ");
|
|
32
|
-
}
|
|
33
|
-
else if (kind === "failed") {
|
|
34
|
-
label = truncateInline(state.status.error, ERROR_LABEL_MAX);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
label = "disabled";
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
name: state.name,
|
|
41
|
-
kind,
|
|
42
|
-
label,
|
|
43
|
-
toolCount,
|
|
44
|
-
promptCount,
|
|
45
|
-
errorDetail,
|
|
46
|
-
canReconnect: kind === "failed" || kind === "disabled",
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Single-char status marker used at the start of each sidebar row.
|
|
52
|
-
* Mirrors opencode's convention so a connected row reads as a bullet, failed
|
|
53
|
-
* as a cross, disabled as a hollow circle. Colour is applied separately by
|
|
54
|
-
* the caller using theme mapping.
|
|
55
|
-
*/
|
|
56
|
-
export function renderMcpRowMarker(kind) {
|
|
57
|
-
if (kind === "connected")
|
|
58
|
-
return "●";
|
|
59
|
-
if (kind === "failed")
|
|
60
|
-
return "✗";
|
|
61
|
-
return "○";
|
|
62
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export interface SidebarFileChange {
|
|
2
|
-
file: string;
|
|
3
|
-
additions: number;
|
|
4
|
-
deletions: number;
|
|
5
|
-
}
|
|
6
|
-
export interface SidebarGitState {
|
|
7
|
-
branch?: string;
|
|
8
|
-
files: SidebarFileChange[];
|
|
9
|
-
}
|
|
10
|
-
export declare function parseGitNumstat(output: string): SidebarFileChange[];
|
|
11
|
-
export declare function mergeFileChanges(...groups: SidebarFileChange[][]): SidebarFileChange[];
|
|
12
|
-
export declare function readGitSidebarState(cwd: string): SidebarGitState;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
export function parseGitNumstat(output) {
|
|
3
|
-
return output
|
|
4
|
-
.split(/\r?\n/)
|
|
5
|
-
.map((line) => line.trim())
|
|
6
|
-
.filter(Boolean)
|
|
7
|
-
.map((line) => {
|
|
8
|
-
const [rawAdditions, rawDeletions, ...pathParts] = line.split(/\t/);
|
|
9
|
-
const file = pathParts.join("\t").trim();
|
|
10
|
-
if (!file)
|
|
11
|
-
return undefined;
|
|
12
|
-
return {
|
|
13
|
-
file,
|
|
14
|
-
additions: parseGitCount(rawAdditions),
|
|
15
|
-
deletions: parseGitCount(rawDeletions),
|
|
16
|
-
};
|
|
17
|
-
})
|
|
18
|
-
.filter((item) => !!item);
|
|
19
|
-
}
|
|
20
|
-
export function mergeFileChanges(...groups) {
|
|
21
|
-
const merged = new Map();
|
|
22
|
-
for (const group of groups) {
|
|
23
|
-
for (const item of group) {
|
|
24
|
-
const existing = merged.get(item.file);
|
|
25
|
-
if (!existing) {
|
|
26
|
-
merged.set(item.file, { ...item });
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
existing.additions += item.additions;
|
|
30
|
-
existing.deletions += item.deletions;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return [...merged.values()].sort((a, b) => a.file.localeCompare(b.file));
|
|
34
|
-
}
|
|
35
|
-
export function readGitSidebarState(cwd) {
|
|
36
|
-
const branch = runGit(cwd, ["branch", "--show-current"]).trim()
|
|
37
|
-
|| runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]).trim()
|
|
38
|
-
|| undefined;
|
|
39
|
-
const unstaged = parseGitNumstat(runGit(cwd, ["--no-pager", "diff", "--numstat"]));
|
|
40
|
-
const staged = parseGitNumstat(runGit(cwd, ["--no-pager", "diff", "--cached", "--numstat"]));
|
|
41
|
-
const untracked = runGit(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
42
|
-
.split(/\r?\n/)
|
|
43
|
-
.map((file) => file.trim())
|
|
44
|
-
.filter(Boolean)
|
|
45
|
-
.map((file) => ({ file, additions: 0, deletions: 0 }));
|
|
46
|
-
return {
|
|
47
|
-
branch,
|
|
48
|
-
files: mergeFileChanges(unstaged, staged, untracked),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function runGit(cwd, args) {
|
|
52
|
-
try {
|
|
53
|
-
return execFileSync("git", args, {
|
|
54
|
-
cwd,
|
|
55
|
-
encoding: "utf8",
|
|
56
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
57
|
-
timeout: 750,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return "";
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function parseGitCount(value) {
|
|
65
|
-
if (value === "-")
|
|
66
|
-
return 0;
|
|
67
|
-
const count = Number.parseInt(value, 10);
|
|
68
|
-
return Number.isFinite(count) ? count : 0;
|
|
69
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extract user-visible signals from a partial tool-call JSON buffer.
|
|
3
|
-
*
|
|
4
|
-
* We deliberately do NOT attempt a full partial-JSON parse. The goal is just
|
|
5
|
-
* to surface the file path (so the tool header can render) and a coarse
|
|
6
|
-
* "how much has been streamed" hint, both available the moment the model has
|
|
7
|
-
* emitted enough text for them to be unambiguous.
|
|
8
|
-
*/
|
|
9
|
-
export interface StreamingArgsHint {
|
|
10
|
-
/** First fully-closed string value found for a known path field. */
|
|
11
|
-
path?: string;
|
|
12
|
-
/** Count of escaped newline sequences (`\n`) seen so far — proxy for written line count. */
|
|
13
|
-
newlineCount: number;
|
|
14
|
-
}
|
|
15
|
-
export declare function extractStreamingArgsHint(raw: string): StreamingArgsHint;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extract user-visible signals from a partial tool-call JSON buffer.
|
|
3
|
-
*
|
|
4
|
-
* We deliberately do NOT attempt a full partial-JSON parse. The goal is just
|
|
5
|
-
* to surface the file path (so the tool header can render) and a coarse
|
|
6
|
-
* "how much has been streamed" hint, both available the moment the model has
|
|
7
|
-
* emitted enough text for them to be unambiguous.
|
|
8
|
-
*/
|
|
9
|
-
const PATH_FIELDS = ["path", "file_path", "filePath"];
|
|
10
|
-
export function extractStreamingArgsHint(raw) {
|
|
11
|
-
let path;
|
|
12
|
-
for (const field of PATH_FIELDS) {
|
|
13
|
-
const re = new RegExp(`"${field}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`);
|
|
14
|
-
const m = raw.match(re);
|
|
15
|
-
if (m) {
|
|
16
|
-
try {
|
|
17
|
-
path = JSON.parse(`"${m[1]}"`);
|
|
18
|
-
break;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
// The matched substring ended mid-escape; ignore and wait for more.
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const newlines = raw.match(/\\n/g);
|
|
26
|
-
return {
|
|
27
|
-
path,
|
|
28
|
-
newlineCount: newlines ? newlines.length : 0,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { fg, StyledText } from "@opentui/core";
|
|
2
|
-
export const fallbackToolRenderer = {
|
|
3
|
-
canRender: (_tool) => true,
|
|
4
|
-
render: renderFallbackTool,
|
|
5
|
-
};
|
|
6
|
-
function renderFallbackTool({ ctx, tool, syntaxStyle, width, helpers }) {
|
|
7
|
-
const { theme } = helpers;
|
|
8
|
-
const icon = helpers.toolStateIcon(tool);
|
|
9
|
-
const color = helpers.toolColor(tool);
|
|
10
|
-
const header = helpers.toolHeader(tool);
|
|
11
|
-
const diff = helpers.extractToolDiff(tool);
|
|
12
|
-
const isError = tool.isError === true || tool.status === "error";
|
|
13
|
-
if (diff && !isError && tool.name === "edit") {
|
|
14
|
-
return helpers.createBox(ctx, {
|
|
15
|
-
paddingLeft: 3,
|
|
16
|
-
marginTop: 1,
|
|
17
|
-
flexDirection: "column",
|
|
18
|
-
flexShrink: 0,
|
|
19
|
-
}, [
|
|
20
|
-
helpers.createText(ctx, new StyledText([
|
|
21
|
-
fg(color)(`${icon} ${helpers.displayToolName(tool.name)}`),
|
|
22
|
-
fg(theme.toolText)(header ? ` ${header}` : ""),
|
|
23
|
-
])),
|
|
24
|
-
helpers.createBox(ctx, {
|
|
25
|
-
paddingLeft: 1,
|
|
26
|
-
marginTop: 1,
|
|
27
|
-
border: ["left"],
|
|
28
|
-
borderColor: theme.borderSubtle,
|
|
29
|
-
flexDirection: "column",
|
|
30
|
-
flexShrink: 0,
|
|
31
|
-
}, [helpers.createDiffRenderable(ctx, diff, helpers.toolPath(tool), syntaxStyle, width)]),
|
|
32
|
-
]);
|
|
33
|
-
}
|
|
34
|
-
const chunks = [
|
|
35
|
-
fg(color)(`${icon} ${helpers.displayToolName(tool.name)}`),
|
|
36
|
-
];
|
|
37
|
-
if (header)
|
|
38
|
-
chunks.push(fg(theme.toolText)(` ${header}`));
|
|
39
|
-
const showTail = !!tool.result || tool.status === "running" || tool.status === "pending" || tool.streamingArgs === true;
|
|
40
|
-
if (showTail) {
|
|
41
|
-
const summary = helpers.summarizeToolResult(tool);
|
|
42
|
-
if (summary) {
|
|
43
|
-
chunks.push(fg(theme.text)("\n"));
|
|
44
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
45
|
-
chunks.push(fg(isError ? theme.toolError : theme.textMuted)(summary));
|
|
46
|
-
}
|
|
47
|
-
const preview = helpers.toolPreview(tool);
|
|
48
|
-
if (preview) {
|
|
49
|
-
for (const line of preview.lines) {
|
|
50
|
-
chunks.push(fg(theme.text)("\n"));
|
|
51
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
52
|
-
chunks.push(fg(theme.toolText)(line));
|
|
53
|
-
}
|
|
54
|
-
if (preview.omitted > 0) {
|
|
55
|
-
chunks.push(fg(theme.text)("\n"));
|
|
56
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
57
|
-
chunks.push(fg(theme.textMuted)(`+ ${preview.omitted} more`));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const containerProps = {
|
|
62
|
-
paddingLeft: isError ? 1 : 3,
|
|
63
|
-
marginTop: 1,
|
|
64
|
-
flexDirection: "column",
|
|
65
|
-
flexShrink: 0,
|
|
66
|
-
};
|
|
67
|
-
if (isError) {
|
|
68
|
-
containerProps.border = ["left"];
|
|
69
|
-
containerProps.borderColor = theme.toolError;
|
|
70
|
-
containerProps.paddingLeft = 2;
|
|
71
|
-
}
|
|
72
|
-
return helpers.createBox(ctx, containerProps, [
|
|
73
|
-
helpers.createText(ctx, new StyledText(chunks), { wrapMode: "word" }),
|
|
74
|
-
]);
|
|
75
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { fallbackToolRenderer } from "./fallback.js";
|
|
2
|
-
import { subagentToolRenderer } from "./subagent.js";
|
|
3
|
-
import { writeToolRenderer } from "./write.js";
|
|
4
|
-
const TOOL_RENDERERS = [
|
|
5
|
-
writeToolRenderer,
|
|
6
|
-
subagentToolRenderer,
|
|
7
|
-
fallbackToolRenderer,
|
|
8
|
-
];
|
|
9
|
-
export function findToolRenderer(tool) {
|
|
10
|
-
return TOOL_RENDERERS.find((renderer) => renderer.canRender(tool));
|
|
11
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { fg, StyledText } from "@opentui/core";
|
|
2
|
-
export const subagentToolRenderer = {
|
|
3
|
-
canRender: (tool) => tool.name === "subagent" || tool.metadata?.kind === "subagent",
|
|
4
|
-
render: renderSubagentTool,
|
|
5
|
-
};
|
|
6
|
-
function renderSubagentTool({ ctx, tool, width, helpers }) {
|
|
7
|
-
const { theme } = helpers;
|
|
8
|
-
const metadata = tool.metadata ?? {};
|
|
9
|
-
const subagents = subagentsFrom(tool);
|
|
10
|
-
const mode = typeof metadata.mode === "string" ? metadata.mode : (subagents.length > 1 ? "parallel" : "single");
|
|
11
|
-
const completed = subagents.filter((item) => item.status === "completed").length;
|
|
12
|
-
const color = tool.isError ? theme.toolError : tool.status === "running" ? theme.toolPending : theme.toolSuccess;
|
|
13
|
-
const headerChunks = [
|
|
14
|
-
fg(color)(`> ${helpers.displayToolName(tool.name)}`),
|
|
15
|
-
fg(theme.toolText)(` ${mode}`),
|
|
16
|
-
];
|
|
17
|
-
if (subagents.length > 0) {
|
|
18
|
-
headerChunks.push(fg(theme.textMuted)(` ${completed}/${subagents.length}`));
|
|
19
|
-
}
|
|
20
|
-
const children = [
|
|
21
|
-
helpers.createText(ctx, new StyledText(headerChunks), { wrapMode: "word" }),
|
|
22
|
-
];
|
|
23
|
-
const rows = subagents.map((subagent) => renderSubagentRow(ctx, subagent, width, helpers));
|
|
24
|
-
if (rows.length > 0) {
|
|
25
|
-
children.push(helpers.createBox(ctx, {
|
|
26
|
-
paddingLeft: 1,
|
|
27
|
-
marginTop: 0,
|
|
28
|
-
border: ["left"],
|
|
29
|
-
borderColor: theme.borderSubtle,
|
|
30
|
-
flexDirection: "column",
|
|
31
|
-
flexShrink: 0,
|
|
32
|
-
}, rows));
|
|
33
|
-
}
|
|
34
|
-
else if (tool.result) {
|
|
35
|
-
children.push(helpers.createText(ctx, helpers.summarizeToolResult(tool), {
|
|
36
|
-
fg: tool.isError ? theme.toolError : theme.textMuted,
|
|
37
|
-
wrapMode: "word",
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
return helpers.createBox(ctx, {
|
|
41
|
-
paddingLeft: 3,
|
|
42
|
-
marginTop: 1,
|
|
43
|
-
flexDirection: "column",
|
|
44
|
-
flexShrink: 0,
|
|
45
|
-
}, children);
|
|
46
|
-
}
|
|
47
|
-
function renderSubagentRow(ctx, subagent, width, helpers) {
|
|
48
|
-
const { theme } = helpers;
|
|
49
|
-
const status = subagent.status ?? "running";
|
|
50
|
-
const color = status === "completed"
|
|
51
|
-
? theme.toolSuccess
|
|
52
|
-
: status === "running" || status === "queued"
|
|
53
|
-
? theme.toolPending
|
|
54
|
-
: theme.toolError;
|
|
55
|
-
const source = subagent.profileSource ? ` [${subagent.profileSource}]` : "";
|
|
56
|
-
const usage = subagent.usage?.totalTokens ? ` ${subagent.usage.totalTokens} tokens` : "";
|
|
57
|
-
const summary = firstUsefulLine(subagent.error || subagent.summary || lastToolNote(subagent.toolNotes));
|
|
58
|
-
const task = firstUsefulLine(subagent.task);
|
|
59
|
-
const maxLine = Math.max(24, width - 12);
|
|
60
|
-
const lines = [
|
|
61
|
-
helpers.createText(ctx, new StyledText([
|
|
62
|
-
fg(color)(`${statusIcon(status)} ${subagentLabel(subagent)}`),
|
|
63
|
-
fg(theme.textMuted)(`${source} ${status}${usage}`),
|
|
64
|
-
]), { wrapMode: "word" }),
|
|
65
|
-
];
|
|
66
|
-
if (task) {
|
|
67
|
-
lines.push(helpers.createText(ctx, shorten(`task: ${task}`, maxLine), {
|
|
68
|
-
fg: theme.textMuted,
|
|
69
|
-
wrapMode: "word",
|
|
70
|
-
}));
|
|
71
|
-
}
|
|
72
|
-
if (summary) {
|
|
73
|
-
lines.push(helpers.createText(ctx, shorten(summary, maxLine), {
|
|
74
|
-
fg: subagent.error ? theme.toolError : theme.toolText,
|
|
75
|
-
wrapMode: "word",
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
return helpers.createBox(ctx, {
|
|
79
|
-
flexDirection: "column",
|
|
80
|
-
flexShrink: 0,
|
|
81
|
-
}, lines);
|
|
82
|
-
}
|
|
83
|
-
function subagentLabel(subagent) {
|
|
84
|
-
if (subagent.nickname && subagent.agentName) {
|
|
85
|
-
return `${subagent.nickname} (${subagent.agentName})`;
|
|
86
|
-
}
|
|
87
|
-
return subagent.nickname ?? subagent.agentName ?? "subagent";
|
|
88
|
-
}
|
|
89
|
-
function subagentsFrom(tool) {
|
|
90
|
-
const raw = tool.metadata?.subagents;
|
|
91
|
-
if (!Array.isArray(raw))
|
|
92
|
-
return [];
|
|
93
|
-
return raw.filter((item) => typeof item === "object" && item !== null);
|
|
94
|
-
}
|
|
95
|
-
function statusIcon(status) {
|
|
96
|
-
if (status === "completed")
|
|
97
|
-
return "+";
|
|
98
|
-
if (status === "running")
|
|
99
|
-
return ">";
|
|
100
|
-
if (status === "queued")
|
|
101
|
-
return ".";
|
|
102
|
-
return "!";
|
|
103
|
-
}
|
|
104
|
-
function lastToolNote(notes) {
|
|
105
|
-
return notes?.filter(Boolean).at(-1);
|
|
106
|
-
}
|
|
107
|
-
function firstUsefulLine(value) {
|
|
108
|
-
return value?.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
|
109
|
-
}
|
|
110
|
-
function shorten(value, max) {
|
|
111
|
-
if (value.length <= max)
|
|
112
|
-
return value;
|
|
113
|
-
return `${value.slice(0, Math.max(0, max - 3))}...`;
|
|
114
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { RenderContext, Renderable, StyledText, SyntaxStyle, TextRenderable } from "@opentui/core";
|
|
2
|
-
import type { DisplayToolCall } from "../display-history.js";
|
|
3
|
-
export interface ToolRenderer {
|
|
4
|
-
canRender(tool: DisplayToolCall): boolean;
|
|
5
|
-
expansionKey?(messageKey: string, tool: DisplayToolCall): string | undefined;
|
|
6
|
-
signature?(messageKey: string, tool: DisplayToolCall, expandedWrites: Set<string>): string;
|
|
7
|
-
render(context: ToolRenderContext): Renderable;
|
|
8
|
-
}
|
|
9
|
-
export interface ToolRenderContext {
|
|
10
|
-
ctx: RenderContext;
|
|
11
|
-
tool: DisplayToolCall;
|
|
12
|
-
syntaxStyle: SyntaxStyle;
|
|
13
|
-
width: number;
|
|
14
|
-
writeExpanded: boolean;
|
|
15
|
-
onToggleWrite?: () => void;
|
|
16
|
-
helpers: ToolRenderHelpers;
|
|
17
|
-
}
|
|
18
|
-
export interface ToolRenderHelpers {
|
|
19
|
-
theme: Record<string, string>;
|
|
20
|
-
createBox: (ctx: RenderContext, options: Record<string, unknown>, children?: Array<Renderable | null | undefined>) => Renderable;
|
|
21
|
-
createText: (ctx: RenderContext, content: string | StyledText, options?: Record<string, unknown>) => TextRenderable;
|
|
22
|
-
createCodeBlockRenderable: (ctx: RenderContext, content: string, filePath: string | undefined, syntaxStyle: SyntaxStyle) => Renderable;
|
|
23
|
-
createDiffRenderable: (ctx: RenderContext, diff: string, filePath: string | undefined, syntaxStyle: SyntaxStyle, width?: number) => Renderable;
|
|
24
|
-
toolColor: (tool: DisplayToolCall) => string;
|
|
25
|
-
displayToolName: (name: string) => string;
|
|
26
|
-
toolHeader: (tool: DisplayToolCall) => string;
|
|
27
|
-
toolPath: (tool: DisplayToolCall) => string | undefined;
|
|
28
|
-
extractToolDiff: (tool: DisplayToolCall) => string | undefined;
|
|
29
|
-
summarizeToolResult: (tool: DisplayToolCall) => string;
|
|
30
|
-
isToolFinished: (tool: DisplayToolCall) => boolean;
|
|
31
|
-
toolPreview: (tool: DisplayToolCall) => {
|
|
32
|
-
lines: string[];
|
|
33
|
-
omitted: number;
|
|
34
|
-
} | undefined;
|
|
35
|
-
toolStateIcon: (tool: DisplayToolCall) => string;
|
|
36
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { DisplayToolCall } from "../display-history.js";
|
|
2
|
-
export declare const WRITE_PREVIEW_CHAR_LIMIT = 5000;
|
|
3
|
-
export declare function isWritePreviewTool(tool: DisplayToolCall): tool is DisplayToolCall & {
|
|
4
|
-
args: {
|
|
5
|
-
content?: string;
|
|
6
|
-
};
|
|
7
|
-
};
|
|
8
|
-
export declare function formatWritePreview(content: string, expanded: boolean): {
|
|
9
|
-
content: string;
|
|
10
|
-
omittedLines: number;
|
|
11
|
-
omittedChars: number;
|
|
12
|
-
};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const WRITE_PREVIEW_LINE_LIMIT = 10;
|
|
2
|
-
export const WRITE_PREVIEW_CHAR_LIMIT = 5000;
|
|
3
|
-
export function isWritePreviewTool(tool) {
|
|
4
|
-
if (tool.isError)
|
|
5
|
-
return false;
|
|
6
|
-
if (tool.name !== "write")
|
|
7
|
-
return false;
|
|
8
|
-
if (typeof tool.args?.content === "string")
|
|
9
|
-
return true;
|
|
10
|
-
// While the model is still streaming the JSON args, content may not be
|
|
11
|
-
// populated yet — keep ownership so the header renders progressively.
|
|
12
|
-
return tool.streamingArgs === true;
|
|
13
|
-
}
|
|
14
|
-
export function formatWritePreview(content, expanded) {
|
|
15
|
-
const lines = content.split(/\r?\n/);
|
|
16
|
-
if (expanded) {
|
|
17
|
-
return { content, omittedLines: 0, omittedChars: 0 };
|
|
18
|
-
}
|
|
19
|
-
let previewContent = lines.slice(0, WRITE_PREVIEW_LINE_LIMIT).join("\n");
|
|
20
|
-
let omittedLines = Math.max(0, lines.length - WRITE_PREVIEW_LINE_LIMIT);
|
|
21
|
-
if (previewContent.length > WRITE_PREVIEW_CHAR_LIMIT) {
|
|
22
|
-
previewContent = previewContent.slice(0, WRITE_PREVIEW_CHAR_LIMIT);
|
|
23
|
-
omittedLines = Math.max(omittedLines, lines.length - previewContent.split(/\r?\n/).length);
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
content: previewContent,
|
|
27
|
-
omittedLines,
|
|
28
|
-
omittedChars: Math.max(0, content.length - previewContent.length),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { DisplayMessage, DisplayToolCall } from "../display-history.js";
|
|
2
|
-
import type { ToolRenderer } from "./types.js";
|
|
3
|
-
export declare const writeToolRenderer: ToolRenderer;
|
|
4
|
-
export declare function writeToolKey(messageKey: string, tool: DisplayToolCall): string;
|
|
5
|
-
export declare function writeToolExpansionDigest(message: DisplayMessage, messageKey: string, expandedWrites: Set<string>): string;
|
|
6
|
-
export declare function writeToolExpansionSignature(messageKey: string, tool: DisplayToolCall, expandedWrites: Set<string>): string;
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { fg, StyledText } from "@opentui/core";
|
|
2
|
-
import { hashString } from "../render-signature.js";
|
|
3
|
-
import { formatWritePreview, isWritePreviewTool, WRITE_PREVIEW_CHAR_LIMIT } from "./write-preview.js";
|
|
4
|
-
export const writeToolRenderer = {
|
|
5
|
-
canRender: isWritePreviewTool,
|
|
6
|
-
expansionKey: writeToolKey,
|
|
7
|
-
signature: writeToolExpansionSignature,
|
|
8
|
-
render: renderWriteTool,
|
|
9
|
-
};
|
|
10
|
-
export function writeToolKey(messageKey, tool) {
|
|
11
|
-
return `${messageKey}:write:${tool.id}`;
|
|
12
|
-
}
|
|
13
|
-
export function writeToolExpansionDigest(message, messageKey, expandedWrites) {
|
|
14
|
-
return (message.toolCalls ?? [])
|
|
15
|
-
.filter((tool) => isWritePreviewTool(tool))
|
|
16
|
-
.map((tool) => writeToolExpansionSignature(messageKey, tool, expandedWrites))
|
|
17
|
-
.join("|");
|
|
18
|
-
}
|
|
19
|
-
export function writeToolExpansionSignature(messageKey, tool, expandedWrites) {
|
|
20
|
-
if (!isWritePreviewTool(tool))
|
|
21
|
-
return "";
|
|
22
|
-
const content = typeof tool.args.content === "string" ? tool.args.content : "";
|
|
23
|
-
return [
|
|
24
|
-
tool.id,
|
|
25
|
-
expandedWrites.has(writeToolKey(messageKey, tool)) ? "expanded" : "collapsed",
|
|
26
|
-
content.length,
|
|
27
|
-
content.split(/\r?\n/).length,
|
|
28
|
-
hashString(content.slice(0, WRITE_PREVIEW_CHAR_LIMIT)),
|
|
29
|
-
hashString(content.slice(-WRITE_PREVIEW_CHAR_LIMIT)),
|
|
30
|
-
].join(":");
|
|
31
|
-
}
|
|
32
|
-
function renderWriteTool({ ctx, tool, syntaxStyle, writeExpanded, onToggleWrite, helpers }) {
|
|
33
|
-
const { theme } = helpers;
|
|
34
|
-
const color = helpers.toolColor(tool);
|
|
35
|
-
const icon = "●";
|
|
36
|
-
const header = helpers.toolHeader(tool);
|
|
37
|
-
const hasContent = typeof tool.args.content === "string";
|
|
38
|
-
const contentStr = hasContent ? String(tool.args.content) : "";
|
|
39
|
-
const preview = hasContent ? formatWritePreview(contentStr, writeExpanded) : null;
|
|
40
|
-
const writeLineCount = hasContent
|
|
41
|
-
? contentStr.split(/\r?\n/).length
|
|
42
|
-
: (tool.streamingNewlineCount ?? 0) + 1;
|
|
43
|
-
const summary = tool.result
|
|
44
|
-
? helpers.summarizeToolResult(tool)
|
|
45
|
-
: `${helpers.isToolFinished(tool) ? "Prepared" : "Writing"} ${writeLineCount} line${writeLineCount === 1 ? "" : "s"} to ${helpers.toolPath(tool) ?? "file"}`;
|
|
46
|
-
const hint = preview && preview.omittedLines > 0
|
|
47
|
-
? `... +${preview.omittedLines} lines (${writeExpanded ? "ctrl+o to collapse" : "ctrl+o to expand"})`
|
|
48
|
-
: preview && preview.omittedChars > 0
|
|
49
|
-
? `... +${preview.omittedChars} chars (${writeExpanded ? "ctrl+o to collapse" : "ctrl+o to expand"})`
|
|
50
|
-
: preview && writeExpanded
|
|
51
|
-
? "(ctrl+o to collapse)"
|
|
52
|
-
: "";
|
|
53
|
-
return helpers.createBox(ctx, {
|
|
54
|
-
paddingLeft: 3,
|
|
55
|
-
marginTop: 1,
|
|
56
|
-
flexDirection: "column",
|
|
57
|
-
flexShrink: 0,
|
|
58
|
-
}, [
|
|
59
|
-
helpers.createText(ctx, new StyledText([
|
|
60
|
-
fg(color)(`${icon} ${helpers.displayToolName(tool.name)}`),
|
|
61
|
-
fg(theme.toolText)(header ? ` ${header}` : ""),
|
|
62
|
-
]), {
|
|
63
|
-
onMouseUp: onToggleWrite,
|
|
64
|
-
}),
|
|
65
|
-
helpers.createBox(ctx, {
|
|
66
|
-
paddingLeft: 1,
|
|
67
|
-
marginTop: 0,
|
|
68
|
-
border: ["left"],
|
|
69
|
-
borderColor: theme.borderSubtle,
|
|
70
|
-
flexDirection: "column",
|
|
71
|
-
flexShrink: 0,
|
|
72
|
-
}, [
|
|
73
|
-
helpers.createText(ctx, `└ ${summary}`, {
|
|
74
|
-
fg: tool.isError ? theme.toolError : theme.textMuted,
|
|
75
|
-
onMouseUp: onToggleWrite,
|
|
76
|
-
}),
|
|
77
|
-
preview
|
|
78
|
-
? helpers.createCodeBlockRenderable(ctx, preview.content, helpers.toolPath(tool), syntaxStyle)
|
|
79
|
-
: null,
|
|
80
|
-
hint
|
|
81
|
-
? helpers.createText(ctx, hint, {
|
|
82
|
-
fg: theme.textMuted,
|
|
83
|
-
onMouseUp: onToggleWrite,
|
|
84
|
-
})
|
|
85
|
-
: null,
|
|
86
|
-
]),
|
|
87
|
-
]);
|
|
88
|
-
}
|
|
File without changes
|