@bubblebrain-ai/bubble 0.0.3 → 0.0.4
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/README.md +8 -3
- package/dist/agent/execution-governor.d.ts +14 -0
- package/dist/agent/execution-governor.js +172 -14
- package/dist/agent/task-classifier.d.ts +1 -1
- package/dist/agent/task-classifier.js +60 -0
- package/dist/agent/tool-intent.d.ts +14 -0
- package/dist/agent/tool-intent.js +125 -1
- package/dist/agent.js +4 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +45 -0
- package/dist/main.d.ts +1 -1
- package/dist/main.js +1 -1
- package/dist/orchestrator/default-hooks.js +53 -1
- package/dist/orchestrator/hooks.d.ts +5 -0
- package/dist/prompt/compose.js +12 -0
- package/dist/prompt/provider-prompts/deepseek.d.ts +1 -0
- package/dist/prompt/provider-prompts/deepseek.js +8 -0
- package/dist/prompt/provider-prompts/glm.d.ts +1 -0
- package/dist/prompt/provider-prompts/glm.js +7 -0
- package/dist/prompt/provider-prompts/kimi.d.ts +1 -0
- package/dist/prompt/provider-prompts/kimi.js +7 -0
- package/dist/prompt/reminders.d.ts +2 -0
- package/dist/prompt/reminders.js +28 -2
- package/dist/prompt/runtime.js +15 -2
- package/dist/prompt/task-reminders.d.ts +2 -0
- package/dist/prompt/task-reminders.js +56 -0
- package/dist/slash-commands/commands.js +2 -3
- package/dist/tools/bash.js +10 -7
- package/dist/tools/edit.js +5 -0
- package/dist/tools/write.js +8 -1
- package/dist/tui/image-paste.d.ts +41 -0
- package/dist/tui/image-paste.js +217 -0
- package/dist/tui/run.js +102 -2
- package/package.json +3 -3
package/dist/bin.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const mainPath = fileURLToPath(new URL("./main.js", import.meta.url));
|
|
5
|
+
if (process.versions.bun) {
|
|
6
|
+
await import("./main.js");
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
10
|
+
if (bunCheck.error) {
|
|
11
|
+
console.error([
|
|
12
|
+
"Bubble requires Bun to run.",
|
|
13
|
+
"",
|
|
14
|
+
"Install Bun, then run bubble again:",
|
|
15
|
+
" curl -fsSL https://bun.sh/install | bash",
|
|
16
|
+
"",
|
|
17
|
+
"After installation, restart your terminal if the bun command is not found.",
|
|
18
|
+
].join("\n"));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const child = spawn("bun", [mainPath, ...process.argv.slice(2)], {
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
env: process.env,
|
|
24
|
+
});
|
|
25
|
+
child.on("error", (error) => {
|
|
26
|
+
console.error(`Failed to start Bubble with Bun: ${error.message}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
30
|
+
process.on(signal, () => {
|
|
31
|
+
if (!child.killed) {
|
|
32
|
+
child.kill(signal);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const result = await new Promise((resolve) => {
|
|
37
|
+
child.on("exit", (code, signal) => resolve({ code, signal }));
|
|
38
|
+
});
|
|
39
|
+
if (result.signal) {
|
|
40
|
+
process.kill(process.pid, result.signal);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
process.exit(result.code ?? 1);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/main.d.ts
CHANGED
package/dist/main.js
CHANGED
|
@@ -2,7 +2,8 @@ import { classifyTask } from "../agent/task-classifier.js";
|
|
|
2
2
|
import { EvidenceTracker } from "../agent/evidence-tracker.js";
|
|
3
3
|
import { ExecutionGovernor } from "../agent/execution-governor.js";
|
|
4
4
|
import { arbitrateToolCall } from "../agent/tool-arbiter.js";
|
|
5
|
-
import { buildTaskSummaryReminder, buildWorkflowPhaseReminder } from "../prompt/reminders.js";
|
|
5
|
+
import { buildTaskSummaryReminder, buildVerificationReminder, buildWorkflowPhaseReminder } from "../prompt/reminders.js";
|
|
6
|
+
import { reminderForTaskType } from "../prompt/task-reminders.js";
|
|
6
7
|
import { formatCoverageSummary, resolveWorkflowPhase } from "./workflow.js";
|
|
7
8
|
export function createDefaultHooks() {
|
|
8
9
|
return [
|
|
@@ -11,6 +12,10 @@ export function createDefaultHooks() {
|
|
|
11
12
|
const taskType = classifyTask(ctx.input);
|
|
12
13
|
ctx.state.taskType = taskType;
|
|
13
14
|
ctx.state.governor = new ExecutionGovernor(taskType);
|
|
15
|
+
const taskReminder = reminderForTaskType(taskType);
|
|
16
|
+
if (taskReminder) {
|
|
17
|
+
ctx.queueReminder(taskReminder);
|
|
18
|
+
}
|
|
14
19
|
if (taskType === "security_investigation") {
|
|
15
20
|
ctx.state.evidenceTracker = new EvidenceTracker();
|
|
16
21
|
ctx.state.workflowPhase = "investigate";
|
|
@@ -70,6 +75,12 @@ export function createDefaultHooks() {
|
|
|
70
75
|
}
|
|
71
76
|
ctx.state.evidenceTracker?.observe(ctx.toolCall, ctx.result);
|
|
72
77
|
ctx.state.governor?.afterToolResult(ctx.toolCall, ctx.result);
|
|
78
|
+
if (isCodeWriteResult(ctx.toolCall, ctx.result)) {
|
|
79
|
+
ctx.state.codeChanged = true;
|
|
80
|
+
}
|
|
81
|
+
else if (ctx.state.codeChanged && isVerificationResult(ctx.toolCall, ctx.result)) {
|
|
82
|
+
ctx.state.verificationCompleted = true;
|
|
83
|
+
}
|
|
73
84
|
if (ctx.toolCall.name === "task") {
|
|
74
85
|
ctx.queueReminder(buildTaskSummaryReminder());
|
|
75
86
|
}
|
|
@@ -90,7 +101,48 @@ export function createDefaultHooks() {
|
|
|
90
101
|
if (ctx.state.governor?.snapshot().searchFrozen && allSearchResultsWereLowSignal) {
|
|
91
102
|
ctx.requestTextOnlyTurn("Search continuation has become low-yield. Summarize the strongest evidence already collected instead of continuing broad exploration.");
|
|
92
103
|
}
|
|
104
|
+
const changedThisTurn = ctx.toolResults.some((result) => result.metadata?.kind === "write" || result.metadata?.kind === "edit");
|
|
105
|
+
if (changedThisTurn && !ctx.state.verificationCompleted && !ctx.state.verificationReminderQueued) {
|
|
106
|
+
ctx.state.verificationReminderQueued = true;
|
|
107
|
+
ctx.queueReminder(buildVerificationReminder("The previous turn changed files and no verification evidence has been observed yet."));
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
afterTurn(ctx) {
|
|
111
|
+
if (ctx.state.codeChanged && !ctx.state.verificationCompleted && !ctx.state.finalVerificationReminderSent) {
|
|
112
|
+
ctx.state.finalVerificationReminderSent = true;
|
|
113
|
+
ctx.state.forceContinuationReason = "Files were changed but no verification evidence was observed before the final answer.";
|
|
114
|
+
ctx.queueReminder(buildVerificationReminder(ctx.state.forceContinuationReason));
|
|
115
|
+
}
|
|
93
116
|
},
|
|
94
117
|
},
|
|
95
118
|
];
|
|
96
119
|
}
|
|
120
|
+
function isCodeWriteResult(_toolCall, result) {
|
|
121
|
+
if (result.isError || result.status === "blocked" || result.status === "command_error") {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return result.metadata?.kind === "write" || result.metadata?.kind === "edit";
|
|
125
|
+
}
|
|
126
|
+
function isVerificationResult(toolCall, result) {
|
|
127
|
+
if (result.isError || result.status === "blocked" || result.status === "command_error") {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (toolCall.name === "lsp") {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if (toolCall.name !== "bash") {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const command = typeof result.metadata?.command === "string"
|
|
137
|
+
? result.metadata.command
|
|
138
|
+
: typeof toolCall.parsedArgs.command === "string"
|
|
139
|
+
? toolCall.parsedArgs.command
|
|
140
|
+
: "";
|
|
141
|
+
return isVerificationCommand(command);
|
|
142
|
+
}
|
|
143
|
+
function isVerificationCommand(command) {
|
|
144
|
+
const normalized = command.trim().toLowerCase();
|
|
145
|
+
return /\b(npm|pnpm|yarn|bun)\s+(test|run\s+(test|build|typecheck|lint|check|tsc)|exec\s+tsc)\b/.test(normalized)
|
|
146
|
+
|| /\b(npx|pnpm\s+exec|bunx)\s+(vitest|tsc|eslint|playwright)\b/.test(normalized)
|
|
147
|
+
|| /\b(vitest|tsc|pytest|ruff|cargo\s+test|go\s+test|swift\s+test)\b/.test(normalized);
|
|
148
|
+
}
|
|
@@ -12,6 +12,11 @@ export interface TurnHookState {
|
|
|
12
12
|
workflowKey?: string;
|
|
13
13
|
turnCount?: number;
|
|
14
14
|
forceTextOnlyReason?: string;
|
|
15
|
+
forceContinuationReason?: string;
|
|
16
|
+
codeChanged?: boolean;
|
|
17
|
+
verificationCompleted?: boolean;
|
|
18
|
+
verificationReminderQueued?: boolean;
|
|
19
|
+
finalVerificationReminderSent?: boolean;
|
|
15
20
|
taskBudget?: {
|
|
16
21
|
total: number;
|
|
17
22
|
spent: number;
|
package/dist/prompt/compose.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { buildAnthropicProviderPrompt } from "./provider-prompts/anthropic.js";
|
|
2
2
|
import { buildCodexProviderPrompt } from "./provider-prompts/codex.js";
|
|
3
3
|
import { buildDefaultProviderPrompt } from "./provider-prompts/default.js";
|
|
4
|
+
import { buildDeepSeekProviderPrompt } from "./provider-prompts/deepseek.js";
|
|
4
5
|
import { buildGeminiProviderPrompt } from "./provider-prompts/gemini.js";
|
|
6
|
+
import { buildGlmProviderPrompt } from "./provider-prompts/glm.js";
|
|
5
7
|
import { buildGptProviderPrompt } from "./provider-prompts/gpt.js";
|
|
8
|
+
import { buildKimiProviderPrompt } from "./provider-prompts/kimi.js";
|
|
6
9
|
import { buildEnvironmentPrompt, defaultToolNames } from "./environment.js";
|
|
7
10
|
import { buildRuntimePrompt } from "./runtime.js";
|
|
8
11
|
import { buildSkillsPrompt } from "./skills.js";
|
|
@@ -39,6 +42,15 @@ function buildProviderPrompt(agentName, providerId, modelId, modelName) {
|
|
|
39
42
|
if (provider === "openai-codex" || model.includes("codex") || model.startsWith("gpt-5")) {
|
|
40
43
|
return buildCodexProviderPrompt(agentName);
|
|
41
44
|
}
|
|
45
|
+
if (provider === "deepseek" || model.startsWith("deepseek")) {
|
|
46
|
+
return buildDeepSeekProviderPrompt(agentName);
|
|
47
|
+
}
|
|
48
|
+
if (["moonshot-cn", "moonshot-intl", "kimi-for-coding"].includes(provider) || model.startsWith("kimi") || model.startsWith("k2.")) {
|
|
49
|
+
return buildKimiProviderPrompt(agentName);
|
|
50
|
+
}
|
|
51
|
+
if (["zhipuai", "zhipuai-coding-plan", "zai", "zai-coding-plan"].includes(provider) || model.startsWith("glm")) {
|
|
52
|
+
return buildGlmProviderPrompt(agentName);
|
|
53
|
+
}
|
|
42
54
|
if (provider === "openai" || provider === "openrouter" || model.startsWith("gpt") || model.startsWith("o1")) {
|
|
43
55
|
return buildGptProviderPrompt(agentName);
|
|
44
56
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildDeepSeekProviderPrompt(agentName: string): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function buildDeepSeekProviderPrompt(agentName) {
|
|
2
|
+
return `You are ${agentName}, a direct coding agent running on a DeepSeek model.
|
|
3
|
+
|
|
4
|
+
Prefer short plans followed by concrete tool use. Avoid broad speculation.
|
|
5
|
+
After each tool result, update your understanding before choosing the next action.
|
|
6
|
+
Do not repeat equivalent searches unless the previous result changed the search space.
|
|
7
|
+
When provider/API behavior is involved, inspect serialization and request-shape code before changing generic agent logic.`;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildGlmProviderPrompt(agentName: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function buildGlmProviderPrompt(agentName) {
|
|
2
|
+
return `You are ${agentName}, a pragmatic coding agent running on a GLM/Z.AI model.
|
|
3
|
+
|
|
4
|
+
Be specific and evidence-driven. Prefer source inspection and verification over generic recommendations.
|
|
5
|
+
When debugging, identify the failing boundary before editing.
|
|
6
|
+
When implementing, finish the requested change end to end and verify the relevant path.`;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildKimiProviderPrompt(agentName: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function buildKimiProviderPrompt(agentName) {
|
|
2
|
+
return `You are ${agentName}, a terminal coding agent running on a Kimi/Moonshot model.
|
|
3
|
+
|
|
4
|
+
Keep tool use disciplined: pursue one concrete hypothesis at a time, read results carefully, and converge after evidence is sufficient.
|
|
5
|
+
Do not fan out into many parallel search directions unless the task truly requires it.
|
|
6
|
+
For tool-call or reasoning-mode issues, inspect message history serialization before changing unrelated agent behavior.`;
|
|
7
|
+
}
|
|
@@ -22,6 +22,7 @@ export declare function buildDeferredToolsReminder(names: string[]): string;
|
|
|
22
22
|
export declare function buildInvestigationReminder(): string;
|
|
23
23
|
export declare function buildLoopWarningReminder(reason: string): string;
|
|
24
24
|
export declare function buildSearchFreezeReminder(reason: string): string;
|
|
25
|
+
export declare function buildExplorationFreezeReminder(reason: string): string;
|
|
25
26
|
export declare function buildToolFreezeReminder(reason: string): string;
|
|
26
27
|
export declare function buildWorkflowPhaseReminder(input: {
|
|
27
28
|
phase: "investigate" | "correlate" | "conclude";
|
|
@@ -29,3 +30,4 @@ export declare function buildWorkflowPhaseReminder(input: {
|
|
|
29
30
|
pending: string[];
|
|
30
31
|
}): string;
|
|
31
32
|
export declare function buildTaskSummaryReminder(): string;
|
|
33
|
+
export declare function buildVerificationReminder(reason: string): string;
|
package/dist/prompt/reminders.js
CHANGED
|
@@ -22,6 +22,7 @@ Plan mode is now ACTIVE.
|
|
|
22
22
|
Rules while in plan mode:
|
|
23
23
|
- Only read-only tools are allowed, including read, glob, grep, lsp, web_search, web_fetch, task, skill, todo_write, tool_search, question, and exit_plan_mode.
|
|
24
24
|
- Writes, edits, and shell commands WILL be rejected by the harness; do not try them.
|
|
25
|
+
- Do not edit files or claim implementation is complete while plan mode is active.
|
|
25
26
|
- Investigate the codebase, then use the question tool to clarify important ambiguities, tradeoffs, requirements, or preference choices that would materially change the plan.
|
|
26
27
|
- Call exit_plan_mode with a concrete step-by-step plan after the important questions are resolved.
|
|
27
28
|
- Do not use the question tool to ask whether the plan is approved; exit_plan_mode is the approval step.
|
|
@@ -34,11 +35,13 @@ Permission mode is now: bypassPermissions.
|
|
|
34
35
|
ALL tool calls auto-approve with no user confirmation. The user has explicitly opted into this.
|
|
35
36
|
Proceed with extra care — explain risky actions in the chat BEFORE performing them, and
|
|
36
37
|
prefer reversible operations when possible.
|
|
38
|
+
Do not perform destructive operations, credential exposure, or unrelated reversions just because approvals are bypassed.
|
|
37
39
|
`;
|
|
38
40
|
const DEFAULT_ENTER = `
|
|
39
41
|
Permission mode is now: default Build mode.
|
|
40
42
|
|
|
41
43
|
File edits and writes auto-approve. Bash commands and other destructive tools still require explicit approval unless allowed by rules.
|
|
44
|
+
Execute the requested change end to end; do not stop at analysis unless blocked or the user explicitly asks for discussion only.
|
|
42
45
|
`;
|
|
43
46
|
/** Picks the correct reminder text for a transition TO a given mode. */
|
|
44
47
|
export function reminderForMode(mode) {
|
|
@@ -86,11 +89,11 @@ Stop once these categories are covered. Do not keep repeating near-identical sea
|
|
|
86
89
|
}
|
|
87
90
|
export function buildLoopWarningReminder(reason) {
|
|
88
91
|
return wrapInSystemReminder(`
|
|
89
|
-
|
|
92
|
+
Tool loop warning.
|
|
90
93
|
|
|
91
94
|
${reason}
|
|
92
95
|
|
|
93
|
-
Do not repeat near-identical
|
|
96
|
+
Do not repeat near-identical reads or searches unless you are changing the path or testing a genuinely new hypothesis.
|
|
94
97
|
If current evidence is sufficient, summarize your findings now.
|
|
95
98
|
`);
|
|
96
99
|
}
|
|
@@ -104,6 +107,19 @@ Do not continue blind keyword searching. Use the evidence already gathered to re
|
|
|
104
107
|
You may still read specific files if you already know where the relevant configuration or persistence logic lives.
|
|
105
108
|
`);
|
|
106
109
|
}
|
|
110
|
+
export function buildExplorationFreezeReminder(reason) {
|
|
111
|
+
return wrapInSystemReminder(`
|
|
112
|
+
Implementation phase has advanced from exploration to modification.
|
|
113
|
+
|
|
114
|
+
Reason: ${reason}
|
|
115
|
+
|
|
116
|
+
You have enough context to act. Do not continue reading, searching, or delegating exploration.
|
|
117
|
+
Choose one of:
|
|
118
|
+
1. Use edit/write to make the requested change.
|
|
119
|
+
2. If no safe change can be made from the gathered context, explain the concrete blocker.
|
|
120
|
+
3. If files were already changed, run the narrowest meaningful verification or finish with the result.
|
|
121
|
+
`);
|
|
122
|
+
}
|
|
107
123
|
export function buildToolFreezeReminder(reason) {
|
|
108
124
|
return wrapInSystemReminder(`
|
|
109
125
|
CRITICAL - MAXIMUM STEPS REACHED
|
|
@@ -156,3 +172,13 @@ Treat the task output as a bounded subtask result:
|
|
|
156
172
|
- do not re-run the same exploratory search unless the subtask uncovered a concrete contradiction
|
|
157
173
|
`);
|
|
158
174
|
}
|
|
175
|
+
export function buildVerificationReminder(reason) {
|
|
176
|
+
return wrapInSystemReminder(`
|
|
177
|
+
Verification required before final answer.
|
|
178
|
+
|
|
179
|
+
${reason}
|
|
180
|
+
|
|
181
|
+
You have changed files in this turn. Run the narrowest meaningful verification command or runtime check before finalizing.
|
|
182
|
+
If verification truly cannot be run, state the concrete blocker and the residual risk.
|
|
183
|
+
`);
|
|
184
|
+
}
|
package/dist/prompt/runtime.js
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
const defaultGuidelines = [
|
|
2
|
+
"Inspect relevant files, command output, or runtime state before making claims about code behavior",
|
|
3
|
+
"Separate confirmed facts from inference when the evidence is incomplete",
|
|
4
|
+
"Prefer runtime and call-chain evidence over README text or configuration names for behavior questions",
|
|
2
5
|
"Before editing or writing files, read them first if they exist",
|
|
3
6
|
"Use edit for targeted changes to existing files; use write for creating new files",
|
|
4
|
-
"
|
|
5
|
-
"Show file paths clearly when working with files",
|
|
7
|
+
"Edit only the files required for the requested change",
|
|
6
8
|
"Prefer structured search tools over bash for repository searches whenever possible",
|
|
7
9
|
"Do not repeat near-identical searches when they are not producing new evidence",
|
|
8
10
|
"When investigating configuration or security questions, stop once the relevant load path, storage path, and exposure path are identified",
|
|
9
11
|
"Use the task tool for bounded investigative subproblems instead of letting the main loop churn on repeated exploratory searches",
|
|
12
|
+
"After code edits, run the narrowest meaningful verification command or explain why verification is not possible",
|
|
13
|
+
"When finishing a coding task, report what changed, where it changed, verification results, and remaining risk",
|
|
14
|
+
"Be concise in your responses",
|
|
10
15
|
];
|
|
11
16
|
export function buildRuntimePrompt(options = {}) {
|
|
12
17
|
const thinkingLevel = options.thinkingLevel ?? "off";
|
|
13
18
|
const guidelines = dedupe(defaultGuidelines, options.guidelines ?? []);
|
|
14
19
|
return `Current thinking level: ${thinkingLevel}
|
|
15
20
|
|
|
21
|
+
Execution protocol:
|
|
22
|
+
1. Understand the user's requested outcome and current constraints.
|
|
23
|
+
2. Inspect the relevant files or state before making claims or edits.
|
|
24
|
+
3. Choose the smallest coherent change that solves the actual problem.
|
|
25
|
+
4. Edit only the necessary files.
|
|
26
|
+
5. Verify with the narrowest meaningful command or runtime check when possible.
|
|
27
|
+
6. Finish with changed files, verification results, and unresolved risk.
|
|
28
|
+
|
|
16
29
|
Guidelines:
|
|
17
30
|
${guidelines.map((item) => `- ${item}`).join("\n")}`;
|
|
18
31
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { wrapInSystemReminder } from "./reminders.js";
|
|
2
|
+
export function reminderForTaskType(taskType) {
|
|
3
|
+
switch (taskType) {
|
|
4
|
+
case "debugging":
|
|
5
|
+
return wrapInSystemReminder(`
|
|
6
|
+
Debugging workflow:
|
|
7
|
+
- Reproduce or identify the failing boundary before editing.
|
|
8
|
+
- Trace input, transformation, and output paths.
|
|
9
|
+
- Prefer fixing the mechanism over raising thresholds or adding superficial fallbacks.
|
|
10
|
+
- Verify the specific failure path after the change.
|
|
11
|
+
`);
|
|
12
|
+
case "implementation":
|
|
13
|
+
return wrapInSystemReminder(`
|
|
14
|
+
Implementation workflow:
|
|
15
|
+
- Do not stop at a proposal when the user asked for a change.
|
|
16
|
+
- Inspect the relevant files first, then make the smallest coherent edit.
|
|
17
|
+
- Keep unrelated files and behavior out of scope.
|
|
18
|
+
- Run a narrow verification command or explain why it cannot be run.
|
|
19
|
+
`);
|
|
20
|
+
case "code_review":
|
|
21
|
+
return wrapInSystemReminder(`
|
|
22
|
+
Code review workflow:
|
|
23
|
+
- Lead with concrete findings, ordered by severity.
|
|
24
|
+
- Reference file paths and line numbers when possible.
|
|
25
|
+
- Prioritize bugs, regressions, missing tests, security, and user-visible risk.
|
|
26
|
+
- Keep summaries secondary to findings.
|
|
27
|
+
`);
|
|
28
|
+
case "code_explanation":
|
|
29
|
+
return wrapInSystemReminder(`
|
|
30
|
+
Code explanation workflow:
|
|
31
|
+
- Answer the direct question first.
|
|
32
|
+
- Ground claims in concrete files, functions, and call paths.
|
|
33
|
+
- Distinguish current source evidence from inference.
|
|
34
|
+
- Avoid proposing changes unless the user asks for them.
|
|
35
|
+
`);
|
|
36
|
+
case "repo_orientation":
|
|
37
|
+
return wrapInSystemReminder(`
|
|
38
|
+
Repository orientation workflow:
|
|
39
|
+
- Start with the repo purpose and main execution paths.
|
|
40
|
+
- Inspect README/package metadata plus core runtime files before summarizing.
|
|
41
|
+
- Keep the first pass read-only unless the user asks for changes or runtime verification.
|
|
42
|
+
`);
|
|
43
|
+
case "product_discussion":
|
|
44
|
+
return wrapInSystemReminder(`
|
|
45
|
+
Product discussion workflow:
|
|
46
|
+
- Clarify the product goal, user workflow, and tradeoffs before suggesting implementation.
|
|
47
|
+
- Give direct product judgment when the user asks for direction.
|
|
48
|
+
- Avoid drifting into code changes unless the user explicitly asks to execute.
|
|
49
|
+
`);
|
|
50
|
+
case "security_investigation":
|
|
51
|
+
case "code_search":
|
|
52
|
+
case "general":
|
|
53
|
+
default:
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -157,7 +157,7 @@ async function handleMemoryCommand(args, ctx) {
|
|
|
157
157
|
return lines.join("\n");
|
|
158
158
|
}
|
|
159
159
|
if (sub === "add") {
|
|
160
|
-
return "Manual memory writes are disabled. Bubble now follows the
|
|
160
|
+
return "Manual memory writes are disabled. Bubble now follows the automatic startup memory pipeline.";
|
|
161
161
|
}
|
|
162
162
|
if (sub === "search") {
|
|
163
163
|
const query = rest.join(" ").trim();
|
|
@@ -265,7 +265,7 @@ const builtinSlashCommandEntries = [
|
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
name: "memory",
|
|
268
|
-
description: "Inspect and maintain
|
|
268
|
+
description: "Inspect and maintain Bubble's automatic persistent memory. Usage: /memory [status|search|compact|summarize|refresh|reset]",
|
|
269
269
|
async handler(args, ctx) {
|
|
270
270
|
return handleMemoryCommand(args, ctx);
|
|
271
271
|
},
|
|
@@ -737,7 +737,6 @@ const builtinSlashCommandEntries = [
|
|
|
737
737
|
return "Session is already compact enough.";
|
|
738
738
|
}
|
|
739
739
|
const systemMessage = ctx.agent.messages.find((message) => message.role === "system");
|
|
740
|
-
ctx.clearMessages();
|
|
741
740
|
ctx.agent.messages = [
|
|
742
741
|
...(systemMessage ? [systemMessage] : []),
|
|
743
742
|
...ctx.sessionManager.getMessages(),
|
package/dist/tools/bash.js
CHANGED
|
@@ -5,7 +5,7 @@ import { spawn } from "node:child_process";
|
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { platform } from "node:os";
|
|
7
7
|
import { gateToolAction } from "../approval/tool-helper.js";
|
|
8
|
-
import { parseSearchBashCommand } from "../agent/tool-intent.js";
|
|
8
|
+
import { parseReadBashCommand, parseSearchBashCommand } from "../agent/tool-intent.js";
|
|
9
9
|
import { referencesSensitivePath } from "./sensitive-paths.js";
|
|
10
10
|
const MAX_OUTPUT = 50 * 1024;
|
|
11
11
|
export function createBashTool(cwd, approval) {
|
|
@@ -27,6 +27,7 @@ export function createBashTool(cwd, approval) {
|
|
|
27
27
|
const command = String(args.command);
|
|
28
28
|
const timeoutSec = typeof args.timeout === "number" ? args.timeout : 60;
|
|
29
29
|
const parsedSearch = parseSearchBashCommand(command);
|
|
30
|
+
const parsedRead = parsedSearch ? undefined : parseReadBashCommand(command);
|
|
30
31
|
if (referencesSensitivePath(command)) {
|
|
31
32
|
return {
|
|
32
33
|
content: "Error: Bash access to sensitive credential storage is blocked.",
|
|
@@ -95,9 +96,9 @@ export function createBashTool(cwd, approval) {
|
|
|
95
96
|
isError: true,
|
|
96
97
|
status: "timeout",
|
|
97
98
|
metadata: {
|
|
98
|
-
kind: parsedSearch ? "search" : "shell",
|
|
99
|
+
kind: parsedSearch ? "search" : parsedRead ? "read" : "shell",
|
|
99
100
|
pattern: parsedSearch?.pattern,
|
|
100
|
-
path: parsedSearch?.path,
|
|
101
|
+
path: parsedSearch?.path ?? parsedRead?.path,
|
|
101
102
|
},
|
|
102
103
|
});
|
|
103
104
|
return;
|
|
@@ -109,9 +110,9 @@ export function createBashTool(cwd, approval) {
|
|
|
109
110
|
isError: true,
|
|
110
111
|
status: "blocked",
|
|
111
112
|
metadata: {
|
|
112
|
-
kind: parsedSearch ? "search" : "shell",
|
|
113
|
+
kind: parsedSearch ? "search" : parsedRead ? "read" : "shell",
|
|
113
114
|
pattern: parsedSearch?.pattern,
|
|
114
|
-
path: parsedSearch?.path,
|
|
115
|
+
path: parsedSearch?.path ?? parsedRead?.path,
|
|
115
116
|
reason: "cancelled",
|
|
116
117
|
},
|
|
117
118
|
});
|
|
@@ -131,6 +132,7 @@ export function createBashTool(cwd, approval) {
|
|
|
131
132
|
kind: "search",
|
|
132
133
|
pattern: parsedSearch.pattern,
|
|
133
134
|
path: parsedSearch.path,
|
|
135
|
+
command,
|
|
134
136
|
matches: 0,
|
|
135
137
|
},
|
|
136
138
|
});
|
|
@@ -142,9 +144,10 @@ export function createBashTool(cwd, approval) {
|
|
|
142
144
|
isError,
|
|
143
145
|
status: isError ? "command_error" : "success",
|
|
144
146
|
metadata: {
|
|
145
|
-
kind: parsedSearch ? "search" : "shell",
|
|
147
|
+
kind: parsedSearch ? "search" : parsedRead ? "read" : "shell",
|
|
146
148
|
pattern: parsedSearch?.pattern,
|
|
147
|
-
path: parsedSearch?.path,
|
|
149
|
+
path: parsedSearch?.path ?? parsedRead?.path,
|
|
150
|
+
command,
|
|
148
151
|
matches: parsedSearch ? countSearchMatches(stdout) : undefined,
|
|
149
152
|
},
|
|
150
153
|
});
|
package/dist/tools/edit.js
CHANGED
package/dist/tools/write.js
CHANGED
|
@@ -67,7 +67,14 @@ export function createWriteTool(cwd, options = {}, approval, lsp) {
|
|
|
67
67
|
// LSP diagnostics should not turn a successful write into a failed tool call.
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
return {
|
|
70
|
+
return {
|
|
71
|
+
content,
|
|
72
|
+
status: "success",
|
|
73
|
+
metadata: {
|
|
74
|
+
kind: "write",
|
|
75
|
+
path: filePath,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
71
78
|
}
|
|
72
79
|
catch (err) {
|
|
73
80
|
return { content: `Error: ${err.message}`, isError: true };
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* TemporaryItems path and the clipboard — the path often gets cleaned up before
|
|
8
8
|
* we can read it, so we fall back to the clipboard.
|
|
9
9
|
*/
|
|
10
|
+
import type { ContentPart } from "../types.js";
|
|
10
11
|
export interface ImageAttachment {
|
|
11
12
|
base64: string;
|
|
12
13
|
mediaType: string;
|
|
@@ -17,7 +18,41 @@ export interface ImageAttachment {
|
|
|
17
18
|
filename?: string;
|
|
18
19
|
sourcePath?: string;
|
|
19
20
|
}
|
|
21
|
+
export interface ImagePathToken {
|
|
22
|
+
rawPath: string;
|
|
23
|
+
start: number;
|
|
24
|
+
end: number;
|
|
25
|
+
}
|
|
26
|
+
export interface ImageInputResolution {
|
|
27
|
+
actualInput: string | ContentPart[];
|
|
28
|
+
displayInput: string;
|
|
29
|
+
errors: string[];
|
|
30
|
+
attachments: ImageAttachment[];
|
|
31
|
+
imagePathCount: number;
|
|
32
|
+
}
|
|
33
|
+
export interface LabeledImageAttachment extends ImageAttachment {
|
|
34
|
+
label: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ComposerImageResolution {
|
|
37
|
+
text: string;
|
|
38
|
+
attachments: LabeledImageAttachment[];
|
|
39
|
+
errors: string[];
|
|
40
|
+
imagePathCount: number;
|
|
41
|
+
nextLabelIndex: number;
|
|
42
|
+
}
|
|
20
43
|
export declare function isImageFilePath(raw: string): boolean;
|
|
44
|
+
export declare function extractImagePathTokens(input: string): ImagePathToken[];
|
|
45
|
+
export declare function removeImagePathTokens(input: string, tokens: ImagePathToken[]): string;
|
|
46
|
+
export declare function imageAttachmentLabel(att: ImageAttachment, index: number): string;
|
|
47
|
+
export declare function imageAttachmentReference(att: ImageAttachment, index: number): string;
|
|
48
|
+
export declare function imageAttachmentLabelPattern(): RegExp;
|
|
49
|
+
export declare function buildImageContentParts(promptText: string, attachments: ImageAttachment[]): ContentPart[];
|
|
50
|
+
export declare function formatImageDisplayInput(promptText: string, attachments: ImageAttachment[], labelStart?: number): string;
|
|
51
|
+
export declare function buildImageContentPartsFromLabels(input: string, attachmentsByLabel: Map<string, ImageAttachment>): {
|
|
52
|
+
actualInput?: ContentPart[];
|
|
53
|
+
displayInput: string;
|
|
54
|
+
usedLabels: string[];
|
|
55
|
+
};
|
|
21
56
|
/**
|
|
22
57
|
* Split a pasted blob into candidate path tokens.
|
|
23
58
|
*
|
|
@@ -52,3 +87,9 @@ export declare function ingestClipboardImage(): Promise<{
|
|
|
52
87
|
attachment?: ImageAttachment;
|
|
53
88
|
error?: string;
|
|
54
89
|
}>;
|
|
90
|
+
export declare function resolveImageInput(input: string, options?: {
|
|
91
|
+
labelStart?: number;
|
|
92
|
+
}): Promise<ImageInputResolution>;
|
|
93
|
+
export declare function resolveComposerImagePaths(input: string, options?: {
|
|
94
|
+
labelStart?: number;
|
|
95
|
+
}): Promise<ComposerImageResolution>;
|