@bubblebrain-ai/bubble 0.0.13 → 0.0.15

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 (80) hide show
  1. package/dist/agent/execution-governor.js +1 -1
  2. package/dist/agent/tool-intent.js +1 -0
  3. package/dist/agent.d.ts +2 -0
  4. package/dist/agent.js +589 -316
  5. package/dist/approval/controller.d.ts +1 -0
  6. package/dist/approval/controller.js +20 -3
  7. package/dist/approval/tool-helper.js +2 -0
  8. package/dist/approval/types.d.ts +14 -1
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.js +12 -0
  11. package/dist/context/compact.js +9 -3
  12. package/dist/context/projector.js +27 -12
  13. package/dist/debug-trace.d.ts +27 -0
  14. package/dist/debug-trace.js +385 -0
  15. package/dist/feishu/agent-host/approval-card.js +9 -0
  16. package/dist/feishu/serve.js +7 -1
  17. package/dist/main.js +41 -0
  18. package/dist/model-catalog.js +1 -0
  19. package/dist/orchestrator/default-hooks.js +19 -8
  20. package/dist/orchestrator/hooks.d.ts +1 -0
  21. package/dist/prompt/environment.js +2 -0
  22. package/dist/prompt/reminders.d.ts +5 -6
  23. package/dist/prompt/reminders.js +8 -9
  24. package/dist/prompt/runtime.js +2 -2
  25. package/dist/provider-openai-codex.d.ts +7 -0
  26. package/dist/provider-openai-codex.js +265 -124
  27. package/dist/provider-registry.d.ts +2 -0
  28. package/dist/provider-registry.js +58 -9
  29. package/dist/provider.d.ts +3 -0
  30. package/dist/provider.js +5 -1
  31. package/dist/session-log.js +13 -1
  32. package/dist/slash-commands/commands.js +12 -0
  33. package/dist/slash-commands/types.d.ts +2 -0
  34. package/dist/stats/usage.d.ts +52 -0
  35. package/dist/stats/usage.js +414 -0
  36. package/dist/tools/apply-patch.d.ts +9 -0
  37. package/dist/tools/apply-patch.js +330 -0
  38. package/dist/tools/bash.js +205 -44
  39. package/dist/tools/edit-apply.d.ts +5 -2
  40. package/dist/tools/edit-apply.js +221 -31
  41. package/dist/tools/edit.js +12 -3
  42. package/dist/tools/file-mutation-queue.d.ts +1 -0
  43. package/dist/tools/file-mutation-queue.js +12 -1
  44. package/dist/tools/index.d.ts +2 -0
  45. package/dist/tools/index.js +7 -1
  46. package/dist/tools/patch-apply.d.ts +41 -0
  47. package/dist/tools/patch-apply.js +312 -0
  48. package/dist/tools/server-manager.d.ts +36 -0
  49. package/dist/tools/server-manager.js +234 -0
  50. package/dist/tools/server.d.ts +6 -0
  51. package/dist/tools/server.js +245 -0
  52. package/dist/tools/write.d.ts +3 -6
  53. package/dist/tools/write.js +26 -46
  54. package/dist/tui/display-history.d.ts +1 -0
  55. package/dist/tui/display-history.js +5 -4
  56. package/dist/tui/edit-diff.js +6 -1
  57. package/dist/tui/model-picker-data.d.ts +10 -0
  58. package/dist/tui/model-picker-data.js +32 -0
  59. package/dist/tui/run.d.ts +2 -0
  60. package/dist/tui/run.js +717 -122
  61. package/dist/tui/tool-renderers/fallback.js +1 -1
  62. package/dist/tui/tool-renderers/write-preview.js +2 -0
  63. package/dist/tui/trace-groups.js +10 -3
  64. package/dist/tui-ink/app.js +1 -4
  65. package/dist/tui-ink/approval/approval-dialog.js +7 -1
  66. package/dist/tui-ink/display-history.d.ts +1 -0
  67. package/dist/tui-ink/display-history.js +5 -4
  68. package/dist/tui-ink/message-list.js +14 -8
  69. package/dist/tui-ink/trace-groups.js +1 -1
  70. package/dist/tui-opentui/app.js +2 -0
  71. package/dist/tui-opentui/approval/approval-dialog.js +7 -1
  72. package/dist/tui-opentui/display-history.d.ts +1 -0
  73. package/dist/tui-opentui/display-history.js +5 -4
  74. package/dist/tui-opentui/edit-diff.js +6 -1
  75. package/dist/tui-opentui/message-list.js +6 -3
  76. package/dist/tui-opentui/trace-groups.js +10 -3
  77. package/dist/types.d.ts +12 -2
  78. package/dist/update/index.d.ts +46 -0
  79. package/dist/update/index.js +240 -0
  80. package/package.json +1 -1
