@boardwalk-labs/runner 0.2.0 → 0.2.1
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 +11 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +14 -0
- package/dist/daemon/container.d.ts +3 -2
- package/dist/daemon/container.js +15 -2
- package/dist/daemon/daemon.d.ts +1 -0
- package/dist/daemon/daemon.js +16 -1
- package/dist/runtime/agent/budget.d.ts +14 -1
- package/dist/runtime/agent/budget.js +23 -0
- package/dist/runtime/broker_child_dispatcher.d.ts +0 -2
- package/dist/runtime/broker_child_dispatcher.js +0 -7
- package/dist/runtime/budget_gate.d.ts +51 -0
- package/dist/runtime/budget_gate.js +114 -0
- package/dist/runtime/freeze_coordinator.js +8 -0
- package/dist/runtime/index.d.ts +14 -1
- package/dist/runtime/index.js +72 -17
- package/dist/runtime/leaf_executor.d.ts +20 -0
- package/dist/runtime/leaf_executor.js +13 -4
- package/dist/runtime/local_workspace_store.d.ts +22 -0
- package/dist/runtime/local_workspace_store.js +103 -0
- package/dist/runtime/program_runner.d.ts +28 -13
- package/dist/runtime/program_runner.js +69 -23
- package/dist/runtime/program_worker.d.ts +5 -0
- package/dist/runtime/program_worker.js +2 -0
- package/dist/runtime/suspension.d.ts +8 -2
- package/dist/runtime/workflow_host.d.ts +23 -2
- package/dist/runtime/workflow_host.js +35 -0
- package/dist/runtime/workspace_store.d.ts +31 -3
- package/dist/runtime/workspace_store.js +43 -4
- package/package.json +1 -1
|
@@ -27,6 +27,24 @@ const exec = promisify(execFile);
|
|
|
27
27
|
* workspace never enters memory. NOT a security boundary (tenant isolation is the per-workflow key);
|
|
28
28
|
* purely a guardrail, like the run budget's `max_usd`. */
|
|
29
29
|
export const WORKSPACE_SNAPSHOT_MAX_BYTES = 512 * 1024 * 1024;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve what this run persists (docs/WORKSPACE_PERSISTENCE.md §3): the manifest's declaration
|
|
32
|
+
* UNIONED with every `agent({ memory })` dir the run actually used.
|
|
33
|
+
*
|
|
34
|
+
* The union is why this is resolved at PERSIST time, not construction time: memory dirs are
|
|
35
|
+
* undeclared by design (`sdk/src/types.ts` — "`mcp` servers, `skills`, and `memory` — the manifest
|
|
36
|
+
* declares none of them"), so they are only known once the run has made the agent calls. A workflow
|
|
37
|
+
* whose manifest says nothing at all still persists, iff it used memory.
|
|
38
|
+
*
|
|
39
|
+
* `true` swallows the list: the whole workspace already contains every memory dir. An empty array
|
|
40
|
+
* means persist nothing, which is the common case and must stay cheap.
|
|
41
|
+
*/
|
|
42
|
+
export function resolvePersistSelection(declared, memoryDirs) {
|
|
43
|
+
if (declared === true)
|
|
44
|
+
return true;
|
|
45
|
+
const list = declared === undefined || declared === false ? [] : declared;
|
|
46
|
+
return [...new Set([...list, ...memoryDirs])];
|
|
47
|
+
}
|
|
30
48
|
export class WorkspaceStore {
|
|
31
49
|
deps;
|
|
32
50
|
tmpPath;
|
|
@@ -71,8 +89,15 @@ export class WorkspaceStore {
|
|
|
71
89
|
* only redundant archive is a self-hosted+persist run, where the broker returns a null URL. */
|
|
72
90
|
async persist() {
|
|
73
91
|
try {
|
|
92
|
+
// What this run actually compounds: the manifest's declaration ∪ the memory dirs it used.
|
|
93
|
+
// Nothing selected is the common case (a workflow that opted into neither) — return before any
|
|
94
|
+
// fs or broker work, so persistence costs a run that doesn't use it precisely nothing.
|
|
95
|
+
const selection = this.deps.selection();
|
|
96
|
+
const paths = selection === true ? undefined : await this.presentPaths(selection);
|
|
97
|
+
if (paths !== undefined && paths.length === 0)
|
|
98
|
+
return 0;
|
|
74
99
|
await mkdir(dirname(this.tmpPath), { recursive: true }); // per-run TMPDIR may not exist yet
|
|
75
|
-
const size = await this.deps.archiver.archive(this.deps.workspaceRoot, this.tmpPath);
|
|
100
|
+
const size = await this.deps.archiver.archive(this.deps.workspaceRoot, this.tmpPath, paths);
|
|
76
101
|
// Guardrail: an oversized snapshot is dropped (logged), never read into memory or uploaded — the
|
|
77
102
|
// workflow re-does filesystem work next run, as it would without persistence. Checked on the
|
|
78
103
|
// on-disk archive size so the big tarball never hits the worker's heap.
|
|
@@ -98,12 +123,23 @@ export class WorkspaceStore {
|
|
|
98
123
|
return 0;
|
|
99
124
|
}
|
|
100
125
|
}
|
|
126
|
+
/** Narrow a selection to the dirs that exist: `tar` fails the whole archive on one missing member,
|
|
127
|
+
* and declaring a dir the run hasn't written yet is ordinary (first run of `persist: ["cache"]`). */
|
|
128
|
+
async presentPaths(selection) {
|
|
129
|
+
const checked = await Promise.all(selection.map(async (p) => (await this.deps.fs.exists(join(this.deps.workspaceRoot, p))) ? p : null));
|
|
130
|
+
return checked.filter((p) => p !== null);
|
|
131
|
+
}
|
|
101
132
|
}
|
|
102
133
|
/** Production archiver — shells out to the runner image's `tar` (the runner has full shell tooling). */
|
|
103
134
|
export class TarWorkspaceArchiver {
|
|
104
|
-
async archive(dir, destPath) {
|
|
105
|
-
// `-C dir
|
|
106
|
-
|
|
135
|
+
async archive(dir, destPath, paths) {
|
|
136
|
+
// `-C dir <members>` archives relative to dir, so extract restores in place either way: `.` for
|
|
137
|
+
// the whole tree (`persist: true`), or exactly the named dirs (`persist: [...]` ∪ memory dirs).
|
|
138
|
+
// Members are workspace-relative and schema-validated (no `..`, no absolute, no backslashes —
|
|
139
|
+
// sdk/src/manifest.ts `persistPath`) and pre-filtered to those that exist; `--` keeps a name
|
|
140
|
+
// that starts with `-` from being read as a flag.
|
|
141
|
+
const members = paths === undefined ? ["."] : [...paths];
|
|
142
|
+
await exec("tar", ["czf", destPath, "-C", dir, "--", ...members]);
|
|
107
143
|
return (await stat(destPath)).size;
|
|
108
144
|
}
|
|
109
145
|
async extract(srcPath, dir) {
|
|
@@ -128,6 +164,9 @@ export class NodeWorkspaceFs {
|
|
|
128
164
|
rm(path) {
|
|
129
165
|
return rm(path, { force: true });
|
|
130
166
|
}
|
|
167
|
+
async exists(path) {
|
|
168
|
+
return (await stat(path).catch(() => null)) !== null;
|
|
169
|
+
}
|
|
131
170
|
}
|
|
132
171
|
function errMsg(err) {
|
|
133
172
|
return err instanceof Error ? err.message : String(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boardwalk-labs/runner",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Boardwalk self-hosted runner: execute cloud-scheduled runs on your own machines. Currently: the canonical runner contract types.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|