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