coding-agent-adapters 0.11.0 → 0.12.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
@@ -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,96 @@ 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
+ const markerPrefix = options?.markerPrefix || GEMINI_HOOK_MARKER_PREFIX;
1253
+ const scriptPath = options?.scriptPath || ".gemini/hooks/parallax-hook-telemetry.sh";
1254
+ const scriptCommand = `"${"$"}GEMINI_PROJECT_ROOT"/${scriptPath}`;
1255
+ const hookEntry = [{ matcher: "", hooks: [{ type: "command", command: scriptCommand }] }];
1256
+ const settingsHooks = {
1257
+ Notification: hookEntry,
1258
+ BeforeTool: hookEntry,
1259
+ AfterAgent: hookEntry,
1260
+ SessionEnd: hookEntry
1261
+ };
1262
+ const scriptContent = `#!/usr/bin/env bash
1263
+ set -euo pipefail
1264
+
1265
+ INPUT="$(cat)"
1266
+ [ -z "${"$"}INPUT" ] && exit 0
1267
+
1268
+ if ! command -v jq >/dev/null 2>&1; then
1269
+ # Valid no-op response
1270
+ printf '%s
1271
+ ' '{"continue":true}'
1272
+ exit 0
1273
+ fi
1274
+
1275
+ EVENT="$(printf '%s' "${"$"}INPUT" | jq -r '.hookEventName // .hook_event_name // empty')"
1276
+ [ -z "${"$"}EVENT" ] && { printf '%s
1277
+ ' '{"continue":true}'; exit 0; }
1278
+
1279
+ NOTIFICATION_TYPE="$(printf '%s' "${"$"}INPUT" | jq -r '.notificationType // .notification_type // empty')"
1280
+ TOOL_NAME="$(printf '%s' "${"$"}INPUT" | jq -r '.toolName // .tool_name // empty')"
1281
+ MESSAGE="$(printf '%s' "${"$"}INPUT" | jq -r '.message // empty')"
1282
+
1283
+ PAYLOAD="$(jq -nc \\
1284
+ --arg event "${"$"}EVENT" \\
1285
+ --arg notification_type "${"$"}NOTIFICATION_TYPE" \\
1286
+ --arg tool_name "${"$"}TOOL_NAME" \\
1287
+ --arg message "${"$"}MESSAGE" \\
1288
+ '({event: $event}
1289
+ + (if $notification_type != "" then {notification_type: $notification_type} else {} end)
1290
+ + (if $tool_name != "" then {tool_name: $tool_name} else {} end)
1291
+ + (if $message != "" then {message: $message} else {} end))')"
1292
+
1293
+ MARKER="${markerPrefix} ${"$"}PAYLOAD"
1294
+ jq -nc --arg m "${"$"}MARKER" '{continue: true, suppressOutput: true, systemMessage: $m}'
1295
+ `;
1296
+ return {
1297
+ markerPrefix,
1298
+ scriptPath,
1299
+ scriptContent,
1300
+ settingsHooks
1301
+ };
1302
+ }
1303
+ getHookMarkers(output) {
1304
+ const markers = [];
1305
+ const markerRegex = /(?:^|\n)\s*([A-Z0-9_]+)\s+(\{[^\n\r]+\})/g;
1306
+ let match;
1307
+ while ((match = markerRegex.exec(output)) !== null) {
1308
+ const markerToken = match[1];
1309
+ if (!markerToken.includes("GEMINI_HOOK")) {
1310
+ continue;
1311
+ }
1312
+ const payload = match[2];
1313
+ try {
1314
+ const parsed = JSON.parse(payload);
1315
+ const event = typeof parsed.event === "string" ? parsed.event : void 0;
1316
+ if (!event) continue;
1317
+ markers.push({
1318
+ event,
1319
+ notification_type: typeof parsed.notification_type === "string" ? parsed.notification_type : void 0,
1320
+ tool_name: typeof parsed.tool_name === "string" ? parsed.tool_name : void 0,
1321
+ message: typeof parsed.message === "string" ? parsed.message : void 0
1322
+ });
1323
+ } catch {
1324
+ }
1325
+ }
1326
+ return markers;
1327
+ }
1328
+ getLatestHookMarker(output) {
1329
+ const markers = this.getHookMarkers(output);
1330
+ return markers.length > 0 ? markers[markers.length - 1] : null;
1331
+ }
1332
+ stripHookMarkers(output) {
1333
+ return output.replace(/(?:^|\n)\s*[A-Z0-9_]*GEMINI_HOOK[A-Z0-9_]*\s+\{[^\n\r]+\}\s*/g, "\n");
1334
+ }
1063
1335
  detectLogin(output) {
1064
1336
  const stripped = this.stripAnsi(output);
1065
1337
  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 +1382,17 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1110
1382
  }
