@bubblebrain-ai/bubble 0.0.1

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 (248) hide show
  1. package/README.md +70 -0
  2. package/dist/agent/evidence-tracker.d.ts +15 -0
  3. package/dist/agent/evidence-tracker.js +93 -0
  4. package/dist/agent/execution-governor.d.ts +30 -0
  5. package/dist/agent/execution-governor.js +169 -0
  6. package/dist/agent/subtask-policy.d.ts +14 -0
  7. package/dist/agent/subtask-policy.js +60 -0
  8. package/dist/agent/task-classifier.d.ts +3 -0
  9. package/dist/agent/task-classifier.js +36 -0
  10. package/dist/agent/tool-arbiter.d.ts +7 -0
  11. package/dist/agent/tool-arbiter.js +33 -0
  12. package/dist/agent/tool-intent.d.ts +20 -0
  13. package/dist/agent/tool-intent.js +176 -0
  14. package/dist/agent.d.ts +95 -0
  15. package/dist/agent.js +672 -0
  16. package/dist/approval/controller.d.ts +48 -0
  17. package/dist/approval/controller.js +78 -0
  18. package/dist/approval/danger.d.ts +13 -0
  19. package/dist/approval/danger.js +55 -0
  20. package/dist/approval/diff-hunks.d.ts +12 -0
  21. package/dist/approval/diff-hunks.js +32 -0
  22. package/dist/approval/session-cache.d.ts +35 -0
  23. package/dist/approval/session-cache.js +68 -0
  24. package/dist/approval/tool-helper.d.ts +14 -0
  25. package/dist/approval/tool-helper.js +32 -0
  26. package/dist/approval/types.d.ts +56 -0
  27. package/dist/approval/types.js +8 -0
  28. package/dist/bubble-home.d.ts +8 -0
  29. package/dist/bubble-home.js +19 -0
  30. package/dist/cli.d.ts +19 -0
  31. package/dist/cli.js +82 -0
  32. package/dist/config.d.ts +41 -0
  33. package/dist/config.js +144 -0
  34. package/dist/context/budget.d.ts +21 -0
  35. package/dist/context/budget.js +72 -0
  36. package/dist/context/compact-llm.d.ts +16 -0
  37. package/dist/context/compact-llm.js +132 -0
  38. package/dist/context/compact.d.ts +15 -0
  39. package/dist/context/compact.js +251 -0
  40. package/dist/context/overflow.d.ts +9 -0
  41. package/dist/context/overflow.js +46 -0
  42. package/dist/context/projector.d.ts +26 -0
  43. package/dist/context/projector.js +150 -0
  44. package/dist/context/prune.d.ts +9 -0
  45. package/dist/context/prune.js +111 -0
  46. package/dist/lsp/config.d.ts +18 -0
  47. package/dist/lsp/config.js +58 -0
  48. package/dist/lsp/diagnostics.d.ts +24 -0
  49. package/dist/lsp/diagnostics.js +103 -0
  50. package/dist/lsp/index.d.ts +3 -0
  51. package/dist/lsp/index.js +3 -0
  52. package/dist/lsp/service.d.ts +85 -0
  53. package/dist/lsp/service.js +695 -0
  54. package/dist/main.d.ts +5 -0
  55. package/dist/main.js +352 -0
  56. package/dist/mcp/client.d.ts +68 -0
  57. package/dist/mcp/client.js +163 -0
  58. package/dist/mcp/config.d.ts +26 -0
  59. package/dist/mcp/config.js +127 -0
  60. package/dist/mcp/manager.d.ts +55 -0
  61. package/dist/mcp/manager.js +296 -0
  62. package/dist/mcp/name.d.ts +26 -0
  63. package/dist/mcp/name.js +40 -0
  64. package/dist/mcp/transports.d.ts +53 -0
  65. package/dist/mcp/transports.js +248 -0
  66. package/dist/mcp/types.d.ts +111 -0
  67. package/dist/mcp/types.js +14 -0
  68. package/dist/memory/db.d.ts +62 -0
  69. package/dist/memory/db.js +313 -0
  70. package/dist/memory/index.d.ts +9 -0
  71. package/dist/memory/index.js +9 -0
  72. package/dist/memory/paths.d.ts +18 -0
  73. package/dist/memory/paths.js +38 -0
  74. package/dist/memory/phase1.d.ts +23 -0
  75. package/dist/memory/phase1.js +172 -0
  76. package/dist/memory/phase2.d.ts +19 -0
  77. package/dist/memory/phase2.js +100 -0
  78. package/dist/memory/prompts.d.ts +19 -0
  79. package/dist/memory/prompts.js +99 -0
  80. package/dist/memory/reset.d.ts +1 -0
  81. package/dist/memory/reset.js +13 -0
  82. package/dist/memory/start.d.ts +24 -0
  83. package/dist/memory/start.js +50 -0
  84. package/dist/memory/storage.d.ts +10 -0
  85. package/dist/memory/storage.js +82 -0
  86. package/dist/memory/store.d.ts +43 -0
  87. package/dist/memory/store.js +193 -0
  88. package/dist/memory/usage.d.ts +1 -0
  89. package/dist/memory/usage.js +38 -0
  90. package/dist/model-catalog.d.ts +20 -0
  91. package/dist/model-catalog.js +99 -0
  92. package/dist/model-config.d.ts +32 -0
  93. package/dist/model-config.js +59 -0
  94. package/dist/model-pricing.d.ts +23 -0
  95. package/dist/model-pricing.js +46 -0
  96. package/dist/oauth/index.d.ts +3 -0
  97. package/dist/oauth/index.js +2 -0
  98. package/dist/oauth/openai-codex.d.ts +9 -0
  99. package/dist/oauth/openai-codex.js +173 -0
  100. package/dist/oauth/storage.d.ts +18 -0
  101. package/dist/oauth/storage.js +60 -0
  102. package/dist/oauth/types.d.ts +15 -0
  103. package/dist/oauth/types.js +1 -0
  104. package/dist/orchestrator/default-hooks.d.ts +2 -0
  105. package/dist/orchestrator/default-hooks.js +96 -0
  106. package/dist/orchestrator/hooks.d.ts +78 -0
  107. package/dist/orchestrator/hooks.js +52 -0
  108. package/dist/orchestrator/workflow.d.ts +10 -0
  109. package/dist/orchestrator/workflow.js +22 -0
  110. package/dist/permission/mode.d.ts +23 -0
  111. package/dist/permission/mode.js +20 -0
  112. package/dist/permissions/rule.d.ts +39 -0
  113. package/dist/permissions/rule.js +234 -0
  114. package/dist/permissions/settings.d.ts +71 -0
  115. package/dist/permissions/settings.js +202 -0
  116. package/dist/permissions/types.d.ts +61 -0
  117. package/dist/permissions/types.js +14 -0
  118. package/dist/prompt/compose.d.ts +12 -0
  119. package/dist/prompt/compose.js +67 -0
  120. package/dist/prompt/environment.d.ts +12 -0
  121. package/dist/prompt/environment.js +38 -0
  122. package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
  123. package/dist/prompt/provider-prompts/anthropic.js +5 -0
  124. package/dist/prompt/provider-prompts/codex.d.ts +1 -0
  125. package/dist/prompt/provider-prompts/codex.js +5 -0
  126. package/dist/prompt/provider-prompts/default.d.ts +1 -0
  127. package/dist/prompt/provider-prompts/default.js +6 -0
  128. package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
  129. package/dist/prompt/provider-prompts/gemini.js +5 -0
  130. package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
  131. package/dist/prompt/provider-prompts/gpt.js +5 -0
  132. package/dist/prompt/reminders.d.ts +30 -0
  133. package/dist/prompt/reminders.js +164 -0
  134. package/dist/prompt/runtime.d.ts +12 -0
  135. package/dist/prompt/runtime.js +31 -0
  136. package/dist/prompt/skills.d.ts +2 -0
  137. package/dist/prompt/skills.js +4 -0
  138. package/dist/provider-openai-codex.d.ts +14 -0
  139. package/dist/provider-openai-codex.js +409 -0
  140. package/dist/provider-registry.d.ts +56 -0
  141. package/dist/provider-registry.js +244 -0
  142. package/dist/provider-transform.d.ts +10 -0
  143. package/dist/provider-transform.js +69 -0
  144. package/dist/provider.d.ts +31 -0
  145. package/dist/provider.js +269 -0
  146. package/dist/question/controller.d.ts +22 -0
  147. package/dist/question/controller.js +97 -0
  148. package/dist/question/index.d.ts +2 -0
  149. package/dist/question/index.js +2 -0
  150. package/dist/question/types.d.ts +42 -0
  151. package/dist/question/types.js +6 -0
  152. package/dist/session-log.d.ts +16 -0
  153. package/dist/session-log.js +267 -0
  154. package/dist/session-types.d.ts +55 -0
  155. package/dist/session-types.js +1 -0
  156. package/dist/session.d.ts +32 -0
  157. package/dist/session.js +135 -0
  158. package/dist/skills/discovery.d.ts +12 -0
  159. package/dist/skills/discovery.js +148 -0
  160. package/dist/skills/format.d.ts +2 -0
  161. package/dist/skills/format.js +47 -0
  162. package/dist/skills/frontmatter.d.ts +5 -0
  163. package/dist/skills/frontmatter.js +60 -0
  164. package/dist/skills/invocation.d.ts +8 -0
  165. package/dist/skills/invocation.js +51 -0
  166. package/dist/skills/registry.d.ts +17 -0
  167. package/dist/skills/registry.js +42 -0
  168. package/dist/skills/types.d.ts +32 -0
  169. package/dist/skills/types.js +1 -0
  170. package/dist/slash-commands/commands.d.ts +7 -0
  171. package/dist/slash-commands/commands.js +779 -0
  172. package/dist/slash-commands/index.d.ts +4 -0
  173. package/dist/slash-commands/index.js +8 -0
  174. package/dist/slash-commands/registry.d.ts +31 -0
  175. package/dist/slash-commands/registry.js +70 -0
  176. package/dist/slash-commands/types.d.ts +44 -0
  177. package/dist/slash-commands/types.js +1 -0
  178. package/dist/slash-commands/unified.d.ts +38 -0
  179. package/dist/slash-commands/unified.js +38 -0
  180. package/dist/system-prompt.d.ts +34 -0
  181. package/dist/system-prompt.js +7 -0
  182. package/dist/tools/bash.d.ts +6 -0
  183. package/dist/tools/bash.js +135 -0
  184. package/dist/tools/edit.d.ts +16 -0
  185. package/dist/tools/edit.js +95 -0
  186. package/dist/tools/exa-mcp.d.ts +3 -0
  187. package/dist/tools/exa-mcp.js +74 -0
  188. package/dist/tools/exit-plan-mode.d.ts +17 -0
  189. package/dist/tools/exit-plan-mode.js +68 -0
  190. package/dist/tools/glob.d.ts +5 -0
  191. package/dist/tools/glob.js +129 -0
  192. package/dist/tools/grep.d.ts +5 -0
  193. package/dist/tools/grep.js +111 -0
  194. package/dist/tools/index.d.ts +36 -0
  195. package/dist/tools/index.js +59 -0
  196. package/dist/tools/lsp.d.ts +4 -0
  197. package/dist/tools/lsp.js +92 -0
  198. package/dist/tools/memory.d.ts +3 -0
  199. package/dist/tools/memory.js +90 -0
  200. package/dist/tools/question.d.ts +3 -0
  201. package/dist/tools/question.js +174 -0
  202. package/dist/tools/read.d.ts +7 -0
  203. package/dist/tools/read.js +83 -0
  204. package/dist/tools/sensitive-paths.d.ts +3 -0
  205. package/dist/tools/sensitive-paths.js +24 -0
  206. package/dist/tools/skill.d.ts +5 -0
  207. package/dist/tools/skill.js +51 -0
  208. package/dist/tools/task.d.ts +2 -0
  209. package/dist/tools/task.js +57 -0
  210. package/dist/tools/todo.d.ts +12 -0
  211. package/dist/tools/todo.js +151 -0
  212. package/dist/tools/tool-search.d.ts +23 -0
  213. package/dist/tools/tool-search.js +124 -0
  214. package/dist/tools/web-fetch.d.ts +6 -0
  215. package/dist/tools/web-fetch.js +75 -0
  216. package/dist/tools/web-search.d.ts +5 -0
  217. package/dist/tools/web-search.js +49 -0
  218. package/dist/tools/write.d.ts +11 -0
  219. package/dist/tools/write.js +77 -0
  220. package/dist/tui/display-history.d.ts +35 -0
  221. package/dist/tui/display-history.js +243 -0
  222. package/dist/tui/file-mentions.d.ts +29 -0
  223. package/dist/tui/file-mentions.js +174 -0
  224. package/dist/tui/image-paste.d.ts +54 -0
  225. package/dist/tui/image-paste.js +288 -0
  226. package/dist/tui/markdown-theme-rules.d.ts +23 -0
  227. package/dist/tui/markdown-theme-rules.js +164 -0
  228. package/dist/tui/markdown-theme.d.ts +5 -0
  229. package/dist/tui/markdown-theme.js +27 -0
  230. package/dist/tui/opencode-spinner.d.ts +21 -0
  231. package/dist/tui/opencode-spinner.js +216 -0
  232. package/dist/tui/prompt-keybindings.d.ts +41 -0
  233. package/dist/tui/prompt-keybindings.js +28 -0
  234. package/dist/tui/recent-activity.d.ts +8 -0
  235. package/dist/tui/recent-activity.js +71 -0
  236. package/dist/tui/run.d.ts +39 -0
  237. package/dist/tui/run.js +5696 -0
  238. package/dist/tui/sidebar-mcp.d.ts +31 -0
  239. package/dist/tui/sidebar-mcp.js +62 -0
  240. package/dist/tui/sidebar-state.d.ts +12 -0
  241. package/dist/tui/sidebar-state.js +69 -0
  242. package/dist/types.d.ts +219 -0
  243. package/dist/types.js +4 -0
  244. package/dist/variant/thinking-level.d.ts +5 -0
  245. package/dist/variant/thinking-level.js +25 -0
  246. package/dist/variant/variant-resolver.d.ts +4 -0
  247. package/dist/variant/variant-resolver.js +12 -0
  248. package/package.json +47 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * exit_plan_mode tool - proposes a plan, awaits user approval, and (on approve) flips the
