@bubblebrain-ai/bubble 0.0.35 → 0.0.36

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 (143) hide show
  1. package/README.md +27 -7
  2. package/dist/agent/categories.d.ts +66 -2
  3. package/dist/agent/categories.js +120 -19
  4. package/dist/agent/discovery-barrier.js +1 -1
  5. package/dist/agent/profiles.d.ts +1 -0
  6. package/dist/agent/profiles.js +25 -11
  7. package/dist/agent/routing-catalog.d.ts +113 -0
  8. package/dist/agent/routing-catalog.js +240 -0
  9. package/dist/agent/workflow/control.d.ts +14 -0
  10. package/dist/agent/workflow/control.js +30 -9
  11. package/dist/agent.d.ts +75 -5
  12. package/dist/agent.js +313 -61
  13. package/dist/approval/controller.js +28 -0
  14. package/dist/approval/tool-helper.js +2 -0
  15. package/dist/approval/types.d.ts +21 -1
  16. package/dist/cli.d.ts +1 -1
  17. package/dist/cli.js +2 -1
  18. package/dist/config.d.ts +4 -0
  19. package/dist/config.js +4 -0
  20. package/dist/context/compact-llm.d.ts +2 -2
  21. package/dist/external-runtime/grok-acp.d.ts +56 -0
  22. package/dist/external-runtime/grok-acp.js +386 -0
  23. package/dist/external-runtime/grok-binary.d.ts +25 -0
  24. package/dist/external-runtime/grok-binary.js +108 -0
  25. package/dist/external-runtime/grok-errors.d.ts +7 -0
  26. package/dist/external-runtime/grok-errors.js +15 -0
  27. package/dist/external-runtime/grok-input-policy.d.ts +58 -0
  28. package/dist/external-runtime/grok-input-policy.js +52 -0
  29. package/dist/external-runtime/grok-ndjson.d.ts +14 -0
  30. package/dist/external-runtime/grok-ndjson.js +193 -0
  31. package/dist/external-runtime/grok-network.d.ts +15 -0
  32. package/dist/external-runtime/grok-network.js +81 -0
  33. package/dist/external-runtime/grok-preflight.d.ts +28 -0
  34. package/dist/external-runtime/grok-preflight.js +194 -0
  35. package/dist/external-runtime/grok-process.d.ts +26 -0
  36. package/dist/external-runtime/grok-process.js +184 -0
  37. package/dist/external-runtime/grok-profile.d.ts +32 -0
  38. package/dist/external-runtime/grok-profile.js +419 -0
  39. package/dist/external-runtime/grok-provider.d.ts +31 -0
  40. package/dist/external-runtime/grok-provider.js +41 -0
  41. package/dist/external-runtime/grok-runtime.d.ts +91 -0
  42. package/dist/external-runtime/grok-runtime.js +882 -0
  43. package/dist/external-runtime/index.d.ts +13 -0
  44. package/dist/external-runtime/index.js +13 -0
  45. package/dist/external-runtime/session-policy.d.ts +11 -0
  46. package/dist/external-runtime/session-policy.js +30 -0
  47. package/dist/external-runtime/types.d.ts +65 -0
  48. package/dist/external-runtime/types.js +1 -0
  49. package/dist/feishu/agent-host/approval-card.js +21 -0
  50. package/dist/feishu/agent-host/run-driver.js +19 -3
  51. package/dist/feishu/serve.js +1 -0
  52. package/dist/goal/tools.d.ts +6 -3
  53. package/dist/goal/tools.js +7 -19
  54. package/dist/main.js +105 -27
  55. package/dist/memory/db.d.ts +2 -0
  56. package/dist/memory/db.js +10 -0
  57. package/dist/memory/index.d.ts +1 -0
  58. package/dist/memory/index.js +1 -0
  59. package/dist/memory/phase1.js +14 -2
  60. package/dist/memory/phase2.js +21 -2
  61. package/dist/memory/prompts.js +1 -1
  62. package/dist/memory/session-policy.d.ts +25 -0
  63. package/dist/memory/session-policy.js +55 -0
  64. package/dist/memory/storage.d.ts +1 -0
  65. package/dist/memory/storage.js +13 -0
  66. package/dist/model-catalog.d.ts +18 -0
  67. package/dist/model-catalog.js +87 -41
  68. package/dist/model-config.d.ts +3 -1
  69. package/dist/model-config.js +11 -0
  70. package/dist/oauth/grok.d.ts +25 -0
  71. package/dist/oauth/grok.js +218 -0
  72. package/dist/oauth/index.d.ts +1 -0
  73. package/dist/oauth/index.js +1 -0
  74. package/dist/oauth/storage.d.ts +9 -0
  75. package/dist/oauth/storage.js +25 -0
  76. package/dist/orchestrator/default-hooks.js +17 -7
  77. package/dist/prompt/compose.d.ts +3 -0
  78. package/dist/prompt/compose.js +3 -0
  79. package/dist/prompt/delegation.js +22 -3
  80. package/dist/prompt/environment.js +0 -4
  81. package/dist/prompt/reminders.d.ts +0 -1
  82. package/dist/prompt/reminders.js +1 -11
  83. package/dist/prompt/routing.d.ts +14 -0
  84. package/dist/prompt/routing.js +71 -0
  85. package/dist/prompt/runtime.js +1 -1
  86. package/dist/prompt/task-reminders.d.ts +19 -0
  87. package/dist/prompt/task-reminders.js +49 -0
  88. package/dist/provider-ai-sdk.js +2 -2
  89. package/dist/provider-grok.d.ts +26 -0
  90. package/dist/provider-grok.js +59 -0
  91. package/dist/provider-openai-codex.d.ts +17 -1
  92. package/dist/provider-openai-codex.js +101 -29
  93. package/dist/provider-registry.d.ts +72 -0
  94. package/dist/provider-registry.js +336 -59
  95. package/dist/provider-transform.d.ts +1 -1
  96. package/dist/provider-transform.js +28 -5
  97. package/dist/provider.d.ts +3 -0
  98. package/dist/provider.js +14 -0
  99. package/dist/session-types.d.ts +19 -1
  100. package/dist/session.d.ts +1 -0
  101. package/dist/session.js +4 -0
  102. package/dist/slash-commands/commands.js +224 -14
  103. package/dist/slash-commands/types.d.ts +14 -1
  104. package/dist/system-prompt.d.ts +2 -0
  105. package/dist/tools/agent-lifecycle.js +9 -11
  106. package/dist/tools/bash.js +1 -1
  107. package/dist/tools/child-tools.js +2 -0
  108. package/dist/tools/index.d.ts +2 -2
  109. package/dist/tools/index.js +7 -5
  110. package/dist/tools/lsp.js +1 -0
  111. package/dist/tools/memory.d.ts +7 -2
  112. package/dist/tools/memory.js +28 -43
  113. package/dist/tools/server.js +4 -0
  114. package/dist/tools/tool-search.d.ts +1 -1
  115. package/dist/tools/tool-search.js +23 -3
  116. package/dist/tui/model-switch.d.ts +7 -0
  117. package/dist/tui/model-switch.js +15 -5
  118. package/dist/tui-ink/app.d.ts +3 -1
  119. package/dist/tui-ink/app.js +648 -232
  120. package/dist/tui-ink/approval/approval-dialog.js +36 -0
  121. package/dist/tui-ink/composer-buffer.d.ts +55 -0
  122. package/dist/tui-ink/composer-buffer.js +317 -0
  123. package/dist/tui-ink/footer.d.ts +1 -0
  124. package/dist/tui-ink/footer.js +7 -2
  125. package/dist/tui-ink/input-box.d.ts +35 -1
  126. package/dist/tui-ink/input-box.js +215 -122
  127. package/dist/tui-ink/model-picker.d.ts +61 -4
  128. package/dist/tui-ink/model-picker.js +453 -123
  129. package/dist/tui-ink/paste-operation-tracker.d.ts +13 -0
  130. package/dist/tui-ink/paste-operation-tracker.js +25 -0
  131. package/dist/tui-ink/run.d.ts +3 -0
  132. package/dist/tui-ink/run.js +1 -1
  133. package/dist/tui-ink/subagent-view.d.ts +31 -0
  134. package/dist/tui-ink/subagent-view.js +82 -1
  135. package/dist/types.d.ts +5 -5
  136. package/dist/types.js +1 -1
  137. package/dist/variant/thinking-level.d.ts +2 -2
  138. package/dist/variant/thinking-level.js +2 -1
  139. package/dist/variant/variant-resolver.d.ts +9 -0
  140. package/dist/variant/variant-resolver.js +17 -0
  141. package/package.json +2 -1
  142. package/dist/tools/task.d.ts +0 -2
  143. package/dist/tools/task.js +0 -58
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tracks overlapping paste operations by identity. Finishing an older paste
3
+ * must never clear the pending state of a newer paste.
4
+ */
5
+ export declare class PasteOperationTracker {
6
+ private nextId;
7
+ private readonly active;
8
+ begin(): number;
9
+ finish(id: number): void;
10
+ invalidateAll(): void;
11
+ get hasPending(): boolean;
12
+ get pendingCount(): number;
13
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Tracks overlapping paste operations by identity. Finishing an older paste
3
+ * must never clear the pending state of a newer paste.
4
+ */
5
+ export class PasteOperationTracker {
6
+ nextId = 1;
7
+ active = new Set();
8
+ begin() {
9
+ const id = this.nextId++;
10
+ this.active.add(id);
11
+ return id;
12
+ }
13
+ finish(id) {
14
+ this.active.delete(id);
15
+ }
16
+ invalidateAll() {
17
+ this.active.clear();
18
+ }
19
+ get hasPending() {
20
+ return this.active.size > 0;
21
+ }
22
+ get pendingCount() {
23
+ return this.active.size;
24
+ }
25
+ }
@@ -14,6 +14,7 @@ import type { QuestionController } from "../question/index.js";
14
14
  import type { MemoryScope } from "../memory/index.js";
15
15
  import type { ExternalHookController } from "../hooks/controller.js";
16
16
  import type { ResolvedTheme, ThemeMode } from "./theme.js";
17
+ import type { ExternalRuntimeManager } from "../external-runtime/types.js";
17
18
  export interface RunTuiOptions {
18
19
  sessionManager?: SessionManager;
19
20
  createProvider?: (providerId: string, apiKey: string, baseURL: string) => Provider;
@@ -48,6 +49,8 @@ export interface RunTuiOptions {
48
49
  updateNoticeRefresh?: Promise<string | null>;
49
50
  /** External lifecycle hooks, threaded into slash-command execution. */
50
51
  hookController?: ExternalHookController;
52
+ /** Subscription-backed external agent runtime used by the interactive TUI. */
53
+ externalRuntime?: ExternalRuntimeManager;
51
54
  }
52
55
  export declare function createInkAppElement(agent: Agent, args: CliArgs, options: RunTuiOptions, onExit: (summary: ExitSummary) => void): React.ReactElement;
53
56
  export declare function runTui(agent: Agent, args: CliArgs, options?: RunTuiOptions): Promise<ExitSummary | undefined>;
@@ -3,7 +3,7 @@ import { render } from "ink";
3
3
  import { App } from "./app.js";
4
4
  import { warmHighlighter } from "./code-highlight.js";
5
5
  export function createInkAppElement(agent, args, options, onExit) {
6
- return (_jsx(App, { agent: agent, args: args, sessionManager: options.sessionManager, switchSession: options.switchSession, createProvider: options.createProvider, registry: options.registry, skillRegistry: options.skillRegistry, planHandlerRef: options.planHandlerRef, approvalHandlerRef: options.approvalHandlerRef, questionController: options.questionController, bashAllowlist: options.bashAllowlist, settingsManager: options.settingsManager, lspService: options.lspService, mcpManager: options.mcpManager, goalStore: options.goalStore, themeMode: options.themeMode, themeOverrides: options.themeOverrides, detectedTheme: options.detectedTheme, onThemeModeChange: options.onThemeModeChange, flushMemory: options.flushMemory, runMemoryCompaction: options.runMemoryCompaction, runMemorySummary: options.runMemorySummary, runMemoryRefresh: options.runMemoryRefresh, bypassEnabled: options.bypassEnabled, updateNotice: options.updateNotice, updateNoticeRefresh: options.updateNoticeRefresh, hookController: options.hookController, onExit: onExit }));
6
+ return (_jsx(App, { agent: agent, args: args, sessionManager: options.sessionManager, switchSession: options.switchSession, createProvider: options.createProvider, registry: options.registry, skillRegistry: options.skillRegistry, planHandlerRef: options.planHandlerRef, approvalHandlerRef: options.approvalHandlerRef, questionController: options.questionController, bashAllowlist: options.bashAllowlist, settingsManager: options.settingsManager, lspService: options.lspService, mcpManager: options.mcpManager, goalStore: options.goalStore, themeMode: options.themeMode, themeOverrides: options.themeOverrides, detectedTheme: options.detectedTheme, onThemeModeChange: options.onThemeModeChange, flushMemory: options.flushMemory, runMemoryCompaction: options.runMemoryCompaction, runMemorySummary: options.runMemorySummary, runMemoryRefresh: options.runMemoryRefresh, bypassEnabled: options.bypassEnabled, updateNotice: options.updateNotice, updateNoticeRefresh: options.updateNoticeRefresh, hookController: options.hookController, externalRuntime: options.externalRuntime, onExit: onExit }));
7
7
  }
8
8
  /**
9
9
  * Best-effort terminal restore for abnormal exits. Bubble renders into the
@@ -6,6 +6,7 @@
6
6
  import { type SubagentRouteLike } from "../agent/subagent-route-format.js";
7
7
  import type { Theme } from "./theme.js";
8
8
  import type { DisplayMessage, DisplayToolCall } from "./display-history.js";
9
+ import type { ToolResultMetadata } from "../types.js";
9
10
  export interface SubagentDisplay {
10
11
  subAgentId?: string;
11
12
  agentName?: string;
@@ -35,6 +36,36 @@ export interface SubagentGroup {
35
36
  label: string;
36
37
  members: SubagentDisplay[];
37
38
  }
39
+ /**
40
+ * Merges subagent tool metadata, deduping member snapshots by subAgentId so a
41
+ * stream of per-child updates accumulates into one member list instead of each
42
+ * update replacing the last.
43
+ */
44
+ export declare function mergeToolMetadata(current: ToolResultMetadata | undefined, incoming: ToolResultMetadata | undefined): ToolResultMetadata | undefined;
45
+ /**
46
+ * Drops accumulator entries whose every member reached a final status: by then
47
+ * the authoritative snapshot lives in the settled transcript (wait_workflow /
48
+ * wait_agent result), so the entry is dead weight that would otherwise pile up
49
+ * for the life of the process. Entries with running/queued members stay — for
50
+ * a workflow spanning turns they are the only live view. Returns whether the
51
+ * map changed (callers bump their version counter on true).
52
+ */
53
+ export declare function pruneSettledLiveSubagentTools(map: Map<string, DisplayToolCall>): boolean;
54
+ /**
55
+ * Accumulates a subagent tool_update whose originating tool call has already
56
+ * settled out of the current streaming round. The TUI clears its streaming
57
+ * toolCalls on every turn_start, but background children keep reporting
58
+ * against the launching run_workflow/spawn_agent call id across later rounds
59
+ * (e.g. while a wait_workflow blocks) — without this side-channel those
60
+ * updates are dropped and traces only appear after the whole team finishes.
61
+ * Entries act as synthetic tool calls feeding collectSubagentGroups.
62
+ * Returns true when the update was absorbed.
63
+ */
64
+ export declare function accumulateLiveSubagentUpdate(map: Map<string, DisplayToolCall>, event: {
65
+ id: string;
66
+ name: string;
67
+ metadata?: ToolResultMetadata;
68
+ }): boolean;
38
69
  /**
39
70
  * Collects every spawned subagent from the live transcript + streaming tools,
40
71
  * grouped by their originating tool call, for the inspector. Pure.
@@ -75,6 +75,84 @@ function subagentFreshness(member) {
75
75
  function memberKey(member) {
76
76
  return member.subAgentId || `${member.nickname ?? ""}|${member.task ?? ""}`;
77
77
  }
78
+ /**
79
+ * Merges subagent tool metadata, deduping member snapshots by subAgentId so a
80
+ * stream of per-child updates accumulates into one member list instead of each
81
+ * update replacing the last.
82
+ */
83
+ export function mergeToolMetadata(current, incoming) {
84
+ if (!incoming)
85
+ return current;
86
+ if (current?.kind !== "subagent" || incoming.kind !== "subagent") {
87
+ return incoming;
88
+ }
89
+ const currentSubagents = Array.isArray(current.subagents) ? current.subagents : [];
90
+ const incomingSubagents = Array.isArray(incoming.subagents) ? incoming.subagents : [];
91
+ const byId = new Map();
92
+ for (const item of currentSubagents) {
93
+ const subAgentId = typeof item === "object" && item !== null && "subAgentId" in item
94
+ ? String(item.subAgentId)
95
+ : "";
96
+ byId.set(subAgentId || `current:${byId.size}`, item);
97
+ }
98
+ for (const item of incomingSubagents) {
99
+ const subAgentId = typeof item === "object" && item !== null && "subAgentId" in item
100
+ ? String(item.subAgentId)
101
+ : "";
102
+ byId.set(subAgentId || `incoming:${byId.size}`, item);
103
+ }
104
+ return {
105
+ ...current,
106
+ ...incoming,
107
+ subagents: [...byId.values()],
108
+ };
109
+ }
110
+ const FINAL_MEMBER_STATUSES = new Set(["completed", "failed", "blocked", "cancelled", "closed"]);
111
+ /**
112
+ * Drops accumulator entries whose every member reached a final status: by then
113
+ * the authoritative snapshot lives in the settled transcript (wait_workflow /
114
+ * wait_agent result), so the entry is dead weight that would otherwise pile up
115
+ * for the life of the process. Entries with running/queued members stay — for
116
+ * a workflow spanning turns they are the only live view. Returns whether the
117
+ * map changed (callers bump their version counter on true).
118
+ */
119
+ export function pruneSettledLiveSubagentTools(map) {
120
+ let changed = false;
121
+ for (const [id, tc] of map) {
122
+ const members = Array.isArray(tc.metadata?.subagents) ? tc.metadata.subagents : [];
123
+ const allFinal = members.every((member) => typeof member === "object" && member !== null
124
+ && FINAL_MEMBER_STATUSES.has(String(member.status)));
125
+ if (allFinal) {
126
+ map.delete(id);
127
+ changed = true;
128
+ }
129
+ }
130
+ return changed;
131
+ }
132
+ /**
133
+ * Accumulates a subagent tool_update whose originating tool call has already
134
+ * settled out of the current streaming round. The TUI clears its streaming
135
+ * toolCalls on every turn_start, but background children keep reporting
136
+ * against the launching run_workflow/spawn_agent call id across later rounds
137
+ * (e.g. while a wait_workflow blocks) — without this side-channel those
138
+ * updates are dropped and traces only appear after the whole team finishes.
139
+ * Entries act as synthetic tool calls feeding collectSubagentGroups.
140
+ * Returns true when the update was absorbed.
141
+ */
142
+ export function accumulateLiveSubagentUpdate(map, event) {
143
+ if (event.metadata?.kind !== "subagent")
144
+ return false;
145
+ const prev = map.get(event.id);
146
+ const metadata = mergeToolMetadata(prev?.metadata, event.metadata);
147
+ if (!metadata)
148
+ return false;
149
+ // Per-child updates carry no mode; group them under the launching workflow
150
+ // call rather than falling back to one single-agent group per member.
151
+ if (event.name === "run_workflow" && metadata.mode === undefined)
152
+ metadata.mode = "workflow";
153
+ map.set(event.id, { id: event.id, name: event.name, args: prev?.args ?? {}, metadata });
154
+ return true;
155
+ }
78
156
  /**
79
157
  * Collects every spawned subagent from the live transcript + streaming tools,
80
158
  * grouped by their originating tool call, for the inspector. Pure.
@@ -159,5 +237,8 @@ export function collectSubagentGroups(messages, streamingTools) {
159
237
  kind: g.kind,
160
238
  label: g.label,
161
239
  members: g.memberKeys.map((k) => freshest.get(k)).filter((m) => !!m),
162
- }));
240
+ }))
241
+ // A group can end up empty when a later echo of the same run (e.g. the
242
+ // wait_workflow result vs the live accumulator) claimed all its members.
243
+ .filter((group) => group.members.length > 0);
163
244
  }
package/dist/types.d.ts CHANGED
@@ -12,7 +12,8 @@ export interface ImageContent {
12
12
  };
13
13
  }
14
14
  export type ContentPart = TextContent | ImageContent;
15
- export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
15
+ export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
16
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
16
17
  export type ReasoningEffort = ThinkingLevel;
17
18
  export type ProviderRawContentBlock = Record<string, unknown> & {
18
19
  type: string;
@@ -169,10 +170,9 @@ export interface ToolContext {
169
170
  name: string;
170
171
  };
171
172
  agent?: {
172
- runSubtask: (input: string | ContentPart[], cwd: string, options?: {
173
- subtaskType?: string;
174
- description?: string;
175
- }) => Promise<ToolResult>;
173
+ /** Deferred-tool surface; backs tool_search when no controller is wired. */
174
+ listDeferredTools?: () => ToolRegistryEntry[];
175
+ unlockDeferredTools?: (names: string[]) => void;
176
176
  runSubAgent?: (input: string | ContentPart[], cwd: string, options: {
177
177
  profile: import("./agent/profiles.js").AgentProfile;
178
178
  runId: string;
package/dist/types.js CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Core types for the coding agent.
3
3
  */
4
- export {};
4
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
@@ -1,5 +1,5 @@
1
- import type { ThinkingLevel } from "../types.js";
2
- export declare const THINKING_LEVELS: ThinkingLevel[];
1
+ import { type ThinkingLevel } from "../types.js";
2
+ export { THINKING_LEVELS } from "../types.js";
3
3
  export declare function isThinkingLevel(value: string | undefined): value is ThinkingLevel;
4
4
  export declare function clampThinkingLevel(requestedLevel: ThinkingLevel, supportedLevels: readonly ThinkingLevel[]): ThinkingLevel;
5
5
  export declare function getNextThinkingLevel(currentLevel: ThinkingLevel, supportedLevels: readonly ThinkingLevel[]): ThinkingLevel;
@@ -1,4 +1,5 @@
1
- export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
1
+ import { THINKING_LEVELS } from "../types.js";
2
+ export { THINKING_LEVELS } from "../types.js";
2
3
  export function isThinkingLevel(value) {
3
4
  return !!value && THINKING_LEVELS.includes(value);
4
5
  }
@@ -15,3 +15,12 @@ export declare function isThinkingOnlyModel(providerId?: string, modelId?: strin
15
15
  */
16
16
  export declare function isThinkingToggleModel(providerId?: string, modelId?: string): boolean;
17
17
  export declare function normalizeThinkingLevel(level: ThinkingLevel, supportedLevels: readonly ThinkingLevel[]): ThinkingLevel;
18
+ /**
19
+ * Normalizes effort inherited from a previous model or restored session.
20
+ *
21
+ * Unlike an explicit CLI/config choice, an inherited unsupported value does
22
+ * not express a direction the user just chose for the target model. Prefer the
23
+ * target model's declared default in that case, then fall back to the canonical
24
+ * downward clamp for legacy models without an explicit default.
25
+ */
26
+ export declare function normalizeInheritedThinkingLevel(providerId: string, modelId: string, inheritedLevel: ThinkingLevel): ThinkingLevel;
@@ -39,3 +39,20 @@ export function isThinkingToggleModel(providerId = "", modelId = "") {
39
39
  export function normalizeThinkingLevel(level, supportedLevels) {
40
40
  return clampThinkingLevel(level, supportedLevels);
41
41
  }
42
+ /**
43
+ * Normalizes effort inherited from a previous model or restored session.
44
+ *
45
+ * Unlike an explicit CLI/config choice, an inherited unsupported value does
46
+ * not express a direction the user just chose for the target model. Prefer the
47
+ * target model's declared default in that case, then fall back to the canonical
48
+ * downward clamp for legacy models without an explicit default.
49
+ */
50
+ export function normalizeInheritedThinkingLevel(providerId, modelId, inheritedLevel) {
51
+ const supportedLevels = getAvailableThinkingLevels(providerId, modelId);
52
+ if (supportedLevels.includes(inheritedLevel))
53
+ return inheritedLevel;
54
+ const modelDefault = getModelDefaultReasoningLevel(providerId, modelId);
55
+ if (modelDefault && supportedLevels.includes(modelDefault))
56
+ return modelDefault;
57
+ return clampThinkingLevel(inheritedLevel, supportedLevels);
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {
@@ -24,6 +24,7 @@
24
24
  "test:watch": "vitest"
25
25
  },
26
26
  "dependencies": {
27
+ "@agentclientprotocol/sdk": "1.2.1",
27
28
  "@ai-sdk/google": "^3.0.88",
28
29
  "@ai-sdk/provider": "^3.0.13",
29
30
  "@larksuiteoapi/node-sdk": "^1.65.0",
@@ -1,2 +0,0 @@
1
- import type { ToolRegistryEntry } from "../types.js";
2
- export declare function createTaskTool(): ToolRegistryEntry;
@@ -1,58 +0,0 @@
1
- export function createTaskTool() {
2
- return {
3
- name: "task",
4
- readOnly: true,
5
- effect: "read",
6
- description: `Delegate a bounded investigative subtask to a read-only sub-agent and return a concise summary.
7
-
8
- Use this when:
9
- - a search/investigation can be scoped as a sub-problem
10
- - you want a focused summary before continuing the main task
11
- - you need to inspect a specific hypothesis or area without polluting the main loop with exploratory churn
12
-
13
- Do not use this for edits or shell-heavy workflows. The subtask agent runs in read-only plan mode.`,
14
- parameters: {
15
- type: "object",
16
- properties: {
17
- prompt: {
18
- type: "string",
19
- description: "The focused subtask prompt to investigate.",
20
- },
21
- description: {
22
- type: "string",
23
- description: "Short description of what this subtask is trying to verify.",
24
- },
25
- subtaskType: {
26
- type: "string",
27
- enum: ["search", "security_investigation", "evidence_correlation", "general_readonly"],
28
- description: "The bounded subtask policy to apply.",
29
- },
30
- },
31
- required: ["prompt"],
32
- additionalProperties: false,
33
- },
34
- async execute(args, ctx) {
35
- const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
36
- const description = typeof args.description === "string" ? args.description.trim() : "";
37
- const subtaskType = typeof args.subtaskType === "string" ? args.subtaskType : undefined;
38
- if (!prompt) {
39
- return { content: "Error: task prompt is required", isError: true };
40
- }
41
- if (!ctx.agent?.runSubtask) {
42
- return { content: "Error: task tool requires an agent runtime", isError: true };
43
- }
44
- const composed = description
45
- ? `${description}\n\n${prompt}\n\nInvestigate this sub-problem in read-only mode and return a concise evidence-based summary.`
46
- : `${prompt}\n\nInvestigate this sub-problem in read-only mode and return a concise evidence-based summary.`;
47
- const result = await ctx.agent.runSubtask(composed, ctx.cwd, { subtaskType, description });
48
- return {
49
- ...result,
50
- metadata: {
51
- ...result.metadata,
52
- kind: "security",
53
- reason: description || result.metadata?.reason,
54
- },
55
- };
56
- },
57
- };
58
- }