1111
1383
  detectBlockingPrompt(output) {
1112
1384
  const stripped = this.stripAnsi(output);
1385
+ const marker = this.getLatestHookMarker(stripped);
1386
+ if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
1387
+ return {
1388
+ detected: true,
1389
+ type: "permission",
1390
+ prompt: marker.message || "Gemini tool permission",
1391
+ suggestedResponse: "keys:enter",
1392
+ canAutoRespond: true,
1393
+ instructions: "Gemini is asking to allow a tool action"
1394
+ };
1395
+ }
1113
1396
  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
1397
  return {
1115
1398
  detected: true,
@@ -1205,7 +1488,11 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1205
1488
  */
1206
1489
  detectLoading(output) {
1207
1490
  const stripped = this.stripAnsi(output);
1491
+ const marker = this.getLatestHookMarker(stripped);
1208
1492
  const tail = stripped.slice(-500);
1493
+ if (marker?.event === "BeforeTool") {
1494
+ return true;
1495
+ }
1209
1496
  if (/esc\s+to\s+cancel/i.test(tail)) {
1210
1497
  return true;
1211
1498
  }
@@ -1214,6 +1501,17 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1214
1501
  }
1215
1502
  return false;
1216
1503
  }
1504
+ detectToolRunning(output) {
1505
+ const stripped = this.stripAnsi(output);
1506
+ const marker = this.getLatestHookMarker(stripped);
1507
+ if (marker?.event === "BeforeTool" && marker.tool_name) {
1508
+ return {
1509
+ toolName: marker.tool_name.toLowerCase(),
1510
+ description: `${marker.tool_name} (hook)`
1511
+ };
1512
+ }
1513
+ return null;
1514
+ }
1217
1515
  /**
1218
1516
  * Detect task completion for Gemini CLI.
1219
1517
  *
@@ -1226,6 +1524,10 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1226
1524
  */
1227
1525
  detectTaskComplete(output) {
1228
1526
  const stripped = this.stripAnsi(output);
1527
+ const marker = this.getLatestHookMarker(stripped);
1528
+ if (marker?.event === "AfterAgent") {
1529
+ return true;
1530
+ }
1229
1531
  if (/◇\s+Ready/.test(stripped)) {
1230
1532
  return true;
1231
1533
  }
@@ -1236,6 +1538,13 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1236
1538
  }
