@metabob/minibob 0.1.2

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 (174) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/CHANGELOG.md +112 -0
  3. package/README.md +380 -0
  4. package/bin/minibob.js +36 -0
  5. package/dist/acp-gossip.d.ts +72 -0
  6. package/dist/acp-gossip.d.ts.map +1 -0
  7. package/dist/acp-gossip.js +156 -0
  8. package/dist/acp-gossip.js.map +1 -0
  9. package/dist/acp.d.ts +62 -0
  10. package/dist/acp.d.ts.map +1 -0
  11. package/dist/acp.js +292 -0
  12. package/dist/acp.js.map +1 -0
  13. package/dist/activity.d.ts +157 -0
  14. package/dist/activity.d.ts.map +1 -0
  15. package/dist/activity.js +518 -0
  16. package/dist/activity.js.map +1 -0
  17. package/dist/agent-runtime.d.ts +104 -0
  18. package/dist/agent-runtime.d.ts.map +1 -0
  19. package/dist/boredom.d.ts +125 -0
  20. package/dist/boredom.d.ts.map +1 -0
  21. package/dist/boredom.js +244 -0
  22. package/dist/boredom.js.map +1 -0
  23. package/dist/cli/acp-server.d.ts +23 -0
  24. package/dist/cli/acp-server.d.ts.map +1 -0
  25. package/dist/cli/burrow.d.ts +26 -0
  26. package/dist/cli/burrow.d.ts.map +1 -0
  27. package/dist/cli/doctor.d.ts +22 -0
  28. package/dist/cli/doctor.d.ts.map +1 -0
  29. package/dist/cli/goal.d.ts +22 -0
  30. package/dist/cli/goal.d.ts.map +1 -0
  31. package/dist/cli/index.d.ts +47 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/instance-registry.d.ts +78 -0
  34. package/dist/cli/instance-registry.d.ts.map +1 -0
  35. package/dist/cli/observe.d.ts +35 -0
  36. package/dist/cli/observe.d.ts.map +1 -0
  37. package/dist/cli/vessel.d.ts +14 -0
  38. package/dist/cli/vessel.d.ts.map +1 -0
  39. package/dist/composition-observer.d.ts +96 -0
  40. package/dist/composition-observer.d.ts.map +1 -0
  41. package/dist/config.d.ts +36 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +128 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/docker/Dockerfile +35 -0
  46. package/dist/environment.d.ts +72 -0
  47. package/dist/environment.d.ts.map +1 -0
  48. package/dist/environment.js +142 -0
  49. package/dist/environment.js.map +1 -0
  50. package/dist/goal-processor.d.ts +165 -0
  51. package/dist/goal-processor.d.ts.map +1 -0
  52. package/dist/helm/minibob-cluster/Chart.yaml +13 -0
  53. package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
  54. package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
  55. package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
  56. package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
  57. package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
  58. package/dist/helm/minibob-cluster/values-local.yaml +41 -0
  59. package/dist/helm/minibob-cluster/values-production.yaml +57 -0
  60. package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
  61. package/dist/helm/minibob-cluster/values.yaml +127 -0
  62. package/dist/improviser.d.ts +74 -0
  63. package/dist/improviser.d.ts.map +1 -0
  64. package/dist/impulse-filter.d.ts +74 -0
  65. package/dist/impulse-filter.d.ts.map +1 -0
  66. package/dist/impulse.d.ts +92 -0
  67. package/dist/impulse.d.ts.map +1 -0
  68. package/dist/impulse.js +234 -0
  69. package/dist/impulse.js.map +1 -0
  70. package/dist/lib.d.ts +29 -0
  71. package/dist/lib.d.ts.map +1 -0
  72. package/dist/lib.js +18561 -0
  73. package/dist/lib.js.map +98 -0
  74. package/dist/lifecycle-hooks.d.ts +99 -0
  75. package/dist/lifecycle-hooks.d.ts.map +1 -0
  76. package/dist/lifecycle-hooks.js +135 -0
  77. package/dist/lifecycle-hooks.js.map +1 -0
  78. package/dist/llm.d.ts +31 -0
  79. package/dist/llm.d.ts.map +1 -0
  80. package/dist/llm.js +349 -0
  81. package/dist/llm.js.map +1 -0
  82. package/dist/mcp-activity-bridge.d.ts +66 -0
  83. package/dist/mcp-activity-bridge.d.ts.map +1 -0
  84. package/dist/mcp-activity-bridge.js +126 -0
  85. package/dist/mcp-activity-bridge.js.map +1 -0
  86. package/dist/mcp.d.ts +216 -0
  87. package/dist/mcp.d.ts.map +1 -0
  88. package/dist/mcp.js +292 -0
  89. package/dist/mcp.js.map +1 -0
  90. package/dist/memory-agent.d.ts +92 -0
  91. package/dist/memory-agent.d.ts.map +1 -0
  92. package/dist/memory-agent.js +277 -0
  93. package/dist/memory-agent.js.map +1 -0
  94. package/dist/runtime-mapping.d.ts +97 -0
  95. package/dist/runtime-mapping.d.ts.map +1 -0
  96. package/dist/search-first-executor.d.ts +113 -0
  97. package/dist/search-first-executor.d.ts.map +1 -0
  98. package/dist/session.d.ts +48 -0
  99. package/dist/session.d.ts.map +1 -0
  100. package/dist/template-extractor.d.ts +9 -0
  101. package/dist/template-extractor.d.ts.map +1 -0
  102. package/dist/template-generator.d.ts +12 -0
  103. package/dist/template-generator.d.ts.map +1 -0
  104. package/dist/tools.d.ts +58 -0
  105. package/dist/tools.d.ts.map +1 -0
  106. package/dist/tools.js +771 -0
  107. package/dist/tools.js.map +1 -0
  108. package/dist/types.d.ts +503 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/types.js +8 -0
  111. package/dist/types.js.map +1 -0
  112. package/dist/understanding/analyzer.d.ts +55 -0
  113. package/dist/understanding/analyzer.d.ts.map +1 -0
  114. package/dist/understanding/explorer.d.ts +73 -0
  115. package/dist/understanding/explorer.d.ts.map +1 -0
  116. package/dist/understanding/index.d.ts +7 -0
  117. package/dist/understanding/index.d.ts.map +1 -0
  118. package/dist/understanding/types.d.ts +136 -0
  119. package/dist/understanding/types.d.ts.map +1 -0
  120. package/dist/validation.d.ts +29 -0
  121. package/dist/validation.d.ts.map +1 -0
  122. package/dist/validation.js +106 -0
  123. package/dist/validation.js.map +1 -0
  124. package/dist/vessel-bootstrap.d.ts +190 -0
  125. package/dist/vessel-bootstrap.d.ts.map +1 -0
  126. package/dist/vessel-registry.d.ts +229 -0
  127. package/dist/vessel-registry.d.ts.map +1 -0
  128. package/index.ts +1329 -0
  129. package/package.json +54 -0
  130. package/src/acp-gossip.ts +193 -0
  131. package/src/acp.ts +362 -0
  132. package/src/activity.ts +1464 -0
  133. package/src/agent-runtime.ts +365 -0
  134. package/src/boredom.ts +423 -0
  135. package/src/cli/acp-server.ts +377 -0
  136. package/src/cli/burrow.ts +896 -0
  137. package/src/cli/doctor.ts +526 -0
  138. package/src/cli/goal.ts +224 -0
  139. package/src/cli/index.ts +147 -0
  140. package/src/cli/instance-registry.ts +271 -0
  141. package/src/cli/observe.ts +682 -0
  142. package/src/cli/vessel.ts +287 -0
  143. package/src/components/SystemOverview.tsx +331 -0
  144. package/src/composition-observer.ts +449 -0
  145. package/src/config.ts +172 -0
  146. package/src/environment.ts +167 -0
  147. package/src/goal-processor.ts +654 -0
  148. package/src/improviser.ts +591 -0
  149. package/src/impulse-filter.ts +273 -0
  150. package/src/impulse.ts +311 -0
  151. package/src/lib.ts +147 -0
  152. package/src/lifecycle-hooks.ts +181 -0
  153. package/src/llm.ts +434 -0
  154. package/src/mcp-activity-bridge.ts +158 -0
  155. package/src/mcp.ts +747 -0
  156. package/src/memory-agent.ts +316 -0
  157. package/src/runtime-mapping.ts +527 -0
  158. package/src/search-first-executor.ts +666 -0
  159. package/src/session.ts +141 -0
  160. package/src/template-extractor.ts +256 -0
  161. package/src/template-generator.ts +130 -0
  162. package/src/tools.ts +924 -0
  163. package/src/types.ts +497 -0
  164. package/src/understanding/analyzer.ts +354 -0
  165. package/src/understanding/explorer.ts +488 -0
  166. package/src/understanding/index.ts +27 -0
  167. package/src/understanding/types.ts +153 -0
  168. package/src/validation.ts +125 -0
  169. package/src/vessel-bootstrap.ts +440 -0
  170. package/src/vessel-registry.ts +621 -0
  171. package/templates/core/edit-file.json +85 -0
  172. package/templates/understanding/diagnose-problem.json +32 -0
  173. package/templates/understanding/explore-codebase-v2.json +57 -0
  174. package/templates/understanding/explore-codebase.json +37 -0
