@bubblebrain-ai/bubble 0.0.13 → 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/tool-intent.js +1 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +589 -316
- 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 +28 -0
- 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 +12 -0
- package/dist/slash-commands/types.d.ts +2 -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/display-history.d.ts +1 -0
- package/dist/tui/display-history.js +5 -4
- package/dist/tui/edit-diff.js +6 -1
- package/dist/tui/model-picker-data.d.ts +10 -0
- package/dist/tui/model-picker-data.js +32 -0
- package/dist/tui/run.js +632 -89
- package/dist/tui/tool-renderers/fallback.js +1 -1
- package/dist/tui/tool-renderers/write-preview.js +2 -0
- package/dist/tui/trace-groups.js +10 -3
- package/dist/tui-ink/app.js +1 -4
- 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 +14 -8
- package/dist/tui-ink/trace-groups.js +1 -1
- package/dist/tui-opentui/app.js +2 -0
- package/dist/tui-opentui/approval/approval-dialog.js +7 -1
- package/dist/tui-opentui/display-history.d.ts +1 -0
- package/dist/tui-opentui/display-history.js +5 -4
- package/dist/tui-opentui/edit-diff.js +6 -1
- package/dist/tui-opentui/message-list.js +6 -3
- package/dist/tui-opentui/trace-groups.js +10 -3
- package/dist/types.d.ts +12 -2
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -27,6 +27,7 @@ import { buildMemoryPrompt, formatMemoryStartupResult, recordMemoryCitations, ru
|
|
|
27
27
|
import { basename } from "node:path";
|
|
28
28
|
import { normalizeSingleLine, truncateVisual } from "./text-display.js";
|
|
29
29
|
import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
|
|
30
|
+
import { configureDebugTrace, summarizeAgentEventForTrace, summarizeTraceMessage, traceEvent, } from "./debug-trace.js";
|
|
30
31
|
async function main() {
|
|
31
32
|
const args = parseArgs(process.argv.slice(2));
|
|
32
33
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
@@ -72,6 +73,7 @@ async function main() {
|
|
|
72
73
|
baseURL: defaultProvider.baseURL,
|
|
73
74
|
thinkingLevel: args.thinkingLevel,
|
|
74
75
|
promptCacheKey: sessionPromptCacheKey,
|
|
76
|
+
openAICodexAuth: registry.createOpenAICodexAuthAdapter(defaultProvider.id),
|
|
75
77
|
})
|
|
76
78
|
: createUnavailableProvider(unavailableProviderMessage);
|
|
77
79
|
const createProvider = (providerId, apiKey, baseURL) => createProviderInstance({
|
|
@@ -80,6 +82,7 @@ async function main() {
|
|
|
80
82
|
baseURL,
|
|
81
83
|
thinkingLevel: args.thinkingLevel,
|
|
82
84
|
promptCacheKey: sessionPromptCacheKey,
|
|
85
|
+
openAICodexAuth: registry.createOpenAICodexAuthAdapter(providerId),
|
|
83
86
|
});
|
|
84
87
|
const createProviderForRoute = async (route) => {
|
|
85
88
|
const providerId = route.providerId;
|
|
@@ -276,6 +279,25 @@ async function main() {
|
|
|
276
279
|
tools: tools.map((tool) => tool.name),
|
|
277
280
|
memoryPrompt,
|
|
278
281
|
});
|
|
282
|
+
const traceInfo = configureDebugTrace({
|
|
283
|
+
cwd: args.cwd,
|
|
284
|
+
sessionFile: sessionManager?.getSessionFile(),
|
|
285
|
+
provider: activeProviderId || "none",
|
|
286
|
+
model: activeModel || "none",
|
|
287
|
+
renderer: printMode ? "print" : "opentui-core",
|
|
288
|
+
});
|
|
289
|
+
if (traceInfo.enabled) {
|
|
290
|
+
traceEvent("run_start", {
|
|
291
|
+
tracePath: traceInfo.path,
|
|
292
|
+
rawEnabled: traceInfo.rawEnabled,
|
|
293
|
+
resumed: resumedExistingSession,
|
|
294
|
+
printMode,
|
|
295
|
+
mode: initialMode,
|
|
296
|
+
thinkingLevel: initialThinkingLevel,
|
|
297
|
+
tools: tools.length,
|
|
298
|
+
cwd: args.cwd,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
279
301
|
const budgetLedger = new BudgetLedger();
|
|
280
302
|
let sessionTitleUpdater;
|
|
281
303
|
const agent = new Agent({
|
|
@@ -301,6 +323,9 @@ async function main() {
|
|
|
301
323
|
if (message.role === "meta")
|
|
302
324
|
return;
|
|
303
325
|
sessionManager.appendMessage(message);
|
|
326
|
+
traceEvent("session_message_persisted", {
|
|
327
|
+
message: summarizeTraceMessage(message),
|
|
328
|
+
});
|
|
304
329
|
sessionTitleUpdater?.handlePersistedMessage(message);
|
|
305
330
|
if (message.role === "assistant") {
|
|
306
331
|
recordMemoryCitations(args.cwd, message.content);
|
|
@@ -404,6 +429,7 @@ async function main() {
|
|
|
404
429
|
process.exit(1);
|
|
405
430
|
}
|
|
406
431
|
for await (const event of agent.run(prompt, args.cwd)) {
|
|
432
|
+
traceEvent("print_agent_event", summarizeAgentEventForTrace(event));
|
|
407
433
|
if (event.type === "text_delta") {
|
|
408
434
|
process.stdout.write(event.content);
|
|
409
435
|
}
|
|
@@ -465,7 +491,9 @@ async function main() {
|
|
|
465
491
|
}
|
|
466
492
|
}
|
|
467
493
|
finally {
|
|
494
|
+
traceEvent("run_shutdown_start");
|
|
468
495
|
await shutdownRuntime();
|
|
496
|
+
traceEvent("run_shutdown_end");
|
|
469
497
|
}
|
|
470
498
|
}
|
|
471
499
|
function printOpenTuiExitSummary(sessionManager, options) {
|
package/dist/model-catalog.js
CHANGED
|
@@ -25,6 +25,7 @@ const OPENAI_CHAT_LEVELS = ["off"];
|
|
|
25
25
|
const TOGGLE_THINKING_LEVELS = ["off", "medium"];
|
|
26
26
|
const DEEPSEEK_V4_LEVELS = ["high", "max"];
|
|
27
27
|
export const BUILTIN_MODELS = [
|
|
28
|
+
{ id: "gpt-5.5", name: "gpt-5.5", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000, toolOutputTokenLimit: 10000 },
|
|
28
29
|
{ id: "gpt-5.4", name: "gpt-5.4", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
|
|
29
30
|
{ id: "gpt-5.4-mini", name: "gpt-5.4-mini", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
|
|
30
31
|
{ id: "gpt-5.3-codex", name: "gpt-5.3-codex", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
|
|
@@ -78,11 +78,18 @@ export function createDefaultHooks() {
|
|
|
78
78
|
}
|
|
79
79
|
ctx.state.evidenceTracker?.observe(ctx.toolCall, ctx.result);
|
|
80
80
|
ctx.state.governor?.afterToolResult(ctx.toolCall, ctx.result);
|
|
81
|
-
// Edit/write retry-escalation:
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
// Edit/write retry-escalation: models can spiral on "identical content"
|
|
82
|
+
// or "not found" errors. Nudge them to re-ground or switch strategy.
|
|
83
|
+
if (isMutationTool(ctx.toolCall.name) && ctx.result.isError) {
|
|
84
|
+
if (ctx.toolCall.name === "edit" && ctx.result.status === "no_match" && ctx.result.metadata?.kind === "edit") {
|
|
85
|
+
const path = typeof ctx.result.metadata.path === "string" ? ctx.result.metadata.path : "";
|
|
86
|
+
const reminded = ctx.state.editNoMatchReminderPaths ?? (ctx.state.editNoMatchReminderPaths = []);
|
|
87
|
+
if (path && !reminded.includes(path)) {
|
|
88
|
+
reminded.push(path);
|
|
89
|
+
const summary = ctx.result.content.split("\n")[0] || "";
|
|
90
|
+
ctx.queueReminder(buildEditRetryEscalationReminder(`Edit oldText did not match ${path}. ${summary}`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
86
93
|
const hash = hashEditCall(ctx.toolCall);
|
|
87
94
|
const history = ctx.state.recentEditFailures ?? (ctx.state.recentEditFailures = []);
|
|
88
95
|
history.push(hash);
|
|
@@ -96,10 +103,11 @@ export function createDefaultHooks() {
|
|
|
96
103
|
ctx.queueReminder(buildEditRetryEscalationReminder(`Last failure: ${ctx.toolCall.name} on the same target with identical arguments. ${summary}`));
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
|
-
else if ((ctx.toolCall.name
|
|
106
|
+
else if (isMutationTool(ctx.toolCall.name) && !ctx.result.isError) {
|
|
100
107
|
// Successful mutation resets the dedup state so a later, unrelated
|
|
101
108
|
// failure won't fire the reminder spuriously.
|
|
102
109
|
ctx.state.recentEditFailures = [];
|
|
110
|
+
ctx.state.editNoMatchReminderPaths = [];
|
|
103
111
|
ctx.state.editRetryReminderSent = false;
|
|
104
112
|
}
|
|
105
113
|
// Redundant-Read detection moved into the read tool itself: it now
|
|
@@ -151,10 +159,13 @@ function markCodeChanged(state) {
|
|
|
151
159
|
state.codeChanged = true;
|
|
152
160
|
}
|
|
153
161
|
function isCodeWriteResult(_toolCall, result) {
|
|
154
|
-
if (result.isError || result.status === "blocked" || result.status === "command_error") {
|
|
162
|
+
if (result.isError || result.status === "blocked" || result.status === "cancelled" || result.status === "command_error") {
|
|
155
163
|
return false;
|
|
156
164
|
}
|
|
157
|
-
return result.metadata?.kind === "write" || result.metadata?.kind === "edit";
|
|
165
|
+
return result.metadata?.kind === "write" || result.metadata?.kind === "edit" || result.metadata?.kind === "patch";
|
|
166
|
+
}
|
|
167
|
+
function isMutationTool(name) {
|
|
168
|
+
return name === "edit" || name === "write" || name === "apply_patch";
|
|
158
169
|
}
|
|
159
170
|
function hasSubagentLifecycleActivity(toolCalls, toolResults) {
|
|
160
171
|
return toolCalls.some((toolCall) => isSubagentLifecycleTool(toolCall.name))
|
|
@@ -16,6 +16,7 @@ export interface TurnHookState {
|
|
|
16
16
|
codeChanged?: boolean;
|
|
17
17
|
smallTaskHintSent?: boolean;
|
|
18
18
|
recentEditFailures?: string[];
|
|
19
|
+
editNoMatchReminderPaths?: string[];
|
|
19
20
|
editRetryReminderSent?: boolean;
|
|
20
21
|
recentReadPaths?: string[];
|
|
21
22
|
redundantReadReminded?: Set<string>;
|
|
@@ -3,6 +3,7 @@ export const defaultToolSnippets = {
|
|
|
3
3
|
read: "Read the contents of a file",
|
|
4
4
|
bash: "Execute a bash command",
|
|
5
5
|
edit: "Apply targeted string replacements to a file",
|
|
6
|
+
apply_patch: "Apply a structured patch for multi-file or larger code changes",
|
|
6
7
|
write: "Write a new file or overwrite an existing one",
|
|
7
8
|
glob: "Find files by glob pattern without using bash",
|
|
8
9
|
grep: "Search file contents using regex",
|
|
@@ -23,6 +24,7 @@ export const defaultToolNames = [
|
|
|
23
24
|
"glob",
|
|
24
25
|
"bash",
|
|
25
26
|
"edit",
|
|
27
|
+
"apply_patch",
|
|
26
28
|
"write",
|
|
27
29
|
"grep",
|
|
28
30
|
"lsp",
|
|
@@ -31,12 +31,11 @@ export declare function buildWorkflowPhaseReminder(input: {
|
|
|
31
31
|
}): string;
|
|
32
32
|
export declare function buildTaskSummaryReminder(): string;
|
|
33
33
|
/**
|
|
34
|
-
* Fired when
|
|
35
|
-
*
|
|
36
|
-
* ones — can otherwise spiral on `No changes made: identical
|
|
37
|
-
* `oldText not found` because their internal reasoning convinces
|
|
38
|
-
* are typing the change correctly
|
|
39
|
-
* This nudge forces a strategy change.
|
|
34
|
+
* Fired when a file mutation failure suggests the model may be relying on stale
|
|
35
|
+
* local memory instead of the current file bytes. Models — especially
|
|
36
|
+
* thinking-heavy ones — can otherwise spiral on `No changes made: identical
|
|
37
|
+
* content` or `oldText not found` because their internal reasoning convinces
|
|
38
|
+
* them they are typing the change correctly.
|
|
40
39
|
*/
|
|
41
40
|
export declare function buildEditRetryEscalationReminder(reason: string): string;
|
|
42
41
|
/**
|
package/dist/prompt/reminders.js
CHANGED
|
@@ -178,23 +178,22 @@ Treat the task output as a bounded subtask result:
|
|
|
178
178
|
// validation scripts that found the bug but could never fix it. CC trusts the
|
|
179
179
|
// model to decide when verification is meaningful; we follow that.
|
|
180
180
|
/**
|
|
181
|
-
* Fired when
|
|
182
|
-
*
|
|
183
|
-
* ones — can otherwise spiral on `No changes made: identical
|
|
184
|
-
* `oldText not found` because their internal reasoning convinces
|
|
185
|
-
* are typing the change correctly
|
|
186
|
-
* This nudge forces a strategy change.
|
|
181
|
+
* Fired when a file mutation failure suggests the model may be relying on stale
|
|
182
|
+
* local memory instead of the current file bytes. Models — especially
|
|
183
|
+
* thinking-heavy ones — can otherwise spiral on `No changes made: identical
|
|
184
|
+
* content` or `oldText not found` because their internal reasoning convinces
|
|
185
|
+
* them they are typing the change correctly.
|
|
187
186
|
*/
|
|
188
187
|
export function buildEditRetryEscalationReminder(reason) {
|
|
189
188
|
return wrapInSystemReminder(`
|
|
190
|
-
|
|
189
|
+
A file mutation just failed in a way that usually means your local view of the file is stale or the edit anchor is wrong.
|
|
191
190
|
|
|
192
191
|
${reason}
|
|
193
192
|
|
|
194
|
-
Stop retrying
|
|
193
|
+
Stop retrying from memory. Pick one of:
|
|
195
194
|
- Re-read the target file and compare the actual bytes to your intended oldText / newText. Trailing whitespace, unicode lookalikes, or off-by-one boundaries are common causes.
|
|
196
195
|
- If you intended to add a single character (e.g. fixing a 5-digit hex color to 6 digits), confirm that your newText string actually contains the added character before sending again.
|
|
197
|
-
- Use the write tool with
|
|
196
|
+
- Use the write tool with the full new content instead of edit — useful when the change spans many lines or the diff anchor is ambiguous.
|
|
198
197
|
- If you cannot determine the cause, ask the user for clarification.
|
|
199
198
|
`);
|
|
200
199
|
}
|
package/dist/prompt/runtime.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
const defaultGuidelines = [
|
|
7
7
|
"Ground decisions in the codebase: inspect relevant files, command output, or runtime state before making claims about behavior. Separate confirmed facts from inference when evidence is incomplete.",
|
|
8
8
|
"Choose the smallest coherent change. Edit only the files required for the requested change; do not refactor or improve adjacent code unprompted.",
|
|
9
|
-
"For modifications to existing code, read the file first. For brand-new files whose target path is known and does not exist, write directly without exploratory reading. Use edit for small targeted changes
|
|
9
|
+
"For modifications to existing code, read the file first. For brand-new files whose target path is known and does not exist, write directly without exploratory reading. Use edit for small targeted changes, apply_patch for related multi-file or larger structured changes, and write for intentional full-file replacement of an existing file. Never delete and recreate a file just to overwrite it.",
|
|
10
10
|
"Prefer structured tools (glob, grep, lsp, read) over bash for search and inspection. Do not repeat a near-identical search or re-read the same file unless new evidence changes the question.",
|
|
11
|
-
"If a tool fails, diagnose the error before switching tactics. Do not retry the identical call with identical arguments. After two equivalent failures, switch approach — re-read the file, use a different tool, rewrite the whole file with write
|
|
11
|
+
"If a tool fails, diagnose the error before switching tactics. Do not retry the identical call with identical arguments. After two equivalent failures, switch approach — re-read the file, use a different tool, rewrite the whole file with write, or ask the user.",
|
|
12
12
|
"Before reporting a task complete, verify it works when verification is meaningful and cheap — run the existing test, execute the script, check the output. If no test exists, the change is purely declarative (static HTML/markdown/config), or running the code is not practical, state that explicitly rather than inventing a verification step. Do not write throwaway validation scripts to prove correctness; if there is no real check to run, report the change and stop.",
|
|
13
13
|
];
|
|
14
14
|
export function buildRuntimePrompt(options = {}) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Provider, ReasoningEffort, ThinkingLevel, TokenUsage } from "./types.js";
|
|
2
|
+
import type { OAuthCredentials } from "./oauth/types.js";
|
|
2
3
|
export interface CodexModelDescriptor {
|
|
3
4
|
id: string;
|
|
4
5
|
displayName?: string;
|
|
@@ -11,6 +12,11 @@ export interface CodexModelDescriptor {
|
|
|
11
12
|
}
|
|
12
13
|
export declare function isOpenAICodexBaseUrl(baseURL: string): boolean;
|
|
13
14
|
export declare function getOpenAICodexFallbackModels(): string[];
|
|
15
|
+
export interface OpenAICodexAuthAdapter {
|
|
16
|
+
getCredentials: () => OAuthCredentials | undefined | Promise<OAuthCredentials | undefined>;
|
|
17
|
+
refreshCredentials: (current?: OAuthCredentials) => Promise<OAuthCredentials>;
|
|
18
|
+
isExpired?: (credentials: OAuthCredentials, graceMs: number) => boolean;
|
|
19
|
+
}
|
|
14
20
|
export declare function extractChatGptAccountId(accessToken: string): string | undefined;
|
|
15
21
|
export declare function createOpenAICodexProvider(options: {
|
|
16
22
|
providerId?: string;
|
|
@@ -18,6 +24,7 @@ export declare function createOpenAICodexProvider(options: {
|
|
|
18
24
|
baseURL: string;
|
|
19
25
|
thinkingLevel?: ThinkingLevel;
|
|
20
26
|
promptCacheKey?: string;
|
|
27
|
+
auth?: OpenAICodexAuthAdapter;
|
|
21
28
|
}): Provider;
|
|
22
29
|
export declare function normalizeOpenAICodexUsage(usage: any): TokenUsage;
|
|
23
30
|
export declare function buildOpenAICodexPromptCacheKey(input: {
|