1237
1539
  detectReady(output) {
1238
1540
  const stripped = this.stripAnsi(output);
1541
+ const marker = this.getLatestHookMarker(stripped);
1542
+ if (marker?.event === "Notification" && marker.notification_type === "ToolPermission") {
1543
+ return false;
1544
+ }
1545
+ if (marker?.event === "AfterAgent") {
1546
+ return true;
1547
+ }
1239
1548
  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
1549
  if (hasActiveOverlay) {
1241
1550
  return false;
@@ -1253,7 +1562,8 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1253
1562
  /gemini>\s*$/i.test(stripped);
1254
1563
  }
1255
1564
  parseOutput(output) {
1256
- const stripped = this.stripAnsi(output);
1565
+ const withoutHookMarkers = this.stripHookMarkers(output);
1566
+ const stripped = this.stripAnsi(withoutHookMarkers);
1257
1567
  const isComplete = this.isResponseComplete(stripped);
1258
1568
  if (!isComplete) {
1259
1569
  return null;
@@ -1277,6 +1587,13 @@ var GeminiAdapter = class extends BaseCodingAdapter {
1277
1587
  */
1278
1588
  detectExit(output) {
1279
1589
  const stripped = this.stripAnsi(output);
1590
+ const marker = this.getLatestHookMarker(stripped);
1591
+ if (marker?.event === "SessionEnd") {
1592
+ return {
1593
+ exited: true,
1594
+ code: 0
1595
+ };
1596
+ }
1280
1597
  if (/folder.?trust.?level.?must.?be.?selected.*exiting/i.test(stripped)) {
1281
1598
  return {
1282
1599
  exited: true,
@@ -2126,6 +2443,213 @@ var AiderAdapter = class extends BaseCodingAdapter {
2126
2443
  }
2127
2444
  };
2128
2445
 
2446
+ // src/hermes-adapter.ts
2447
+ var HermesAdapter = class extends BaseCodingAdapter {
2448
+ adapterType = "hermes";
2449
+ displayName = "Hermes Agent";
2450
+ /** Prompt-toolkit TUI + spinner rendering needs a slightly longer settle. */
2451
+ readySettleMs = 400;
2452
+ installation = {
2453
+ command: 'pip install "hermes-agent[cli]"',
2454
+ alternatives: [
2455
+ 'pipx install "hermes-agent[cli]"',
2456
+ 'uv tool install "hermes-agent[cli]"'
2457
+ ],
2458
+ docsUrl: "https://github.com/NousResearch/hermes-agent"
2459
+ };
2460
+ getWorkspaceFiles() {
2461
+ return [
2462
+ {
2463
+ relativePath: "AGENTS.md",
2464
+ description: "Project instructions and architecture notes loaded by Hermes context files",
2465
+ autoLoaded: true,
2466
+ type: "memory",
2467
+ format: "markdown"
2468
+ },
2469
+ {
2470
+ relativePath: "SOUL.md",
2471
+ description: "Optional persona/context file auto-injected into Hermes system prompt",
2472
+ autoLoaded: true,
2473
+ type: "memory",
2474
+ format: "markdown"
2475
+ },
2476
+ {
2477
+ relativePath: "cli-config.yaml",
2478
+ description: "Legacy/local Hermes CLI configuration file",
2479
+ autoLoaded: true,
2480
+ type: "config",
2481
+ format: "yaml"
2482
+ }
2483
+ ];
2484
+ }
2485
+ getRecommendedModels(_credentials) {
2486
+ return {
2487
+ powerful: "anthropic/claude-opus-4.6",
2488
+ fast: "google/gemini-3-flash-preview"
2489
+ };
2490
+ }
2491
+ getCommand() {
2492
+ return "hermes";
2493
+ }
2494
+ getArgs(_config) {
2495
+ return ["chat"];
2496
+ }
2497
+ getEnv(config) {
2498
+ const env = {};
2499
+ const credentials = this.getCredentials(config);
2500
+ if (credentials.openaiKey) {
2501
+ env.OPENROUTER_API_KEY = credentials.openaiKey;
2502
+ env.OPENAI_API_KEY = credentials.openaiKey;
2503
+ }
2504
+ if (credentials.anthropicKey) {
2505
+ env.ANTHROPIC_API_KEY = credentials.anthropicKey;
2506
+ }
2507
+ if (credentials.googleKey) {
2508
+ env.GOOGLE_API_KEY = credentials.googleKey;
2509
+ env.GEMINI_API_KEY = credentials.googleKey;
2510
+ }
2511
+ if (!this.isInteractive(config)) {
2512
+ env.HERMES_QUIET = "1";
2513
+ }
2514
+ return env;
2515
+ }
2516
+ detectLogin(output) {
2517
+ const stripped = this.stripAnsi(output);
2518
+ 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)) {
2519
+ return {
2520
+ required: true,
2521
+ type: "api_key",
2522
+ instructions: "Hermes requires provider credentials. Run: hermes setup"
2523
+ };
2524
+ }
2525
+ return { required: false };
2526
+ }
2527
+ detectBlockingPrompt(output) {
2528
+ const stripped = this.stripAnsi(output);
2529
+ const loginDetection = this.detectLogin(output);
2530
+ if (loginDetection.required) {
2531
+ return {
2532
+ detected: true,
2533
+ type: "login",
2534
+ prompt: loginDetection.instructions,
2535
+ canAutoRespond: false,
2536
+ instructions: loginDetection.instructions
2537
+ };
2538
+ }
2539
+ if (/Hermes needs your input|Other \(type your answer\)/i.test(stripped)) {
2540
+ return {
2541
+ detected: true,
2542
+ type: "tool_wait",
2543
+ prompt: "Hermes clarify prompt",
2544
+ canAutoRespond: false,
2545
+ instructions: "Hermes is waiting for clarify input (arrow keys + Enter or free text)."
2546
+ };
2547
+ }
2548
+ if (/Sudo Password Required|password hidden|Password \(hidden\):/i.test(stripped)) {
2549
+ return {
2550
+ detected: true,
2551
+ type: "tool_wait",
2552
+ prompt: "Hermes sudo password prompt",
2553
+ canAutoRespond: false,
2554
+ instructions: "Hermes terminal tool is waiting for a sudo password or skip."
2555
+ };
2556
+ }
2557
+ if (/Dangerous Command|Allow once|Allow for this session|permanent allowlist|\bDeny\b/i.test(stripped)) {
2558
+ return {
2559
+ detected: true,
2560
+ type: "permission",
2561
+ prompt: "Hermes dangerous command approval",
2562
+ canAutoRespond: false,
2563
+ instructions: "Choose approval policy (once/session/always/deny)."
2564
+ };
2565
+ }
2566
+ return super.detectBlockingPrompt(output);
2567
+ }
2568
+ detectLoading(output) {
2569
+ const stripped = this.stripAnsi(output);
2570
+ const tail = stripped.slice(-1200);
2571
+ if (/(?:pondering|contemplating|musing|cogitating|ruminating|deliberating|mulling|reflecting|processing|reasoning|analyzing|computing|synthesizing|formulating|brainstorming)\.\.\.\s*\(\d+\.\d+s\)/i.test(tail)) {
2572
+ return true;
2573
+ }
2574
+ if (/\(\d+\.\d+s\)\s*$/.test(tail) && /(?:🔍|📄|💻|⚙️|📖|✍️|🔧|🌐|👆|⌨️|📋|🧠|📚|🎨|🐍|🔀|⚡|💬)/.test(tail)) {
2575
+ return true;
2576
+ }
2577
+ if (/⚕\s*❯\s*$/.test(tail)) {
2578
+ return true;
2579
+ }
2580
+ return false;
2581
+ }
2582
+ detectTaskComplete(output) {
2583
+ if (/╭─\s*⚕\s*Hermes/i.test(output)) {
2584
+ return true;
2585
+ }
2586
+ const stripped = this.stripAnsi(output);
2587
+ if (!stripped.trim()) return false;
2588
+ if (this.detectLoading(stripped)) {
2589
+ return false;
2590
+ }
2591
+ const hasIdlePrompt = /(?:^|\n)\s*❯\s*$/m.test(stripped);
2592
+ const hasToolFeed = /(?:^|\n)\s*┊\s+\S+/m.test(stripped);
2593
+ if (hasIdlePrompt && hasToolFeed) {
2594
+ return true;
2595
+ }
2596
+ return false;
2597
+ }
2598
+ detectReady(output) {
2599
+ const stripped = this.stripAnsi(output);
2600
+ const tail = stripped.slice(-800);
2601
+ if (!tail.trim()) return false;
2602
+ if (this.detectLoading(tail)) {
2603
+ return false;
2604
+ }
2605
+ if (/Hermes needs your input|Sudo Password Required|Dangerous Command/i.test(tail)) {
2606
+ return false;
2607
+ }
2608
+ if (/(?:⚕|⚠|🔐|\?|✎)\s*❯\s*$/.test(tail)) {
2609
+ return false;
2610
+ }
2611
+ return /(?:^|\n)\s*❯\s*$/m.test(tail);
2612
+ }
2613
+ parseOutput(output) {
2614
+ const raw = output;
2615
+ const stripped = this.stripAnsi(output);
2616
+ const complete = this.detectTaskComplete(raw) || this.detectReady(stripped);
2617
+ if (!complete) {
2618
+ return null;
2619
+ }
2620
+ let content = "";
2621
+ const boxMatch = raw.match(/╭─\s*⚕\s*Hermes[^\n]*\n([\s\S]*?)\n\s*╰/i);
2622
+ if (boxMatch?.[1]) {
2623
+ content = boxMatch[1].trim();
2624
+ }
2625
+ if (!content) {
2626
+ content = this.extractContent(stripped, /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/gim);
2627
+ }
2628
+ return {
2629
+ type: this.containsQuestion(content) ? "question" : "response",
2630
+ content,
2631
+ isComplete: true,
2632
+ isQuestion: this.containsQuestion(content),
2633
+ metadata: {
2634
+ raw: output
2635
+ }
2636
+ };
2637
+ }
2638
+ getPromptPattern() {
2639
+ return /(?:^|\n)\s*(?:⚕|⚠|🔐|\?|✎)?\s*❯\s*$/i;
2640
+ }
2641
+ detectExit(output) {
2642
+ const stripped = this.stripAnsi(output);
2643
+ if (/Goodbye!\s*⚕/i.test(stripped)) {
2644
+ return { exited: true, code: 0 };
2645
+ }
2646
+ return super.detectExit(output);
2647
+ }
2648
+ getHealthCheckCommand() {
2649
+ return "hermes version";
2650
+ }
2651
+ };
2652
+
2129
2653
  // src/pattern-loader.ts
2130
2654
  var BASELINE_PATTERNS = {
2131
2655
  claude: {
@@ -2249,6 +2773,41 @@ var BASELINE_PATTERNS = {
2249
2773
  toolWait: [],
2250
2774
  exit: [],
2251
2775
  source: "baseline"
2776
+ },
2777
+ hermes: {
2778
+ ready: [
2779
+ "\u276F",
2780
+ "\u2695 Hermes",
2781
+ "Welcome to Hermes Agent"
2782
+ ],
2783
+ auth: [
2784
+ "isn't configured yet",
2785
+ "no API keys or providers found",
2786
+ "Run setup now? [Y/n]"
2787
+ ],
2788
+ blocking: [
2789
+ "Hermes needs your input",
2790
+ "Sudo Password Required",
2791
+ "Dangerous Command"
2792
+ ],
2793
+ loading: [
2794
+ "deliberating...",
2795
+ "(0.0s)",
2796
+ "\u2695 \u276F"
2797
+ ],
2798
+ turnComplete: [
2799
+ "\u256D\u2500 \u2695 Hermes",
2800
+ "\u276F"
2801
+ ],
2802
+ toolWait: [
2803
+ "Hermes needs your input",
2804
+ "Sudo Password Required",
2805
+ "Dangerous Command"
2806
+ ],
2807
+ exit: [
2808
+ "Goodbye! \u2695"
2809
+ ],
2810
+ source: "baseline"
2252
2811
  }
2253
2812
  };
2254
2813
  var patternCache = /* @__PURE__ */ new Map();
@@ -2319,14 +2878,16 @@ function createAllAdapters() {
2319
2878
  new ClaudeAdapter(),
2320
2879
  new GeminiAdapter(),
2321
2880
  new CodexAdapter(),
2322
- new AiderAdapter()
2881
+ new AiderAdapter(),
2882
+ new HermesAdapter()
2323
2883
  ];
2324
2884
  }
2325
2885
  var ADAPTER_TYPES = {
2326
2886
  claude: ClaudeAdapter,
2327
2887
  gemini: GeminiAdapter,
2328
2888
  codex: CodexAdapter,
2329
- aider: AiderAdapter
2889
+ aider: AiderAdapter,
2890
+ hermes: HermesAdapter
2330
2891
  };
2331
2892
  function createAdapter(type) {
2332
2893
  const AdapterClass = ADAPTER_TYPES[type];
@@ -2373,6 +2934,6 @@ async function printMissingAdapters(types) {
2373
2934
  }
2374
2935
  }
2375
2936
 
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 };
2937
+ 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
2938
  //# sourceMappingURL=index.js.map
2378
2939
  //# sourceMappingURL=index.js.map