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/README.md CHANGED
@@ -94,6 +94,27 @@ Adapters detect prompts that block the session and require user action:
94
94
  | Codex | Directory trust, tool approval, update available, model migration, CWD selection |
95
95
  | Aider | File operations, shell commands, git init, pip install, destructive operations |
96
96
 
97
+ ### Task Completion Detection
98
+
99
+ Each adapter implements `detectTaskComplete(output)` to recognize when the CLI has finished a task and returned to its idle prompt. This is more specific than `detectReady()` — it matches high-confidence completion indicators (duration summaries, explicit done messages) that short-circuit the LLM stall classifier in pty-manager.
100
+
101
+ | Adapter | Completion Indicators | Source Patterns |
102
+ |---------|----------------------|----------------|
103
+ | Claude | Turn duration (`Cooked for 3m 12s`, custom verb) + `❯` prompt | `claude_completed_turn_duration` |
104
+ | Gemini | `◇ Ready` window title, `Type your message` composer | `gemini_ready_title` |
105
+ | Codex | `Worked for 1m 05s` separator + `›` prompt | `codex_completed_worked_for_separator`, `codex_ready_prompt` |
106
+ | Aider | `Aider is waiting for your input`, mode prompts with edit/cost markers | `aider_completed_llm_response_ready` |
107
+
108
+ ```typescript
109
+ const claude = new ClaudeAdapter();
110
+ claude.detectTaskComplete('Cooked for 3m 12s\n❯ '); // true
111
+ claude.detectTaskComplete('Reading 5 files…'); // false
112
+
113
+ const aider = new AiderAdapter();
114
+ aider.detectTaskComplete('Applied edit to main.ts\nTokens: 1234\ncode> '); // true
115
+ aider.detectTaskComplete('Waiting for claude-sonnet-4-20250514'); // false
116
+ ```
117
+
97
118
  ### Exit Detection
98
119
 
99
120
  Adapters detect when a CLI session has ended:
@@ -217,6 +238,78 @@ await adapter.writeMemoryFile('/path/to/workspace', '# Task-Specific Context\n..
217
238
  });
218
239
  ```
219
240
 
241
+ ## Approval Presets
242
+
243
+ Each coding agent CLI has its own config format for controlling tool permissions. The approval preset system provides 4 named levels that translate to the correct per-CLI config files and CLI flags.
244
+
245
+ ### Preset Levels
246
+
247
+ | Preset | Description | Auto-approve | Require approval | Blocked |
248
+ |--------|-------------|-------------|-----------------|---------|
249
+ | `readonly` | Read-only. Safe for auditing. | file_read, planning, user_interaction | — | file_write, shell, web, agent |
250
+ | `standard` | Standard dev. Reads + web auto, writes/shell prompt. | file_read, planning, user_interaction, web | file_write, shell, agent | — |
251
+ | `permissive` | File ops auto-approved, shell still prompts. | file_read, file_write, planning, user_interaction, web, agent | shell | — |
252
+ | `autonomous` | Everything auto-approved. Use with sandbox. | all categories | — | — |
253
+
254
+ ### Generating Configs
255
+
256
+ ```typescript
257
+ import { generateApprovalConfig, listPresets, getPresetDefinition } from 'coding-agent-adapters';
258
+
259
+ // List all available presets
260
+ const presets = listPresets();
261
+
262
+ // Generate CLI-specific config for a preset
263
+ const config = generateApprovalConfig('claude', 'permissive');
264
+ // {
265
+ // preset: 'permissive',
266
+ // cliFlags: [],
267
+ // workspaceFiles: [{ relativePath: '.claude/settings.json', content: '...', format: 'json' }],
268
+ // envVars: {},
269
+ // summary: 'Claude Code: File ops auto-approved, shell still prompts.',
270
+ // }
271
+
272
+ // Each CLI gets its own config format
273
+ generateApprovalConfig('gemini', 'readonly'); // → .gemini/settings.json + --approval-mode plan
274
+ generateApprovalConfig('codex', 'autonomous'); // → .codex/config.json + --full-auto
275
+ generateApprovalConfig('aider', 'permissive'); // → .aider.conf.yml + --yes-always
276
+ ```
277
+
278
+ ### Using Presets with Adapters
279
+
280
+ When `approvalPreset` is set in `adapterConfig`, the adapter's `getArgs()` automatically appends the correct CLI flags:
281
+
282
+ ```typescript
283
+ const session = await manager.spawn({
284
+ name: 'sandboxed-agent',
285
+ type: 'claude',
286
+ workdir: '/path/to/project',
287
+ adapterConfig: {
288
+ anthropicKey: process.env.ANTHROPIC_API_KEY,
289
+ approvalPreset: 'autonomous',
290
+ },
291
+ });
292
+ ```
293
+
294
+ You can also write the config files to a workspace manually using `writeApprovalConfig()`:
295
+
296
+ ```typescript
297
+ const adapter = new ClaudeAdapter();
298
+ const writtenFiles = await adapter.writeApprovalConfig('/path/to/workspace', {
299
+ adapterConfig: { approvalPreset: 'permissive' },
300
+ });
301
+ // writtenFiles: ['/path/to/workspace/.claude/settings.json']
302
+ ```
303
+
304
+ ### Per-CLI Output
305
+
306
+ | CLI | Config File | Key Controls |
307
+ |-----|------------|--------------|
308
+ | Claude Code | `.claude/settings.json` | `permissions.allow`, `permissions.deny`, `sandbox.*` |
309
+ | Gemini CLI | `.gemini/settings.json` | `general.defaultApprovalMode`, `tools.allowed`, `tools.exclude` |
310
+ | Codex | `.codex/config.json` | `approval_policy`, `sandbox_mode`, `tools.web_search` |
311
+ | Aider | `.aider.conf.yml` | `yes-always`, `no-auto-commits` |
312
+
220
313
  ## Preflight Check
221
314
 
222
315
  Before spawning agents, check if the required CLIs are installed:
@@ -318,6 +411,11 @@ export class CursorAdapter extends BaseCodingAdapter {
318
411
  return /cursor>\s*$/m.test(output);
319
412
  }
320
413
 
414
+ detectTaskComplete(output: string): boolean {
415
+ // High-confidence: task summary + idle prompt
416
+ return /completed in \d+s/.test(output) && /cursor>\s*$/m.test(output);
417
+ }
418
+
321
419
  parseOutput(output: string): ParsedOutput | null {
322
420
  return { type: 'response', content: output.trim(), isComplete: true, isQuestion: output.includes('?') };
323
421
  }