@bubblebrain-ai/bubble 0.0.3 → 0.0.5

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.
Files changed (97) hide show
  1. package/README.md +8 -3
  2. package/dist/agent/budget-ledger.d.ts +20 -0
  3. package/dist/agent/budget-ledger.js +51 -0
  4. package/dist/agent/execution-governor.d.ts +14 -0
  5. package/dist/agent/execution-governor.js +172 -14
  6. package/dist/agent/profiles.d.ts +59 -0
  7. package/dist/agent/profiles.js +460 -0
  8. package/dist/agent/subagent-control.d.ts +52 -0
  9. package/dist/agent/subagent-control.js +38 -0
  10. package/dist/agent/task-classifier.d.ts +1 -1
  11. package/dist/agent/task-classifier.js +60 -0
  12. package/dist/agent/tool-intent.d.ts +14 -0
  13. package/dist/agent/tool-intent.js +125 -1
  14. package/dist/agent.d.ts +60 -1
  15. package/dist/agent.js +606 -53
  16. package/dist/bin.d.ts +2 -0
  17. package/dist/bin.js +45 -0
  18. package/dist/context/budget.js +1 -0
  19. package/dist/context/compact-llm.js +7 -6
  20. package/dist/context/compact.js +6 -6
  21. package/dist/context/projector.d.ts +3 -3
  22. package/dist/context/projector.js +32 -18
  23. package/dist/context/prune.d.ts +2 -2
  24. package/dist/context/prune.js +1 -4
  25. package/dist/main.d.ts +1 -1
  26. package/dist/main.js +13 -6
  27. package/dist/mcp/manager.js +1 -0
  28. package/dist/orchestrator/default-hooks.js +92 -1
  29. package/dist/orchestrator/hooks.d.ts +10 -0
  30. package/dist/prompt/compose.d.ts +1 -0
  31. package/dist/prompt/compose.js +20 -1
  32. package/dist/prompt/environment.js +21 -2
  33. package/dist/prompt/provider-prompts/deepseek.d.ts +1 -0
  34. package/dist/prompt/provider-prompts/deepseek.js +8 -0
  35. package/dist/prompt/provider-prompts/glm.d.ts +1 -0
  36. package/dist/prompt/provider-prompts/glm.js +7 -0
  37. package/dist/prompt/provider-prompts/kimi.d.ts +1 -0
  38. package/dist/prompt/provider-prompts/kimi.js +7 -0
  39. package/dist/prompt/reminders.d.ts +5 -1
  40. package/dist/prompt/reminders.js +51 -6
  41. package/dist/prompt/runtime.d.ts +1 -1
  42. package/dist/prompt/runtime.js +16 -3
  43. package/dist/prompt/task-reminders.d.ts +2 -0
  44. package/dist/prompt/task-reminders.js +56 -0
  45. package/dist/provider-artifacts.d.ts +7 -0
  46. package/dist/provider-artifacts.js +60 -0
  47. package/dist/provider.d.ts +6 -7
  48. package/dist/provider.js +77 -15
  49. package/dist/session-log.js +3 -1
  50. package/dist/slash-commands/commands.js +2 -3
  51. package/dist/system-prompt.d.ts +2 -0
  52. package/dist/tools/agent-lifecycle.d.ts +6 -0
  53. package/dist/tools/agent-lifecycle.js +355 -0
  54. package/dist/tools/bash.js +12 -7
  55. package/dist/tools/edit-apply.d.ts +25 -0
  56. package/dist/tools/edit-apply.js +197 -0
  57. package/dist/tools/edit.js +64 -52
  58. package/dist/tools/exit-plan-mode.js +3 -1
  59. package/dist/tools/file-mutation-queue.d.ts +1 -0
  60. package/dist/tools/file-mutation-queue.js +32 -0
  61. package/dist/tools/glob.js +1 -0
  62. package/dist/tools/grep.js +1 -0
  63. package/dist/tools/index.d.ts +1 -1
  64. package/dist/tools/index.js +3 -3
  65. package/dist/tools/lsp.js +2 -0
  66. package/dist/tools/memory.js +2 -0
  67. package/dist/tools/question.js +2 -0
  68. package/dist/tools/read.js +1 -0
  69. package/dist/tools/skill.js +1 -0
  70. package/dist/tools/task.js +1 -0
  71. package/dist/tools/todo.js +1 -0
  72. package/dist/tools/tool-search.js +2 -1
  73. package/dist/tools/web-fetch.js +1 -0
  74. package/dist/tools/web-search.js +1 -0
  75. package/dist/tools/write.js +10 -1
  76. package/dist/tui/display-history.d.ts +8 -1
  77. package/dist/tui/image-paste.d.ts +41 -0
  78. package/dist/tui/image-paste.js +217 -0
  79. package/dist/tui/markdown-inline.d.ts +22 -0
  80. package/dist/tui/markdown-inline.js +68 -0
  81. package/dist/tui/render-signature.d.ts +1 -0
  82. package/dist/tui/render-signature.js +7 -0
  83. package/dist/tui/run.js +814 -269
  84. package/dist/tui/tool-renderers/fallback.d.ts +2 -0
  85. package/dist/tui/tool-renderers/fallback.js +75 -0
  86. package/dist/tui/tool-renderers/registry.d.ts +3 -0
  87. package/dist/tui/tool-renderers/registry.js +11 -0
  88. package/dist/tui/tool-renderers/subagent.d.ts +2 -0
  89. package/dist/tui/tool-renderers/subagent.js +114 -0
  90. package/dist/tui/tool-renderers/types.d.ts +36 -0
  91. package/dist/tui/tool-renderers/types.js +1 -0
  92. package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
  93. package/dist/tui/tool-renderers/write-preview.js +22 -0
  94. package/dist/tui/tool-renderers/write.d.ts +6 -0
  95. package/dist/tui/tool-renderers/write.js +82 -0
  96. package/dist/types.d.ts +90 -10
  97. package/package.json +3 -3
