coding-agent-adapters 0.14.0 → 0.15.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.cjs +1103 -1006
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -101
- package/dist/index.d.ts +104 -101
- package/dist/index.js +1135 -1042
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,18 +1,40 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
1
2
|
import { mkdir, writeFile, appendFile } from 'fs/promises';
|
|
2
3
|
import { join, dirname } from 'path';
|
|
3
4
|
import { BaseCLIAdapter } from 'adapter-types';
|
|
4
5
|
|
|
5
|
-
// src/
|
|
6
|
+
// src/index.ts
|
|
6
7
|
|
|
7
8
|
// src/approval-presets.ts
|
|
9
|
+
var _autonomousSandboxWarningLogged = false;
|
|
8
10
|
var TOOL_CATEGORIES = [
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
+
{
|
|
12
|
+
category: "file_read",
|
|
13
|
+
risk: "low",
|
|
14
|
+
description: "Read files, search, list directories"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
category: "file_write",
|
|
18
|
+
risk: "medium",
|
|
19
|
+
description: "Write, edit, and create files"
|
|
20
|
+
},
|
|
11
21
|
{ category: "shell", risk: "high", description: "Execute shell commands" },
|
|
12
22
|
{ category: "web", risk: "medium", description: "Web search and fetch" },
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
{
|
|
24
|
+
category: "agent",
|
|
25
|
+
risk: "medium",
|
|
26
|
+
description: "Spawn sub-agents, skills, MCP tools"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
category: "planning",
|
|
30
|
+
risk: "low",
|
|
31
|
+
description: "Task planning and todo management"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
category: "user_interaction",
|
|
35
|
+
risk: "low",
|
|
36
|
+
description: "Ask user questions"
|
|
37
|
+
}
|
|
16
38
|
];
|
|
17
39
|
var PRESET_DEFINITIONS = [
|
|
18
40
|
{
|
|
@@ -32,14 +54,29 @@ var PRESET_DEFINITIONS = [
|
|
|
32
54
|
{
|
|
33
55
|
preset: "permissive",
|
|
34
56
|
description: "File ops auto-approved, shell still prompts.",
|
|
35
|
-
autoApprove: [
|
|
57
|
+
autoApprove: [
|
|
58
|
+
"file_read",
|
|
59
|
+
"file_write",
|
|
60
|
+
"planning",
|
|
61
|
+
"user_interaction",
|
|
62
|
+
"web",
|
|
63
|
+
"agent"
|
|
64
|
+
],
|
|
36
65
|
requireApproval: ["shell"],
|
|
37
66
|
blocked: []
|
|
38
67
|
},
|
|
39
68
|
{
|
|
40
69
|
preset: "autonomous",
|
|
41
70
|
description: "Everything auto-approved. Use with sandbox.",
|
|
42
|
-
autoApprove: [
|
|
71
|
+
autoApprove: [
|
|
72
|
+
"file_read",
|
|
73
|
+
"file_write",
|
|
74
|
+
"shell",
|
|
75
|
+
"web",
|
|
76
|
+
"agent",
|
|
77
|
+
"planning",
|
|
78
|
+
"user_interaction"
|
|
79
|
+
],
|
|
43
80
|
requireApproval: [],
|
|
44
81
|
blocked: []
|
|
45
82
|
}
|
|
@@ -160,7 +197,10 @@ function getToolsForCategories(mapping, categories) {
|
|
|
160
197
|
}
|
|
161
198
|
function generateClaudeApprovalConfig(preset) {
|
|
162
199
|
const def = getPresetDefinition(preset);
|
|
163
|
-
const allowTools = getToolsForCategories(
|
|
200
|
+
const allowTools = getToolsForCategories(
|
|
201
|
+
CLAUDE_TOOL_CATEGORIES,
|
|
202
|
+
def.autoApprove
|
|
203
|
+
);
|
|
164
204
|
const denyTools = getToolsForCategories(CLAUDE_TOOL_CATEGORIES, def.blocked);
|
|
165
205
|
const settings = {
|
|
166
206
|
permissions: {}
|
|
@@ -180,6 +220,13 @@ function generateClaudeApprovalConfig(preset) {
|
|
|
180
220
|
}
|
|
181
221
|
const cliFlags = [];
|
|
182
222
|
if (preset === "autonomous") {
|
|
223
|
+
cliFlags.push("--dangerously-skip-permissions");
|
|
224
|
+
if (!_autonomousSandboxWarningLogged) {
|
|
225
|
+
console.warn(
|
|
226
|
+
"Autonomous preset uses --dangerously-skip-permissions. Ensure agents run in a sandboxed environment."
|
|
227
|
+
);
|
|
228
|
+
_autonomousSandboxWarningLogged = true;
|
|
229
|
+
}
|
|
183
230
|
const allTools = Object.keys(CLAUDE_TOOL_CATEGORIES);
|
|
184
231
|
cliFlags.push("--tools", allTools.join(","));
|
|
185
232
|
}
|
|
@@ -200,8 +247,14 @@ function generateClaudeApprovalConfig(preset) {
|
|
|
200
247
|
function generateGeminiApprovalConfig(preset) {
|
|
201
248
|
const def = getPresetDefinition(preset);
|
|
202
249
|
const cliFlags = [];
|
|
203
|
-
const allowedTools = getToolsForCategories(
|
|
204
|
-
|
|
250
|
+
const allowedTools = getToolsForCategories(
|
|
251
|
+
GEMINI_TOOL_CATEGORIES,
|
|
252
|
+
def.autoApprove
|
|
253
|
+
);
|
|
254
|
+
const excludeTools = getToolsForCategories(
|
|
255
|
+
GEMINI_TOOL_CATEGORIES,
|
|
256
|
+
def.blocked
|
|
257
|
+
);
|
|
205
258
|
let approvalMode;
|
|
206
259
|
switch (preset) {
|
|
207
260
|
case "readonly":
|
|
@@ -327,7 +380,8 @@ function generateAiderApprovalConfig(preset) {
|
|
|
327
380
|
workspaceFiles: [
|
|
328
381
|
{
|
|
329
382
|
relativePath: ".aider.conf.yml",
|
|
330
|
-
content: lines.join("\n")
|
|
383
|
+
content: `${lines.join("\n")}
|
|
384
|
+
`,
|
|
331
385
|
format: "yaml"
|
|
332
386
|
}
|
|
333
387
|
],
|
|
@@ -389,7 +443,9 @@ var BaseCodingAdapter = class extends BaseCLIAdapter {
|
|
|
389
443
|
* Returns the relativePath of the first 'memory' type file from getWorkspaceFiles().
|
|
390
444
|
*/
|
|
391
445
|
get memoryFilePath() {
|
|
392
|
-
const memoryFile = this.getWorkspaceFiles().find(
|
|
446
|
+
const memoryFile = this.getWorkspaceFiles().find(
|
|
447
|
+
(f) => f.type === "memory"
|
|
448
|
+
);
|
|
393
449
|
if (!memoryFile) {
|
|
394
450
|
throw new Error(`${this.displayName} adapter has no memory file defined`);
|
|
395
451
|
}
|
|
@@ -430,7 +486,10 @@ var BaseCodingAdapter = class extends BaseCLIAdapter {
|
|
|
430
486
|
result = super.stripAnsi(result);
|
|
431
487
|
result = result.replace(/[\x00-\x08\x0b-\x0d\x0e-\x1f\x7f]/g, "");
|
|
432
488
|
result = result.replace(/\xa0/g, " ");
|
|
433
|
-
result = result.replace(
|
|
489
|
+
result = result.replace(
|
|
490
|
+
/[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g,
|
|
491
|
+
" "
|
|
492
|
+
);
|
|
434
493
|
result = result.replace(/ {2,}/g, " ");
|
|
435
494
|
return result;
|
|
436
495
|
}
|
|
@@ -505,7 +564,10 @@ Docs: ${this.installation.docsUrl}`
|
|
|
505
564
|
extractContent(output, promptPattern) {
|
|
506
565
|
let content = output;
|
|
507
566
|
content = content.replace(promptPattern, "");
|
|
508
|
-
content = content.replace(
|
|
567
|
+
content = content.replace(
|
|
568
|
+
/^(Thinking|Working|Reading|Writing|Processing|Generating)\.+$/gm,
|
|
569
|
+
""
|
|
570
|
+
);
|
|
509
571
|
content = content.trim();
|
|
510
572
|
return content;
|
|
511
573
|
}
|
|
@@ -525,7 +587,10 @@ Docs: ${this.installation.docsUrl}`
|
|
|
525
587
|
getApprovalConfig(config) {
|
|
526
588
|
const preset = this.getApprovalPreset(config);
|
|
527
589
|
if (!preset) return null;
|
|
528
|
-
return generateApprovalConfig(
|
|
590
|
+
return generateApprovalConfig(
|
|
591
|
+
this.adapterType,
|
|
592
|
+
preset
|
|
593
|
+
);
|
|
529
594
|
}
|
|
530
595
|
/**
|
|
531
596
|
* Write approval config files to a workspace directory.
|
|
@@ -565,155 +630,273 @@ Docs: ${this.installation.docsUrl}`
|
|
|
565
630
|
}
|
|
566
631
|
};
|
|
567
632
|
|
|
568
|
-
// src/
|
|
569
|
-
var
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
633
|
+
// src/aider-adapter.ts
|
|
634
|
+
var AiderAdapter = class extends BaseCodingAdapter {
|
|
635
|
+
adapterType = "aider";
|
|
636
|
+
displayName = "Aider";
|
|
637
|
+
/** Minimal TUI, mostly text output — shorter settle delay */
|
|
638
|
+
readySettleMs = 200;
|
|
639
|
+
/**
|
|
640
|
+
* Aider uses plain text [y/n] prompts, NOT TUI arrow-key menus.
|
|
641
|
+
*/
|
|
642
|
+
usesTuiMenus = false;
|
|
575
643
|
installation = {
|
|
576
|
-
command: "
|
|
644
|
+
command: "pip install aider-chat",
|
|
577
645
|
alternatives: [
|
|
578
|
-
"
|
|
579
|
-
"brew install
|
|
646
|
+
"pipx install aider-chat (isolated install)",
|
|
647
|
+
"brew install aider (macOS with Homebrew)"
|
|
580
648
|
],
|
|
581
|
-
docsUrl: "https://
|
|
582
|
-
minVersion: "
|
|
649
|
+
docsUrl: "https://aider.chat/docs/install.html",
|
|
650
|
+
minVersion: "0.50.0"
|
|
583
651
|
};
|
|
584
652
|
/**
|
|
585
|
-
* Auto-response rules for
|
|
586
|
-
*
|
|
587
|
-
*
|
|
653
|
+
* Auto-response rules for Aider CLI.
|
|
654
|
+
* Aider uses plain text prompts via io.py:832 with (Y)es/(N)o format.
|
|
655
|
+
* All rules are responseType: 'text' — Aider never uses TUI menus.
|
|
656
|
+
*
|
|
657
|
+
* Decline rules come first to override the generic accept patterns.
|
|
588
658
|
*/
|
|
589
659
|
autoResponseRules = [
|
|
660
|
+
// ── Decline rules (specific, checked first) ────────────────────────
|
|
590
661
|
{
|
|
591
|
-
pattern: /
|
|
662
|
+
pattern: /allow collection of anonymous analytics/i,
|
|
592
663
|
type: "config",
|
|
593
|
-
response: "",
|
|
594
|
-
responseType: "
|
|
595
|
-
|
|
596
|
-
description: "Accept Claude first-run theme/style prompt",
|
|
664
|
+
response: "n",
|
|
665
|
+
responseType: "text",
|
|
666
|
+
description: "Decline Aider telemetry opt-in",
|
|
597
667
|
safe: true,
|
|
598
668
|
once: true
|
|
599
669
|
},
|
|
600
670
|
{
|
|
601
|
-
pattern: /
|
|
602
|
-
type: "
|
|
603
|
-
response: "",
|
|
604
|
-
responseType: "
|
|
605
|
-
|
|
606
|
-
description: "Accept trust prompt for working directory",
|
|
671
|
+
pattern: /would you like to see what.?s new in this version/i,
|
|
672
|
+
type: "config",
|
|
673
|
+
response: "n",
|
|
674
|
+
responseType: "text",
|
|
675
|
+
description: "Decline release notes offer",
|
|
607
676
|
safe: true,
|
|
608
677
|
once: true
|
|
609
678
|
},
|
|
610
679
|
{
|
|
611
|
-
pattern: /
|
|
612
|
-
type: "
|
|
613
|
-
response: "",
|
|
614
|
-
responseType: "
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
safe: true,
|
|
618
|
-
once: true
|
|
680
|
+
pattern: /open a github issue pre-filled/i,
|
|
681
|
+
type: "config",
|
|
682
|
+
response: "n",
|
|
683
|
+
responseType: "text",
|
|
684
|
+
description: "Decline automatic bug report",
|
|
685
|
+
safe: true
|
|
619
686
|
},
|
|
620
687
|
{
|
|
621
|
-
pattern: /
|
|
622
|
-
type: "
|
|
688
|
+
pattern: /open documentation url for more info\?/i,
|
|
689
|
+
type: "config",
|
|
623
690
|
response: "n",
|
|
624
691
|
responseType: "text",
|
|
625
|
-
description: "Decline
|
|
692
|
+
description: "Decline opening Aider documentation for model warnings",
|
|
626
693
|
safe: true
|
|
627
694
|
},
|
|
695
|
+
// ── File / edit operations ──────────────────────────────────────────
|
|
628
696
|
{
|
|
629
|
-
pattern: /
|
|
630
|
-
type: "
|
|
631
|
-
response: "
|
|
697
|
+
pattern: /add .+ to the chat\?/i,
|
|
698
|
+
type: "permission",
|
|
699
|
+
response: "y",
|
|
632
700
|
responseType: "text",
|
|
633
|
-
description: "
|
|
701
|
+
description: "Allow Aider to add files to chat context",
|
|
634
702
|
safe: true
|
|
635
703
|
},
|
|
636
704
|
{
|
|
637
|
-
pattern: /
|
|
638
|
-
type: "
|
|
639
|
-
response: "
|
|
705
|
+
pattern: /add url to the chat\?/i,
|
|
706
|
+
type: "permission",
|
|
707
|
+
response: "y",
|
|
640
708
|
responseType: "text",
|
|
641
|
-
description: "
|
|
709
|
+
description: "Allow Aider to add URL content to chat",
|
|
642
710
|
safe: true
|
|
643
711
|
},
|
|
644
712
|
{
|
|
645
|
-
pattern: /
|
|
646
|
-
type: "
|
|
647
|
-
response: "
|
|
713
|
+
pattern: /create new file\?/i,
|
|
714
|
+
type: "permission",
|
|
715
|
+
response: "y",
|
|
648
716
|
responseType: "text",
|
|
649
|
-
description: "
|
|
717
|
+
description: "Allow Aider to create new files",
|
|
650
718
|
safe: true
|
|
651
719
|
},
|
|
652
720
|
{
|
|
653
|
-
pattern: /
|
|
721
|
+
pattern: /allow edits to file/i,
|
|
722
|
+
type: "permission",
|
|
723
|
+
response: "y",
|
|
724
|
+
responseType: "text",
|
|
725
|
+
description: "Allow edits to file not yet in chat",
|
|
726
|
+
safe: true
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
pattern: /edit the files\?/i,
|
|
730
|
+
type: "permission",
|
|
731
|
+
response: "y",
|
|
732
|
+
responseType: "text",
|
|
733
|
+
description: "Accept architect mode edits",
|
|
734
|
+
safe: true
|
|
735
|
+
},
|
|
736
|
+
// ── Shell operations ────────────────────────────────────────────────
|
|
737
|
+
{
|
|
738
|
+
pattern: /run shell commands?\?/i,
|
|
739
|
+
type: "permission",
|
|
740
|
+
response: "y",
|
|
741
|
+
responseType: "text",
|
|
742
|
+
description: "Allow Aider to run shell commands",
|
|
743
|
+
safe: true
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
pattern: /add command output to the chat\?/i,
|
|
747
|
+
type: "permission",
|
|
748
|
+
response: "y",
|
|
749
|
+
responseType: "text",
|
|
750
|
+
description: "Add shell command output to chat context",
|
|
751
|
+
safe: true
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
pattern: /add \d+.*tokens of command output to the chat\?/i,
|
|
755
|
+
type: "permission",
|
|
756
|
+
response: "y",
|
|
757
|
+
responseType: "text",
|
|
758
|
+
description: "Add /run command output to chat context",
|
|
759
|
+
safe: true
|
|
760
|
+
},
|
|
761
|
+
// ── Setup / maintenance ─────────────────────────────────────────────
|
|
762
|
+
{
|
|
763
|
+
pattern: /no git repo found.*create one/i,
|
|
654
764
|
type: "config",
|
|
655
|
-
response: "
|
|
765
|
+
response: "y",
|
|
656
766
|
responseType: "text",
|
|
657
|
-
description: "
|
|
767
|
+
description: "Create git repo for change tracking",
|
|
658
768
|
safe: true,
|
|
659
769
|
once: true
|
|
660
770
|
},
|
|
661
771
|
{
|
|
662
|
-
pattern: /
|
|
772
|
+
pattern: /add .+ to \.gitignore/i,
|
|
663
773
|
type: "config",
|
|
664
774
|
response: "y",
|
|
665
775
|
responseType: "text",
|
|
666
|
-
description: "
|
|
776
|
+
description: "Update .gitignore with Aider patterns",
|
|
777
|
+
safe: true,
|
|
778
|
+
once: true
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
pattern: /run pip install\?/i,
|
|
782
|
+
type: "config",
|
|
783
|
+
response: "y",
|
|
784
|
+
responseType: "text",
|
|
785
|
+
description: "Install missing Python dependencies",
|
|
786
|
+
safe: true
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
pattern: /install playwright\?/i,
|
|
790
|
+
type: "config",
|
|
791
|
+
response: "y",
|
|
792
|
+
responseType: "text",
|
|
793
|
+
description: "Install Playwright for web scraping",
|
|
794
|
+
safe: true
|
|
795
|
+
},
|
|
796
|
+
// ── Other safe confirmations ────────────────────────────────────────
|
|
797
|
+
{
|
|
798
|
+
pattern: /fix lint errors in/i,
|
|
799
|
+
type: "permission",
|
|
800
|
+
response: "y",
|
|
801
|
+
responseType: "text",
|
|
802
|
+
description: "Accept lint error fix suggestion",
|
|
803
|
+
safe: true
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
pattern: /try to proceed anyway\?/i,
|
|
807
|
+
type: "config",
|
|
808
|
+
response: "y",
|
|
809
|
+
responseType: "text",
|
|
810
|
+
description: "Continue despite context limit warning",
|
|
667
811
|
safe: true
|
|
668
812
|
}
|
|
669
813
|
];
|
|
670
814
|
getWorkspaceFiles() {
|
|
671
815
|
return [
|
|
672
816
|
{
|
|
673
|
-
relativePath: "
|
|
674
|
-
description: "Project
|
|
817
|
+
relativePath: ".aider.conventions.md",
|
|
818
|
+
description: "Project conventions and instructions read on startup (--read flag)",
|
|
675
819
|
autoLoaded: true,
|
|
676
820
|
type: "memory",
|
|
677
821
|
format: "markdown"
|
|
678
822
|
},
|
|
679
823
|
{
|
|
680
|
-
relativePath: ".
|
|
681
|
-
description: "Project-scoped
|
|
824
|
+
relativePath: ".aider.conf.yml",
|
|
825
|
+
description: "Project-scoped Aider configuration (model, flags, options)",
|
|
682
826
|
autoLoaded: true,
|
|
683
827
|
type: "config",
|
|
684
|
-
format: "
|
|
828
|
+
format: "yaml"
|
|
685
829
|
},
|
|
686
830
|
{
|
|
687
|
-
relativePath: ".
|
|
688
|
-
description: "
|
|
689
|
-
autoLoaded:
|
|
690
|
-
type: "
|
|
691
|
-
format: "
|
|
831
|
+
relativePath: ".aiderignore",
|
|
832
|
+
description: "Gitignore-style file listing paths Aider should not edit",
|
|
833
|
+
autoLoaded: true,
|
|
834
|
+
type: "rules",
|
|
835
|
+
format: "text"
|
|
692
836
|
}
|
|
693
837
|
];
|
|
694
838
|
}
|
|
695
|
-
getRecommendedModels(
|
|
839
|
+
getRecommendedModels(credentials) {
|
|
840
|
+
if (credentials?.anthropicKey) {
|
|
841
|
+
return {
|
|
842
|
+
powerful: "anthropic/claude-sonnet-4-20250514",
|
|
843
|
+
fast: "anthropic/claude-haiku-4-5-20251001"
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
if (credentials?.openaiKey) {
|
|
847
|
+
return {
|
|
848
|
+
powerful: "openai/o3",
|
|
849
|
+
fast: "openai/gpt-4o-mini"
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
if (credentials?.googleKey) {
|
|
853
|
+
return {
|
|
854
|
+
powerful: "gemini/gemini-3-pro",
|
|
855
|
+
fast: "gemini/gemini-3-flash"
|
|
856
|
+
};
|
|
857
|
+
}
|
|
696
858
|
return {
|
|
697
|
-
powerful: "claude-sonnet-4-20250514",
|
|
698
|
-
fast: "claude-haiku-4-5-20251001"
|
|
859
|
+
powerful: "anthropic/claude-sonnet-4-20250514",
|
|
860
|
+
fast: "anthropic/claude-haiku-4-5-20251001"
|
|
699
861
|
};
|
|
700
862
|
}
|
|
701
863
|
getCommand() {
|
|
702
|
-
return "
|
|
864
|
+
return "aider";
|
|
703
865
|
}
|
|
704
866
|
getArgs(config) {
|
|
705
867
|
const args = [];
|
|
706
|
-
const
|
|
707
|
-
if (!
|
|
708
|
-
args.push("--
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
868
|
+
const interactive = this.isInteractive(config);
|
|
869
|
+
if (!interactive) {
|
|
870
|
+
args.push("--auto-commits");
|
|
871
|
+
args.push("--no-pretty");
|
|
872
|
+
args.push("--no-show-diffs");
|
|
712
873
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
args.push("--
|
|
874
|
+
const provider = config.adapterConfig?.provider;
|
|
875
|
+
const credentials = this.getCredentials(config);
|
|
876
|
+
if (config.env?.AIDER_MODEL) {
|
|
877
|
+
args.push("--model", config.env.AIDER_MODEL);
|
|
878
|
+
} else if (interactive && (credentials.googleKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY)) {
|
|
879
|
+
args.push("--model", "gemini");
|
|
880
|
+
} else if (!interactive && provider === "anthropic") {
|
|
881
|
+
args.push("--model", "sonnet");
|
|
882
|
+
} else if (!interactive && provider === "openai") {
|
|
883
|
+
args.push("--model", "4o");
|
|
884
|
+
} else if (!interactive && provider === "google") {
|
|
885
|
+
args.push("--model", "gemini");
|
|
886
|
+
} else if (!interactive && credentials.anthropicKey) {
|
|
887
|
+
args.push("--model", "sonnet");
|
|
888
|
+
} else if (!interactive && credentials.openaiKey) {
|
|
889
|
+
args.push("--model", "4o");
|
|
890
|
+
} else if (!interactive && credentials.googleKey) {
|
|
891
|
+
args.push("--model", "gemini");
|
|
892
|
+
}
|
|
893
|
+
if (!interactive) {
|
|
894
|
+
if (credentials.anthropicKey)
|
|
895
|
+
args.push("--api-key", `anthropic=${credentials.anthropicKey}`);
|
|
896
|
+
if (credentials.openaiKey)
|
|
897
|
+
args.push("--api-key", `openai=${credentials.openaiKey}`);
|
|
898
|
+
if (credentials.googleKey)
|
|
899
|
+
args.push("--api-key", `gemini=${credentials.googleKey}`);
|
|
717
900
|
}
|
|
718
901
|
const approvalConfig = this.getApprovalConfig(config);
|
|
719
902
|
if (approvalConfig) {
|
|
@@ -723,154 +906,56 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
723
906
|
}
|
|
724
907
|
getEnv(config) {
|
|
725
908
|
const env = {};
|
|
726
|
-
const credentials = this.getCredentials(config);
|
|
727
|
-
const adapterConfig = config.adapterConfig;
|
|
728
|
-
if (credentials.anthropicKey) {
|
|
729
|
-
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
730
|
-
}
|
|
731
|
-
if (config.env?.ANTHROPIC_MODEL) {
|
|
732
|
-
env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
|
|
733
|
-
}
|
|
734
909
|
if (!this.isInteractive(config)) {
|
|
735
|
-
env.
|
|
910
|
+
env.NO_COLOR = "1";
|
|
736
911
|
}
|
|
737
|
-
if (
|
|
738
|
-
env.
|
|
739
|
-
env.PARALLAX_CLAUDE_HOOK_MARKER_PREFIX = adapterConfig.claudeHookMarkerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
|
|
912
|
+
if (config.env?.AIDER_NO_GIT === "true") {
|
|
913
|
+
env.AIDER_NO_GIT = "true";
|
|
740
914
|
}
|
|
741
915
|
return env;
|
|
742
916
|
}
|
|
743
|
-
getHookTelemetryProtocol(options) {
|
|
744
|
-
if (options?.httpUrl) {
|
|
745
|
-
const httpHookBase = {
|
|
746
|
-
type: "http",
|
|
747
|
-
url: options.httpUrl,
|
|
748
|
-
timeout: 5
|
|
749
|
-
};
|
|
750
|
-
if (options.sessionId) {
|
|
751
|
-
httpHookBase.headers = { "X-Parallax-Session-Id": options.sessionId };
|
|
752
|
-
}
|
|
753
|
-
const hookEntry2 = [{ matcher: "", hooks: [{ ...httpHookBase }] }];
|
|
754
|
-
const hookEntryNoMatcher = [{ hooks: [{ ...httpHookBase }] }];
|
|
755
|
-
const settingsHooks2 = {
|
|
756
|
-
PermissionRequest: hookEntryNoMatcher,
|
|
757
|
-
PreToolUse: hookEntry2,
|
|
758
|
-
Stop: hookEntryNoMatcher,
|
|
759
|
-
Notification: hookEntry2,
|
|
760
|
-
TaskCompleted: hookEntryNoMatcher
|
|
761
|
-
};
|
|
762
|
-
return {
|
|
763
|
-
markerPrefix: "",
|
|
764
|
-
scriptPath: "",
|
|
765
|
-
scriptContent: "",
|
|
766
|
-
settingsHooks: settingsHooks2
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
const markerPrefix = options?.markerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
|
|
770
|
-
const scriptPath = options?.scriptPath || ".claude/hooks/parallax-hook-telemetry.sh";
|
|
771
|
-
const scriptCommand = `"${"$"}CLAUDE_PROJECT_DIR"/${scriptPath}`;
|
|
772
|
-
const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
|
|
773
|
-
const settingsHooks = {
|
|
774
|
-
Notification: hookEntry,
|
|
775
|
-
PreToolUse: hookEntry,
|
|
776
|
-
TaskCompleted: hookEntry,
|
|
777
|
-
SessionEnd: hookEntry
|
|
778
|
-
};
|
|
779
|
-
const scriptContent = `#!/usr/bin/env bash
|
|
780
|
-
set -euo pipefail
|
|
781
|
-
|
|
782
|
-
INPUT="$(cat)"
|
|
783
|
-
[ -z "${"$"}INPUT" ] && exit 0
|
|
784
|
-
|
|
785
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
786
|
-
exit 0
|
|
787
|
-
fi
|
|
788
|
-
|
|
789
|
-
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
|
|
790
|
-
[ -z "${"$"}EVENT" ] && exit 0
|
|
791
|
-
|
|
792
|
-
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notification_type // empty')"
|
|
793
|
-
TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.tool_name // empty')"
|
|
794
|
-
MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
|
|
795
|
-
|
|
796
|
-
printf '%s ' '${markerPrefix}'
|
|
797
|
-
jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION_TYPE" --arg tool_name "${"$"}TOOL_NAME" --arg message "${"$"}MESSAGE" '({event: $event}
|
|
798
|
-
+ (if $notification_type != "" then {notification_type: $notification_type} else {} end)
|
|
799
|
-
+ (if $tool_name != "" then {tool_name: $tool_name} else {} end)
|
|
800
|
-
+ (if $message != "" then {message: $message} else {} end))'
|
|
801
|
-
`;
|
|
802
|
-
return {
|
|
803
|
-
markerPrefix,
|
|
804
|
-
scriptPath,
|
|
805
|
-
scriptContent,
|
|
806
|
-
settingsHooks
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
getHookMarkers(output) {
|
|
810
|
-
const markers = [];
|
|
811
|
-
const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
|
|
812
|
-
let match;
|
|
813
|
-
while ((match = markerRegex.exec(output)) !== null) {
|
|
814
|
-
const markerToken = match[1];
|
|
815
|
-
if (!markerToken.includes("CLAUDE_HOOK")) {
|
|
816
|
-
continue;
|
|
817
|
-
}
|
|
818
|
-
const payload = match[2];
|
|
819
|
-
try {
|
|
820
|
-
const parsed = JSON.parse(payload);
|
|
821
|
-
const event = typeof parsed.event === "string" ? parsed.event : void 0;
|
|
822
|
-
if (!event) continue;
|
|
823
|
-
markers.push({
|
|
824
|
-
event,
|
|
825
|
-
notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
|
|
826
|
-
tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
|
|
827
|
-
message: typeof parsed.message === "string" ? parsed.message : void 0
|
|
828
|
-
});
|
|
829
|
-
} catch {
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
return markers;
|
|
833
|
-
}
|
|
834
|
-
getLatestHookMarker(output) {
|
|
835
|
-
const markers = this.getHookMarkers(output);
|
|
836
|
-
return markers.length > 0 ? markers[markers.length - 1] : null;
|
|
837
|
-
}
|
|
838
|
-
stripHookMarkers(output) {
|
|
839
|
-
return output.replace(/(?:^|\n)\s*[A-Z0-9_]*CLAUDE_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
|
|
840
|
-
}
|
|
841
917
|
detectLogin(output) {
|
|
842
918
|
const stripped = this.stripAnsi(output);
|
|
843
|
-
if (stripped.includes("
|
|
919
|
+
if (stripped.includes("No API key") || stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("OPENAI_API_KEY") || stripped.includes("Missing API key")) {
|
|
844
920
|
return {
|
|
845
921
|
required: true,
|
|
846
|
-
type: "
|
|
847
|
-
instructions:
|
|
922
|
+
type: "api_key",
|
|
923
|
+
instructions: "Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"
|
|
848
924
|
};
|
|
849
925
|
}
|
|
850
|
-
if (stripped.includes("API key
|
|
926
|
+
if (stripped.includes("Invalid API key") || stripped.includes("Authentication failed") || stripped.includes("Unauthorized")) {
|
|
851
927
|
return {
|
|
852
928
|
required: true,
|
|
853
929
|
type: "api_key",
|
|
854
|
-
instructions: "
|
|
930
|
+
instructions: "API key is invalid - please check your credentials"
|
|
855
931
|
};
|
|
856
932
|
}
|
|
857
|
-
if (
|
|
933
|
+
if (/login to openrouter or create a free account/i.test(stripped)) {
|
|
934
|
+
return {
|
|
935
|
+
required: true,
|
|
936
|
+
type: "oauth",
|
|
937
|
+
instructions: "Aider offering OpenRouter OAuth login \u2014 provide API keys to skip"
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
if (/please open this url in your browser to connect aider with openrouter/i.test(
|
|
941
|
+
stripped
|
|
942
|
+
) || /waiting up to 5 minutes for you to finish in the browser/i.test(stripped)) {
|
|
858
943
|
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
859
944
|
return {
|
|
860
945
|
required: true,
|
|
861
946
|
type: "browser",
|
|
862
947
|
url: urlMatch ? urlMatch[0] : void 0,
|
|
863
|
-
instructions: "
|
|
948
|
+
instructions: "Complete OpenRouter authentication in browser"
|
|
864
949
|
};
|
|
865
950
|
}
|
|
866
951
|
return { required: false };
|
|
867
952
|
}
|
|
868
953
|
/**
|
|
869
|
-
* Detect blocking prompts specific to
|
|
954
|
+
* Detect blocking prompts specific to Aider CLI.
|
|
955
|
+
* Source: io.py, onboarding.py, base_coder.py, report.py
|
|
870
956
|
*/
|
|
871
957
|
detectBlockingPrompt(output) {
|
|
872
958
|
const stripped = this.stripAnsi(output);
|
|
873
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
874
959
|
const loginDetection = this.detectLogin(output);
|
|
875
960
|
if (loginDetection.required) {
|
|
876
961
|
return {
|
|
@@ -882,242 +967,116 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
|
|
|
882
967
|
instructions: loginDetection.instructions
|
|
883
968
|
};
|
|
884
969
|
}
|
|
885
|
-
if (
|
|
886
|
-
if (marker.notification_type === "permission_prompt") {
|
|
887
|
-
return {
|
|
888
|
-
detected: true,
|
|
889
|
-
type: "permission",
|
|
890
|
-
prompt: marker.message || "Claude permission prompt",
|
|
891
|
-
suggestedResponse: "keys:enter",
|
|
892
|
-
canAutoRespond: true,
|
|
893
|
-
instructions: "Claude is waiting for permission approval"
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
if (marker.notification_type === "elicitation_dialog") {
|
|
897
|
-
return {
|
|
898
|
-
detected: true,
|
|
899
|
-
type: "tool_wait",
|
|
900
|
-
prompt: marker.message || "Claude elicitation dialog",
|
|
901
|
-
canAutoRespond: false,
|
|
902
|
-
instructions: "Claude is waiting for required user input"
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
if (/how is claude doing this session\?\s*\(optional\)|1:\s*bad\s+2:\s*fine\s+3:\s*good\s+0:\s*dismiss/i.test(stripped)) {
|
|
907
|
-
return {
|
|
908
|
-
detected: true,
|
|
909
|
-
type: "config",
|
|
910
|
-
prompt: "Claude session survey",
|
|
911
|
-
options: ["1", "2", "3", "0"],
|
|
912
|
-
suggestedResponse: "0",
|
|
913
|
-
canAutoRespond: true,
|
|
914
|
-
instructions: "Optional survey prompt; reply 0 to dismiss"
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
if (/enter\/tab\/space to toggle.*esc to cancel|enter to confirm.*esc to cancel|esc to close/i.test(stripped)) {
|
|
970
|
+
if (/select.*model|choose.*model|which model/i.test(stripped)) {
|
|
918
971
|
return {
|
|
919
972
|
detected: true,
|
|
920
|
-
type: "
|
|
921
|
-
prompt: "
|
|
922
|
-
options: ["keys:enter", "keys:esc", "keys:down,enter"],
|
|
923
|
-
suggestedResponse: "keys:esc",
|
|
973
|
+
type: "model_select",
|
|
974
|
+
prompt: "Model selection required",
|
|
924
975
|
canAutoRespond: false,
|
|
925
|
-
instructions: "
|
|
976
|
+
instructions: "Please select a model or set AIDER_MODEL env var"
|
|
926
977
|
};
|
|
927
978
|
}
|
|
928
|
-
if (/
|
|
979
|
+
if (/please answer with one of:/i.test(stripped)) {
|
|
929
980
|
return {
|
|
930
981
|
detected: true,
|
|
931
|
-
type: "
|
|
932
|
-
prompt: "
|
|
933
|
-
options: ["keys:esc", "keys:enter", "keys:down,enter"],
|
|
934
|
-
suggestedResponse: "keys:esc",
|
|
982
|
+
type: "unknown",
|
|
983
|
+
prompt: "Invalid confirmation input",
|
|
935
984
|
canAutoRespond: false,
|
|
936
|
-
instructions: "
|
|
985
|
+
instructions: "Aider received an invalid response to a confirmation prompt"
|
|
937
986
|
};
|
|
938
987
|
}
|
|
939
|
-
if (/
|
|
988
|
+
if (/delete|remove|overwrite/i.test(stripped) && (/\[y\/n\]/i.test(stripped) || /\(Y\)es\/\(N\)o/i.test(stripped))) {
|
|
940
989
|
return {
|
|
941
990
|
detected: true,
|
|
942
991
|
type: "permission",
|
|
943
|
-
prompt: "
|
|
944
|
-
|
|
945
|
-
canAutoRespond: true,
|
|
946
|
-
instructions: "Claude is asking permission to use a tool"
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
if (/choose.*model|select.*model|available models/i.test(stripped) && /\d+\)|claude-/i.test(stripped)) {
|
|
950
|
-
return {
|
|
951
|
-
detected: true,
|
|
952
|
-
type: "model_select",
|
|
953
|
-
prompt: "Claude model selection",
|
|
992
|
+
prompt: "Destructive operation confirmation",
|
|
993
|
+
options: ["y", "n"],
|
|
954
994
|
canAutoRespond: false,
|
|
955
|
-
instructions: "
|
|
995
|
+
instructions: "Aider is asking to perform a potentially destructive operation"
|
|
956
996
|
};
|
|
957
997
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
998
|
+
return super.detectBlockingPrompt(output);
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Detect if Aider is actively loading/processing.
|
|
1002
|
+
*
|
|
1003
|
+
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
1004
|
+
* - aider_active_waiting_model: "Waiting for <model>"
|
|
1005
|
+
* - aider_active_waiting_llm_default: "Waiting for LLM"
|
|
1006
|
+
* - aider_active_generating_commit_message: "Generating commit message with ..."
|
|
1007
|
+
*/
|
|
1008
|
+
detectLoading(output) {
|
|
1009
|
+
const stripped = this.stripAnsi(output);
|
|
1010
|
+
const tail = stripped.slice(-500);
|
|
1011
|
+
if (/Waiting\s+for\s+(?:LLM|[A-Za-z0-9_./:@-]+)/i.test(tail)) {
|
|
1012
|
+
return true;
|
|
966
1013
|
}
|
|
967
|
-
if (/
|
|
968
|
-
return {
|
|
969
|
-
detected: true,
|
|
970
|
-
type: "config",
|
|
971
|
-
prompt: "First-time setup",
|
|
972
|
-
canAutoRespond: false,
|
|
973
|
-
instructions: "Claude Code requires initial configuration"
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
if (/allow.*access|grant.*permission|access to .* files/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
|
|
977
|
-
return {
|
|
978
|
-
detected: true,
|
|
979
|
-
type: "permission",
|
|
980
|
-
prompt: "File/directory access permission",
|
|
981
|
-
options: ["y", "n"],
|
|
982
|
-
suggestedResponse: "y",
|
|
983
|
-
canAutoRespond: true,
|
|
984
|
-
instructions: "Claude Code requesting file access permission"
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
if (this.detectReady(output) || this.detectTaskComplete(output)) {
|
|
988
|
-
return { detected: false };
|
|
989
|
-
}
|
|
990
|
-
if (/❯/.test(stripped.slice(-300))) {
|
|
991
|
-
return { detected: false };
|
|
992
|
-
}
|
|
993
|
-
return super.detectBlockingPrompt(output);
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Detect if Claude Code is actively loading/processing.
|
|
997
|
-
*
|
|
998
|
-
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
999
|
-
* - claude_active_reading_files: "Reading N files…"
|
|
1000
|
-
* - General: "esc to interrupt" spinner status line
|
|
1001
|
-
*/
|
|
1002
|
-
detectLoading(output) {
|
|
1003
|
-
const stripped = this.stripAnsi(output);
|
|
1004
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
1005
|
-
const tail = stripped.slice(-500);
|
|
1006
|
-
if (marker?.event === "PreToolUse") {
|
|
1007
|
-
return true;
|
|
1008
|
-
}
|
|
1009
|
-
if (/esc\s+to\s+interrupt/i.test(tail)) {
|
|
1010
|
-
return true;
|
|
1011
|
-
}
|
|
1012
|
-
if (/Reading\s+\d+\s+files/i.test(tail)) {
|
|
1014
|
+
if (/Generating\s+commit\s+message\s+with\s+/i.test(tail)) {
|
|
1013
1015
|
return true;
|
|
1014
1016
|
}
|
|
1015
1017
|
return false;
|
|
1016
1018
|
}
|
|
1017
1019
|
/**
|
|
1018
|
-
* Detect
|
|
1019
|
-
*
|
|
1020
|
-
* Claude Code can launch external tools (browser, bash, Node, Python, etc.)
|
|
1021
|
-
* that show status lines like "Claude in Chrome[javascript_tool]" or
|
|
1022
|
-
* "[bash_tool]", "[python_tool]", etc.
|
|
1023
|
-
*
|
|
1024
|
-
* When detected, stall detection is suppressed and the UI can display
|
|
1025
|
-
* which tool is active.
|
|
1026
|
-
*/
|
|
1027
|
-
detectToolRunning(output) {
|
|
1028
|
-
const stripped = this.stripAnsi(output);
|
|
1029
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
1030
|
-
const tail = stripped.slice(-500);
|
|
1031
|
-
if (marker?.event === "PreToolUse" && marker.tool_name) {
|
|
1032
|
-
return {
|
|
1033
|
-
toolName: marker.tool_name.toLowerCase(),
|
|
1034
|
-
description: `${marker.tool_name} (hook)`
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
const contextualMatch = tail.match(/Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i);
|
|
1038
|
-
if (contextualMatch) {
|
|
1039
|
-
const appName = contextualMatch[1];
|
|
1040
|
-
const toolType = contextualMatch[2].toLowerCase();
|
|
1041
|
-
const friendlyName = toolType.replace(/_tool$/i, "");
|
|
1042
|
-
return { toolName: friendlyName, description: `${appName} (${toolType})` };
|
|
1043
|
-
}
|
|
1044
|
-
const toolMatch = tail.match(/\[(\w+_tool)\]/i);
|
|
1045
|
-
if (toolMatch) {
|
|
1046
|
-
const toolType = toolMatch[1].toLowerCase();
|
|
1047
|
-
const friendlyName = toolType.replace(/_tool$/i, "");
|
|
1048
|
-
return { toolName: friendlyName, description: toolType };
|
|
1049
|
-
}
|
|
1050
|
-
return null;
|
|
1051
|
-
}
|
|
1052
|
-
/**
|
|
1053
|
-
* Detect task completion for Claude Code.
|
|
1020
|
+
* Detect task completion for Aider.
|
|
1054
1021
|
*
|
|
1055
|
-
* High-confidence
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1022
|
+
* High-confidence patterns:
|
|
1023
|
+
* - "Aider is waiting for your input" notification (bell message)
|
|
1024
|
+
* - Edit-format mode prompts (ask>, code>, architect>) after output
|
|
1058
1025
|
*
|
|
1059
1026
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
1060
|
-
* -
|
|
1061
|
-
* - claude_completed_turn_duration_custom_verb
|
|
1027
|
+
* - aider_completed_llm_response_ready
|
|
1062
1028
|
*/
|
|
1063
1029
|
detectTaskComplete(output) {
|
|
1064
1030
|
const stripped = this.stripAnsi(output);
|
|
1065
|
-
|
|
1066
|
-
if (!stripped.trim()) return false;
|
|
1067
|
-
if (marker?.event === "TaskCompleted") {
|
|
1031
|
+
if (/Aider\s+is\s+waiting\s+for\s+your\s+input/.test(stripped)) {
|
|
1068
1032
|
return true;
|
|
1069
1033
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1034
|
+
const hasPrompt = /(?:(?:ask|code|architect)(?:\s+multi)?)?>\s*$/m.test(
|
|
1035
|
+
stripped
|
|
1036
|
+
);
|
|
1037
|
+
if (hasPrompt) {
|
|
1038
|
+
const hasEditMarkers = /Applied edit to|Commit [a-f0-9]+|wrote to|Updated/i.test(stripped);
|
|
1039
|
+
const hasTokenUsage = /Tokens:|Cost:/i.test(stripped);
|
|
1040
|
+
if (hasEditMarkers || hasTokenUsage) {
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1072
1043
|
}
|
|
1073
|
-
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
detectReady(output) {
|
|
1047
|
+
const stripped = this.stripAnsi(output);
|
|
1048
|
+
if (/login to openrouter/i.test(stripped) || /open this url in your browser/i.test(stripped) || /waiting up to 5 minutes/i.test(stripped)) {
|
|
1074
1049
|
return false;
|
|
1075
1050
|
}
|
|
1076
|
-
|
|
1077
|
-
const tail = stripped.slice(-300);
|
|
1078
|
-
const hasIdlePrompt = /❯/.test(tail);
|
|
1079
|
-
if (hasDuration && hasIdlePrompt) {
|
|
1051
|
+
if (/(?:ask|code|architect|help)(?:\s+multi)?>\s*$/m.test(stripped) || /^multi>\s*$/m.test(stripped)) {
|
|
1080
1052
|
return true;
|
|
1081
1053
|
}
|
|
1082
|
-
if (
|
|
1054
|
+
if (/^Aider v\d+/m.test(stripped)) {
|
|
1083
1055
|
return true;
|
|
1084
1056
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
detectReady(output) {
|
|
1088
|
-
const stripped = this.stripAnsi(output);
|
|
1089
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
1090
|
-
if (!stripped.trim()) return false;
|
|
1091
|
-
if (marker?.event === "Notification") {
|
|
1092
|
-
if (marker.notification_type === "permission_prompt" || marker.notification_type === "elicitation_dialog") {
|
|
1093
|
-
return false;
|
|
1094
|
-
}
|
|
1095
|
-
if (marker.notification_type === "idle_prompt") {
|
|
1096
|
-
return true;
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
|
|
1100
|
-
return false;
|
|
1057
|
+
if (/^(?:Readonly|Editable):/m.test(stripped)) {
|
|
1058
|
+
return true;
|
|
1101
1059
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const hasInteractivePromptBar = /❯\s+\S/.test(tail) && (/\/effort/i.test(stripped) || /Welcome back/i.test(stripped) || /Recent activity/i.test(stripped) || /What's new/i.test(stripped));
|
|
1107
|
-
return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint || hasInteractivePromptBar;
|
|
1060
|
+
return (
|
|
1061
|
+
// Legacy prompt patterns
|
|
1062
|
+
stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
|
|
1063
|
+
);
|
|
1108
1064
|
}
|
|
1109
1065
|
parseOutput(output) {
|
|
1110
|
-
const
|
|
1111
|
-
const stripped = this.stripAnsi(withoutHookMarkers);
|
|
1066
|
+
const stripped = this.stripAnsi(output);
|
|
1112
1067
|
const isComplete = this.isResponseComplete(stripped);
|
|
1113
1068
|
if (!isComplete) {
|
|
1114
1069
|
return null;
|
|
1115
1070
|
}
|
|
1116
1071
|
const isQuestion = this.containsQuestion(stripped);
|
|
1117
|
-
|
|
1072
|
+
let content = this.extractContent(stripped, /^.*aider>\s*/gim);
|
|
1073
|
+
content = content.replace(
|
|
1074
|
+
/^(Added|Removed|Created|Updated) .+ (to|from) the chat\.?$/gm,
|
|
1075
|
+
""
|
|
1076
|
+
);
|
|
1118
1077
|
return {
|
|
1119
1078
|
type: isQuestion ? "question" : "response",
|
|
1120
|
-
content,
|
|
1079
|
+
content: content.trim(),
|
|
1121
1080
|
isComplete: true,
|
|
1122
1081
|
isQuestion,
|
|
1123
1082
|
metadata: {
|
|
@@ -1125,114 +1084,183 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
|
|
|
1125
1084
|
}
|
|
1126
1085
|
};
|
|
1127
1086
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
return "claude --version";
|
|
1133
|
-
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Detect exit conditions specific to Aider.
|
|
1089
|
+
* Source: base_coder.py:994, base_coder.py:998, report.py:77, versioncheck.py:58
|
|
1090
|
+
*/
|
|
1134
1091
|
detectExit(output) {
|
|
1135
1092
|
const stripped = this.stripAnsi(output);
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1093
|
+
if (/\^C again to exit/i.test(stripped) || /\^C KeyboardInterrupt/i.test(stripped)) {
|
|
1094
|
+
return { exited: true, code: 130 };
|
|
1095
|
+
}
|
|
1096
|
+
if (/re-run aider to use new version/i.test(stripped)) {
|
|
1097
|
+
return {
|
|
1098
|
+
exited: true,
|
|
1099
|
+
code: 0,
|
|
1100
|
+
error: "Aider updated \u2014 restart required"
|
|
1101
|
+
};
|
|
1139
1102
|
}
|
|
1140
1103
|
return super.detectExit(output);
|
|
1141
1104
|
}
|
|
1105
|
+
getPromptPattern() {
|
|
1106
|
+
return /(?:ask|code|architect|help|aider|multi)(?:\s+multi)?>\s*$/i;
|
|
1107
|
+
}
|
|
1108
|
+
getHealthCheckCommand() {
|
|
1109
|
+
return "aider --version";
|
|
1110
|
+
}
|
|
1142
1111
|
};
|
|
1143
1112
|
|
|
1144
|
-
// src/
|
|
1145
|
-
var
|
|
1146
|
-
var
|
|
1147
|
-
adapterType = "
|
|
1148
|
-
displayName = "
|
|
1113
|
+
// src/claude-adapter.ts
|
|
1114
|
+
var CLAUDE_HOOK_MARKER_PREFIX = "PARALLAX_CLAUDE_HOOK";
|
|
1115
|
+
var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
1116
|
+
adapterType = "claude";
|
|
1117
|
+
displayName = "Claude Code";
|
|
1118
|
+
/** Heaviest TUI — status bar, shortcuts, update notices, /ide suggestions.
|
|
1119
|
+
* 3000ms needed because detectReady fires early during boot rendering. */
|
|
1120
|
+
readySettleMs = 3e3;
|
|
1149
1121
|
installation = {
|
|
1150
|
-
command: "npm install -g @
|
|
1122
|
+
command: "npm install -g @anthropic-ai/claude-code",
|
|
1151
1123
|
alternatives: [
|
|
1152
|
-
"
|
|
1124
|
+
"npx @anthropic-ai/claude-code (run without installing)",
|
|
1125
|
+
"brew install claude-code (macOS with Homebrew)"
|
|
1153
1126
|
],
|
|
1154
|
-
docsUrl: "https://
|
|
1127
|
+
docsUrl: "https://docs.anthropic.com/en/docs/claude-code",
|
|
1128
|
+
minVersion: "1.0.0"
|
|
1155
1129
|
};
|
|
1156
1130
|
/**
|
|
1157
|
-
* Auto-response rules for
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1131
|
+
* Auto-response rules for Claude Code CLI.
|
|
1132
|
+
* These handle common text-based [y/n] prompts that can be safely auto-responded.
|
|
1133
|
+
* Explicit responseType: 'text' prevents the usesTuiMenus default from kicking in.
|
|
1160
1134
|
*/
|
|
1161
1135
|
autoResponseRules = [
|
|
1162
1136
|
{
|
|
1163
|
-
pattern: /
|
|
1164
|
-
type: "
|
|
1137
|
+
pattern: /choose\s+the\s+text\s+style\s+that\s+looks\s+best\s+with\s+your\s+terminal|syntax\s+theme:/i,
|
|
1138
|
+
type: "config",
|
|
1165
1139
|
response: "",
|
|
1166
1140
|
responseType: "keys",
|
|
1167
1141
|
keys: ["enter"],
|
|
1168
|
-
description: "
|
|
1142
|
+
description: "Accept Claude first-run theme/style prompt",
|
|
1169
1143
|
safe: true,
|
|
1170
1144
|
once: true
|
|
1171
1145
|
},
|
|
1172
1146
|
{
|
|
1173
|
-
pattern: /trust.?
|
|
1147
|
+
pattern: /trust.*(?:folder|directory)|safety.?check|project.you.created|(?:Yes|Allow).*(?:No|Deny).*(?:Enter|Return)/i,
|
|
1174
1148
|
type: "permission",
|
|
1175
1149
|
response: "",
|
|
1176
1150
|
responseType: "keys",
|
|
1177
1151
|
keys: ["enter"],
|
|
1178
|
-
description: "
|
|
1152
|
+
description: "Accept trust prompt for working directory",
|
|
1179
1153
|
safe: true,
|
|
1180
1154
|
once: true
|
|
1181
1155
|
},
|
|
1182
1156
|
{
|
|
1183
|
-
pattern: /allow
|
|
1184
|
-
type: "
|
|
1157
|
+
pattern: /wants? (?:your )?permission|needs your permission|(?:Allow|Approve)\s[\s\S]{0,50}(?:Deny|Don't allow)/i,
|
|
1158
|
+
type: "permission",
|
|
1185
1159
|
response: "",
|
|
1186
1160
|
responseType: "keys",
|
|
1187
|
-
keys: ["
|
|
1188
|
-
description:
|
|
1161
|
+
keys: ["enter"],
|
|
1162
|
+
description: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
|
|
1189
1163
|
safe: true,
|
|
1190
1164
|
once: true
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
pattern: /update available.*\[y\/n\]/i,
|
|
1168
|
+
type: "update",
|
|
1169
|
+
response: "n",
|
|
1170
|
+
responseType: "text",
|
|
1171
|
+
description: "Decline Claude Code update to continue execution",
|
|
1172
|
+
safe: true
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
pattern: /new version.*available.*\[y\/n\]/i,
|
|
1176
|
+
type: "update",
|
|
1177
|
+
response: "n",
|
|
1178
|
+
responseType: "text",
|
|
1179
|
+
description: "Decline version upgrade prompt",
|
|
1180
|
+
safe: true
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
pattern: /would you like to enable.*telemetry.*\[y\/n\]/i,
|
|
1184
|
+
type: "config",
|
|
1185
|
+
response: "n",
|
|
1186
|
+
responseType: "text",
|
|
1187
|
+
description: "Decline telemetry prompt",
|
|
1188
|
+
safe: true
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
pattern: /send anonymous usage data.*\[y\/n\]/i,
|
|
1192
|
+
type: "config",
|
|
1193
|
+
response: "n",
|
|
1194
|
+
responseType: "text",
|
|
1195
|
+
description: "Decline anonymous usage data",
|
|
1196
|
+
safe: true
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
pattern: /how is claude doing this session\?\s*\(optional\)|1:\s*bad\s+2:\s*fine\s+3:\s*good\s+0:\s*dismiss/i,
|
|
1200
|
+
type: "config",
|
|
1201
|
+
response: "0",
|
|
1202
|
+
responseType: "text",
|
|
1203
|
+
description: "Dismiss optional Claude session survey",
|
|
1204
|
+
safe: true,
|
|
1205
|
+
once: true
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
pattern: /continue without.*\[y\/n\]/i,
|
|
1209
|
+
type: "config",
|
|
1210
|
+
response: "y",
|
|
1211
|
+
responseType: "text",
|
|
1212
|
+
description: "Continue without optional feature",
|
|
1213
|
+
safe: true
|
|
1214
|
+
}
|
|
1215
|
+
];
|
|
1216
|
+
getWorkspaceFiles() {
|
|
1217
|
+
return [
|
|
1218
|
+
{
|
|
1219
|
+
relativePath: "CLAUDE.md",
|
|
1220
|
+
description: "Project-level instructions read automatically on startup",
|
|
1221
|
+
autoLoaded: true,
|
|
1222
|
+
type: "memory",
|
|
1223
|
+
format: "markdown"
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
relativePath: ".claude/settings.json",
|
|
1227
|
+
description: "Project-scoped settings (allowed tools, permissions)",
|
|
1228
|
+
autoLoaded: true,
|
|
1229
|
+
type: "config",
|
|
1230
|
+
format: "json"
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
relativePath: ".claude/commands",
|
|
1234
|
+
description: "Custom slash commands directory",
|
|
1235
|
+
autoLoaded: false,
|
|
1236
|
+
type: "config",
|
|
1237
|
+
format: "markdown"
|
|
1238
|
+
}
|
|
1239
|
+
];
|
|
1240
|
+
}
|
|
1241
|
+
getRecommendedModels(_credentials) {
|
|
1242
|
+
return {
|
|
1243
|
+
powerful: "claude-sonnet-4-20250514",
|
|
1244
|
+
fast: "claude-haiku-4-5-20251001"
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
getCommand() {
|
|
1248
|
+
return "claude";
|
|
1249
|
+
}
|
|
1227
1250
|
getArgs(config) {
|
|
1228
1251
|
const args = [];
|
|
1252
|
+
const adapterConfig = config.adapterConfig;
|
|
1229
1253
|
if (!this.isInteractive(config)) {
|
|
1230
|
-
args.push("--
|
|
1231
|
-
args.push("--output-format", "text");
|
|
1254
|
+
args.push("--print");
|
|
1232
1255
|
if (config.workdir) {
|
|
1233
1256
|
args.push("--cwd", config.workdir);
|
|
1234
1257
|
}
|
|
1235
1258
|
}
|
|
1259
|
+
if (adapterConfig?.resume) {
|
|
1260
|
+
args.push("--resume", adapterConfig.resume);
|
|
1261
|
+
} else if (adapterConfig?.continue) {
|
|
1262
|
+
args.push("--continue");
|
|
1263
|
+
}
|
|
1236
1264
|
const approvalConfig = this.getApprovalConfig(config);
|
|
1237
1265
|
if (approvalConfig) {
|
|
1238
1266
|
args.push(...approvalConfig.cliFlags);
|
|
@@ -1243,34 +1271,39 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1243
1271
|
const env = {};
|
|
1244
1272
|
const credentials = this.getCredentials(config);
|
|
1245
1273
|
const adapterConfig = config.adapterConfig;
|
|
1246
|
-
if (credentials.
|
|
1247
|
-
env.
|
|
1248
|
-
env.GEMINI_API_KEY = credentials.googleKey;
|
|
1274
|
+
if (credentials.anthropicKey) {
|
|
1275
|
+
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
1249
1276
|
}
|
|
1250
|
-
if (config.env?.
|
|
1251
|
-
env.
|
|
1277
|
+
if (config.env?.ANTHROPIC_MODEL) {
|
|
1278
|
+
env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
|
|
1252
1279
|
}
|
|
1253
1280
|
if (!this.isInteractive(config)) {
|
|
1254
|
-
env.
|
|
1281
|
+
env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
|
|
1255
1282
|
}
|
|
1256
|
-
if (adapterConfig?.
|
|
1257
|
-
env.
|
|
1258
|
-
env.
|
|
1283
|
+
if (adapterConfig?.claudeHookTelemetry) {
|
|
1284
|
+
env.PARALLAX_CLAUDE_HOOK_TELEMETRY = "1";
|
|
1285
|
+
env.PARALLAX_CLAUDE_HOOK_MARKER_PREFIX = adapterConfig.claudeHookMarkerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
|
|
1259
1286
|
}
|
|
1260
1287
|
return env;
|
|
1261
1288
|
}
|
|
1262
1289
|
getHookTelemetryProtocol(options) {
|
|
1263
1290
|
if (options?.httpUrl) {
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1291
|
+
const httpHookBase = {
|
|
1292
|
+
type: "http",
|
|
1293
|
+
url: options.httpUrl,
|
|
1294
|
+
timeout: 5
|
|
1295
|
+
};
|
|
1296
|
+
if (options.sessionId) {
|
|
1297
|
+
httpHookBase.headers = { "X-Parallax-Session-Id": options.sessionId };
|
|
1298
|
+
}
|
|
1299
|
+
const hookEntry2 = [{ matcher: "", hooks: [{ ...httpHookBase }] }];
|
|
1300
|
+
const hookEntryNoMatcher = [{ hooks: [{ ...httpHookBase }] }];
|
|
1268
1301
|
const settingsHooks2 = {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1302
|
+
PermissionRequest: hookEntryNoMatcher,
|
|
1303
|
+
PreToolUse: hookEntry2,
|
|
1304
|
+
Stop: hookEntryNoMatcher,
|
|
1305
|
+
Notification: hookEntry2,
|
|
1306
|
+
TaskCompleted: hookEntryNoMatcher
|
|
1274
1307
|
};
|
|
1275
1308
|
return {
|
|
1276
1309
|
markerPrefix: "",
|
|
@@ -1279,14 +1312,16 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1279
1312
|
settingsHooks: settingsHooks2
|
|
1280
1313
|
};
|
|
1281
1314
|
}
|
|
1282
|
-
const markerPrefix = options?.markerPrefix ||
|
|
1283
|
-
const scriptPath = options?.scriptPath || ".
|
|
1284
|
-
const scriptCommand = `"${"$"}
|
|
1285
|
-
const hookEntry = [
|
|
1315
|
+
const markerPrefix = options?.markerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
|
|
1316
|
+
const scriptPath = options?.scriptPath || ".claude/hooks/parallax-hook-telemetry.sh";
|
|
1317
|
+
const scriptCommand = `"${"$"}CLAUDE_PROJECT_DIR"/${scriptPath}`;
|
|
1318
|
+
const hookEntry = [
|
|
1319
|
+
{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }
|
|
1320
|
+
];
|
|
1286
1321
|
const settingsHooks = {
|
|
1287
1322
|
Notification: hookEntry,
|
|
1288
|
-
|
|
1289
|
-
|
|
1323
|
+
PreToolUse: hookEntry,
|
|
1324
|
+
TaskCompleted: hookEntry,
|
|
1290
1325
|
SessionEnd: hookEntry
|
|
1291
1326
|
};
|
|
1292
1327
|
const scriptContent = `#!/usr/bin/env bash
|
|
@@ -1296,32 +1331,21 @@ INPUT="$(cat)"
|
|
|
1296
1331
|
[ -z "${"$"}INPUT" ] && exit 0
|
|
1297
1332
|
|
|
1298
1333
|
if ! command -v jq >/dev/null 2>&1; then
|
|
1299
|
-
# Valid no-op response
|
|
1300
|
-
printf '%s
|
|
1301
|
-
' '{"continue":true}'
|
|
1302
1334
|
exit 0
|
|
1303
1335
|
fi
|
|
1304
1336
|
|
|
1305
|
-
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.
|
|
1306
|
-
[ -z "${"$"}EVENT" ] &&
|
|
1307
|
-
' '{"continue":true}'; exit 0; }
|
|
1337
|
+
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
|
|
1338
|
+
[ -z "${"$"}EVENT" ] && exit 0
|
|
1308
1339
|
|
|
1309
|
-
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.
|
|
1310
|
-
TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.
|
|
1340
|
+
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notification_type // empty')"
|
|
1341
|
+
TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.tool_name // empty')"
|
|
1311
1342
|
MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
|
|
1312
1343
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
--arg notification_type "${"$"}NOTIFICATION_TYPE" \\
|
|
1316
|
-
--arg tool_name "${"$"}TOOL_NAME" \\
|
|
1317
|
-
--arg message "${"$"}MESSAGE" \\
|
|
1318
|
-
'({event: $event}
|
|
1344
|
+
printf '%s ' '${markerPrefix}'
|
|
1345
|
+
jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION_TYPE" --arg tool_name "${"$"}TOOL_NAME" --arg message "${"$"}MESSAGE" '({event: $event}
|
|
1319
1346
|
+ (if $notification_type != "" then {notification_type: $notification_type} else {} end)
|
|
1320
1347
|
+ (if $tool_name != "" then {tool_name: $tool_name} else {} end)
|
|
1321
|
-
+ (if $message != "" then {message: $message} else {} end))'
|
|
1322
|
-
|
|
1323
|
-
MARKER="${markerPrefix} ${"$"}PAYLOAD"
|
|
1324
|
-
jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
|
|
1348
|
+
+ (if $message != "" then {message: $message} else {} end))'
|
|
1325
1349
|
`;
|
|
1326
1350
|
return {
|
|
1327
1351
|
markerPrefix,
|
|
@@ -1336,7 +1360,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1336
1360
|
let match;
|
|
1337
1361
|
while ((match = markerRegex.exec(output)) !== null) {
|
|
1338
1362
|
const markerToken = match[1];
|
|
1339
|
-
if (!markerToken.includes("
|
|
1363
|
+
if (!markerToken.includes("CLAUDE_HOOK")) {
|
|
1340
1364
|
continue;
|
|
1341
1365
|
}
|
|
1342
1366
|
const payload = match[2];
|
|
@@ -1360,208 +1384,288 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1360
1384
|
return markers.length > 0 ? markers[markers.length - 1] : null;
|
|
1361
1385
|
}
|
|
1362
1386
|
stripHookMarkers(output) {
|
|
1363
|
-
return output.replace(
|
|
1387
|
+
return output.replace(
|
|
1388
|
+
/(?:^|\n)\s*[A-Z0-9_]*CLAUDE_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g,
|
|
1389
|
+
"\n"
|
|
1390
|
+
);
|
|
1364
1391
|
}
|
|
1365
1392
|
detectLogin(output) {
|
|
1366
1393
|
const stripped = this.stripAnsi(output);
|
|
1367
|
-
if (stripped.includes("
|
|
1394
|
+
if (stripped.includes("Not logged in") || stripped.includes("Please run /login") || stripped.includes("please log in") || stripped.includes("run /login")) {
|
|
1368
1395
|
return {
|
|
1369
1396
|
required: true,
|
|
1370
|
-
type: "
|
|
1371
|
-
instructions:
|
|
1397
|
+
type: "cli_auth",
|
|
1398
|
+
instructions: 'Claude Code requires authentication. Run "claude login" in your terminal.'
|
|
1372
1399
|
};
|
|
1373
1400
|
}
|
|
1374
|
-
if (
|
|
1401
|
+
if (stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("authentication required") || stripped.includes("Please sign in") || stripped.includes("Invalid API key")) {
|
|
1375
1402
|
return {
|
|
1376
1403
|
required: true,
|
|
1377
1404
|
type: "api_key",
|
|
1378
|
-
instructions: "
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
if (/how.?would.?you.?like.?to.?authenticate/i.test(stripped) || /get.?started/i.test(stripped) && /login.?with.?google|use.?gemini.?api.?key|vertex/i.test(stripped)) {
|
|
1382
|
-
return {
|
|
1383
|
-
required: true,
|
|
1384
|
-
type: "oauth",
|
|
1385
|
-
instructions: "Gemini CLI authentication required \u2014 select an auth method"
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1388
|
-
if (/waiting.?for.?auth/i.test(stripped)) {
|
|
1389
|
-
return {
|
|
1390
|
-
required: true,
|
|
1391
|
-
type: "oauth",
|
|
1392
|
-
instructions: "Waiting for browser authentication to complete"
|
|
1405
|
+
instructions: "Set ANTHROPIC_API_KEY environment variable or provide credentials in adapterConfig"
|
|
1393
1406
|
};
|
|
1394
1407
|
}
|
|
1395
|
-
if (stripped.includes("
|
|
1408
|
+
if (stripped.includes("Open this URL") || stripped.includes("browser to authenticate")) {
|
|
1396
1409
|
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
1397
|
-
return {
|
|
1398
|
-
required: true,
|
|
1399
|
-
type: "oauth",
|
|
1400
|
-
url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
|
|
1401
|
-
instructions: "Google OAuth authentication required"
|
|
1402
|
-
};
|
|
1403
|
-
}
|
|
1404
|
-
if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
|
|
1405
1410
|
return {
|
|
1406
1411
|
required: true,
|
|
1407
1412
|
type: "browser",
|
|
1408
|
-
|
|
1413
|
+
url: urlMatch ? urlMatch[0] : void 0,
|
|
1414
|
+
instructions: "Browser authentication required"
|
|
1409
1415
|
};
|
|
1410
1416
|
}
|
|
1411
1417
|
return { required: false };
|
|
1412
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Detect blocking prompts specific to Claude Code CLI
|
|
1421
|
+
*/
|
|
1413
1422
|
detectBlockingPrompt(output) {
|
|
1414
1423
|
const stripped = this.stripAnsi(output);
|
|
1415
1424
|
const marker = this.getLatestHookMarker(stripped);
|
|
1416
|
-
if (
|
|
1425
|
+
if (this.detectLoading(output)) {
|
|
1426
|
+
return { detected: false };
|
|
1427
|
+
}
|
|
1428
|
+
const trimmedTail = stripped.slice(-200).trim();
|
|
1429
|
+
if (/^[a-zA-Z]{1,30}(?:…|\.{3})\s*$/.test(trimmedTail)) {
|
|
1430
|
+
return { detected: false };
|
|
1431
|
+
}
|
|
1432
|
+
const loginDetection = this.detectLogin(output);
|
|
1433
|
+
if (loginDetection.required) {
|
|
1417
1434
|
return {
|
|
1418
1435
|
detected: true,
|
|
1419
|
-
type: "
|
|
1420
|
-
prompt:
|
|
1421
|
-
|
|
1422
|
-
canAutoRespond:
|
|
1423
|
-
instructions:
|
|
1436
|
+
type: "login",
|
|
1437
|
+
prompt: loginDetection.instructions,
|
|
1438
|
+
url: loginDetection.url,
|
|
1439
|
+
canAutoRespond: false,
|
|
1440
|
+
instructions: loginDetection.instructions
|
|
1424
1441
|
};
|
|
1425
1442
|
}
|
|
1426
|
-
if (
|
|
1443
|
+
if (marker?.event === "Notification") {
|
|
1444
|
+
if (marker.notification_type === "permission_prompt") {
|
|
1445
|
+
return {
|
|
1446
|
+
detected: true,
|
|
1447
|
+
type: "permission",
|
|
1448
|
+
prompt: marker.message || "Claude permission prompt",
|
|
1449
|
+
suggestedResponse: "keys:enter",
|
|
1450
|
+
canAutoRespond: true,
|
|
1451
|
+
instructions: "Claude is waiting for permission approval"
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
if (marker.notification_type === "elicitation_dialog") {
|
|
1455
|
+
return {
|
|
1456
|
+
detected: true,
|
|
1457
|
+
type: "tool_wait",
|
|
1458
|
+
prompt: marker.message || "Claude elicitation dialog",
|
|
1459
|
+
canAutoRespond: false,
|
|
1460
|
+
instructions: "Claude is waiting for required user input"
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (/Bypass Permissions mode.*accept all responsibility/is.test(stripped) && /❯\s*1\.\s*No.*exit/i.test(stripped) && /2\.\s*Yes.*I accept/i.test(stripped)) {
|
|
1427
1465
|
return {
|
|
1428
1466
|
detected: true,
|
|
1429
1467
|
type: "permission",
|
|
1430
|
-
prompt: "
|
|
1431
|
-
|
|
1468
|
+
prompt: "Bypass Permissions confirmation",
|
|
1469
|
+
options: ["1", "2"],
|
|
1470
|
+
suggestedResponse: "2",
|
|
1432
1471
|
canAutoRespond: true,
|
|
1433
|
-
instructions: "
|
|
1472
|
+
instructions: "Claude is asking to confirm bypass permissions mode; reply 2 to accept"
|
|
1434
1473
|
};
|
|
1435
1474
|
}
|
|
1436
|
-
if (/
|
|
1475
|
+
if (/how is claude doing this session\?\s*\(optional\)|1:\s*bad\s+2:\s*fine\s+3:\s*good\s+0:\s*dismiss/i.test(
|
|
1476
|
+
stripped
|
|
1477
|
+
)) {
|
|
1437
1478
|
return {
|
|
1438
1479
|
detected: true,
|
|
1439
|
-
type: "
|
|
1440
|
-
prompt: "
|
|
1441
|
-
|
|
1442
|
-
|
|
1480
|
+
type: "config",
|
|
1481
|
+
prompt: "Claude session survey",
|
|
1482
|
+
options: ["1", "2", "3", "0"],
|
|
1483
|
+
suggestedResponse: "0",
|
|
1484
|
+
canAutoRespond: true,
|
|
1485
|
+
instructions: "Optional survey prompt; reply 0 to dismiss"
|
|
1443
1486
|
};
|
|
1444
1487
|
}
|
|
1445
|
-
if (/
|
|
1488
|
+
if (/enter\/tab\/space to toggle.*esc to cancel|enter to confirm.*esc to cancel|esc to close/i.test(
|
|
1489
|
+
stripped
|
|
1490
|
+
)) {
|
|
1446
1491
|
return {
|
|
1447
1492
|
detected: true,
|
|
1448
|
-
type: "
|
|
1449
|
-
prompt: "
|
|
1493
|
+
type: "config",
|
|
1494
|
+
prompt: "Claude dialog awaiting navigation",
|
|
1495
|
+
options: ["keys:enter", "keys:esc", "keys:down,enter"],
|
|
1496
|
+
suggestedResponse: "keys:esc",
|
|
1450
1497
|
canAutoRespond: false,
|
|
1451
|
-
instructions: "
|
|
1498
|
+
instructions: "Use Enter/Esc or arrow keys to navigate this dialog"
|
|
1452
1499
|
};
|
|
1453
1500
|
}
|
|
1454
|
-
if (/
|
|
1501
|
+
if (/press .* to navigate .* enter .* esc|use (?:arrow|↑↓) keys|enter to select|esc to (?:go back|close|cancel)/i.test(
|
|
1502
|
+
stripped
|
|
1503
|
+
) || /(?:^|\n)\s*(?:❯|>)\s*\/(?:agents|chrome|config|tasks|skills|remote-env)\b/im.test(
|
|
1504
|
+
stripped
|
|
1505
|
+
)) {
|
|
1455
1506
|
return {
|
|
1456
1507
|
detected: true,
|
|
1457
1508
|
type: "config",
|
|
1458
|
-
prompt: "
|
|
1509
|
+
prompt: "Claude menu navigation required",
|
|
1510
|
+
options: ["keys:esc", "keys:enter", "keys:down,enter"],
|
|
1511
|
+
suggestedResponse: "keys:esc",
|
|
1459
1512
|
canAutoRespond: false,
|
|
1460
|
-
instructions:
|
|
1513
|
+
instructions: "Claude is showing an interactive menu; use arrow keys + Enter or Esc"
|
|
1461
1514
|
};
|
|
1462
1515
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1516
|
+
if (/Do you want to|wants? (your )?permission|needs your permission/i.test(
|
|
1517
|
+
stripped
|
|
1518
|
+
)) {
|
|
1465
1519
|
return {
|
|
1466
1520
|
detected: true,
|
|
1467
|
-
type: "
|
|
1468
|
-
prompt:
|
|
1469
|
-
|
|
1470
|
-
canAutoRespond:
|
|
1471
|
-
instructions:
|
|
1521
|
+
type: "permission",
|
|
1522
|
+
prompt: "Claude tool permission",
|
|
1523
|
+
suggestedResponse: "keys:enter",
|
|
1524
|
+
canAutoRespond: true,
|
|
1525
|
+
instructions: "Claude is asking permission to use a tool"
|
|
1472
1526
|
};
|
|
1473
1527
|
}
|
|
1474
|
-
if (/
|
|
1528
|
+
if (/choose.*model|select.*model|available models/i.test(stripped) && /\d+\)|claude-/i.test(stripped)) {
|
|
1475
1529
|
return {
|
|
1476
1530
|
detected: true,
|
|
1477
|
-
type: "
|
|
1478
|
-
prompt: "
|
|
1531
|
+
type: "model_select",
|
|
1532
|
+
prompt: "Claude model selection",
|
|
1479
1533
|
canAutoRespond: false,
|
|
1480
|
-
instructions: "
|
|
1534
|
+
instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
|
|
1481
1535
|
};
|
|
1482
1536
|
}
|
|
1483
|
-
if (/select.*
|
|
1537
|
+
if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
|
|
1484
1538
|
return {
|
|
1485
1539
|
detected: true,
|
|
1486
|
-
type: "
|
|
1487
|
-
prompt: "
|
|
1540
|
+
type: "config",
|
|
1541
|
+
prompt: "API tier selection",
|
|
1488
1542
|
canAutoRespond: false,
|
|
1489
|
-
instructions: "Please select
|
|
1543
|
+
instructions: "Please select an API tier"
|
|
1490
1544
|
};
|
|
1491
1545
|
}
|
|
1492
|
-
if (/
|
|
1546
|
+
if (/welcome to claude|first time setup|initial configuration/i.test(stripped)) {
|
|
1493
1547
|
return {
|
|
1494
1548
|
detected: true,
|
|
1495
|
-
type: "
|
|
1496
|
-
prompt: "
|
|
1549
|
+
type: "config",
|
|
1550
|
+
prompt: "First-time setup",
|
|
1497
1551
|
canAutoRespond: false,
|
|
1498
|
-
instructions: "
|
|
1552
|
+
instructions: "Claude Code requires initial configuration"
|
|
1499
1553
|
};
|
|
1500
1554
|
}
|
|
1501
|
-
if (/
|
|
1555
|
+
if (/allow.*access|grant.*permission|access to .* files/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
|
|
1502
1556
|
return {
|
|
1503
1557
|
detected: true,
|
|
1504
|
-
type: "
|
|
1505
|
-
prompt: "
|
|
1506
|
-
|
|
1507
|
-
|
|
1558
|
+
type: "permission",
|
|
1559
|
+
prompt: "File/directory access permission",
|
|
1560
|
+
options: ["y", "n"],
|
|
1561
|
+
suggestedResponse: "y",
|
|
1562
|
+
canAutoRespond: true,
|
|
1563
|
+
instructions: "Claude Code requesting file access permission"
|
|
1508
1564
|
};
|
|
1509
1565
|
}
|
|
1566
|
+
if (this.detectReady(output) || this.detectTaskComplete(output)) {
|
|
1567
|
+
return { detected: false };
|
|
1568
|
+
}
|
|
1569
|
+
if (/❯/.test(stripped.slice(-300))) {
|
|
1570
|
+
return { detected: false };
|
|
1571
|
+
}
|
|
1510
1572
|
return super.detectBlockingPrompt(output);
|
|
1511
1573
|
}
|
|
1512
1574
|
/**
|
|
1513
|
-
* Detect if
|
|
1575
|
+
* Detect if Claude Code is actively loading/processing.
|
|
1514
1576
|
*
|
|
1515
1577
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
1516
|
-
* -
|
|
1517
|
-
* -
|
|
1578
|
+
* - claude_active_reading_files: "Reading N files…"
|
|
1579
|
+
* - General: "esc to interrupt" spinner status line
|
|
1518
1580
|
*/
|
|
1519
1581
|
detectLoading(output) {
|
|
1520
1582
|
const stripped = this.stripAnsi(output);
|
|
1521
1583
|
const marker = this.getLatestHookMarker(stripped);
|
|
1522
1584
|
const tail = stripped.slice(-500);
|
|
1523
|
-
if (marker?.event === "
|
|
1585
|
+
if (marker?.event === "PreToolUse") {
|
|
1524
1586
|
return true;
|
|
1525
1587
|
}
|
|
1526
|
-
if (/esc\s+to\s+
|
|
1588
|
+
if (/esc\s+to\s+interrupt/i.test(tail)) {
|
|
1527
1589
|
return true;
|
|
1528
1590
|
}
|
|
1529
|
-
if (/
|
|
1591
|
+
if (/Reading\s+\d+\s+files/i.test(tail)) {
|
|
1530
1592
|
return true;
|
|
1531
1593
|
}
|
|
1532
1594
|
return false;
|
|
1533
1595
|
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Detect if an external tool/process is running within the Claude session.
|
|
1598
|
+
*
|
|
1599
|
+
* Claude Code can launch external tools (browser, bash, Node, Python, etc.)
|
|
1600
|
+
* that show status lines like "Claude in Chrome[javascript_tool]" or
|
|
1601
|
+
* "[bash_tool]", "[python_tool]", etc.
|
|
1602
|
+
*
|
|
1603
|
+
* When detected, stall detection is suppressed and the UI can display
|
|
1604
|
+
* which tool is active.
|
|
1605
|
+
*/
|
|
1534
1606
|
detectToolRunning(output) {
|
|
1535
1607
|
const stripped = this.stripAnsi(output);
|
|
1536
1608
|
const marker = this.getLatestHookMarker(stripped);
|
|
1537
|
-
|
|
1609
|
+
const tail = stripped.slice(-500);
|
|
1610
|
+
if (marker?.event === "PreToolUse" && marker.tool_name) {
|
|
1538
1611
|
return {
|
|
1539
1612
|
toolName: marker.tool_name.toLowerCase(),
|
|
1540
1613
|
description: `${marker.tool_name} (hook)`
|
|
1541
1614
|
};
|
|
1542
1615
|
}
|
|
1616
|
+
const contextualMatch = tail.match(
|
|
1617
|
+
/Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i
|
|
1618
|
+
);
|
|
1619
|
+
if (contextualMatch) {
|
|
1620
|
+
const appName = contextualMatch[1];
|
|
1621
|
+
const toolType = contextualMatch[2].toLowerCase();
|
|
1622
|
+
const friendlyName = toolType.replace(/_tool$/i, "");
|
|
1623
|
+
return {
|
|
1624
|
+
toolName: friendlyName,
|
|
1625
|
+
description: `${appName} (${toolType})`
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
const toolMatch = tail.match(/\[(\w+_tool)\]/i);
|
|
1629
|
+
if (toolMatch) {
|
|
1630
|
+
const toolType = toolMatch[1].toLowerCase();
|
|
1631
|
+
const friendlyName = toolType.replace(/_tool$/i, "");
|
|
1632
|
+
return { toolName: friendlyName, description: toolType };
|
|
1633
|
+
}
|
|
1543
1634
|
return null;
|
|
1544
1635
|
}
|
|
1545
1636
|
/**
|
|
1546
|
-
* Detect task completion for
|
|
1637
|
+
* Detect task completion for Claude Code.
|
|
1547
1638
|
*
|
|
1548
|
-
* High-confidence
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
1639
|
+
* High-confidence pattern: turn duration summary + idle prompt.
|
|
1640
|
+
* Claude Code shows "<Verb> for Xm Ys" (e.g. "Cooked for 3m 12s")
|
|
1641
|
+
* when a turn completes, followed by the ❯ input prompt.
|
|
1551
1642
|
*
|
|
1552
1643
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
1553
|
-
* -
|
|
1644
|
+
* - claude_completed_turn_duration
|
|
1645
|
+
* - claude_completed_turn_duration_custom_verb
|
|
1554
1646
|
*/
|
|
1555
1647
|
detectTaskComplete(output) {
|
|
1556
1648
|
const stripped = this.stripAnsi(output);
|
|
1557
1649
|
const marker = this.getLatestHookMarker(stripped);
|
|
1558
|
-
if (
|
|
1650
|
+
if (!stripped.trim()) return false;
|
|
1651
|
+
if (marker?.event === "TaskCompleted") {
|
|
1559
1652
|
return true;
|
|
1560
1653
|
}
|
|
1561
|
-
if (
|
|
1654
|
+
if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
|
|
1562
1655
|
return true;
|
|
1563
1656
|
}
|
|
1564
|
-
if (/
|
|
1657
|
+
if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
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(
|
|
1661
|
+
stripped
|
|
1662
|
+
);
|
|
1663
|
+
const tail = stripped.slice(-300);
|
|
1664
|
+
const hasIdlePrompt = /❯/.test(tail);
|
|
1665
|
+
if (hasDuration && hasIdlePrompt) {
|
|
1666
|
+
return true;
|
|
1667
|
+
}
|
|
1668
|
+
if (hasIdlePrompt && stripped.includes("for shortcuts")) {
|
|
1565
1669
|
return true;
|
|
1566
1670
|
}
|
|
1567
1671
|
return false;
|
|
@@ -1569,27 +1673,24 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1569
1673
|
detectReady(output) {
|
|
1570
1674
|
const stripped = this.stripAnsi(output);
|
|
1571
1675
|
const marker = this.getLatestHookMarker(stripped);
|
|
1572
|
-
if (
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
return false;
|
|
1581
|
-
}
|
|
1582
|
-
if (/type.?your.?message/i.test(stripped)) {
|
|
1583
|
-
return true;
|
|
1676
|
+
if (!stripped.trim()) return false;
|
|
1677
|
+
if (marker?.event === "Notification") {
|
|
1678
|
+
if (marker.notification_type === "permission_prompt" || marker.notification_type === "elicitation_dialog") {
|
|
1679
|
+
return false;
|
|
1680
|
+
}
|
|
1681
|
+
if (marker.notification_type === "idle_prompt") {
|
|
1682
|
+
return true;
|
|
1683
|
+
}
|
|
1584
1684
|
}
|
|
1585
|
-
if (/do
|
|
1685
|
+
if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
|
|
1586
1686
|
return false;
|
|
1587
1687
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
/
|
|
1688
|
+
const tail = stripped.slice(-300);
|
|
1689
|
+
const hasConversationalReadyText = stripped.includes("How can I help") || stripped.includes("What would you like");
|
|
1690
|
+
const hasLegacyPrompt = /claude>/i.test(tail);
|
|
1691
|
+
const hasShortcutsHint = stripped.includes("for shortcuts");
|
|
1692
|
+
const hasInteractivePromptBar = /❯\s+\S/.test(tail) && (/\/effort/i.test(stripped) || /Welcome back/i.test(stripped) || /Recent activity/i.test(stripped) || /What's new/i.test(stripped));
|
|
1693
|
+
return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint || hasInteractivePromptBar;
|
|
1593
1694
|
}
|
|
1594
1695
|
parseOutput(output) {
|
|
1595
1696
|
const withoutHookMarkers = this.stripHookMarkers(output);
|
|
@@ -1599,8 +1700,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1599
1700
|
return null;
|
|
1600
1701
|
}
|
|
1601
1702
|
const isQuestion = this.containsQuestion(stripped);
|
|
1602
|
-
|
|
1603
|
-
content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
|
|
1703
|
+
const content = this.extractContent(stripped, /^.*>\s*/gm);
|
|
1604
1704
|
return {
|
|
1605
1705
|
type: isQuestion ? "question" : "response",
|
|
1606
1706
|
content,
|
|
@@ -1611,57 +1711,30 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1611
1711
|
}
|
|
1612
1712
|
};
|
|
1613
1713
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1714
|
+
getPromptPattern() {
|
|
1715
|
+
return /claude>\s*$/i;
|
|
1716
|
+
}
|
|
1717
|
+
getHealthCheckCommand() {
|
|
1718
|
+
return "claude --version";
|
|
1719
|
+
}
|
|
1618
1720
|
detectExit(output) {
|
|
1619
1721
|
const stripped = this.stripAnsi(output);
|
|
1620
1722
|
const marker = this.getLatestHookMarker(stripped);
|
|
1621
1723
|
if (marker?.event === "SessionEnd") {
|
|
1622
|
-
return {
|
|
1623
|
-
exited: true,
|
|
1624
|
-
code: 0
|
|
1625
|
-
};
|
|
1626
|
-
}
|
|
1627
|
-
if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
|
|
1628
|
-
return {
|
|
1629
|
-
exited: true,
|
|
1630
|
-
code: 1,
|
|
1631
|
-
error: "Gemini CLI exited because no folder trust level was selected"
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
if (/you are now logged out/i.test(stripped)) {
|
|
1635
|
-
return {
|
|
1636
|
-
exited: true,
|
|
1637
|
-
code: 0
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
if (/Agent\s+powering\s+down/i.test(stripped)) {
|
|
1641
|
-
return {
|
|
1642
|
-
exited: true,
|
|
1643
|
-
code: 0
|
|
1644
|
-
};
|
|
1724
|
+
return { exited: true, code: 0 };
|
|
1645
1725
|
}
|
|
1646
1726
|
return super.detectExit(output);
|
|
1647
1727
|
}
|
|
1648
|
-
getPromptPattern() {
|
|
1649
|
-
return /gemini>\s*$/i;
|
|
1650
|
-
}
|
|
1651
|
-
getHealthCheckCommand() {
|
|
1652
|
-
return "gemini --version";
|
|
1653
|
-
}
|
|
1654
1728
|
};
|
|
1655
1729
|
|
|
1656
1730
|
// src/codex-adapter.ts
|
|
1657
1731
|
var CodexAdapter = class extends BaseCodingAdapter {
|
|
1658
1732
|
adapterType = "codex";
|
|
1659
1733
|
displayName = "OpenAI Codex";
|
|
1734
|
+
readySettleMs = 2e3;
|
|
1660
1735
|
installation = {
|
|
1661
1736
|
command: "npm install -g @openai/codex",
|
|
1662
|
-
alternatives: [
|
|
1663
|
-
"pip install openai (Python SDK)"
|
|
1664
|
-
],
|
|
1737
|
+
alternatives: ["pip install openai (Python SDK)"],
|
|
1665
1738
|
docsUrl: "https://github.com/openai/codex"
|
|
1666
1739
|
};
|
|
1667
1740
|
/**
|
|
@@ -1975,7 +2048,9 @@ var CodexAdapter = class extends BaseCodingAdapter {
|
|
|
1975
2048
|
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)) {
|
|
1976
2049
|
return false;
|
|
1977
2050
|
}
|
|
1978
|
-
if (/explain this codebase|summarize recent commits|find and fix a bug/i.test(
|
|
2051
|
+
if (/explain this codebase|summarize recent commits|find and fix a bug/i.test(
|
|
2052
|
+
stripped
|
|
2053
|
+
)) {
|
|
1979
2054
|
return true;
|
|
1980
2055
|
}
|
|
1981
2056
|
return stripped.includes("How can I help") || /(?:codex|>)\s*$/i.test(stripped);
|
|
@@ -2024,270 +2099,96 @@ var CodexAdapter = class extends BaseCodingAdapter {
|
|
|
2024
2099
|
}
|
|
2025
2100
|
};
|
|
2026
2101
|
|
|
2027
|
-
// src/
|
|
2028
|
-
var
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
readySettleMs =
|
|
2033
|
-
/**
|
|
2034
|
-
* Aider uses plain text [y/n] prompts, NOT TUI arrow-key menus.
|
|
2035
|
-
*/
|
|
2036
|
-
usesTuiMenus = false;
|
|
2102
|
+
// src/gemini-adapter.ts
|
|
2103
|
+
var GEMINI_HOOK_MARKER_PREFIX = "PARALLAX_GEMINI_HOOK";
|
|
2104
|
+
var GeminiAdapter = class extends BaseCodingAdapter {
|
|
2105
|
+
adapterType = "gemini";
|
|
2106
|
+
displayName = "Google Gemini";
|
|
2107
|
+
readySettleMs = 1500;
|
|
2037
2108
|
installation = {
|
|
2038
|
-
command: "
|
|
2039
|
-
alternatives: [
|
|
2040
|
-
|
|
2041
|
-
"brew install aider (macOS with Homebrew)"
|
|
2042
|
-
],
|
|
2043
|
-
docsUrl: "https://aider.chat/docs/install.html",
|
|
2044
|
-
minVersion: "0.50.0"
|
|
2109
|
+
command: "npm install -g @google/gemini-cli",
|
|
2110
|
+
alternatives: ["See documentation for latest installation method"],
|
|
2111
|
+
docsUrl: "https://github.com/google-gemini/gemini-cli#installation"
|
|
2045
2112
|
};
|
|
2046
2113
|
/**
|
|
2047
|
-
* Auto-response rules for
|
|
2048
|
-
*
|
|
2049
|
-
*
|
|
2050
|
-
*
|
|
2051
|
-
* Decline rules come first to override the generic accept patterns.
|
|
2114
|
+
* Auto-response rules for Gemini CLI.
|
|
2115
|
+
* Gemini uses Ink/React TUI with arrow-key radio menus.
|
|
2116
|
+
* Source: FolderTrustDialog.tsx, MultiFolderTrustDialog.tsx, CloudFreePrivacyNotice.tsx
|
|
2052
2117
|
*/
|
|
2053
2118
|
autoResponseRules = [
|
|
2054
|
-
// ── Decline rules (specific, checked first) ────────────────────────
|
|
2055
2119
|
{
|
|
2056
|
-
pattern: /
|
|
2057
|
-
type: "
|
|
2058
|
-
response: "
|
|
2059
|
-
responseType: "
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
once: true
|
|
2063
|
-
},
|
|
2064
|
-
{
|
|
2065
|
-
pattern: /would you like to see what.?s new in this version/i,
|
|
2066
|
-
type: "config",
|
|
2067
|
-
response: "n",
|
|
2068
|
-
responseType: "text",
|
|
2069
|
-
description: "Decline release notes offer",
|
|
2120
|
+
pattern: /do.?you.?trust.?this.?folder|trust.?folder|trust.?parent.?folder/i,
|
|
2121
|
+
type: "permission",
|
|
2122
|
+
response: "",
|
|
2123
|
+
responseType: "keys",
|
|
2124
|
+
keys: ["enter"],
|
|
2125
|
+
description: "Trust current folder (default selection in radio menu)",
|
|
2070
2126
|
safe: true,
|
|
2071
2127
|
once: true
|
|
2072
2128
|
},
|
|
2073
2129
|
{
|
|
2074
|
-
pattern: /
|
|
2075
|
-
type: "config",
|
|
2076
|
-
response: "n",
|
|
2077
|
-
responseType: "text",
|
|
2078
|
-
description: "Decline automatic bug report",
|
|
2079
|
-
safe: true
|
|
2080
|
-
},
|
|
2081
|
-
{
|
|
2082
|
-
pattern: /open documentation url for more info\?/i,
|
|
2083
|
-
type: "config",
|
|
2084
|
-
response: "n",
|
|
2085
|
-
responseType: "text",
|
|
2086
|
-
description: "Decline opening Aider documentation for model warnings",
|
|
2087
|
-
safe: true
|
|
2088
|
-
},
|
|
2089
|
-
// ── File / edit operations ──────────────────────────────────────────
|
|
2090
|
-
{
|
|
2091
|
-
pattern: /add .+ to the chat\?/i,
|
|
2092
|
-
type: "permission",
|
|
2093
|
-
response: "y",
|
|
2094
|
-
responseType: "text",
|
|
2095
|
-
description: "Allow Aider to add files to chat context",
|
|
2096
|
-
safe: true
|
|
2097
|
-
},
|
|
2098
|
-
{
|
|
2099
|
-
pattern: /add url to the chat\?/i,
|
|
2100
|
-
type: "permission",
|
|
2101
|
-
response: "y",
|
|
2102
|
-
responseType: "text",
|
|
2103
|
-
description: "Allow Aider to add URL content to chat",
|
|
2104
|
-
safe: true
|
|
2105
|
-
},
|
|
2106
|
-
{
|
|
2107
|
-
pattern: /create new file\?/i,
|
|
2108
|
-
type: "permission",
|
|
2109
|
-
response: "y",
|
|
2110
|
-
responseType: "text",
|
|
2111
|
-
description: "Allow Aider to create new files",
|
|
2112
|
-
safe: true
|
|
2113
|
-
},
|
|
2114
|
-
{
|
|
2115
|
-
pattern: /allow edits to file/i,
|
|
2116
|
-
type: "permission",
|
|
2117
|
-
response: "y",
|
|
2118
|
-
responseType: "text",
|
|
2119
|
-
description: "Allow edits to file not yet in chat",
|
|
2120
|
-
safe: true
|
|
2121
|
-
},
|
|
2122
|
-
{
|
|
2123
|
-
pattern: /edit the files\?/i,
|
|
2124
|
-
type: "permission",
|
|
2125
|
-
response: "y",
|
|
2126
|
-
responseType: "text",
|
|
2127
|
-
description: "Accept architect mode edits",
|
|
2128
|
-
safe: true
|
|
2129
|
-
},
|
|
2130
|
-
// ── Shell operations ────────────────────────────────────────────────
|
|
2131
|
-
{
|
|
2132
|
-
pattern: /run shell commands?\?/i,
|
|
2133
|
-
type: "permission",
|
|
2134
|
-
response: "y",
|
|
2135
|
-
responseType: "text",
|
|
2136
|
-
description: "Allow Aider to run shell commands",
|
|
2137
|
-
safe: true
|
|
2138
|
-
},
|
|
2139
|
-
{
|
|
2140
|
-
pattern: /add command output to the chat\?/i,
|
|
2141
|
-
type: "permission",
|
|
2142
|
-
response: "y",
|
|
2143
|
-
responseType: "text",
|
|
2144
|
-
description: "Add shell command output to chat context",
|
|
2145
|
-
safe: true
|
|
2146
|
-
},
|
|
2147
|
-
{
|
|
2148
|
-
pattern: /add \d+.*tokens of command output to the chat\?/i,
|
|
2130
|
+
pattern: /trust.?the.?following.?folders.*(added|workspace)/i,
|
|
2149
2131
|
type: "permission",
|
|
2150
|
-
response: "
|
|
2151
|
-
responseType: "
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
},
|
|
2155
|
-
// ── Setup / maintenance ─────────────────────────────────────────────
|
|
2156
|
-
{
|
|
2157
|
-
pattern: /no git repo found.*create one/i,
|
|
2158
|
-
type: "config",
|
|
2159
|
-
response: "y",
|
|
2160
|
-
responseType: "text",
|
|
2161
|
-
description: "Create git repo for change tracking",
|
|
2132
|
+
response: "",
|
|
2133
|
+
responseType: "keys",
|
|
2134
|
+
keys: ["enter"],
|
|
2135
|
+
description: "Trust multiple folders being added to workspace",
|
|
2162
2136
|
safe: true,
|
|
2163
2137
|
once: true
|
|
2164
2138
|
},
|
|
2165
2139
|
{
|
|
2166
|
-
pattern: /
|
|
2140
|
+
pattern: /allow.?google.?to.?use.?this.?data/i,
|
|
2167
2141
|
type: "config",
|
|
2168
|
-
response: "
|
|
2169
|
-
responseType: "
|
|
2170
|
-
|
|
2142
|
+
response: "",
|
|
2143
|
+
responseType: "keys",
|
|
2144
|
+
keys: ["down", "enter"],
|
|
2145
|
+
description: 'Decline Google data collection (select "No")',
|
|
2171
2146
|
safe: true,
|
|
2172
2147
|
once: true
|
|
2173
|
-
},
|
|
2174
|
-
{
|
|
2175
|
-
pattern: /run pip install\?/i,
|
|
2176
|
-
type: "config",
|
|
2177
|
-
response: "y",
|
|
2178
|
-
responseType: "text",
|
|
2179
|
-
description: "Install missing Python dependencies",
|
|
2180
|
-
safe: true
|
|
2181
|
-
},
|
|
2182
|
-
{
|
|
2183
|
-
pattern: /install playwright\?/i,
|
|
2184
|
-
type: "config",
|
|
2185
|
-
response: "y",
|
|
2186
|
-
responseType: "text",
|
|
2187
|
-
description: "Install Playwright for web scraping",
|
|
2188
|
-
safe: true
|
|
2189
|
-
},
|
|
2190
|
-
// ── Other safe confirmations ────────────────────────────────────────
|
|
2191
|
-
{
|
|
2192
|
-
pattern: /fix lint errors in/i,
|
|
2193
|
-
type: "permission",
|
|
2194
|
-
response: "y",
|
|
2195
|
-
responseType: "text",
|
|
2196
|
-
description: "Accept lint error fix suggestion",
|
|
2197
|
-
safe: true
|
|
2198
|
-
},
|
|
2199
|
-
{
|
|
2200
|
-
pattern: /try to proceed anyway\?/i,
|
|
2201
|
-
type: "config",
|
|
2202
|
-
response: "y",
|
|
2203
|
-
responseType: "text",
|
|
2204
|
-
description: "Continue despite context limit warning",
|
|
2205
|
-
safe: true
|
|
2206
2148
|
}
|
|
2207
2149
|
];
|
|
2208
2150
|
getWorkspaceFiles() {
|
|
2209
2151
|
return [
|
|
2210
2152
|
{
|
|
2211
|
-
relativePath: ".
|
|
2212
|
-
description: "Project
|
|
2153
|
+
relativePath: "GEMINI.md",
|
|
2154
|
+
description: "Project-level instructions read automatically on startup",
|
|
2213
2155
|
autoLoaded: true,
|
|
2214
2156
|
type: "memory",
|
|
2215
2157
|
format: "markdown"
|
|
2216
2158
|
},
|
|
2217
2159
|
{
|
|
2218
|
-
relativePath: ".
|
|
2219
|
-
description: "Project-scoped
|
|
2160
|
+
relativePath: ".gemini/settings.json",
|
|
2161
|
+
description: "Project-scoped settings (tool permissions, sandbox config)",
|
|
2220
2162
|
autoLoaded: true,
|
|
2221
2163
|
type: "config",
|
|
2222
|
-
format: "
|
|
2164
|
+
format: "json"
|
|
2223
2165
|
},
|
|
2224
2166
|
{
|
|
2225
|
-
relativePath: ".
|
|
2226
|
-
description: "
|
|
2227
|
-
autoLoaded:
|
|
2228
|
-
type: "
|
|
2229
|
-
format: "
|
|
2167
|
+
relativePath: ".gemini/styles",
|
|
2168
|
+
description: "Custom style/persona definitions directory",
|
|
2169
|
+
autoLoaded: false,
|
|
2170
|
+
type: "config",
|
|
2171
|
+
format: "markdown"
|
|
2230
2172
|
}
|
|
2231
2173
|
];
|
|
2232
2174
|
}
|
|
2233
|
-
getRecommendedModels(
|
|
2234
|
-
if (credentials?.anthropicKey) {
|
|
2235
|
-
return {
|
|
2236
|
-
powerful: "anthropic/claude-sonnet-4-20250514",
|
|
2237
|
-
fast: "anthropic/claude-haiku-4-5-20251001"
|
|
2238
|
-
};
|
|
2239
|
-
}
|
|
2240
|
-
if (credentials?.openaiKey) {
|
|
2241
|
-
return {
|
|
2242
|
-
powerful: "openai/o3",
|
|
2243
|
-
fast: "openai/gpt-4o-mini"
|
|
2244
|
-
};
|
|
2245
|
-
}
|
|
2246
|
-
if (credentials?.googleKey) {
|
|
2247
|
-
return {
|
|
2248
|
-
powerful: "gemini/gemini-3-pro",
|
|
2249
|
-
fast: "gemini/gemini-3-flash"
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2175
|
+
getRecommendedModels(_credentials) {
|
|
2252
2176
|
return {
|
|
2253
|
-
powerful: "
|
|
2254
|
-
fast: "
|
|
2177
|
+
powerful: "gemini-3-pro",
|
|
2178
|
+
fast: "gemini-3-flash"
|
|
2255
2179
|
};
|
|
2256
2180
|
}
|
|
2257
2181
|
getCommand() {
|
|
2258
|
-
return "
|
|
2182
|
+
return "gemini";
|
|
2259
2183
|
}
|
|
2260
2184
|
getArgs(config) {
|
|
2261
2185
|
const args = [];
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
args.push("--
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
const provider = config.adapterConfig?.provider;
|
|
2269
|
-
const credentials = this.getCredentials(config);
|
|
2270
|
-
if (config.env?.AIDER_MODEL) {
|
|
2271
|
-
args.push("--model", config.env.AIDER_MODEL);
|
|
2272
|
-
} else if (interactive && (credentials.googleKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY)) {
|
|
2273
|
-
args.push("--model", "gemini");
|
|
2274
|
-
} else if (!interactive && provider === "anthropic") {
|
|
2275
|
-
args.push("--model", "sonnet");
|
|
2276
|
-
} else if (!interactive && provider === "openai") {
|
|
2277
|
-
args.push("--model", "4o");
|
|
2278
|
-
} else if (!interactive && provider === "google") {
|
|
2279
|
-
args.push("--model", "gemini");
|
|
2280
|
-
} else if (!interactive && credentials.anthropicKey) {
|
|
2281
|
-
args.push("--model", "sonnet");
|
|
2282
|
-
} else if (!interactive && credentials.openaiKey) {
|
|
2283
|
-
args.push("--model", "4o");
|
|
2284
|
-
} else if (!interactive && credentials.googleKey) {
|
|
2285
|
-
args.push("--model", "gemini");
|
|
2286
|
-
}
|
|
2287
|
-
if (!interactive) {
|
|
2288
|
-
if (credentials.anthropicKey) args.push("--api-key", `anthropic=${credentials.anthropicKey}`);
|
|
2289
|
-
if (credentials.openaiKey) args.push("--api-key", `openai=${credentials.openaiKey}`);
|
|
2290
|
-
if (credentials.googleKey) args.push("--api-key", `gemini=${credentials.googleKey}`);
|
|
2186
|
+
if (!this.isInteractive(config)) {
|
|
2187
|
+
args.push("--non-interactive");
|
|
2188
|
+
args.push("--output-format", "text");
|
|
2189
|
+
if (config.workdir) {
|
|
2190
|
+
args.push("--cwd", config.workdir);
|
|
2191
|
+
}
|
|
2291
2192
|
}
|
|
2292
2193
|
const approvalConfig = this.getApprovalConfig(config);
|
|
2293
2194
|
if (approvalConfig) {
|
|
@@ -2297,54 +2198,239 @@ var AiderAdapter = class extends BaseCodingAdapter {
|
|
|
2297
2198
|
}
|
|
2298
2199
|
getEnv(config) {
|
|
2299
2200
|
const env = {};
|
|
2201
|
+
const credentials = this.getCredentials(config);
|
|
2202
|
+
const adapterConfig = config.adapterConfig;
|
|
2203
|
+
if (credentials.googleKey) {
|
|
2204
|
+
env.GOOGLE_API_KEY = credentials.googleKey;
|
|
2205
|
+
env.GEMINI_API_KEY = credentials.googleKey;
|
|
2206
|
+
}
|
|
2207
|
+
if (config.env?.GEMINI_MODEL) {
|
|
2208
|
+
env.GEMINI_MODEL = config.env.GEMINI_MODEL;
|
|
2209
|
+
}
|
|
2300
2210
|
if (!this.isInteractive(config)) {
|
|
2301
2211
|
env.NO_COLOR = "1";
|
|
2302
2212
|
}
|
|
2303
|
-
if (
|
|
2304
|
-
env.
|
|
2213
|
+
if (adapterConfig?.geminiHookTelemetry) {
|
|
2214
|
+
env.PARALLAX_GEMINI_HOOK_TELEMETRY = "1";
|
|
2215
|
+
env.PARALLAX_GEMINI_HOOK_MARKER_PREFIX = adapterConfig.geminiHookMarkerPrefix || GEMINI_HOOK_MARKER_PREFIX;
|
|
2305
2216
|
}
|
|
2306
2217
|
return env;
|
|
2307
2218
|
}
|
|
2219
|
+
getHookTelemetryProtocol(options) {
|
|
2220
|
+
if (options?.httpUrl) {
|
|
2221
|
+
const sessionHeader = options.sessionId ? ` -H 'X-Parallax-Session-Id: ${options.sessionId}'` : "";
|
|
2222
|
+
const curlCommand = `bash -c 'curl -sf -X POST "${options.httpUrl}" -H "Content-Type: application/json"${sessionHeader} -d @- --max-time 4 2>/dev/null || echo "{\\"continue\\":true}"'`;
|
|
2223
|
+
const hookEntry2 = [
|
|
2224
|
+
{
|
|
2225
|
+
matcher: "",
|
|
2226
|
+
hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }]
|
|
2227
|
+
}
|
|
2228
|
+
];
|
|
2229
|
+
const hookEntryNoMatcher = [
|
|
2230
|
+
{ hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }] }
|
|
2231
|
+
];
|
|
2232
|
+
const settingsHooks2 = {
|
|
2233
|
+
BeforeTool: hookEntry2,
|
|
2234
|
+
AfterTool: hookEntry2,
|
|
2235
|
+
AfterAgent: hookEntryNoMatcher,
|
|
2236
|
+
SessionEnd: hookEntryNoMatcher,
|
|
2237
|
+
Notification: hookEntry2
|
|
2238
|
+
};
|
|
2239
|
+
return {
|
|
2240
|
+
markerPrefix: "",
|
|
2241
|
+
scriptPath: "",
|
|
2242
|
+
scriptContent: "",
|
|
2243
|
+
settingsHooks: settingsHooks2
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
const markerPrefix = options?.markerPrefix || GEMINI_HOOK_MARKER_PREFIX;
|
|
2247
|
+
const scriptPath = options?.scriptPath || ".gemini/hooks/parallax-hook-telemetry.sh";
|
|
2248
|
+
const scriptCommand = `"${"$"}GEMINI_PROJECT_ROOT"/${scriptPath}`;
|
|
2249
|
+
const hookEntry = [
|
|
2250
|
+
{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }
|
|
2251
|
+
];
|
|
2252
|
+
const settingsHooks = {
|
|
2253
|
+
Notification: hookEntry,
|
|
2254
|
+
BeforeTool: hookEntry,
|
|
2255
|
+
AfterAgent: hookEntry,
|
|
2256
|
+
SessionEnd: hookEntry
|
|
2257
|
+
};
|
|
2258
|
+
const scriptContent = `#!/usr/bin/env bash
|
|
2259
|
+
set -euo pipefail
|
|
2260
|
+
|
|
2261
|
+
INPUT="$(cat)"
|
|
2262
|
+
[ -z "${"$"}INPUT" ] && exit 0
|
|
2263
|
+
|
|
2264
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
2265
|
+
# Valid no-op response
|
|
2266
|
+
printf '%s
|
|
2267
|
+
' '{"continue":true}'
|
|
2268
|
+
exit 0
|
|
2269
|
+
fi
|
|
2270
|
+
|
|
2271
|
+
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hookEventName // .hook_event_name // empty')"
|
|
2272
|
+
[ -z "${"$"}EVENT" ] && { printf '%s
|
|
2273
|
+
' '{"continue":true}'; exit 0; }
|
|
2274
|
+
|
|
2275
|
+
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notificationType // .notification_type // empty')"
|
|
2276
|
+
TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.toolName // .tool_name // empty')"
|
|
2277
|
+
MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
|
|
2278
|
+
|
|
2279
|
+
PAYLOAD="$(jq -nc \\
|
|
2280
|
+
--arg event "${"$"}EVENT" \\
|
|
2281
|
+
--arg notification_type "${"$"}NOTIFICATION_TYPE" \\
|
|
2282
|
+
--arg tool_name "${"$"}TOOL_NAME" \\
|
|
2283
|
+
--arg message "${"$"}MESSAGE" \\
|
|
2284
|
+
'({event: $event}
|
|
2285
|
+
+ (if $notification_type != "" then {notification_type: $notification_type} else {} end)
|
|
2286
|
+
+ (if $tool_name != "" then {tool_name: $tool_name} else {} end)
|
|
2287
|
+
+ (if $message != "" then {message: $message} else {} end))')"
|
|
2288
|
+
|
|
2289
|
+
MARKER="${markerPrefix} ${"$"}PAYLOAD"
|
|
2290
|
+
jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
|
|
2291
|
+
`;
|
|
2292
|
+
return {
|
|
2293
|
+
markerPrefix,
|
|
2294
|
+
scriptPath,
|
|
2295
|
+
scriptContent,
|
|
2296
|
+
settingsHooks
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
getHookMarkers(output) {
|
|
2300
|
+
const markers = [];
|
|
2301
|
+
const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
|
|
2302
|
+
let match;
|
|
2303
|
+
while ((match = markerRegex.exec(output)) !== null) {
|
|
2304
|
+
const markerToken = match[1];
|
|
2305
|
+
if (!markerToken.includes("GEMINI_HOOK")) {
|
|
2306
|
+
continue;
|
|
2307
|
+
}
|
|
2308
|
+
const payload = match[2];
|
|
2309
|
+
try {
|
|
2310
|
+
const parsed = JSON.parse(payload);
|
|
2311
|
+
const event = typeof parsed.event === "string" ? parsed.event : void 0;
|
|
2312
|
+
if (!event) continue;
|
|
2313
|
+
markers.push({
|
|
2314
|
+
event,
|
|
2315
|
+
notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
|
|
2316
|
+
tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
|
|
2317
|
+
message: typeof parsed.message === "string" ? parsed.message : void 0
|
|
2318
|
+
});
|
|
2319
|
+
} catch {
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return markers;
|
|
2323
|
+
}
|
|
2324
|
+
getLatestHookMarker(output) {
|
|
2325
|
+
const markers = this.getHookMarkers(output);
|
|
2326
|
+
return markers.length > 0 ? markers[markers.length - 1] : null;
|
|
2327
|
+
}
|
|
2328
|
+
stripHookMarkers(output) {
|
|
2329
|
+
return output.replace(
|
|
2330
|
+
/(?:^|\n)\s*[A-Z0-9_]*GEMINI_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g,
|
|
2331
|
+
"\n"
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2308
2334
|
detectLogin(output) {
|
|
2309
2335
|
const stripped = this.stripAnsi(output);
|
|
2310
|
-
if (stripped.includes("
|
|
2336
|
+
if (stripped.includes("API key not found") || /set (?:GOOGLE_API_KEY|GEMINI_API_KEY)/i.test(stripped) || stripped.includes("authentication required") || stripped.includes("Invalid API key") || stripped.includes("API key is not valid")) {
|
|
2311
2337
|
return {
|
|
2312
2338
|
required: true,
|
|
2313
2339
|
type: "api_key",
|
|
2314
|
-
instructions: "Set
|
|
2340
|
+
instructions: "Set GOOGLE_API_KEY or GEMINI_API_KEY environment variable"
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
if (/enter.?gemini.?api.?key/i.test(stripped)) {
|
|
2344
|
+
return {
|
|
2345
|
+
required: true,
|
|
2346
|
+
type: "api_key",
|
|
2347
|
+
instructions: "Enter a Gemini API key or set GEMINI_API_KEY environment variable"
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
if (/how.?would.?you.?like.?to.?authenticate/i.test(stripped) || /get.?started/i.test(stripped) && /login.?with.?google|use.?gemini.?api.?key|vertex/i.test(stripped)) {
|
|
2351
|
+
return {
|
|
2352
|
+
required: true,
|
|
2353
|
+
type: "oauth",
|
|
2354
|
+
instructions: "Gemini CLI authentication required \u2014 select an auth method"
|
|
2315
2355
|
};
|
|
2316
2356
|
}
|
|
2317
|
-
if (
|
|
2357
|
+
if (/waiting.?for.?auth/i.test(stripped)) {
|
|
2318
2358
|
return {
|
|
2319
2359
|
required: true,
|
|
2320
|
-
type: "
|
|
2321
|
-
instructions: "
|
|
2360
|
+
type: "oauth",
|
|
2361
|
+
instructions: "Waiting for browser authentication to complete"
|
|
2322
2362
|
};
|
|
2323
2363
|
}
|
|
2324
|
-
if (
|
|
2364
|
+
if (stripped.includes("Sign in with Google") || stripped.includes("OAuth") || stripped.includes("accounts.google.com")) {
|
|
2365
|
+
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
2325
2366
|
return {
|
|
2326
2367
|
required: true,
|
|
2327
2368
|
type: "oauth",
|
|
2328
|
-
|
|
2369
|
+
url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
|
|
2370
|
+
instructions: "Google OAuth authentication required"
|
|
2329
2371
|
};
|
|
2330
2372
|
}
|
|
2331
|
-
if (
|
|
2332
|
-
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
2373
|
+
if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
|
|
2333
2374
|
return {
|
|
2334
2375
|
required: true,
|
|
2335
2376
|
type: "browser",
|
|
2336
|
-
|
|
2337
|
-
instructions: "Complete OpenRouter authentication in browser"
|
|
2377
|
+
instructions: "Run: gcloud auth application-default login"
|
|
2338
2378
|
};
|
|
2339
2379
|
}
|
|
2340
2380
|
return { required: false };
|
|
2341
2381
|
}
|
|
2342
|
-
/**
|
|
2343
|
-
* Detect blocking prompts specific to Aider CLI.
|
|
2344
|
-
* Source: io.py, onboarding.py, base_coder.py, report.py
|
|
2345
|
-
*/
|
|
2346
2382
|
detectBlockingPrompt(output) {
|
|
2347
2383
|
const stripped = this.stripAnsi(output);
|
|
2384
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2385
|
+
if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
|
|
2386
|
+
return {
|
|
2387
|
+
detected: true,
|
|
2388
|
+
type: "permission",
|
|
2389
|
+
prompt: marker.message || "Gemini tool permission",
|
|
2390
|
+
suggestedResponse: "keys:enter",
|
|
2391
|
+
canAutoRespond: true,
|
|
2392
|
+
instructions: "Gemini is asking to allow a tool action"
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
if (/apply.?this.?change\??/i.test(stripped) || /allow.?execution.?of/i.test(stripped) || /do.?you.?want.?to.?proceed\??/i.test(stripped) || /waiting.?for.?user.?confirmation/i.test(stripped)) {
|
|
2396
|
+
return {
|
|
2397
|
+
detected: true,
|
|
2398
|
+
type: "permission",
|
|
2399
|
+
prompt: "Gemini tool execution confirmation",
|
|
2400
|
+
suggestedResponse: "keys:enter",
|
|
2401
|
+
canAutoRespond: true,
|
|
2402
|
+
instructions: "Gemini is asking to apply a change (file write, shell command, etc.)"
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
if (/do.?you.?want.?to.?continue\s*\([yY]\/[nN]\)\??/i.test(stripped) || /continue\??\s*\([yY]\/[nN]\)\??/i.test(stripped) || /are.?you.?sure\??\s*\([yY]\/[nN]\)\??/i.test(stripped)) {
|
|
2406
|
+
return {
|
|
2407
|
+
detected: true,
|
|
2408
|
+
type: "tool_wait",
|
|
2409
|
+
prompt: "Interactive shell confirmation required (y/n)",
|
|
2410
|
+
canAutoRespond: false,
|
|
2411
|
+
instructions: "Focus shell input (Tab) and answer the y/n confirmation prompt"
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
if (/Interactive\s+shell\s+awaiting\s+input/i.test(stripped)) {
|
|
2415
|
+
return {
|
|
2416
|
+
detected: true,
|
|
2417
|
+
type: "tool_wait",
|
|
2418
|
+
prompt: "Gemini interactive shell needs user focus",
|
|
2419
|
+
canAutoRespond: false,
|
|
2420
|
+
instructions: "Press Tab to focus the interactive shell, or wait for it to complete"
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
if (/enable.?checkpointing.?to.?recover.?your.?session.?after.?a.?crash/i.test(
|
|
2424
|
+
stripped
|
|
2425
|
+
)) {
|
|
2426
|
+
return {
|
|
2427
|
+
detected: true,
|
|
2428
|
+
type: "config",
|
|
2429
|
+
prompt: "Gemini checkpoint setup prompt",
|
|
2430
|
+
canAutoRespond: false,
|
|
2431
|
+
instructions: 'Respond to checkpoint setup prompt (for example: press "s" to configure or dismiss)'
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2348
2434
|
const loginDetection = this.detectLogin(output);
|
|
2349
2435
|
if (loginDetection.required) {
|
|
2350
2436
|
return {
|
|
@@ -2356,111 +2442,147 @@ var AiderAdapter = class extends BaseCodingAdapter {
|
|
|
2356
2442
|
instructions: loginDetection.instructions
|
|
2357
2443
|
};
|
|
2358
2444
|
}
|
|
2359
|
-
if (/
|
|
2445
|
+
if (/further.?action.?is.?required/i.test(stripped) || /verify.?your.?account/i.test(stripped) || /waiting.?for.?verification/i.test(stripped)) {
|
|
2446
|
+
return {
|
|
2447
|
+
detected: true,
|
|
2448
|
+
type: "config",
|
|
2449
|
+
prompt: "Account verification required",
|
|
2450
|
+
canAutoRespond: false,
|
|
2451
|
+
instructions: "Your Gemini account requires verification before continuing"
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
if (/select.*model|choose.*model|gemini-/i.test(stripped) && /\d+\)/i.test(stripped)) {
|
|
2360
2455
|
return {
|
|
2361
2456
|
detected: true,
|
|
2362
2457
|
type: "model_select",
|
|
2363
|
-
prompt: "
|
|
2458
|
+
prompt: "Gemini model selection",
|
|
2364
2459
|
canAutoRespond: false,
|
|
2365
|
-
instructions: "Please select a model or set
|
|
2460
|
+
instructions: "Please select a model or set GEMINI_MODEL env var"
|
|
2366
2461
|
};
|
|
2367
2462
|
}
|
|
2368
|
-
if (/
|
|
2463
|
+
if (/select.*project|choose.*project|google cloud project/i.test(stripped)) {
|
|
2369
2464
|
return {
|
|
2370
2465
|
detected: true,
|
|
2371
|
-
type: "
|
|
2372
|
-
prompt: "
|
|
2466
|
+
type: "project_select",
|
|
2467
|
+
prompt: "Google Cloud project selection",
|
|
2373
2468
|
canAutoRespond: false,
|
|
2374
|
-
instructions: "
|
|
2469
|
+
instructions: "Please select a Google Cloud project"
|
|
2375
2470
|
};
|
|
2376
2471
|
}
|
|
2377
|
-
if (/
|
|
2472
|
+
if (/safety.*filter|content.*blocked|unsafe.*content/i.test(stripped)) {
|
|
2378
2473
|
return {
|
|
2379
2474
|
detected: true,
|
|
2380
|
-
type: "
|
|
2381
|
-
prompt: "
|
|
2382
|
-
options: ["y", "n"],
|
|
2475
|
+
type: "unknown",
|
|
2476
|
+
prompt: "Safety filter triggered",
|
|
2383
2477
|
canAutoRespond: false,
|
|
2384
|
-
instructions: "
|
|
2478
|
+
instructions: "Content was blocked by safety filters"
|
|
2385
2479
|
};
|
|
2386
2480
|
}
|
|
2387
2481
|
return super.detectBlockingPrompt(output);
|
|
2388
2482
|
}
|
|
2389
2483
|
/**
|
|
2390
|
-
* Detect if
|
|
2484
|
+
* Detect if Gemini CLI is actively loading/processing.
|
|
2391
2485
|
*
|
|
2392
2486
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
2393
|
-
* -
|
|
2394
|
-
* -
|
|
2395
|
-
* - aider_active_generating_commit_message: "Generating commit message with ..."
|
|
2487
|
+
* - gemini_active_loading_line: "(esc to cancel, Xs)"
|
|
2488
|
+
* - gemini_active_waiting_user_confirmation: "Waiting for user confirmation..."
|
|
2396
2489
|
*/
|
|
2397
2490
|
detectLoading(output) {
|
|
2398
2491
|
const stripped = this.stripAnsi(output);
|
|
2492
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2399
2493
|
const tail = stripped.slice(-500);
|
|
2400
|
-
if (
|
|
2494
|
+
if (marker?.event === "BeforeTool") {
|
|
2401
2495
|
return true;
|
|
2402
2496
|
}
|
|
2403
|
-
if (/
|
|
2497
|
+
if (/esc\s+to\s+cancel/i.test(tail)) {
|
|
2498
|
+
return true;
|
|
2499
|
+
}
|
|
2500
|
+
if (/Waiting\s+for\s+user\s+confirmation/i.test(tail)) {
|
|
2404
2501
|
return true;
|
|
2405
2502
|
}
|
|
2406
2503
|
return false;
|
|
2407
2504
|
}
|
|
2505
|
+
detectToolRunning(output) {
|
|
2506
|
+
const stripped = this.stripAnsi(output);
|
|
2507
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2508
|
+
if (marker?.event === "BeforeTool" && marker.tool_name) {
|
|
2509
|
+
return {
|
|
2510
|
+
toolName: marker.tool_name.toLowerCase(),
|
|
2511
|
+
description: `${marker.tool_name} (hook)`
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2408
2516
|
/**
|
|
2409
|
-
* Detect task completion for
|
|
2517
|
+
* Detect task completion for Gemini CLI.
|
|
2410
2518
|
*
|
|
2411
2519
|
* High-confidence patterns:
|
|
2412
|
-
* - "
|
|
2413
|
-
* -
|
|
2520
|
+
* - "◇ Ready" window title signal (OSC sequence, may survive ANSI stripping)
|
|
2521
|
+
* - "Type your message" composer placeholder after agent output
|
|
2414
2522
|
*
|
|
2415
2523
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
2416
|
-
* -
|
|
2524
|
+
* - gemini_ready_title
|
|
2417
2525
|
*/
|
|
2418
2526
|
detectTaskComplete(output) {
|
|
2419
2527
|
const stripped = this.stripAnsi(output);
|
|
2420
|
-
|
|
2528
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2529
|
+
if (marker?.event === "AfterAgent") {
|
|
2421
2530
|
return true;
|
|
2422
2531
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
return true;
|
|
2429
|
-
}
|
|
2532
|
+
if (/◇\s+Ready/.test(stripped)) {
|
|
2533
|
+
return true;
|
|
2534
|
+
}
|
|
2535
|
+
if (/type.?your.?message/i.test(stripped)) {
|
|
2536
|
+
return true;
|
|
2430
2537
|
}
|
|
2431
2538
|
return false;
|
|
2432
2539
|
}
|
|
2433
2540
|
detectReady(output) {
|
|
2434
2541
|
const stripped = this.stripAnsi(output);
|
|
2435
|
-
|
|
2542
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2543
|
+
if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
|
|
2436
2544
|
return false;
|
|
2437
2545
|
}
|
|
2438
|
-
if (
|
|
2546
|
+
if (marker?.event === "AfterAgent") {
|
|
2439
2547
|
return true;
|
|
2440
2548
|
}
|
|
2441
|
-
|
|
2549
|
+
const hasActiveOverlay = /interactive\s+shell\s+awaiting\s+input|press\s+tab\s+to\s+focus\s+shell/i.test(
|
|
2550
|
+
stripped
|
|
2551
|
+
) || /waiting\s+for\s+user\s+confirmation|apply.?this.?change|allow.?execution|do.?you.?want.?to.?proceed/i.test(
|
|
2552
|
+
stripped
|
|
2553
|
+
) || /do.?you.?want.?to.?continue\s*\([yY]\/[nN]\)\??|are.?you.?sure\??\s*\([yY]\/[nN]\)\??/i.test(
|
|
2554
|
+
stripped
|
|
2555
|
+
) || /enable.?checkpointing.?to.?recover.?your.?session.?after.?a.?crash/i.test(
|
|
2556
|
+
stripped
|
|
2557
|
+
) || /esc\s+to\s+cancel|esc\s+to\s+interrupt/i.test(stripped);
|
|
2558
|
+
if (hasActiveOverlay) {
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
if (/type.?your.?message/i.test(stripped)) {
|
|
2442
2562
|
return true;
|
|
2443
2563
|
}
|
|
2444
|
-
if (
|
|
2564
|
+
if (/do.?you.?trust.?this.?folder/i.test(stripped) || /how.?would.?you.?like.?to.?authenticate/i.test(stripped) || /waiting.?for.?auth/i.test(stripped) || /allow.?google.?to.?use.?this.?data/i.test(stripped)) {
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
if (/^\s*[>!*]\s+/m.test(stripped) || /\(r:\)/.test(stripped)) {
|
|
2445
2568
|
return true;
|
|
2446
2569
|
}
|
|
2447
|
-
return (
|
|
2448
|
-
|
|
2449
|
-
stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
|
|
2450
|
-
);
|
|
2570
|
+
return stripped.includes("How can I help") || stripped.includes("What would you like") || // Match "gemini> " prompt specifically, not bare ">"
|
|
2571
|
+
/gemini>\s*$/i.test(stripped);
|
|
2451
2572
|
}
|
|
2452
2573
|
parseOutput(output) {
|
|
2453
|
-
const
|
|
2574
|
+
const withoutHookMarkers = this.stripHookMarkers(output);
|
|
2575
|
+
const stripped = this.stripAnsi(withoutHookMarkers);
|
|
2454
2576
|
const isComplete = this.isResponseComplete(stripped);
|
|
2455
2577
|
if (!isComplete) {
|
|
2456
2578
|
return null;
|
|
2457
2579
|
}
|
|
2458
2580
|
const isQuestion = this.containsQuestion(stripped);
|
|
2459
|
-
let content = this.extractContent(stripped, /^.*
|
|
2460
|
-
content = content.replace(
|
|
2581
|
+
let content = this.extractContent(stripped, /^.*(?:gemini|>)\s*/gim);
|
|
2582
|
+
content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
|
|
2461
2583
|
return {
|
|
2462
2584
|
type: isQuestion ? "question" : "response",
|
|
2463
|
-
content
|
|
2585
|
+
content,
|
|
2464
2586
|
isComplete: true,
|
|
2465
2587
|
isQuestion,
|
|
2466
2588
|
metadata: {
|
|
@@ -2469,28 +2591,44 @@ var AiderAdapter = class extends BaseCodingAdapter {
|
|
|
2469
2591
|
};
|
|
2470
2592
|
}
|
|
2471
2593
|
/**
|
|
2472
|
-
* Detect exit conditions specific to
|
|
2473
|
-
* Source:
|
|
2594
|
+
* Detect exit conditions specific to Gemini CLI.
|
|
2595
|
+
* Source: FolderTrustDialog.tsx:127, LogoutConfirmationDialog.tsx:64
|
|
2474
2596
|
*/
|
|
2475
2597
|
detectExit(output) {
|
|
2476
2598
|
const stripped = this.stripAnsi(output);
|
|
2477
|
-
|
|
2478
|
-
|
|
2599
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2600
|
+
if (marker?.event === "SessionEnd") {
|
|
2601
|
+
return {
|
|
2602
|
+
exited: true,
|
|
2603
|
+
code: 0
|
|
2604
|
+
};
|
|
2479
2605
|
}
|
|
2480
|
-
if (/
|
|
2606
|
+
if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
|
|
2481
2607
|
return {
|
|
2482
2608
|
exited: true,
|
|
2483
|
-
code:
|
|
2484
|
-
error: "
|
|
2609
|
+
code: 1,
|
|
2610
|
+
error: "Gemini CLI exited because no folder trust level was selected"
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
if (/you are now logged out/i.test(stripped)) {
|
|
2614
|
+
return {
|
|
2615
|
+
exited: true,
|
|
2616
|
+
code: 0
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
if (/Agent\s+powering\s+down/i.test(stripped)) {
|
|
2620
|
+
return {
|
|
2621
|
+
exited: true,
|
|
2622
|
+
code: 0
|
|
2485
2623
|
};
|
|
2486
2624
|
}
|
|
2487
2625
|
return super.detectExit(output);
|
|
2488
2626
|
}
|
|
2489
2627
|
getPromptPattern() {
|
|
2490
|
-
return /
|
|
2628
|
+
return /gemini>\s*$/i;
|
|
2491
2629
|
}
|
|
2492
2630
|
getHealthCheckCommand() {
|
|
2493
|
-
return "
|
|
2631
|
+
return "gemini --version";
|
|
2494
2632
|
}
|
|
2495
2633
|
};
|
|
2496
2634
|
|
|
@@ -2596,7 +2734,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2596
2734
|
instructions: "Hermes is waiting for clarify input (arrow keys + Enter or free text)."
|
|
2597
2735
|
};
|
|
2598
2736
|
}
|
|
2599
|
-
if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(
|
|
2737
|
+
if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(
|
|
2738
|
+
stripped
|
|
2739
|
+
)) {
|
|
2600
2740
|
return {
|
|
2601
2741
|
detected: true,
|
|
2602
2742
|
type: "tool_wait",
|
|
@@ -2605,7 +2745,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2605
2745
|
instructions: "Hermes terminal tool is waiting for a sudo password or skip."
|
|
2606
2746
|
};
|
|
2607
2747
|
}
|
|
2608
|
-
if (/Dangerous Command|Allow once|Allow for this session|permanent allowlist|\bDeny\b/i.test(
|
|
2748
|
+
if (/Dangerous Command|Allow once|Allow for this session|permanent allowlist|\bDeny\b/i.test(
|
|
2749
|
+
stripped
|
|
2750
|
+
)) {
|
|
2609
2751
|
return {
|
|
2610
2752
|
detected: true,
|
|
2611
2753
|
type: "permission",
|
|
@@ -2619,7 +2761,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2619
2761
|
detectLoading(output) {
|
|
2620
2762
|
const stripped = this.stripAnsi(output);
|
|
2621
2763
|
const tail = stripped.slice(-1200);
|
|
2622
|
-
if (/(?:pondering|contemplating|musing|cogitating|ruminating|deliberating|mulling|reflecting|processing|reasoning|analyzing|computing|synthesizing|formulating|brainstorming)\.\.\.\s*\(\d+\.\d+s\)/i.test(
|
|
2764
|
+
if (/(?:pondering|contemplating|musing|cogitating|ruminating|deliberating|mulling|reflecting|processing|reasoning|analyzing|computing|synthesizing|formulating|brainstorming)\.\.\.\s*\(\d+\.\d+s\)/i.test(
|
|
2765
|
+
tail
|
|
2766
|
+
)) {
|
|
2623
2767
|
return true;
|
|
2624
2768
|
}
|
|
2625
2769
|
if (/\(\d+\.\d+s\)\s*$/.test(tail) && /(?:🔍|📄|💻|⚙️|📖|✍️|🔧|🌐|👆|⌨️|📋|🧠|📚|🎨|🐍|🔀|⚡|💬)/.test(tail)) {
|
|
@@ -2653,7 +2797,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2653
2797
|
if (this.detectLoading(tail)) {
|
|
2654
2798
|
return false;
|
|
2655
2799
|
}
|
|
2656
|
-
if (/Hermes needs your input|Sudo Password Required|Dangerous Command/i.test(
|
|
2800
|
+
if (/Hermes needs your input|Sudo Password Required|Dangerous Command/i.test(
|
|
2801
|
+
tail
|
|
2802
|
+
)) {
|
|
2657
2803
|
return false;
|
|
2658
2804
|
}
|
|
2659
2805
|
if (/(?:⚕|⚠|🔐|\?|✎)\s*❯\s*$/.test(tail)) {
|
|
@@ -2674,7 +2820,10 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2674
2820
|
content = boxMatch[1].trim();
|
|
2675
2821
|
}
|
|
2676
2822
|
if (!content) {
|
|
2677
|
-
content = this.extractContent(
|
|
2823
|
+
content = this.extractContent(
|
|
2824
|
+
stripped,
|
|
2825
|
+
/(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim
|
|
2826
|
+
);
|
|
2678
2827
|
}
|
|
2679
2828
|
return {
|
|
2680
2829
|
type: this.containsQuestion(content) ? "question" : "response",
|
|
@@ -2704,12 +2853,7 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2704
2853
|
// src/pattern-loader.ts
|
|
2705
2854
|
var BASELINE_PATTERNS = {
|
|
2706
2855
|
claude: {
|
|
2707
|
-
ready: [
|
|
2708
|
-
"Claude Code",
|
|
2709
|
-
"How can I help",
|
|
2710
|
-
"What would you like",
|
|
2711
|
-
"Ready"
|
|
2712
|
-
],
|
|
2856
|
+
ready: ["Claude Code", "How can I help", "What would you like", "Ready"],
|
|
2713
2857
|
auth: [
|
|
2714
2858
|
"ANTHROPIC_API_KEY",
|
|
2715
2859
|
"API key not found",
|
|
@@ -2717,17 +2861,9 @@ var BASELINE_PATTERNS = {
|
|
|
2717
2861
|
"Please sign in",
|
|
2718
2862
|
"Invalid API key"
|
|
2719
2863
|
],
|
|
2720
|
-
blocking: [
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
],
|
|
2724
|
-
loading: [
|
|
2725
|
-
"Reading X files\u2026"
|
|
2726
|
-
],
|
|
2727
|
-
turnComplete: [
|
|
2728
|
-
"Cooked for 1m 6s",
|
|
2729
|
-
"<CustomVerb> for 4m 39s"
|
|
2730
|
-
],
|
|
2864
|
+
blocking: ["update available", "[y/n]"],
|
|
2865
|
+
loading: ["Reading X files\u2026"],
|
|
2866
|
+
turnComplete: ["Cooked for 1m 6s", "<CustomVerb> for 4m 39s"],
|
|
2731
2867
|
toolWait: [],
|
|
2732
2868
|
exit: [],
|
|
2733
2869
|
source: "baseline"
|
|
@@ -2747,10 +2883,7 @@ var BASELINE_PATTERNS = {
|
|
|
2747
2883
|
"gcloud auth",
|
|
2748
2884
|
"Application Default Credentials"
|
|
2749
2885
|
],
|
|
2750
|
-
blocking: [
|
|
2751
|
-
"update available",
|
|
2752
|
-
"[y/n]"
|
|
2753
|
-
],
|
|
2886
|
+
blocking: ["update available", "[y/n]"],
|
|
2754
2887
|
loading: [
|
|
2755
2888
|
"<phrase> (esc to cancel, 25s)",
|
|
2756
2889
|
"Waiting for user confirmation...",
|
|
@@ -2759,37 +2892,21 @@ var BASELINE_PATTERNS = {
|
|
|
2759
2892
|
"Warming up the AI hamsters"
|
|
2760
2893
|
],
|
|
2761
2894
|
turnComplete: [],
|
|
2762
|
-
toolWait: [
|
|
2763
|
-
|
|
2764
|
-
],
|
|
2765
|
-
exit: [
|
|
2766
|
-
"Agent powering down. Goodbye!"
|
|
2767
|
-
],
|
|
2895
|
+
toolWait: ["Interactive shell awaiting input... press tab to focus shell"],
|
|
2896
|
+
exit: ["Agent powering down. Goodbye!"],
|
|
2768
2897
|
source: "baseline"
|
|
2769
2898
|
},
|
|
2770
2899
|
codex: {
|
|
2771
|
-
ready: [
|
|
2772
|
-
"Codex",
|
|
2773
|
-
"How can I help",
|
|
2774
|
-
"Ready"
|
|
2775
|
-
],
|
|
2900
|
+
ready: ["Codex", "How can I help", "Ready"],
|
|
2776
2901
|
auth: [
|
|
2777
2902
|
"OPENAI_API_KEY",
|
|
2778
2903
|
"API key not found",
|
|
2779
2904
|
"Unauthorized",
|
|
2780
2905
|
"Invalid API key"
|
|
2781
2906
|
],
|
|
2782
|
-
blocking: [
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
],
|
|
2786
|
-
loading: [
|
|
2787
|
-
"\u2022 Working (0s \u2022 esc to interrupt)",
|
|
2788
|
-
"Booting MCP server: alpha"
|
|
2789
|
-
],
|
|
2790
|
-
turnComplete: [
|
|
2791
|
-
"Worked for 1m 05s"
|
|
2792
|
-
],
|
|
2907
|
+
blocking: ["update available", "[y/n]"],
|
|
2908
|
+
loading: ["\u2022 Working (0s \u2022 esc to interrupt)", "Booting MCP server: alpha"],
|
|
2909
|
+
turnComplete: ["Worked for 1m 05s"],
|
|
2793
2910
|
toolWait: [
|
|
2794
2911
|
"Waiting for background terminal \xB7 <command>",
|
|
2795
2912
|
"Searching the web"
|
|
@@ -2798,39 +2915,21 @@ var BASELINE_PATTERNS = {
|
|
|
2798
2915
|
source: "baseline"
|
|
2799
2916
|
},
|
|
2800
2917
|
aider: {
|
|
2801
|
-
ready: [
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
"Ready"
|
|
2805
|
-
],
|
|
2806
|
-
auth: [
|
|
2807
|
-
"API key",
|
|
2808
|
-
"OPENAI_API_KEY",
|
|
2809
|
-
"ANTHROPIC_API_KEY",
|
|
2810
|
-
"No API key"
|
|
2811
|
-
],
|
|
2812
|
-
blocking: [
|
|
2813
|
-
"(Y)es/(N)o",
|
|
2814
|
-
"[y/n]"
|
|
2815
|
-
],
|
|
2918
|
+
ready: ["Aider", "What would you like", "Ready"],
|
|
2919
|
+
auth: ["API key", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "No API key"],
|
|
2920
|
+
blocking: ["(Y)es/(N)o", "[y/n]"],
|
|
2816
2921
|
loading: [
|
|
2817
2922
|
"Waiting for <model>",
|
|
2818
2923
|
"Waiting for LLM",
|
|
2819
2924
|
"Generating commit message with <model>"
|
|
2820
2925
|
],
|
|
2821
|
-
turnComplete: [
|
|
2822
|
-
"Aider is waiting for your input"
|
|
2823
|
-
],
|
|
2926
|
+
turnComplete: ["Aider is waiting for your input"],
|
|
2824
2927
|
toolWait: [],
|
|
2825
2928
|
exit: [],
|
|
2826
2929
|
source: "baseline"
|
|
2827
2930
|
},
|
|
2828
2931
|
hermes: {
|
|
2829
|
-
ready: [
|
|
2830
|
-
"\u276F",
|
|
2831
|
-
"\u2695 Hermes",
|
|
2832
|
-
"Welcome to Hermes Agent"
|
|
2833
|
-
],
|
|
2932
|
+
ready: ["\u276F", "\u2695 Hermes", "Welcome to Hermes Agent"],
|
|
2834
2933
|
auth: [
|
|
2835
2934
|
"isn't configured yet",
|
|
2836
2935
|
"no API keys or providers found",
|
|
@@ -2841,23 +2940,14 @@ var BASELINE_PATTERNS = {
|
|
|
2841
2940
|
"Sudo Password Required",
|
|
2842
2941
|
"Dangerous Command"
|
|
2843
2942
|
],
|
|
2844
|
-
loading: [
|
|
2845
|
-
|
|
2846
|
-
"(0.0s)",
|
|
2847
|
-
"\u2695 \u276F"
|
|
2848
|
-
],
|
|
2849
|
-
turnComplete: [
|
|
2850
|
-
"\u256D\u2500 \u2695 Hermes",
|
|
2851
|
-
"\u276F"
|
|
2852
|
-
],
|
|
2943
|
+
loading: ["deliberating...", "(0.0s)", "\u2695 \u276F"],
|
|
2944
|
+
turnComplete: ["\u256D\u2500 \u2695 Hermes", "\u276F"],
|
|
2853
2945
|
toolWait: [
|
|
2854
2946
|
"Hermes needs your input",
|
|
2855
2947
|
"Sudo Password Required",
|
|
2856
2948
|
"Dangerous Command"
|
|
2857
2949
|
],
|
|
2858
|
-
exit: [
|
|
2859
|
-
"Goodbye! \u2695"
|
|
2860
|
-
],
|
|
2950
|
+
exit: ["Goodbye! \u2695"],
|
|
2861
2951
|
source: "baseline"
|
|
2862
2952
|
}
|
|
2863
2953
|
};
|
|
@@ -2924,6 +3014,7 @@ async function hasDynamicPatterns(adapter) {
|
|
|
2924
3014
|
}
|
|
2925
3015
|
|
|
2926
3016
|
// src/index.ts
|
|
3017
|
+
var logger = pino({ name: "coding-agent-adapters" });
|
|
2927
3018
|
function createAllAdapters() {
|
|
2928
3019
|
return [
|
|
2929
3020
|
new ClaudeAdapter(),
|
|
@@ -2970,18 +3061,20 @@ async function printMissingAdapters(types) {
|
|
|
2970
3061
|
const results = types ? await checkAdapters(types) : await checkAllAdapters();
|
|
2971
3062
|
const missing = results.filter((r) => !r.installed);
|
|
2972
3063
|
if (missing.length === 0) {
|
|
2973
|
-
|
|
3064
|
+
logger.info("All CLI tools are installed");
|
|
2974
3065
|
return;
|
|
2975
3066
|
}
|
|
2976
|
-
|
|
3067
|
+
logger.info({ count: missing.length }, "Missing CLI tools detected");
|
|
2977
3068
|
for (const m of missing) {
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
3069
|
+
logger.warn(
|
|
3070
|
+
{
|
|
3071
|
+
adapter: m.adapter,
|
|
3072
|
+
installCommand: m.installCommand,
|
|
3073
|
+
docsUrl: m.docsUrl,
|
|
3074
|
+
...m.error ? { error: m.error } : {}
|
|
3075
|
+
},
|
|
3076
|
+
"CLI tool not installed"
|
|
3077
|
+
);
|
|
2985
3078
|
}
|
|
2986
3079
|
}
|
|
2987
3080
|
|