- captureExec: stop wrapping with { … ; }; Bare shell splits ; naively, so {

became argv[0] (“unknown command: {”). Redirect only, append EXIT:N from
  BARE_OS_EXIT_STATUS after execLine.

- Streaming: normalize newlines for SSH PTY (bareOsPtyStdoutCrlf on SSH child ctx,
  bareAgentWriteOut uses ctx).
This commit is contained in:
Raven Scott
2026-04-22 17:34:02 -04:00
parent bc22a0d63e
commit 93198d4fa6
9 changed files with 120 additions and 39 deletions
+38 -11
View File
@@ -3368,17 +3368,16 @@ async function bareAgentDispatchTool(o) {
async function captureExec(line, timeoutMs, captureOpts) { async function captureExec(line, timeoutMs, captureOpts) {
const outPath = paths.cmdOut const outPath = paths.cmdOut
const captureExit = Boolean(captureOpts && captureOpts.captureExit) const captureExit = Boolean(captureOpts && captureOpts.captureExit)
const wrapped = captureExit /**
? '{ ' + * Do not wrap with `{ cmd ; }` — Bare OS `splitTokensBySemicolon` splits on every `;`
line + * at depth 0 and does not treat `{ … }` as a compound, so `{` became argv[0]
' ; } > ' + * (`unknown command: {`). Redirect only; read exit from env after `execLine`.
bareAgentShellQuote(outPath) + */
' 2>&1; printf "\\nEXIT:%s\\n" $? >> ' + const wrapped =
bareAgentShellQuote(outPath) line +
: line + ' > ' +
' > ' + bareAgentShellQuote(outPath) +
bareAgentShellQuote(outPath) + ' 2>&1'
' 2>&1'
const opts = const opts =
signal || timeoutMs signal || timeoutMs
? { ? {
@@ -3409,6 +3408,34 @@ async function bareAgentDispatchTool(o) {
} catch { } catch {
/* ignore */ /* ignore */
} }
if (captureExit) {
const env = ctx.vfs?.env && typeof ctx.vfs.env === 'object' ? ctx.vfs.env : null
const rawEc =
env && env.BARE_OS_EXIT_STATUS != null && env.BARE_OS_EXIT_STATUS !== ''
? env.BARE_OS_EXIT_STATUS
: ctx.exitCode
const n = Number(rawEc)
const codeStr = String(Number.isFinite(n) ? n : 0)
const exitLine = '\nEXIT:' + codeStr + '\n'
captured += exitLine
try {
if (vfs?.readFile && vfs?.writeFile && ctx.b4a && typeof ctx.b4a.concat === 'function') {
let prev = await vfs.readFile(outPath)
const prevBytes =
prev && prev.length
? prev instanceof Uint8Array
? prev
: ctx.b4a.from(prev)
: ctx.b4a.from('')
await vfs.writeFile(
outPath,
ctx.b4a.concat([prevBytes, ctx.b4a.from(exitLine)])
)
}
} catch {
/* ignore */
}
}
const max = 120_000 const max = 120_000
if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated' if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated'
return { ok: true, stdout_stderr: captured } return { ok: true, stdout_stderr: captured }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"schema": 2, "schema": 2,
"profileId": "bare-os-posix-like", "profileId": "bare-os-posix-like",
"generatedAt": "2026-04-22T21:10:53.539Z", "generatedAt": "2026-04-22T21:32:31.052Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.", "note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [ "commandIndex": [
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"schema": 1, "schema": 1,
"atMs": 1776892253538, "atMs": 1776893551051,
"commands": [ "commands": [
"agent", "agent",
"arch", "arch",
File diff suppressed because one or more lines are too long
+38 -11
View File
@@ -549,17 +549,16 @@ async function bareAgentDispatchTool(o) {
async function captureExec(line, timeoutMs, captureOpts) { async function captureExec(line, timeoutMs, captureOpts) {
const outPath = paths.cmdOut const outPath = paths.cmdOut
const captureExit = Boolean(captureOpts && captureOpts.captureExit) const captureExit = Boolean(captureOpts && captureOpts.captureExit)
const wrapped = captureExit /**
? '{ ' + * Do not wrap with `{ cmd ; }` — Bare OS `splitTokensBySemicolon` splits on every `;`
line + * at depth 0 and does not treat `{ … }` as a compound, so `{` became argv[0]
' ; } > ' + * (`unknown command: {`). Redirect only; read exit from env after `execLine`.
bareAgentShellQuote(outPath) + */
' 2>&1; printf "\\nEXIT:%s\\n" $? >> ' + const wrapped =
bareAgentShellQuote(outPath) line +
: line + ' > ' +
' > ' + bareAgentShellQuote(outPath) +
bareAgentShellQuote(outPath) + ' 2>&1'
' 2>&1'
const opts = const opts =
signal || timeoutMs signal || timeoutMs
? { ? {
@@ -590,6 +589,34 @@ async function bareAgentDispatchTool(o) {
} catch { } catch {
/* ignore */ /* ignore */
} }
if (captureExit) {
const env = ctx.vfs?.env && typeof ctx.vfs.env === 'object' ? ctx.vfs.env : null
const rawEc =
env && env.BARE_OS_EXIT_STATUS != null && env.BARE_OS_EXIT_STATUS !== ''
? env.BARE_OS_EXIT_STATUS
: ctx.exitCode
const n = Number(rawEc)
const codeStr = String(Number.isFinite(n) ? n : 0)
const exitLine = '\nEXIT:' + codeStr + '\n'
captured += exitLine
try {
if (vfs?.readFile && vfs?.writeFile && ctx.b4a && typeof ctx.b4a.concat === 'function') {
let prev = await vfs.readFile(outPath)
const prevBytes =
prev && prev.length
? prev instanceof Uint8Array
? prev
: ctx.b4a.from(prev)
: ctx.b4a.from('')
await vfs.writeFile(
outPath,
ctx.b4a.concat([prevBytes, ctx.b4a.from(exitLine)])
)
}
} catch {
/* ignore */
}
}
const max = 120_000 const max = 120_000
if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated' if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated'
return { ok: true, stdout_stderr: captured } return { ok: true, stdout_stderr: captured }
+38 -11
View File
@@ -3368,17 +3368,16 @@ async function bareAgentDispatchTool(o) {
async function captureExec(line, timeoutMs, captureOpts) { async function captureExec(line, timeoutMs, captureOpts) {
const outPath = paths.cmdOut const outPath = paths.cmdOut
const captureExit = Boolean(captureOpts && captureOpts.captureExit) const captureExit = Boolean(captureOpts && captureOpts.captureExit)
const wrapped = captureExit /**
? '{ ' + * Do not wrap with `{ cmd ; }` — Bare OS `splitTokensBySemicolon` splits on every `;`
line + * at depth 0 and does not treat `{ … }` as a compound, so `{` became argv[0]
' ; } > ' + * (`unknown command: {`). Redirect only; read exit from env after `execLine`.
bareAgentShellQuote(outPath) + */
' 2>&1; printf "\\nEXIT:%s\\n" $? >> ' + const wrapped =
bareAgentShellQuote(outPath) line +
: line + ' > ' +
' > ' + bareAgentShellQuote(outPath) +
bareAgentShellQuote(outPath) + ' 2>&1'
' 2>&1'
const opts = const opts =
signal || timeoutMs signal || timeoutMs
? { ? {
@@ -3409,6 +3408,34 @@ async function bareAgentDispatchTool(o) {
} catch { } catch {
/* ignore */ /* ignore */
} }
if (captureExit) {
const env = ctx.vfs?.env && typeof ctx.vfs.env === 'object' ? ctx.vfs.env : null
const rawEc =
env && env.BARE_OS_EXIT_STATUS != null && env.BARE_OS_EXIT_STATUS !== ''
? env.BARE_OS_EXIT_STATUS
: ctx.exitCode
const n = Number(rawEc)
const codeStr = String(Number.isFinite(n) ? n : 0)
const exitLine = '\nEXIT:' + codeStr + '\n'
captured += exitLine
try {
if (vfs?.readFile && vfs?.writeFile && ctx.b4a && typeof ctx.b4a.concat === 'function') {
let prev = await vfs.readFile(outPath)
const prevBytes =
prev && prev.length
? prev instanceof Uint8Array
? prev
: ctx.b4a.from(prev)
: ctx.b4a.from('')
await vfs.writeFile(
outPath,
ctx.b4a.concat([prevBytes, ctx.b4a.from(exitLine)])
)
}
} catch {
/* ignore */
}
}
const max = 120_000 const max = 120_000
if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated' if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated'
return { ok: true, stdout_stderr: captured } return { ok: true, stdout_stderr: captured }
@@ -1,7 +1,7 @@
{ {
"schema": 2, "schema": 2,
"profileId": "bare-os-posix-like", "profileId": "bare-os-posix-like",
"generatedAt": "2026-04-22T21:10:53.539Z", "generatedAt": "2026-04-22T21:32:31.052Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.", "note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [ "commandIndex": [
{ {
@@ -1,6 +1,6 @@
{ {
"schema": 1, "schema": 1,
"atMs": 1776892253538, "atMs": 1776893551051,
"commands": [ "commands": [
"agent", "agent",
"arch", "arch",
File diff suppressed because one or more lines are too long