@@ -0,0 +1,2 @@
1
+ import type { ToolRenderer } from "./types.js";
2
+ export declare const fallbackToolRenderer: ToolRenderer;
@@ -0,0 +1,75 @@
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
+ }
@@ -0,0 +1,3 @@
1
+ import type { DisplayToolCall } from "../display-history.js";
2
+ import type { ToolRenderer } from "./types.js";
3
+ export declare function findToolRenderer(tool: DisplayToolCall): ToolRenderer | undefined;
@@ -0,0 +1,11 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolRenderer } from "./types.js";
2
+ export declare const subagentToolRenderer: ToolRenderer;
@@ -0,0 +1,114 @@
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
+ }
@@ -0,0 +1,36 @@
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
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
+ };
@@ -0,0 +1,22 @@
1
+ const WRITE_PREVIEW_LINE_LIMIT = 10;
2
+ export const WRITE_PREVIEW_CHAR_LIMIT = 5000;
3
+ export function isWritePreviewTool(tool) {
4
+ return !tool.isError && tool.name === "write" && typeof tool.args?.content === "string";
5
+ }
6
+ export function formatWritePreview(content, expanded) {
7
+ const lines = content.split(/\r?\n/);
8
+ if (expanded) {
9
+ return { content, omittedLines: 0, omittedChars: 0 };
10
+ }
11
+ let previewContent = lines.slice(0, WRITE_PREVIEW_LINE_LIMIT).join("\n");
12
+ let omittedLines = Math.max(0, lines.length - WRITE_PREVIEW_LINE_LIMIT);
13
+ if (previewContent.length > WRITE_PREVIEW_CHAR_LIMIT) {
14
+ previewContent = previewContent.slice(0, WRITE_PREVIEW_CHAR_LIMIT);
15
+ omittedLines = Math.max(omittedLines, lines.length - previewContent.split(/\r?\n/).length);
16
+ }
17
+ return {
18
+ content: previewContent,
19
+ omittedLines,
20
+ omittedChars: Math.max(0, content.length - previewContent.length),
21
+ };
22
+ }
@@ -0,0 +1,6 @@
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;
@@ -0,0 +1,82 @@
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 = 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 preview = formatWritePreview(String(tool.args.content), writeExpanded);
38
+ const writeLineCount = String(tool.args.content).split(/\r?\n/).length;
39
+ const summary = tool.result
40
+ ? helpers.summarizeToolResult(tool)
41
+ : `${helpers.isToolFinished(tool) ? "Prepared" : "Writing"} ${writeLineCount} line${writeLineCount === 1 ? "" : "s"} to ${helpers.toolPath(tool) ?? "file"}`;
42
+ const hint = preview.omittedLines > 0
43
+ ? `... +${preview.omittedLines} lines (${writeExpanded ? "ctrl+o to collapse" : "ctrl+o to expand"})`
44
+ : preview.omittedChars > 0
45
+ ? `... +${preview.omittedChars} chars (${writeExpanded ? "ctrl+o to collapse" : "ctrl+o to expand"})`
46
+ : writeExpanded
47
+ ? "(ctrl+o to collapse)"
48
+ : "";
49
+ return helpers.createBox(ctx, {
50
+ paddingLeft: 3,
51
+ marginTop: 1,
52
+ flexDirection: "column",
53
+ flexShrink: 0,
54
+ }, [
55
+ helpers.createText(ctx, new StyledText([
56
+ fg(color)(`${icon} ${helpers.displayToolName(tool.name)}`),
57
+ fg(theme.toolText)(header ? ` ${header}` : ""),
58
+ ]), {
59
+ onMouseUp: onToggleWrite,
60
+ }),
61
+ helpers.createBox(ctx, {
62
+ paddingLeft: 1,
63
+ marginTop: 0,
64
+ border: ["left"],
65
+ borderColor: theme.borderSubtle,
66
+ flexDirection: "column",
67
+ flexShrink: 0,
68
+ }, [
69
+ helpers.createText(ctx, `└ ${summary}`, {
70
+ fg: tool.isError ? theme.toolError : theme.textMuted,
71
+ onMouseUp: onToggleWrite,
72
+ }),
73
+ helpers.createCodeBlockRenderable(ctx, preview.content, helpers.toolPath(tool), syntaxStyle),
74
+ hint
75
+ ? helpers.createText(ctx, hint, {
76
+ fg: theme.textMuted,
77
+ onMouseUp: onToggleWrite,
78
+ })
79
+ : null,
80
+ ]),
81
+ ]);
82
+ }
package/dist/types.d.ts CHANGED
@@ -17,11 +17,6 @@ export type ReasoningEffort = ThinkingLevel;
17
17
  export interface UserMessage {
18
18
  role: "user";
19
19
  content: string | ContentPart[];
20
- /**
21
- * Marks this message as harness-emitted metadata (e.g. a <system-reminder>),
22
- * not actual user input. Renderers may hide these; compaction should generally preserve them.
23
- */
24
- isMeta?: boolean;
25
20
  }
