@yolo-labs/yolobridge 0.29.0 → 0.30.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/cli.js +19 -7
- package/dist/resolve-agent-binary.js +105 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ import { runShare, runDeliver } from './share-cmd.js';
|
|
|
35
35
|
import { runAllow } from './approved-paths.js';
|
|
36
36
|
import { runDetach } from './detach-cmd.js';
|
|
37
37
|
import { getStatus, formatStatus } from './status-cmd.js';
|
|
38
|
+
import { resolveAgentBinary } from './resolve-agent-binary.js';
|
|
38
39
|
import { startLocalAgent, stopLocalAgent, DEFAULT_AGENT_BIN } from './local-agent.js';
|
|
39
40
|
import { runListWorkspaces, formatWorkspacesTable } from './workspaces-cmd.js';
|
|
40
41
|
import { startMcpProxy, mcpUrl, SECRET_ENV_VAR } from './mcp-proxy.js';
|
|
@@ -326,13 +327,6 @@ async function cmdAttach(args) {
|
|
|
326
327
|
cliVersion: readOwnVersion(),
|
|
327
328
|
});
|
|
328
329
|
let mcpProxyHandle;
|
|
329
|
-
/**
|
|
330
|
-
* Serves terminals on 127.0.0.1 for the tile's "open terminal".
|
|
331
|
-
*
|
|
332
|
-
* ⚠️ SEPARATE FROM THE AGENT PTY. `startLocalAgent` owns the one agent
|
|
333
|
-
* session; this owns any shells the operator opens from the workspace. They
|
|
334
|
-
* share a lifetime — both die with the attach — and nothing else.
|
|
335
|
-
*/
|
|
336
330
|
// argv fragment pointing the spawned agent at the local MCP proxy, or
|
|
337
331
|
// `[]` when MCP isn't wired in — see `agent-mcp-args.ts`. Nothing else is
|
|
338
332
|
// tracked for cleanup any more: as of 2026-08-26 `attach` writes NOTHING
|
|
@@ -472,6 +466,24 @@ async function cmdAttach(args) {
|
|
|
472
466
|
// Say where Ctrl+C goes BEFORE the agent takes over the screen.
|
|
473
467
|
// Without this the operator presses it expecting to quit, nothing
|
|
474
468
|
// happens, and there is no way to discover why.
|
|
469
|
+
// ⚠️ RESOLVE THE BINARY BEFORE SPAWNING IT, so a failure can be
|
|
470
|
+
// EXPLAINED. node-pty reports an unspawnable binary as the bare
|
|
471
|
+
// string `posix_spawnp failed.` — no binary name, no PATH, no
|
|
472
|
+
// remedy — which on macOS reached an operator as "failed to start
|
|
473
|
+
// the local agent (posix_spawnp failed.)" and told them nothing
|
|
474
|
+
// they could act on. Worse, the one fact they could see (`claude`
|
|
475
|
+
// works when typed) argued that nothing was wrong, because a shell
|
|
476
|
+
// alias or function resolves interactively and cannot be spawned.
|
|
477
|
+
// This does not change WHETHER the attach fails — only whether the
|
|
478
|
+
// operator can tell why.
|
|
479
|
+
const resolvedBinary = resolveAgentBinary(agentBin ?? DEFAULT_AGENT_BIN, process.env);
|
|
480
|
+
if (!resolvedBinary.ok) {
|
|
481
|
+
process.stdout.write(`yolo-bridge: cannot start the agent — ${resolvedBinary.message}\n`);
|
|
482
|
+
process.stdout.write('yolo-bridge: detaching...\n');
|
|
483
|
+
stopRequested = true;
|
|
484
|
+
await runDetach({ commonApiBaseUrl: apiUrl() }).catch(() => undefined);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
475
487
|
process.stdout.write('yolo-bridge: Ctrl+C goes to the agent · Ctrl-P Ctrl-Q to detach\n');
|
|
476
488
|
startLocalAgent({
|
|
477
489
|
agentBin,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Can the agent binary actually be spawned, and if not, WHY?
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ THE FAILURE THIS EXISTS FOR. node-pty reports an unspawnable binary as
|
|
5
|
+
* the literal string `posix_spawnp failed.` — no binary name, no PATH, no
|
|
6
|
+
* errno, no remedy. On macOS that surfaced as:
|
|
7
|
+
*
|
|
8
|
+
* yolo-bridge: failed to start the local agent (posix_spawnp failed.), detaching...
|
|
9
|
+
*
|
|
10
|
+
* which is true, useless, and indistinguishable from half a dozen unrelated
|
|
11
|
+
* causes. The operator cannot tell whether the agent is missing, installed but
|
|
12
|
+
* not executable, or whether `attach` even looked in the right place — and the
|
|
13
|
+
* one thing they CAN see (`claude` works when they type it) argues that
|
|
14
|
+
* nothing is wrong.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ THE MACOS TRAP THIS NAMES EXPLICITLY. `claude` working in the operator's
|
|
17
|
+
* terminal does NOT mean there is a binary to spawn: a shell alias or function
|
|
18
|
+
* from `.zshrc` resolves interactively and does not exist as a file, so
|
|
19
|
+
* `posix_spawnp` cannot find it however correct the PATH looks. A message that
|
|
20
|
+
* only says "not found" invites the reply "but it IS installed", so this says
|
|
21
|
+
* where it looked and offers the escape hatch.
|
|
22
|
+
*
|
|
23
|
+
* Resolution mirrors `posix_spawnp`: a name containing a separator is a path,
|
|
24
|
+
* anything else is searched across PATH in order.
|
|
25
|
+
*/
|
|
26
|
+
import * as fs from 'node:fs';
|
|
27
|
+
import * as path from 'node:path';
|
|
28
|
+
const DEFAULT_FS = fs;
|
|
29
|
+
function isExecutableFile(p, io) {
|
|
30
|
+
try {
|
|
31
|
+
if (!io.statSync(p).isFile())
|
|
32
|
+
return 'no';
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return 'no';
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
io.accessSync(p, io.constants.X_OK);
|
|
39
|
+
return 'yes';
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return 'not-executable';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Where `bin` would be spawned from, or a sentence explaining why it cannot be.
|
|
47
|
+
*
|
|
48
|
+
* The message is the whole point: it is what the operator reads instead of
|
|
49
|
+
* `posix_spawnp failed.`, so it names the binary, says where the search
|
|
50
|
+
* looked, and gives the two ways out.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveAgentBinary(bin, env = process.env, io = DEFAULT_FS) {
|
|
53
|
+
const escapeHatch = `Point yolo-bridge at it explicitly with \`--agent /full/path/to/${bin}\` `
|
|
54
|
+
+ `(or set YOLOBRIDGE_AGENT_BIN).`;
|
|
55
|
+
// An explicit path is taken at its word — no PATH search, same as execve.
|
|
56
|
+
if (bin.includes(path.sep) || bin.includes('/')) {
|
|
57
|
+
const abs = path.resolve(bin);
|
|
58
|
+
const state = isExecutableFile(abs, io);
|
|
59
|
+
if (state === 'yes')
|
|
60
|
+
return { ok: true, path: abs };
|
|
61
|
+
if (state === 'not-executable') {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
reason: 'not-executable',
|
|
65
|
+
message: `The agent at ${abs} exists but is not executable. \`chmod +x ${abs}\` and re-attach.`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { ok: false, reason: 'not-found', message: `No agent binary at ${abs}. ${escapeHatch}` };
|
|
69
|
+
}
|
|
70
|
+
const rawPath = env.PATH ?? '';
|
|
71
|
+
if (!rawPath.trim()) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
reason: 'no-path',
|
|
75
|
+
message: `Cannot look for \`${bin}\`: PATH is empty in this process. ${escapeHatch}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const dirs = rawPath.split(path.delimiter).filter(Boolean);
|
|
79
|
+
let sawNonExecutable;
|
|
80
|
+
for (const dir of dirs) {
|
|
81
|
+
const candidate = path.join(dir, bin);
|
|
82
|
+
const state = isExecutableFile(candidate, io);
|
|
83
|
+
if (state === 'yes')
|
|
84
|
+
return { ok: true, path: candidate };
|
|
85
|
+
if (state === 'not-executable' && !sawNonExecutable)
|
|
86
|
+
sawNonExecutable = candidate;
|
|
87
|
+
}
|
|
88
|
+
if (sawNonExecutable) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
reason: 'not-executable',
|
|
92
|
+
message: `Found \`${bin}\` at ${sawNonExecutable}, but it is not executable. `
|
|
93
|
+
+ `\`chmod +x ${sawNonExecutable}\` and re-attach.`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
reason: 'not-found',
|
|
99
|
+
message: `\`${bin}\` is not on this process's PATH (searched ${dirs.length} `
|
|
100
|
+
+ `${dirs.length === 1 ? 'directory' : 'directories'}).\n`
|
|
101
|
+
+ ` ⚠️ If \`${bin}\` works when you type it, it may be a shell alias or function rather than\n`
|
|
102
|
+
+ ` a real binary — those cannot be spawned. Check with: type -a ${bin}\n`
|
|
103
|
+
+ ` ${escapeHatch}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolo-labs/yolobridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "YoloBridge \u2014 local coding-agent daemon that attaches a user's own Claude Code/Codex session to a YOLO Studio workspace as a first-class tile (docs/YOLOBRIDGE_PLAN.md, build-order Phase 5).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|