aws-runtime-bridge 1.3.8 → 1.3.9

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.
@@ -92,6 +92,18 @@ export declare class ClaudeSdkAdapter extends EventEmitter implements BaseProvid
92
92
  * 支持 Windows / macOS / Linux
93
93
  */
94
94
  private findClaudeExecutable;
95
+ /**
96
+ * 从面板配置的命令行中提取实际可执行文件,避免把参数一起传给 PATH 查找。
97
+ */
98
+ private extractExecutableCommand;
99
+ /**
100
+ * 按当前 Bridge 进程的 PATH/PATHEXT 直接查找可执行文件,避免依赖 which/where 和 shell 解析。
101
+ */
102
+ private resolveExecutableFromPath;
103
+ private getWindowsPathExtensions;
104
+ private shouldUseDefaultClaudeFallbacks;
105
+ private isWindowsWrapperPath;
106
+ private resolveWindowsWrapper;
95
107
  /**
96
108
  * 从 wrapper 脚本解析 cli.js 路径
97
109
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ClaudeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/ClaudeSdkAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClaudeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/ClaudeSdkAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EAGpB,mBAAmB,EACnB,aAAa,EACd,MAAM,YAAY,CAAC;AAwHpB;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,YAAa,YAAW,mBAAmB;IAC/E,QAAQ,CAAC,UAAU,iBAAiB;IACpC,QAAQ,CAAC,WAAW,iBAAiB;IAErC,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,YAAY,CAAuD;IAC3E,OAAO,CAAC,gBAAgB,CAA2C;IACnE,OAAO,CAAC,SAAS,CAAiB;IAGlC,OAAO,CAAC,kBAAkB,CAGZ;IAGd,OAAO,CAAC,gBAAgB,CAGV;IAGd,OAAO,CAAC,gBAAgB,CAAkC;IAG1D,OAAO,CAAC,eAAe,CAA0D;IAGjF,OAAO,CAAC,gBAAgB,CAAkC;IAIpD,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyF5E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCpE;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAmDrB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnE,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBrF,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOlD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BxD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,EAAE;IAIzD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3D,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD,OAAO,IAAI,IAAI;IAoBf,OAAO,CAAC,YAAY,CAAiF;IACrG,OAAO,CAAC,UAAU,CAA0C;IAC5D,OAAO,CAAC,cAAc,CAAkC;IAExD;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IASzG;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAuB1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAuCtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,wBAAwB;IAShC,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,6BAA6B;IAIrC;;OAEG;YACW,kBAAkB;IA+DhC;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;;OAGG;IACG,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;YAyBF,OAAO;IAarB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA8F5B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAMhC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAsCjC,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,+BAA+B;IAIvC,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,qBAAqB;IAO7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B,OAAO,CAAC,iBAAiB;IAmFzB,OAAO,CAAC,uBAAuB;YAqCjB,aAAa;IAwB7B,OAAO,CAAC,gBAAgB;IAoKtB,OAAO,CAAC,aAAa;IAuErB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAqHzB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,cAAc;CAWvB"}
@@ -9,11 +9,11 @@
9
9
  * - 支持 AskUserQuestion 拦截并转发到聊天面板
10
10
  * - 支持 turn_complete 事件精确判断 AI 就绪状态
11
11
  */
12
+ import { execSync } from 'child_process';
12
13
  import { EventEmitter } from 'events';
13
- import { v4 as uuidv4 } from 'uuid';
14
14
  import * as fs from 'fs';
15
15
  import * as path from 'path';
16
- import { execSync } from 'child_process';
16
+ import { v4 as uuidv4 } from 'uuid';
17
17
  import { getToolActionInfo } from './types.js';
18
18
  // ============ AsyncIterableQueue ============
19
19
  /**
@@ -574,13 +574,25 @@ export class ClaudeSdkAdapter extends EventEmitter {
574
574
  * 支持 Windows / macOS / Linux
575
575
  */