package/dist/tools.js ADDED
@@ -0,0 +1,771 @@
1
+ /**
2
+ * minibob Built-in Tools
3
+ *
4
+ * Minimal set of tools for activity execution and self-development.
5
+ *
6
+ * SECURITY: Path validation, command whitelisting, and input sanitization
7
+ * enforced to prevent command injection and path traversal attacks.
8
+ */
9
+ import * as path from "node:path";
10
+ // =============================================================================
11
+ // SECURITY UTILITIES
12
+ // =============================================================================
13
+ /**
14
+ * Allowed bash commands (whitelist approach)
15
+ * Only these commands can be executed via the bash tool
16
+ */
17
+ const ALLOWED_BASH_COMMANDS = new Set([
18
+ // Version control
19
+ "git",
20
+ // Package managers
21
+ "npm", "pnpm", "yarn", "bun",
22
+ // Build tools
23
+ "make", "cmake", "cargo", "go",
24
+ // File operations (read-only)
25
+ "ls", "cat", "head", "tail", "find", "grep", "rg", "fd",
26
+ // Text processing
27
+ "sed", "awk", "cut", "sort", "uniq", "wc",
28
+ // Directory operations (safe)
29
+ "pwd", "cd", "mkdir", "rmdir",
30
+ // Testing
31
+ "pytest", "jest", "vitest", "cargo test",
32
+ // Linting/formatting
33
+ "eslint", "prettier", "black", "rustfmt",
34
+ // Type checking
35
+ "tsc", "mypy", "cargo check",
36
+ ]);
37
+ /**
38
+ * Dangerous command patterns that are always blocked
39
+ */
40
+ const BLOCKED_COMMAND_PATTERNS = [
41
+ /rm\s+-rf\s+\//, // rm -rf /
42
+ /:\(\)\{.*\}/, // Fork bombs
43
+ />\s*\/dev\/sd[a-z]/, // Writing to raw disk
44
+ /mkfs/, // Filesystem formatting
45
+ /dd\s+if=.*of=\/dev/, // Direct disk write
46
+ ];
47
+ /**
48
+ * Validate and sanitize a bash command
49
+ * @throws Error if command is not allowed
50
+ */
51
+ function validateBashCommand(command) {
52
+ // Check blocked patterns first
53
+ for (const pattern of BLOCKED_COMMAND_PATTERNS) {
54
+ if (pattern.test(command)) {
55
+ throw new Error(`Blocked dangerous command pattern: ${pattern.source}`);
56
+ }
57
+ }
58
+ // Extract the first command (before pipes, semicolons, etc.)
59
+ const firstCommand = command
60
+ .split(/[|;&]/)[0]?.trim()
61
+ .split(/\s+/)[0];
62
+ if (!firstCommand) {
63
+ throw new Error("Empty command");
64
+ }
65
+ // Check if command is in whitelist
66
+ if (!ALLOWED_BASH_COMMANDS.has(firstCommand)) {
67
+ throw new Error(`Command '${firstCommand}' not in whitelist. Allowed: ${Array.from(ALLOWED_BASH_COMMANDS).join(", ")}`);
68
+ }
69
+ }
70
+ /**
71
+ * Validate a file path is within the working directory
72
+ * Prevents path traversal attacks (../../etc/passwd)
73
+ * @returns Canonicalized absolute path
74
+ * @throws Error if path escapes working directory
75
+ */
76
+ function validatePath(filePath, workingDirectory) {
77
+ // Resolve to absolute path
78
+ const absolutePath = path.isAbsolute(filePath)
79
+ ? path.resolve(filePath)
80
+ : path.resolve(workingDirectory, filePath);
81
+ // Canonicalize (removes .., ., symlinks)
82
+ const canonicalPath = path.normalize(absolutePath);
83
+ const canonicalWorkDir = path.normalize(path.resolve(workingDirectory));
84
+ // Check if path is within working directory
85
+ if (!canonicalPath.startsWith(canonicalWorkDir)) {
86
+ throw new Error(`Path traversal blocked: ${filePath} resolves outside working directory (${workingDirectory})`);
87
+ }
88
+ return canonicalPath;
89
+ }
90
+ // =============================================================================
91
+ // TOOL DEFINITIONS
92
+ // =============================================================================
93
+ export const toolDefinitions = {
94
+ bash: {
95
+ name: "bash",
96
+ description: "Execute a shell command. Returns stdout and stderr.",
97
+ parameters: {
98
+ type: "object",
99
+ properties: {
100
+ command: {
101
+ type: "string",
102
+ description: "The shell command to execute",
103
+ },
104
+ cwd: {
105
+ type: "string",
106
+ description: "Working directory for the command (optional)",
107
+ },
108
+ timeout: {
109
+ type: "number",
110
+ description: "Timeout in milliseconds (default: 60000)",
111
+ },
112
+ },
113
+ required: ["command"],
114
+ },
115
+ },
116
+ read: {
117
+ name: "read",
118
+ description: "Read a file from the filesystem. Returns file content with line numbers.",
119
+ parameters: {
120
+ type: "object",
121
+ properties: {
122
+ path: {
123
+ type: "string",
124
+ description: "Path to the file to read",
125
+ },
126
+ offset: {
127
+ type: "number",
128
+ description: "Line number to start reading from (0-based)",
129
+ },
130
+ limit: {
131
+ type: "number",
132
+ description: "Maximum number of lines to read (default: 2000)",
133
+ },
134
+ },
135
+ required: ["path"],
136
+ },
137
+ },
138
+ write: {
139
+ name: "write",
140
+ description: "Write content to a file. Creates directories if needed.",
141
+ parameters: {
142
+ type: "object",
143
+ properties: {
144
+ path: {
145
+ type: "string",
146
+ description: "Path to the file to write",
147
+ },
148
+ content: {
149
+ type: "string",
150
+ description: "Content to write to the file",
151
+ },
152
+ },
153
+ required: ["path", "content"],
154
+ },
155
+ },
156
+ edit: {
157
+ name: "edit",
158
+ description: "Edit a file by replacing exact text. The oldString must match exactly.",
159
+ parameters: {
160
+ type: "object",
161
+ properties: {
162
+ path: {
163
+ type: "string",
164
+ description: "Path to the file to edit",
165
+ },
166
+ oldString: {
167
+ type: "string",
168
+ description: "Exact text to find and replace",
169
+ },
170
+ newString: {
171
+ type: "string",
172
+ description: "Text to replace with",
173
+ },
174
+ replaceAll: {
175
+ type: "boolean",
176
+ description: "Replace all occurrences (default: false)",
177
+ },
178
+ },
179
+ required: ["path", "oldString", "newString"],
180
+ },
181
+ },
182
+ glob: {
183
+ name: "glob",
184
+ description: "Find files matching a glob pattern.",
185
+ parameters: {
186
+ type: "object",
187
+ properties: {
188
+ pattern: {
189
+ type: "string",
190
+ description: "Glob pattern to match (e.g., '**/*.ts')",
191
+ },
192
+ cwd: {
193
+ type: "string",
194
+ description: "Directory to search from (optional)",
195
+ },
196
+ },
197
+ required: ["pattern"],
198
+ },
199
+ },
200
+ grep: {
201
+ name: "grep",
202
+ description: "Search file contents using a regex pattern.",
203
+ parameters: {
204
+ type: "object",
205
+ properties: {
206
+ pattern: {
207
+ type: "string",
208
+ description: "Regex pattern to search for",
209
+ },
210
+ include: {
211
+ type: "string",
212
+ description: "File pattern to include (e.g., '*.ts')",
213
+ },
214
+ cwd: {
215
+ type: "string",
216
+ description: "Directory to search in (optional)",
217
+ },
218
+ },
219
+ required: ["pattern"],
220
+ },
221
+ },
222
+ list: {
223
+ name: "list",
224
+ description: "List files and directories in a path.",
225
+ parameters: {
226
+ type: "object",
227
+ properties: {
228
+ path: {
229
+ type: "string",
230
+ description: "Directory path to list",
231
+ },
232
+ },
233
+ required: ["path"],
234
+ },
235
+ },
236
+ git: {
237
+ name: "git",
238
+ description: "Execute git commands for version control operations.",
239
+ parameters: {
240
+ type: "object",
241
+ properties: {
242
+ command: {
243
+ type: "string",
244
+ description: "Git subcommand (e.g., 'status', 'add', 'commit', 'diff')",
245
+ enum: ["status", "add", "commit", "diff", "log", "branch", "checkout", "push", "pull"],
246
+ },
247
+ args: {
248
+ type: "array",
249
+ description: "Arguments for the git command",
250
+ },
251
+ cwd: {
252
+ type: "string",
253
+ description: "Working directory (optional)",
254
+ },
255
+ },
256
+ required: ["command"],
257
+ },
258
+ },
259
+ activity: {
260
+ name: "activity",
261
+ description: "Execute an activity template (enables nested activity execution). Template can be loaded from MCP backend or local file.",
262
+ parameters: {
263
+ type: "object",
264
+ properties: {
265
+ templateId: {
266
+ type: "string",
267
+ description: "Activity template ID (from MCP) or path (local file)",
268
+ },
269
+ variables: {
270
+ type: "object",
271
+ description: "Variables to pass to the activity",
272
+ },
273
+ reason: {
274
+ type: "string",
275
+ description: "Reason for executing this activity",
276
+ },
277
+ },
278
+ required: ["templateId"],
279
+ },
280
+ },
281
+ search_activities: {
282
+ name: "search_activities",
283
+ description: "Search for existing activity templates by category or keyword. Use this BEFORE creating new activities to avoid duplication.",
284
+ parameters: {
285
+ type: "object",
286
+ properties: {
287
+ category: {
288
+ type: "string",
289
+ description: "Filter by category (feature, bugfix, refactor, tool, infrastructure)",
290
+ enum: ["feature", "bugfix", "refactor", "tool", "infrastructure"],
291
+ },
292
+ verbose: {
293
+ type: "boolean",
294
+ description: "Show full details (default: false, returns compact list)",
295
+ },
296
+ },
297
+ required: [],
298
+ },
299
+ },
300
+ create_activity_goal_seeking: {
301
+ name: "create_activity_goal_seeking",
302
+ description: "Create a new activity template for a non-trivial task. The agent will autonomously generate tasks and learn from execution. Use this when no existing activity matches your need.",
303
+ parameters: {
304
+ type: "object",
305
+ properties: {
306
+ goalDescription: {
307
+ type: "string",
308
+ description: "High-level goal description (e.g., 'Deploy app to production with health checks')",
309
+ },
310
+ templateName: {
311
+ type: "string",
312
+ description: "Human-readable template name (e.g., 'Deploy Production Application')",
313
+ },
314
+ category: {
315
+ type: "string",
316
+ description: "Template category",
317
+ enum: ["feature", "bugfix", "refactor", "tool", "infrastructure"],
318
+ },
319
+ variables: {
320
+ type: "object",
321
+ description: "Context variables for goal decomposition",
322
+ },
323
+ },
324
+ required: ["goalDescription", "templateName", "category"],
325
+ },
326
+ },
327
+ impulse_create: {
328
+ name: "impulse_create",
329
+ description: "Create a new impulse for context management.",
330
+ parameters: {
331
+ type: "object",
332
+ properties: {
333
+ id: {
334
+ type: "string",
335
+ description: "Unique impulse ID",
336
+ },
337
+ type: {
338
+ type: "string",
339
+ description: "Impulse pointer type",
340
+ enum: ["memo", "file", "activityOutput", "custom"],
341
+ },
342
+ content: {
343
+ type: "string",
344
+ description: "Content (for memo type) or path (for file type)",
345
+ },
346
+ budget: {
347
+ type: "number",
348
+ description: "Token budget for this impulse",
349
+ },
350
+ priority: {
351
+ type: "string",
352
+ description: "Priority level",
353
+ enum: ["critical", "high", "medium", "low"],
354
+ },
355
+ },
356
+ required: ["id", "type", "budget"],
357
+ },
358
+ },
359
+ };
360
+ /**
361
+ * Create tool handlers with working directory context
362
+ */
363
+ export function createToolHandlers(options) {
364
+ // Support backward compatibility - if string passed, convert to options
365
+ const opts = typeof options === "string"
366
+ ? { workingDirectory: options }
367
+ : options;
368
+ const workingDirectory = opts.workingDirectory;
369
+ const resolvePath = (path) => {
370
+ if (path.startsWith("/"))
371
+ return path;
372
+ return `${workingDirectory}/${path}`;
373
+ };
374
+ return {
375
+ bash: async (params) => {
376
+ const command = params.command;
377
+ const cwd = params.cwd ?? workingDirectory;
378
+ const timeout = params.timeout ?? 60000;
379
+ try {
380
+ // SECURITY: Validate command against whitelist
381
+ validateBashCommand(command);
382
+ const proc = Bun.spawn(["sh", "-c", command], {
383
+ cwd,
384
+ stdout: "pipe",
385
+ stderr: "pipe",
386
+ });
387
+ // Handle timeout
388
+ const timeoutPromise = new Promise((_, reject) => {
389
+ setTimeout(() => {
390
+ proc.kill();
391
+ reject(new Error(`Command timed out after ${timeout}ms`));
392
+ }, timeout);
393
+ });
394
+ const exitCode = await Promise.race([proc.exited, timeoutPromise]);
395
+ const stdout = await new Response(proc.stdout).text();
396
+ const stderr = await new Response(proc.stderr).text();
397
+ if (exitCode !== 0) {
398
+ return {
399
+ success: false,
400
+ error: `Command failed with exit code ${exitCode}\nStderr: ${stderr}`,
401
+ output: stdout,
402
+ };
403
+ }
404
+ return {
405
+ success: true,
406
+ output: stdout + (stderr ? `\nStderr: ${stderr}` : ""),
407
+ };
408
+ }
409
+ catch (error) {
410
+ return {
411
+ success: false,
412
+ error: error instanceof Error ? error.message : String(error),
413
+ };
414
+ }
415
+ },
416
+ read: async (params) => {
417
+ const filePath = params.path;
418
+ const offset = params.offset ?? 0;
419
+ const limit = params.limit ?? 2000;
420
+ try {
421
+ // SECURITY: Validate path is within working directory
422
+ const validatedPath = validatePath(filePath, workingDirectory);
423
+ const file = Bun.file(validatedPath);
424
+ if (!(await file.exists())) {
425
+ return {
426
+ success: false,
427
+ error: `File not found: ${validatedPath}`,
428
+ };
429
+ }
430
+ const content = await file.text();
431
+ const lines = content.split("\n");
432
+ const selectedLines = lines.slice(offset, offset + limit);
433
+ // Format with line numbers
434
+ const formatted = selectedLines
435
+ .map((line, i) => {
436
+ const lineNum = (offset + i + 1).toString().padStart(5, "0");
437
+ return `${lineNum}| ${line}`;
438
+ })
439
+ .join("\n");
440
+ return {
441
+ success: true,
442
+ output: `<file>\n${formatted}\n</file>`,
443
+ metadata: {
444
+ totalLines: lines.length,
445
+ readLines: selectedLines.length,
446
+ offset,
447
+ },
448
+ };
449
+ }
450
+ catch (error) {
451
+ return {
452
+ success: false,
453
+ error: error instanceof Error ? error.message : String(error),
454
+ };
455
+ }
456
+ },
457
+ write: async (params) => {
458
+ const filePath = params.path;
459
+ const content = params.content;
460
+ try {
461
+ // SECURITY: Validate path is within working directory
462
+ const validatedPath = validatePath(filePath, workingDirectory);
463
+ // Create directory if needed
464
+ const dir = validatedPath.substring(0, validatedPath.lastIndexOf("/"));
465
+ if (dir) {
466
+ await Bun.$ `mkdir -p ${dir}`.quiet();
467
+ }
468
+ await Bun.write(validatedPath, content);
469
+ return {
470
+ success: true,
471
+ output: `Wrote ${content.length} bytes to ${path}`,
472
+ };
473
+ }
474
+ catch (error) {
475
+ return {
476
+ success: false,
477
+ error: error instanceof Error ? error.message : String(error),
478
+ };
479
+ }
480
+ },
481
+ edit: async (params) => {
482
+ const filePath = params.path;
483
+ const oldString = params.oldString;
484
+ const newString = params.newString;
485
+ const replaceAll = params.replaceAll ?? false;
486
+ try {
487
+ // SECURITY: Validate path is within working directory
488
+ const validatedPath = validatePath(filePath, workingDirectory);
489
+ const file = Bun.file(validatedPath);
490
+ if (!(await file.exists())) {
491
+ return {
492
+ success: false,
493
+ error: `File not found: ${path}`,
494
+ };
495
+ }
496
+ const content = await file.text();
497
+ if (!content.includes(oldString)) {
498
+ return {
499
+ success: false,
500
+ error: `oldString not found in file: "${oldString.substring(0, 50)}..."`,
501
+ };
502
+ }
503
+ // Check for multiple matches if not replaceAll
504
+ if (!replaceAll) {
505
+ const matches = content.split(oldString).length - 1;
506
+ if (matches > 1) {
507
+ return {
508
+ success: false,
509
+ error: `oldString found ${matches} times. Use replaceAll: true or provide more context.`,
510
+ };
511
+ }
512
+ }
513
+ const newContent = replaceAll
514
+ ? content.split(oldString).join(newString)
515
+ : content.replace(oldString, newString);
516
+ await Bun.write(validatedPath, newContent);
517
+ return {
518
+ success: true,
519
+ output: `Edited ${validatedPath}`,
520
+ };
521
+ }
522
+ catch (error) {
523
+ return {
524
+ success: false,
525
+ error: error instanceof Error ? error.message : String(error),
526
+ };
527
+ }
528
+ },
529
+ glob: async (params) => {
530
+ const pattern = params.pattern;
531
+ const cwd = params.cwd ?? workingDirectory;
532
+ try {
533
+ const glob = new Bun.Glob(pattern);
534
+ const files = [];
535
+ for await (const file of glob.scan({ cwd })) {
536
+ files.push(file);
537
+ if (files.length >= 100)
538
+ break; // Limit results
539
+ }
540
+ return {
541
+ success: true,
542
+ output: files.length > 0 ? files.join("\n") : "No files found",
543
+ metadata: { count: files.length },
544
+ };
545
+ }
546
+ catch (error) {
547
+ return {
548
+ success: false,
549
+ error: error instanceof Error ? error.message : String(error),
550
+ };
551
+ }
552
+ },
553
+ grep: async (params) => {
554
+ const pattern = params.pattern;
555
+ const include = params.include;
556
+ const cwd = params.cwd ?? workingDirectory;
557
+ try {
558
+ // Use ripgrep if available, fall back to grep
559
+ const rgArgs = ["rg", "-l", pattern];
560
+ if (include) {
561
+ rgArgs.push("-g", include);
562
+ }
563
+ const proc = Bun.spawn(rgArgs, {
564
+ cwd,
565
+ stdout: "pipe",
566
+ stderr: "pipe",
567
+ });
568
+ const exitCode = await proc.exited;
569
+ const stdout = await new Response(proc.stdout).text();
570
+ if (exitCode !== 0 && exitCode !== 1) {
571
+ // Exit code 1 means no matches, which is OK
572
+ return {
573
+ success: false,
574
+ error: `grep failed with exit code ${exitCode}`,
575
+ };
576
+ }
577
+ const files = stdout.trim().split("\n").filter(Boolean);
578
+ return {
579
+ success: true,
580
+ output: files.length > 0 ? `Found ${files.length} files:\n${files.join("\n")}` : "No matches found",
581
+ metadata: { count: files.length },
582
+ };
583
+ }
584
+ catch (error) {
585
+ return {
586
+ success: false,
587
+ error: error instanceof Error ? error.message : String(error),
588
+ };
589
+ }
590
+ },
591
+ list: async (params) => {
592
+ const filePath = params.path;
593
+ try {
594
+ // SECURITY: Validate path is within working directory
595
+ const validatedPath = validatePath(filePath, workingDirectory);
596
+ const dir = await Bun.$ `ls -la ${validatedPath}`.text();
597
+ return {
598
+ success: true,
599
+ output: dir,
600
+ };
601
+ }
602
+ catch (error) {
603
+ return {
604
+ success: false,
605
+ error: error instanceof Error ? error.message : String(error),
606
+ };
607
+ }
608
+ },
609
+ git: async (params) => {
610
+ const command = params.command;
611
+ const args = params.args ?? [];
612
+ const cwd = params.cwd ?? workingDirectory;
613
+ try {
614
+ const gitArgs = ["git", command, ...args];
615
+ const proc = Bun.spawn(gitArgs, {
616
+ cwd,
617
+ stdout: "pipe",
618
+ stderr: "pipe",
619
+ });
620
+ const exitCode = await proc.exited;
621
+ const stdout = await new Response(proc.stdout).text();
622
+ const stderr = await new Response(proc.stderr).text();
623
+ if (exitCode !== 0) {
624
+ return {
625
+ success: false,
626
+ error: `git ${command} failed: ${stderr}`,
627
+ output: stdout,
628
+ };
629
+ }
630
+ return {
631
+ success: true,
632
+ output: stdout || `git ${command} completed successfully`,
633
+ };
634
+ }
635
+ catch (error) {
636
+ return {
637
+ success: false,
638
+ error: error instanceof Error ? error.message : String(error),
639
+ };
640
+ }
641
+ },
642
+ activity: async (params) => {
643
+ if (!opts.onActivityExecute) {
644
+ return {
645
+ success: false,
646
+ error: "Activity execution not configured (onActivityExecute callback required)",
647
+ };
648
+ }
649
+ try {
650
+ const templateId = params.templateId;
651
+ const variables = params.variables ?? {};
652
+ const reason = params.reason;
653
+ console.log(`[Tool:activity] Executing nested activity: ${templateId}`);
654
+ const result = await opts.onActivityExecute(templateId, variables, reason);
655
+ return {
656
+ success: true,
657
+ output: `Activity "${templateId}" executed successfully\n\nResult:\n${JSON.stringify(result, null, 2)}`,
658
+ metadata: { result },
659
+ };
660
+ }
661
+ catch (error) {
662
+ return {
663
+ success: false,
664
+ error: error instanceof Error ? error.message : String(error),
665
+ };
666
+ }
667
+ },
668
+ search_activities: async (params) => {
669
+ if (!opts.onSearchActivities) {
670
+ return {
671
+ success: false,
672
+ error: "Activity search not configured (onSearchActivities callback required)",
673
+ };
674
+ }
675
+ try {
676
+ const category = params.category;
677
+ const verbose = params.verbose ?? false;
678
+ console.log(`[Tool:search_activities] Searching activities:`, { category, verbose });
679
+ const result = await opts.onSearchActivities(category, verbose);
680
+ return {
681
+ success: true,
682
+ output: `Found ${result.count} activities:\n\n${JSON.stringify(result.activities, null, 2)}`,
683
+ metadata: { result },
684
+ };
685
+ }
686
+ catch (error) {
687
+ return {
688
+ success: false,
689
+ error: error instanceof Error ? error.message : String(error),
690
+ };
691
+ }
692
+ },
693
+ create_activity_goal_seeking: async (params) => {
694
+ if (!opts.onCreateActivity) {
695
+ return {
696
+ success: false,
697
+ error: "Activity creation not configured (onCreateActivity callback required)",
698
+ };
699
+ }
700
+ try {
701
+ const goalDescription = params.goalDescription;
702
+ const templateName = params.templateName;
703
+ const category = params.category;
704
+ const variables = params.variables ?? {};
705
+ console.log(`[Tool:create_activity_goal_seeking] Creating activity:`, {
706
+ templateName,
707
+ category,
708
+ goalDescription: goalDescription.substring(0, 100) + "...",
709
+ });
710
+ const result = await opts.onCreateActivity({
711
+ goalDescription,
712
+ templateName,
713
+ category,
714
+ variables,
715
+ });
716
+ return {
717
+ success: true,
718
+ output: `Activity template "${templateName}" created successfully\n\nTemplate ID: ${result.templateId}\n\nYou can now execute it with: activity(templateId: "${result.templateId}")`,
719
+ metadata: { result },
720
+ };
721
+ }
722
+ catch (error) {
723
+ return {
724
+ success: false,
725
+ error: error instanceof Error ? error.message : String(error),
726
+ };
727
+ }
728
+ },
729
+ impulse_create: async (params) => {
730
+ if (!opts.onImpulseCreate) {
731
+ return {
732
+ success: false,
733
+ error: "Impulse creation not configured (onImpulseCreate callback required)",
734
+ };
735
+ }
736
+ try {
737
+ const id = params.id;
738
+ const type = params.type;
739
+ const content = params.content;
740
+ const budget = params.budget;
741
+ const priority = params.priority;
742
+ const impulse = { id, type, content, budget, priority };
743
+ opts.onImpulseCreate(impulse);
744
+ return {
745
+ success: true,
746
+ output: `Impulse "${id}" created with type ${type} and budget ${budget} tokens`,
747
+ };
748
+ }
749
+ catch (error) {
750
+ return {
751
+ success: false,
752
+ error: error instanceof Error ? error.message : String(error),
753
+ };
754
+ }
755
+ },
756
+ // Merge in any custom tools provided by the host application
757
+ ...Object.fromEntries(Object.entries(opts.customTools ?? {}).map(([name, { handler }]) => [name, handler])),
758
+ };
759
+ }
760
+ /**
761
+ * Get all tool definitions as an array.
762
+ * @param extraDefinitions Additional tool definitions from custom tools (e.g. Perspective domain tools).
763
+ */
764
+ export function getAllToolDefinitions(extraDefinitions) {
765
+ const base = Object.values(toolDefinitions);
766
+ if (extraDefinitions && extraDefinitions.length > 0) {
767
+ return [...base, ...extraDefinitions];
768
+ }
769
+ return base;
770
+ }
771
+ //# sourceMappingURL=tools.js.map