coding-agent-adapters 0.2.19 → 0.4.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.
package/dist/index.js CHANGED
@@ -1,7 +1,365 @@
1
- import { mkdir, appendFile, writeFile } from 'fs/promises';
1
+ import { mkdir, writeFile, appendFile } from 'fs/promises';
2
2
  import { join, dirname } from 'path';
3
3
  import { BaseCLIAdapter } from 'pty-manager';
4
4
 
5
+ // src/base-coding-adapter.ts
6
+
7
+ // src/approval-presets.ts
8
+ var TOOL_CATEGORIES = [
9
+ { category: "file_read", risk: "low", description: "Read files, search, list directories" },
10
+ { category: "file_write", risk: "medium", description: "Write, edit, and create files" },
11
+ { category: "shell", risk: "high", description: "Execute shell commands" },
12
+ { category: "web", risk: "medium", description: "Web search and fetch" },
13
+ { category: "agent", risk: "medium", description: "Spawn sub-agents, skills, MCP tools" },
14
+ { category: "planning", risk: "low", description: "Task planning and todo management" },
15
+ { category: "user_interaction", risk: "low", description: "Ask user questions" }
16
+ ];
17
+ var PRESET_DEFINITIONS = [
18
+ {
19
+ preset: "readonly",
20
+ description: "Read-only. Safe for auditing.",
21
+ autoApprove: ["file_read", "planning", "user_interaction"],
22
+ requireApproval: [],
23
+ blocked: ["file_write", "shell", "web", "agent"]
24
+ },
25
+ {
26
+ preset: "standard",
27
+ description: "Standard dev. Reads + web auto, writes/shell prompt.",
28
+ autoApprove: ["file_read", "planning", "user_interaction", "web"],
29
+ requireApproval: ["file_write", "shell", "agent"],
30
+ blocked: []
31
+ },
32
+ {
33
+ preset: "permissive",
34
+ description: "File ops auto-approved, shell still prompts.",
35
+ autoApprove: ["file_read", "file_write", "planning", "user_interaction", "web", "agent"],
36
+ requireApproval: ["shell"],
37
+ blocked: []
38
+ },
39
+ {
40
+ preset: "autonomous",
41
+ description: "Everything auto-approved. Use with sandbox.",
42
+ autoApprove: ["file_read", "file_write", "shell", "web", "agent", "planning", "user_interaction"],
43
+ requireApproval: [],
44
+ blocked: []
45
+ }
46
+ ];
47
+ var CLAUDE_TOOL_CATEGORIES = {
48
+ // file_read
49
+ Read: "file_read",
50
+ Grep: "file_read",
51
+ Glob: "file_read",
52
+ LS: "file_read",
53
+ NotebookRead: "file_read",
54
+ // file_write
55
+ Write: "file_write",
56
+ Edit: "file_write",
57
+ MultiEdit: "file_write",
58
+ NotebookEdit: "file_write",
59
+ // shell
60
+ Bash: "shell",
61
+ BashOutput: "shell",
62
+ KillShell: "shell",
63
+ // web
64
+ WebSearch: "web",
65
+ WebFetch: "web",
66
+ // agent
67
+ Task: "agent",
68
+ Skill: "agent",
69
+ // planning
70
+ TodoWrite: "planning",
71
+ // user_interaction
72
+ AskUserQuestion: "user_interaction"
73
+ };
74
+ var GEMINI_TOOL_CATEGORIES = {
75
+ // file_read
76
+ read_file: "file_read",
77
+ read_many_files: "file_read",
78
+ list_directory: "file_read",
79
+ glob: "file_read",
80
+ search_file_content: "file_read",
81
+ // file_write
82
+ write_file: "file_write",
83
+ replace: "file_write",
84
+ // shell
85
+ run_shell_command: "shell",
86
+ // web
87
+ web_fetch: "web",
88
+ google_web_search: "web",
89
+ // agent
90
+ activate_skill: "agent",
91
+ get_internal_docs: "agent",
92
+ // planning
93
+ save_memory: "planning",
94
+ write_todos: "planning",
95
+ // user_interaction
96
+ ask_user: "user_interaction"
97
+ };
98
+ var CODEX_TOOL_CATEGORIES = {
99
+ // shell (codex uses shell for most operations)
100
+ exec_command: "shell",
101
+ write_stdin: "shell",
102
+ shell_command: "shell",
103
+ // file_write
104
+ apply_patch: "file_write",
105
+ // file_read
106
+ grep_files: "file_read",
107
+ read_file: "file_read",
108
+ list_dir: "file_read",
109
+ // web
110
+ web_search: "web",
111
+ view_image: "web",
112
+ // agent
113
+ spawn_agent: "agent",
114
+ send_input: "agent",
115
+ resume_agent: "agent",
116
+ wait: "agent",
117
+ close_agent: "agent",
118
+ // planning
119
+ update_plan: "planning",
120
+ // user_interaction
121
+ request_user_input: "user_interaction"
122
+ };
123
+ var AIDER_COMMAND_CATEGORIES = {
124
+ // file_read
125
+ "/read-only": "file_read",
126
+ "/ls": "file_read",
127
+ "/map": "file_read",
128
+ "/map-refresh": "file_read",
129
+ "/tokens": "file_read",
130
+ "/diff": "file_read",
131
+ "/context": "file_read",
132
+ // file_write
133
+ "/add": "file_write",
134
+ "/drop": "file_write",
135
+ "/edit": "file_write",
136
+ "/code": "file_write",
137
+ "/architect": "file_write",
138
+ "/undo": "file_write",
139
+ // shell
140
+ "/run": "shell",
141
+ "/test": "shell",
142
+ "/lint": "shell",
143
+ "/git": "shell",
144
+ // web
145
+ "/web": "web",
146
+ // planning
147
+ "/ask": "planning",
148
+ // user_interaction
149
+ "/voice": "user_interaction",
150
+ "/help": "user_interaction",
151
+ // config/other
152
+ "/model": "planning",
153
+ "/settings": "planning",
154
+ "/commit": "file_write",
155
+ "/clear": "planning",
156
+ "/reset": "planning"
157
+ };
158
+ function getToolsForCategories(mapping, categories) {
159
+ return Object.entries(mapping).filter(([, cat]) => categories.includes(cat)).map(([tool]) => tool);
160
+ }
161
+ function generateClaudeApprovalConfig(preset) {
162
+ const def = getPresetDefinition(preset);
163
+ const allowTools = getToolsForCategories(CLAUDE_TOOL_CATEGORIES, def.autoApprove);
164
+ const denyTools = getToolsForCategories(CLAUDE_TOOL_CATEGORIES, def.blocked);
165
+ const settings = {
166
+ permissions: {}
167
+ };
168
+ const permissions = settings.permissions;
169
+ if (allowTools.length > 0) {
170
+ permissions.allow = allowTools;
171
+ }
172
+ if (denyTools.length > 0) {
173
+ permissions.deny = denyTools;
174
+ }
175
+ if (preset === "autonomous") {
176
+ settings.sandbox = {
177
+ enabled: true,
178
+ autoAllowBashIfSandboxed: true
179
+ };
180
+ }
181
+ const cliFlags = [];
182
+ if (preset === "autonomous") {
183
+ const allTools = Object.keys(CLAUDE_TOOL_CATEGORIES);
184
+ cliFlags.push("--tools", allTools.join(","));
185
+ }
186
+ return {
187
+ preset,
188
+ cliFlags,
189
+ workspaceFiles: [
190
+ {
191
+ relativePath: ".claude/settings.json",
192
+ content: JSON.stringify(settings, null, 2),
193
+ format: "json"
194
+ }
195
+ ],
196
+ envVars: {},
197
+ summary: `Claude Code: ${def.description}`
198
+ };
199
+ }
200
+ function generateGeminiApprovalConfig(preset) {
201
+ const def = getPresetDefinition(preset);
202
+ const cliFlags = [];
203
+ const allowedTools = getToolsForCategories(GEMINI_TOOL_CATEGORIES, def.autoApprove);
204
+ const excludeTools = getToolsForCategories(GEMINI_TOOL_CATEGORIES, def.blocked);
205
+ let approvalMode;
206
+ switch (preset) {
207
+ case "readonly":
208
+ approvalMode = "plan";
209
+ cliFlags.push("--approval-mode", "plan");
210
+ break;
211
+ case "standard":
212
+ approvalMode = "default";
213
+ break;
214
+ case "permissive":
215
+ approvalMode = "auto_edit";
216
+ cliFlags.push("--approval-mode", "auto_edit");
217
+ break;
218
+ case "autonomous":
219
+ approvalMode = "auto_edit";
220
+ cliFlags.push("-y");
221
+ break;
222
+ }
223
+ const settings = {
224
+ general: {
225
+ defaultApprovalMode: approvalMode
226
+ },
227
+ tools: {}
228
+ };
229
+ const tools = settings.tools;
230
+ if (allowedTools.length > 0) {
231
+ tools.allowed = allowedTools;
232
+ }
233
+ if (excludeTools.length > 0) {
234
+ tools.exclude = excludeTools;
235
+ }
236
+ return {
237
+ preset,
238
+ cliFlags,
239
+ workspaceFiles: [
240
+ {
241
+ relativePath: ".gemini/settings.json",
242
+ content: JSON.stringify(settings, null, 2),
243
+ format: "json"
244
+ }
245
+ ],
246
+ envVars: {},
247
+ summary: `Gemini CLI: ${def.description}`
248
+ };
249
+ }
250
+ function generateCodexApprovalConfig(preset) {
251
+ const cliFlags = [];
252
+ let approvalPolicy;
253
+ let sandboxMode;
254
+ let webSearch;
255
+ switch (preset) {
256
+ case "readonly":
257
+ approvalPolicy = "untrusted";
258
+ sandboxMode = "workspace-read";
259
+ webSearch = false;
260
+ cliFlags.push("--sandbox", "workspace-read", "-a", "untrusted");
261
+ break;
262
+ case "standard":
263
+ approvalPolicy = "on-failure";
264
+ sandboxMode = "workspace-write";
265
+ webSearch = true;
266
+ cliFlags.push("--sandbox", "workspace-write");
267
+ break;
268
+ case "permissive":
269
+ approvalPolicy = "on-request";
270
+ sandboxMode = "workspace-write";
271
+ webSearch = true;
272
+ cliFlags.push("-a", "on-request");
273
+ break;
274
+ case "autonomous":
275
+ approvalPolicy = "never";
276
+ sandboxMode = "workspace-write";
277
+ webSearch = true;
278
+ cliFlags.push("--full-auto");
279
+ break;
280
+ }
281
+ const config = {
282
+ approval_policy: approvalPolicy,
283
+ sandbox_mode: sandboxMode,
284
+ tools: {
285
+ web_search: webSearch
286
+ }
287
+ };
288
+ return {
289
+ preset,
290
+ cliFlags,
291
+ workspaceFiles: [
292
+ {
293
+ relativePath: ".codex/config.json",
294
+ content: JSON.stringify(config, null, 2),
295
+ format: "json"
296
+ }
297
+ ],
298
+ envVars: {},
299
+ summary: `Codex: ${getPresetDefinition(preset).description}`
300
+ };
301
+ }
302
+ function generateAiderApprovalConfig(preset) {
303
+ const def = getPresetDefinition(preset);
304
+ const cliFlags = [];
305
+ const lines = [];
306
+ switch (preset) {
307
+ case "readonly":
308
+ lines.push("yes-always: false");
309
+ lines.push("no-auto-commits: true");
310
+ cliFlags.push("--no-auto-commits");
311
+ break;
312
+ case "standard":
313
+ lines.push("yes-always: false");
314
+ break;
315
+ case "permissive":
316
+ lines.push("yes-always: true");
317
+ cliFlags.push("--yes-always");
318
+ break;
319
+ case "autonomous":
320
+ lines.push("yes-always: true");
321
+ cliFlags.push("--yes-always");
322
+ break;
323
+ }
324
+ return {
325
+ preset,
326
+ cliFlags,
327
+ workspaceFiles: [
328
+ {
329
+ relativePath: ".aider.conf.yml",
330
+ content: lines.join("\n") + "\n",
331
+ format: "yaml"
332
+ }
333
+ ],
334
+ envVars: {},
335
+ summary: `Aider: ${def.description}`
336
+ };
337
+ }
338
+ function generateApprovalConfig(adapterType, preset) {
339
+ switch (adapterType) {
340
+ case "claude":
341
+ return generateClaudeApprovalConfig(preset);
342
+ case "gemini":
343
+ return generateGeminiApprovalConfig(preset);
344
+ case "codex":
345
+ return generateCodexApprovalConfig(preset);
346
+ case "aider":
347
+ return generateAiderApprovalConfig(preset);
348
+ default:
349
+ throw new Error(`Unknown adapter type: ${adapterType}`);
350
+ }
351
+ }
352
+ function listPresets() {
353
+ return [...PRESET_DEFINITIONS];
354
+ }
355
+ function getPresetDefinition(preset) {
356
+ const def = PRESET_DEFINITIONS.find((d) => d.preset === preset);
357
+ if (!def) {
358
+ throw new Error(`Unknown preset: ${preset}`);
359
+ }
360
+ return def;
361
+ }
362
+
5
363
  // src/base-coding-adapter.ts
