qlogicagent 0.2.1 → 0.3.0

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 (229) hide show
  1. package/dist/agent.js +1 -0
  2. package/dist/cli.js +9 -0
  3. package/dist/contracts.js +1 -0
  4. package/dist/index.js +5 -15
  5. package/dist/orchestration.js +118 -0
  6. package/package.json +56 -42
  7. package/dist/agent/agent.js +0 -113
  8. package/dist/agent/tool-loop.js +0 -575
  9. package/dist/agent/types.js +0 -14
  10. package/dist/cli/main.js +0 -23
  11. package/dist/cli/stdio-server.js +0 -463
  12. package/dist/config/config.js +0 -21
  13. package/dist/contracts/hooks.js +0 -7
  14. package/dist/contracts/index.js +0 -10
  15. package/dist/contracts/planner.js +0 -2
  16. package/dist/contracts/skill-candidate.js +0 -195
  17. package/dist/contracts/todo.js +0 -9
  18. package/dist/llm/builtin-providers.js +0 -531
  19. package/dist/llm/index.js +0 -14
  20. package/dist/llm/llm-client.js +0 -67
  21. package/dist/llm/model-catalog.js +0 -191
  22. package/dist/llm/provider-def.js +0 -12
  23. package/dist/llm/provider-registry.js +0 -147
  24. package/dist/llm/transport.js +0 -27
  25. package/dist/llm/transports/anthropic-messages.js +0 -293
  26. package/dist/llm/transports/openai-chat.js +0 -165
  27. package/dist/orchestration/agent-registry.js +0 -116
  28. package/dist/orchestration/approval-aware-tool-plan.js +0 -87
  29. package/dist/orchestration/context-compression.js +0 -583
  30. package/dist/orchestration/conversation-repair.js +0 -429
  31. package/dist/orchestration/curator-scheduler.js +0 -135
  32. package/dist/orchestration/embedded-failover-policy.js +0 -168
  33. package/dist/orchestration/error-classification.js +0 -77
  34. package/dist/orchestration/failover-classification.js +0 -381
  35. package/dist/orchestration/failover-error.js +0 -198
  36. package/dist/orchestration/fork-subagent.js +0 -98
  37. package/dist/orchestration/index.js +0 -267
  38. package/dist/orchestration/memory-flush-policy.js +0 -85
  39. package/dist/orchestration/memory-provider.js +0 -2
  40. package/dist/orchestration/parallel-tool-calls.js +0 -59
  41. package/dist/orchestration/prompt-cache-strategy.js +0 -228
  42. package/dist/orchestration/reactive-compact.js +0 -78
  43. package/dist/orchestration/retry-loop.js +0 -24
  44. package/dist/orchestration/skill-candidate.js +0 -141
  45. package/dist/orchestration/skill-consolidation.js +0 -220
  46. package/dist/orchestration/skill-improvement.js +0 -66
  47. package/dist/orchestration/skill-similarity.js +0 -131
  48. package/dist/orchestration/streaming-tool-executor.js +0 -96
  49. package/dist/orchestration/team-orchestration.js +0 -369
  50. package/dist/orchestration/team-tool-loop-wiring.js +0 -147
  51. package/dist/orchestration/tool-choice-policy.js +0 -164
  52. package/dist/orchestration/tool-loop-state.js +0 -133
  53. package/dist/orchestration/tool-schema.js +0 -297
  54. package/dist/orchestration/transcript-repair.js +0 -426
  55. package/dist/orchestration/turn-loop-guard.js +0 -92
  56. package/dist/orchestration/web-browser-policy.js +0 -39
  57. package/dist/runtime/context-compression.js +0 -274
  58. package/dist/runtime/hook-registry.js +0 -53
  59. package/dist/runtime/memory-hooks.js +0 -65
  60. package/dist/runtime/tool-eligibility.js +0 -111
  61. package/dist/skills/index.js +0 -82
  62. package/dist/skills/memory-extractor.js +0 -173
  63. package/dist/skills/memory-query-tool.js +0 -127
  64. package/dist/skills/memory-store.js +0 -228
  65. package/dist/skills/memory-tool.js +0 -192
  66. package/dist/skills/portable-tool.js +0 -14
  67. package/dist/skills/qmemory-adapter.js +0 -165
  68. package/dist/skills/skill-frontmatter.js +0 -344
  69. package/dist/skills/skill-guard.js +0 -229
  70. package/dist/skills/skill-loader.js +0 -303
  71. package/dist/skills/skill-source.js +0 -126
  72. package/dist/skills/skill-types.js +0 -6
  73. package/dist/skills/think-tool.js +0 -59
  74. package/dist/skills/todo-tool.js +0 -114
  75. package/dist/skills/tools/agent-tool.js +0 -142
  76. package/dist/skills/tools/apply-patch-tool.js +0 -184
  77. package/dist/skills/tools/ask-user-tool.js +0 -121
  78. package/dist/skills/tools/brief-tool.js +0 -95
  79. package/dist/skills/tools/browser-tool.js +0 -155
  80. package/dist/skills/tools/checkpoint-tool.js +0 -102
  81. package/dist/skills/tools/config-tool.js +0 -143
  82. package/dist/skills/tools/cron-tool.js +0 -175
  83. package/dist/skills/tools/edit-tool.js +0 -70
  84. package/dist/skills/tools/exec-tool.js +0 -133
  85. package/dist/skills/tools/image-generate-tool.js +0 -67
  86. package/dist/skills/tools/instructions-tool.js +0 -187
  87. package/dist/skills/tools/lsp-tool.js +0 -227
  88. package/dist/skills/tools/mcp-client-types.js +0 -53
  89. package/dist/skills/tools/mcp-tool.js +0 -503
  90. package/dist/skills/tools/memory-tool.js +0 -88
  91. package/dist/skills/tools/monitor-tool.js +0 -131
  92. package/dist/skills/tools/music-generate-tool.js +0 -62
  93. package/dist/skills/tools/notify-tool.js +0 -62
  94. package/dist/skills/tools/patch-tool.js +0 -505
  95. package/dist/skills/tools/pdf-tool.js +0 -88
  96. package/dist/skills/tools/plan-mode-tool.js +0 -122
  97. package/dist/skills/tools/read-tool.js +0 -84
  98. package/dist/skills/tools/repl-tool.js +0 -69
  99. package/dist/skills/tools/search-tool.js +0 -225
  100. package/dist/skills/tools/send-message-tool.js +0 -76
  101. package/dist/skills/tools/skill-list-tool.js +0 -54
  102. package/dist/skills/tools/skill-manage-tool.js +0 -153
  103. package/dist/skills/tools/skill-view-tool.js +0 -72
  104. package/dist/skills/tools/sleep-tool.js +0 -81
  105. package/dist/skills/tools/structured-output-tool.js +0 -176
  106. package/dist/skills/tools/task-tool.js +0 -161
  107. package/dist/skills/tools/team-tool.js +0 -105
  108. package/dist/skills/tools/tool-search-tool.js +0 -110
  109. package/dist/skills/tools/tts-tool.js +0 -45
  110. package/dist/skills/tools/video-edit-tool.js +0 -74
  111. package/dist/skills/tools/video-generate-tool.js +0 -66
  112. package/dist/skills/tools/video-merge-tool.js +0 -92
  113. package/dist/skills/tools/video-upscale-tool.js +0 -52
  114. package/dist/skills/tools/web-fetch-tool.js +0 -92
  115. package/dist/skills/tools/web-search-tool.js +0 -86
  116. package/dist/skills/tools/worktree-tool.js +0 -147
  117. package/dist/skills/tools/write-tool.js +0 -81
  118. /package/dist/{agent → types/agent}/agent.d.ts +0 -0
  119. /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
  120. /package/dist/{agent → types/agent}/types.d.ts +0 -0
  121. /package/dist/{cli → types/cli}/main.d.ts +0 -0
  122. /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
  123. /package/dist/{config → types/config}/config.d.ts +0 -0
  124. /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
  125. /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
  126. /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
  127. /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
  128. /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
  129. /package/dist/{index.d.ts → types/index.d.ts} +0 -0
  130. /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
  131. /package/dist/{llm → types/llm}/index.d.ts +0 -0
  132. /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
  133. /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
  134. /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
  135. /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
  136. /package/dist/{llm → types/llm}/transport.d.ts +0 -0
  137. /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
  138. /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
  139. /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
  140. /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
  141. /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
  142. /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
  143. /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
  144. /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
  145. /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
  146. /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
  147. /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
  148. /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
  149. /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
  150. /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
  151. /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
  152. /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
  153. /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
  154. /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
  155. /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
  156. /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
  157. /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
  158. /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
  159. /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
  160. /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
  161. /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
  162. /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
  163. /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
  164. /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
  165. /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
  166. /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
  167. /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
  168. /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
  169. /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
  170. /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
  171. /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
  172. /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
  173. /package/dist/{skills → types/skills}/index.d.ts +0 -0
  174. /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
  175. /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
  176. /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
  177. /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
  178. /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
  179. /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
  180. /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
  181. /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
  182. /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
  183. /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
  184. /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
  185. /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
  186. /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
  187. /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
  188. /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
  189. /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
  190. /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
  191. /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
  192. /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
  193. /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
  194. /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
  195. /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
  196. /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
  197. /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
  198. /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
  199. /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
  200. /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
  201. /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
  202. /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
  203. /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
  204. /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
  205. /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
  206. /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
  207. /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
  208. /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
  209. /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
  210. /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
  211. /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
  212. /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
  213. /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
  214. /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
  215. /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
  216. /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
  217. /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
  218. /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
  219. /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
  220. /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
  221. /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
  222. /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
  223. /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
  224. /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
  225. /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
  226. /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
  227. /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
  228. /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
  229. /package/dist/{skills → types/skills}/tools/write-tool.d.ts +0 -0
