Merge remote-tracking branch 'origin/master' into codex/truncated-design
# Conflicts: # docs/capability-seams.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.md # docs/event-producer-consumer.md # docs/module-graph.md # docs/rfc/INDEX.md # docs/tool-catalog.md # examples/acp-agent/README.md # packages/README.md # packages/bash/bash/README.md # packages/core/tools/tests/gen-tool-catalog.spec.ts # packages/support/acp-snapshot/src/harness.ts # pnpm-lock.yaml # scripts/gen-doc-graphs.ts # scripts/gen-tool-catalog.ts # scripts/type-equiv.manifest.json
This commit is contained in:
@@ -27,4 +27,4 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi;
|
||||
|
||||
## Sandboxing
|
||||
|
||||
`TODO(permissions/sandbox)`: execution policy does NOT belong in this package. Use the `tools/pre-execute` deny/ask gate or implement a sandboxing `BashExecutor` — see docs/architecture.md § Extending The Harness. Reference points: Claude Code wraps commands in sandbox-exec/bubblewrap; Codex applies seatbelt/landlock plus an execpolicy prefix-rule engine.
|
||||
Execution policy does NOT belong in this package: this executor always runs commands unconfined. Confinement is [`dsh-bash-sandbox`](../bash-sandbox/README.md), which extends this executor verbatim and confines commands under the `ctx.sandbox` seam's bwrap/Landlock/Seatbelt backends ([sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md)); per-call allow/deny/ask policy belongs on the `tools/pre-execute` gate.
|
||||
|
||||
@@ -136,6 +136,10 @@ export class LocalBashExecutor extends BashExecutor {
|
||||
// Carry the owner through verbatim (required-but-nullable on the spec):
|
||||
// the executor never interprets it — the consumer's access policy does.
|
||||
owner: request.owner,
|
||||
// Carry a sandbox-mode override through verbatim: this executor never
|
||||
// confines, so the field is inert here (the seam contract) — a
|
||||
// sandboxing subclass overrides resolve() to stamp its default instead.
|
||||
sandboxMode: request.sandboxMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +221,18 @@ export class LocalBashExecutor extends BashExecutor {
|
||||
return this.tasks.get(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Full collected stderr of a tracked task from stream start (bounded by the
|
||||
* in-memory cap; bytes only in the spill file are not re-read). A protected
|
||||
* seam for subclasses that classify a settled task's outcome — reading here
|
||||
* does NOT advance the consumer's {@link readOutput} cursor. An unknown id
|
||||
* (a task already dropped by disposal) reads as empty.
|
||||
*/
|
||||
protected collectedStderr(id: BashTaskId): string {
|
||||
const task = this.tasks.get(id)
|
||||
return task === undefined ? '' : task.running.stderr.readFrom(0).text
|
||||
}
|
||||
|
||||
ownerOf(id: BashTaskId): OwnerToken | undefined {
|
||||
// Unknown id and known-but-ownerless both read as undefined — the consumer
|
||||
// treats undefined as "open" and a truly unknown id fails at readOutput/kill.
|
||||
|
||||
@@ -40,12 +40,14 @@ async function readUntil(
|
||||
): Promise<BashTaskRead> {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
let last: BashTaskRead | undefined
|
||||
let delta = ''
|
||||
while (Date.now() < deadline) {
|
||||
last = bash.readOutput(id)
|
||||
if (last.delta.includes(expected)) return last
|
||||
delta += last.delta
|
||||
if (delta.includes(expected)) return { ...last, delta }
|
||||
await new Promise(resolve => setTimeout(resolve, 20))
|
||||
}
|
||||
throw new Error(`task ${id} output did not include ${JSON.stringify(expected)}; last delta was ${JSON.stringify(last?.delta ?? '')}`)
|
||||
throw new Error(`task ${id} output did not include ${JSON.stringify(expected)}; output was ${JSON.stringify(delta)}, last delta was ${JSON.stringify(last?.delta ?? '')}`)
|
||||
}
|
||||
|
||||
describe('LocalBashExecutor.run', () => {
|
||||
|
||||
Reference in New Issue
Block a user