6
364
  var BaseCodingAdapter = class extends BaseCLIAdapter {
7
365
  /**
@@ -112,6 +470,40 @@ Docs: ${this.installation.docsUrl}`
112
470
  content = content.trim();
113
471
  return content;
114
472
  }
473
+ // ─────────────────────────────────────────────────────────────────────────────
474
+ // Approval Presets
475
+ // ─────────────────────────────────────────────────────────────────────────────
476
+ /**
477
+ * Extract the approval preset from a spawn config, if set.
478
+ */
479
+ getApprovalPreset(config) {
480
+ const adapterConfig = config.adapterConfig;
481
+ return adapterConfig?.approvalPreset;
482
+ }
483
+ /**
484
+ * Generate the approval config for this adapter, if a preset is set.
485
+ */
486
+ getApprovalConfig(config) {
487
+ const preset = this.getApprovalPreset(config);
488
+ if (!preset) return null;
489
+ return generateApprovalConfig(this.adapterType, preset);
490
+ }
491
+ /**
492
+ * Write approval config files to a workspace directory.
493
+ * Returns the list of files written (absolute paths).
494
+ */
495
+ async writeApprovalConfig(workspacePath, config) {
496
+ const approvalConfig = this.getApprovalConfig(config);
497
+ if (!approvalConfig) return [];
498
+ const written = [];
499
+ for (const file of approvalConfig.workspaceFiles) {
500
+ const fullPath = join(workspacePath, file.relativePath);
501
+ await mkdir(dirname(fullPath), { recursive: true });
502
+ await writeFile(fullPath, file.content, "utf-8");
503
+ written.push(fullPath);
504
+ }
505
+ return written;
506
+ }
115
507
  /**
116
508
  * Write content to this agent's memory file in a workspace.
117
509
  * Creates parent directories as needed.
@@ -246,6 +638,10 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
246
638
  args.push("--cwd", config.workdir);
247
639
  }
248
640
  }
641
+ const approvalConfig = this.getApprovalConfig(config);
642
+ if (approvalConfig) {
643
+ args.push(...approvalConfig.cliFlags);
644
+ }
249
645
  return args;
250
646
  }
251
647
  getEnv(config) {
@@ -351,6 +747,29 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
351
747
  }
352
748
  return super.detectBlockingPrompt(output);
353
749
  }
750
+ /**
751
+ * Detect task completion for Claude Code.
752
+ *
753
+ * High-confidence pattern: turn duration summary + idle prompt.
754
+ * Claude Code shows "<Verb> for Xm Ys" (e.g. "Cooked for 3m 12s")
755
+ * when a turn completes, followed by the ❯ input prompt.
756
+ *
757
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
758
+ * - claude_completed_turn_duration
759
+ * - claude_completed_turn_duration_custom_verb
760
+ */
761
+ detectTaskComplete(output) {
762
+ const stripped = this.stripAnsi(output);
763
+ const hasDuration = /[A-Z][A-Za-z' -]{2,40}\s+for\s+\d+(?:h\s+\d{1,2}m\s+\d{1,2}s|m\s+\d{1,2}s|s)/.test(stripped);
764
+ const hasIdlePrompt = /❯\s*$/.test(stripped);
765
+ if (hasDuration && hasIdlePrompt) {
766
+ return true;
767
+ }
768
+ if (hasIdlePrompt && stripped.includes("for shortcuts")) {
769
+ return true;
770
+ }
771
+ return false;
772
+ }
354
773
  detectReady(output) {
355
774
  const stripped = this.stripAnsi(output);
356
775
  if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
@@ -478,6 +897,10 @@ var GeminiAdapter = class extends BaseCodingAdapter {
478
897
  args.push("--cwd", config.workdir);
479
898
  }
480
899
  }
900
+ const approvalConfig = this.getApprovalConfig(config);
901
+ if (approvalConfig) {
902
+ args.push(...approvalConfig.cliFlags);
903
+ }
481
904
  return args;
482
905
  }
483
906
  getEnv(config) {
@@ -604,6 +1027,26 @@ var GeminiAdapter = class extends BaseCodingAdapter {
604
1027
  }
605
1028
  return super.detectBlockingPrompt(output);
606
1029
  }
1030
+ /**
1031
+ * Detect task completion for Gemini CLI.
1032
+ *
1033
+ * High-confidence patterns:
1034
+ * - "◇ Ready" window title signal (OSC sequence, may survive ANSI stripping)
1035
+ * - "Type your message" composer placeholder after agent output
1036
+ *
1037
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1038
+ * - gemini_ready_title
1039
+ */
1040
+ detectTaskComplete(output) {
1041
+ const stripped = this.stripAnsi(output);
1042
+ if (/◇\s+Ready/.test(stripped)) {
1043
+ return true;
1044
+ }
1045
+ if (/type.?your.?message/i.test(stripped)) {
1046
+ return true;
1047
+ }
1048
+ return false;
1049
+ }
607
1050
  detectReady(output) {
608
1051
  const stripped = this.stripAnsi(output);
609
1052
  if (/type.?your.?message/i.test(stripped)) {
@@ -782,6 +1225,10 @@ var CodexAdapter = class extends BaseCodingAdapter {
782
1225
  args.push("--cwd", config.workdir);
783
1226
  }
784
1227
  }
1228
+ const approvalConfig = this.getApprovalConfig(config);
1229
+ if (approvalConfig) {
1230
+ args.push(...approvalConfig.cliFlags);
1231
+ }
785
1232
  return args;
786
1233
  }
787
1234
  getEnv(config) {
@@ -910,6 +1357,32 @@ var CodexAdapter = class extends BaseCodingAdapter {
910
1357
  }
911
1358
  return super.detectBlockingPrompt(output);
912
1359
  }
1360
+ /**
1361
+ * Detect task completion for Codex CLI.
1362
+ *
1363
+ * High-confidence patterns:
1364
+ * - "Worked for Xm Ys" separator after work-heavy turns
1365
+ * - "› Ask Codex to do anything" ready prompt
1366
+ *
1367
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1368
+ * - codex_completed_worked_for_separator
1369
+ * - codex_ready_prompt
1370
+ */
1371
+ detectTaskComplete(output) {
1372
+ const stripped = this.stripAnsi(output);
1373
+ const hasWorkedFor = /Worked\s+for\s+\d+(?:h\s+\d{2}m\s+\d{2}s|m\s+\d{2}s|s)/.test(stripped);
1374
+ const hasReadyPrompt = /›\s+Ask\s+Codex\s+to\s+do\s+anything/.test(stripped);
1375
+ if (hasWorkedFor && hasReadyPrompt) {
1376
+ return true;
1377
+ }
1378
+ if (hasReadyPrompt) {
1379
+ return true;
1380
+ }
1381
+ if (hasWorkedFor && /›\s+/m.test(stripped)) {
1382
+ return true;
1383
+ }
1384
+ return false;
1385
+ }
913
1386
  detectReady(output) {
914
1387
  const stripped = this.stripAnsi(output);
915
1388
  if (/do.?you.?trust.?the.?contents/i.test(stripped) || /sign.?in.?with.?chatgpt/i.test(stripped) || /update.?available/i.test(stripped) || /enable.?full.?access/i.test(stripped) || /choose.?working.?directory/i.test(stripped)) {
@@ -1217,6 +1690,10 @@ var AiderAdapter = class extends BaseCodingAdapter {
1217
1690
  if (credentials.anthropicKey) args.push("--api-key", `anthropic=${credentials.anthropicKey}`);
1218
1691
  if (credentials.openaiKey) args.push("--api-key", `openai=${credentials.openaiKey}`);
1219
1692
  if (credentials.googleKey) args.push("--api-key", `gemini=${credentials.googleKey}`);
1693
+ const approvalConfig = this.getApprovalConfig(config);
1694
+ if (approvalConfig) {
1695
+ args.push(...approvalConfig.cliFlags);
1696
+ }
1220
1697
  return args;
1221
1698
  }
1222
1699
  getEnv(config) {
@@ -1310,6 +1787,31 @@ var AiderAdapter = class extends BaseCodingAdapter {
1310
1787
  }
1311
1788
  return super.detectBlockingPrompt(output);
1312
1789
  }
1790
+ /**
1791
+ * Detect task completion for Aider.
1792
+ *
1793
+ * High-confidence patterns:
1794
+ * - "Aider is waiting for your input" notification (bell message)
1795
+ * - Edit-format mode prompts (ask>, code>, architect>) after output
1796
+ *
1797
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1798
+ * - aider_completed_llm_response_ready
1799
+ */
1800
+ detectTaskComplete(output) {
1801
+ const stripped = this.stripAnsi(output);
1802
+ if (/Aider\s+is\s+waiting\s+for\s+your\s+input/.test(stripped)) {
1803
+ return true;
1804
+ }
1805
+ const hasPrompt = /(?:ask|code|architect)(?:\s+multi)?>\s*$/m.test(stripped);
1806
+ if (hasPrompt) {
1807
+ const hasEditMarkers = /Applied edit to|Commit [a-f0-9]+|wrote to|Updated/i.test(stripped);
1808
+ const hasTokenUsage = /Tokens:|Cost:/i.test(stripped);
1809
+ if (hasEditMarkers || hasTokenUsage) {
1810
+ return true;
1811
+ }
1812
+ }
1813
+ return false;
1814
+ }
1313
1815
  detectReady(output) {
1314
1816
  const stripped = this.stripAnsi(output);
1315
1817
  if (/login to openrouter/i.test(stripped) || /open this url in your browser/i.test(stripped) || /waiting up to 5 minutes/i.test(stripped)) {
@@ -1572,6 +2074,6 @@ async function printMissingAdapters(types) {
1572
2074
  }
1573
2075
  }
1574
2076
 
1575
- export { ADAPTER_TYPES, AiderAdapter, BaseCodingAdapter, ClaudeAdapter, CodexAdapter, GeminiAdapter, checkAdapters, checkAllAdapters, clearPatternCache, createAdapter, createAllAdapters, getBaselinePatterns, hasDynamicPatterns, loadPatterns, loadPatternsSync, preloadAllPatterns, printMissingAdapters };
2077
+ export { ADAPTER_TYPES, AIDER_COMMAND_CATEGORIES, AiderAdapter, BaseCodingAdapter, CLAUDE_TOOL_CATEGORIES, CODEX_TOOL_CATEGORIES, ClaudeAdapter, CodexAdapter, GEMINI_TOOL_CATEGORIES, GeminiAdapter, PRESET_DEFINITIONS, TOOL_CATEGORIES, checkAdapters, checkAllAdapters, clearPatternCache, createAdapter, createAllAdapters, generateAiderApprovalConfig, generateApprovalConfig, generateClaudeApprovalConfig, generateCodexApprovalConfig, generateGeminiApprovalConfig, getBaselinePatterns, getPresetDefinition, hasDynamicPatterns, listPresets, loadPatterns, loadPatternsSync, preloadAllPatterns, printMissingAdapters };
1576
2078
  //# sourceMappingURL=index.js.map
1577
2079
  //# sourceMappingURL=index.js.map