-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoffline-box.ts
More file actions
111 lines (106 loc) · 5.09 KB
/
Copy pathoffline-box.ts
File metadata and controls
111 lines (106 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* The OFFLINE seam — an in-process `SandboxClient` so the WHOLE benchmark runs
* with no creds and no network, exactly like `examples/ui-audit/` does.
*
* The offline "agent" is a SCRIPTED STAND-IN for a real coding agent: it writes a
* canned solution per round instead of calling a model. That is the only thing
* stubbed — the matrix, the verifier, the held-out test execution, the judge wiring,
* and the stats all run for real. `--live` swaps this client for `new SandboxClient(...)`
* and the same dispatch runs each round in a real harness box.
*
* It implements only what `openSandboxRun` actually calls on a box:
* - `streamPrompt(prompt, opts)` — the "agent" turn. Writes the round's scripted
* solution into a real temp workspace and emits one terminal `done` event — the
* SAME shape a live box emits, carrying `tokenUsage` so the run meters honestly
* and `extractLlmCallEvent` reads it.
* - `fs.read` / `fs.write` — over the temp workspace (the `artifact` deliverable +
* the seeded test files live here).
* - `exec(cmd)` — runs the check + seed commands. Offline, `node` IS present so the
* test commands (`node --experimental-transform-types --test`) run for real — which
* is what lets the HELD-OUT execution genuinely grade the solution with no creds. But
* `tsc`/`biome` usually aren't installed, so the typecheck (and the test layer that
* `dependsOn` it) read as a FAIL — the honest offline signal. The dev checks never
* fully pass offline, so all `maxRounds` run, which is when refinement shows.
* - `delete()` — tears the temp dir down.
*
* The cast on the returned box (subset-as-`SandboxInstance`) is the offline seam: it
* implements only the members `openSandboxRun` calls, not the full ~40-member interface.
*/
import { exec as execCb } from 'node:child_process'
import { mkdtempSync, rmSync } from 'node:fs'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { promisify } from 'node:util'
import type { SandboxClient } from '@tangle-network/agent-runtime/loops'
import type { CreateSandboxOptions, SandboxEvent, SandboxInstance } from '@tangle-network/sandbox'
const execAsync = promisify(execCb)
/** A scripted offline solution: which file, and what content to write on a given
* round. `solutionFor(round)` lets round N differ from round N-1 — a REAL refine
* demo, not a constant. */
export interface OfflineScript {
path: string
solutionFor: (round: number) => string
}
function instanceMethods(workdir: string, script: OfflineScript) {
let round = 0
return {
id: `offline-${Math.random().toString(36).slice(2, 8)}`,
// The "agent" turn. Writes the scripted solution, emits one terminal event.
async *streamPrompt(_message: string | unknown[]): AsyncGenerator<SandboxEvent> {
const content = script.solutionFor(round)
round += 1
const abs = join(workdir, script.path)
await mkdir(dirname(abs), { recursive: true })
await writeFile(abs, content, 'utf8')
// The real sandbox terminal event shape: `done` with `data.tokenUsage` +
// top-level `totalCostUsd`. `extractLlmCallEvent` reads exactly this. The cast is
// structural: this is one member of the wide `SandboxEvent` union, written out
// literally; we don't reconstruct the whole union just to emit one done event.
yield {
type: 'done',
data: {
tokenUsage: { inputTokens: 600, outputTokens: 400 },
totalCostUsd: 0,
finalText: `wrote ${script.path} (offline round ${round})`,
},
} as unknown as SandboxEvent
},
fs: {
async read(path: string): Promise<string> {
return readFile(join(workdir, path), 'utf8')
},
async write(path: string, content: string): Promise<void> {
const abs = join(workdir, path)
await mkdir(dirname(abs), { recursive: true })
await writeFile(abs, content, 'utf8')
},
},
async exec(command: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
try {
const { stdout, stderr } = await execAsync(command, { cwd: workdir, timeout: 30_000 })
return { exitCode: 0, stdout, stderr }
} catch (err) {
const e = err as { code?: number; stdout?: string; stderr?: string; message?: string }
return {
exitCode: e.code ?? 1,
stdout: e.stdout ?? '',
stderr: e.stderr ?? e.message ?? '',
}
}
},
async delete(): Promise<void> {
rmSync(workdir, { recursive: true, force: true })
},
}
}
/** An in-process `SandboxClient`. Each `create()` mints a fresh temp workspace box. */
export function offlineSandboxClient(script: OfflineScript): SandboxClient {
return {
async create(_options?: CreateSandboxOptions): Promise<SandboxInstance> {
const workdir = mkdtempSync(join(tmpdir(), 'coding-bench-'))
// Subset-as-`SandboxInstance` (the offline seam — see the module docstring).
return instanceMethods(workdir, script) as unknown as SandboxInstance
},
}
}