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