@@ -17,6 +17,36 @@ function stripBom(content) {
17
17
  function normalizeToLF(text) {
18
18
  return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
19
19
  }
20
+ function unescapeOverEscaped(text) {
21
+ return text
22
+ .replace(/\\u([0-9a-fA-F]{4})/g, (_match, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
23
+ .replace(/\\n/g, "\n")
24
+ .replace(/\\t/g, "\t")
25
+ .replace(/\\r/g, "\r")
26
+ .replace(/\\f/g, "\f")
27
+ .replace(/\\b/g, "\b")
28
+ .replace(/\\v/g, "\v")
29
+ .replace(/\\"/g, '"')
30
+ .replace(/\\\\/g, "\\");
31
+ }
32
+ function addTextCandidate(candidates, seen, text, mode) {
33
+ if (text.length === 0 || seen.has(text))
34
+ return;
35
+ seen.add(text);
36
+ candidates.push({ text, mode });
37
+ }
38
+ function generateTextCandidates(oldText) {
39
+ const candidates = [];
40
+ const seen = new Set();
41
+ const trimmed = oldText.trim();
42
+ const unescaped = normalizeToLF(unescapeOverEscaped(oldText));
43
+ const unescapedTrimmed = normalizeToLF(unescapeOverEscaped(trimmed));
44
+ addTextCandidate(candidates, seen, oldText, "exact");
45
+ addTextCandidate(candidates, seen, trimmed, "trimmed");
46
+ addTextCandidate(candidates, seen, unescaped, "unescaped");
47
+ addTextCandidate(candidates, seen, unescapedTrimmed, "unescaped");
48
+ return candidates;
49
+ }
20
50
  function restoreLineEndings(text, lineEnding) {
21
51
  return lineEnding === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
22
52
  }
@@ -29,6 +59,9 @@ function normalizeLineForMatch(line) {
29
59
  .replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, "-")
30
60
  .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " ");
31
61
  }
62
+ function normalizeLeadingWhitespaceForMatch(line) {
63
+ return normalizeLineForMatch(line).replace(/^\s+/, " ");
64
+ }
32
65
  function findAllOccurrences(content, needle) {
33
66
  const indexes = [];
34
67
  if (needle.length === 0)
@@ -67,6 +100,10 @@ function normalizedOldNonBlankLines(oldText) {
67
100
  .map((line) => normalizeLineForMatch(line.text))
68
101
  .filter((line) => line.trim().length > 0);
69
102
  }
103
+ function singleNonBlankOldLine(oldText) {
104
+ const lines = normalizedOldNonBlankLines(oldText);
105
+ return lines.length === 1 ? lines[0] : undefined;
106
+ }
70
107
  function findNormalizedLineMatches(content, oldText) {
71
108
  const contentLines = splitLines(content);
72
109
  const searchable = nonBlankLines(contentLines);
@@ -90,6 +127,103 @@ function findNormalizedLineMatches(content, oldText) {
90
127
  }
91
128
  return matches;
92
129
  }
130
+ function findSmartLineMatches(content, oldText) {
131
+ const contentLines = splitLines(content);
132
+ const oldLines = splitLines(oldText).map((line) => normalizeLeadingWhitespaceForMatch(line.text));
133
+ if (oldLines.length === 0 || oldLines.every((line) => line.length === 0))
134
+ return [];
135
+ const matches = [];
136
+ for (let i = 0; i <= contentLines.length - oldLines.length; i++) {
137
+ let matched = true;
138
+ for (let j = 0; j < oldLines.length; j++) {
139
+ const actual = normalizeLeadingWhitespaceForMatch(contentLines[i + j].text);
140
+ if (actual !== oldLines[j]) {
141
+ matched = false;
142
+ break;
143
+ }
144
+ }
145
+ if (matched) {
146
+ const first = contentLines[i];
147
+ const last = contentLines[i + oldLines.length - 1];
148
+ matches.push({ start: first.start, end: last.endNoNewline });
149
+ }
150
+ }
151
+ return matches;
152
+ }
153
+ function splitMarkdownTableCells(line) {
154
+ const normalized = normalizeLineForMatch(line).trim();
155
+ if (!normalized.startsWith("|") || !normalized.endsWith("|"))
156
+ return undefined;
157
+ const parts = [];
158
+ let current = "";
159
+ let escaped = false;
160
+ for (const char of normalized) {
161
+ if (escaped) {
162
+ current += char;
163
+ escaped = false;
164
+ continue;
165
+ }
166
+ if (char === "\\") {
167
+ current += char;
168
+ escaped = true;
169
+ continue;
170
+ }
171
+ if (char === "|") {
172
+ parts.push(current);
173
+ current = "";
174
+ continue;
175
+ }
176
+ current += char;
177
+ }
178
+ parts.push(current);
179
+ if (parts.length < 4 || parts[0] !== "" || parts[parts.length - 1] !== "")
180
+ return undefined;
181
+ const cells = parts.slice(1, -1).map((cell) => cell.trim());
182
+ return cells.length >= 2 ? cells : undefined;
183
+ }
184
+ function sameCells(a, b) {
185
+ return a.length === b.length && a.every((cell, index) => cell === b[index]);
186
+ }
187
+ function findMarkdownTableMatches(content, oldText) {
188
+ const oldLine = singleNonBlankOldLine(oldText);
189
+ if (!oldLine)
190
+ return [];
191
+ const oldCells = splitMarkdownTableCells(oldLine);
192
+ if (!oldCells)
193
+ return [];
194
+ const matches = [];
195
+ for (const line of splitLines(content)) {
196
+ const cells = splitMarkdownTableCells(line.text);
197
+ if (cells && sameCells(cells, oldCells)) {
198
+ matches.push({ start: line.start, end: line.endNoNewline });
199
+ }
200
+ }
201
+ return matches;
202
+ }
203
+ function collapseInlineWhitespace(text) {
204
+ return normalizeLineForMatch(text).trim().replace(/[ \t]+/g, " ");
205
+ }
206
+ function isDocumentLikePath(path) {
207
+ return !!path && /\.(?:md|mdx|markdown|txt|rst|adoc)$/i.test(path);
208
+ }
209
+ function findSingleLineWhitespaceMatches(content, oldText, options) {
210
+ if (!isDocumentLikePath(options?.path))
211
+ return [];
212
+ const oldLine = singleNonBlankOldLine(oldText);
213
+ if (!oldLine)
214
+ return [];
215
+ const normalizedOld = collapseInlineWhitespace(oldLine);
216
+ if (normalizedOld.length === 0)
217
+ return [];
218
+ const matches = [];
219
+ for (const line of splitLines(content)) {
220
+ const collapsed = collapseInlineWhitespace(line.text);
221
+ if (collapsed === normalizedOld && line.text !== oldLine) {
222
+ matches.push({ start: line.start, end: line.endNoNewline });
223
+ }
224
+ }
225
+ return matches;
226
+ }
93
227
  function summarizeOldText(oldText) {
94
228
  const firstLine = normalizeToLF(oldText).split("\n").find((line) => line.trim().length > 0) ?? oldText;
95
229
  return firstLine.length > 80 ? `${firstLine.slice(0, 80)}...` : firstLine;
@@ -114,7 +248,7 @@ function findBestLineHint(content, oldText) {
114
248
  const startLine = contentLines[best.index].lineIndex + 1;
115
249
  return `Closest line-based candidate starts near line ${startLine} and matched ${best.score}/${oldLines.length} non-blank lines.`;
116
250
  }
117
- function matchEdit(content, edit, index, total) {
251
+ function matchEdit(content, edit, index, total, options) {
118
252
  if (edit.oldText.length === 0) {
119
253
  throw new EditApplyError(total === 1 ? "Error: oldText must not be empty." : `Error: edits[${index}].oldText must not be empty.`);
120
254
  }
@@ -127,37 +261,93 @@ function matchEdit(content, edit, index, total) {
127
261
  "",
128
262
  "Common causes and how to escape:",
129
263
  "- Your tokenizer may be folding repeated characters into a single token (hex colors like '#ec489' vs '#ec4899', repeated digits, etc.). The two strings feel different in your head but serialize to identical bytes.",
130
- "- Use the write tool with overwrite=true and the full new content for full-file replacements that hinge on a single repeated character or trailing digit.",
264
+ "- Use the write tool with the full new content for full-file replacements that hinge on a single repeated character or trailing digit.",
131
265
  "- Or re-read the file with the read tool, then copy the exact bytes you want to replace before retrying.",
132
266
  ].join("\n"));
133
267
  }
134
268
  const oldText = normalizeToLF(edit.oldText);
135
- const exact = findAllOccurrences(content, oldText);
136
- if (exact.length === 1) {
137
- return { editIndex: index, mode: "exact", start: exact[0], end: exact[0] + oldText.length };
269
+ const candidates = generateTextCandidates(oldText);
270
+ for (const candidate of candidates) {
271
+ const exact = findAllOccurrences(content, candidate.text);
272
+ if (exact.length === 1) {
273
+ return { editIndex: index, mode: candidate.mode, start: exact[0], end: exact[0] + candidate.text.length };
274
+ }
275
+ if (exact.length > 1) {
276
+ const recovery = [
277
+ "",
278
+ "Extend oldText with more surrounding context (the lines immediately before/after) until it uniquely identifies the intended span.",
279
+ ].join("\n");
280
+ const duplicateReason = candidate.mode === "exact"
281
+ ? `appears ${exact.length} times in file`
282
+ : `matched ${exact.length} times after ${candidate.mode} matching`;
283
+ throw new EditApplyError(total === 1
284
+ ? `Error: oldText ${duplicateReason}. Must be unique: "${summarizeOldText(oldText)}"${recovery}`
285
+ : `Error: edits[${index}].oldText ${duplicateReason}. Must be unique: "${summarizeOldText(oldText)}"${recovery}`);
286
+ }
138
287
  }
139
- if (exact.length > 1) {
140
- const recovery = [
141
- "",
142
- "Extend oldText with more surrounding context (the lines immediately before/after) until it uniquely identifies the intended span.",
143
- ].join("\n");
144
- throw new EditApplyError(total === 1
145
- ? `Error: oldText appears ${exact.length} times in file. Must be unique: "${summarizeOldText(oldText)}"${recovery}`
146
- : `Error: edits[${index}].oldText appears ${exact.length} times in file. Must be unique: "${summarizeOldText(oldText)}"${recovery}`);
147
- }
148
- const normalizedLineMatches = findNormalizedLineMatches(content, oldText);
149
- if (normalizedLineMatches.length === 1) {
150
- return {
151
- editIndex: index,
152
- mode: "normalized-line",
153
- start: normalizedLineMatches[0].start,
154
- end: normalizedLineMatches[0].end,
155
- };
156
- }
157
- if (normalizedLineMatches.length > 1) {
158
- throw new EditApplyError(total === 1
159
- ? `Error: oldText matched ${normalizedLineMatches.length} normalized line blocks in file. Provide more surrounding context.`
160
- : `Error: edits[${index}].oldText matched ${normalizedLineMatches.length} normalized line blocks in file. Provide more surrounding context.`);
288
+ for (const candidate of candidates) {
289
+ const normalizedLineMatches = findNormalizedLineMatches(content, candidate.text);
290
+ if (normalizedLineMatches.length === 1) {
291
+ return {
292
+ editIndex: index,
293
+ mode: candidate.mode === "exact" ? "normalized-line" : candidate.mode,
294
+ start: normalizedLineMatches[0].start,
295
+ end: normalizedLineMatches[0].end,
296
+ };
297
+ }
298
+ if (normalizedLineMatches.length > 1) {
299
+ throw new EditApplyError(total === 1
300
+ ? `Error: oldText matched ${normalizedLineMatches.length} normalized line blocks in file. Provide more surrounding context.`
301
+ : `Error: edits[${index}].oldText matched ${normalizedLineMatches.length} normalized line blocks in file. Provide more surrounding context.`);
302
+ }
303
+ }
304
+ for (const candidate of candidates) {
305
+ const markdownTableMatches = findMarkdownTableMatches(content, candidate.text);
306
+ if (markdownTableMatches.length === 1) {
307
+ return {
308
+ editIndex: index,
309
+ mode: "markdown-table",
310
+ start: markdownTableMatches[0].start,
311
+ end: markdownTableMatches[0].end,
312
+ };
313
+ }
314
+ if (markdownTableMatches.length > 1) {
315
+ throw new EditApplyError(total === 1
316
+ ? `Error: oldText matched ${markdownTableMatches.length} markdown table rows in file. Provide more surrounding context.`
317
+ : `Error: edits[${index}].oldText matched ${markdownTableMatches.length} markdown table rows in file. Provide more surrounding context.`);
318
+ }
319
+ }
320
+ for (const candidate of candidates) {
321
+ const whitespaceMatches = findSingleLineWhitespaceMatches(content, candidate.text, options);
322
+ if (whitespaceMatches.length === 1) {
323
+ return {
324
+ editIndex: index,
325
+ mode: "single-line-whitespace",
326
+ start: whitespaceMatches[0].start,
327
+ end: whitespaceMatches[0].end,
328
+ };
329
+ }
330
+ if (whitespaceMatches.length > 1) {
331
+ throw new EditApplyError(total === 1
332
+ ? `Error: oldText matched ${whitespaceMatches.length} whitespace-normalized lines in file. Provide more surrounding context.`
333
+ : `Error: edits[${index}].oldText matched ${whitespaceMatches.length} whitespace-normalized lines in file. Provide more surrounding context.`);
334
+ }
335
+ }
336
+ for (const candidate of candidates) {
337
+ const smartLineMatches = findSmartLineMatches(content, candidate.text);
338
+ if (smartLineMatches.length === 1) {
339
+ return {
340
+ editIndex: index,
341
+ mode: "smart-line",
342
+ start: smartLineMatches[0].start,
343
+ end: smartLineMatches[0].end,
344
+ };
345
+ }
346
+ if (smartLineMatches.length > 1) {
347
+ throw new EditApplyError(total === 1
348
+ ? `Error: oldText matched ${smartLineMatches.length} indentation-normalized line blocks in file. Provide more surrounding context.`
349
+ : `Error: edits[${index}].oldText matched ${smartLineMatches.length} indentation-normalized line blocks in file. Provide more surrounding context.`);
350
+ }
161
351
  }
162
352
  const hint = findBestLineHint(content, oldText);
163
353
  const hintSuffix = hint ? `\n${hint}` : "";
@@ -166,7 +356,7 @@ function matchEdit(content, edit, index, total) {
166
356
  "How to recover:",
167
357
  "- Re-read the file with the read tool to see its current bytes; the file may have been changed by a prior edit this turn.",
168
358
  "- Shorten oldText to a smaller unique anchor and try again. Long multi-line anchors are fragile to whitespace and indentation.",
169
- "- If many lines need to change, use the write tool with overwrite=true and the full new content instead of stacking edits.",
359
+ "- If many lines need to change, use the write tool with the full new content instead of stacking edits.",
170
360
  ].join("\n");
171
361
  throw new EditApplyError(total === 1
172
362
  ? `Error: oldText not found in file: "${summarizeOldText(oldText)}"${hintSuffix}${recovery}`
@@ -182,7 +372,7 @@ function assertNoOverlaps(matches) {
182
372
  }
183
373
  }
184
374
  }
185
- export function applyEditsToContent(rawContent, edits) {
375
+ export function applyEditsToContent(rawContent, edits, options) {
186
376
  if (!Array.isArray(edits) || edits.length === 0) {
187
377
  throw new EditApplyError("Error: No edits provided");
188
378
  }
@@ -193,7 +383,7 @@ export function applyEditsToContent(rawContent, edits) {
193
383
  oldText: normalizeToLF(edit.oldText),
194
384
  newText: normalizeToLF(edit.newText),
195
385
  }));
196
- const matches = normalizedEdits.map((edit, index) => matchEdit(normalizedOriginal, edit, index, normalizedEdits.length));
386
+ const matches = normalizedEdits.map((edit, index) => matchEdit(normalizedOriginal, edit, index, normalizedEdits.length, options));
197
387
  assertNoOverlaps(matches);
198
388
  const byDescendingStart = [...matches].sort((a, b) => b.start - a.start);
199
389
  let normalizedNext = normalizedOriginal;
@@ -208,7 +398,7 @@ export function applyEditsToContent(rawContent, edits) {
208
398
  "Common causes and how to escape:",
209
399
  "- oldText and newText are byte-identical. Verify newText actually contains the intended change (a missing trailing char like turning '#ec489' into '#ec4899' is a frequent culprit).",
210
400
  "- The file already contains newText. Re-read the file to confirm the current state before editing again.",
211
- "- For wholesale rewrites, use the write tool with overwrite=true and the full new content instead.",
401
+ "- For wholesale rewrites, use the write tool with the full new content instead.",
212
402
  ].join("\n"));
213
403
  }
214
404
  return {
@@ -18,7 +18,7 @@ export function createEditTool(cwd, approval, lsp, fileState) {
18
18
  name: "edit",
19
19
  effect: "write_direct",
20
20
  requiresApproval: true,
21
- description: "Apply targeted string replacements to a file. Prefer exact oldText. The tool can tolerate line ending, trailing whitespace, Unicode punctuation/space, and blank-line differences only when the target is unique.",
21
+ description: "Apply targeted string replacements to a file. Prefer exact oldText copied from a recent read. The tool can tolerate common AI formatting mistakes such as extra leading/trailing whitespace, over-escaped sequences, line ending differences, indentation differences, trailing whitespace, Unicode punctuation/space, and blank-line differences when the target is unique.",
22
22
  parameters: {
23
23
  type: "object",
24
24
  properties: {
@@ -62,11 +62,20 @@ export function createEditTool(cwd, approval, lsp, fileState) {
62
62
  const original = await readFile(filePath, "utf-8");
63
63
  let applied;
64
64
  try {
65
- applied = applyEditsToContent(original, args.edits);
65
+ applied = applyEditsToContent(original, args.edits, { path: filePath });
66
66
  }
67
67
  catch (err) {
68
68
  if (err instanceof EditApplyError) {
69
- return { content: err.message, isError: true, status: err.status };
69
+ return {
70
+ content: err.message,
71
+ isError: true,
72
+ status: err.status,
73
+ metadata: {
74
+ kind: "edit",
75
+ path: filePath,
76
+ reason: err.status,
77
+ },
78
+ };
70
79
  }
71
80
  throw err;
72
81
  }
@@ -1 +1,2 @@
1
1
  export declare function withFileMutationQueue<T>(filePath: string, fn: () => Promise<T>): Promise<T>;
2
+ export declare function withFileMutationQueues<T>(filePaths: string[], fn: () => Promise<T>): Promise<T>;
@@ -11,7 +11,18 @@ function queueKey(filePath) {
11
11
  }
12
12
  }
13
13
  export async function withFileMutationQueue(filePath, fn) {
14
- const key = queueKey(filePath);
14
+ return withQueueKey(queueKey(filePath), fn);
15
+ }
16
+ export async function withFileMutationQueues(filePaths, fn) {
17
+ const keys = [...new Set(filePaths.map(queueKey))].sort();
18
+ const run = (index) => {
19
+ if (index >= keys.length)
20
+ return fn();
21
+ return withQueueKey(keys[index], () => run(index + 1));
22
+ };
23
+ return run(0);
24
+ }
25
+ async function withQueueKey(key, fn) {
15
26
  const current = queues.get(key) ?? Promise.resolve();
16
27
  let release;
17
28
  const next = new Promise((resolveNext) => {
@@ -3,8 +3,10 @@
3
3
  */
4
4
  export { createReadTool } from "./read.js";
5
5
  export { createBashTool } from "./bash.js";
6
+ export { createManagedServerTools } from "./server.js";
6
7
  export { createWriteTool } from "./write.js";
7
8
  export { createEditTool } from "./edit.js";
9
+ export { createApplyPatchTool } from "./apply-patch.js";
8
10
  export { createGlobTool } from "./glob.js";
9
11
  export { createGrepTool } from "./grep.js";
10
12
  export { createLspTool } from "./lsp.js";
@@ -3,8 +3,10 @@
3
3
  */
4
4
  export { createReadTool } from "./read.js";
5
5
  export { createBashTool } from "./bash.js";
6
+ export { createManagedServerTools } from "./server.js";
6
7
  export { createWriteTool } from "./write.js";
7
8
  export { createEditTool } from "./edit.js";
9
+ export { createApplyPatchTool } from "./apply-patch.js";
8
10
  export { createGlobTool } from "./glob.js";
9
11
  export { createGrepTool } from "./grep.js";
10
12
  export { createLspTool } from "./lsp.js";
@@ -19,7 +21,9 @@ export { createToolSearchTool } from "./tool-search.js";
19
21
  export { createQuestionTool } from "./question.js";
20
22
  export { createMemoryReadSummaryTool, createMemorySearchTool } from "./memory.js";
21
23
  import { createBashTool } from "./bash.js";
24
+ import { createManagedServerTools } from "./server.js";
22
25
  import { createEditTool } from "./edit.js";
26
+ import { createApplyPatchTool } from "./apply-patch.js";
23
27
  import { createExitPlanModeTool } from "./exit-plan-mode.js";
24
28
  import { createGlobTool } from "./glob.js";
25
29
  import { createGrepTool } from "./grep.js";
@@ -44,8 +48,10 @@ export function createAllTools(cwd, skillRegistry, options = {}) {
44
48
  return [
45
49
  createReadTool(cwd, approval, lsp, fileState),
46
50
  createBashTool(cwd, approval, fileState),
47
- createWriteTool(cwd, { refuseOverwrite: true }, approval, lsp, fileState),
51
+ ...createManagedServerTools(cwd, approval),
52
+ createWriteTool(cwd, {}, approval, lsp, fileState),
48
53
  createEditTool(cwd, approval, lsp, fileState),
54
+ createApplyPatchTool(cwd, approval, lsp, fileState),
49
55
  createGlobTool(cwd),
50
56
  createGrepTool(cwd),
51
57
  createLspTool(cwd, lsp, approval),
@@ -0,0 +1,41 @@
1
+ export type PatchFileOperation = {
2
+ type: "add";
3
+ path: string;
4
+ lines: string[];
5
+ } | {
6
+ type: "delete";
7
+ path: string;
8
+ } | {
9
+ type: "update";
10
+ path: string;
11
+ movePath?: string;
12
+ chunks: PatchChunk[];
13
+ };
14
+ export interface PatchChunk {
15
+ header: string;
16
+ lines: PatchLine[];
17
+ }
18
+ export type PatchLine = {
19
+ kind: "context";
20
+ text: string;
21
+ } | {
22
+ kind: "remove";
23
+ text: string;
24
+ } | {
25
+ kind: "add";
26
+ text: string;
27
+ };
28
+ export interface ParsedApplyPatch {
29
+ operations: PatchFileOperation[];
30
+ }
31
+ export interface PatchedContentResult {
32
+ content: string;
33
+ usedFallback: boolean;
34
+ }
35
+ export declare class PatchApplyError extends Error {
36
+ readonly status: "no_match" | "blocked";
37
+ constructor(message: string, status?: "no_match" | "blocked");
38
+ }
39
+ export declare function parseApplyPatch(patchText: string): ParsedApplyPatch;
40
+ export declare function buildAddedFileContent(lines: string[]): string;
41
+ export declare function applyPatchChunks(rawContent: string, chunks: PatchChunk[], path: string): PatchedContentResult;