coding-agent-adapters 0.13.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 +1119 -990
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -103
- package/dist/index.d.ts +106 -103
- package/dist/index.js +1106 -981
- 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,145 +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: /
|
|
592
|
-
type: "
|
|
593
|
-
response: "",
|
|
594
|
-
responseType: "
|
|
595
|
-
|
|
596
|
-
description: "Accept trust prompt for working directory",
|
|
662
|
+
pattern: /allow collection of anonymous analytics/i,
|
|
663
|
+
type: "config",
|
|
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: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
|
|
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: "
|
|
680
|
+
pattern: /open a github issue pre-filled/i,
|
|
681
|
+
type: "config",
|
|
613
682
|
response: "n",
|
|
614
683
|
responseType: "text",
|
|
615
|
-
description: "Decline
|
|
684
|
+
description: "Decline automatic bug report",
|
|
616
685
|
safe: true
|
|
617
686
|
},
|
|
618
687
|
{
|
|
619
|
-
pattern: /
|
|
620
|
-
type: "
|
|
688
|
+
pattern: /open documentation url for more info\?/i,
|
|
689
|
+
type: "config",
|
|
621
690
|
response: "n",
|
|
622
691
|
responseType: "text",
|
|
623
|
-
description: "Decline
|
|
692
|
+
description: "Decline opening Aider documentation for model warnings",
|
|
624
693
|
safe: true
|
|
625
694
|
},
|
|
695
|
+
// ── File / edit operations ──────────────────────────────────────────
|
|
626
696
|
{
|
|
627
|
-
pattern: /
|
|
628
|
-
type: "
|
|
629
|
-
response: "
|
|
697
|
+
pattern: /add .+ to the chat\?/i,
|
|
698
|
+
type: "permission",
|
|
699
|
+
response: "y",
|
|
630
700
|
responseType: "text",
|
|
631
|
-
description: "
|
|
701
|
+
description: "Allow Aider to add files to chat context",
|
|
632
702
|
safe: true
|
|
633
703
|
},
|
|
634
704
|
{
|
|
635
|
-
pattern: /
|
|
636
|
-
type: "
|
|
637
|
-
response: "
|
|
705
|
+
pattern: /add url to the chat\?/i,
|
|
706
|
+
type: "permission",
|
|
707
|
+
response: "y",
|
|
638
708
|
responseType: "text",
|
|
639
|
-
description: "
|
|
709
|
+
description: "Allow Aider to add URL content to chat",
|
|
640
710
|
safe: true
|
|
641
711
|
},
|
|
642
712
|
{
|
|
643
|
-
pattern: /
|
|
713
|
+
pattern: /create new file\?/i,
|
|
714
|
+
type: "permission",
|
|
715
|
+
response: "y",
|
|
716
|
+
responseType: "text",
|
|
717
|
+
description: "Allow Aider to create new files",
|
|
718
|
+
safe: true
|
|
719
|
+
},
|
|
720
|
+
{
|
|
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,
|
|
644
764
|
type: "config",
|
|
645
|
-
response: "
|
|
765
|
+
response: "y",
|
|
646
766
|
responseType: "text",
|
|
647
|
-
description: "
|
|
767
|
+
description: "Create git repo for change tracking",
|
|
648
768
|
safe: true,
|
|
649
769
|
once: true
|
|
650
770
|
},
|
|
651
771
|
{
|
|
652
|
-
pattern: /
|
|
772
|
+
pattern: /add .+ to \.gitignore/i,
|
|
653
773
|
type: "config",
|
|
654
774
|
response: "y",
|
|
655
775
|
responseType: "text",
|
|
656
|
-
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",
|
|
657
811
|
safe: true
|
|
658
812
|
}
|
|
659
813
|
];
|
|
660
814
|
getWorkspaceFiles() {
|
|
661
815
|
return [
|
|
662
816
|
{
|
|
663
|
-
relativePath: "
|
|
664
|
-
description: "Project
|
|
817
|
+
relativePath: ".aider.conventions.md",
|
|
818
|
+
description: "Project conventions and instructions read on startup (--read flag)",
|
|
665
819
|
autoLoaded: true,
|
|
666
820
|
type: "memory",
|
|
667
821
|
format: "markdown"
|
|
668
822
|
},
|
|
669
823
|
{
|
|
670
|
-
relativePath: ".
|
|
671
|
-
description: "Project-scoped
|
|
824
|
+
relativePath: ".aider.conf.yml",
|
|
825
|
+
description: "Project-scoped Aider configuration (model, flags, options)",
|
|
672
826
|
autoLoaded: true,
|
|
673
827
|
type: "config",
|
|
674
|
-
format: "
|
|
828
|
+
format: "yaml"
|
|
675
829
|
},
|
|
676
830
|
{
|
|
677
|
-
relativePath: ".
|
|
678
|
-
description: "
|
|
679
|
-
autoLoaded:
|
|
680
|
-
type: "
|
|
681
|
-
format: "
|
|
831
|
+
relativePath: ".aiderignore",
|
|
832
|
+
description: "Gitignore-style file listing paths Aider should not edit",
|
|
833
|
+
autoLoaded: true,
|
|
834
|
+
type: "rules",
|
|
835
|
+
format: "text"
|
|
682
836
|
}
|
|
683
837
|
];
|
|
684
838
|
}
|
|
685
|
-
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
|
+
}
|
|
686
858
|
return {
|
|
687
|
-
powerful: "claude-sonnet-4-20250514",
|
|
688
|
-
fast: "claude-haiku-4-5-20251001"
|
|
859
|
+
powerful: "anthropic/claude-sonnet-4-20250514",
|
|
860
|
+
fast: "anthropic/claude-haiku-4-5-20251001"
|
|
689
861
|
};
|
|
690
862
|
}
|
|
691
863
|
getCommand() {
|
|
692
|
-
return "
|
|
864
|
+
return "aider";
|
|
693
865
|
}
|
|
694
866
|
getArgs(config) {
|
|
695
867
|
const args = [];
|
|
696
|
-
const
|
|
697
|
-
if (!
|
|
698
|
-
args.push("--
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
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");
|
|
702
873
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
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}`);
|
|
707
900
|
}
|
|
708
901
|
const approvalConfig = this.getApprovalConfig(config);
|
|
709
902
|
if (approvalConfig) {
|
|
@@ -713,154 +906,56 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
|
713
906
|
}
|
|
714
907
|
getEnv(config) {
|
|
715
908
|
const env = {};
|
|
716
|
-
const credentials = this.getCredentials(config);
|
|
717
|
-
const adapterConfig = config.adapterConfig;
|
|
718
|
-
if (credentials.anthropicKey) {
|
|
719
|
-
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
720
|
-
}
|
|
721
|
-
if (config.env?.ANTHROPIC_MODEL) {
|
|
722
|
-
env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
|
|
723
|
-
}
|
|
724
909
|
if (!this.isInteractive(config)) {
|
|
725
|
-
env.
|
|
910
|
+
env.NO_COLOR = "1";
|
|
726
911
|
}
|
|
727
|
-
if (
|
|
728
|
-
env.
|
|
729
|
-
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";
|
|
730
914
|
}
|
|
731
915
|
return env;
|
|
732
916
|
}
|
|
733
|
-
getHookTelemetryProtocol(options) {
|
|
734
|
-
if (options?.httpUrl) {
|
|
735
|
-
const httpHookBase = {
|
|
736
|
-
type: "http",
|
|
737
|
-
url: options.httpUrl,
|
|
738
|
-
timeout: 5
|
|
739
|
-
};
|
|
740
|
-
if (options.sessionId) {
|
|
741
|
-
httpHookBase.headers = { "X-Parallax-Session-Id": options.sessionId };
|
|
742
|
-
}
|
|
743
|
-
const hookEntry2 = [{ matcher: "", hooks: [{ ...httpHookBase }] }];
|
|
744
|
-
const hookEntryNoMatcher = [{ hooks: [{ ...httpHookBase }] }];
|
|
745
|
-
const settingsHooks2 = {
|
|
746
|
-
PermissionRequest: hookEntryNoMatcher,
|
|
747
|
-
PreToolUse: hookEntry2,
|
|
748
|
-
Stop: hookEntryNoMatcher,
|
|
749
|
-
Notification: hookEntry2,
|
|
750
|
-
TaskCompleted: hookEntryNoMatcher
|
|
751
|
-
};
|
|
752
|
-
return {
|
|
753
|
-
markerPrefix: "",
|
|
754
|
-
scriptPath: "",
|
|
755
|
-
scriptContent: "",
|
|
756
|
-
settingsHooks: settingsHooks2
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
const markerPrefix = options?.markerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
|
|
760
|
-
const scriptPath = options?.scriptPath || ".claude/hooks/parallax-hook-telemetry.sh";
|
|
761
|
-
const scriptCommand = `"${"$"}CLAUDE_PROJECT_DIR"/${scriptPath}`;
|
|
762
|
-
const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
|
|
763
|
-
const settingsHooks = {
|
|
764
|
-
Notification: hookEntry,
|
|
765
|
-
PreToolUse: hookEntry,
|
|
766
|
-
TaskCompleted: hookEntry,
|
|
767
|
-
SessionEnd: hookEntry
|
|
768
|
-
};
|
|
769
|
-
const scriptContent = `#!/usr/bin/env bash
|
|
770
|
-
set -euo pipefail
|
|
771
|
-
|
|
772
|
-
INPUT="$(cat)"
|
|
773
|
-
[ -z "${"$"}INPUT" ] && exit 0
|
|
774
|
-
|
|
775
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
776
|
-
exit 0
|
|
777
|
-
fi
|
|
778
|
-
|
|
779
|
-
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
|
|
780
|
-
[ -z "${"$"}EVENT" ] && exit 0
|
|
781
|
-
|
|
782
|
-
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notification_type // empty')"
|
|
783
|
-
TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.tool_name // empty')"
|
|
784
|
-
MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
|
|
785
|
-
|
|
786
|
-
printf '%s ' '${markerPrefix}'
|
|
787
|
-
jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION_TYPE" --arg tool_name "${"$"}TOOL_NAME" --arg message "${"$"}MESSAGE" '({event: $event}
|
|
788
|
-
+ (if $notification_type != "" then {notification_type: $notification_type} else {} end)
|
|
789
|
-
+ (if $tool_name != "" then {tool_name: $tool_name} else {} end)
|
|
790
|
-
+ (if $message != "" then {message: $message} else {} end))'
|
|
791
|
-
`;
|
|
792
|
-
return {
|
|
793
|
-
markerPrefix,
|
|
794
|
-
scriptPath,
|
|
795
|
-
scriptContent,
|
|
796
|
-
settingsHooks
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
getHookMarkers(output) {
|
|
800
|
-
const markers = [];
|
|
801
|
-
const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
|
|
802
|
-
let match;
|
|
803
|
-
while ((match = markerRegex.exec(output)) !== null) {
|
|
804
|
-
const markerToken = match[1];
|
|
805
|
-
if (!markerToken.includes("CLAUDE_HOOK")) {
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
const payload = match[2];
|
|
809
|
-
try {
|
|
810
|
-
const parsed = JSON.parse(payload);
|
|
811
|
-
const event = typeof parsed.event === "string" ? parsed.event : void 0;
|
|
812
|
-
if (!event) continue;
|
|
813
|
-
markers.push({
|
|
814
|
-
event,
|
|
815
|
-
notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
|
|
816
|
-
tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
|
|
817
|
-
message: typeof parsed.message === "string" ? parsed.message : void 0
|
|
818
|
-
});
|
|
819
|
-
} catch {
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
return markers;
|
|
823
|
-
}
|
|
824
|
-
getLatestHookMarker(output) {
|
|
825
|
-
const markers = this.getHookMarkers(output);
|
|
826
|
-
return markers.length > 0 ? markers[markers.length - 1] : null;
|
|
827
|
-
}
|
|
828
|
-
stripHookMarkers(output) {
|
|
829
|
-
return output.replace(/(?:^|\n)\s*[A-Z0-9_]*CLAUDE_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
|
|
830
|
-
}
|
|
831
917
|
detectLogin(output) {
|
|
832
918
|
const stripped = this.stripAnsi(output);
|
|
833
|
-
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")) {
|
|
834
920
|
return {
|
|
835
921
|
required: true,
|
|
836
|
-
type: "
|
|
837
|
-
instructions:
|
|
922
|
+
type: "api_key",
|
|
923
|
+
instructions: "Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"
|
|
838
924
|
};
|
|
839
925
|
}
|
|
840
|
-
if (stripped.includes("API key
|
|
926
|
+
if (stripped.includes("Invalid API key") || stripped.includes("Authentication failed") || stripped.includes("Unauthorized")) {
|
|
841
927
|
return {
|
|
842
928
|
required: true,
|
|
843
929
|
type: "api_key",
|
|
844
|
-
instructions: "
|
|
930
|
+
instructions: "API key is invalid - please check your credentials"
|
|
845
931
|
};
|
|
846
932
|
}
|
|
847
|
-
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)) {
|
|
848
943
|
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
849
944
|
return {
|
|
850
945
|
required: true,
|
|
851
946
|
type: "browser",
|
|
852
947
|
url: urlMatch ? urlMatch[0] : void 0,
|
|
853
|
-
instructions: "
|
|
948
|
+
instructions: "Complete OpenRouter authentication in browser"
|
|
854
949
|
};
|
|
855
950
|
}
|
|
856
951
|
return { required: false };
|
|
857
952
|
}
|
|
858
953
|
/**
|
|
859
|
-
* Detect blocking prompts specific to
|
|
954
|
+
* Detect blocking prompts specific to Aider CLI.
|
|
955
|
+
* Source: io.py, onboarding.py, base_coder.py, report.py
|
|
860
956
|
*/
|
|
861
957
|
detectBlockingPrompt(output) {
|
|
862
958
|
const stripped = this.stripAnsi(output);
|
|
863
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
864
959
|
const loginDetection = this.detectLogin(output);
|
|
865
960
|
if (loginDetection.required) {
|
|
866
961
|
return {
|
|
@@ -872,241 +967,116 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
|
|
|
872
967
|
instructions: loginDetection.instructions
|
|
873
968
|
};
|
|
874
969
|
}
|
|
875
|
-
if (
|
|
876
|
-
if (marker.notification_type === "permission_prompt") {
|
|
877
|
-
return {
|
|
878
|
-
detected: true,
|
|
879
|
-
type: "permission",
|
|
880
|
-
prompt: marker.message || "Claude permission prompt",
|
|
881
|
-
suggestedResponse: "keys:enter",
|
|
882
|
-
canAutoRespond: true,
|
|
883
|
-
instructions: "Claude is waiting for permission approval"
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
if (marker.notification_type === "elicitation_dialog") {
|
|
887
|
-
return {
|
|
888
|
-
detected: true,
|
|
889
|
-
type: "tool_wait",
|
|
890
|
-
prompt: marker.message || "Claude elicitation dialog",
|
|
891
|
-
canAutoRespond: false,
|
|
892
|
-
instructions: "Claude is waiting for required user input"
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
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)) {
|
|
897
|
-
return {
|
|
898
|
-
detected: true,
|
|
899
|
-
type: "config",
|
|
900
|
-
prompt: "Claude session survey",
|
|
901
|
-
options: ["1", "2", "3", "0"],
|
|
902
|
-
suggestedResponse: "0",
|
|
903
|
-
canAutoRespond: true,
|
|
904
|
-
instructions: "Optional survey prompt; reply 0 to dismiss"
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
if (/enter\/tab\/space to toggle.*esc to cancel|enter to confirm.*esc to cancel|esc to close/i.test(stripped)) {
|
|
908
|
-
return {
|
|
909
|
-
detected: true,
|
|
910
|
-
type: "config",
|
|
911
|
-
prompt: "Claude dialog awaiting navigation",
|
|
912
|
-
options: ["keys:enter", "keys:esc", "keys:down,enter"],
|
|
913
|
-
suggestedResponse: "keys:esc",
|
|
914
|
-
canAutoRespond: false,
|
|
915
|
-
instructions: "Use Enter/Esc or arrow keys to navigate this dialog"
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
if (/press .* to navigate .* enter .* esc|use (?:arrow|↑↓) keys|enter to select|esc to (?:go back|close|cancel)/i.test(stripped) || /(?:^|\n)\s*(?:❯|>)\s*\/(?:agents|chrome|config|tasks|skills|remote-env)\b/im.test(stripped)) {
|
|
919
|
-
return {
|
|
920
|
-
detected: true,
|
|
921
|
-
type: "config",
|
|
922
|
-
prompt: "Claude menu navigation required",
|
|
923
|
-
options: ["keys:esc", "keys:enter", "keys:down,enter"],
|
|
924
|
-
suggestedResponse: "keys:esc",
|
|
925
|
-
canAutoRespond: false,
|
|
926
|
-
instructions: "Claude is showing an interactive menu; use arrow keys + Enter or Esc"
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
if (/Do you want to|wants? (your )?permission|needs your permission/i.test(stripped)) {
|
|
930
|
-
return {
|
|
931
|
-
detected: true,
|
|
932
|
-
type: "permission",
|
|
933
|
-
prompt: "Claude tool permission",
|
|
934
|
-
suggestedResponse: "keys:enter",
|
|
935
|
-
canAutoRespond: true,
|
|
936
|
-
instructions: "Claude is asking permission to use a tool"
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
if (/choose.*model|select.*model|available models/i.test(stripped) && /\d+\)|claude-/i.test(stripped)) {
|
|
970
|
+
if (/select.*model|choose.*model|which model/i.test(stripped)) {
|
|
940
971
|
return {
|
|
941
972
|
detected: true,
|
|
942
973
|
type: "model_select",
|
|
943
|
-
prompt: "
|
|
944
|
-
canAutoRespond: false,
|
|
945
|
-
instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
|
|
949
|
-
return {
|
|
950
|
-
detected: true,
|
|
951
|
-
type: "config",
|
|
952
|
-
prompt: "API tier selection",
|
|
974
|
+
prompt: "Model selection required",
|
|
953
975
|
canAutoRespond: false,
|
|
954
|
-
instructions: "Please select
|
|
976
|
+
instructions: "Please select a model or set AIDER_MODEL env var"
|
|
955
977
|
};
|
|
956
978
|
}
|
|
957
|
-
if (/
|
|
979
|
+
if (/please answer with one of:/i.test(stripped)) {
|
|
958
980
|
return {
|
|
959
981
|
detected: true,
|
|
960
|
-
type: "
|
|
961
|
-
prompt: "
|
|
982
|
+
type: "unknown",
|
|
983
|
+
prompt: "Invalid confirmation input",
|
|
962
984
|
canAutoRespond: false,
|
|
963
|
-
instructions: "
|
|
985
|
+
instructions: "Aider received an invalid response to a confirmation prompt"
|
|
964
986
|
};
|
|
965
987
|
}
|
|
966
|
-
if (/
|
|
988
|
+
if (/delete|remove|overwrite/i.test(stripped) && (/\[y\/n\]/i.test(stripped) || /\(Y\)es\/\(N\)o/i.test(stripped))) {
|
|
967
989
|
return {
|
|
968
990
|
detected: true,
|
|
969
991
|
type: "permission",
|
|
970
|
-
prompt: "
|
|
992
|
+
prompt: "Destructive operation confirmation",
|
|
971
993
|
options: ["y", "n"],
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
instructions: "Claude Code requesting file access permission"
|
|
994
|
+
canAutoRespond: false,
|
|
995
|
+
instructions: "Aider is asking to perform a potentially destructive operation"
|
|
975
996
|
};
|
|
976
997
|
}
|
|
977
|
-
if (this.detectReady(output) || this.detectTaskComplete(output)) {
|
|
978
|
-
return { detected: false };
|
|
979
|
-
}
|
|
980
|
-
if (/❯/.test(stripped.slice(-300))) {
|
|
981
|
-
return { detected: false };
|
|
982
|
-
}
|
|
983
998
|
return super.detectBlockingPrompt(output);
|
|
984
999
|
}
|
|
985
1000
|
/**
|
|
986
|
-
* Detect if
|
|
1001
|
+
* Detect if Aider is actively loading/processing.
|
|
987
1002
|
*
|
|
988
1003
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
989
|
-
* -
|
|
990
|
-
* -
|
|
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 ..."
|
|
991
1007
|
*/
|
|
992
1008
|
detectLoading(output) {
|
|
993
1009
|
const stripped = this.stripAnsi(output);
|
|
994
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
995
1010
|
const tail = stripped.slice(-500);
|
|
996
|
-
if (
|
|
1011
|
+
if (/Waiting\s+for\s+(?:LLM|[A-Za-z0-9_./:@-]+)/i.test(tail)) {
|
|
997
1012
|
return true;
|
|
998
1013
|
}
|
|
999
|
-
if (/
|
|
1000
|
-
return true;
|
|
1001
|
-
}
|
|
1002
|
-
if (/Reading\s+\d+\s+files/i.test(tail)) {
|
|
1014
|
+
if (/Generating\s+commit\s+message\s+with\s+/i.test(tail)) {
|
|
1003
1015
|
return true;
|
|
1004
1016
|
}
|
|
1005
1017
|
return false;
|
|
1006
1018
|
}
|
|
1007
1019
|
/**
|
|
1008
|
-
* Detect
|
|
1009
|
-
*
|
|
1010
|
-
* Claude Code can launch external tools (browser, bash, Node, Python, etc.)
|
|
1011
|
-
* that show status lines like "Claude in Chrome[javascript_tool]" or
|
|
1012
|
-
* "[bash_tool]", "[python_tool]", etc.
|
|
1013
|
-
*
|
|
1014
|
-
* When detected, stall detection is suppressed and the UI can display
|
|
1015
|
-
* which tool is active.
|
|
1016
|
-
*/
|
|
1017
|
-
detectToolRunning(output) {
|
|
1018
|
-
const stripped = this.stripAnsi(output);
|
|
1019
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
1020
|
-
const tail = stripped.slice(-500);
|
|
1021
|
-
if (marker?.event === "PreToolUse" && marker.tool_name) {
|
|
1022
|
-
return {
|
|
1023
|
-
toolName: marker.tool_name.toLowerCase(),
|
|
1024
|
-
description: `${marker.tool_name} (hook)`
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
const contextualMatch = tail.match(/Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i);
|
|
1028
|
-
if (contextualMatch) {
|
|
1029
|
-
const appName = contextualMatch[1];
|
|
1030
|
-
const toolType = contextualMatch[2].toLowerCase();
|
|
1031
|
-
const friendlyName = toolType.replace(/_tool$/i, "");
|
|
1032
|
-
return { toolName: friendlyName, description: `${appName} (${toolType})` };
|
|
1033
|
-
}
|
|
1034
|
-
const toolMatch = tail.match(/\[(\w+_tool)\]/i);
|
|
1035
|
-
if (toolMatch) {
|
|
1036
|
-
const toolType = toolMatch[1].toLowerCase();
|
|
1037
|
-
const friendlyName = toolType.replace(/_tool$/i, "");
|
|
1038
|
-
return { toolName: friendlyName, description: toolType };
|
|
1039
|
-
}
|
|
1040
|
-
return null;
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Detect task completion for Claude Code.
|
|
1020
|
+
* Detect task completion for Aider.
|
|
1044
1021
|
*
|
|
1045
|
-
* High-confidence
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1022
|
+
* High-confidence patterns:
|
|
1023
|
+
* - "Aider is waiting for your input" notification (bell message)
|
|
1024
|
+
* - Edit-format mode prompts (ask>, code>, architect>) after output
|
|
1048
1025
|
*
|
|
1049
1026
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
1050
|
-
* -
|
|
1051
|
-
* - claude_completed_turn_duration_custom_verb
|
|
1027
|
+
* - aider_completed_llm_response_ready
|
|
1052
1028
|
*/
|
|
1053
1029
|
detectTaskComplete(output) {
|
|
1054
1030
|
const stripped = this.stripAnsi(output);
|
|
1055
|
-
|
|
1056
|
-
if (!stripped.trim()) return false;
|
|
1057
|
-
if (marker?.event === "TaskCompleted") {
|
|
1031
|
+
if (/Aider\s+is\s+waiting\s+for\s+your\s+input/.test(stripped)) {
|
|
1058
1032
|
return true;
|
|
1059
1033
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
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
|
+
}
|
|
1062
1043
|
}
|
|
1063
|
-
|
|
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)) {
|
|
1064
1049
|
return false;
|
|
1065
1050
|
}
|
|
1066
|
-
|
|
1067
|
-
const tail = stripped.slice(-300);
|
|
1068
|
-
const hasIdlePrompt = /❯/.test(tail);
|
|
1069
|
-
if (hasDuration && hasIdlePrompt) {
|
|
1051
|
+
if (/(?:ask|code|architect|help)(?:\s+multi)?>\s*$/m.test(stripped) || /^multi>\s*$/m.test(stripped)) {
|
|
1070
1052
|
return true;
|
|
1071
1053
|
}
|
|
1072
|
-
if (
|
|
1054
|
+
if (/^Aider v\d+/m.test(stripped)) {
|
|
1073
1055
|
return true;
|
|
1074
1056
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
detectReady(output) {
|
|
1078
|
-
const stripped = this.stripAnsi(output);
|
|
1079
|
-
const marker = this.getLatestHookMarker(stripped);
|
|
1080
|
-
if (!stripped.trim()) return false;
|
|
1081
|
-
if (marker?.event === "Notification") {
|
|
1082
|
-
if (marker.notification_type === "permission_prompt" || marker.notification_type === "elicitation_dialog") {
|
|
1083
|
-
return false;
|
|
1084
|
-
}
|
|
1085
|
-
if (marker.notification_type === "idle_prompt") {
|
|
1086
|
-
return true;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
|
|
1090
|
-
return false;
|
|
1057
|
+
if (/^(?:Readonly|Editable):/m.test(stripped)) {
|
|
1058
|
+
return true;
|
|
1091
1059
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint;
|
|
1060
|
+
return (
|
|
1061
|
+
// Legacy prompt patterns
|
|
1062
|
+
stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
|
|
1063
|
+
);
|
|
1097
1064
|
}
|
|
1098
1065
|
parseOutput(output) {
|
|
1099
|
-
const
|
|
1100
|
-
const stripped = this.stripAnsi(withoutHookMarkers);
|
|
1066
|
+
const stripped = this.stripAnsi(output);
|
|
1101
1067
|
const isComplete = this.isResponseComplete(stripped);
|
|
1102
1068
|
if (!isComplete) {
|
|
1103
1069
|
return null;
|
|
1104
1070
|
}
|
|
1105
1071
|
const isQuestion = this.containsQuestion(stripped);
|
|
1106
|
-
|
|
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
|
+
);
|
|
1107
1077
|
return {
|
|
1108
1078
|
type: isQuestion ? "question" : "response",
|
|
1109
|
-
content,
|
|
1079
|
+
content: content.trim(),
|
|
1110
1080
|
isComplete: true,
|
|
1111
1081
|
isQuestion,
|
|
1112
1082
|
metadata: {
|
|
@@ -1114,90 +1084,154 @@ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION
|
|
|
1114
1084
|
}
|
|
1115
1085
|
};
|
|
1116
1086
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
return "claude --version";
|
|
1122
|
-
}
|
|
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
|
+
*/
|
|
1123
1091
|
detectExit(output) {
|
|
1124
1092
|
const stripped = this.stripAnsi(output);
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
+
};
|
|
1128
1102
|
}
|
|
1129
1103
|
return super.detectExit(output);
|
|
1130
1104
|
}
|
|
1105
|
+
getPromptPattern() {
|
|
1106
|
+
return /(?:ask|code|architect|help|aider|multi)(?:\s+multi)?>\s*$/i;
|
|
1107
|
+
}
|
|
1108
|
+
getHealthCheckCommand() {
|
|
1109
|
+
return "aider --version";
|
|
1110
|
+
}
|
|
1131
1111
|
};
|
|
1132
1112
|
|
|
1133
|
-
// src/
|
|
1134
|
-
var
|
|
1135
|
-
var
|
|
1136
|
-
adapterType = "
|
|
1137
|
-
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;
|
|
1138
1121
|
installation = {
|
|
1139
|
-
command: "npm install -g @
|
|
1122
|
+
command: "npm install -g @anthropic-ai/claude-code",
|
|
1140
1123
|
alternatives: [
|
|
1141
|
-
"
|
|
1124
|
+
"npx @anthropic-ai/claude-code (run without installing)",
|
|
1125
|
+
"brew install claude-code (macOS with Homebrew)"
|
|
1142
1126
|
],
|
|
1143
|
-
docsUrl: "https://
|
|
1127
|
+
docsUrl: "https://docs.anthropic.com/en/docs/claude-code",
|
|
1128
|
+
minVersion: "1.0.0"
|
|
1144
1129
|
};
|
|
1145
1130
|
/**
|
|
1146
|
-
* Auto-response rules for
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
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.
|
|
1149
1134
|
*/
|
|
1150
1135
|
autoResponseRules = [
|
|
1151
1136
|
{
|
|
1152
|
-
pattern: /
|
|
1153
|
-
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",
|
|
1154
1139
|
response: "",
|
|
1155
1140
|
responseType: "keys",
|
|
1156
1141
|
keys: ["enter"],
|
|
1157
|
-
description: "
|
|
1142
|
+
description: "Accept Claude first-run theme/style prompt",
|
|
1158
1143
|
safe: true,
|
|
1159
1144
|
once: true
|
|
1160
1145
|
},
|
|
1161
1146
|
{
|
|
1162
|
-
pattern: /trust.?
|
|
1147
|
+
pattern: /trust.*(?:folder|directory)|safety.?check|project.you.created|(?:Yes|Allow).*(?:No|Deny).*(?:Enter|Return)/i,
|
|
1163
1148
|
type: "permission",
|
|
1164
1149
|
response: "",
|
|
1165
1150
|
responseType: "keys",
|
|
1166
1151
|
keys: ["enter"],
|
|
1167
|
-
description: "
|
|
1152
|
+
description: "Accept trust prompt for working directory",
|
|
1168
1153
|
safe: true,
|
|
1169
1154
|
once: true
|
|
1170
1155
|
},
|
|
1171
1156
|
{
|
|
1172
|
-
pattern: /allow
|
|
1173
|
-
type: "
|
|
1157
|
+
pattern: /wants? (?:your )?permission|needs your permission|(?:Allow|Approve)\s[\s\S]{0,50}(?:Deny|Don't allow)/i,
|
|
1158
|
+
type: "permission",
|
|
1174
1159
|
response: "",
|
|
1175
1160
|
responseType: "keys",
|
|
1176
|
-
keys: ["
|
|
1177
|
-
description:
|
|
1161
|
+
keys: ["enter"],
|
|
1162
|
+
description: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
|
|
1163
|
+
safe: true,
|
|
1164
|
+
once: true
|
|
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",
|
|
1178
1204
|
safe: true,
|
|
1179
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
|
|
1180
1214
|
}
|
|
1181
1215
|
];
|
|
1182
1216
|
getWorkspaceFiles() {
|
|
1183
1217
|
return [
|
|
1184
1218
|
{
|
|
1185
|
-
relativePath: "
|
|
1219
|
+
relativePath: "CLAUDE.md",
|
|
1186
1220
|
description: "Project-level instructions read automatically on startup",
|
|
1187
1221
|
autoLoaded: true,
|
|
1188
1222
|
type: "memory",
|
|
1189
1223
|
format: "markdown"
|
|
1190
1224
|
},
|
|
1191
1225
|
{
|
|
1192
|
-
relativePath: ".
|
|
1193
|
-
description: "Project-scoped settings (
|
|
1226
|
+
relativePath: ".claude/settings.json",
|
|
1227
|
+
description: "Project-scoped settings (allowed tools, permissions)",
|
|
1194
1228
|
autoLoaded: true,
|
|
1195
1229
|
type: "config",
|
|
1196
1230
|
format: "json"
|
|
1197
1231
|
},
|
|
1198
1232
|
{
|
|
1199
|
-
relativePath: ".
|
|
1200
|
-
description: "Custom
|
|
1233
|
+
relativePath: ".claude/commands",
|
|
1234
|
+
description: "Custom slash commands directory",
|
|
1201
1235
|
autoLoaded: false,
|
|
1202
1236
|
type: "config",
|
|
1203
1237
|
format: "markdown"
|
|
@@ -1206,22 +1240,27 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1206
1240
|
}
|
|
1207
1241
|
getRecommendedModels(_credentials) {
|
|
1208
1242
|
return {
|
|
1209
|
-
powerful: "
|
|
1210
|
-
fast: "
|
|
1243
|
+
powerful: "claude-sonnet-4-20250514",
|
|
1244
|
+
fast: "claude-haiku-4-5-20251001"
|
|
1211
1245
|
};
|
|
1212
1246
|
}
|
|
1213
1247
|
getCommand() {
|
|
1214
|
-
return "
|
|
1248
|
+
return "claude";
|
|
1215
1249
|
}
|
|
1216
1250
|
getArgs(config) {
|
|
1217
1251
|
const args = [];
|
|
1252
|
+
const adapterConfig = config.adapterConfig;
|
|
1218
1253
|
if (!this.isInteractive(config)) {
|
|
1219
|
-
args.push("--
|
|
1220
|
-
args.push("--output-format", "text");
|
|
1254
|
+
args.push("--print");
|
|
1221
1255
|
if (config.workdir) {
|
|
1222
1256
|
args.push("--cwd", config.workdir);
|
|
1223
1257
|
}
|
|
1224
1258
|
}
|
|
1259
|
+
if (adapterConfig?.resume) {
|
|
1260
|
+
args.push("--resume", adapterConfig.resume);
|
|
1261
|
+
} else if (adapterConfig?.continue) {
|
|
1262
|
+
args.push("--continue");
|
|
1263
|
+
}
|
|
1225
1264
|
const approvalConfig = this.getApprovalConfig(config);
|
|
1226
1265
|
if (approvalConfig) {
|
|
1227
1266
|
args.push(...approvalConfig.cliFlags);
|
|
@@ -1232,34 +1271,39 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1232
1271
|
const env = {};
|
|
1233
1272
|
const credentials = this.getCredentials(config);
|
|
1234
1273
|
const adapterConfig = config.adapterConfig;
|
|
1235
|
-
if (credentials.
|
|
1236
|
-
env.
|
|
1237
|
-
env.GEMINI_API_KEY = credentials.googleKey;
|
|
1274
|
+
if (credentials.anthropicKey) {
|
|
1275
|
+
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
1238
1276
|
}
|
|
1239
|
-
if (config.env?.
|
|
1240
|
-
env.
|
|
1277
|
+
if (config.env?.ANTHROPIC_MODEL) {
|
|
1278
|
+
env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
|
|
1241
1279
|
}
|
|
1242
1280
|
if (!this.isInteractive(config)) {
|
|
1243
|
-
env.
|
|
1281
|
+
env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
|
|
1244
1282
|
}
|
|
1245
|
-
if (adapterConfig?.
|
|
1246
|
-
env.
|
|
1247
|
-
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;
|
|
1248
1286
|
}
|
|
1249
1287
|
return env;
|
|
1250
1288
|
}
|
|
1251
1289
|
getHookTelemetryProtocol(options) {
|
|
1252
1290
|
if (options?.httpUrl) {
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
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 }] }];
|
|
1257
1301
|
const settingsHooks2 = {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1302
|
+
PermissionRequest: hookEntryNoMatcher,
|
|
1303
|
+
PreToolUse: hookEntry2,
|
|
1304
|
+
Stop: hookEntryNoMatcher,
|
|
1305
|
+
Notification: hookEntry2,
|
|
1306
|
+
TaskCompleted: hookEntryNoMatcher
|
|
1263
1307
|
};
|
|
1264
1308
|
return {
|
|
1265
1309
|
markerPrefix: "",
|
|
@@ -1268,14 +1312,16 @@ var GeminiAdapter = class extends BaseCodingAdapter {
|
|
|
1268
1312
|
settingsHooks: settingsHooks2
|
|
1269
1313
|
};
|
|
1270
1314
|
}
|
|
1271
|
-
const markerPrefix = options?.markerPrefix ||
|
|
1272
|
-
const scriptPath = options?.scriptPath || ".
|
|
1273
|
-
const scriptCommand = `"${"$"}
|
|
1274
|
-
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
|
+
];
|
|
1275
1321
|
const settingsHooks = {
|
|
1276
1322
|
Notification: hookEntry,
|
|
1277
|
-
|
|
1278
|
-
|
|
1323
|
+
PreToolUse: hookEntry,
|
|
1324
|
+
TaskCompleted: hookEntry,
|
|
1279
1325
|
SessionEnd: hookEntry
|
|
1280
1326
|
};
|
|
1281
1327
|
const scriptContent = `#!/usr/bin/env bash
|
|
@@ -1285,32 +1331,21 @@ INPUT="$(cat)"
|
|
|
1285
1331
|
[ -z "${"$"}INPUT" ] && exit 0
|
|
1286
1332
|
|
|
1287
1333
|
if ! command -v jq >/dev/null 2>&1; then
|
|
1288
|
-
# Valid no-op response
|
|
1289
|
-
printf '%s
|
|
1290
|
-
' '{"continue":true}'
|
|
1291
1334
|
exit 0
|
|
1292
1335
|
fi
|
|
1293
1336
|
|
|
1294
|
-
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.
|
|
1295
|
-
[ -z "${"$"}EVENT" ] &&
|
|
1296
|
-
' '{"continue":true}'; exit 0; }
|
|
1337
|
+
EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
|
|
1338
|
+
[ -z "${"$"}EVENT" ] && exit 0
|
|
1297
1339
|
|
|
1298
|
-
NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.
|
|
1299
|
-
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')"
|
|
1300
1342
|
MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
|
|
1301
1343
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
--arg notification_type "${"$"}NOTIFICATION_TYPE" \\
|
|
1305
|
-
--arg tool_name "${"$"}TOOL_NAME" \\
|
|
1306
|
-
--arg message "${"$"}MESSAGE" \\
|
|
1307
|
-
'({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}
|
|
1308
1346
|
+ (if $notification_type != "" then {notification_type: $notification_type} else {} end)
|
|
1309
1347
|
+ (if $tool_name != "" then {tool_name: $tool_name} else {} end)
|
|
1310
|
-
+ (if $message != "" then {message: $message} else {} end))'
|
|
1311
|
-
|
|
1312
|
-
MARKER="${markerPrefix} ${"$"}PAYLOAD"
|
|
1313
|
-
jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
|
|
1348
|
+
+ (if $message != "" then {message: $message} else {} end))'
|
|
1314
1349
|
`;
|
|
1315
1350
|
return {
|
|
1316
1351
|
markerPrefix,
|
|
@@ -1325,7 +1360,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1325
1360
|
let match;
|
|
1326
1361
|
while ((match = markerRegex.exec(output)) !== null) {
|
|
1327
1362
|
const markerToken = match[1];
|
|
1328
|
-
if (!markerToken.includes("
|
|
1363
|
+
if (!markerToken.includes("CLAUDE_HOOK")) {
|
|
1329
1364
|
continue;
|
|
1330
1365
|
}
|
|
1331
1366
|
const payload = match[2];
|
|
@@ -1349,208 +1384,288 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1349
1384
|
return markers.length > 0 ? markers[markers.length - 1] : null;
|
|
1350
1385
|
}
|
|
1351
1386
|
stripHookMarkers(output) {
|
|
1352
|
-
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
|
+
);
|
|
1353
1391
|
}
|
|
1354
1392
|
detectLogin(output) {
|
|
1355
1393
|
const stripped = this.stripAnsi(output);
|
|
1356
|
-
if (stripped.includes("
|
|
1394
|
+
if (stripped.includes("Not logged in") || stripped.includes("Please run /login") || stripped.includes("please log in") || stripped.includes("run /login")) {
|
|
1357
1395
|
return {
|
|
1358
1396
|
required: true,
|
|
1359
|
-
type: "
|
|
1360
|
-
instructions:
|
|
1397
|
+
type: "cli_auth",
|
|
1398
|
+
instructions: 'Claude Code requires authentication. Run "claude login" in your terminal.'
|
|
1361
1399
|
};
|
|
1362
1400
|
}
|
|
1363
|
-
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")) {
|
|
1364
1402
|
return {
|
|
1365
1403
|
required: true,
|
|
1366
1404
|
type: "api_key",
|
|
1367
|
-
instructions: "
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
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)) {
|
|
1371
|
-
return {
|
|
1372
|
-
required: true,
|
|
1373
|
-
type: "oauth",
|
|
1374
|
-
instructions: "Gemini CLI authentication required \u2014 select an auth method"
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
if (/waiting.?for.?auth/i.test(stripped)) {
|
|
1378
|
-
return {
|
|
1379
|
-
required: true,
|
|
1380
|
-
type: "oauth",
|
|
1381
|
-
instructions: "Waiting for browser authentication to complete"
|
|
1405
|
+
instructions: "Set ANTHROPIC_API_KEY environment variable or provide credentials in adapterConfig"
|
|
1382
1406
|
};
|
|
1383
1407
|
}
|
|
1384
|
-
if (stripped.includes("
|
|
1408
|
+
if (stripped.includes("Open this URL") || stripped.includes("browser to authenticate")) {
|
|
1385
1409
|
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
1386
|
-
return {
|
|
1387
|
-
required: true,
|
|
1388
|
-
type: "oauth",
|
|
1389
|
-
url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
|
|
1390
|
-
instructions: "Google OAuth authentication required"
|
|
1391
|
-
};
|
|
1392
|
-
}
|
|
1393
|
-
if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
|
|
1394
1410
|
return {
|
|
1395
1411
|
required: true,
|
|
1396
1412
|
type: "browser",
|
|
1397
|
-
|
|
1413
|
+
url: urlMatch ? urlMatch[0] : void 0,
|
|
1414
|
+
instructions: "Browser authentication required"
|
|
1398
1415
|
};
|
|
1399
1416
|
}
|
|
1400
1417
|
return { required: false };
|
|
1401
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Detect blocking prompts specific to Claude Code CLI
|
|
1421
|
+
*/
|
|
1402
1422
|
detectBlockingPrompt(output) {
|
|
1403
1423
|
const stripped = this.stripAnsi(output);
|
|
1404
1424
|
const marker = this.getLatestHookMarker(stripped);
|
|
1405
|
-
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) {
|
|
1406
1434
|
return {
|
|
1407
1435
|
detected: true,
|
|
1408
|
-
type: "
|
|
1409
|
-
prompt:
|
|
1410
|
-
|
|
1411
|
-
canAutoRespond:
|
|
1412
|
-
instructions:
|
|
1436
|
+
type: "login",
|
|
1437
|
+
prompt: loginDetection.instructions,
|
|
1438
|
+
url: loginDetection.url,
|
|
1439
|
+
canAutoRespond: false,
|
|
1440
|
+
instructions: loginDetection.instructions
|
|
1413
1441
|
};
|
|
1414
1442
|
}
|
|
1415
|
-
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)) {
|
|
1416
1465
|
return {
|
|
1417
1466
|
detected: true,
|
|
1418
1467
|
type: "permission",
|
|
1419
|
-
prompt: "
|
|
1420
|
-
|
|
1468
|
+
prompt: "Bypass Permissions confirmation",
|
|
1469
|
+
options: ["1", "2"],
|
|
1470
|
+
suggestedResponse: "2",
|
|
1421
1471
|
canAutoRespond: true,
|
|
1422
|
-
instructions: "
|
|
1472
|
+
instructions: "Claude is asking to confirm bypass permissions mode; reply 2 to accept"
|
|
1423
1473
|
};
|
|
1424
1474
|
}
|
|
1425
|
-
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
|
+
)) {
|
|
1426
1478
|
return {
|
|
1427
1479
|
detected: true,
|
|
1428
|
-
type: "
|
|
1429
|
-
prompt: "
|
|
1430
|
-
|
|
1431
|
-
|
|
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"
|
|
1432
1486
|
};
|
|
1433
1487
|
}
|
|
1434
|
-
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
|
+
)) {
|
|
1435
1491
|
return {
|
|
1436
1492
|
detected: true,
|
|
1437
|
-
type: "
|
|
1438
|
-
prompt: "
|
|
1493
|
+
type: "config",
|
|
1494
|
+
prompt: "Claude dialog awaiting navigation",
|
|
1495
|
+
options: ["keys:enter", "keys:esc", "keys:down,enter"],
|
|
1496
|
+
suggestedResponse: "keys:esc",
|
|
1439
1497
|
canAutoRespond: false,
|
|
1440
|
-
instructions: "
|
|
1498
|
+
instructions: "Use Enter/Esc or arrow keys to navigate this dialog"
|
|
1441
1499
|
};
|
|
1442
1500
|
}
|
|
1443
|
-
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
|
+
)) {
|
|
1444
1506
|
return {
|
|
1445
1507
|
detected: true,
|
|
1446
1508
|
type: "config",
|
|
1447
|
-
prompt: "
|
|
1509
|
+
prompt: "Claude menu navigation required",
|
|
1510
|
+
options: ["keys:esc", "keys:enter", "keys:down,enter"],
|
|
1511
|
+
suggestedResponse: "keys:esc",
|
|
1448
1512
|
canAutoRespond: false,
|
|
1449
|
-
instructions:
|
|
1513
|
+
instructions: "Claude is showing an interactive menu; use arrow keys + Enter or Esc"
|
|
1450
1514
|
};
|
|
1451
1515
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1516
|
+
if (/Do you want to|wants? (your )?permission|needs your permission/i.test(
|
|
1517
|
+
stripped
|
|
1518
|
+
)) {
|
|
1454
1519
|
return {
|
|
1455
1520
|
detected: true,
|
|
1456
|
-
type: "
|
|
1457
|
-
prompt:
|
|
1458
|
-
|
|
1459
|
-
canAutoRespond:
|
|
1460
|
-
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"
|
|
1461
1526
|
};
|
|
1462
1527
|
}
|
|
1463
|
-
if (/
|
|
1528
|
+
if (/choose.*model|select.*model|available models/i.test(stripped) && /\d+\)|claude-/i.test(stripped)) {
|
|
1464
1529
|
return {
|
|
1465
1530
|
detected: true,
|
|
1466
|
-
type: "
|
|
1467
|
-
prompt: "
|
|
1531
|
+
type: "model_select",
|
|
1532
|
+
prompt: "Claude model selection",
|
|
1468
1533
|
canAutoRespond: false,
|
|
1469
|
-
instructions: "
|
|
1534
|
+
instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
|
|
1470
1535
|
};
|
|
1471
1536
|
}
|
|
1472
|
-
if (/select.*
|
|
1537
|
+
if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
|
|
1473
1538
|
return {
|
|
1474
1539
|
detected: true,
|
|
1475
|
-
type: "
|
|
1476
|
-
prompt: "
|
|
1540
|
+
type: "config",
|
|
1541
|
+
prompt: "API tier selection",
|
|
1477
1542
|
canAutoRespond: false,
|
|
1478
|
-
instructions: "Please select
|
|
1543
|
+
instructions: "Please select an API tier"
|
|
1479
1544
|
};
|
|
1480
1545
|
}
|
|
1481
|
-
if (/
|
|
1546
|
+
if (/welcome to claude|first time setup|initial configuration/i.test(stripped)) {
|
|
1482
1547
|
return {
|
|
1483
1548
|
detected: true,
|
|
1484
|
-
type: "
|
|
1485
|
-
prompt: "
|
|
1549
|
+
type: "config",
|
|
1550
|
+
prompt: "First-time setup",
|
|
1486
1551
|
canAutoRespond: false,
|
|
1487
|
-
instructions: "
|
|
1552
|
+
instructions: "Claude Code requires initial configuration"
|
|
1488
1553
|
};
|
|
1489
1554
|
}
|
|
1490
|
-
if (/
|
|
1555
|
+
if (/allow.*access|grant.*permission|access to .* files/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
|
|
1491
1556
|
return {
|
|
1492
1557
|
detected: true,
|
|
1493
|
-
type: "
|
|
1494
|
-
prompt: "
|
|
1495
|
-
|
|
1496
|
-
|
|
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"
|
|
1497
1564
|
};
|
|
1498
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
|
+
}
|
|
1499
1572
|
return super.detectBlockingPrompt(output);
|
|
1500
1573
|
}
|
|
1501
1574
|
/**
|
|
1502
|
-
* Detect if
|
|
1575
|
+
* Detect if Claude Code is actively loading/processing.
|
|
1503
1576
|
*
|
|
1504
1577
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
1505
|
-
* -
|
|
1506
|
-
* -
|
|
1578
|
+
* - claude_active_reading_files: "Reading N files…"
|
|
1579
|
+
* - General: "esc to interrupt" spinner status line
|
|
1507
1580
|
*/
|
|
1508
1581
|
detectLoading(output) {
|
|
1509
1582
|
const stripped = this.stripAnsi(output);
|
|
1510
1583
|
const marker = this.getLatestHookMarker(stripped);
|
|
1511
1584
|
const tail = stripped.slice(-500);
|
|
1512
|
-
if (marker?.event === "
|
|
1585
|
+
if (marker?.event === "PreToolUse") {
|
|
1513
1586
|
return true;
|
|
1514
1587
|
}
|
|
1515
|
-
if (/esc\s+to\s+
|
|
1588
|
+
if (/esc\s+to\s+interrupt/i.test(tail)) {
|
|
1516
1589
|
return true;
|
|
1517
1590
|
}
|
|
1518
|
-
if (/
|
|
1591
|
+
if (/Reading\s+\d+\s+files/i.test(tail)) {
|
|
1519
1592
|
return true;
|
|
1520
1593
|
}
|
|
1521
1594
|
return false;
|
|
1522
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
|
+
*/
|
|
1523
1606
|
detectToolRunning(output) {
|
|
1524
1607
|
const stripped = this.stripAnsi(output);
|
|
1525
1608
|
const marker = this.getLatestHookMarker(stripped);
|
|
1526
|
-
|
|
1609
|
+
const tail = stripped.slice(-500);
|
|
1610
|
+
if (marker?.event === "PreToolUse" && marker.tool_name) {
|
|
1527
1611
|
return {
|
|
1528
1612
|
toolName: marker.tool_name.toLowerCase(),
|
|
1529
1613
|
description: `${marker.tool_name} (hook)`
|
|
1530
1614
|
};
|
|
1531
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
|
+
}
|
|
1532
1634
|
return null;
|
|
1533
1635
|
}
|
|
1534
1636
|
/**
|
|
1535
|
-
* Detect task completion for
|
|
1637
|
+
* Detect task completion for Claude Code.
|
|
1536
1638
|
*
|
|
1537
|
-
* High-confidence
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
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.
|
|
1540
1642
|
*
|
|
1541
1643
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
1542
|
-
* -
|
|
1644
|
+
* - claude_completed_turn_duration
|
|
1645
|
+
* - claude_completed_turn_duration_custom_verb
|
|
1543
1646
|
*/
|
|
1544
1647
|
detectTaskComplete(output) {
|
|
1545
1648
|
const stripped = this.stripAnsi(output);
|
|
1546
1649
|
const marker = this.getLatestHookMarker(stripped);
|
|
1547
|
-
if (
|
|
1650
|
+
if (!stripped.trim()) return false;
|
|
1651
|
+
if (marker?.event === "TaskCompleted") {
|
|
1548
1652
|
return true;
|
|
1549
1653
|
}
|
|
1550
|
-
if (
|
|
1654
|
+
if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
|
|
1551
1655
|
return true;
|
|
1552
1656
|
}
|
|
1553
|
-
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")) {
|
|
1554
1669
|
return true;
|
|
1555
1670
|
}
|
|
1556
1671
|
return false;
|
|
@@ -1558,27 +1673,24 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1558
1673
|
detectReady(output) {
|
|
1559
1674
|
const stripped = this.stripAnsi(output);
|
|
1560
1675
|
const marker = this.getLatestHookMarker(stripped);
|
|
1561
|
-
if (
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
return false;
|
|
1570
|
-
}
|
|
1571
|
-
if (/type.?your.?message/i.test(stripped)) {
|
|
1572
|
-
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
|
+
}
|
|
1573
1684
|
}
|
|
1574
|
-
if (/do
|
|
1685
|
+
if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
|
|
1575
1686
|
return false;
|
|
1576
1687
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
/
|
|
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;
|
|
1582
1694
|
}
|
|
1583
1695
|
parseOutput(output) {
|
|
1584
1696
|
const withoutHookMarkers = this.stripHookMarkers(output);
|
|
@@ -1588,8 +1700,7 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1588
1700
|
return null;
|
|
1589
1701
|
}
|
|
1590
1702
|
const isQuestion = this.containsQuestion(stripped);
|
|
1591
|
-
|
|
1592
|
-
content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
|
|
1703
|
+
const content = this.extractContent(stripped, /^.*>\s*/gm);
|
|
1593
1704
|
return {
|
|
1594
1705
|
type: isQuestion ? "question" : "response",
|
|
1595
1706
|
content,
|
|
@@ -1600,57 +1711,30 @@ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMess
|
|
|
1600
1711
|
}
|
|
1601
1712
|
};
|
|
1602
1713
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1714
|
+
getPromptPattern() {
|
|
1715
|
+
return /claude>\s*$/i;
|
|
1716
|
+
}
|
|
1717
|
+
getHealthCheckCommand() {
|
|
1718
|
+
return "claude --version";
|
|
1719
|
+
}
|
|
1607
1720
|
detectExit(output) {
|
|
1608
1721
|
const stripped = this.stripAnsi(output);
|
|
1609
1722
|
const marker = this.getLatestHookMarker(stripped);
|
|
1610
1723
|
if (marker?.event === "SessionEnd") {
|
|
1611
|
-
return {
|
|
1612
|
-
exited: true,
|
|
1613
|
-
code: 0
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
|
|
1617
|
-
return {
|
|
1618
|
-
exited: true,
|
|
1619
|
-
code: 1,
|
|
1620
|
-
error: "Gemini CLI exited because no folder trust level was selected"
|
|
1621
|
-
};
|
|
1622
|
-
}
|
|
1623
|
-
if (/you are now logged out/i.test(stripped)) {
|
|
1624
|
-
return {
|
|
1625
|
-
exited: true,
|
|
1626
|
-
code: 0
|
|
1627
|
-
};
|
|
1628
|
-
}
|
|
1629
|
-
if (/Agent\s+powering\s+down/i.test(stripped)) {
|
|
1630
|
-
return {
|
|
1631
|
-
exited: true,
|
|
1632
|
-
code: 0
|
|
1633
|
-
};
|
|
1724
|
+
return { exited: true, code: 0 };
|
|
1634
1725
|
}
|
|
1635
1726
|
return super.detectExit(output);
|
|
1636
1727
|
}
|
|
1637
|
-
getPromptPattern() {
|
|
1638
|
-
return /gemini>\s*$/i;
|
|
1639
|
-
}
|
|
1640
|
-
getHealthCheckCommand() {
|
|
1641
|
-
return "gemini --version";
|
|
1642
|
-
}
|
|
1643
1728
|
};
|
|
1644
1729
|
|
|
1645
1730
|
// src/codex-adapter.ts
|
|
1646
1731
|
var CodexAdapter = class extends BaseCodingAdapter {
|
|
1647
1732
|
adapterType = "codex";
|
|
1648
1733
|
displayName = "OpenAI Codex";
|
|
1734
|
+
readySettleMs = 2e3;
|
|
1649
1735
|
installation = {
|
|
1650
1736
|
command: "npm install -g @openai/codex",
|
|
1651
|
-
alternatives: [
|
|
1652
|
-
"pip install openai (Python SDK)"
|
|
1653
|
-
],
|
|
1737
|
+
alternatives: ["pip install openai (Python SDK)"],
|
|
1654
1738
|
docsUrl: "https://github.com/openai/codex"
|
|
1655
1739
|
};
|
|
1656
1740
|
/**
|
|
@@ -1945,18 +2029,28 @@ var CodexAdapter = class extends BaseCodingAdapter {
|
|
|
1945
2029
|
return false;
|
|
1946
2030
|
}
|
|
1947
2031
|
detectReady(output) {
|
|
2032
|
+
const rawTail = output.slice(-2e3);
|
|
2033
|
+
const hasRawComposerSignals = /OpenAI\s+Codex/i.test(rawTail) && (/Explain\s+this\s+codebase/i.test(rawTail) || /Summarize\s+recent\s+commits/i.test(rawTail) || /Ask\s+Codex\s+to\s+do\s+anything/i.test(rawTail) || /\?\s+for\s+shortcuts/i.test(rawTail) || /context\s+left/i.test(rawTail));
|
|
2034
|
+
if (hasRawComposerSignals) {
|
|
2035
|
+
return true;
|
|
2036
|
+
}
|
|
1948
2037
|
const stripped = this.stripAnsi(output);
|
|
1949
2038
|
if (!stripped.trim()) return false;
|
|
1950
2039
|
const tail = stripped.slice(-1200);
|
|
1951
2040
|
const hasComposerPrompt = /^\s*›\s*(?!\d+\.)\S.*$/m.test(tail) || /›\s+Ask\s+Codex\s+to\s+do\s+anything/.test(tail);
|
|
1952
2041
|
const hasComposerFooter = /\?\s+for\s+shortcuts/i.test(tail) || /context\s+left/i.test(tail) || /tab\s+to\s+queue\s+message/i.test(tail) || /shift\s*\+\s*enter\s+for\s+newline/i.test(tail);
|
|
1953
|
-
|
|
2042
|
+
const hasStartupComposerHints = /Summarize\s+recent\s+commits/i.test(tail) || /Explain\s+this\s+codebase/i.test(tail) || /Ask\s+Codex\s+to\s+do\s+anything/i.test(tail);
|
|
2043
|
+
const hasCodexHeader = /OpenAI\s+Codex/i.test(tail) && /directory:\s+~?\/?.+/i.test(tail);
|
|
2044
|
+
const hasInteractiveStatusBar = /gpt-[\w.-]+\s+(?:high|medium|low)/i.test(tail) && /left\b/i.test(tail);
|
|
2045
|
+
if (hasComposerPrompt || hasComposerFooter || hasStartupComposerHints || hasCodexHeader && hasInteractiveStatusBar) {
|
|
1954
2046
|
return true;
|
|
1955
2047
|
}
|
|
1956
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)) {
|
|
1957
2049
|
return false;
|
|
1958
2050
|
}
|
|
1959
|
-
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
|
+
)) {
|
|
1960
2054
|
return true;
|
|
1961
2055
|
}
|
|
1962
2056
|
return stripped.includes("How can I help") || /(?:codex|>)\s*$/i.test(stripped);
|
|
@@ -2005,258 +2099,97 @@ var CodexAdapter = class extends BaseCodingAdapter {
|
|
|
2005
2099
|
}
|
|
2006
2100
|
};
|
|
2007
2101
|
|
|
2008
|
-
// src/
|
|
2009
|
-
var
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
readySettleMs =
|
|
2014
|
-
/**
|
|
2015
|
-
* Aider uses plain text [y/n] prompts, NOT TUI arrow-key menus.
|
|
2016
|
-
*/
|
|
2017
|
-
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;
|
|
2018
2108
|
installation = {
|
|
2019
|
-
command: "
|
|
2020
|
-
alternatives: [
|
|
2021
|
-
|
|
2022
|
-
"brew install aider (macOS with Homebrew)"
|
|
2023
|
-
],
|
|
2024
|
-
docsUrl: "https://aider.chat/docs/install.html",
|
|
2025
|
-
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"
|
|
2026
2112
|
};
|
|
2027
2113
|
/**
|
|
2028
|
-
* Auto-response rules for
|
|
2029
|
-
*
|
|
2030
|
-
*
|
|
2031
|
-
*
|
|
2032
|
-
* 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
|
|
2033
2117
|
*/
|
|
2034
2118
|
autoResponseRules = [
|
|
2035
|
-
// ── Decline rules (specific, checked first) ────────────────────────
|
|
2036
|
-
{
|
|
2037
|
-
pattern: /allow collection of anonymous analytics/i,
|
|
2038
|
-
type: "config",
|
|
2039
|
-
response: "n",
|
|
2040
|
-
responseType: "text",
|
|
2041
|
-
description: "Decline Aider telemetry opt-in",
|
|
2042
|
-
safe: true,
|
|
2043
|
-
once: true
|
|
2044
|
-
},
|
|
2045
2119
|
{
|
|
2046
|
-
pattern: /
|
|
2047
|
-
type: "
|
|
2048
|
-
response: "
|
|
2049
|
-
responseType: "
|
|
2050
|
-
|
|
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)",
|
|
2051
2126
|
safe: true,
|
|
2052
2127
|
once: true
|
|
2053
2128
|
},
|
|
2054
2129
|
{
|
|
2055
|
-
pattern: /
|
|
2056
|
-
type: "config",
|
|
2057
|
-
response: "n",
|
|
2058
|
-
responseType: "text",
|
|
2059
|
-
description: "Decline automatic bug report",
|
|
2060
|
-
safe: true
|
|
2061
|
-
},
|
|
2062
|
-
// ── File / edit operations ──────────────────────────────────────────
|
|
2063
|
-
{
|
|
2064
|
-
pattern: /add .+ to the chat\?/i,
|
|
2065
|
-
type: "permission",
|
|
2066
|
-
response: "y",
|
|
2067
|
-
responseType: "text",
|
|
2068
|
-
description: "Allow Aider to add files to chat context",
|
|
2069
|
-
safe: true
|
|
2070
|
-
},
|
|
2071
|
-
{
|
|
2072
|
-
pattern: /add url to the chat\?/i,
|
|
2073
|
-
type: "permission",
|
|
2074
|
-
response: "y",
|
|
2075
|
-
responseType: "text",
|
|
2076
|
-
description: "Allow Aider to add URL content to chat",
|
|
2077
|
-
safe: true
|
|
2078
|
-
},
|
|
2079
|
-
{
|
|
2080
|
-
pattern: /create new file\?/i,
|
|
2081
|
-
type: "permission",
|
|
2082
|
-
response: "y",
|
|
2083
|
-
responseType: "text",
|
|
2084
|
-
description: "Allow Aider to create new files",
|
|
2085
|
-
safe: true
|
|
2086
|
-
},
|
|
2087
|
-
{
|
|
2088
|
-
pattern: /allow edits to file/i,
|
|
2089
|
-
type: "permission",
|
|
2090
|
-
response: "y",
|
|
2091
|
-
responseType: "text",
|
|
2092
|
-
description: "Allow edits to file not yet in chat",
|
|
2093
|
-
safe: true
|
|
2094
|
-
},
|
|
2095
|
-
{
|
|
2096
|
-
pattern: /edit the files\?/i,
|
|
2097
|
-
type: "permission",
|
|
2098
|
-
response: "y",
|
|
2099
|
-
responseType: "text",
|
|
2100
|
-
description: "Accept architect mode edits",
|
|
2101
|
-
safe: true
|
|
2102
|
-
},
|
|
2103
|
-
// ── Shell operations ────────────────────────────────────────────────
|
|
2104
|
-
{
|
|
2105
|
-
pattern: /run shell commands?\?/i,
|
|
2106
|
-
type: "permission",
|
|
2107
|
-
response: "y",
|
|
2108
|
-
responseType: "text",
|
|
2109
|
-
description: "Allow Aider to run shell commands",
|
|
2110
|
-
safe: true
|
|
2111
|
-
},
|
|
2112
|
-
{
|
|
2113
|
-
pattern: /add command output to the chat\?/i,
|
|
2114
|
-
type: "permission",
|
|
2115
|
-
response: "y",
|
|
2116
|
-
responseType: "text",
|
|
2117
|
-
description: "Add shell command output to chat context",
|
|
2118
|
-
safe: true
|
|
2119
|
-
},
|
|
2120
|
-
{
|
|
2121
|
-
pattern: /add \d+.*tokens of command output to the chat\?/i,
|
|
2130
|
+
pattern: /trust.?the.?following.?folders.*(added|workspace)/i,
|
|
2122
2131
|
type: "permission",
|
|
2123
|
-
response: "
|
|
2124
|
-
responseType: "
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
},
|
|
2128
|
-
// ── Setup / maintenance ─────────────────────────────────────────────
|
|
2129
|
-
{
|
|
2130
|
-
pattern: /no git repo found.*create one/i,
|
|
2131
|
-
type: "config",
|
|
2132
|
-
response: "y",
|
|
2133
|
-
responseType: "text",
|
|
2134
|
-
description: "Create git repo for change tracking",
|
|
2132
|
+
response: "",
|
|
2133
|
+
responseType: "keys",
|
|
2134
|
+
keys: ["enter"],
|
|
2135
|
+
description: "Trust multiple folders being added to workspace",
|
|
2135
2136
|
safe: true,
|
|
2136
2137
|
once: true
|
|
2137
2138
|
},
|
|
2138
2139
|
{
|
|
2139
|
-
pattern: /
|
|
2140
|
+
pattern: /allow.?google.?to.?use.?this.?data/i,
|
|
2140
2141
|
type: "config",
|
|
2141
|
-
response: "
|
|
2142
|
-
responseType: "
|
|
2143
|
-
|
|
2142
|
+
response: "",
|
|
2143
|
+
responseType: "keys",
|
|
2144
|
+
keys: ["down", "enter"],
|
|
2145
|
+
description: 'Decline Google data collection (select "No")',
|
|
2144
2146
|
safe: true,
|
|
2145
2147
|
once: true
|
|
2146
|
-
},
|
|
2147
|
-
{
|
|
2148
|
-
pattern: /run pip install\?/i,
|
|
2149
|
-
type: "config",
|
|
2150
|
-
response: "y",
|
|
2151
|
-
responseType: "text",
|
|
2152
|
-
description: "Install missing Python dependencies",
|
|
2153
|
-
safe: true
|
|
2154
|
-
},
|
|
2155
|
-
{
|
|
2156
|
-
pattern: /install playwright\?/i,
|
|
2157
|
-
type: "config",
|
|
2158
|
-
response: "y",
|
|
2159
|
-
responseType: "text",
|
|
2160
|
-
description: "Install Playwright for web scraping",
|
|
2161
|
-
safe: true
|
|
2162
|
-
},
|
|
2163
|
-
// ── Other safe confirmations ────────────────────────────────────────
|
|
2164
|
-
{
|
|
2165
|
-
pattern: /fix lint errors in/i,
|
|
2166
|
-
type: "permission",
|
|
2167
|
-
response: "y",
|
|
2168
|
-
responseType: "text",
|
|
2169
|
-
description: "Accept lint error fix suggestion",
|
|
2170
|
-
safe: true
|
|
2171
|
-
},
|
|
2172
|
-
{
|
|
2173
|
-
pattern: /try to proceed anyway\?/i,
|
|
2174
|
-
type: "config",
|
|
2175
|
-
response: "y",
|
|
2176
|
-
responseType: "text",
|
|
2177
|
-
description: "Continue despite context limit warning",
|
|
2178
|
-
safe: true
|
|
2179
2148
|
}
|
|
2180
2149
|
];
|
|
2181
2150
|
getWorkspaceFiles() {
|
|
2182
2151
|
return [
|
|
2183
2152
|
{
|
|
2184
|
-
relativePath: ".
|
|
2185
|
-
description: "Project
|
|
2153
|
+
relativePath: "GEMINI.md",
|
|
2154
|
+
description: "Project-level instructions read automatically on startup",
|
|
2186
2155
|
autoLoaded: true,
|
|
2187
2156
|
type: "memory",
|
|
2188
2157
|
format: "markdown"
|
|
2189
2158
|
},
|
|
2190
2159
|
{
|
|
2191
|
-
relativePath: ".
|
|
2192
|
-
description: "Project-scoped
|
|
2160
|
+
relativePath: ".gemini/settings.json",
|
|
2161
|
+
description: "Project-scoped settings (tool permissions, sandbox config)",
|
|
2193
2162
|
autoLoaded: true,
|
|
2194
2163
|
type: "config",
|
|
2195
|
-
format: "
|
|
2164
|
+
format: "json"
|
|
2196
2165
|
},
|
|
2197
2166
|
{
|
|
2198
|
-
relativePath: ".
|
|
2199
|
-
description: "
|
|
2200
|
-
autoLoaded:
|
|
2201
|
-
type: "
|
|
2202
|
-
format: "
|
|
2167
|
+
relativePath: ".gemini/styles",
|
|
2168
|
+
description: "Custom style/persona definitions directory",
|
|
2169
|
+
autoLoaded: false,
|
|
2170
|
+
type: "config",
|
|
2171
|
+
format: "markdown"
|
|
2203
2172
|
}
|
|
2204
2173
|
];
|
|
2205
2174
|
}
|
|
2206
|
-
getRecommendedModels(
|
|
2207
|
-
if (credentials?.anthropicKey) {
|
|
2208
|
-
return {
|
|
2209
|
-
powerful: "anthropic/claude-sonnet-4-20250514",
|
|
2210
|
-
fast: "anthropic/claude-haiku-4-5-20251001"
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2213
|
-
if (credentials?.openaiKey) {
|
|
2214
|
-
return {
|
|
2215
|
-
powerful: "openai/o3",
|
|
2216
|
-
fast: "openai/gpt-4o-mini"
|
|
2217
|
-
};
|
|
2218
|
-
}
|
|
2219
|
-
if (credentials?.googleKey) {
|
|
2220
|
-
return {
|
|
2221
|
-
powerful: "gemini/gemini-3-pro",
|
|
2222
|
-
fast: "gemini/gemini-3-flash"
|
|
2223
|
-
};
|
|
2224
|
-
}
|
|
2175
|
+
getRecommendedModels(_credentials) {
|
|
2225
2176
|
return {
|
|
2226
|
-
powerful: "
|
|
2227
|
-
fast: "
|
|
2177
|
+
powerful: "gemini-3-pro",
|
|
2178
|
+
fast: "gemini-3-flash"
|
|
2228
2179
|
};
|
|
2229
2180
|
}
|
|
2230
2181
|
getCommand() {
|
|
2231
|
-
return "
|
|
2182
|
+
return "gemini";
|
|
2232
2183
|
}
|
|
2233
2184
|
getArgs(config) {
|
|
2234
2185
|
const args = [];
|
|
2235
|
-
args.push("--auto-commits");
|
|
2236
2186
|
if (!this.isInteractive(config)) {
|
|
2237
|
-
args.push("--
|
|
2238
|
-
args.push("--
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
if (config.env?.AIDER_MODEL) {
|
|
2243
|
-
args.push("--model", config.env.AIDER_MODEL);
|
|
2244
|
-
} else if (provider === "anthropic") {
|
|
2245
|
-
args.push("--model", "sonnet");
|
|
2246
|
-
} else if (provider === "openai") {
|
|
2247
|
-
args.push("--model", "4o");
|
|
2248
|
-
} else if (provider === "google") {
|
|
2249
|
-
args.push("--model", "gemini");
|
|
2250
|
-
} else if (credentials.anthropicKey) {
|
|
2251
|
-
args.push("--model", "sonnet");
|
|
2252
|
-
} else if (credentials.openaiKey) {
|
|
2253
|
-
args.push("--model", "4o");
|
|
2254
|
-
} else if (credentials.googleKey) {
|
|
2255
|
-
args.push("--model", "gemini");
|
|
2187
|
+
args.push("--non-interactive");
|
|
2188
|
+
args.push("--output-format", "text");
|
|
2189
|
+
if (config.workdir) {
|
|
2190
|
+
args.push("--cwd", config.workdir);
|
|
2191
|
+
}
|
|
2256
2192
|
}
|
|
2257
|
-
if (credentials.anthropicKey) args.push("--api-key", `anthropic=${credentials.anthropicKey}`);
|
|
2258
|
-
if (credentials.openaiKey) args.push("--api-key", `openai=${credentials.openaiKey}`);
|
|
2259
|
-
if (credentials.googleKey) args.push("--api-key", `gemini=${credentials.googleKey}`);
|
|
2260
2193
|
const approvalConfig = this.getApprovalConfig(config);
|
|
2261
2194
|
if (approvalConfig) {
|
|
2262
2195
|
args.push(...approvalConfig.cliFlags);
|
|
@@ -2265,54 +2198,239 @@ var AiderAdapter = class extends BaseCodingAdapter {
|
|
|
2265
2198
|
}
|
|
2266
2199
|
getEnv(config) {
|
|
2267
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
|
+
}
|
|
2268
2210
|
if (!this.isInteractive(config)) {
|
|
2269
2211
|
env.NO_COLOR = "1";
|
|
2270
2212
|
}
|
|
2271
|
-
if (
|
|
2272
|
-
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;
|
|
2273
2216
|
}
|
|
2274
2217
|
return env;
|
|
2275
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
|
+
}
|
|
2276
2334
|
detectLogin(output) {
|
|
2277
2335
|
const stripped = this.stripAnsi(output);
|
|
2278
|
-
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")) {
|
|
2279
2337
|
return {
|
|
2280
2338
|
required: true,
|
|
2281
2339
|
type: "api_key",
|
|
2282
|
-
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"
|
|
2283
2355
|
};
|
|
2284
2356
|
}
|
|
2285
|
-
if (
|
|
2357
|
+
if (/waiting.?for.?auth/i.test(stripped)) {
|
|
2286
2358
|
return {
|
|
2287
2359
|
required: true,
|
|
2288
|
-
type: "
|
|
2289
|
-
instructions: "
|
|
2360
|
+
type: "oauth",
|
|
2361
|
+
instructions: "Waiting for browser authentication to complete"
|
|
2290
2362
|
};
|
|
2291
2363
|
}
|
|
2292
|
-
if (
|
|
2364
|
+
if (stripped.includes("Sign in with Google") || stripped.includes("OAuth") || stripped.includes("accounts.google.com")) {
|
|
2365
|
+
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
2293
2366
|
return {
|
|
2294
2367
|
required: true,
|
|
2295
2368
|
type: "oauth",
|
|
2296
|
-
|
|
2369
|
+
url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
|
|
2370
|
+
instructions: "Google OAuth authentication required"
|
|
2297
2371
|
};
|
|
2298
2372
|
}
|
|
2299
|
-
if (
|
|
2300
|
-
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
2373
|
+
if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
|
|
2301
2374
|
return {
|
|
2302
2375
|
required: true,
|
|
2303
2376
|
type: "browser",
|
|
2304
|
-
|
|
2305
|
-
instructions: "Complete OpenRouter authentication in browser"
|
|
2377
|
+
instructions: "Run: gcloud auth application-default login"
|
|
2306
2378
|
};
|
|
2307
2379
|
}
|
|
2308
2380
|
return { required: false };
|
|
2309
2381
|
}
|
|
2310
|
-
/**
|
|
2311
|
-
* Detect blocking prompts specific to Aider CLI.
|
|
2312
|
-
* Source: io.py, onboarding.py, base_coder.py, report.py
|
|
2313
|
-
*/
|
|
2314
2382
|
detectBlockingPrompt(output) {
|
|
2315
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
|
+
}
|
|
2316
2434
|
const loginDetection = this.detectLogin(output);
|
|
2317
2435
|
if (loginDetection.required) {
|
|
2318
2436
|
return {
|
|
@@ -2324,111 +2442,147 @@ var AiderAdapter = class extends BaseCodingAdapter {
|
|
|
2324
2442
|
instructions: loginDetection.instructions
|
|
2325
2443
|
};
|
|
2326
2444
|
}
|
|
2327
|
-
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)) {
|
|
2328
2455
|
return {
|
|
2329
2456
|
detected: true,
|
|
2330
2457
|
type: "model_select",
|
|
2331
|
-
prompt: "
|
|
2458
|
+
prompt: "Gemini model selection",
|
|
2332
2459
|
canAutoRespond: false,
|
|
2333
|
-
instructions: "Please select a model or set
|
|
2460
|
+
instructions: "Please select a model or set GEMINI_MODEL env var"
|
|
2334
2461
|
};
|
|
2335
2462
|
}
|
|
2336
|
-
if (/
|
|
2463
|
+
if (/select.*project|choose.*project|google cloud project/i.test(stripped)) {
|
|
2337
2464
|
return {
|
|
2338
2465
|
detected: true,
|
|
2339
|
-
type: "
|
|
2340
|
-
prompt: "
|
|
2466
|
+
type: "project_select",
|
|
2467
|
+
prompt: "Google Cloud project selection",
|
|
2341
2468
|
canAutoRespond: false,
|
|
2342
|
-
instructions: "
|
|
2469
|
+
instructions: "Please select a Google Cloud project"
|
|
2343
2470
|
};
|
|
2344
2471
|
}
|
|
2345
|
-
if (/
|
|
2472
|
+
if (/safety.*filter|content.*blocked|unsafe.*content/i.test(stripped)) {
|
|
2346
2473
|
return {
|
|
2347
2474
|
detected: true,
|
|
2348
|
-
type: "
|
|
2349
|
-
prompt: "
|
|
2350
|
-
options: ["y", "n"],
|
|
2475
|
+
type: "unknown",
|
|
2476
|
+
prompt: "Safety filter triggered",
|
|
2351
2477
|
canAutoRespond: false,
|
|
2352
|
-
instructions: "
|
|
2478
|
+
instructions: "Content was blocked by safety filters"
|
|
2353
2479
|
};
|
|
2354
2480
|
}
|
|
2355
2481
|
return super.detectBlockingPrompt(output);
|
|
2356
2482
|
}
|
|
2357
2483
|
/**
|
|
2358
|
-
* Detect if
|
|
2484
|
+
* Detect if Gemini CLI is actively loading/processing.
|
|
2359
2485
|
*
|
|
2360
2486
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
2361
|
-
* -
|
|
2362
|
-
* -
|
|
2363
|
-
* - 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..."
|
|
2364
2489
|
*/
|
|
2365
2490
|
detectLoading(output) {
|
|
2366
2491
|
const stripped = this.stripAnsi(output);
|
|
2492
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2367
2493
|
const tail = stripped.slice(-500);
|
|
2368
|
-
if (
|
|
2494
|
+
if (marker?.event === "BeforeTool") {
|
|
2369
2495
|
return true;
|
|
2370
2496
|
}
|
|
2371
|
-
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)) {
|
|
2372
2501
|
return true;
|
|
2373
2502
|
}
|
|
2374
2503
|
return false;
|
|
2375
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
|
+
}
|
|
2376
2516
|
/**
|
|
2377
|
-
* Detect task completion for
|
|
2517
|
+
* Detect task completion for Gemini CLI.
|
|
2378
2518
|
*
|
|
2379
2519
|
* High-confidence patterns:
|
|
2380
|
-
* - "
|
|
2381
|
-
* -
|
|
2520
|
+
* - "◇ Ready" window title signal (OSC sequence, may survive ANSI stripping)
|
|
2521
|
+
* - "Type your message" composer placeholder after agent output
|
|
2382
2522
|
*
|
|
2383
2523
|
* Patterns from: AGENT_LOADING_STATUS_PATTERNS.json
|
|
2384
|
-
* -
|
|
2524
|
+
* - gemini_ready_title
|
|
2385
2525
|
*/
|
|
2386
2526
|
detectTaskComplete(output) {
|
|
2387
2527
|
const stripped = this.stripAnsi(output);
|
|
2388
|
-
|
|
2528
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2529
|
+
if (marker?.event === "AfterAgent") {
|
|
2389
2530
|
return true;
|
|
2390
2531
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
return true;
|
|
2397
|
-
}
|
|
2532
|
+
if (/◇\s+Ready/.test(stripped)) {
|
|
2533
|
+
return true;
|
|
2534
|
+
}
|
|
2535
|
+
if (/type.?your.?message/i.test(stripped)) {
|
|
2536
|
+
return true;
|
|
2398
2537
|
}
|
|
2399
2538
|
return false;
|
|
2400
2539
|
}
|
|
2401
2540
|
detectReady(output) {
|
|
2402
2541
|
const stripped = this.stripAnsi(output);
|
|
2403
|
-
|
|
2542
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2543
|
+
if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
|
|
2404
2544
|
return false;
|
|
2405
2545
|
}
|
|
2406
|
-
if (
|
|
2546
|
+
if (marker?.event === "AfterAgent") {
|
|
2407
2547
|
return true;
|
|
2408
2548
|
}
|
|
2409
|
-
|
|
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)) {
|
|
2410
2562
|
return true;
|
|
2411
2563
|
}
|
|
2412
|
-
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)) {
|
|
2413
2568
|
return true;
|
|
2414
2569
|
}
|
|
2415
|
-
return (
|
|
2416
|
-
|
|
2417
|
-
stripped.includes("aider>") || /Added.*to the chat/i.test(stripped) || />\s*$/.test(stripped)
|
|
2418
|
-
);
|
|
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);
|
|
2419
2572
|
}
|
|
2420
2573
|
parseOutput(output) {
|
|
2421
|
-
const
|
|
2574
|
+
const withoutHookMarkers = this.stripHookMarkers(output);
|
|
2575
|
+
const stripped = this.stripAnsi(withoutHookMarkers);
|
|
2422
2576
|
const isComplete = this.isResponseComplete(stripped);
|
|
2423
2577
|
if (!isComplete) {
|
|
2424
2578
|
return null;
|
|
2425
2579
|
}
|
|
2426
2580
|
const isQuestion = this.containsQuestion(stripped);
|
|
2427
|
-
let content = this.extractContent(stripped, /^.*
|
|
2428
|
-
content = content.replace(
|
|
2581
|
+
let content = this.extractContent(stripped, /^.*(?:gemini|>)\s*/gim);
|
|
2582
|
+
content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
|
|
2429
2583
|
return {
|
|
2430
2584
|
type: isQuestion ? "question" : "response",
|
|
2431
|
-
content
|
|
2585
|
+
content,
|
|
2432
2586
|
isComplete: true,
|
|
2433
2587
|
isQuestion,
|
|
2434
2588
|
metadata: {
|
|
@@ -2437,28 +2591,44 @@ var AiderAdapter = class extends BaseCodingAdapter {
|
|
|
2437
2591
|
};
|
|
2438
2592
|
}
|
|
2439
2593
|
/**
|
|
2440
|
-
* Detect exit conditions specific to
|
|
2441
|
-
* Source:
|
|
2594
|
+
* Detect exit conditions specific to Gemini CLI.
|
|
2595
|
+
* Source: FolderTrustDialog.tsx:127, LogoutConfirmationDialog.tsx:64
|
|
2442
2596
|
*/
|
|
2443
2597
|
detectExit(output) {
|
|
2444
2598
|
const stripped = this.stripAnsi(output);
|
|
2445
|
-
|
|
2446
|
-
|
|
2599
|
+
const marker = this.getLatestHookMarker(stripped);
|
|
2600
|
+
if (marker?.event === "SessionEnd") {
|
|
2601
|
+
return {
|
|
2602
|
+
exited: true,
|
|
2603
|
+
code: 0
|
|
2604
|
+
};
|
|
2447
2605
|
}
|
|
2448
|
-
if (/
|
|
2606
|
+
if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
|
|
2449
2607
|
return {
|
|
2450
2608
|
exited: true,
|
|
2451
|
-
code:
|
|
2452
|
-
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
|
|
2453
2623
|
};
|
|
2454
2624
|
}
|
|
2455
2625
|
return super.detectExit(output);
|
|
2456
2626
|
}
|
|
2457
2627
|
getPromptPattern() {
|
|
2458
|
-
return /
|
|
2628
|
+
return /gemini>\s*$/i;
|
|
2459
2629
|
}
|
|
2460
2630
|
getHealthCheckCommand() {
|
|
2461
|
-
return "
|
|
2631
|
+
return "gemini --version";
|
|
2462
2632
|
}
|
|
2463
2633
|
};
|
|
2464
2634
|
|
|
@@ -2564,7 +2734,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2564
2734
|
instructions: "Hermes is waiting for clarify input (arrow keys + Enter or free text)."
|
|
2565
2735
|
};
|
|
2566
2736
|
}
|
|
2567
|
-
if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(
|
|
2737
|
+
if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(
|
|
2738
|
+
stripped
|
|
2739
|
+
)) {
|
|
2568
2740
|
return {
|
|
2569
2741
|
detected: true,
|
|
2570
2742
|
type: "tool_wait",
|
|
@@ -2573,7 +2745,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2573
2745
|
instructions: "Hermes terminal tool is waiting for a sudo password or skip."
|
|
2574
2746
|
};
|
|
2575
2747
|
}
|
|
2576
|
-
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
|
+
)) {
|
|
2577
2751
|
return {
|
|
2578
2752
|
detected: true,
|
|
2579
2753
|
type: "permission",
|
|
@@ -2587,7 +2761,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2587
2761
|
detectLoading(output) {
|
|
2588
2762
|
const stripped = this.stripAnsi(output);
|
|
2589
2763
|
const tail = stripped.slice(-1200);
|
|
2590
|
-
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
|
+
)) {
|
|
2591
2767
|
return true;
|
|
2592
2768
|
}
|
|
2593
2769
|
if (/\(\d+\.\d+s\)\s*$/.test(tail) && /(?:🔍|📄|💻|⚙️|📖|✍️|🔧|🌐|👆|⌨️|📋|🧠|📚|🎨|🐍|🔀|⚡|💬)/.test(tail)) {
|
|
@@ -2621,7 +2797,9 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2621
2797
|
if (this.detectLoading(tail)) {
|
|
2622
2798
|
return false;
|
|
2623
2799
|
}
|
|
2624
|
-
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
|
+
)) {
|
|
2625
2803
|
return false;
|
|
2626
2804
|
}
|
|
2627
2805
|
if (/(?:⚕|⚠|🔐|\?|✎)\s*❯\s*$/.test(tail)) {
|
|
@@ -2642,7 +2820,10 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2642
2820
|
content = boxMatch[1].trim();
|
|
2643
2821
|
}
|
|
2644
2822
|
if (!content) {
|
|
2645
|
-
content = this.extractContent(
|
|
2823
|
+
content = this.extractContent(
|
|
2824
|
+
stripped,
|
|
2825
|
+
/(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim
|
|
2826
|
+
);
|
|
2646
2827
|
}
|
|
2647
2828
|
return {
|
|
2648
2829
|
type: this.containsQuestion(content) ? "question" : "response",
|
|
@@ -2672,12 +2853,7 @@ var HermesAdapter = class extends BaseCodingAdapter {
|
|
|
2672
2853
|
// src/pattern-loader.ts
|
|
2673
2854
|
var BASELINE_PATTERNS = {
|
|
2674
2855
|
claude: {
|
|
2675
|
-
ready: [
|
|
2676
|
-
"Claude Code",
|
|
2677
|
-
"How can I help",
|
|
2678
|
-
"What would you like",
|
|
2679
|
-
"Ready"
|
|
2680
|
-
],
|
|
2856
|
+
ready: ["Claude Code", "How can I help", "What would you like", "Ready"],
|
|
2681
2857
|
auth: [
|
|
2682
2858
|
"ANTHROPIC_API_KEY",
|
|
2683
2859
|
"API key not found",
|
|
@@ -2685,17 +2861,9 @@ var BASELINE_PATTERNS = {
|
|
|
2685
2861
|
"Please sign in",
|
|
2686
2862
|
"Invalid API key"
|
|
2687
2863
|
],
|
|
2688
|
-
blocking: [
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
],
|
|
2692
|
-
loading: [
|
|
2693
|
-
"Reading X files\u2026"
|
|
2694
|
-
],
|
|
2695
|
-
turnComplete: [
|
|
2696
|
-
"Cooked for 1m 6s",
|
|
2697
|
-
"<CustomVerb> for 4m 39s"
|
|
2698
|
-
],
|
|
2864
|
+
blocking: ["update available", "[y/n]"],
|
|
2865
|
+
loading: ["Reading X files\u2026"],
|
|
2866
|
+
turnComplete: ["Cooked for 1m 6s", "<CustomVerb> for 4m 39s"],
|
|
2699
2867
|
toolWait: [],
|
|
2700
2868
|
exit: [],
|
|
2701
2869
|
source: "baseline"
|
|
@@ -2715,10 +2883,7 @@ var BASELINE_PATTERNS = {
|
|
|
2715
2883
|
"gcloud auth",
|
|
2716
2884
|
"Application Default Credentials"
|
|
2717
2885
|
],
|
|
2718
|
-
blocking: [
|
|
2719
|
-
"update available",
|
|
2720
|
-
"[y/n]"
|
|
2721
|
-
],
|
|
2886
|
+
blocking: ["update available", "[y/n]"],
|
|
2722
2887
|
loading: [
|
|
2723
2888
|
"<phrase> (esc to cancel, 25s)",
|
|
2724
2889
|
"Waiting for user confirmation...",
|
|
@@ -2727,37 +2892,21 @@ var BASELINE_PATTERNS = {
|
|
|
2727
2892
|
"Warming up the AI hamsters"
|
|
2728
2893
|
],
|
|
2729
2894
|
turnComplete: [],
|
|
2730
|
-
toolWait: [
|
|
2731
|
-
|
|
2732
|
-
],
|
|
2733
|
-
exit: [
|
|
2734
|
-
"Agent powering down. Goodbye!"
|
|
2735
|
-
],
|
|
2895
|
+
toolWait: ["Interactive shell awaiting input... press tab to focus shell"],
|
|
2896
|
+
exit: ["Agent powering down. Goodbye!"],
|
|
2736
2897
|
source: "baseline"
|
|
2737
2898
|
},
|
|
2738
2899
|
codex: {
|
|
2739
|
-
ready: [
|
|
2740
|
-
"Codex",
|
|
2741
|
-
"How can I help",
|
|
2742
|
-
"Ready"
|
|
2743
|
-
],
|
|
2900
|
+
ready: ["Codex", "How can I help", "Ready"],
|
|
2744
2901
|
auth: [
|
|
2745
2902
|
"OPENAI_API_KEY",
|
|
2746
2903
|
"API key not found",
|
|
2747
2904
|
"Unauthorized",
|
|
2748
2905
|
"Invalid API key"
|
|
2749
2906
|
],
|
|
2750
|
-
blocking: [
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
],
|
|
2754
|
-
loading: [
|
|
2755
|
-
"\u2022 Working (0s \u2022 esc to interrupt)",
|
|
2756
|
-
"Booting MCP server: alpha"
|
|
2757
|
-
],
|
|
2758
|
-
turnComplete: [
|
|
2759
|
-
"Worked for 1m 05s"
|
|
2760
|
-
],
|
|
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"],
|
|
2761
2910
|
toolWait: [
|
|
2762
2911
|
"Waiting for background terminal \xB7 <command>",
|
|
2763
2912
|
"Searching the web"
|
|
@@ -2766,39 +2915,21 @@ var BASELINE_PATTERNS = {
|
|
|
2766
2915
|
source: "baseline"
|
|
2767
2916
|
},
|
|
2768
2917
|
aider: {
|
|
2769
|
-
ready: [
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
"Ready"
|
|
2773
|
-
],
|
|
2774
|
-
auth: [
|
|
2775
|
-
"API key",
|
|
2776
|
-
"OPENAI_API_KEY",
|
|
2777
|
-
"ANTHROPIC_API_KEY",
|
|
2778
|
-
"No API key"
|
|
2779
|
-
],
|
|
2780
|
-
blocking: [
|
|
2781
|
-
"(Y)es/(N)o",
|
|
2782
|
-
"[y/n]"
|
|
2783
|
-
],
|
|
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]"],
|
|
2784
2921
|
loading: [
|
|
2785
2922
|
"Waiting for <model>",
|
|
2786
2923
|
"Waiting for LLM",
|
|
2787
2924
|
"Generating commit message with <model>"
|
|
2788
2925
|
],
|
|
2789
|
-
turnComplete: [
|
|
2790
|
-
"Aider is waiting for your input"
|
|
2791
|
-
],
|
|
2926
|
+
turnComplete: ["Aider is waiting for your input"],
|
|
2792
2927
|
toolWait: [],
|
|
2793
2928
|
exit: [],
|
|
2794
2929
|
source: "baseline"
|
|
2795
2930
|
},
|
|
2796
2931
|
hermes: {
|
|
2797
|
-
ready: [
|
|
2798
|
-
"\u276F",
|
|
2799
|
-
"\u2695 Hermes",
|
|
2800
|
-
"Welcome to Hermes Agent"
|
|
2801
|
-
],
|
|
2932
|
+
ready: ["\u276F", "\u2695 Hermes", "Welcome to Hermes Agent"],
|
|
2802
2933
|
auth: [
|
|
2803
2934
|
"isn't configured yet",
|
|
2804
2935
|
"no API keys or providers found",
|
|
@@ -2809,23 +2940,14 @@ var BASELINE_PATTERNS = {
|
|
|
2809
2940
|
"Sudo Password Required",
|
|
2810
2941
|
"Dangerous Command"
|
|
2811
2942
|
],
|
|
2812
|
-
loading: [
|
|
2813
|
-
|
|
2814
|
-
"(0.0s)",
|
|
2815
|
-
"\u2695 \u276F"
|
|
2816
|
-
],
|
|
2817
|
-
turnComplete: [
|
|
2818
|
-
"\u256D\u2500 \u2695 Hermes",
|
|
2819
|
-
"\u276F"
|
|
2820
|
-
],
|
|
2943
|
+
loading: ["deliberating...", "(0.0s)", "\u2695 \u276F"],
|
|
2944
|
+
turnComplete: ["\u256D\u2500 \u2695 Hermes", "\u276F"],
|
|
2821
2945
|
toolWait: [
|
|
2822
2946
|
"Hermes needs your input",
|
|
2823
2947
|
"Sudo Password Required",
|
|
2824
2948
|
"Dangerous Command"
|
|
2825
2949
|
],
|
|
2826
|
-
exit: [
|
|
2827
|
-
"Goodbye! \u2695"
|
|
2828
|
-
],
|
|
2950
|
+
exit: ["Goodbye! \u2695"],
|
|
2829
2951
|
source: "baseline"
|
|
2830
2952
|
}
|
|
2831
2953
|
};
|
|
@@ -2892,6 +3014,7 @@ async function hasDynamicPatterns(adapter) {
|
|
|
2892
3014
|
}
|
|
2893
3015
|
|
|
2894
3016
|
// src/index.ts
|
|
3017
|
+
var logger = pino({ name: "coding-agent-adapters" });
|
|
2895
3018
|
function createAllAdapters() {
|
|
2896
3019
|
return [
|
|
2897
3020
|
new ClaudeAdapter(),
|
|
@@ -2938,18 +3061,20 @@ async function printMissingAdapters(types) {
|
|
|
2938
3061
|
const results = types ? await checkAdapters(types) : await checkAllAdapters();
|
|
2939
3062
|
const missing = results.filter((r) => !r.installed);
|
|
2940
3063
|
if (missing.length === 0) {
|
|
2941
|
-
|
|
3064
|
+
logger.info("All CLI tools are installed");
|
|
2942
3065
|
return;
|
|
2943
3066
|
}
|
|
2944
|
-
|
|
3067
|
+
logger.info({ count: missing.length }, "Missing CLI tools detected");
|
|
2945
3068
|
for (const m of missing) {
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
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
|
+
);
|
|
2953
3078
|
}
|
|
2954
3079
|
}
|
|
2955
3080
|
|