Skip to content

Commit d893349

Browse files
GiniGini
authored andcommitted
feat: enforce deterministic sandbox deck tooling
1 parent b268a93 commit d893349

6 files changed

Lines changed: 19 additions & 4 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ For AWS/Bedrock runtime work, read `docs/AGENTCORE-AWS-RUNTIME.md` and the refer
4242
## Frontend foundation
4343

4444
Use `assistant-ui` as the preferred foundation for ONEVibe conversation threads, streaming messages, composer, history navigation, accessible message actions, and tool-state rendering. Preserve ONEComputer's bespoke dark/light visual system and custom evidence/artifact rail rather than forcing those surfaces into generic chat components. Any adoption must bind to the real server transcript/SSE contracts; demo arrays or browser-authoritative history are prohibited.
45+
46+
## Sandbox artifact dependencies
47+
48+
Artifact tooling required by an acceptance gate must be image/bootstrap managed and verified before the runtime reports ready. Never make a live agent install packages through the development proxy. Keep Claude's `--tools` availability mode-specific and use `--allowedTools` only as the separate approval layer; adding an approval allowlist does not remove a tool. Slide mode may receive a narrowly documented shell capability to invoke preinstalled renderers, while ordinary conversation modes must not.

docs/IMPLEMENTATION-LOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@
9393
- Attempted that upgraded harness against Azure with the sandbox-reachable LiteLLM relay. The provider returned HTTP 504 before yielding a sandbox identity; ONEVibe fenced the lease as `unknown`, did not retry, and the provider list showed no visible sandbox row. The attempt exposed an HTML provider-body projection bug, which was removed so external error bodies can no longer enter task evidence.
9494
- Promoted and hardened the Azure async lifecycle path: persisted provisioning identity before bootstrap, consistent single-resource lifecycle reads, headless Claude Code as the required runtime, optional Desktop installation, explicit managed CLI paths, stdin prompt transport, and a relay-only development proxy bypass that keeps TLS verification enabled. The first real sandbox Claude turn then completed through the scoped public relay with durable transcript/tool events and a lease-bound session. The PPTX gate remains open because the image lacks deterministic slide/PDF rendering dependencies.
9595
- Studied the AgentCore Claude/Codex harness's AWS chain and documented the accepted production design in `AGENTCORE-AWS-RUNTIME.md`: a sandbox-scoped AWS container credential-provider endpoint backed by short-lived STS sessions, never mounted profiles or copied static keys. Added `LIVE-E2E-ENGINEERING-LOG.md` so failed provider experiments and their fixes remain durable engineering evidence.
96+
- Prepared deterministic sandbox deck generation by adding managed `pptxgenjs`/`pdf-lib` bootstrap verification, a fixed six-file Slide deliverable contract, and a true mode-specific Claude tool availability list. Bash is omitted from ordinary jobs and enabled only for Slide mode; live Azure deck and negative capability tests remain required before closing the gate.

docs/LIVE-E2E-ENGINEERING-LOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,8 @@ The deck gate failed: the sandbox lacked `python-pptx`/PDF libraries, package in
4646
4. Validate real PPTX/PDF magic bytes, transcript restart, evidence chain, and explicit cleanup.
4747
5. Run credential/workspace/event/export residue scans.
4848

49+
### Deterministic deck runtime follow-up
50+
51+
The next POC slice bakes `pptxgenjs` and `pdf-lib` into the ONEComputer headless Claude bootstrap and verifies both modules before a sandbox may become ready. Slide jobs receive `NODE_PATH` for those managed modules and are instructed to produce a fixed six-file deliverable contract without installing packages at task time.
52+
53+
Tool governance now distinguishes availability from approval. ONEVibe passes the same mode-specific list to Claude's `--tools` and `--allowedTools`: ordinary modes retain only path-confined file/search tools, while Slide mode additionally receives Bash solely to run the preinstalled renderer. This still requires a live negative test proving Bash is absent outside Slide mode and a real deck run proving the expected binary signatures.

