agentworth 0.1.4 → 0.1.6

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.
Files changed (2) hide show
  1. package/lib/resolver.js +41 -4
  2. package/package.json +1 -1
package/lib/resolver.js CHANGED
@@ -264,6 +264,36 @@ export async function downloadAndExtractBinary(options = {}) {
264
264
  * @param {string} [currentScriptPath]
265
265
  * @returns {string | null}
266
266
  */
267
+ /**
268
+ * True when `candidate` and `selfPath` are the same file, following symlinks.
269
+ */
270
+ export function isSelf(candidate, selfPath) {
271
+ if (!selfPath) return false;
272
+ try {
273
+ return fs.realpathSync(candidate) === fs.realpathSync(selfPath);
274
+ } catch {
275
+ return path.resolve(candidate) === path.resolve(selfPath);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * True when `candidate` is a Node script (a bin shim) rather than the native
281
+ * binary. Covers shims that are copies rather than symlinks.
282
+ */
283
+ export function looksLikeNodeScript(filePath) {
284
+ try {
285
+ if (/\.(js|cjs|mjs)$/.test(filePath)) return true;
286
+ const fd = fs.openSync(filePath, 'r');
287
+ const buf = Buffer.alloc(64);
288
+ const n = fs.readSync(fd, buf, 0, 64, 0);
289
+ fs.closeSync(fd);
290
+ const first = buf.subarray(0, n).toString('utf8').split('\n')[0];
291
+ return first.startsWith('#!') && /\bnode\b/.test(first);
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
267
297
  export function findPathBinary(binName = getBinaryName(), pathEnv = process.env.PATH, currentScriptPath) {
268
298
  if (!pathEnv) {
269
299
  return null;
@@ -274,8 +304,12 @@ export function findPathBinary(binName = getBinaryName(), pathEnv = process.env.
274
304
  if (!entry) continue;
275
305
  const candidate = path.join(entry, binName);
276
306
  if (isExecutable(candidate)) {
277
- // Avoid circular invocation if PATH points to this JS wrapper script
278
- if (currentScriptPath && path.resolve(candidate) === path.resolve(currentScriptPath)) {
307
+ // Avoid circular invocation if PATH points back at this JS wrapper.
308
+ // npm/npx put node_modules/.bin on PATH and the entry there is a SYMLINK
309
+ // to this very file. path.resolve() does not follow symlinks, so this guard
310
+ // missed it and the launcher spawned itself until the OS refused to fork
311
+ // (EAGAIN on macOS, v0.1.4). Compare real paths, and reject Node scripts.
312
+ if (isSelf(candidate, currentScriptPath) || looksLikeNodeScript(candidate)) {
279
313
  continue;
280
314
  }
281
315
  return candidate;
@@ -337,8 +371,9 @@ export function resolveBinary(options = {}) {
337
371
  }
338
372
 
339
373
  // 3. System PATH (highest user priority for installed binaries)
374
+ // Skipped when already inside a launcher: the only thing PATH could offer is us.
340
375
  const currentBin = path.resolve(baseDir, '..', 'bin', 'agentworth.js');
341
- if (env.PATH !== undefined) {
376
+ if (env.PATH !== undefined && !env.AGENTWORTH_LAUNCHER_ACTIVE) {
342
377
  const pathBin = findPathBinary(binName, env.PATH, currentBin);
343
378
  if (pathBin) {
344
379
  return {
@@ -487,9 +522,11 @@ export function run(argv = process.argv.slice(2), options = {}) {
487
522
  return 1;
488
523
  }
489
524
 
525
+ // Belt and braces: a child that somehow re-enters this launcher cannot loop.
526
+ const childEnv = { ...(options.env || process.env), AGENTWORTH_LAUNCHER_ACTIVE: '1' };
490
527
  const result = spawnSync(binaryResult.path, resolvedArgs, {
491
528
  stdio: 'inherit',
492
- env: options.env || process.env,
529
+ env: childEnv,
493
530
  });
494
531
 
495
532
  if (result.error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworth",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Discover, normalize, and understand AI-agent histories locally.",
5
5
  "type": "module",
6
6
  "main": "./lib/resolver.js",