coding-agent-adapters 0.6.0 → 0.7.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
@@ -53,7 +53,9 @@ session.send('Help me refactor this function to use async/await');
53
53
 
54
54
  ## Session Lifecycle Detection
55
55
 
56
- Each adapter implements detection for every stage of a CLI session:
56
+ Each adapter implements detection for every stage of a CLI session.
57
+
58
+ All detection methods use `stripAnsi()` which strips ANSI escape sequences, cursor positioning codes, bare control characters, TUI spinner/box-drawing characters, and collapses whitespace — ensuring regex patterns match through raw terminal output. Prompt indicators (`❯`, `›`, `◇`) are preserved.
57
59
 
58
60
  ### Login / Auth Detection
59
61
 
@@ -105,9 +107,31 @@ Adapters detect prompts that block the session and require user action:
105
107
  | Codex | Directory trust, tool approval, update available, model migration, CWD selection |
106
108
  | Aider | File operations, shell commands, git init, pip install, destructive operations |
107
109
 
110
+ ### Loading / Active-Work Detection
111
+
112
+ Each adapter implements `detectLoading(output)` to detect when the CLI is actively processing — thinking spinners, file reading, model streaming. When loading is detected, pty-manager suppresses stall detection entirely, avoiding unnecessary LLM classifier calls during normal operation.
113
+
114
+ | Adapter | Loading Indicators | Source Patterns |
115
+ |---------|-------------------|----------------|
116
+ | Claude | `esc to interrupt`, `Reading N files` | `claude_active_reading_files` |
117
+ | Gemini | `esc to cancel`, `Waiting for user confirmation` | `gemini_active_loading_line` |
118
+ | Codex | `esc to interrupt`, `Booting MCP server`, `Searching the web` | `codex_active_status_row`, `codex_active_booting_mcp` |
119
+ | Aider | `Waiting for LLM/<model>`, `Generating commit message with` | `aider_active_waiting_model`, `aider_active_waiting_llm_default` |
120
+
121
+ ```typescript
122
+ const claude = new ClaudeAdapter();
123
+ claude.detectLoading('• Working (5s • esc to interrupt)'); // true
124
+ claude.detectLoading('Reading 42 files…'); // true
125
+ claude.detectLoading('❯ '); // false
126
+
127
+ const aider = new AiderAdapter();
128
+ aider.detectLoading('Waiting for claude-sonnet-4-20250514'); // true
129
+ aider.detectLoading('code> '); // false
130
+ ```
131
+
108
132
  ### Task Completion Detection
109
133
 
110
- 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.
134
+ 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. Patterns match through raw ANSI-laden TUI output including spinner characters and cursor positioning codes.
111
135
 
112
136
  | Adapter | Completion Indicators | Source Patterns |
113
137
  |---------|----------------------|----------------|
@@ -425,6 +449,11 @@ export class CursorAdapter extends BaseCodingAdapter {
425
449
  return /cursor>\s*$/m.test(output);
426
450
  }
427
451
 