@@ -1,505 +0,0 @@
1
- // ============================================================
2
- // Patch Tool — fuzzy-match file editing with 9-strategy degradation.
3
- // Reference: hermes-agent-main/tools/fuzzy_match.py (9-strategy chain)
4
- // hermes-agent-main/tools/patch_parser.py (V4A format)
5
- // Category: coding
6
- // ============================================================
7
- export const PATCH_TOOL_NAME = "patch";
8
- export const PATCH_TOOL_SCHEMA = {
9
- type: "object",
10
- properties: {
11
- input: {
12
- type: "string",
13
- description: "Patch content. Supports two formats:\n" +
14
- "1) V4A unified diff (*** Begin Patch / *** End Patch markers)\n" +
15
- "2) Simple find-replace: first line is file path, then <<<< SEARCH / ==== / >>>> REPLACE blocks.\n" +
16
- "Fuzzy matching automatically handles whitespace/indent/unicode drift in the search text.",
17
- },
18
- replaceAll: {
19
- type: "boolean",
20
- description: "Replace all occurrences instead of first only (default: false).",
21
- },
22
- },
23
- required: ["input"],
24
- };
25
- function escapeNormalize(s) {
26
- return s.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\'/g, "'").replace(/\\"/g, '"');
27
- }
28
- function unicodeNormalize(s) {
29
- return s
30
- .replace(/[\u201c\u201d\u201e\u201f]/g, '"')
31
- .replace(/[\u2018\u2019\u201a\u201b]/g, "'")
32
- .replace(/\u2014/g, "--")
33
- .replace(/\u2013/g, "-")
34
- .replace(/\u2026/g, "...")
35
- .replace(/\u00a0/g, " ");
36
- }
37
- function lineSimilarity(a, b) {
38
- if (a === b)
39
- return 1;
40
- const longer = Math.max(a.length, b.length);
41
- if (longer === 0)
42
- return 1;
43
- let dist = 0;
44
- const la = a.length;
45
- const lb = b.length;
46
- const maxLen = Math.max(la, lb);
47
- // Simplified: character-level distance ratio
48
- for (let i = 0; i < Math.min(la, lb); i++) {
49
- if (a[i] !== b[i])
50
- dist++;
51
- }
52
- dist += Math.abs(la - lb);
53
- return 1 - dist / maxLen;
54
- }
55
- function fuzzyFind(content, search, replaceAll) {
56
- const results = [];
57
- // Strategy 1: exact
58
- let idx = content.indexOf(search);
59
- if (idx !== -1) {
60
- if (replaceAll) {
61
- let pos = 0;
62
- while ((idx = content.indexOf(search, pos)) !== -1) {
63
- results.push({ start: idx, end: idx + search.length, strategy: "exact" });
64
- pos = idx + search.length;
65
- }
66
- }
67
- else {
68
- results.push({ start: idx, end: idx + search.length, strategy: "exact" });
69
- }
70
- return results;
71
- }
72
- // Strategy 2: line_trimmed
73
- const searchLines = search.split("\n").map((l) => l.trim());
74
- const contentLines = content.split("\n");
75
- const trimmedMatch = findLineBlock(contentLines, searchLines, (a, b) => a.trim() === b);
76
- if (trimmedMatch) {
77
- return [{ ...lineBlockToRange(content, contentLines, trimmedMatch), strategy: "line_trimmed" }];
78
- }
79
- // Strategy 3: whitespace_normalized
80
- const normSearch = search.replace(/[ \t]+/g, " ");
81
- const normContent = content.replace(/[ \t]+/g, " ");
82
- idx = normContent.indexOf(normSearch);
83
- if (idx !== -1) {
84
- const mapped = mapNormalizedPos(content, normContent, idx, normSearch.length);
85
- if (mapped)
86
- return [{ ...mapped, strategy: "whitespace_normalized" }];
87
- }
88
- // Strategy 4: indentation_flexible
89
- const flexMatch = findLineBlock(contentLines, searchLines, (a, b) => a.trimStart() === b.trimStart());
90
- if (flexMatch) {
91
- return [{ ...lineBlockToRange(content, contentLines, flexMatch), strategy: "indentation_flexible" }];
92
- }
93
- // Strategy 5: escape_normalized
94
- const escSearch = escapeNormalize(search);
95
- if (escSearch !== search) {
96
- idx = content.indexOf(escSearch);
97
- if (idx !== -1) {
98
- return [{ start: idx, end: idx + escSearch.length, strategy: "escape_normalized" }];
99
- }
100
- }
101
- // Strategy 6: trimmed_boundary
102
- const sLines = search.split("\n");
103
- if (sLines.length >= 3) {
104
- const innerSearch = sLines.slice(1, -1).join("\n");
105
- const firstTrimmed = sLines[0].trim();
106
- const lastTrimmed = sLines[sLines.length - 1].trim();
107
- for (let i = 0; i < contentLines.length; i++) {
108
- if (contentLines[i].trim() === firstTrimmed) {
109
- const innerStart = contentLines.slice(0, i + 1).join("\n").length + 1;
110
- const innerIdx = content.indexOf(innerSearch, innerStart);
111
- if (innerIdx !== -1) {
112
- const afterInner = innerIdx + innerSearch.length;
113
- const nextNewline = content.indexOf("\n", afterInner);
114
- const lastLine = nextNewline === -1 ? content.slice(afterInner) : content.slice(afterInner, nextNewline);
115
- if (lastLine.trim() === lastTrimmed || (nextNewline !== -1 && content.slice(afterInner + 1, content.indexOf("\n", afterInner + 1) === -1 ? undefined : content.indexOf("\n", afterInner + 1)).trim() === lastTrimmed)) {
116
- const rangeStart = contentLines.slice(0, i).join("\n").length + (i > 0 ? 1 : 0);
117
- // Find end after last line
118
- let endI = i;
119
- const needed = sLines.length;
120
- if (i + needed <= contentLines.length) {
121
- endI = i + needed;
122
- const rangeEnd = contentLines.slice(0, endI).join("\n").length;
123
- return [{ start: rangeStart, end: rangeEnd, strategy: "trimmed_boundary" }];
124
- }
125
- }
126
- }
127
- }
128
- }
129
- }
130
- // Strategy 7: unicode_normalized
131
- const uniSearch = unicodeNormalize(search);
132
- const uniContent = unicodeNormalize(content);
133
- if (uniSearch !== search || uniContent !== content) {
134
- idx = uniContent.indexOf(uniSearch);
135
- if (idx !== -1) {
136
- const mapped = mapNormalizedPos(content, uniContent, idx, uniSearch.length);
137
- if (mapped)
138
- return [{ ...mapped, strategy: "unicode_normalized" }];
139
- }
140
- }
141
- // Strategy 8: block_anchor
142
- if (sLines.length >= 3) {
143
- const firstLine = sLines[0];
144
- const lastLine = sLines[sLines.length - 1];
145
- for (let i = 0; i < contentLines.length; i++) {
146
- if (contentLines[i] === firstLine) {
147
- for (let j = i + sLines.length - 1; j < Math.min(i + sLines.length + 2, contentLines.length); j++) {
148
- if (contentLines[j] === lastLine) {
149
- const blockLen = j - i + 1;
150
- const middleLines = contentLines.slice(i + 1, j);
151
- const searchMiddle = sLines.slice(1, -1);
152
- const matchCount = searchMiddle.filter((sl) => middleLines.some((cl) => lineSimilarity(cl, sl) >= 0.7)).length;
153
- const ratio = searchMiddle.length > 0 ? matchCount / searchMiddle.length : 1;
154
- if (ratio >= 0.5 && blockLen <= sLines.length + 2) {
155
- const rangeStart = contentLines.slice(0, i).join("\n").length + (i > 0 ? 1 : 0);
156
- const rangeEnd = contentLines.slice(0, j + 1).join("\n").length;
157
- return [{ start: rangeStart, end: rangeEnd, strategy: "block_anchor" }];
158
- }
159
- }
160
- }
161
- }
162
- }
163
- }
164
- // Strategy 9: context_aware
165
- if (sLines.length >= 2) {
166
- for (let i = 0; i <= contentLines.length - sLines.length; i++) {
167
- const candidateLines = contentLines.slice(i, i + sLines.length);
168
- const similarities = sLines.map((sl, k) => lineSimilarity(sl, candidateLines[k]));
169
- const goodLines = similarities.filter((s) => s >= 0.8).length;
170
- if (goodLines / sLines.length >= 0.5) {
171
- const rangeStart = contentLines.slice(0, i).join("\n").length + (i > 0 ? 1 : 0);
172
- const rangeEnd = contentLines.slice(0, i + sLines.length).join("\n").length;
173
- return [{ start: rangeStart, end: rangeEnd, strategy: "context_aware" }];
174
- }
175
- }
176
- }
177
- return [];
178
- }
179
- function findLineBlock(contentLines, searchLines, compareFn) {
180
- for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
181
- let match = true;
182
- for (let j = 0; j < searchLines.length; j++) {
183
- if (!compareFn(contentLines[i + j], searchLines[j])) {
184
- match = false;
185
- break;
186
- }
187
- }
188
- if (match)
189
- return { startIdx: i, endIdx: i + searchLines.length };
190
- }
191
- return null;
192
- }
193
- function lineBlockToRange(content, contentLines, block) {
194
- const start = contentLines.slice(0, block.startIdx).join("\n").length + (block.startIdx > 0 ? 1 : 0);
195
- const end = contentLines.slice(0, block.endIdx).join("\n").length;
196
- return { start, end };
197
- }
198
- function mapNormalizedPos(original, normalized, normStart, normLen) {
199
- // Simple character-length mapping (works for whitespace collapse and unicode transforms)
200
- // Walk both strings in sync to find positional correspondence
201
- let oi = 0;
202
- let ni = 0;
203
- let origStart = -1;
204
- let origEnd = -1;
205
- while (oi <= original.length && ni <= normalized.length) {
206
- if (ni === normStart && origStart === -1)
207
- origStart = oi;
208
- if (ni === normStart + normLen) {
209
- origEnd = oi;
210
- break;
211
- }
212
- if (ni >= normalized.length)
213
- break;
214
- if (oi >= original.length)
215
- break;
216
- ni++;
217
- oi++;
218
- // Skip extra chars in original that were collapsed
219
- while (oi < original.length && ni < normalized.length && original[oi] !== normalized[ni]) {
220
- oi++;
221
- }
222
- }
223
- if (origStart !== -1 && origEnd === -1)
224
- origEnd = original.length;
225
- if (origStart === -1)
226
- return null;
227
- return { start: origStart, end: origEnd };
228
- }
229
- function parseV4A(input) {
230
- const lines = input.split("\n");
231
- const ops = [];
232
- let current = null;
233
- let currentHunk = null;
234
- let inPatch = false;
235
- for (const line of lines) {
236
- if (line.startsWith("*** Begin Patch")) {
237
- inPatch = true;
238
- continue;
239
- }
240
- if (line.startsWith("*** End Patch"))
241
- break;
242
- if (!inPatch)
243
- continue;
244
- if (line.startsWith("*** Add File: ")) {
245
- if (current)
246
- ops.push(current);
247
- current = { type: "add", path: line.slice(14).trim(), hunks: [] };
248
- currentHunk = { lines: [] };
249
- current.hunks.push(currentHunk);
250
- }
251
- else if (line.startsWith("*** Update File: ")) {
252
- if (current)
253
- ops.push(current);
254
- current = { type: "update", path: line.slice(17).trim(), hunks: [] };
255
- currentHunk = null;
256
- }
257
- else if (line.startsWith("*** Delete File: ")) {
258
- if (current)
259
- ops.push(current);
260
- current = { type: "delete", path: line.slice(17).trim(), hunks: [] };
261
- currentHunk = null;
262
- }
263
- else if (line.startsWith("*** Move File: ")) {
264
- if (current)
265
- ops.push(current);
266
- const parts = line.slice(15).trim().split(" -> ");
267
- current = { type: "move", path: parts[0].trim(), newPath: parts[1]?.trim(), hunks: [] };
268
- currentHunk = null;
269
- }
270
- else if (line.startsWith("@@ ") && current) {
271
- currentHunk = { contextHint: line.slice(3).replace(/ @@$/, "").trim() || undefined, lines: [] };
272
- current.hunks.push(currentHunk);
273
- }
274
- else if (currentHunk) {
275
- if (line.startsWith("+")) {
276
- currentHunk.lines.push({ prefix: "+", content: line.slice(1) });
277
- }
278
- else if (line.startsWith("-")) {
279
- currentHunk.lines.push({ prefix: "-", content: line.slice(1) });
280
- }
281
- else {
282
- currentHunk.lines.push({ prefix: " ", content: line.startsWith(" ") ? line.slice(1) : line });
283
- }
284
- }
285
- }
286
- if (current)
287
- ops.push(current);
288
- return ops;
289
- }
290
- function parseSimpleFormat(input) {
291
- const ops = [];
292
- const blocks = input.split(/^(<<<< SEARCH)$/m);
293
- if (blocks.length < 2)
294
- return [];
295
- // First element before first <<<< SEARCH is the file path
296
- const pathLine = blocks[0].trim().split("\n").pop()?.trim();
297
- if (!pathLine)
298
- return [];
299
- for (let i = 1; i < blocks.length; i += 2) {
300
- const rest = blocks[i + 1] || "";
301
- const sepIdx = rest.indexOf("\n====\n");
302
- if (sepIdx === -1)
303
- continue;
304
- const search = rest.slice(0, sepIdx).replace(/^\n/, "");
305
- const afterSep = rest.slice(sepIdx + 6);
306
- const replaceEnd = afterSep.indexOf("\n>>>> REPLACE");
307
- const replace = replaceEnd === -1 ? afterSep : afterSep.slice(0, replaceEnd);
308
- ops.push({ path: pathLine, search, replace });
309
- }
310
- return ops;
311
- }
312
- // ── Main tool function ──────────────────────────────────────
313
- function applyV4AHunk(content, hunk, replaceAll) {
314
- const contextLines = hunk.lines.filter((l) => l.prefix === " ").map((l) => l.content);
315
- const removeLines = hunk.lines.filter((l) => l.prefix === "-").map((l) => l.content);
316
- const addLines = hunk.lines.filter((l) => l.prefix === "+").map((l) => l.content);
317
- // Build the search text from context + removals (what should exist in file)
318
- const searchParts = [];
319
- let phase = "context";
320
- for (const line of hunk.lines) {
321
- if (line.prefix === " " || line.prefix === "-") {
322
- searchParts.push(line.content);
323
- }
324
- // Skip additions in search
325
- }
326
- const searchText = searchParts.join("\n");
327
- if (!searchText) {
328
- // Pure addition — append at end or after context hint
329
- return { content: content + "\n" + addLines.join("\n"), strategy: "exact" };
330
- }
331
- const matches = fuzzyFind(content, searchText, replaceAll);
332
- if (matches.length === 0)
333
- return null;
334
- // Build replacement: context lines + additions (removals excluded)
335
- const replaceParts = [];
336
- for (const line of hunk.lines) {
337
- if (line.prefix === " " || line.prefix === "+") {
338
- replaceParts.push(line.content);
339
- }
340
- }
341
- const replaceText = replaceParts.join("\n");
342
- // Apply first match
343
- const m = matches[0];
344
- const newContent = content.slice(0, m.start) + replaceText + content.slice(m.end);
345
- return { content: newContent, strategy: m.strategy };
346
- }
347
- export function createPatchTool(deps) {
348
- return {
349
- name: PATCH_TOOL_NAME,
350
- label: "Patch",
351
- description: "Apply edits to files using fuzzy matching. Supports V4A unified diff format " +
352
- "(*** Begin Patch / *** End Patch) for multi-file operations, and simple search/replace " +
353
- "blocks. The fuzzy matcher handles whitespace, indentation, unicode, and escape drift " +
354
- "— LLM output with minor formatting differences will still match correctly.",
355
- parameters: PATCH_TOOL_SCHEMA,
356
- execute: async (_toolCallId, params) => {
357
- const replaceAll = params.replaceAll ?? false;
358
- const result = { filesModified: [], filesAdded: [], filesDeleted: [], strategies: {}, errors: [] };
359
- // Detect format: V4A or simple
360
- const isV4A = params.input.includes("*** Begin Patch");
361
- const isSimple = params.input.includes("<<<< SEARCH");
362
- if (isV4A) {
363
- const ops = parseV4A(params.input);
364
- if (ops.length === 0) {
365
- return {
366
- content: [{ type: "text", text: "Error: No valid V4A operations found. Ensure *** Begin Patch / *** End Patch markers are present." }],
367
- details: { type: "patch", error: "parse_failed" },
368
- };
369
- }
370
- for (const op of ops) {
371
- const resolved = deps.resolvePath(op.path);
372
- try {
373
- switch (op.type) {
374
- case "add": {
375
- const addContent = op.hunks.flatMap((h) => h.lines.filter((l) => l.prefix === "+").map((l) => l.content)).join("\n");
376
- await deps.writeFile(resolved, addContent);
377
- result.filesAdded.push(op.path);
378
- result.strategies[op.path] = "exact";
379
- break;
380
- }
381
- case "delete": {
382
- await deps.deleteFile(resolved);
383
- result.filesDeleted.push(op.path);
384
- break;
385
- }
386
- case "move": {
387
- const existing = await deps.readFile(resolved);
388
- if (op.newPath) {
389
- const dest = deps.resolvePath(op.newPath);
390
- let content = existing;
391
- for (const hunk of op.hunks) {
392
- const hr = applyV4AHunk(content, hunk, replaceAll);
393
- if (hr) {
394
- content = hr.content;
395
- result.strategies[op.path] = hr.strategy;
396
- }
397
- }
398
- await deps.writeFile(dest, content);
399
- await deps.deleteFile(resolved);
400
- result.filesModified.push(`${op.path} → ${op.newPath}`);
401
- }
402
- break;
403
- }
404
- case "update": {
405
- let content = await deps.readFile(resolved);
406
- let lastStrategy = "exact";
407
- let allSuccess = true;
408
- for (const hunk of op.hunks) {
409
- const hr = applyV4AHunk(content, hunk, replaceAll);
410
- if (hr) {
411
- content = hr.content;
412
- lastStrategy = hr.strategy;
413
- }
414
- else {
415
- allSuccess = false;
416
- // Build readable error
417
- const searchSnippet = hunk.lines
418
- .filter((l) => l.prefix === " " || l.prefix === "-")
419
- .map((l) => l.content)
420
- .slice(0, 5)
421
- .join("\n");
422
- result.errors.push(`${op.path}: hunk not matched. Search begins with:\n${searchSnippet}`);
423
- }
424
- }
425
- if (allSuccess || content !== await deps.readFile(resolved)) {
426
- await deps.writeFile(resolved, content);
427
- result.filesModified.push(op.path);
428
- result.strategies[op.path] = lastStrategy;
429
- }
430
- break;
431
- }
432
- }
433
- }
434
- catch (err) {
435
- result.errors.push(`${op.type} ${op.path}: ${err instanceof Error ? err.message : String(err)}`);
436
- }
437
- }
438
- }
439
- else if (isSimple) {
440
- const ops = parseSimpleFormat(params.input);
441
- if (ops.length === 0) {
442
- return {
443
- content: [{ type: "text", text: "Error: Invalid search/replace format. Use <<<< SEARCH / ==== / >>>> REPLACE blocks." }],
444
- details: { type: "patch", error: "parse_failed" },
445
- };
446
- }
447
- for (const op of ops) {
448
- const resolved = deps.resolvePath(op.path);
449
- try {
450
- let content = await deps.readFile(resolved);
451
- const matches = fuzzyFind(content, op.search, replaceAll);
452
- if (matches.length === 0) {
453
- result.errors.push(`${op.path}: search text not matched (tried all 9 strategies)`);
454
- continue;
455
- }
456
- // Apply in reverse order to preserve positions
457
- const sorted = [...matches].sort((a, b) => b.start - a.start);
458
- for (const m of sorted) {
459
- content = content.slice(0, m.start) + op.replace + content.slice(m.end);
460
- }
461
- await deps.writeFile(resolved, content);
462
- result.filesModified.push(op.path);
463
- result.strategies[op.path] = matches[0].strategy;
464
- }
465
- catch (err) {
466
- result.errors.push(`${op.path}: ${err instanceof Error ? err.message : String(err)}`);
467
- }
468
- }
469
- }
470
- else {
471
- // Try parsing as a plain old/new pair in V4A-like format (backwards compat with apply_patch)
472
- const ops = parseV4A("*** Begin Patch\n" + params.input + "\n*** End Patch");
473
- if (ops.length > 0) {
474
- // Re-run through V4A path
475
- return createPatchTool(deps).execute(_toolCallId, { input: "*** Begin Patch\n" + params.input + "\n*** End Patch", replaceAll });
476
- }
477
- return {
478
- content: [{ type: "text", text: "Error: Unrecognized patch format. Use V4A (*** Begin Patch) or search/replace (<<<< SEARCH / ==== / >>>> REPLACE)." }],
479
- details: { type: "patch", error: "unknown_format" },
480
- };
481
- }
482
- // Format output
483
- const parts = [];
484
- if (result.filesAdded.length)
485
- parts.push(`Added: ${result.filesAdded.join(", ")}`);
486
- if (result.filesModified.length) {
487
- const strategyInfo = result.filesModified
488
- .map((f) => `${f} (${result.strategies[f] || "exact"})`)
489
- .join(", ");
490
- parts.push(`Modified: ${strategyInfo}`);
491
- }
492
- if (result.filesDeleted.length)
493
- parts.push(`Deleted: ${result.filesDeleted.join(", ")}`);
494
- if (result.errors.length)
495
- parts.push(`\nErrors:\n${result.errors.join("\n")}`);
496
- const success = result.errors.length === 0;
497
- return {
498
- content: [{ type: "text", text: parts.join("\n") || "No changes applied." }],
499
- details: { type: "patch", ...result, success },
500
- };
501
- },
502
- };
503
- }
504
- // Export the fuzzy engine for unit testing
505
- export { fuzzyFind };
@@ -1,88 +0,0 @@
1
- // ============================================================
2
- // PDF Tool — analyze PDF documents via relay/backend pipeline.
3
- // Category: document
4
- // ============================================================
5
- export const PDF_TOOL_NAME = "pdf";
6
- export const PDF_TOOL_SCHEMA = {
7
- type: "object",
8
- properties: {
9
- prompt: {
10
- type: "string",
11
- description: "Describe what to analyze or extract from the PDF(s).",
12
- },
13
- pdf: {
14
- type: "string",
15
- description: "Single PDF path or URL.",
16
- },
17
- pdfs: {
18
- type: "array",
19
- items: { type: "string" },
20
- description: "Multiple PDF paths or URLs (up to 10).",
21
- },
22
- pages: {
23
- type: "string",
24
- description: 'Page range to process, e.g. "1-5", "1,3,5-7". Defaults to all pages.',
25
- },
26
- },
27
- required: [],
28
- };
29
- const MAX_PDF_COUNT = 10;
30
- export function createPdfTool(deps) {
31
- return {
32
- name: PDF_TOOL_NAME,
33
- label: "PDF",
34
- description: "Analyze one or more PDF documents. Use `pdf` for a single path/URL, or `pdfs` for multiple (up to 10). " +
35
- "Provide a `prompt` describing what to analyze. Use `pages` to limit which pages to process.",
36
- parameters: PDF_TOOL_SCHEMA,
37
- execute: async (_toolCallId, params) => {
38
- // Normalize sources
39
- const sources = [];
40
- if (params.pdf)
41
- sources.push(params.pdf);
42
- if (params.pdfs)
43
- sources.push(...params.pdfs);
44
- // Deduplicate
45
- const unique = [...new Set(sources)];
46
- if (unique.length === 0) {
47
- return { content: [{ type: "text", text: "Error: provide at least one PDF via `pdf` or `pdfs`." }] };
48
- }
49
- if (unique.length > MAX_PDF_COUNT) {
50
- return { content: [{ type: "text", text: `Error: maximum ${MAX_PDF_COUNT} PDFs per call.` }] };
51
- }
52
- const allTexts = [];
53
- const allImages = [];
54
- const errors = [];
55
- for (const src of unique) {
56
- try {
57
- const result = await deps.extractPdf(src, { pages: params.pages });
58
- allTexts.push(result.text);
59
- if (result.pageImages) {
60
- for (const img of result.pageImages) {
61
- allImages.push({ base64: img.base64, mimeType: img.mimeType });
62
- }
63
- }
64
- }
65
- catch (err) {
66
- errors.push(`${src}: ${err instanceof Error ? err.message : String(err)}`);
67
- }
68
- }
69
- if (allTexts.length === 0) {
70
- return { content: [{ type: "text", text: `Failed to extract any PDFs: ${errors.join("; ")}` }] };
71
- }
72
- const combinedText = allTexts.join("\n\n---\n\n");
73
- // If LLM analysis is available and prompt given, use it
74
- if (deps.analyzePdfContent && params.prompt) {
75
- const analysis = await deps.analyzePdfContent(params.prompt, { text: combinedText, images: allImages });
76
- return {
77
- content: [{ type: "text", text: analysis.text }],
78
- details: { type: "pdf", model: analysis.model, provider: analysis.provider, pdfCount: unique.length, errors },
79
- };
80
- }
81
- // Otherwise return raw extracted text
82
- return {
83
- content: [{ type: "text", text: combinedText }],
84
- details: { type: "pdf", pdfCount: unique.length, errors },
85
- };
86
- },
87
- };
88
- }