icopilot 2.2.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 (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,484 @@
1
+ import { config } from '../config.js';
2
+ import { proposeAndRun } from './shell.js';
3
+ import { proposeWrite, proposeWriteBatch, readFileSafe } from './file-ops.js';
4
+ import { editFileTool, EDIT_FILE_SCHEMA } from './edit-file.js';
5
+ import { multiEditSchema, multiEditTool } from './multi-edit.js';
6
+ import { applyPatchTool } from './apply-patch.js';
7
+ import { grepTool } from './grep.js';
8
+ import { globTool } from './glob.js';
9
+ import { readImage, DESCRIBE_IMAGE_SCHEMA } from './image.js';
10
+ import { listDirectory, listDirectorySchema } from './list-directory.js';
11
+ import { searchSymbols, searchSymbolsSchema } from './search-symbols.js';
12
+ import { runInTerminal, runInTerminalSchema } from './run-in-terminal.js';
13
+ import { withRetry } from './retry.js';
14
+ import { webFetchTool, WEB_FETCH_SCHEMA } from './web.js';
15
+ import { AuditLogger } from '../security/audit.js';
16
+ import { RoleManager, defaultRolesConfigPath } from '../security/roles.js';
17
+ import { hookManager } from '../hooks/lifecycle.js';
18
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
19
+ const mcpModulePromise = dynamicImport('../mcp/index.js').catch(() => null);
20
+ let mcpLoaded = false;
21
+ let mcpTools = null;
22
+ const auditLogger = new AuditLogger();
23
+ export const TOOL_SCHEMAS = [
24
+ {
25
+ type: 'function',
26
+ function: {
27
+ name: 'run_shell',
28
+ description: 'Propose a shell command to the user. The user MUST approve before it executes. Returns stdout/stderr/exitCode.',
29
+ parameters: {
30
+ type: 'object',
31
+ properties: {
32
+ command: { type: 'string', description: 'Exact command line to run.' },
33
+ explain: {
34
+ type: 'string',
35
+ description: 'One-sentence rationale shown to the user.',
36
+ },
37
+ },
38
+ required: ['command'],
39
+ },
40
+ },
41
+ },
42
+ {
43
+ type: 'function',
44
+ function: {
45
+ name: 'read_file',
46
+ description: 'Read a file from the working directory.',
47
+ parameters: {
48
+ type: 'object',
49
+ properties: { path: { type: 'string' } },
50
+ required: ['path'],
51
+ },
52
+ },
53
+ },
54
+ {
55
+ type: 'function',
56
+ function: {
57
+ name: 'write_file',
58
+ description: 'Propose creating or overwriting a file. User must approve via diff preview.',
59
+ parameters: {
60
+ type: 'object',
61
+ properties: {
62
+ path: { type: 'string' },
63
+ content: { type: 'string' },
64
+ },
65
+ required: ['path', 'content'],
66
+ },
67
+ },
68
+ },
69
+ {
70
+ type: 'function',
71
+ function: {
72
+ name: 'write_files',
73
+ description: 'Propose creating or overwriting multiple files atomically. User approves one combined diff preview.',
74
+ parameters: {
75
+ type: 'object',
76
+ properties: {
77
+ items: {
78
+ type: 'array',
79
+ items: {
80
+ type: 'object',
81
+ properties: {
82
+ path: { type: 'string' },
83
+ content: { type: 'string' },
84
+ },
85
+ required: ['path', 'content'],
86
+ },
87
+ },
88
+ },
89
+ required: ['items'],
90
+ },
91
+ },
92
+ },
93
+ EDIT_FILE_SCHEMA,
94
+ multiEditSchema,
95
+ {
96
+ type: 'function',
97
+ function: {
98
+ name: 'apply_patch',
99
+ description: 'Apply a unified diff patch with interactive hunk-level selection.',
100
+ parameters: {
101
+ type: 'object',
102
+ properties: { patch: { type: 'string' } },
103
+ required: ['patch'],
104
+ },
105
+ },
106
+ },
107
+ {
108
+ type: 'function',
109
+ function: {
110
+ name: 'grep',
111
+ description: 'Read-only repository grep. Returns matching file, line, and text snippets.',
112
+ parameters: {
113
+ type: 'object',
114
+ properties: {
115
+ pattern: { type: 'string' },
116
+ path: { type: 'string' },
117
+ regex: { type: 'boolean' },
118
+ ignoreCase: { type: 'boolean' },
119
+ maxResults: { type: 'number', default: 200 },
120
+ },
121
+ required: ['pattern'],
122
+ },
123
+ },
124
+ },
125
+ {
126
+ type: 'function',
127
+ function: {
128
+ name: 'glob',
129
+ description: 'Read-only repository file glob.',
130
+ parameters: {
131
+ type: 'object',
132
+ properties: {
133
+ pattern: { type: 'string' },
134
+ cwd: { type: 'string' },
135
+ ignore: { type: 'array', items: { type: 'string' } },
136
+ },
137
+ required: ['pattern'],
138
+ },
139
+ },
140
+ },
141
+ searchSymbolsSchema,
142
+ listDirectorySchema,
143
+ runInTerminalSchema,
144
+ WEB_FETCH_SCHEMA,
145
+ DESCRIBE_IMAGE_SCHEMA,
146
+ ];
147
+ export async function getAllToolSchemas() {
148
+ const roleManager = createRoleManager();
149
+ const mcp = await getMcpToolsSafe();
150
+ if (!mcp)
151
+ return TOOL_SCHEMAS.filter((schema) => roleManager.checkAccess(schema.function.name).allowed);
152
+ const seen = new Set(TOOL_SCHEMAS.map((schema) => schema.function.name));
153
+ return [
154
+ ...TOOL_SCHEMAS,
155
+ ...mcp.schemas.filter((schema) => !seen.has(schema.function.name)),
156
+ ].filter((schema) => roleManager.checkAccess(schema.function.name).allowed);
157
+ }
158
+ export async function dispatchTool(name, args) {
159
+ const startedAt = Date.now();
160
+ const access = createRoleManager().checkAccess(name);
161
+ if (!access.allowed) {
162
+ const denied = JSON.stringify({ error: access.reason || `access denied: ${name}` });
163
+ recordAudit(name, args, denied, Date.now() - startedAt, 'denied');
164
+ return denied;
165
+ }
166
+ const preToolHook = await hookManager.emit('preToolUse', {
167
+ tool: name,
168
+ args,
169
+ cwd: config.cwd,
170
+ });
171
+ if (preToolHook.action === 'deny') {
172
+ const denied = JSON.stringify({ error: preToolHook.reason || `tool blocked by hook: ${name}` });
173
+ recordAudit(name, args, denied, Date.now() - startedAt, 'denied');
174
+ return denied;
175
+ }
176
+ const effectiveArgs = preToolHook.action === 'modify' && preToolHook.modifications
177
+ ? { ...args, ...coerceHookObject(preToolHook.modifications) }
178
+ : args;
179
+ try {
180
+ const builtIn = await withRetry(() => dispatchBuiltIn(name, effectiveArgs));
181
+ if (builtIn !== undefined) {
182
+ const hookedOutput = await applyPostToolHook(name, effectiveArgs, builtIn, startedAt);
183
+ recordAudit(name, effectiveArgs, hookedOutput, Date.now() - startedAt);
184
+ return hookedOutput;
185
+ }
186
+ const mcp = await getMcpToolsSafe();
187
+ if (mcp) {
188
+ const out = await mcp.dispatch(name, effectiveArgs);
189
+ const hookedOutput = await applyPostToolHook(name, effectiveArgs, out, startedAt);
190
+ recordAudit(name, effectiveArgs, hookedOutput, Date.now() - startedAt);
191
+ return hookedOutput;
192
+ }
193
+ const out = JSON.stringify({ error: `unknown tool: ${name}` });
194
+ const hookedOutput = await applyPostToolHook(name, effectiveArgs, out, startedAt);
195
+ recordAudit(name, effectiveArgs, hookedOutput, Date.now() - startedAt, 'failure');
196
+ return hookedOutput;
197
+ }
198
+ catch (error) {
199
+ auditLogger.log({
200
+ action: 'tool.execute',
201
+ tool: name,
202
+ command: extractCommand(name, effectiveArgs),
203
+ args: effectiveArgs,
204
+ result: 'failure',
205
+ duration: Date.now() - startedAt,
206
+ details: formatErrorMessage(error),
207
+ });
208
+ await hookManager.emit('errorOccurred', {
209
+ scope: 'tool',
210
+ tool: name,
211
+ args: effectiveArgs,
212
+ cwd: config.cwd,
213
+ message: formatErrorMessage(error),
214
+ });
215
+ throw error;
216
+ }
217
+ }
218
+ async function dispatchBuiltIn(name, args) {
219
+ switch (name) {
220
+ case 'run_shell': {
221
+ const r = await proposeAndRun(String(args.command || ''), {
222
+ explain: args.explain ? String(args.explain) : undefined,
223
+ });
224
+ return JSON.stringify({
225
+ ran: r.ran,
226
+ exitCode: r.exitCode,
227
+ stdout: truncate(r.stdout, 8000),
228
+ stderr: truncate(r.stderr, 4000),
229
+ });
230
+ }
231
+ case 'read_file': {
232
+ try {
233
+ const c = readFileSafe(String(args.path));
234
+ return JSON.stringify({ ok: true, content: truncate(c, 64_000) });
235
+ }
236
+ catch (e) {
237
+ return JSON.stringify({ ok: false, error: e?.message || String(e) });
238
+ }
239
+ }
240
+ case 'write_file': {
241
+ const r = await proposeWrite(String(args.path), String(args.content ?? ''));
242
+ return JSON.stringify({ wrote: r.wrote, bytes: r.bytes, error: r.error });
243
+ }
244
+ case 'write_files': {
245
+ const items = Array.isArray(args.items) ? args.items : [];
246
+ const r = await proposeWriteBatch(items.map((item) => ({
247
+ path: String(item.path),
248
+ content: String(item.content ?? ''),
249
+ })));
250
+ return JSON.stringify(r);
251
+ }
252
+ case 'edit_file':
253
+ return editFileTool({
254
+ path: String(args.path || ''),
255
+ startLine: Number(args.startLine),
256
+ endLine: Number(args.endLine),
257
+ newContent: String(args.newContent ?? ''),
258
+ });
259
+ case 'multi_edit':
260
+ return multiEditTool({
261
+ description: String(args.description ?? ''),
262
+ rollbackable: Boolean(args.rollbackable),
263
+ files: Array.isArray(args.files)
264
+ ? args.files.map((file) => ({
265
+ file: String(file.file ?? ''),
266
+ edits: Array.isArray(file.edits)
267
+ ? file.edits.map((edit) => ({
268
+ oldText: String(edit.oldText ?? ''),
269
+ newText: String(edit.newText ?? ''),
270
+ }))
271
+ : [],
272
+ }))
273
+ : [],
274
+ });
275
+ case 'apply_patch':
276
+ return applyPatchTool({ patch: String(args.patch || '') });
277
+ case 'grep':
278
+ return grepTool({
279
+ pattern: String(args.pattern || ''),
280
+ path: args.path ? String(args.path) : undefined,
281
+ regex: Boolean(args.regex),
282
+ ignoreCase: Boolean(args.ignoreCase),
283
+ maxResults: args.maxResults ? Number(args.maxResults) : undefined,
284
+ });
285
+ case 'glob':
286
+ return globTool({
287
+ pattern: String(args.pattern || ''),
288
+ cwd: args.cwd ? String(args.cwd) : undefined,
289
+ ignore: Array.isArray(args.ignore) ? args.ignore.map(String) : undefined,
290
+ });
291
+ case 'list_directory':
292
+ return listDirectory({
293
+ path: String(args.path || '.'),
294
+ recursive: Boolean(args.recursive),
295
+ maxDepth: args.maxDepth !== undefined ? Number(args.maxDepth) : undefined,
296
+ pattern: args.pattern ? String(args.pattern) : undefined,
297
+ });
298
+ case 'search_symbols':
299
+ return searchSymbols({
300
+ query: String(args.query || ''),
301
+ filePattern: args.filePattern ? String(args.filePattern) : undefined,
302
+ type: normalizeSearchSymbolType(args.type),
303
+ });
304
+ case 'run_in_terminal':
305
+ return JSON.stringify(await runInTerminal({
306
+ command: String(args.command || ''),
307
+ cwd: args.cwd ? String(args.cwd) : undefined,
308
+ timeout: args.timeout === undefined ? undefined : Number(args.timeout),
309
+ env: args.env && typeof args.env === 'object'
310
+ ? Object.fromEntries(Object.entries(args.env).map(([key, value]) => [key, String(value)]))
311
+ : undefined,
312
+ }));
313
+ case 'web_fetch':
314
+ return webFetchTool(args);
315
+ case 'describe_image':
316
+ return JSON.stringify(readImage(args));
317
+ default:
318
+ return undefined;
319
+ }
320
+ }
321
+ async function getMcpToolsSafe() {
322
+ if (mcpTools)
323
+ return mcpTools;
324
+ const mod = await mcpModulePromise;
325
+ if (!mod)
326
+ return null;
327
+ try {
328
+ if (!mcpLoaded) {
329
+ await mod.loadMcpServers();
330
+ mcpLoaded = true;
331
+ }
332
+ mcpTools = await mod.getMcpTools();
333
+ return mcpTools;
334
+ }
335
+ catch {
336
+ return null;
337
+ }
338
+ }
339
+ function truncate(s, n) {
340
+ return s.length > n ? s.slice(0, n) + `\n…[truncated ${s.length - n} chars]` : s;
341
+ }
342
+ function normalizeSearchSymbolType(value) {
343
+ return value === 'function' ||
344
+ value === 'class' ||
345
+ value === 'variable' ||
346
+ value === 'interface' ||
347
+ value === 'type' ||
348
+ value === 'all'
349
+ ? value
350
+ : undefined;
351
+ }
352
+ function createRoleManager() {
353
+ const roleManager = new RoleManager(defaultRolesConfigPath(config.cwd));
354
+ roleManager.loadRoles();
355
+ return roleManager;
356
+ }
357
+ function recordAudit(name, args, output, duration, forcedResult) {
358
+ const details = summarizeOutput(output);
359
+ auditLogger.log({
360
+ action: 'tool.execute',
361
+ tool: name,
362
+ command: extractCommand(name, args),
363
+ args,
364
+ result: forcedResult ?? inferAuditResult(name, output),
365
+ duration,
366
+ details: details || undefined,
367
+ });
368
+ }
369
+ function inferAuditResult(name, output) {
370
+ const parsed = tryParseJson(output);
371
+ if (name === 'run_shell') {
372
+ if (parsed && typeof parsed.ran === 'boolean') {
373
+ if (!parsed.ran)
374
+ return 'denied';
375
+ return parsed.exitCode === 0 ? 'success' : 'failure';
376
+ }
377
+ return classifyTextResult(output);
378
+ }
379
+ if (name === 'run_in_terminal') {
380
+ if (parsed && typeof parsed.exitCode === 'number') {
381
+ return parsed.exitCode === 0 ? 'success' : 'failure';
382
+ }
383
+ return classifyTextResult(output);
384
+ }
385
+ if (parsed && typeof parsed.wrote === 'boolean') {
386
+ return parsed.wrote ? 'success' : classifyFromPayload(parsed.error);
387
+ }
388
+ if (parsed && typeof parsed.ok === 'boolean') {
389
+ return parsed.ok ? 'success' : classifyFromPayload(parsed.error);
390
+ }
391
+ if (parsed && typeof parsed.success === 'boolean') {
392
+ if (parsed.success)
393
+ return 'success';
394
+ const failures = Array.isArray(parsed.failed)
395
+ ? parsed.failed
396
+ .map((item) => (typeof item?.error === 'string' ? item.error : ''))
397
+ .filter((message) => message.length > 0)
398
+ : [];
399
+ if (failures.length > 0) {
400
+ return failures.every((message) => isDeniedMessage(message)) ? 'denied' : 'failure';
401
+ }
402
+ return 'failure';
403
+ }
404
+ if (parsed &&
405
+ Array.isArray(parsed.applied) &&
406
+ Array.isArray(parsed.skipped) &&
407
+ Array.isArray(parsed.errors)) {
408
+ if (parsed.errors.length > 0)
409
+ return 'failure';
410
+ if (parsed.applied.length > 0)
411
+ return 'success';
412
+ if (parsed.skipped.length > 0)
413
+ return 'denied';
414
+ }
415
+ if (parsed && typeof parsed.error === 'string') {
416
+ return classifyFromPayload(parsed.error);
417
+ }
418
+ return classifyTextResult(output);
419
+ }
420
+ function classifyFromPayload(message) {
421
+ return typeof message === 'string' && isDeniedMessage(message) ? 'denied' : 'failure';
422
+ }
423
+ function classifyTextResult(text) {
424
+ if (!text.trim())
425
+ return 'success';
426
+ const parsed = tryParseJson(text);
427
+ if (parsed && typeof parsed.error === 'string') {
428
+ return classifyFromPayload(parsed.error);
429
+ }
430
+ return /"error"\s*:/u.test(text) ? 'failure' : 'success';
431
+ }
432
+ function isDeniedMessage(message) {
433
+ return /\b(denied|blocked|cancelled|canceled|rejected|skipped|not selected)\b/iu.test(message);
434
+ }
435
+ function summarizeOutput(output) {
436
+ const trimmed = output.trim();
437
+ if (!trimmed)
438
+ return '';
439
+ return trimmed.length > 400
440
+ ? `${trimmed.slice(0, 400)}…[truncated ${trimmed.length - 400} chars]`
441
+ : trimmed;
442
+ }
443
+ function extractCommand(name, args) {
444
+ if (name === 'run_shell' || name === 'run_in_terminal') {
445
+ return typeof args.command === 'string' && args.command.trim().length > 0
446
+ ? args.command.trim()
447
+ : undefined;
448
+ }
449
+ return undefined;
450
+ }
451
+ function tryParseJson(value) {
452
+ try {
453
+ return JSON.parse(value);
454
+ }
455
+ catch {
456
+ return null;
457
+ }
458
+ }
459
+ function formatErrorMessage(error) {
460
+ return error instanceof Error ? error.message : String(error);
461
+ }
462
+ async function applyPostToolHook(name, args, output, startedAt) {
463
+ const hookResult = await hookManager.emit('postToolUse', {
464
+ tool: name,
465
+ args,
466
+ output,
467
+ cwd: config.cwd,
468
+ duration: Date.now() - startedAt,
469
+ });
470
+ if (hookResult.action !== 'modify' || !hookResult.modifications)
471
+ return output;
472
+ const modifications = coerceHookObject(hookResult.modifications);
473
+ if (typeof modifications.output === 'string')
474
+ return modifications.output;
475
+ if (modifications.result && typeof modifications.result === 'object') {
476
+ return JSON.stringify(modifications.result);
477
+ }
478
+ return output;
479
+ }
480
+ function coerceHookObject(value) {
481
+ return value && typeof value === 'object' && !Array.isArray(value)
482
+ ? value
483
+ : {};
484
+ }
@@ -0,0 +1,74 @@
1
+ import { theme } from '../ui/theme.js';
2
+ export const DEFAULT_RETRY_CONFIG = {
3
+ maxAttempts: 3,
4
+ backoffMs: 1000,
5
+ backoffMultiplier: 2,
6
+ retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'rate_limit', '429', '503', '500'],
7
+ };
8
+ export async function withRetry(fn, config = {}) {
9
+ const resolvedConfig = {
10
+ ...DEFAULT_RETRY_CONFIG,
11
+ ...config,
12
+ retryableErrors: config.retryableErrors ?? DEFAULT_RETRY_CONFIG.retryableErrors,
13
+ };
14
+ let attempt = 0;
15
+ let lastError;
16
+ while (attempt < resolvedConfig.maxAttempts) {
17
+ try {
18
+ return await fn();
19
+ }
20
+ catch (error) {
21
+ lastError = error;
22
+ attempt++;
23
+ if (attempt >= resolvedConfig.maxAttempts ||
24
+ !matchesRetryableError(error, resolvedConfig.retryableErrors)) {
25
+ throw error;
26
+ }
27
+ const waitMs = computeBackoffMs(attempt, resolvedConfig);
28
+ process.stderr.write(theme.warn(`retry attempt ${attempt}/${resolvedConfig.maxAttempts} failed; waiting ${waitMs}ms before retrying...\n`));
29
+ await sleep(waitMs);
30
+ }
31
+ }
32
+ throw lastError;
33
+ }
34
+ export function isRetryableError(error) {
35
+ return matchesRetryableError(error, DEFAULT_RETRY_CONFIG.retryableErrors);
36
+ }
37
+ export function sleep(ms) {
38
+ return new Promise((resolve) => setTimeout(resolve, ms));
39
+ }
40
+ function matchesRetryableError(error, retryableErrors) {
41
+ if (!error)
42
+ return false;
43
+ const err = error;
44
+ if (err.name === 'AbortError' || err.code === 'ABORT_ERR')
45
+ return false;
46
+ const parts = [
47
+ stringifyValue(err.code),
48
+ stringifyValue(err.name),
49
+ stringifyValue(err.message),
50
+ stringifyValue(err.status),
51
+ stringifyValue(err.response?.status),
52
+ ];
53
+ const cause = err.cause;
54
+ if (cause) {
55
+ parts.push(stringifyValue(cause.code), stringifyValue(cause.name), stringifyValue(cause.message), stringifyValue(cause.status), stringifyValue(cause.response?.status));
56
+ }
57
+ const normalized = parts
58
+ .filter((value) => Boolean(value))
59
+ .join(' ')
60
+ .toLowerCase();
61
+ return retryableErrors.some((pattern) => normalized.includes(pattern.toLowerCase()));
62
+ }
63
+ function computeBackoffMs(attempt, config) {
64
+ const exponentialDelay = config.backoffMs * config.backoffMultiplier ** Math.max(0, attempt - 1);
65
+ const jitter = 1 + Math.random();
66
+ return Math.max(0, Math.round(exponentialDelay * jitter));
67
+ }
68
+ function stringifyValue(value) {
69
+ if (typeof value === 'string')
70
+ return value;
71
+ if (typeof value === 'number' || typeof value === 'boolean')
72
+ return String(value);
73
+ return '';
74
+ }