452
+ detectLoading(output: string): boolean {
453
+ // Active loading indicator — suppresses stall detection
454
+ return /processing|thinking/i.test(output);
455
+ }
456
+
428
457
  detectTaskComplete(output: string): boolean {
429
458
  // High-confidence: task summary + idle prompt
430
459
  return /completed in \d+s/.test(output) && /cursor>\s*$/m.test(output);
package/dist/index.cjs CHANGED
@@ -401,17 +401,27 @@ var BaseCodingAdapter = class extends ptyManager.BaseCLIAdapter {
401
401
  return adapterConfig?.interactive === true;
402
402
  }
403
403
  /**
404
- * Override stripAnsi to handle TUI cursor movement codes.
404
+ * Override stripAnsi to handle TUI cursor movement codes, spinner/box-drawing
405
+ * characters, bare control characters, and extra whitespace.
406
+ *
405
407
  * TUI CLIs (Claude Code, Gemini CLI, Codex) use cursor positioning
406
- * sequences instead of literal spaces. Replace ALL cursor movement
407
- * codes with spaces before stripping other ANSI codes so regex
408
- * patterns can match the visible text.
408
+ * sequences instead of literal spaces, and render decorative Unicode
409
+ * characters (spinners, box-drawing). All of these must be stripped so
410
+ * adapter detection methods (detectReady, detectTaskComplete, etc.) can
411
+ * match visible text with their regex patterns.
412
+ *
413
+ * Note: ❯ and › are preserved — they are prompt indicators used by
414
+ * detectReady / detectTaskComplete.
409
415
  */
410
416
  stripAnsi(str) {
411
417
  let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
412
418
  result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
413
419
  result = result.replace(/\x1b\[\d*[JK]/g, " ");
414
- return super.stripAnsi(result);
420
+ result = super.stripAnsi(result);
421
+ result = result.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
422
+ result = result.replace(/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g, " ");
423
+ result = result.replace(/ {2,}/g, " ");
424
+ return result;
415
425
  }
416
426
  /**
417
427
  * Override detectExit to include installation instructions
@@ -760,6 +770,24 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
760
770
  }
761
771
  return super.detectBlockingPrompt(output);
762
772
  }
773
+ /**
774
+ * Detect if Claude Code is actively loading/processing.
775
+ *
776
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
777
+ * - claude_active_reading_files: "Reading N files…"
778
+ * - General: "esc to interrupt" spinner status line
779
+ */
780
+ detectLoading(output) {
781
+ const stripped = this.stripAnsi(output);
782
+ const tail = stripped.slice(-500);
783
+ if (/esc\s+to\s+interrupt/i.test(tail)) {
784
+ return true;
785
+ }
786
+ if (/Reading\s+\d+\s+files/i.test(tail)) {
787
+ return true;
788
+ }
789
+ return false;
790
+ }
763
791
  /**
764
792
  * Detect task completion for Claude Code.
765
793
  *
@@ -1051,6 +1079,24 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1051
1079
  }
1052
1080
  return super.detectBlockingPrompt(output);
1053
1081
  }
1082
+ /**
1083
+ * Detect if Gemini CLI is actively loading/processing.
1084
+ *
1085
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1086
+ * - gemini_active_loading_line: "(esc to cancel, Xs)"
1087
+ * - gemini_active_waiting_user_confirmation: "Waiting for user confirmation..."
1088
+ */
1089
+ detectLoading(output) {
1090
+ const stripped = this.stripAnsi(output);
1091
+ const tail = stripped.slice(-500);
1092
+ if (/esc\s+to\s+cancel/i.test(tail)) {
1093
+ return true;
1094
+ }
1095
+ if (/Waiting\s+for\s+user\s+confirmation/i.test(tail)) {
1096
+ return true;
1097
+ }
1098
+ return false;
1099
+ }
1054
1100
  /**
1055
1101
  * Detect task completion for Gemini CLI.
1056
1102
  *
@@ -1387,6 +1433,28 @@ var CodexAdapter = class extends BaseCodingAdapter {
1387
1433
  }
1388
1434
  return super.detectBlockingPrompt(output);
1389
1435
  }
1436
+ /**
1437
+ * Detect if Codex CLI is actively loading/processing.
1438
+ *
1439
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1440
+ * - codex_active_status_row: "• Working (0s • esc to interrupt)"
1441
+ * - codex_active_booting_mcp: "Booting MCP server: ..."
1442
+ * - codex_active_web_search: "Searching the web"
1443
+ */
1444
+ detectLoading(output) {
1445
+ const stripped = this.stripAnsi(output);
1446
+ const tail = stripped.slice(-500);
1447
+ if (/esc\s+to\s+interrupt/i.test(tail)) {
1448
+ return true;
1449
+ }
1450
+ if (/Booting\s+MCP\s+server/i.test(tail)) {
1451
+ return true;
1452
+ }
1453
+ if (/Searching\s+the\s+web/i.test(tail)) {
1454
+ return true;
1455
+ }
1456
+ return false;
1457
+ }
1390
1458
  /**
1391
1459
  * Detect task completion for Codex CLI.
1392
1460
  *
@@ -1819,6 +1887,25 @@ var AiderAdapter = class extends BaseCodingAdapter {
1819
1887
  }
1820
1888
  return super.detectBlockingPrompt(output);
1821
1889
  }
1890
+ /**
1891
+ * Detect if Aider is actively loading/processing.
1892
+ *
1893
+ * Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
1894
+ * - aider_active_waiting_model: "Waiting for <model>"
1895
+ * - aider_active_waiting_llm_default: "Waiting for LLM"
1896
+ * - aider_active_generating_commit_message: "Generating commit message with ..."
1897
+ */
1898
+ detectLoading(output) {
1899
+ const stripped = this.stripAnsi(output);
1900
+ const tail = stripped.slice(-500);
1901
+ if (/Waiting\s+for\s+(?:LLM|[A-Za-z0-9_./:@-]+)/i.test(tail)) {
1902
+ return true;
1903
+ }
1904
+ if (/Generating\s+commit\s+message\s+with\s+/i.test(tail)) {
1905
+ return true;
1906
+ }
1907
+ return false;
1908
+ }
1822
1909
  /**
1823
1910
  * Detect task completion for Aider.
1824
1911
  *