576
576
  findClaudeExecutable(command) {
577
- const normalizedCommand = (command || 'claude').trim() || 'claude';
577
+ const normalizedCommand = this.extractExecutableCommand(command);
578
578
  // 1. 如果是绝对路径,直接返回
579
579
  if (path.isAbsolute(normalizedCommand) && fs.existsSync(normalizedCommand)) {
580
+ const resolvedWrapper = this.resolveWindowsWrapper(normalizedCommand);
581
+ if (resolvedWrapper) {
582
+ return resolvedWrapper;
583
+ }
584
+ if (this.isWindowsWrapperPath(normalizedCommand)) {
585
+ console.warn(`[ClaudeSdkAdapter] Skipping wrapper script for Claude Code executable: ${normalizedCommand}`);
586
+ return undefined;
587
+ }
580
588
  return normalizedCommand;
581
589
  }
582
- // 2. Windows 特定路径(优先级高)
583
- if (process.platform === 'win32') {
590
+ const pathExecutable = this.resolveExecutableFromPath(normalizedCommand);
591
+ if (pathExecutable) {
592
+ return pathExecutable;
593
+ }
594
+ // 2. Windows 特定路径(作为 claude 默认命令的兜底)
595
+ if (process.platform === 'win32' && this.shouldUseDefaultClaudeFallbacks(normalizedCommand)) {
584
596
  const userProfile = process.env.USERPROFILE || '';
585
597
  const localAppData = process.env.LOCALAPPDATA || '';
586
598
  const appData = process.env.APPDATA || '';
@@ -607,55 +619,111 @@ export class ClaudeSdkAdapter extends EventEmitter {
607
619
  }
608
620
  }
609
621
  }
610
- // 3. 使用 which/where 查找
611
- try {
612
- const findCmd = process.platform === 'win32' ? `where ${normalizedCommand}` : `which ${normalizedCommand}`;
613
- const result = execSync(findCmd, { encoding: 'utf8', timeout: 5000 }).trim();
614
- if (result) {
615
- const foundPath = result.split(/\r?\n/)[0].trim();
616
- if (fs.existsSync(foundPath)) {
617
- // 如果是 wrapper 脚本,尝试找到实际的 cli.js
618
- if (foundPath.endsWith('.cmd') || foundPath.endsWith('.ps1')) {
619
- const cliJs = this.resolveCliJsFromWrapper(foundPath);
620
- if (cliJs)
621
- return cliJs;
622
- }
623
- return foundPath;
624
- }
625
- }
626
- }
627
- catch {
628
- // 忽略查找失败
629
- }
630
- // 4. macOS/Linux 特定路径
622
+ // 3. macOS/Linux 特定路径(作为 claude 默认命令的兜底)
631
623
  const home = process.env.HOME || '';
632
624
  const candidates = [
625
+ path.join(home, '.local', 'bin', 'claude'),
626
+ path.join(home, '.npm-global', 'bin', 'claude'),
627
+ path.join(home, '.local', 'share', 'mise', 'shims', 'claude'),
628
+ path.join(home, '.asdf', 'shims', 'claude'),
629
+ `/opt/homebrew/bin/claude`,
630
+ `/usr/local/bin/claude`,
631
+ `/usr/bin/claude`,
633
632
  `/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js`,
634
633
  `/usr/lib/node_modules/@anthropic-ai/claude-code/cli.js`,
635
634
  path.join(home, '.local', 'share', 'volta', 'tools', 'shared', '@anthropic-ai', 'claude-code', 'cli.js'),
636
635
  path.join(home, '.volta', 'tools', 'shared', '@anthropic-ai', 'claude-code', 'cli.js'),
637
636
  ];
638
- for (const p of candidates) {
639
- if (p && fs.existsSync(p)) {
640
- console.log(`[ClaudeSdkAdapter] Found Claude Code CLI: ${p}`);
641
- return p;
637
+ if (this.shouldUseDefaultClaudeFallbacks(normalizedCommand)) {
638
+ for (const p of candidates) {
639
+ if (p && fs.existsSync(p)) {
640
+ console.log(`[ClaudeSdkAdapter] Found Claude Code CLI: ${p}`);
641
+ return p;
642
+ }
642
643
  }
643
644
  }
644
- // 5. 尝试 npm root -g
645
- try {
646
- const npmRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
647
- const cliJs = path.join(npmRoot, '@anthropic-ai', 'claude-code', 'cli.js');
648
- if (fs.existsSync(cliJs)) {
649
- console.log(`[ClaudeSdkAdapter] Found Claude Code CLI via npm root: ${cliJs}`);
650
- return cliJs;
645
+ // 4. 尝试 npm root -g
646
+ if (this.shouldUseDefaultClaudeFallbacks(normalizedCommand)) {
647
+ try {
648
+ const npmRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
649
+ const cliJs = path.join(npmRoot, '@anthropic-ai', 'claude-code', 'cli.js');
650
+ if (fs.existsSync(cliJs)) {
651
+ console.log(`[ClaudeSdkAdapter] Found Claude Code CLI via npm root: ${cliJs}`);
652
+ return cliJs;
653
+ }
654
+ }
655
+ catch {
656
+ // 忽略
651
657
  }
652
- }
653
- catch {
654
- // 忽略
655
658
  }
656
659
  console.warn(`[ClaudeSdkAdapter] Claude Code executable not found for command: ${normalizedCommand}`);
657
660
  return undefined;
658
661
  }
662
+ /**
663
+ * 从面板配置的命令行中提取实际可执行文件,避免把参数一起传给 PATH 查找。
664
+ */
665
+ extractExecutableCommand(command) {
666
+ const input = (command || 'claude').trim() || 'claude';
667
+ const firstToken = input.match(/^\s*(?:"([^"]+)"|'([^']+)'|(\S+))/);
668
+ return firstToken?.[1] || firstToken?.[2] || firstToken?.[3] || 'claude';
669
+ }
670
+ /**
671
+ * 按当前 Bridge 进程的 PATH/PATHEXT 直接查找可执行文件,避免依赖 which/where 和 shell 解析。
672
+ */
673
+ resolveExecutableFromPath(command) {
674
+ const pathValue = process.env.PATH || '';
675
+ if (!pathValue) {
676
+ return undefined;
677
+ }
678
+ const commandHasExtension = path.extname(command).length > 0;
679
+ const extensions = process.platform === 'win32'
680
+ ? this.getWindowsPathExtensions(commandHasExtension)
681
+ : [''];
682
+ for (const directory of pathValue.split(path.delimiter)) {
683
+ if (!directory) {
684
+ continue;
685
+ }
686
+ for (const extension of extensions) {
687
+ const candidate = path.join(directory, `${command}${extension}`);
688
+ if (!fs.existsSync(candidate)) {
689
+ continue;
690
+ }
691
+ if (this.isWindowsWrapperPath(candidate)) {
692
+ const resolvedWrapper = this.resolveWindowsWrapper(candidate);
693
+ if (resolvedWrapper) {
694
+ return resolvedWrapper;
695
+ }
696
+ continue;
697
+ }
698
+ console.log(`[ClaudeSdkAdapter] Found Claude Code CLI via PATH: ${candidate}`);
699
+ return candidate;
700
+ }
701
+ }
702
+ return undefined;
703
+ }
704
+ getWindowsPathExtensions(commandHasExtension) {
705
+ if (commandHasExtension) {
706
+ return [''];
707
+ }
708
+ const pathExt = process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM;.PS1';
709
+ return pathExt
710
+ .split(';')
711
+ .map((extension) => extension.trim())
712
+ .filter((extension) => extension.length > 0);
713
+ }
714
+ shouldUseDefaultClaudeFallbacks(command) {
715
+ return ['claude', 'claude-code', 'claudecode'].includes(command.toLowerCase());
716
+ }
717
+ isWindowsWrapperPath(filePath) {
718
+ const extension = path.extname(filePath).toLowerCase();
719
+ return ['.cmd', '.bat', '.ps1'].includes(extension);
720
+ }
721
+ resolveWindowsWrapper(wrapperPath) {
722
+ if (!this.isWindowsWrapperPath(wrapperPath)) {
723
+ return undefined;
724
+ }
725
+ return this.resolveCliJsFromWrapper(wrapperPath);
726
+ }
659
727
  /**
660
728
  * 从 wrapper 脚本解析 cli.js 路径
661
729
  */
@@ -669,6 +737,11 @@ export class ClaudeSdkAdapter extends EventEmitter {
669
737
  return voltaShared;
670
738
  }
671
739
  }
740
+ const wrapperDirectory = path.dirname(wrapperPath);
741
+ const npmCliJs = path.join(wrapperDirectory, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
742
+ if (fs.existsSync(npmCliJs)) {
743
+ return npmCliJs;
744
+ }
672
745
  }
673
746
  catch {
674
747
  // 忽略
@@ -1063,7 +1136,7 @@ export class ClaudeSdkAdapter extends EventEmitter {
1063
1136
  toolInput = JSON.parse(jsonStr);
1064
1137
  console.log(`[ClaudeSdkAdapter] Parsed tool input:`, JSON.stringify(toolInput).slice(0, 200));
1065
1138
  }
1066
- catch (e) {
1139
+ catch {
1067
1140
  console.warn(`[ClaudeSdkAdapter] Failed to parse tool input JSON:`, jsonStr.slice(0, 100));
1068
1141
  }
1069
1142
  }
@@ -1,3 +1,6 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
1
4
  import { afterEach, describe, expect, it, vi } from 'vitest';
2
5
  import { ClaudeSdkAdapter } from './ClaudeSdkAdapter.js';
3
6
  describe('ClaudeSdkAdapter', () => {
@@ -5,6 +8,149 @@ describe('ClaudeSdkAdapter', () => {
5
8
  vi.useRealTimers();
6
9
  vi.restoreAllMocks();
7
10
  });
11
+ it('resolves claude from PATH without shelling out to which or where', () => {
12
+ const adapter = new ClaudeSdkAdapter();
13
+ const originalPath = process.env.PATH;
14
+ const originalPathExt = process.env.PATHEXT;
15
+ const commandDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-path-'));
16
+ const commandPath = path.join(commandDirectory, process.platform === 'win32' ? 'claude.EXE' : 'claude');
17
+ fs.writeFileSync(commandPath, '#!/usr/bin/env node\n');
18
+ if (process.platform !== 'win32') {
19
+ fs.chmodSync(commandPath, 0o755);
20
+ }
21
+ process.env.PATH = commandDirectory;
22
+ process.env.PATHEXT = '.EXE;.CMD';
23
+ try {
24
+ const result = adapter.findClaudeExecutable('claude');
25
+ expect(result).toBe(commandPath);
26
+ }
27
+ finally {
28
+ process.env.PATH = originalPath;
29
+ process.env.PATHEXT = originalPathExt;
30
+ fs.rmSync(commandDirectory, { recursive: true, force: true });
31
+ }
32
+ });
33
+ it('extracts the executable before resolving commands that include flags', () => {
34
+ const adapter = new ClaudeSdkAdapter();
35
+ const originalPath = process.env.PATH;
36
+ const originalPathExt = process.env.PATHEXT;
37
+ const commandDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-flags-'));
38
+ const commandPath = path.join(commandDirectory, process.platform === 'win32' ? 'claude.EXE' : 'claude');
39
+ const unexpectedCommandWithFlags = path.join(commandDirectory, process.platform === 'win32' ? 'claude --model sonnet.EXE' : 'claude --model sonnet');
40
+ fs.writeFileSync(commandPath, '#!/usr/bin/env node\n');
41
+ if (process.platform !== 'win32') {
42
+ fs.chmodSync(commandPath, 0o755);
43
+ }
44
+ process.env.PATH = commandDirectory;
45
+ process.env.PATHEXT = '.EXE;.CMD';
46
+ try {
47
+ const result = adapter.findClaudeExecutable('claude --model sonnet');
48
+ expect(result).toBe(commandPath);
49
+ expect(fs.existsSync(unexpectedCommandWithFlags)).toBe(false);
50
+ }
51
+ finally {
52
+ process.env.PATH = originalPath;
53
+ process.env.PATHEXT = originalPathExt;
54
+ fs.rmSync(commandDirectory, { recursive: true, force: true });
55
+ }
56
+ });
57
+ it.skipIf(process.platform !== 'win32')('resolves uppercase Windows wrapper extensions before returning PATH candidate', () => {
58
+ const adapter = new ClaudeSdkAdapter();
59
+ const originalPath = process.env.PATH;
60
+ const originalPathExt = process.env.PATHEXT;
61
+ const originalLocalAppData = process.env.LOCALAPPDATA;
62
+ const commandDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-wrapper-'));
63
+ const localAppDataDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-localappdata-'));
64
+ const wrapperPath = path.join(commandDirectory, 'claude.CMD');
65
+ const cliPath = path.join(localAppDataDirectory, 'Volta', 'tools', 'shared', '@anthropic-ai', 'claude-code', 'cli.js');
66
+ fs.mkdirSync(path.dirname(cliPath), { recursive: true });
67
+ fs.writeFileSync(wrapperPath, 'volta run %~n0 %*\n');
68
+ fs.writeFileSync(cliPath, '#!/usr/bin/env node\n');
69
+ process.env.PATH = commandDirectory;
70
+ process.env.PATHEXT = '.EXE;.CMD;.PS1';
71
+ process.env.LOCALAPPDATA = localAppDataDirectory;
72
+ try {
73
+ const result = adapter.findClaudeExecutable('claude');
74
+ expect(result).toBe(cliPath);
75
+ }
76
+ finally {
77
+ process.env.PATH = originalPath;
78
+ process.env.PATHEXT = originalPathExt;
79
+ process.env.LOCALAPPDATA = originalLocalAppData;
80
+ fs.rmSync(commandDirectory, { recursive: true, force: true });
81
+ fs.rmSync(localAppDataDirectory, { recursive: true, force: true });
82
+ }
83
+ });
84
+ it.skipIf(process.platform !== 'win32')('resolves npm command wrappers to the Claude Code cli.js file', () => {
85
+ const adapter = new ClaudeSdkAdapter();
86
+ const originalPath = process.env.PATH;
87
+ const originalPathExt = process.env.PATHEXT;
88
+ const commandDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-npm-wrapper-'));
89
+ const wrapperPath = path.join(commandDirectory, process.platform === 'win32' ? 'claude.CMD' : 'claude.ps1');
90
+ const cliPath = path.join(commandDirectory, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
91
+ fs.mkdirSync(path.dirname(cliPath), { recursive: true });
92
+ fs.writeFileSync(wrapperPath, '@ECHO off\nnode "%~dp0\\node_modules\\@anthropic-ai\\claude-code\\cli.js" %*\n');
93
+ fs.writeFileSync(cliPath, '#!/usr/bin/env node\n');
94
+ process.env.PATH = commandDirectory;
95
+ process.env.PATHEXT = '.EXE;.CMD;.PS1';
96
+ try {
97
+ const result = adapter.findClaudeExecutable('claude');
98
+ expect(result).toBe(cliPath);
99
+ }
100
+ finally {
101
+ process.env.PATH = originalPath;
102
+ process.env.PATHEXT = originalPathExt;
103
+ fs.rmSync(commandDirectory, { recursive: true, force: true });
104
+ }
105
+ });
106
+ it('skips unresolved command wrappers instead of returning them to the SDK', () => {
107
+ const adapter = new ClaudeSdkAdapter();
108
+ const originalPath = process.env.PATH;
109
+ const originalPathExt = process.env.PATHEXT;
110
+ const commandDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-unresolved-wrapper-'));
111
+ const wrapperPath = path.join(commandDirectory, 'claude.CMD');
112
+ fs.writeFileSync(wrapperPath, '@ECHO off\nnode missing-cli.js %*\n');
113
+ process.env.PATH = commandDirectory;
114
+ process.env.PATHEXT = '.EXE;.CMD;.PS1';
115
+ try {
116
+ const result = adapter.findClaudeExecutable('claude');
117
+ expect(result).not.toBe(wrapperPath);
118
+ }
119
+ finally {
120
+ process.env.PATH = originalPath;
121
+ process.env.PATHEXT = originalPathExt;
122
+ fs.rmSync(commandDirectory, { recursive: true, force: true });
123
+ }
124
+ });
125
+ it('resolves absolute npm command wrappers before passing them to the SDK', () => {
126
+ const adapter = new ClaudeSdkAdapter();
127
+ const commandDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-absolute-wrapper-'));
128
+ const wrapperPath = path.join(commandDirectory, 'claude.cmd');
129
+ const cliPath = path.join(commandDirectory, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
130
+ fs.mkdirSync(path.dirname(cliPath), { recursive: true });
131
+ fs.writeFileSync(wrapperPath, '@ECHO off\nnode "%~dp0\\node_modules\\@anthropic-ai\\claude-code\\cli.js" %*\n');
132
+ fs.writeFileSync(cliPath, '#!/usr/bin/env node\n');
133
+ try {
134
+ const result = adapter.findClaudeExecutable(wrapperPath);
135
+ expect(result).toBe(cliPath);
136
+ }
137
+ finally {
138
+ fs.rmSync(commandDirectory, { recursive: true, force: true });
139
+ }
140
+ });
141
+ it('does not pass unresolved absolute wrapper scripts to the SDK', () => {
142
+ const adapter = new ClaudeSdkAdapter();
143
+ const commandDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-absolute-unresolved-wrapper-'));
144
+ const wrapperPath = path.join(commandDirectory, 'claude.bat');
145
+ fs.writeFileSync(wrapperPath, '@ECHO off\nnode missing-cli.js %*\n');
146
+ try {
147
+ const result = adapter.findClaudeExecutable(wrapperPath);
148
+ expect(result).toBeUndefined();
149
+ }
150
+ finally {
151
+ fs.rmSync(commandDirectory, { recursive: true, force: true });
152
+ }
153
+ });
8
154
  it('does not auto-wake Claude for my_task-style idle commands', async () => {
9
155
  vi.useFakeTimers();
10
156
  const adapter = new ClaudeSdkAdapter();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",