3
+ * agent out of plan mode so subsequent turns may execute it.
4
+ *
5
+ * The tool is read-only from the agent's perspective; the side effects (prompting the user,
6
+ * flipping mode) happen via the injected PlanController.
7
+ */
8
+ export function createExitPlanModeTool(controller) {
9
+ return {
10
+ name: "exit_plan_mode",
11
+ readOnly: true,
12
+ description: "ONLY call this tool when the harness has told you (via a <system-reminder>) that plan mode is ACTIVE. " +
13
+ "Do NOT call it during ordinary work — in default mode you should just use the regular tools directly. " +
14
+ "In plan mode: after investigating, call this with a concrete step-by-step plan so the user can approve, edit, or reject. " +
15
+ "Approval automatically switches the agent out of plan mode.",
16
+ parameters: {
17
+ type: "object",
18
+ properties: {
19
+ plan: {
20
+ type: "string",
21
+ description: "The plan to present to the user. Should be concrete, step-by-step, and cover all changes you intend to make.",
22
+ },
23
+ },
24
+ required: ["plan"],
25
+ },
26
+ async execute(args) {
27
+ // Hard gate: this tool is a no-op outside plan mode. Without this check some
28
+ // models call it during normal work (misled by the word "plan" in the schema),
29
+ // which pops a confusing approval dialog to the user.
30
+ if (controller.getMode() !== "plan") {
31
+ return {
32
+ content: "Error: exit_plan_mode is only valid while plan mode is active. " +
33
+ "You are currently NOT in plan mode — proceed with the user's request directly using the regular tools.",
34
+ isError: true,
35
+ };
36
+ }
37
+ const plan = typeof args.plan === "string" ? args.plan.trim() : "";
38
+ if (!plan) {
39
+ return { content: "Error: plan is required and must be a non-empty string", isError: true };
40
+ }
41
+ let decision;
42
+ try {
43
+ decision = await controller.requestApproval(plan);
44
+ }
45
+ catch (err) {
46
+ return {
47
+ content: `Error requesting plan approval: ${err?.message || String(err)}`,
48
+ isError: true,
49
+ };
50
+ }
51
+ if (decision.action === "approve") {
52
+ controller.setMode("default");
53
+ const finalPlan = decision.plan.trim() || plan;
54
+ const edited = finalPlan !== plan;
55
+ return {
56
+ content: `User approved the plan${edited ? " (with edits)" : ""}. ` +
57
+ `Agent mode has been switched to default — you may now execute the plan using any tools. ` +
58
+ `Approved plan:\n\n${finalPlan}`,
59
+ };
60
+ }
61
+ const reason = decision.reason?.trim();
62
+ return {
63
+ content: `User rejected the plan. Remain in plan mode and revise your approach. ` +
64
+ (reason ? `Reason: ${reason}` : "No reason provided; ask for clarification or gather more context."),
65
+ };
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Glob tool - discover files by path pattern without shell access.
3
+ */
4
+ import type { ToolRegistryEntry } from "../types.js";
5
+ export declare function createGlobTool(cwd: string): ToolRegistryEntry;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Glob tool - discover files by path pattern without shell access.
3
+ */
4
+ import { readdir, stat } from "node:fs/promises";
5
+ import { relative, resolve } from "node:path";
6
+ import picomatch from "picomatch";
7
+ import { isSensitivePath } from "./sensitive-paths.js";
8
+ const MAX_RESULTS = 100;
9
+ const DEFAULT_IGNORES = new Set([
10
+ ".git",
11
+ "node_modules",
12
+ "dist",
13
+ "build",
14
+ ".next",
15
+ ".turbo",
16
+ ".cache",
17
+ "coverage",
18
+ ]);
19
+ export function createGlobTool(cwd) {
20
+ return {
21
+ name: "glob",
22
+ readOnly: true,
23
+ description: `Find files by glob pattern without using the shell. Use this for project structure discovery and filename searches. Returns up to ${MAX_RESULTS} files sorted by recent modification time.`,
24
+ parameters: {
25
+ type: "object",
26
+ properties: {
27
+ pattern: { type: "string", description: "Glob pattern to match files, e.g. '**/*', '**/*.ts', 'src/**/*.tsx'" },
28
+ path: { type: "string", description: "Directory to search in (optional, default: cwd)" },
29
+ },
30
+ required: ["pattern"],
31
+ },
32
+ async execute(args, ctx) {
33
+ const root = resolve(cwd, typeof args.path === "string" && args.path.trim() ? args.path : ".");
34
+ const pattern = String(args.pattern || "").trim();
35
+ if (!pattern) {
36
+ return { content: "Error: glob pattern is required", isError: true, status: "command_error" };
37
+ }
38
+ if (isSensitivePath(root)) {
39
+ return {
40
+ content: `Error: Glob blocked for sensitive credential storage: ${root}`,
41
+ isError: true,
42
+ status: "blocked",
43
+ metadata: {
44
+ kind: "security",
45
+ path: root,
46
+ pattern,
47
+ reason: "Sensitive credential storage is not searchable from general-purpose tasks.",
48
+ },
49
+ };
50
+ }
51
+ const matcher = picomatch(pattern, { dot: true });
52
+ const files = [];
53
+ const truncated = { value: false };
54
+ try {
55
+ const rootStat = await stat(root);
56
+ if (!rootStat.isDirectory()) {
57
+ return { content: `Error: Path is not a directory: ${root}`, isError: true, status: "command_error" };
58
+ }
59
+ await walk(root, root, matcher, files, truncated, ctx.abortSignal);
60
+ }
61
+ catch (error) {
62
+ return { content: `Error: Cannot glob path: ${root} (${error?.message || String(error)})`, isError: true, status: "command_error" };
63
+ }
64
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs || a.path.localeCompare(b.path));
65
+ const matches = files.slice(0, MAX_RESULTS).map((item) => item.path);
66
+ const wasTruncated = truncated.value || files.length > MAX_RESULTS;
67
+ if (matches.length === 0) {
68
+ return {
69
+ content: "No files found.",
70
+ status: "no_match",
71
+ metadata: {
72
+ kind: "search",
73
+ path: root,
74
+ pattern,
75
+ matches: 0,
76
+ truncated: false,
77
+ searchSignature: `glob:${root}:${pattern}`,
78
+ searchFamily: `glob:${pattern}`,
79
+ },
80
+ };
81
+ }
82
+ return {
83
+ content: `${matches.join("\n")}${wasTruncated ? `\n[More than ${MAX_RESULTS} files, output truncated]` : ""}`,
84
+ status: wasTruncated ? "partial" : "success",
85
+ metadata: {
86
+ kind: "search",
87
+ path: root,
88
+ pattern,
89
+ matches: matches.length,
90
+ truncated: wasTruncated,
91
+ searchSignature: `glob:${root}:${pattern}`,
92
+ searchFamily: `glob:${pattern}`,
93
+ },
94
+ };
95
+ },
96
+ };
97
+ }
98
+ async function walk(root, dir, matcher, files, truncated, abortSignal) {
99
+ if (abortSignal?.aborted || files.length >= MAX_RESULTS) {
100
+ truncated.value = true;
101
+ return;
102
+ }
103
+ const entries = await readdir(dir, { withFileTypes: true });
104
+ for (const entry of entries) {
105
+ if (abortSignal?.aborted || files.length >= MAX_RESULTS) {
106
+ truncated.value = true;
107
+ return;
108
+ }
109
+ if (entry.isDirectory() && DEFAULT_IGNORES.has(entry.name)) {
110
+ continue;
111
+ }
112
+ const absolute = resolve(dir, entry.name);
113
+ const rel = toPosix(relative(root, absolute));
114
+ if (entry.isDirectory()) {
115
+ await walk(root, absolute, matcher, files, truncated, abortSignal);
116
+ continue;
117
+ }
118
+ if (!entry.isFile()) {
119
+ continue;
120
+ }
121
+ if (matcher(rel)) {
122
+ const info = await stat(absolute);
123
+ files.push({ path: rel, mtimeMs: info.mtimeMs });
124
+ }
125
+ }
126
+ }
127
+ function toPosix(path) {
128
+ return path.split("\\").join("/");
129
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Grep tool - search file contents using ripgrep.
3
+ */
4
+ import type { ToolRegistryEntry } from "../types.js";
5
+ export declare function createGrepTool(cwd: string): ToolRegistryEntry;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Grep tool - search file contents using ripgrep.
3
+ */
4
+ import { execFile } from "node:child_process";
5
+ import { resolve } from "node:path";
6
+ import { isSensitivePath } from "./sensitive-paths.js";
7
+ import { analyzeToolIntent } from "../agent/tool-intent.js";
8
+ const MAX_MATCHES = 100;
9
+ export function createGrepTool(cwd) {
10
+ return {
11
+ name: "grep",
12
+ readOnly: true,
13
+ description: `Search file contents using regex (via ripgrep). Use this instead of running grep, rg, or ripgrep through bash. Returns up to ${MAX_MATCHES} matches.`,
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ pattern: { type: "string", description: "Regex pattern to search for" },
18
+ path: { type: "string", description: "Directory or file to search in (optional, default: cwd)" },
19
+ glob: { type: "string", description: "Glob pattern to filter files (optional, e.g. '*.ts')" },
20
+ },
21
+ required: ["pattern"],
22
+ },
23
+ async execute(args) {
24
+ const searchPath = args.path ? resolve(cwd, args.path) : cwd;
25
+ const pattern = String(args.pattern);
26
+ const intent = analyzeToolIntent({
27
+ name: "grep",
28
+ parsedArgs: {
29
+ pattern,
30
+ path: args.path,
31
+ glob: args.glob,
32
+ },
33
+ });
34
+ if (isSensitivePath(searchPath)) {
35
+ return {
36
+ content: `Error: Search blocked for sensitive credential storage: ${searchPath}`,
37
+ isError: true,
38
+ status: "blocked",
39
+ metadata: {
40
+ kind: "security",
41
+ path: searchPath,
42
+ pattern,
43
+ searchSignature: intent.search?.signature,
44
+ searchFamily: intent.search?.familyKey,
45
+ reason: "Sensitive credential storage is not searchable from general-purpose tasks.",
46
+ },
47
+ };
48
+ }
49
+ const rgArgs = ["--json", "-n", "--max-count", String(MAX_MATCHES), pattern];
50
+ if (args.glob) {
51
+ rgArgs.push("--glob", String(args.glob));
52
+ }
53
+ rgArgs.push(searchPath);
54
+ return new Promise((resolve) => {
55
+ execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
56
+ // rg returns exit code 1 when no matches found, which is not an error for us
57
+ const lines = stdout.split("\n").filter((l) => l.trim() !== "");
58
+ const matches = [];
59
+ for (const line of lines) {
60
+ try {
61
+ const obj = JSON.parse(line);
62
+ if (obj.type === "match") {
63
+ const path = obj.data.path.text;
64
+ const lineNum = obj.data.line_number;
65
+ const text = obj.data.lines.text?.trim() ?? "";
66
+ matches.push(`${path}:${lineNum}: ${text}`);
67
+ }
68
+ }
69
+ catch {
70
+ // ignore parse errors
71
+ }
72
+ }
73
+ if (matches.length === 0) {
74
+ resolve({
75
+ content: "No matches found.",
76
+ status: "no_match",
77
+ metadata: {
78
+ kind: "search",
79
+ path: searchPath,
80
+ pattern,
81
+ matches: 0,
82
+ truncated: false,
83
+ searchSignature: intent.search?.signature,
84
+ searchFamily: intent.search?.familyKey,
85
+ },
86
+ });
87
+ return;
88
+ }
89
+ let output = matches.join("\n");
90
+ const truncated = matches.length >= MAX_MATCHES;
91
+ if (matches.length >= MAX_MATCHES) {
92
+ output += `\n[More than ${MAX_MATCHES} matches, output truncated]`;
93
+ }
94
+ resolve({
95
+ content: output,
96
+ status: truncated ? "partial" : "success",
97
+ metadata: {
98
+ kind: "search",
99
+ path: searchPath,
100
+ pattern,
101
+ matches: matches.length,
102
+ truncated,
103
+ searchSignature: intent.search?.signature,
104
+ searchFamily: intent.search?.familyKey,
105
+ },
106
+ });
107
+ });
108
+ });
109
+ },
110
+ };
111
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Tool registry factory.
3
+ */
4
+ export { createReadTool } from "./read.js";
5
+ export { createBashTool } from "./bash.js";
6
+ export { createWriteTool } from "./write.js";
7
+ export { createEditTool } from "./edit.js";
8
+ export { createGlobTool } from "./glob.js";
9
+ export { createGrepTool } from "./grep.js";
10
+ export { createLspTool } from "./lsp.js";
11
+ export { createWebFetchTool } from "./web-fetch.js";
12
+ export { createWebSearchTool } from "./web-search.js";
13
+ export { createSkillTool } from "./skill.js";
14
+ export { createTaskTool } from "./task.js";
15
+ export { createTodoTool, type TodoStore } from "./todo.js";
16
+ export { createExitPlanModeTool, type PlanController } from "./exit-plan-mode.js";
17
+ export { createToolSearchTool, type ToolSearchController } from "./tool-search.js";
18
+ export { createQuestionTool } from "./question.js";
19
+ export { createMemoryReadSummaryTool, createMemorySearchTool } from "./memory.js";
20
+ import type { ToolRegistryEntry } from "../types.js";
21
+ import type { ApprovalController } from "../approval/types.js";
22
+ import type { SkillRegistry } from "../skills/registry.js";
23
+ import { type PlanController } from "./exit-plan-mode.js";
24
+ import { type LspService } from "../lsp/index.js";
25
+ import { type TodoStore } from "./todo.js";
26
+ import { type ToolSearchController } from "./tool-search.js";
27
+ import type { QuestionController } from "../question/index.js";
28
+ export interface CreateAllToolsOptions {
29
+ todoStore?: TodoStore;
30
+ planController?: PlanController;
31
+ approvalController?: ApprovalController;
32
+ questionController?: QuestionController;
33
+ toolSearchController?: ToolSearchController;
34
+ lspService?: LspService;
35
+ }
36
+ export declare function createAllTools(cwd: string, skillRegistry?: SkillRegistry, options?: CreateAllToolsOptions): ToolRegistryEntry[];
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Tool registry factory.
3
+ */
4
+ export { createReadTool } from "./read.js";
5
+ export { createBashTool } from "./bash.js";
6
+ export { createWriteTool } from "./write.js";
7
+ export { createEditTool } from "./edit.js";
8
+ export { createGlobTool } from "./glob.js";
9
+ export { createGrepTool } from "./grep.js";
10
+ export { createLspTool } from "./lsp.js";
11
+ export { createWebFetchTool } from "./web-fetch.js";
12
+ export { createWebSearchTool } from "./web-search.js";
13
+ export { createSkillTool } from "./skill.js";
14
+ export { createTaskTool } from "./task.js";
15
+ export { createTodoTool } from "./todo.js";
16
+ export { createExitPlanModeTool } from "./exit-plan-mode.js";
17
+ export { createToolSearchTool } from "./tool-search.js";
18
+ export { createQuestionTool } from "./question.js";
19
+ export { createMemoryReadSummaryTool, createMemorySearchTool } from "./memory.js";
20
+ import { createBashTool } from "./bash.js";
21
+ import { createEditTool } from "./edit.js";
22
+ import { createExitPlanModeTool } from "./exit-plan-mode.js";
23
+ import { createGlobTool } from "./glob.js";
24
+ import { createGrepTool } from "./grep.js";
25
+ import { getLspService } from "../lsp/index.js";
26
+ import { createLspTool } from "./lsp.js";
27
+ import { createReadTool } from "./read.js";
28
+ import { createSkillTool } from "./skill.js";
29
+ import { createTaskTool } from "./task.js";
30
+ import { createTodoTool } from "./todo.js";
31
+ import { createToolSearchTool } from "./tool-search.js";
32
+ import { createWebFetchTool } from "./web-fetch.js";
33
+ import { createWebSearchTool } from "./web-search.js";
34
+ import { createWriteTool } from "./write.js";
35
+ import { createQuestionTool } from "./question.js";
36
+ import { createMemoryReadSummaryTool, createMemorySearchTool } from "./memory.js";
37
+ export function createAllTools(cwd, skillRegistry, options = {}) {
38
+ const approval = options.approvalController;
39
+ const lsp = options.lspService ?? getLspService(cwd);
40
+ return [
41
+ createReadTool(cwd, approval, lsp),
42
+ createBashTool(cwd, approval),
43
+ createWriteTool(cwd, { refuseOverwrite: true }, approval, lsp),
44
+ createEditTool(cwd, approval, lsp),
45
+ createGlobTool(cwd),
46
+ createGrepTool(cwd),
47
+ createLspTool(cwd, lsp, approval),
48
+ createWebSearchTool(),
49
+ createWebFetchTool(approval),
50
+ createMemorySearchTool(cwd),
51
+ createMemoryReadSummaryTool(cwd),
52
+ createTaskTool(),
53
+ ...(options.questionController ? [createQuestionTool(options.questionController)] : []),
54
+ ...(skillRegistry ? [createSkillTool(skillRegistry)] : []),
55
+ ...(options.todoStore ? [createTodoTool(options.todoStore)] : []),
56
+ ...(options.planController ? [createExitPlanModeTool(options.planController)] : []),
57
+ ...(options.toolSearchController ? [createToolSearchTool(options.toolSearchController)] : []),
58
+ ];
59
+ }
@@ -0,0 +1,4 @@
1
+ import type { ApprovalController } from "../approval/types.js";
2
+ import { type LspService } from "../lsp/index.js";
3
+ import type { ToolRegistryEntry } from "../types.js";
4
+ export declare function createLspTool(cwd: string, lsp?: LspService, approval?: ApprovalController): ToolRegistryEntry;
@@ -0,0 +1,92 @@
1
+ import { access } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { gateToolAction } from "../approval/tool-helper.js";
5
+ import { getLspService } from "../lsp/index.js";
6
+ const OPERATIONS = [
7
+ "goToDefinition",
8
+ "findReferences",
9
+ "hover",
10
+ "documentSymbol",
11
+ "workspaceSymbol",
12
+ "goToImplementation",
13
+ "prepareCallHierarchy",
14
+ "incomingCalls",
15
+ "outgoingCalls",
16
+ ];
17
+ export function createLspTool(cwd, lsp = getLspService(cwd), approval) {
18
+ return {
19
+ name: "lsp",
20
+ readOnly: true,
21
+ description: "Use the language server for code navigation. Supports goToDefinition, findReferences, hover, documentSymbol, workspaceSymbol, goToImplementation, prepareCallHierarchy, incomingCalls, and outgoingCalls.",
22
+ parameters: {
23
+ type: "object",
24
+ properties: {
25
+ operation: { type: "string", enum: [...OPERATIONS], description: "LSP operation to perform" },
26
+ filePath: { type: "string", description: "Path to the file, relative or absolute" },
27
+ line: { type: "number", description: "1-based line number for position-based operations" },
28
+ character: { type: "number", description: "1-based character offset for position-based operations" },
29
+ query: { type: "string", description: "Optional query for workspaceSymbol" },
30
+ },
31
+ required: ["operation", "filePath"],
32
+ },
33
+ async execute(args) {
34
+ const operation = args.operation;
35
+ if (!OPERATIONS.includes(operation)) {
36
+ return { content: `Error: Unsupported LSP operation: ${args.operation}`, isError: true };
37
+ }
38
+ const file = resolve(cwd, String(args.filePath));
39
+ try {
40
+ await access(file, constants.R_OK);
41
+ }
42
+ catch {
43
+ return { content: `Error: File not found or not readable: ${file}`, isError: true };
44
+ }
45
+ const available = await lsp.hasClients(file);
46
+ if (!available) {
47
+ return { content: "Error: No LSP server available for this file type.", isError: true };
48
+ }
49
+ const gate = await gateToolAction(approval, {
50
+ type: "lsp",
51
+ path: file,
52
+ operation,
53
+ });
54
+ if (!gate.approved)
55
+ return gate.result;
56
+ await lsp.touchFile(file, "document");
57
+ const position = {
58
+ file,
59
+ line: Math.max(0, Number(args.line ?? 1) - 1),
60
+ character: Math.max(0, Number(args.character ?? 1) - 1),
61
+ };
62
+ const result = await runOperation(lsp, operation, position, String(args.query ?? ""));
63
+ return {
64
+ content: result.length === 0 ? `No results found for ${operation}` : JSON.stringify(result, null, 2),
65
+ status: "success",
66
+ metadata: { kind: "lsp", path: file },
67
+ };
68
+ },
69
+ };
70
+ }
71
+ async function runOperation(lsp, operation, position, query) {
72
+ switch (operation) {
73
+ case "goToDefinition":
74
+ return lsp.definition(position);
75
+ case "findReferences":
76
+ return lsp.references(position);
77
+ case "hover":
78
+ return lsp.hover(position);
79
+ case "documentSymbol":
80
+ return lsp.documentSymbol(position.file);
81
+ case "workspaceSymbol":
82
+ return lsp.workspaceSymbol(query);
83
+ case "goToImplementation":
84
+ return lsp.implementation(position);
85
+ case "prepareCallHierarchy":
86
+ return lsp.prepareCallHierarchy(position);
87
+ case "incomingCalls":
88
+ return lsp.incomingCalls(position);
89
+ case "outgoingCalls":
90
+ return lsp.outgoingCalls(position);
91
+ }
92
+ }
@@ -0,0 +1,3 @@
1
+ import type { ToolRegistryEntry } from "../types.js";
2
+ export declare function createMemorySearchTool(cwd: string): ToolRegistryEntry;
3
+ export declare function createMemoryReadSummaryTool(cwd: string): ToolRegistryEntry;
@@ -0,0 +1,90 @@
1
+ import { readMemorySummary, searchMemory } from "../memory/index.js";
2
+ const MEMORY_SCOPE_ENUM = ["project", "global", "all"];
3
+ export function createMemorySearchTool(cwd) {
4
+ return {
5
+ name: "memory_search",
6
+ description: "Search persistent Bubble memory for prior project facts, user preferences, workflows, decisions, and gotchas.",
7
+ readOnly: true,
8
+ parameters: {
9
+ type: "object",
10
+ properties: {
11
+ query: {
12
+ type: "string",
13
+ description: "Search query. Use concrete terms such as file names, feature names, commands, or error text.",
14
+ },
15
+ scope: {
16
+ type: "string",
17
+ enum: MEMORY_SCOPE_ENUM,
18
+ description: "Memory scope to search. Defaults to all, with project memory first.",
19
+ },
20
+ limit: {
21
+ type: "number",
22
+ description: "Maximum number of results to return. Defaults to 12.",
23
+ },
24
+ },
25
+ required: ["query"],
26
+ additionalProperties: false,
27
+ },
28
+ async execute(args) {
29
+ const query = typeof args.query === "string" ? args.query.trim() : "";
30
+ if (!query) {
31
+ return { content: "query is required", isError: true, status: "no_match" };
32
+ }
33
+ const scope = parseScope(args.scope);
34
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit)
35
+ ? Math.max(1, Math.min(50, Math.floor(args.limit)))
36
+ : undefined;
37
+ const results = searchMemory(cwd, query, { scope, limit });
38
+ if (results.length === 0) {
39
+ return { content: `No memory matches for "${query}".`, status: "no_match" };
40
+ }
41
+ return {
42
+ content: [
43
+ `Memory search results for "${query}":`,
44
+ ...results.flatMap((result) => [
45
+ `- ${result.scope} ${result.path}:${result.line}`,
46
+ ` ${result.text}`,
47
+ ]),
48
+ ].join("\n"),
49
+ metadata: { kind: "search", matches: results.length },
50
+ };
51
+ },
52
+ };
53
+ }
54
+ export function createMemoryReadSummaryTool(cwd) {
55
+ return {
56
+ name: "memory_read_summary",
57
+ description: "Read the concise persistent memory summary for the current project, global scope, or both.",
58
+ readOnly: true,
59
+ parameters: {
60
+ type: "object",
61
+ properties: {
62
+ scope: {
63
+ type: "string",
64
+ enum: MEMORY_SCOPE_ENUM,
65
+ description: "Memory scope to read. Defaults to project.",
66
+ },
67
+ },
68
+ additionalProperties: false,
69
+ },
70
+ async execute(args) {
71
+ const scope = parseScope(args.scope, "project");
72
+ const summaries = readMemorySummary(cwd, scope);
73
+ if (summaries.length === 0) {
74
+ return { content: `No ${scope} memory summary is available.`, status: "no_match" };
75
+ }
76
+ return {
77
+ content: summaries.map((summary) => [
78
+ `# ${summary.scope} memory summary`,
79
+ `Path: ${summary.path}`,
80
+ "",
81
+ summary.content,
82
+ ].join("\n")).join("\n\n---\n\n"),
83
+ metadata: { kind: "read", matches: summaries.length },
84
+ };
85
+ },
86
+ };
87
+ }
88
+ function parseScope(value, fallback = "all") {
89
+ return value === "project" || value === "global" || value === "all" ? value : fallback;
90
+ }
@@ -0,0 +1,3 @@
1
+ import type { ToolRegistryEntry } from "../types.js";
2
+ import type { QuestionController } from "../question/index.js";
3
+ export declare function createQuestionTool(controller: QuestionController): ToolRegistryEntry;