@adhdev/daemon-core 0.9.82-rc.301 → 0.9.82-rc.302

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.301",
3
+ "version": "0.9.82-rc.302",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "author": "vilmire",
47
47
  "license": "AGPL-3.0-or-later",
48
48
  "dependencies": {
49
- "@adhdev/mesh-shared": "0.9.82-rc.301",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.302",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -1,5 +1,6 @@
1
1
  import * as os from 'os';
2
2
  import { ensureNodePtySpawnHelperPermissions } from './spawn-env.js';
3
+ import { resolveWin32Executable } from './resolve-executable.js';
3
4
 
4
5
  let cachedPty: any | null | undefined;
5
6
 
@@ -119,7 +120,7 @@ export class NodePtyTransportFactory implements PtyTransportFactory {
119
120
  cwd = os.homedir();
120
121
  }
121
122
  }
122
- const handle = pty.spawn(command, args, {
123
+ const handle = pty.spawn(resolveWin32Executable(command), args, {
123
124
  name: 'xterm-256color',
124
125
  cols: options.cols,
125
126
  rows: options.rows,
@@ -0,0 +1,45 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import * as path from 'path';
4
+
5
+ // Extensions ConPTY/CreateProcess can launch directly (not .cmd/.bat, which
6
+ // need a cmd.exe wrapper).
7
+ const DIRECT_EXEC_EXT = new Set(['.exe', '.com']);
8
+
9
+ /**
10
+ * Resolve a launch command to an absolute executable path on Windows.
11
+ *
12
+ * node-pty's ConPTY backend resolves a bare/relative command against the
13
+ * *calling process's* `Path` env var and — critically — does NOT apply PATHEXT.
14
+ * So a provider command like `claude` never matches `claude.exe` and the native
15
+ * layer throws `File not found:` (empty), which crashes the daemon. We resolve
16
+ * it to an absolute `.exe` here (in the daemon process, which has the full PATH)
17
+ * before the command ever reaches node-pty.
18
+ *
19
+ * No-op on non-Windows and when the command is already an existing absolute path
20
+ * or cannot be resolved (caller keeps the original behaviour).
21
+ */
22
+ export function resolveWin32Executable(command: string): string {
23
+ if (process.platform !== 'win32') return command;
24
+ const trimmed = (command || '').trim();
25
+ if (!trimmed) return command;
26
+
27
+ // Already an absolute path that exists — keep it.
28
+ if (path.isAbsolute(trimmed) && existsSync(trimmed)) return trimmed;
29
+
30
+ try {
31
+ const out = execFileSync('where', [trimmed], {
32
+ encoding: 'utf8',
33
+ windowsHide: true,
34
+ }).trim();
35
+ if (out) {
36
+ const matches = out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
37
+ // Prefer a directly-launchable executable (.exe/.com) over .cmd/.bat shims.
38
+ const direct = matches.find((m) => DIRECT_EXEC_EXT.has(path.extname(m).toLowerCase()));
39
+ return direct || matches[0] || command;
40
+ }
41
+ } catch {
42
+ // `where` not found / non-zero exit — fall through to original command.
43
+ }
44
+ return command;
45
+ }
@@ -6,6 +6,7 @@ import {
6
6
  type SessionHostRecord,
7
7
  } from '@adhdev/session-host-core';
8
8
  import { LOG } from '../logging/logger.js';
9
+ import { resolveWin32Executable } from './resolve-executable.js';
9
10
  import type { PtyRuntimeMetadata, PtyRuntimeTransport, PtySpawnOptions, PtyTransportFactory } from './pty-transport.js';
10
11
 
11
12
  interface SessionHostPtyTransportFactoryOptions {
@@ -435,7 +436,7 @@ export class SessionHostPtyTransportFactory implements PtyTransportFactory {
435
436
  spawn(command: string, args: string[], spawnOptions: PtySpawnOptions): PtyRuntimeTransport {
436
437
  return new SessionHostRuntimeTransport({
437
438
  ...this.options,
438
- command,
439
+ command: resolveWin32Executable(command),
439
440
  args,
440
441
  spawnOptions,
441
442
  });
@@ -169,6 +169,34 @@ export function buildPinnedGlobalInstallCommand(options: {
169
169
  };
170
170
  }
171
171
 
172
+ /**
173
+ * Build an env for the `npm install` child whose PATH is prefixed with the
174
+ * directory of the node binary currently running this helper.
175
+ *
176
+ * npm runs lifecycle scripts (e.g. adhdev's `preinstall` Node-version guard) by
177
+ * spawning a bare `node`, which resolves from PATH — NOT from the node that runs
178
+ * npm. On Windows a machine can have several node installs (e.g. a standalone
179
+ * `C:\Program Files\nodejs` ahead of an nvm-managed node on PATH). Without this,
180
+ * the guard sees the wrong (unsupported) node version and aborts the upgrade,
181
+ * even though npm/adhdev actually run under a supported node. Pinning the
182
+ * running node's dir to the front of PATH makes lifecycle scripts use the same
183
+ * node as the install itself.
184
+ */
185
+ function buildInstallEnvWithNodeOnPath(baseEnv: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
186
+ // The Node-version guard this works around only fires on Windows, so scope the
187
+ // PATH rewrite to win32 — POSIX keeps its env untouched.
188
+ if (process.platform !== 'win32') return { ...baseEnv };
189
+ const nodeBinDir = path.dirname(process.execPath);
190
+ if (!nodeBinDir) return { ...baseEnv };
191
+ const env: NodeJS.ProcessEnv = { ...baseEnv };
192
+ // Windows env keys are case-insensitive and conventionally spelled `Path`;
193
+ // prepend to the existing key (whatever its case) to avoid creating a dupe.
194
+ const pathKey = Object.keys(env).find((k) => k.toLowerCase() === 'path') || 'PATH';
195
+ const current = env[pathKey] || '';
196
+ env[pathKey] = current ? `${nodeBinDir};${current}` : nodeBinDir;
197
+ return env;
198
+ }
199
+
172
200
  export function getNpmExecOptions(platform: NodeJS.Platform = process.platform): NpmExecOptions {
173
201
  if (platform === 'win32') {
174
202
  return { shell: false, windowsHide: true };
@@ -375,6 +403,7 @@ async function runDaemonUpgradeHelper(payload: DaemonUpgradeHelperPayload): Prom
375
403
  encoding: 'utf8',
376
404
  stdio: 'pipe',
377
405
  maxBuffer: 20 * 1024 * 1024,
406
+ env: buildInstallEnvWithNodeOnPath(),
378
407
  ...installCommand.execOptions,
379
408
  },
380
409
  );