@phnx-labs/agents-cli 1.22.86 → 1.22.88
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/CHANGELOG.md +14 -0
- package/dist/lib/secrets-client.js +58 -66
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.88
|
|
4
|
+
|
|
5
|
+
- **`agents run` no longer fails with `secrets request failed: spawnSync sh
|
|
6
|
+
ETIMEDOUT` when resolving an account credential.** The synchronous secrets path
|
|
7
|
+
(`agents view`, the account catalog, and account resolution on the `agents run`
|
|
8
|
+
hot path) fed the standalone `secrets __serve` its request over a named FIFO on
|
|
9
|
+
fd 3. The standalone wraps fd 3 in a `net.Socket`, and a Socket over a named FIFO
|
|
10
|
+
reads the request but never fires EOF on macOS — so its read loop blocked until
|
|
11
|
+
the 3s sync bound fired and every credential lookup timed out. The request now
|
|
12
|
+
rides `spawnSync`'s stdin (a real pipe/socketpair — the same fd type the async
|
|
13
|
+
path already hands the standalone) dup'd onto fd 3, so it EOFs and a real
|
|
14
|
+
handshake/lookup completes in tens of ms. Source: `cli/src/lib/secrets-client.ts`
|
|
15
|
+
(`serveOnceSync`).
|
|
16
|
+
|
|
3
17
|
## 1.22.86
|
|
4
18
|
|
|
5
19
|
- **Workers now materialize a slot for every registered Claude account, not only
|
|
@@ -17,12 +17,17 @@
|
|
|
17
17
|
* - async: `spawn` with stdio ['ignore','ignore','inherit','pipe','pipe'];
|
|
18
18
|
* write+end child.stdio[3], read child.stdio[4] to EOF.
|
|
19
19
|
* - sync: `spawnSync` only wires stdio 0-2 portably (Bun drops numbered fds
|
|
20
|
-
* 3+), so the fd 3/4 wiring is done in a POSIX shell: the request rides
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
20
|
+
* 3+), so the fd 3/4 wiring is done in a POSIX shell: the request rides
|
|
21
|
+
* `spawnSync`'s STDIN — a real pipe/socketpair on every runtime — which the
|
|
22
|
+
* shell dups onto fd 3 (`3<&0`), and fd 4 is redirected onto the child's
|
|
23
|
+
* stdout (`4>&1`), which `spawnSync` captures natively. Both fds are then the
|
|
24
|
+
* SAME anonymous pipe/socketpair the async path hands the standalone, which
|
|
25
|
+
* is load-bearing: the standalone wraps fd 3 in a `net.Socket`, and a Socket
|
|
26
|
+
* over a NAMED FIFO reads the request but never fires EOF on macOS, so the
|
|
27
|
+
* older FIFO wiring hung the read loop for the full timeout. Bounded to
|
|
28
|
+
* `SYNC_SERVE_TIMEOUT_MS` so a broken standalone fails fast, never for the
|
|
29
|
+
* server's 60s deadline. POSIX only; Windows fails loud pointing at the
|
|
30
|
+
* async path.
|
|
26
31
|
*
|
|
27
32
|
* State root (MIG-1): the standalone selects its state root from `SECRETS_HOME`.
|
|
28
33
|
* agents-cli points it at the user agents dir (`~/.agents`) by default so the
|
|
@@ -42,9 +47,6 @@
|
|
|
42
47
|
* `@phnx-labs/secrets-cli` package is the only implementation.
|
|
43
48
|
*/
|
|
44
49
|
import { spawn, spawnSync } from 'node:child_process';
|
|
45
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
46
|
-
import { tmpdir } from 'node:os';
|
|
47
|
-
import { join } from 'node:path';
|
|
48
50
|
import { findInPath } from './agent-spec/agents.js';
|
|
49
51
|
import { getUserAgentsDir } from './state.js';
|
|
50
52
|
/**
|
|
@@ -371,69 +373,59 @@ function shQuote(token) {
|
|
|
371
373
|
}
|
|
372
374
|
function serveOnceSync(op, args, context) {
|
|
373
375
|
if (process.platform === 'win32') {
|
|
374
|
-
throw new SecretsClientError('SYNC_UNSUPPORTED', 'The synchronous secrets path needs a POSIX shell
|
|
376
|
+
throw new SecretsClientError('SYNC_UNSUPPORTED', 'The synchronous secrets path needs a POSIX shell; use secretsRequest (async) on Windows.');
|
|
375
377
|
}
|
|
376
378
|
const { command, prefix } = invocation(resolveSecretsBin());
|
|
377
379
|
const request = Buffer.from(JSON.stringify(buildRequest(op, args, context)));
|
|
378
380
|
// The standalone's private protocol reads the request from fd 3 and writes the
|
|
379
|
-
// response to fd 4,
|
|
380
|
-
// `
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
maxBuffer: MAX_PROTOCOL_BYTES + 4096,
|
|
422
|
-
});
|
|
423
|
-
if (result.error) {
|
|
424
|
-
const err = result.error;
|
|
425
|
-
const code = err.code === 'ETIMEDOUT' ? 'TIMEOUT' : 'SPAWN_FAILED';
|
|
426
|
-
throw new SecretsClientError(code, `secrets request failed: ${err.message}`);
|
|
427
|
-
}
|
|
428
|
-
const raw = result.stdout ?? Buffer.alloc(0);
|
|
429
|
-
if (raw.length > MAX_PROTOCOL_BYTES) {
|
|
430
|
-
throw new SecretsClientError('RESPONSE_TOO_LARGE', 'secrets response exceeds the protocol limit');
|
|
431
|
-
}
|
|
432
|
-
return parseResponse(raw);
|
|
381
|
+
// response to fd 4, wrapping BOTH in a `net.Socket` (secrets-cli
|
|
382
|
+
// `runProtocolServer`). It also REFUSES a plain file or tty for either — fd 3/4
|
|
383
|
+
// MUST be a pipe or socket (`PRIVATE_PIPE_REQUIRED`) — so no secret is ever
|
|
384
|
+
// staged to disk. But `spawnSync` only wires stdio 0-2 portably — the Bun
|
|
385
|
+
// runtime (this repo runs its whole CLI suite as `bun src/index.ts`, so the CLI-
|
|
386
|
+
// integration tests spawn agents-cli under Bun) silently DROPS numbered fds 3+,
|
|
387
|
+
// so the numbered-fd wiring is done in a POSIX shell.
|
|
388
|
+
//
|
|
389
|
+
// The request rides `spawnSync`'s STDIN, which every runtime backs with a real
|
|
390
|
+
// pipe/socketpair, and the shell dups it onto fd 3 (`3<&0`); fd 4 is dup'd from
|
|
391
|
+
// the captured stdout pipe (`4>&1`), then the child's own fd 1 is sent to
|
|
392
|
+
// /dev/null (`1>/dev/null`, AFTER `4>&1` so fd 4 keeps the pipe) — so the
|
|
393
|
+
// response channel (fd 4) is structurally the ONLY writer to the captured
|
|
394
|
+
// stream and a stray write to the standalone's own stdout can never corrupt the
|
|
395
|
+
// fd-4 bytes. Crucially, fd 3 and fd 4 are now the SAME anonymous
|
|
396
|
+
// pipe/socketpair the async path hands the standalone via `spawn` numbered
|
|
397
|
+
// stdio, rather than a named FIFO: a `net.Socket` wrapped around a named FIFO
|
|
398
|
+
// reads the request but never fires EOF on macOS, so the standalone's
|
|
399
|
+
// `for await (const chunk of input)` on fd 3 would hang for the full timeout
|
|
400
|
+
// (the bug the old FIFO wiring hit — spawnSync's stdout IS a socketpair, so fd 4
|
|
401
|
+
// was fine, but the FIFO on fd 3 never EOF'd). `exec` so the wrapper `sh`
|
|
402
|
+
// BECOMES the standalone (same PID): `spawnSync` waits on the real process, so
|
|
403
|
+
// on return the store is fully flushed and its lock dir released, and a timeout
|
|
404
|
+
// SIGTERM lands on the standalone itself rather than orphaning a wedged child.
|
|
405
|
+
// The response never touches disk. The shell wiring is parent-runtime-agnostic
|
|
406
|
+
// — `spawnSync`'s stdio is a socketpair under both Node and Bun — so fd 3 EOFs
|
|
407
|
+
// whenever the standalone itself runs under Node. (A standalone that `invocation`
|
|
408
|
+
// launches under BUN still hits its own PHNX-3989 EOF hang regardless of the
|
|
409
|
+
// wiring; that stays bounded by SYNC_SERVE_TIMEOUT_MS below, as before.)
|
|
410
|
+
const serve = [command, ...prefix, '__serve'].map(shQuote).join(' ');
|
|
411
|
+
const script = `exec ${serve} 3<&0 4>&1 1>/dev/null`;
|
|
412
|
+
const result = spawnSync('sh', ['-c', script], {
|
|
413
|
+
input: request,
|
|
414
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
415
|
+
env: buildServeEnv(),
|
|
416
|
+
timeout: SYNC_SERVE_TIMEOUT_MS,
|
|
417
|
+
maxBuffer: MAX_PROTOCOL_BYTES + 4096,
|
|
418
|
+
});
|
|
419
|
+
if (result.error) {
|
|
420
|
+
const err = result.error;
|
|
421
|
+
const code = err.code === 'ETIMEDOUT' ? 'TIMEOUT' : 'SPAWN_FAILED';
|
|
422
|
+
throw new SecretsClientError(code, `secrets request failed: ${err.message}`);
|
|
433
423
|
}
|
|
434
|
-
|
|
435
|
-
|
|
424
|
+
const raw = result.stdout ?? Buffer.alloc(0);
|
|
425
|
+
if (raw.length > MAX_PROTOCOL_BYTES) {
|
|
426
|
+
throw new SecretsClientError('RESPONSE_TOO_LARGE', 'secrets response exceeds the protocol limit');
|
|
436
427
|
}
|
|
428
|
+
return parseResponse(raw);
|
|
437
429
|
}
|
|
438
430
|
// --- handshake (once per process) ------------------------------------------
|
|
439
431
|
let handshakeReady = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.88",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|