26
21
  export interface AssistantMessage {
27
22
  role: "assistant";
@@ -40,7 +35,19 @@ export interface SystemMessage {
40
35
  role: "system";
41
36
  content: string;
42
37
  }
43
- export type Message = UserMessage | AssistantMessage | ToolMessage | SystemMessage;
38
+ export type MetaMessageKind = "system-reminder" | "runtime-context";
39
+ export interface MetaMessage {
40
+ role: "meta";
41
+ kind: MetaMessageKind;
42
+ content: string;
43
+ /**
44
+ * Runtime metadata is hidden from transcript and session persistence. When
45
+ * true or omitted, the projector may convert it into model context.
46
+ */
47
+ includeInLlm?: boolean;
48
+ }
49
+ export type ProviderMessage = UserMessage | AssistantMessage | ToolMessage | SystemMessage;
50
+ export type Message = ProviderMessage | MetaMessage;
44
51
  export interface ToolParameter {
45
52
  type?: string;
46
53
  description?: string;
@@ -71,7 +78,7 @@ export interface ParsedToolCall extends ToolCall {
71
78
  }
72
79
  export type ToolResultStatus = "success" | "no_match" | "partial" | "timeout" | "blocked" | "command_error";
73
80
  export interface ToolResultMetadata {
74
- kind?: "search" | "read" | "write" | "edit" | "shell" | "web" | "security" | "lsp" | "question";
81
+ kind?: "search" | "read" | "write" | "edit" | "shell" | "web" | "security" | "lsp" | "question" | "subagent";
75
82
  path?: string;
76
83
  pattern?: string;
77
84
  matches?: number;
@@ -88,6 +95,22 @@ export interface ToolResult {
88
95
  status?: ToolResultStatus;
89
96
  metadata?: ToolResultMetadata;
90
97
  }
98
+ export type ToolEffect = "read" | "write_patch" | "write_direct" | "unknown";
99
+ export interface ToolUpdate {
100
+ type: "subagent_update";
101
+ parentToolCallId: string;
102
+ runId: string;
103
+ subAgentId: string;
104
+ agentName: string;
105
+ nickname?: string;
106
+ status: "queued" | "running" | "completed" | "failed" | "blocked" | "cancelled";
107
+ childEvent?: AgentEvent;
108
+ summaryDelta?: string;
109
+ toolName?: string;
110
+ toolCallId?: string;
111
+ message?: string;
112
+ metadata?: ToolResultMetadata;
113
+ }
91
114
  export type ToolExecutor = (args: Record<string, any>, ctx: ToolContext) => Promise<ToolResult>;
92
115
  export interface ToolContext {
93
116
  cwd: string;
@@ -102,16 +125,52 @@ export interface ToolContext {
102
125
  subtaskType?: string;
103
126
  description?: string;
104
127
  }) => Promise<ToolResult>;
128
+ runSubAgent?: (input: string | ContentPart[], cwd: string, options: {
129
+ profile: import("./agent/profiles.js").AgentProfile;
130
+ runId: string;
131
+ subAgentId: string;
132
+ parentToolCallId: string;
133
+ approval?: "fail" | "disabled";
134
+ emitUpdate?: (update: ToolUpdate) => void;
135
+ description?: string;
136
+ abortSignal?: AbortSignal;
137
+ nickname?: string;
138
+ forkContext?: boolean;
139
+ }) => Promise<import("./agent/profiles.js").SubagentRunResult>;
140
+ spawnSubAgent?: (input: string | ContentPart[], cwd: string, options: {
141
+ profile: import("./agent/profiles.js").AgentProfile;
142
+ parentToolCallId: string;
143
+ approval?: "fail" | "disabled";
144
+ description?: string;
145
+ abortSignal?: AbortSignal;
146
+ forkContext?: boolean;
147
+ }) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot>;
148
+ waitSubAgents?: (options: {
149
+ agentIds?: string[];
150
+ timeoutMs?: number;
151
+ }) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot[]>;
152
+ sendSubAgentInput?: (agentId: string, input: string | ContentPart[], cwd: string, options?: {
153
+ interrupt?: boolean;
154
+ parentToolCallId?: string;
155
+ abortSignal?: AbortSignal;
156
+ }) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot>;
157
+ closeSubAgent?: (agentId: string) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot>;
158
+ listSubAgents?: () => import("./agent/subagent-control.js").SubagentThreadSnapshot[];
105
159
  };
160
+ emitUpdate?: (update: ToolUpdate) => void;
106
161
  }
107
162
  export interface ToolRegistryEntry extends ToolDefinition {
108
163
  execute: ToolExecutor;
109
164
  /** Whether this tool is allowed in plan mode. Defaults to false (treated as write-capable). */
110
165
  readOnly?: boolean;
166
+ /** Capability classification used by subagent profiles. Defaults to "unknown". */
167
+ effect?: ToolEffect;
168
+ /** True when the tool may call ApprovalController.request(...) for an interactive decision. */
169
+ requiresApproval?: boolean;
111
170
  /**
112
171
  * If true, this tool is omitted from the tool list sent to the model on each
113
172
  * turn until unlocked via `tool_search`. Only the tool's name appears in a
114
- * startup &lt;system-reminder&gt;. Used for MCP tools to keep them out of the
173
+ * startup runtime reminder. Used for MCP tools to keep them out of the
115
174
  * per-turn context cost when not in use.
116
175
  */
117
176
  deferred?: boolean;
@@ -154,6 +213,7 @@ export type StreamChunk = {
154
213
  arguments: string;
155
214
  isStart: boolean;
156
215
  isEnd: boolean;
216
+ argumentsFull?: string;
157
217
  } | {
158
218
  type: "usage";
159
219
  usage: TokenUsage;
@@ -169,14 +229,14 @@ export interface TokenUsage {
169
229
  totalTokens?: number;
170
230
  }
171
231
  export interface Provider {
172
- streamChat(messages: Message[], options: {
232
+ streamChat(messages: ProviderMessage[], options: {
173
233
  model: string;
174
234
  tools?: ToolDefinition[];
175
235
  temperature?: number;
176
236
  thinkingLevel?: ThinkingLevel;
177
237
  abortSignal?: AbortSignal;
178
238
  }): AsyncIterable<StreamChunk>;
179
- complete(messages: Message[], options?: {
239
+ complete(messages: ProviderMessage[], options?: {
180
240
  model?: string;
181
241
  temperature?: number;
182
242
  thinkingLevel?: ThinkingLevel;
@@ -191,11 +251,31 @@ export type AgentEvent = {
191
251
  } | {
192
252
  type: "reasoning_delta";
193
253
  content: string;
254
+ } | {
255
+ type: "tool_call_start";
256
+ id: string;
257
+ name: string;
258
+ } | {
259
+ type: "tool_call_delta";
260
+ id: string;
261
+ name: string;
262
+ argumentsDelta: string;
263
+ arguments: string;
264
+ } | {
265
+ type: "tool_call_end";
266
+ id: string;
267
+ name: string;
268
+ arguments: string;
194
269
  } | {
195
270
  type: "tool_start";
196
271
  id: string;
197
272
  name: string;
198
273
  args: Record<string, any>;
274
+ } | {
275
+ type: "tool_update";
276
+ id: string;
277
+ name: string;
278
+ update: ToolUpdate;
199
279
  } | {
200
280
  type: "tool_end";
201
281
  id: string;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=20.0.0"
8
8
  },
9
9
  "bin": {
10
- "bubble": "dist/main.js"
10
+ "bubble": "dist/bin.js"
11
11
  },
12
12
  "files": [
13
13
  "dist",
@@ -16,7 +16,7 @@
16
16
  "!dist/**/*.test.d.ts"
17
17
  ],
18
18
  "scripts": {
19
- "build": "rm -rf dist && tsc && chmod +x dist/main.js",
19
+ "build": "rm -rf dist && tsc && chmod +x dist/bin.js dist/main.js",
20
20
  "dev": "tsc && bun dist/main.js",
21
21
  "prepack": "npm run build",
22
22
  "start": "bun dist/main.js",