server/onecomputer-sandbox-runner.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('OneComputerSandboxRuntimeAdapter', () => {
1414
it('only exposes browser MCP controls when the governed runtime explicitly enables them', async () => {
1515
const { GOVERNED_BROWSER_TOOLS, browserEvidenceFor, governedClaudeTools, isGovernedBrowserTool, isSandboxRuntimeReady } = await import('./onecomputer-sandbox-runner.js')
1616
expect(governedClaudeTools(false)).toEqual(['Read', 'Write', 'Edit', 'Glob', 'Grep'])
17+
expect(governedClaudeTools(false, true)).toEqual(['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash'])
1718
expect(governedClaudeTools(true)).toEqual(expect.arrayContaining([...GOVERNED_BROWSER_TOOLS]))
1819
expect(GOVERNED_BROWSER_TOOLS).toEqual(expect.arrayContaining(['mcp__playwright__browser_select_option', 'mcp__playwright__browser_wait_for']))
1920
expect(GOVERNED_BROWSER_TOOLS).not.toEqual(expect.arrayContaining(['mcp__playwright__browser_evaluate', 'mcp__playwright__browser_file_upload', 'mcp__playwright__browser_cookie_list', 'mcp__playwright__browser_route']))
@@ -109,6 +110,8 @@ describe('OneComputerSandboxRuntimeAdapter', () => {
109110
expect(commands.some((command) => command.includes('claude --print'))).toBe(true)
110111
expect(commands.some((command) => command.includes('--output-format stream-json --verbose'))).toBe(true)
111112
expect(commands.some((command) => command.includes('export PATH=/opt/node22/bin:/home/kasm-user/.npm-global/bin:$PATH'))).toBe(true)
113+
expect(commands.some((command) => command.includes('export NODE_PATH=/home/kasm-user/.npm-global/lib/node_modules'))).toBe(true)
114+
expect(commands.some((command) => command.includes('--tools'))).toBe(true)
112115
const launchCommand = commands.find((command) => command.includes('claude --print'))!
113116
expect(launchCommand).toContain('< .onevibe-prompt > .onevibe-events.jsonl')
114117
expect(launchCommand.indexOf('claude --print')).toBeLessThan(launchCommand.indexOf('rm -f .onevibe-prompt'))

server/onecomputer-sandbox-runner.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ export const GOVERNED_BROWSER_TOOLS = [
8484
'mcp__playwright__browser_take_screenshot',
8585
] as const
8686

87-
export const governedClaudeTools = (browserAutomation: boolean) => [
87+
export const governedClaudeTools = (browserAutomation: boolean, shell = false) => [
8888
'Read', 'Write', 'Edit', 'Glob', 'Grep',
89+
...(shell ? ['Bash'] : []),
8990
...(browserAutomation ? GOVERNED_BROWSER_TOOLS : []),
9091
]
9192

@@ -295,10 +296,11 @@ export class OneComputerSandboxRuntimeAdapter implements RuntimeAdapter {
295296
].join('\n\n')
296297
const encodedPrompt = Buffer.from(agentPrompt).toString('base64')
297298
const workspace = `/tmp/onevibe/${task.id}`
298-
const allowedTools = governedClaudeTools(browserAutomationEnabled)
299+
const allowedTools = governedClaudeTools(browserAutomationEnabled, task.mode === 'slides')
299300
const command = [
300301
'set -eu',
301302
'export PATH=/opt/node22/bin:/home/kasm-user/.npm-global/bin:$PATH',
303+
'export NODE_PATH=/home/kasm-user/.npm-global/lib/node_modules',
302304
...(sandboxBaseUrl ? [`export ANTHROPIC_BASE_URL=${shellQuote(sandboxBaseUrl)}`] : []),
303305
...(!this.options.gatewayEnforced && sandboxNoProxyHost ? [
304306
`export NO_PROXY="\${NO_PROXY:+$NO_PROXY,}${sandboxNoProxyHost}"`,
@@ -317,7 +319,7 @@ export class OneComputerSandboxRuntimeAdapter implements RuntimeAdapter {
317319
'rm -f .onevibe-events.jsonl .onevibe-exitcode .onevibe-pid',
318320
'(',
319321
' set +e',
320-
` claude --print --output-format stream-json --verbose --model ${shellQuote(configuredClaude.model)} --permission-mode bypassPermissions --setting-sources project --allowedTools ${shellQuote(allowedTools.join(','))}${resumableSessionId ? ` --resume ${shellQuote(resumableSessionId)}` : ''} < .onevibe-prompt > .onevibe-events.jsonl 2>&1`,
322+
` claude --print --output-format stream-json --verbose --model ${shellQuote(configuredClaude.model)} --permission-mode bypassPermissions --setting-sources project --tools ${shellQuote(allowedTools.join(','))} --allowedTools ${shellQuote(allowedTools.join(','))}${resumableSessionId ? ` --resume ${shellQuote(resumableSessionId)}` : ''} < .onevibe-prompt > .onevibe-events.jsonl 2>&1`,
321323
' onevibe_exit_code="$?"',
322324
' rm -f .onevibe-prompt',
323325
' printf %s "$onevibe_exit_code" > .onevibe-exitcode',

server/skill-packs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const pack = (id: TaskSkill, title: string, body: string): SkillPack => ({
1313
const packs: Record<TaskSkill, SkillPack> = {
1414
research: pack('research', 'Evidence-led research', 'Separate observed evidence, inference, and unresolved questions. Preserve source provenance when it is available. Do not fabricate citations or claim a reference was fetched unless a tool result proves it.'),
1515
web_build: pack('web_build', 'Accessible web build', 'Build responsive, semantic interfaces. Prefer native controls, visible focus, reduced motion support, and no external assets unless the task explicitly authorizes them. Validate the primary task flow before delivery.'),
16-
slides: pack('slides', 'Executive slide narrative', 'Build a concise decision-oriented deck. Start with the decision in view, make assumptions explicit, keep one idea per slide, and provide speaker notes. Deliver portable PPTX and PDF artifacts when the runtime supports them.'),
16+
slides: pack('slides', 'Executive slide narrative', 'Build a concise decision-oriented deck. Start with the decision in view, make assumptions explicit, keep one idea per slide, and provide speaker notes. In a ONEComputer sandbox, use the preinstalled Node modules `pptxgenjs` and `pdf-lib` through `NODE_PATH`; never install packages during the task. Deliver exactly `deck.pptx`, `deck.pdf`, `outline.json`, `speaker-notes.md`, `index.html`, and `README.md`. Validate that PPTX begins with ZIP magic and PDF begins with `%PDF-` before delivery.'),
1717
data_analysis: pack('data_analysis', 'Transparent data analysis', 'State source limits and assumptions. Keep calculations inspectable, distinguish sample data from factual data, and make the decision implication clear without overstating confidence.'),
1818
document: pack('document', 'Portable structured writing', 'Write for a named audience using a clear summary, meaningful headings, and concrete next steps. Preserve portable source and flag unsupported claims or missing evidence.'),
1919
product_design: pack('product_design', 'Product design review', 'Use purposeful interaction design, clear states, responsive hierarchy, and accessible contrast. Prefer calm, useful composition over decoration and document meaningful design trade-offs.'),

0 commit comments

Comments
 (0)