@ricsam/r5d-worker 0.0.66 → 0.0.68
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/README.md +2 -0
- package/dist/cjs/main.cjs +68 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +68 -3
- package/dist/mjs/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,4 +38,6 @@ worker exec macos docker compose up -d
|
|
|
38
38
|
worker exec macos bun test
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
Agent shell commands receive no stdin by default: prompts read immediate EOF instead of hanging. A command started with `interactive: true` is spawned with a writable stdin pipe, and the agent's `shell_write` tool delivers input through the `exec_stdin` protocol message. The worker acknowledges each write with an `operation_result` carrying the delivered byte count, closes stdin on an explicit `eof`, on cancellation, on timeout, and at process exit, and advertises this support through the `execStdinV1` capability in its hello message. Servers refuse interactive runs and stdin writes for workers without the capability, so this is a protocol-compatible release that uses the ordinary worker update flow. Delivered stdin is audited server-side in the run's `process-runs/<runId>/stdin.log`.
|
|
42
|
+
|
|
41
43
|
When the connected `r5d-browser` requests a port forward, the worker opens each relayed connection only to `127.0.0.1` on the requested worker port. Browser-side and worker-side ports may differ. The worker never opens a public listener, and a disconnected worker leaves the browser's long-lived mapping unavailable until the same worker label reconnects.
|
package/dist/cjs/main.cjs
CHANGED
|
@@ -1960,8 +1960,12 @@ async function executeStreamingCommand(input) {
|
|
|
1960
1960
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
1961
1961
|
});
|
|
1962
1962
|
const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
|
|
1963
|
+
const interactive = input.message.interactive === true;
|
|
1963
1964
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
1964
1965
|
cwd,
|
|
1966
|
+
// Without an explicit stdin the process reads /dev/null and interactive
|
|
1967
|
+
// prompts see immediate EOF; "pipe" keeps stdin open for exec_stdin.
|
|
1968
|
+
...interactive ? { stdin: "pipe" } : {},
|
|
1965
1969
|
stdout: "pipe",
|
|
1966
1970
|
stderr: "pipe",
|
|
1967
1971
|
detached: true,
|
|
@@ -1983,7 +1987,8 @@ async function executeStreamingCommand(input) {
|
|
|
1983
1987
|
argv: input.message.argv,
|
|
1984
1988
|
command: input.message.command,
|
|
1985
1989
|
cwd,
|
|
1986
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1990
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1991
|
+
...interactive ? { interactive: true, stdin: subprocess.stdin } : {}
|
|
1987
1992
|
});
|
|
1988
1993
|
started = true;
|
|
1989
1994
|
sendWorkerMessage(input.ws, {
|
|
@@ -1997,6 +2002,7 @@ async function executeStreamingCommand(input) {
|
|
|
1997
2002
|
if (input.message.timeoutMs) {
|
|
1998
2003
|
timeout = setTimeout(() => {
|
|
1999
2004
|
timedOut = true;
|
|
2005
|
+
closeProcessStdin(activeProcesses.get(input.message.runId));
|
|
2000
2006
|
void (0, import_process_tree.terminateProcessTree)(subprocess).catch((error) => {
|
|
2001
2007
|
process.stderr.write(
|
|
2002
2008
|
`[r5d-worker] failed to terminate timed-out process ${input.message.runId}: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -2056,10 +2062,21 @@ async function executeStreamingCommand(input) {
|
|
|
2056
2062
|
if (timeout) {
|
|
2057
2063
|
clearTimeout(timeout);
|
|
2058
2064
|
}
|
|
2065
|
+
closeProcessStdin(activeProcesses.get(input.message.runId));
|
|
2059
2066
|
activeProcesses.delete(input.message.runId);
|
|
2060
2067
|
cancelledProcessRuns.delete(input.message.runId);
|
|
2061
2068
|
}
|
|
2062
2069
|
}
|
|
2070
|
+
function closeProcessStdin(active) {
|
|
2071
|
+
if (!active?.stdin) {
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
try {
|
|
2075
|
+
void active.stdin.end();
|
|
2076
|
+
} catch {
|
|
2077
|
+
}
|
|
2078
|
+
active.stdin = void 0;
|
|
2079
|
+
}
|
|
2063
2080
|
function sendWorkerMessage(ws, message) {
|
|
2064
2081
|
const target = currentWorkerSocket ?? ws;
|
|
2065
2082
|
try {
|
|
@@ -2083,7 +2100,8 @@ function buildActiveProcessReports() {
|
|
|
2083
2100
|
argv: active.argv,
|
|
2084
2101
|
command: active.command,
|
|
2085
2102
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
2086
|
-
startedAt: active.startedAt
|
|
2103
|
+
startedAt: active.startedAt,
|
|
2104
|
+
...active.interactive ? { interactive: true } : {}
|
|
2087
2105
|
}));
|
|
2088
2106
|
}
|
|
2089
2107
|
function sendActiveProcessReport(ws) {
|
|
@@ -2889,7 +2907,8 @@ async function startWorker(options) {
|
|
|
2889
2907
|
browserPortForwarding: true,
|
|
2890
2908
|
globalWorkspaceSyncLeaseV1: true,
|
|
2891
2909
|
eventualWorkspaceSyncV1: true,
|
|
2892
|
-
workspaceIncidentCandidatePinV1: true
|
|
2910
|
+
workspaceIncidentCandidatePinV1: true,
|
|
2911
|
+
execStdinV1: true
|
|
2893
2912
|
},
|
|
2894
2913
|
projectRoot: projectsRoot,
|
|
2895
2914
|
artifactRoot,
|
|
@@ -3332,6 +3351,7 @@ async function startWorker(options) {
|
|
|
3332
3351
|
let cancelMessage;
|
|
3333
3352
|
if (active) {
|
|
3334
3353
|
try {
|
|
3354
|
+
closeProcessStdin(active);
|
|
3335
3355
|
await (0, import_process_tree.terminateProcessTree)(active.process);
|
|
3336
3356
|
cancelMessage = `Stopped ${active.command}`;
|
|
3337
3357
|
} catch (error) {
|
|
@@ -3353,6 +3373,51 @@ async function startWorker(options) {
|
|
|
3353
3373
|
);
|
|
3354
3374
|
return;
|
|
3355
3375
|
}
|
|
3376
|
+
if (message.type === "exec_stdin") {
|
|
3377
|
+
const sendAck = (payload) => ws.send(
|
|
3378
|
+
JSON.stringify({
|
|
3379
|
+
type: "operation_result",
|
|
3380
|
+
requestId: message.requestId,
|
|
3381
|
+
...payload
|
|
3382
|
+
})
|
|
3383
|
+
);
|
|
3384
|
+
const active = activeProcesses.get(message.runId);
|
|
3385
|
+
if (!active) {
|
|
3386
|
+
sendAck({ error: `Process run ${message.runId} is not active on this worker` });
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
if (!active.interactive || !active.stdin) {
|
|
3390
|
+
sendAck({
|
|
3391
|
+
error: `Process run ${message.runId} has no open stdin. Start a new shell command with "interactive": true to write to its stdin.`
|
|
3392
|
+
});
|
|
3393
|
+
return;
|
|
3394
|
+
}
|
|
3395
|
+
try {
|
|
3396
|
+
let bytesWritten = 0;
|
|
3397
|
+
if (message.data !== void 0 && message.data.length > 0) {
|
|
3398
|
+
bytesWritten = await active.stdin.write(message.data);
|
|
3399
|
+
await active.stdin.flush();
|
|
3400
|
+
}
|
|
3401
|
+
if (message.eof === true) {
|
|
3402
|
+
await active.stdin.end();
|
|
3403
|
+
active.stdin = void 0;
|
|
3404
|
+
}
|
|
3405
|
+
if (targetMayMutateVisibleWorkspace(active.target)) {
|
|
3406
|
+
markWorkspaceDirty({ type: "shell_inline", detail: `exec stdin ${message.runId}` });
|
|
3407
|
+
}
|
|
3408
|
+
sendAck({
|
|
3409
|
+
result: {
|
|
3410
|
+
type: "exec_stdin",
|
|
3411
|
+
runId: message.runId,
|
|
3412
|
+
bytesWritten,
|
|
3413
|
+
eof: message.eof === true
|
|
3414
|
+
}
|
|
3415
|
+
});
|
|
3416
|
+
} catch (error) {
|
|
3417
|
+
sendAck({ error: error instanceof Error ? error.message : String(error) });
|
|
3418
|
+
}
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3356
3421
|
if (message.type === "pty_open") {
|
|
3357
3422
|
if (cliUpdateInProgress) {
|
|
3358
3423
|
sendWorkerMessage(ws, {
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/main.mjs
CHANGED
|
@@ -1931,8 +1931,12 @@ async function executeStreamingCommand(input) {
|
|
|
1931
1931
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
1932
1932
|
});
|
|
1933
1933
|
const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
|
|
1934
|
+
const interactive = input.message.interactive === true;
|
|
1934
1935
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
1935
1936
|
cwd,
|
|
1937
|
+
// Without an explicit stdin the process reads /dev/null and interactive
|
|
1938
|
+
// prompts see immediate EOF; "pipe" keeps stdin open for exec_stdin.
|
|
1939
|
+
...interactive ? { stdin: "pipe" } : {},
|
|
1936
1940
|
stdout: "pipe",
|
|
1937
1941
|
stderr: "pipe",
|
|
1938
1942
|
detached: true,
|
|
@@ -1954,7 +1958,8 @@ async function executeStreamingCommand(input) {
|
|
|
1954
1958
|
argv: input.message.argv,
|
|
1955
1959
|
command: input.message.command,
|
|
1956
1960
|
cwd,
|
|
1957
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1961
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1962
|
+
...interactive ? { interactive: true, stdin: subprocess.stdin } : {}
|
|
1958
1963
|
});
|
|
1959
1964
|
started = true;
|
|
1960
1965
|
sendWorkerMessage(input.ws, {
|
|
@@ -1968,6 +1973,7 @@ async function executeStreamingCommand(input) {
|
|
|
1968
1973
|
if (input.message.timeoutMs) {
|
|
1969
1974
|
timeout = setTimeout(() => {
|
|
1970
1975
|
timedOut = true;
|
|
1976
|
+
closeProcessStdin(activeProcesses.get(input.message.runId));
|
|
1971
1977
|
void terminateProcessTree(subprocess).catch((error) => {
|
|
1972
1978
|
process.stderr.write(
|
|
1973
1979
|
`[r5d-worker] failed to terminate timed-out process ${input.message.runId}: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -2027,10 +2033,21 @@ async function executeStreamingCommand(input) {
|
|
|
2027
2033
|
if (timeout) {
|
|
2028
2034
|
clearTimeout(timeout);
|
|
2029
2035
|
}
|
|
2036
|
+
closeProcessStdin(activeProcesses.get(input.message.runId));
|
|
2030
2037
|
activeProcesses.delete(input.message.runId);
|
|
2031
2038
|
cancelledProcessRuns.delete(input.message.runId);
|
|
2032
2039
|
}
|
|
2033
2040
|
}
|
|
2041
|
+
function closeProcessStdin(active) {
|
|
2042
|
+
if (!active?.stdin) {
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
try {
|
|
2046
|
+
void active.stdin.end();
|
|
2047
|
+
} catch {
|
|
2048
|
+
}
|
|
2049
|
+
active.stdin = void 0;
|
|
2050
|
+
}
|
|
2034
2051
|
function sendWorkerMessage(ws, message) {
|
|
2035
2052
|
const target = currentWorkerSocket ?? ws;
|
|
2036
2053
|
try {
|
|
@@ -2054,7 +2071,8 @@ function buildActiveProcessReports() {
|
|
|
2054
2071
|
argv: active.argv,
|
|
2055
2072
|
command: active.command,
|
|
2056
2073
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
2057
|
-
startedAt: active.startedAt
|
|
2074
|
+
startedAt: active.startedAt,
|
|
2075
|
+
...active.interactive ? { interactive: true } : {}
|
|
2058
2076
|
}));
|
|
2059
2077
|
}
|
|
2060
2078
|
function sendActiveProcessReport(ws) {
|
|
@@ -2860,7 +2878,8 @@ async function startWorker(options) {
|
|
|
2860
2878
|
browserPortForwarding: true,
|
|
2861
2879
|
globalWorkspaceSyncLeaseV1: true,
|
|
2862
2880
|
eventualWorkspaceSyncV1: true,
|
|
2863
|
-
workspaceIncidentCandidatePinV1: true
|
|
2881
|
+
workspaceIncidentCandidatePinV1: true,
|
|
2882
|
+
execStdinV1: true
|
|
2864
2883
|
},
|
|
2865
2884
|
projectRoot: projectsRoot,
|
|
2866
2885
|
artifactRoot,
|
|
@@ -3303,6 +3322,7 @@ async function startWorker(options) {
|
|
|
3303
3322
|
let cancelMessage;
|
|
3304
3323
|
if (active) {
|
|
3305
3324
|
try {
|
|
3325
|
+
closeProcessStdin(active);
|
|
3306
3326
|
await terminateProcessTree(active.process);
|
|
3307
3327
|
cancelMessage = `Stopped ${active.command}`;
|
|
3308
3328
|
} catch (error) {
|
|
@@ -3324,6 +3344,51 @@ async function startWorker(options) {
|
|
|
3324
3344
|
);
|
|
3325
3345
|
return;
|
|
3326
3346
|
}
|
|
3347
|
+
if (message.type === "exec_stdin") {
|
|
3348
|
+
const sendAck = (payload) => ws.send(
|
|
3349
|
+
JSON.stringify({
|
|
3350
|
+
type: "operation_result",
|
|
3351
|
+
requestId: message.requestId,
|
|
3352
|
+
...payload
|
|
3353
|
+
})
|
|
3354
|
+
);
|
|
3355
|
+
const active = activeProcesses.get(message.runId);
|
|
3356
|
+
if (!active) {
|
|
3357
|
+
sendAck({ error: `Process run ${message.runId} is not active on this worker` });
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
if (!active.interactive || !active.stdin) {
|
|
3361
|
+
sendAck({
|
|
3362
|
+
error: `Process run ${message.runId} has no open stdin. Start a new shell command with "interactive": true to write to its stdin.`
|
|
3363
|
+
});
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
try {
|
|
3367
|
+
let bytesWritten = 0;
|
|
3368
|
+
if (message.data !== void 0 && message.data.length > 0) {
|
|
3369
|
+
bytesWritten = await active.stdin.write(message.data);
|
|
3370
|
+
await active.stdin.flush();
|
|
3371
|
+
}
|
|
3372
|
+
if (message.eof === true) {
|
|
3373
|
+
await active.stdin.end();
|
|
3374
|
+
active.stdin = void 0;
|
|
3375
|
+
}
|
|
3376
|
+
if (targetMayMutateVisibleWorkspace(active.target)) {
|
|
3377
|
+
markWorkspaceDirty({ type: "shell_inline", detail: `exec stdin ${message.runId}` });
|
|
3378
|
+
}
|
|
3379
|
+
sendAck({
|
|
3380
|
+
result: {
|
|
3381
|
+
type: "exec_stdin",
|
|
3382
|
+
runId: message.runId,
|
|
3383
|
+
bytesWritten,
|
|
3384
|
+
eof: message.eof === true
|
|
3385
|
+
}
|
|
3386
|
+
});
|
|
3387
|
+
} catch (error) {
|
|
3388
|
+
sendAck({ error: error instanceof Error ? error.message : String(error) });
|
|
3389
|
+
}
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3327
3392
|
if (message.type === "pty_open") {
|
|
3328
3393
|
if (cliUpdateInProgress) {
|
|
3329
3394
|
sendWorkerMessage(ws, {
|
package/dist/mjs/package.json
CHANGED