coding-agent-adapters 0.3.0 → 0.4.1

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 (tolerates trailing status bar) | `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:
@@ -390,6 +411,11 @@ export class CursorAdapter extends BaseCodingAdapter {
390
411
  return /cursor>\s*$/m.test(output);
391
412
  }
392
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
+
393
419
  parseOutput(output: string): ParsedOutput | null {
394
420
  return { type: 'response', content: output.trim(), isComplete: true, isQuestion: output.includes('?') };
395
421
  }
package/dist/index.cjs CHANGED
@@ -56,6 +56,8 @@ var CLAUDE_TOOL_CATEGORIES = {
56
56
  // file_write
57
57
  Write: "file_write",
58
58
  Edit: "file_write",
59
+ MultiEdit: "file_write",
60
+ NotebookEdit: "file_write",
59
61
  // shell
60
62
  Bash: "shell",
61
63
  BashOutput: "shell",
@@ -747,15 +749,40 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
747
749
  }
748
750
  return super.detectBlockingPrompt(output);
749
751
  }
752
+ /**
753
+ * Detect task completion for Claude Code.
754
+ *
755
+ * High-confidence pattern: turn duration summary + idle prompt.
756
+ * Claude Code shows "<Verb> for Xm Ys" (e.g. "Cooked for 3m 12s")
757
+ * when a turn completes, followed by the ❯ input prompt.
758
+ *
759
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
760
+ * - claude_completed_turn_duration
761
+ * - claude_completed_turn_duration_custom_verb
762
+ */
763
+ detectTaskComplete(output) {
764
+ const stripped = this.stripAnsi(output);
765
+ 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);
766
+ const tail = stripped.slice(-300);
767
+ const hasIdlePrompt = /❯/.test(tail);
768
+ if (hasDuration && hasIdlePrompt) {
769
+ return true;
770
+ }
771
+ if (hasIdlePrompt && stripped.includes("for shortcuts")) {
772
+ return true;
773
+ }
774
+ return false;
775
+ }
750
776
  detectReady(output) {
751
777
  const stripped = this.stripAnsi(output);
752
778
  if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
753
779
  return false;
754
780
  }
781
+ const tail = stripped.slice(-300);
755
782
  return stripped.includes("How can I help") || stripped.includes("What would you like") || // v2.1+ shows "for shortcuts" hint when ready
756
783
  stripped.includes("for shortcuts") || // Match "claude> " or similar specific prompts, not bare ">"
757
- /claude>\s*$/i.test(stripped) || // v2.1+ uses ❯ as the input prompt
758
- /❯\s*$/.test(stripped);
784
+ /claude>/i.test(tail) || // v2.1+ uses ❯ as the input prompt
785
+ /❯/.test(tail);
759
786
  }
760
787
  parseOutput(output) {
761
788
  const stripped = this.stripAnsi(output);
@@ -1004,6 +1031,26 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1004
1031
  }
1005
1032
  return super.detectBlockingPrompt(output);
1006
1033
  }
1034
+ /**
1035
+ * Detect task completion for Gemini CLI.
1036
+ *
1037
+ * High-confidence patterns:
1038
+ * - "◇ Ready" window title signal (OSC sequence, may survive ANSI stripping)
1039
+ * - "Type your message" composer placeholder after agent output
1040
+ *
1041
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1042
+ * - gemini_ready_title
1043
+ */
1044
+ detectTaskComplete(output) {
1045
+ const stripped = this.stripAnsi(output);
1046
+ if (/◇\s+Ready/.test(stripped)) {
1047
+ return true;
1048
+ }
1049
+ if (/type.?your.?message/i.test(stripped)) {
1050
+ return true;
1051
+ }
1052
+ return false;
1053
+ }
1007
1054
  detectReady(output) {
1008
1055
  const stripped = this.stripAnsi(output);
1009
1056
  if (/type.?your.?message/i.test(stripped)) {
@@ -1314,6 +1361,32 @@ var CodexAdapter = class extends BaseCodingAdapter {
1314
1361
  }
1315
1362
  return super.detectBlockingPrompt(output);
1316
1363
  }
1364
+ /**
1365
+ * Detect task completion for Codex CLI.
1366
+ *
1367
+ * High-confidence patterns:
1368
+ * - "Worked for Xm Ys" separator after work-heavy turns
1369
+ * - "› Ask Codex to do anything" ready prompt
1370
+ *
1371
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1372
+ * - codex_completed_worked_for_separator
1373
+ * - codex_ready_prompt
1374
+ */
1375
+ detectTaskComplete(output) {
1376
+ const stripped = this.stripAnsi(output);
1377
+ const hasWorkedFor = /Worked\s+for\s+\d+(?:h\s+\d{2}m\s+\d{2}s|m\s+\d{2}s|s)/.test(stripped);
1378
+ const hasReadyPrompt = /›\s+Ask\s+Codex\s+to\s+do\s+anything/.test(stripped);
1379
+ if (hasWorkedFor && hasReadyPrompt) {
1380
+ return true;
1381
+ }
1382
+ if (hasReadyPrompt) {
1383
+ return true;
1384
+ }
1385
+ if (hasWorkedFor && /›\s+/m.test(stripped)) {
1386
+ return true;
1387
+ }
1388
+ return false;
1389
+ }
1317
1390
  detectReady(output) {
1318
1391
  const stripped = this.stripAnsi(output);
1319
1392
  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)) {
@@ -1718,6 +1791,31 @@ var AiderAdapter = class extends BaseCodingAdapter {
1718
1791
  }
1719
1792
  return super.detectBlockingPrompt(output);
1720
1793
  }
1794
+ /**
1795
+ * Detect task completion for Aider.
1796
+ *
1797
+ * High-confidence patterns:
1798
+ * - "Aider is waiting for your input" notification (bell message)
1799
+ * - Edit-format mode prompts (ask>, code>, architect>) after output
1800
+ *
1801
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1802
+ * - aider_completed_llm_response_ready
1803
+ */
1804
+ detectTaskComplete(output) {
1805
+ const stripped = this.stripAnsi(output);
1806
+ if (/Aider\s+is\s+waiting\s+for\s+your\s+input/.test(stripped)) {
1807
+ return true;
1808
+ }
1809
+ const hasPrompt = /(?:ask|code|architect)(?:\s+multi)?>\s*$/m.test(stripped);
1810
+ if (hasPrompt) {
1811
+ const hasEditMarkers = /Applied edit to|Commit [a-f0-9]+|wrote to|Updated/i.test(stripped);
1812
+ const hasTokenUsage = /Tokens:|Cost:/i.test(stripped);
1813
+ if (hasEditMarkers || hasTokenUsage) {
1814
+ return true;
1815
+ }
1816
+ }
1817
+ return false;
1818
+ }
1721
1819
  detectReady(output) {
1722
1820
  const stripped = this.stripAnsi(output);
1723
1821
  if (/login to openrouter/i.test(stripped) || /open this url in your browser/i.test(stripped) || /waiting up to 5 minutes/i.test(stripped)) {