coding-agent-adapters 0.11.1 → 0.13.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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdir, writeFile, appendFile } from 'fs/promises';
2
2
  import { join, dirname } from 'path';
3
- import { BaseCLIAdapter } from 'pty-manager';
3
+ import { BaseCLIAdapter } from 'adapter-types';
4
4
 
5
5
  // src/base-coding-adapter.ts
6
6
 
@@ -335,6 +335,16 @@ function generateAiderApprovalConfig(preset) {
335
335
  summary: `Aider: ${def.description}`
336
336
  };
337
337
  }
338
+ function generateHermesApprovalConfig(preset) {
339
+ const def = getPresetDefinition(preset);
340
+ return {
341
+ preset,
342
+ cliFlags: [],
343
+ workspaceFiles: [],
344
+ envVars: {},
345
+ summary: `Hermes Agent: ${def.description}`
346
+ };
347
+ }
338
348
  function generateApprovalConfig(adapterType, preset) {
339
349
  switch (adapterType) {
340
350
  case "claude":
@@ -345,6 +355,8 @@ function generateApprovalConfig(adapterType, preset) {
345
355
  return generateCodexApprovalConfig(preset);
346
356
  case "aider":
347
357
  return generateAiderApprovalConfig(preset);
358
+ case "hermes":
359
+ return generateHermesApprovalConfig(preset);
348
360
  default:
349
361
  throw new Error(`Unknown adapter type: ${adapterType}`);
350
362
  }
@@ -422,6 +434,13 @@ var BaseCodingAdapter = class extends BaseCLIAdapter {
422
434
  result = result.replace(/ {2,}/g, " ");
423
435
  return result;
424
436
  }
437
+ /**
438
+ * Generate hook telemetry protocol configuration.
439
+ * Returns null by default — only Claude adapter supports hooks.
440
+ */
441
+ getHookTelemetryProtocol(_options) {
442
+ return null;
443
+ }
425
444
  /**
426
445
  * Override detectExit to include installation instructions
427
446
  */
@@ -547,6 +566,7 @@ Docs: ${this.installation.docsUrl}`
547
566
  };
548
567
 
549
568
  // src/claude-adapter.ts
569
+ var CLAUDE_HOOK_MARKER_PREFIX = "PARALLAX_CLAUDE_HOOK";
550
570
  var ClaudeAdapter = class extends BaseCodingAdapter {
551
571
  adapterType = "claude";
552
572
  displayName = "Claude Code";
@@ -584,7 +604,8 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
584
604
  responseType: "keys",
585
605
  keys: ["enter"],
586
606
  description: "Auto-approve tool permission prompts (file access, MCP tools, etc.)",
587
- safe: true
607
+ safe: true,
608
+ once: true
588
609
  },
589
610
  {
590
611
  pattern: /update available.*\[y\/n\]/i,
@@ -693,6 +714,7 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
693
714
  getEnv(config) {
694
715
  const env = {};
695
716
  const credentials = this.getCredentials(config);
717
+ const adapterConfig = config.adapterConfig;
696
718
  if (credentials.anthropicKey) {
697
719
  env.ANTHROPIC_API_KEY = credentials.anthropicKey;
698
720
  }
@@ -702,8 +724,110 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
702
724
  if (!this.isInteractive(config)) {
703
725
  env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
704
726
  }
727
+ if (adapterConfig?.claudeHookTelemetry) {
728
+ env.PARALLAX_CLAUDE_HOOK_TELEMETRY = "1";
729
+ env.PARALLAX_CLAUDE_HOOK_MARKER_PREFIX = adapterConfig.claudeHookMarkerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
730
+ }
705
731
  return env;
706
732
  }
733
+ getHookTelemetryProtocol(options) {
734
+ if (options?.httpUrl) {
735
+ const httpHookBase = {
736
+ type: "http",
737
+ url: options.httpUrl,
738
+ timeout: 5
739
+ };
740
+ if (options.sessionId) {
741
+ httpHookBase.headers = { "X-Parallax-Session-Id": options.sessionId };
742
+ }
743
+ const hookEntry2 = [{ matcher: "", hooks: [{ ...httpHookBase }] }];
744
+ const hookEntryNoMatcher = [{ hooks: [{ ...httpHookBase }] }];
745
+ const settingsHooks2 = {
746
+ PermissionRequest: hookEntryNoMatcher,
747
+ PreToolUse: hookEntry2,
748
+ Stop: hookEntryNoMatcher,
749
+ Notification: hookEntry2,
750
+ TaskCompleted: hookEntryNoMatcher
751
+ };
752
+ return {
753
+ markerPrefix: "",
754
+ scriptPath: "",
755
+ scriptContent: "",
756
+ settingsHooks: settingsHooks2
757
+ };
758
+ }
759
+ const markerPrefix = options?.markerPrefix || CLAUDE_HOOK_MARKER_PREFIX;
760
+ const scriptPath = options?.scriptPath || ".claude/hooks/parallax-hook-telemetry.sh";
761
+ const scriptCommand = `"${"$"}CLAUDE_PROJECT_DIR"/${scriptPath}`;
762
+ const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
763
+ const settingsHooks = {
764
+ Notification: hookEntry,
765
+ PreToolUse: hookEntry,
766
+ TaskCompleted: hookEntry,
767
+ SessionEnd: hookEntry
768
+ };
769
+ const scriptContent = `#!/usr/bin/env bash
770
+ set -euo pipefail
771
+
772
+ INPUT="$(cat)"
773
+ [ -z "${"$"}INPUT" ] && exit 0
774
+
775
+ if ! command -v jq >/dev/null 2>&1; then
776
+ exit 0
777
+ fi
778
+
779
+ EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hook_event_name // empty')"
780
+ [ -z "${"$"}EVENT" ] && exit 0
781
+
782
+ NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notification_type // empty')"
783
+ TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.tool_name // empty')"
784
+ MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
785
+
786
+ printf '%s ' '${markerPrefix}'
787
+ jq -nc --arg event "${"$"}EVENT" --arg notification_type "${"$"}NOTIFICATION_TYPE" --arg tool_name "${"$"}TOOL_NAME" --arg message "${"$"}MESSAGE" '({event: $event}
788
+ + (if $notification_type != "" then {notification_type: $notification_type} else {} end)
789
+ + (if $tool_name != "" then {tool_name: $tool_name} else {} end)
790
+ + (if $message != "" then {message: $message} else {} end))'
791
+ `;
792
+ return {
793
+ markerPrefix,
794
+ scriptPath,
795
+ scriptContent,
796
+ settingsHooks
797
+ };
798
+ }
799
+ getHookMarkers(output) {
800
+ const markers = [];
801
+ const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
802
+ let match;
803
+ while ((match = markerRegex.exec(output)) !== null) {
804
+ const markerToken = match[1];
805
+ if (!markerToken.includes("CLAUDE_HOOK")) {
806
+ continue;
807
+ }
808
+ const payload = match[2];
809
+ try {
810
+ const parsed = JSON.parse(payload);
811
+ const event = typeof parsed.event === "string" ? parsed.event : void 0;
812
+ if (!event) continue;
813
+ markers.push({
814
+ event,
815
+ notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
816
+ tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
817
+ message: typeof parsed.message === "string" ? parsed.message : void 0
818
+ });
819
+ } catch {
820
+ }
821
+ }
822
+ return markers;
823
+ }
824
+ getLatestHookMarker(output) {
825
+ const markers = this.getHookMarkers(output);
826
+ return markers.length > 0 ? markers[markers.length - 1] : null;
827
+ }
828
+ stripHookMarkers(output) {
829
+ return output.replace(/(?:^|\n)\s*[A-Z0-9_]*CLAUDE_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
830
+ }
707
831
  detectLogin(output) {
708
832
  const stripped = this.stripAnsi(output);
709
833
  if (stripped.includes("Not logged in") || stripped.includes("Please run /login") || stripped.includes("please log in") || stripped.includes("run /login")) {
@@ -736,6 +860,7 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
736
860
  */
737
861
  detectBlockingPrompt(output) {
738
862
  const stripped = this.stripAnsi(output);
863
+ const marker = this.getLatestHookMarker(stripped);
739
864
  const loginDetection = this.detectLogin(output);
740
865
  if (loginDetection.required) {
741
866
  return {
@@ -747,6 +872,27 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
747
872
  instructions: loginDetection.instructions
748
873
  };
749
874
  }
875
+ if (marker?.event === "Notification") {
876
+ if (marker.notification_type === "permission_prompt") {
877
+ return {
878
+ detected: true,
879
+ type: "permission",
880
+ prompt: marker.message || "Claude permission prompt",
881
+ suggestedResponse: "keys:enter",
882
+ canAutoRespond: true,
883
+ instructions: "Claude is waiting for permission approval"
884
+ };
885
+ }
886
+ if (marker.notification_type === "elicitation_dialog") {
887
+ return {
888
+ detected: true,
889
+ type: "tool_wait",
890
+ prompt: marker.message || "Claude elicitation dialog",
891
+ canAutoRespond: false,
892
+ instructions: "Claude is waiting for required user input"
893
+ };
894
+ }
895
+ }
750
896
  if (/how is claude doing this session\?\s*\(optional\)|1:\s*bad\s+2:\s*fine\s+3:\s*good\s+0:\s*dismiss/i.test(stripped)) {
751
897
  return {
752
898
  detected: true,
@@ -845,7 +991,11 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
845
991
  */
846
992
  detectLoading(output) {
847
993
  const stripped = this.stripAnsi(output);
994
+ const marker = this.getLatestHookMarker(stripped);
848
995
  const tail = stripped.slice(-500);
996
+ if (marker?.event === "PreToolUse") {
997
+ return true;
998
+ }
849
999
  if (/esc\s+to\s+interrupt/i.test(tail)) {
850
1000
  return true;
851
1001
  }
@@ -866,7 +1016,14 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
866
1016
  */
867
1017
  detectToolRunning(output) {
868
1018
  const stripped = this.stripAnsi(output);
1019
+ const marker = this.getLatestHookMarker(stripped);
869
1020
  const tail = stripped.slice(-500);
1021
+ if (marker?.event === "PreToolUse" && marker.tool_name) {
1022
+ return {
1023
+ toolName: marker.tool_name.toLowerCase(),
1024
+ description: `${marker.tool_name} (hook)`
1025
+ };
1026
+ }
870
1027
  const contextualMatch = tail.match(/Claude\s+in\s+([A-Za-z0-9._-]+)\s*\[(\w+_tool)\]/i);
871
1028
  if (contextualMatch) {
872
1029
  const appName = contextualMatch[1];
@@ -895,7 +1052,14 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
895
1052
  */
896
1053
  detectTaskComplete(output) {
897
1054
  const stripped = this.stripAnsi(output);
1055
+ const marker = this.getLatestHookMarker(stripped);
898
1056
  if (!stripped.trim()) return false;
1057
+ if (marker?.event === "TaskCompleted") {
1058
+ return true;
1059
+ }
1060
+ if (marker?.event === "Notification" && marker.notification_type === "idle_prompt") {
1061
+ return true;
1062
+ }
899
1063
  if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
900
1064
  return false;
901
1065
  }
@@ -912,7 +1076,16 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
912
1076
  }
913
1077
  detectReady(output) {
914
1078
  const stripped = this.stripAnsi(output);
1079
+ const marker = this.getLatestHookMarker(stripped);
915
1080
  if (!stripped.trim()) return false;
1081
+ if (marker?.event === "Notification") {
1082
+ if (marker.notification_type === "permission_prompt" || marker.notification_type === "elicitation_dialog") {
1083
+ return false;
1084
+ }
1085
+ if (marker.notification_type === "idle_prompt") {
1086
+ return true;
1087
+ }
1088
+ }
916
1089
  if (/trust.*directory|do you want to|needs? your permission/i.test(stripped)) {
917
1090
  return false;
918
1091
  }
@@ -923,7 +1096,8 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
923
1096
  return hasConversationalReadyText || hasLegacyPrompt || hasShortcutsHint;
924
1097
  }
925
1098
  parseOutput(output) {
926
- const stripped = this.stripAnsi(output);
1099
+ const withoutHookMarkers = this.stripHookMarkers(output);
1100
+ const stripped = this.stripAnsi(withoutHookMarkers);
927
1101
  const isComplete = this.isResponseComplete(stripped);
928
1102
  if (!isComplete) {
929
1103
  return null;
@@ -946,9 +1120,18 @@ var ClaudeAdapter = class extends BaseCodingAdapter {
946
1120
  getHealthCheckCommand() {
947
1121
  return "claude --version";
948
1122
  }
1123
+ detectExit(output) {
1124
+ const stripped = this.stripAnsi(output);
1125
+ const marker = this.getLatestHookMarker(stripped);
1126
+ if (marker?.event === "SessionEnd") {
1127
+ return { exited: true, code: 0 };
1128
+ }
1129
+ return super.detectExit(output);
1130
+ }
949
1131
  };
950
1132
 
951
1133
  // src/gemini-adapter.ts
1134
+ var GEMINI_HOOK_MARKER_PREFIX = "PARALLAX_GEMINI_HOOK";
952
1135
  var GeminiAdapter = class extends BaseCodingAdapter {
953
1136
  adapterType = "gemini";
954
1137
  displayName = "Google Gemini";
@@ -1048,6 +1231,7 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1048
1231
  getEnv(config) {
1049
1232
  const env = {};
1050
1233
  const credentials = this.getCredentials(config);
1234
+ const adapterConfig = config.adapterConfig;
1051
1235
  if (credentials.googleKey) {
1052
1236
  env.GOOGLE_API_KEY = credentials.googleKey;
1053
1237
  env.GEMINI_API_KEY = credentials.googleKey;
@@ -1058,8 +1242,115 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1058
1242
  if (!this.isInteractive(config)) {
1059
1243
  env.NO_COLOR = "1";
1060
1244
  }
1245
+ if (adapterConfig?.geminiHookTelemetry) {
1246
+ env.PARALLAX_GEMINI_HOOK_TELEMETRY = "1";
1247
+ env.PARALLAX_GEMINI_HOOK_MARKER_PREFIX = adapterConfig.geminiHookMarkerPrefix || GEMINI_HOOK_MARKER_PREFIX;
1248
+ }
1061
1249
  return env;
1062
1250
  }
1251
+ getHookTelemetryProtocol(options) {
1252
+ if (options?.httpUrl) {
1253
+ const sessionHeader = options.sessionId ? ` -H 'X-Parallax-Session-Id: ${options.sessionId}'` : "";
1254
+ 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}"'`;
1255
+ const hookEntry2 = [{ matcher: "", hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }] }];
1256
+ const hookEntryNoMatcher = [{ hooks: [{ type: "command", command: curlCommand, timeout: 5e3 }] }];
1257
+ const settingsHooks2 = {
1258
+ BeforeTool: hookEntry2,
1259
+ AfterTool: hookEntry2,
1260
+ AfterAgent: hookEntryNoMatcher,
1261
+ SessionEnd: hookEntryNoMatcher,
1262
+ Notification: hookEntry2
1263
+ };
1264
+ return {
1265
+ markerPrefix: "",
1266
+ scriptPath: "",
1267
+ scriptContent: "",
1268
+ settingsHooks: settingsHooks2
1269
+ };
1270
+ }
1271
+ const markerPrefix = options?.markerPrefix || GEMINI_HOOK_MARKER_PREFIX;
1272
+ const scriptPath = options?.scriptPath || ".gemini/hooks/parallax-hook-telemetry.sh";
1273
+ const scriptCommand = `"${"$"}GEMINI_PROJECT_ROOT"/${scriptPath}`;
1274
+ const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
1275
+ const settingsHooks = {
1276
+ Notification: hookEntry,
1277
+ BeforeTool: hookEntry,
1278
+ AfterAgent: hookEntry,
1279
+ SessionEnd: hookEntry
1280
+ };
1281
+ const scriptContent = `#!/usr/bin/env bash
1282
+ set -euo pipefail
1283
+
1284
+ INPUT="$(cat)"
1285
+ [ -z "${"$"}INPUT" ] && exit 0
1286
+
1287
+ if ! command -v jq >/dev/null 2>&1; then
1288
+ # Valid no-op response
1289
+ printf '%s
1290
+ ' '{"continue":true}'
1291
+ exit 0
1292
+ fi
1293
+
1294
+ EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hookEventName // .hook_event_name // empty')"
1295
+ [ -z "${"$"}EVENT" ] && { printf '%s
1296
+ ' '{"continue":true}'; exit 0; }
1297
+
1298
+ NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notificationType // .notification_type // empty')"
1299
+ TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.toolName // .tool_name // empty')"
1300
+ MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
1301
+
1302
+ PAYLOAD="$(jq -nc \\
1303
+ --arg event "${"$"}EVENT" \\
1304
+ --arg notification_type "${"$"}NOTIFICATION_TYPE" \\
1305
+ --arg tool_name "${"$"}TOOL_NAME" \\
1306
+ --arg message "${"$"}MESSAGE" \\
1307
+ '({event: $event}
1308
+ + (if $notification_type != "" then {notification_type: $notification_type} else {} end)
1309
+ + (if $tool_name != "" then {tool_name: $tool_name} else {} end)
1310
+ + (if $message != "" then {message: $message} else {} end))')"
1311
+
1312
+ MARKER="${markerPrefix} ${"$"}PAYLOAD"
1313
+ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
1314
+ `;
1315
+ return {
1316
+ markerPrefix,
1317
+ scriptPath,
1318
+ scriptContent,
1319
+ settingsHooks
1320
+ };
1321
+ }
1322
+ getHookMarkers(output) {
1323
+ const markers = [];
1324
+ const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
1325
+ let match;
1326
+ while ((match = markerRegex.exec(output)) !== null) {
1327
+ const markerToken = match[1];
1328
+ if (!markerToken.includes("GEMINI_HOOK")) {
1329
+ continue;
1330
+ }
1331
+ const payload = match[2];
1332
+ try {
1333
+ const parsed = JSON.parse(payload);
1334
+ const event = typeof parsed.event === "string" ? parsed.event : void 0;
1335
+ if (!event) continue;
1336
+ markers.push({
1337
+ event,
1338
+ notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
1339
+ tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
1340
+ message: typeof parsed.message === "string" ? parsed.message : void 0
1341
+ });
1342
+ } catch {
1343
+ }
1344
+ }
1345
+ return markers;
1346
+ }
1347
+ getLatestHookMarker(output) {
1348
+ const markers = this.getHookMarkers(output);
1349
+ return markers.length > 0 ? markers[markers.length - 1] : null;
1350
+ }
1351
+ stripHookMarkers(output) {
1352
+ return output.replace(/(?:^|\n)\s*[A-Z0-9_]*GEMINI_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
1353
+ }
1063
1354
  detectLogin(output) {
1064
1355
  const stripped = this.stripAnsi(output);
1065
1356
  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")) {
@@ -1110,6 +1401,17 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1110
1401
  }
1111
1402
  detectBlockingPrompt(output) {
1112
1403
  const stripped = this.stripAnsi(output);
1404
+ const marker = this.getLatestHookMarker(stripped);
1405
+ if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
1406
+ return {
1407
+ detected: true,
1408
+ type: "permission",
1409
+ prompt: marker.message || "Gemini tool permission",
1410
+ suggestedResponse: "keys:enter",
1411
+ canAutoRespond: true,
1412
+ instructions: "Gemini is asking to allow a tool action"
1413
+ };
1414
+ }
1113
1415
  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)) {
1114
1416
  return {
1115
1417
  detected: true,
@@ -1205,7 +1507,11 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1205
1507
  */
1206
1508
  detectLoading(output) {
1207
1509
  const stripped = this.stripAnsi(output);
1510
+ const marker = this.getLatestHookMarker(stripped);
1208
1511
  const tail = stripped.slice(-500);
1512
+ if (marker?.event === "BeforeTool") {
1513
+ return true;
1514
+ }
1209
1515
  if (/esc\s+to\s+cancel/i.test(tail)) {
1210
1516
  return true;
1211
1517
  }
@@ -1214,6 +1520,17 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1214
1520
  }
1215
1521
  return false;
1216
1522
  }
1523
+ detectToolRunning(output) {
1524
+ const stripped = this.stripAnsi(output);
1525
+ const marker = this.getLatestHookMarker(stripped);
1526
+ if (marker?.event === "BeforeTool" && marker.tool_name) {
1527
+ return {
1528
+ toolName: marker.tool_name.toLowerCase(),
1529
+ description: `${marker.tool_name} (hook)`
1530
+ };
1531
+ }
1532
+ return null;
1533
+ }
1217
1534
  /**
1218
1535
  * Detect task completion for Gemini CLI.
1219
1536
  *
@@ -1226,6 +1543,10 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1226
1543
  */
1227
1544
  detectTaskComplete(output) {
1228
1545
  const stripped = this.stripAnsi(output);
1546
+ const marker = this.getLatestHookMarker(stripped);
1547
+ if (marker?.event === "AfterAgent") {
1548
+ return true;
1549
+ }
1229
1550
  if (/◇\s+Ready/.test(stripped)) {
1230
1551
  return true;
1231
1552
  }
@@ -1236,6 +1557,13 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1236
1557
  }
1237
1558
  detectReady(output) {
1238
1559
  const stripped = this.stripAnsi(output);
1560
+ const marker = this.getLatestHookMarker(stripped);
1561
+ if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
1562
+ return false;
1563
+ }
1564
+ if (marker?.event === "AfterAgent") {
1565
+ return true;
1566
+ }
1239
1567
  const hasActiveOverlay = /interactive\s+shell\s+awaiting\s+input|press\s+tab\s+to\s+focus\s+shell/i.test(stripped) || /waiting\s+for\s+user\s+confirmation|apply.?this.?change|allow.?execution|do.?you.?want.?to.?proceed/i.test(stripped) || /do.?you.?want.?to.?continue\s*\([yY]\/[nN]\)\??|are.?you.?sure\??\s*\([yY]\/[nN]\)\??/i.test(stripped) || /enable.?checkpointing.?to.?recover.?your.?session.?after.?a.?crash/i.test(stripped) || /esc\s+to\s+cancel|esc\s+to\s+interrupt/i.test(stripped);
1240
1568
  if (hasActiveOverlay) {
1241
1569
  return false;
@@ -1253,7 +1581,8 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1253
1581
  /gemini>\s*$/i.test(stripped);
1254
1582
  }
1255
1583
  parseOutput(output) {
1256
- const stripped = this.stripAnsi(output);
1584
+ const withoutHookMarkers = this.stripHookMarkers(output);
1585
+ const stripped = this.stripAnsi(withoutHookMarkers);
1257
1586
  const isComplete = this.isResponseComplete(stripped);
1258
1587
  if (!isComplete) {
1259
1588
  return null;
@@ -1277,6 +1606,13 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1277
1606
  */
1278
1607
  detectExit(output) {
1279
1608
  const stripped = this.stripAnsi(output);
1609
+ const marker = this.getLatestHookMarker(stripped);
1610
+ if (marker?.event === "SessionEnd") {
1611
+ return {
1612
+ exited: true,
1613
+ code: 0
1614
+ };
1615
+ }
1280
1616
  if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
1281
1617
  return {
1282
1618
  exited: true,
@@ -2126,6 +2462,213 @@ var AiderAdapter = class extends BaseCodingAdapter {
2126
2462
  }
2127
2463
  };
2128
2464
 
2465
+ // src/hermes-adapter.ts
2466
+ var HermesAdapter = class extends BaseCodingAdapter {
2467
+ adapterType = "hermes";
2468
+ displayName = "Hermes Agent";
2469
+ /** Prompt-toolkit TUI + spinner rendering needs a slightly longer settle. */
2470
+ readySettleMs = 400;
2471
+ installation = {
2472
+ command: 'pip install "hermes-agent[cli]"',
2473
+ alternatives: [
2474
+ 'pipx install "hermes-agent[cli]"',
2475
+ 'uv tool install "hermes-agent[cli]"'
2476
+ ],
2477
+ docsUrl: "https://github.com/NousResearch/hermes-agent"
2478
+ };
2479
+ getWorkspaceFiles() {
2480
+ return [
2481
+ {
2482
+ relativePath: "AGENTS.md",
2483
+ description: "Project instructions and architecture notes loaded by Hermes context files",
2484
+ autoLoaded: true,
2485
+ type: "memory",
2486
+ format: "markdown"
2487
+ },
2488
+ {
2489
+ relativePath: "SOUL.md",
2490
+ description: "Optional persona/context file auto-injected into Hermes system prompt",
2491
+ autoLoaded: true,
2492
+ type: "memory",
2493
+ format: "markdown"
2494
+ },
2495
+ {
2496
+ relativePath: "cli-config.yaml",
2497
+ description: "Legacy/local Hermes CLI configuration file",
2498
+ autoLoaded: true,
2499
+ type: "config",
2500
+ format: "yaml"
2501
+ }
2502
+ ];
2503
+ }
2504
+ getRecommendedModels(_credentials) {
2505
+ return {
2506
+ powerful: "anthropic/claude-opus-4.6",
2507
+ fast: "google/gemini-3-flash-preview"
2508
+ };
2509
+ }
2510
+ getCommand() {
2511
+ return "hermes";
2512
+ }
2513
+ getArgs(_config) {
2514
+ return ["chat"];
2515
+ }
2516
+ getEnv(config) {
2517
+ const env = {};
2518
+ const credentials = this.getCredentials(config);
2519
+ if (credentials.openaiKey) {
2520
+ env.OPENROUTER_API_KEY = credentials.openaiKey;
2521
+ env.OPENAI_API_KEY = credentials.openaiKey;
2522
+ }
2523
+ if (credentials.anthropicKey) {
2524
+ env.ANTHROPIC_API_KEY = credentials.anthropicKey;
2525
+ }
2526
+ if (credentials.googleKey) {
2527
+ env.GOOGLE_API_KEY = credentials.googleKey;
2528
+ env.GEMINI_API_KEY = credentials.googleKey;
2529
+ }
2530
+ if (!this.isInteractive(config)) {
2531
+ env.HERMES_QUIET = "1";
2532
+ }
2533
+ return env;
2534
+ }
2535
+ detectLogin(output) {
2536
+ const stripped = this.stripAnsi(output);
2537
+ if (/isn.?t configured yet/i.test(stripped) || /no api keys or providers found/i.test(stripped) || /run setup now\?\s*\[y\/n\]/i.test(stripped)) {
2538
+ return {
2539
+ required: true,
2540
+ type: "api_key",
2541
+ instructions: "Hermes requires provider credentials. Run: hermes setup"
2542
+ };
2543
+ }
2544
+ return { required: false };
2545
+ }
2546
+ detectBlockingPrompt(output) {
2547
+ const stripped = this.stripAnsi(output);
2548
+ const loginDetection = this.detectLogin(output);
2549
+ if (loginDetection.required) {
2550
+ return {
2551
+ detected: true,
2552
+ type: "login",
2553
+ prompt: loginDetection.instructions,
2554
+ canAutoRespond: false,
2555
+ instructions: loginDetection.instructions
2556
+ };
2557
+ }
2558
+ if (/Hermes needs your input|Other \(type your answer\)/i.test(stripped)) {
2559
+ return {
2560
+ detected: true,
2561
+ type: "tool_wait",
2562
+ prompt: "Hermes clarify prompt",
2563
+ canAutoRespond: false,
2564
+ instructions: "Hermes is waiting for clarify input (arrow keys + Enter or free text)."
2565
+ };
2566
+ }
2567
+ if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(stripped)) {
2568
+ return {
2569
+ detected: true,
2570
+ type: "tool_wait",
2571
+ prompt: "Hermes sudo password prompt",
2572
+ canAutoRespond: false,
2573
+ instructions: "Hermes terminal tool is waiting for a sudo password or skip."
2574
+ };
2575
+ }
2576
+ if (/Dangerous Command|Allow once|Allow for this session|permanent allowlist|\bDeny\b/i.test(stripped)) {
2577
+ return {
2578
+ detected: true,
2579
+ type: "permission",
2580
+ prompt: "Hermes dangerous command approval",
2581
+ canAutoRespond: false,
2582
+ instructions: "Choose approval policy (once/session/always/deny)."
2583
+ };
2584
+ }
2585
+ return super.detectBlockingPrompt(output);
2586
+ }
2587
+ detectLoading(output) {
2588
+ const stripped = this.stripAnsi(output);
2589
+ const tail = stripped.slice(-1200);
2590
+ if (/(?:pondering|contemplating|musing|cogitating|ruminating|deliberating|mulling|reflecting|processing|reasoning|analyzing|computing|synthesizing|formulating|brainstorming)\.\.\.\s*\(\d+\.\d+s\)/i.test(tail)) {
2591
+ return true;
2592
+ }
2593
+ if (/\(\d+\.\d+s\)\s*$/.test(tail) && /(?:🔍|📄|💻|⚙️|📖|✍️|🔧|🌐|👆|⌨️|📋|🧠|📚|🎨|🐍|🔀|⚡|💬)/.test(tail)) {
2594
+ return true;
2595
+ }
2596
+ if (/⚕\s*❯\s*$/.test(tail)) {
2597
+ return true;
2598
+ }
2599
+ return false;
2600
+ }
2601
+ detectTaskComplete(output) {
2602
+ if (/╭─\s*⚕\s*Hermes/i.test(output)) {
2603
+ return true;
2604
+ }
2605
+ const stripped = this.stripAnsi(output);
2606
+ if (!stripped.trim()) return false;
2607
+ if (this.detectLoading(stripped)) {
2608
+ return false;
2609
+ }
2610
+ const hasIdlePrompt = /(?:^|\n)\s*❯\s*$/m.test(stripped);
2611
+ const hasToolFeed = /(?:^|\n)\s*┊\s+\S+/m.test(stripped);
2612
+ if (hasIdlePrompt && hasToolFeed) {
2613
+ return true;
2614
+ }
2615
+ return false;
2616
+ }
2617
+ detectReady(output) {
2618
+ const stripped = this.stripAnsi(output);
2619
+ const tail = stripped.slice(-800);
2620
+ if (!tail.trim()) return false;
2621
+ if (this.detectLoading(tail)) {
2622
+ return false;
2623
+ }
2624
+ if (/Hermes needs your input|Sudo Password Required|Dangerous Command/i.test(tail)) {
2625
+ return false;
2626
+ }
2627
+ if (/(?:⚕|⚠|🔐|\?|✎)\s*❯\s*$/.test(tail)) {
2628
+ return false;
2629
+ }
2630
+ return /(?:^|\n)\s*❯\s*$/m.test(tail);
2631
+ }
2632
+ parseOutput(output) {
2633
+ const raw = output;
2634
+ const stripped = this.stripAnsi(output);
2635
+ const complete = this.detectTaskComplete(raw) || this.detectReady(stripped);
2636
+ if (!complete) {
2637
+ return null;
2638
+ }
2639
+ let content = "";
2640
+ const boxMatch = raw.match(/╭─\s*⚕\s*Hermes[^\n]*\n([\s\S]*?)\n\s*╰/i);
2641
+ if (boxMatch?.[1]) {
2642
+ content = boxMatch[1].trim();
2643
+ }
2644
+ if (!content) {
2645
+ content = this.extractContent(stripped, /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim);
2646
+ }
2647
+ return {
2648
+ type: this.containsQuestion(content) ? "question" : "response",
2649
+ content,
2650
+ isComplete: true,
2651
+ isQuestion: this.containsQuestion(content),
2652
+ metadata: {
2653
+ raw: output
2654
+ }
2655
+ };
2656
+ }
2657
+ getPromptPattern() {
2658
+ return /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/i;
2659
+ }
2660
+ detectExit(output) {
2661
+ const stripped = this.stripAnsi(output);
2662
+ if (/Goodbye!\s*⚕/i.test(stripped)) {
2663
+ return { exited: true, code: 0 };
2664
+ }
2665
+ return super.detectExit(output);
2666
+ }
2667
+ getHealthCheckCommand() {
2668
+ return "hermes version";
2669
+ }
2670
+ };
2671
+
2129
2672
  // src/pattern-loader.ts
2130
2673
  var BASELINE_PATTERNS = {
2131
2674
  claude: {
@@ -2249,6 +2792,41 @@ var BASELINE_PATTERNS = {
2249
2792
  toolWait: [],
2250
2793
  exit: [],
2251
2794
  source: "baseline"
2795
+ },
2796
+ hermes: {
2797
+ ready: [
2798
+ "\u276F",
2799
+ "\u2695 Hermes",
2800
+ "Welcome to Hermes Agent"
2801
+ ],
2802
+ auth: [
2803
+ "isn't configured yet",
2804
+ "no API keys or providers found",
2805
+ "Run setup now? [Y/n]"
2806
+ ],
2807
+ blocking: [
2808
+ "Hermes needs your input",
2809
+ "Sudo Password Required",
2810
+ "Dangerous Command"
2811
+ ],
2812
+ loading: [
2813
+ "deliberating...",
2814
+ "(0.0s)",
2815
+ "\u2695 \u276F"
2816
+ ],
2817
+ turnComplete: [
2818
+ "\u256D\u2500 \u2695 Hermes",
2819
+ "\u276F"
2820
+ ],
2821
+ toolWait: [
2822
+ "Hermes needs your input",
2823
+ "Sudo Password Required",
2824
+ "Dangerous Command"
2825
+ ],
2826
+ exit: [
2827
+ "Goodbye! \u2695"
2828
+ ],
2829
+ source: "baseline"
2252
2830
  }
2253
2831
  };
2254
2832
  var patternCache = /* @__PURE__ */ new Map();
@@ -2319,14 +2897,16 @@ function createAllAdapters() {
2319
2897
  new ClaudeAdapter(),
2320
2898
  new GeminiAdapter(),
2321
2899
  new CodexAdapter(),
2322
- new AiderAdapter()
2900
+ new AiderAdapter(),
2901
+ new HermesAdapter()
2323
2902
  ];
2324
2903
  }
2325
2904
  var ADAPTER_TYPES = {
2326
2905
  claude: ClaudeAdapter,
2327
2906
  gemini: GeminiAdapter,
2328
2907
  codex: CodexAdapter,
2329
- aider: AiderAdapter
2908
+ aider: AiderAdapter,
2909
+ hermes: HermesAdapter
2330
2910
  };
2331
2911
  function createAdapter(type) {
2332
2912
  const AdapterClass = ADAPTER_TYPES[type];
@@ -2373,6 +2953,6 @@ async function printMissingAdapters(types) {
2373
2953
  }
2374
2954
  }
2375
2955
 
2376
- export { ADAPTER_TYPES, AIDER_COMMAND_CATEGORIES, AiderAdapter, BaseCodingAdapter, CLAUDE_TOOL_CATEGORIES, CODEX_TOOL_CATEGORIES, ClaudeAdapter, CodexAdapter, GEMINI_TOOL_CATEGORIES, GeminiAdapter, PRESET_DEFINITIONS, TOOL_CATEGORIES, checkAdapters, checkAllAdapters, clearPatternCache, createAdapter, createAllAdapters, generateAiderApprovalConfig, generateApprovalConfig, generateClaudeApprovalConfig, generateCodexApprovalConfig, generateGeminiApprovalConfig, getBaselinePatterns, getPresetDefinition, hasDynamicPatterns, listPresets, loadPatterns, loadPatternsSync, preloadAllPatterns, printMissingAdapters };
2956
+ export { ADAPTER_TYPES, AIDER_COMMAND_CATEGORIES, AiderAdapter, BaseCodingAdapter, CLAUDE_TOOL_CATEGORIES, CODEX_TOOL_CATEGORIES, ClaudeAdapter, CodexAdapter, GEMINI_TOOL_CATEGORIES, GeminiAdapter, HermesAdapter, PRESET_DEFINITIONS, TOOL_CATEGORIES, checkAdapters, checkAllAdapters, clearPatternCache, createAdapter, createAllAdapters, generateAiderApprovalConfig, generateApprovalConfig, generateClaudeApprovalConfig, generateCodexApprovalConfig, generateGeminiApprovalConfig, generateHermesApprovalConfig, getBaselinePatterns, getPresetDefinition, hasDynamicPatterns, listPresets, loadPatterns, loadPatternsSync, preloadAllPatterns, printMissingAdapters };
2377
2957
  //# sourceMappingURL=index.js.map
2378
2958
  //# sourceMappingURL=index.js.map