@workbench-ai/workbench-core 0.0.46

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.
Files changed (72) hide show
  1. package/dist/adapter-auth.d.ts +63 -0
  2. package/dist/adapter-auth.d.ts.map +1 -0
  3. package/dist/adapter-auth.js +244 -0
  4. package/dist/execution-events.d.ts +53 -0
  5. package/dist/execution-events.d.ts.map +1 -0
  6. package/dist/execution-events.js +195 -0
  7. package/dist/execution-graph.d.ts +27 -0
  8. package/dist/execution-graph.d.ts.map +1 -0
  9. package/dist/execution-graph.js +126 -0
  10. package/dist/execution-jobs.d.ts +70 -0
  11. package/dist/execution-jobs.d.ts.map +1 -0
  12. package/dist/execution-jobs.js +229 -0
  13. package/dist/execution-outputs.d.ts +9 -0
  14. package/dist/execution-outputs.d.ts.map +1 -0
  15. package/dist/execution-outputs.js +393 -0
  16. package/dist/execution-phases.d.ts +21 -0
  17. package/dist/execution-phases.d.ts.map +1 -0
  18. package/dist/execution-phases.js +262 -0
  19. package/dist/execution-runtime-types.d.ts +35 -0
  20. package/dist/execution-runtime-types.d.ts.map +1 -0
  21. package/dist/execution-runtime-types.js +1 -0
  22. package/dist/execution-scheduler.d.ts +31 -0
  23. package/dist/execution-scheduler.d.ts.map +1 -0
  24. package/dist/execution-scheduler.js +241 -0
  25. package/dist/execution-traces.d.ts +16 -0
  26. package/dist/execution-traces.d.ts.map +1 -0
  27. package/dist/execution-traces.js +164 -0
  28. package/dist/execution-usage.d.ts +12 -0
  29. package/dist/execution-usage.d.ts.map +1 -0
  30. package/dist/execution-usage.js +433 -0
  31. package/dist/generic-spec.d.ts +113 -0
  32. package/dist/generic-spec.d.ts.map +1 -0
  33. package/dist/generic-spec.js +656 -0
  34. package/dist/index.d.ts +160 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +2858 -0
  37. package/dist/model-prices-litellm.d.ts +9674 -0
  38. package/dist/model-prices-litellm.d.ts.map +1 -0
  39. package/dist/model-prices-litellm.js +9668 -0
  40. package/dist/runtime-utils.d.ts +18 -0
  41. package/dist/runtime-utils.d.ts.map +1 -0
  42. package/dist/runtime-utils.js +108 -0
  43. package/dist/sandbox-backends/docker.d.ts +5 -0
  44. package/dist/sandbox-backends/docker.d.ts.map +1 -0
  45. package/dist/sandbox-backends/docker.js +568 -0
  46. package/dist/sandbox-backends/index.d.ts +37 -0
  47. package/dist/sandbox-backends/index.d.ts.map +1 -0
  48. package/dist/sandbox-backends/index.js +79 -0
  49. package/dist/sandbox-backends/names.d.ts +6 -0
  50. package/dist/sandbox-backends/names.d.ts.map +1 -0
  51. package/dist/sandbox-backends/names.js +14 -0
  52. package/dist/sandbox-backends/template-images.d.ts +4 -0
  53. package/dist/sandbox-backends/template-images.d.ts.map +1 -0
  54. package/dist/sandbox-backends/template-images.js +48 -0
  55. package/dist/sandbox-inputs.d.ts +27 -0
  56. package/dist/sandbox-inputs.d.ts.map +1 -0
  57. package/dist/sandbox-inputs.js +220 -0
  58. package/dist/sandbox-plane.d.ts +89 -0
  59. package/dist/sandbox-plane.d.ts.map +1 -0
  60. package/dist/sandbox-plane.js +327 -0
  61. package/dist/subject-patch.d.ts +8 -0
  62. package/dist/subject-patch.d.ts.map +1 -0
  63. package/dist/subject-patch.js +63 -0
  64. package/dist/trace-files.d.ts +18 -0
  65. package/dist/trace-files.d.ts.map +1 -0
  66. package/dist/trace-files.js +94 -0
  67. package/environments/libreoffice-agent/Dockerfile +13 -0
  68. package/environments/libreoffice-python/Dockerfile +11 -0
  69. package/environments/node-22/Dockerfile +3 -0
  70. package/environments/python-3.12/Dockerfile +8 -0
  71. package/package.json +42 -0
  72. package/worker/sandbox-adapter-runner.cjs +275 -0
@@ -0,0 +1,327 @@
1
+ import { assertWorkbenchExecutionIsolation, validateWorkbenchExecutionOutputPayloads, } from "./execution-outputs.js";
2
+ export async function executeValidatedSandboxExecution(plane, execution, options) {
3
+ assertWorkbenchExecutionIsolation(execution);
4
+ const inputs = await options.fileStore.materializeInputs(execution);
5
+ const now = options.now ?? new Date().toISOString();
6
+ const timing = {};
7
+ timing.prepareStartedAt = new Date().toISOString();
8
+ const environment = plane.prepareEnvironment
9
+ ? await plane.prepareEnvironment(execution, options)
10
+ : {
11
+ backend: plane.backend.name,
12
+ kind: execution.sandbox.kind,
13
+ ref: execution.sandbox.ref,
14
+ };
15
+ timing.prepareFinishedAt = new Date().toISOString();
16
+ const allocation = createWorkbenchSandboxAllocation(execution, {
17
+ backend: plane.backend.name,
18
+ runnerId: options.runnerId,
19
+ now,
20
+ });
21
+ const capability = createWorkbenchExecutionCapability(execution, { now });
22
+ assertScopeIssues("Sandbox allocation", collectSandboxAllocationScopeIssues(allocation, execution, { now }));
23
+ assertScopeIssues("Execution capability", collectExecutionCapabilityScopeIssues(capability, execution, { now }));
24
+ timing.createStartedAt = new Date().toISOString();
25
+ const sandbox = await plane.createSandbox({
26
+ execution,
27
+ environment,
28
+ allocation,
29
+ capability,
30
+ inputs,
31
+ }, options);
32
+ timing.createFinishedAt = new Date().toISOString();
33
+ assertScopeIssues("Sandbox handle", collectSandboxHandleScopeIssues(sandbox, allocation, execution));
34
+ let result;
35
+ try {
36
+ timing.execStartedAt = new Date().toISOString();
37
+ result = await plane.exec({
38
+ execution,
39
+ environment,
40
+ sandbox,
41
+ allocation,
42
+ capability,
43
+ inputs,
44
+ }, options);
45
+ timing.execFinishedAt = new Date().toISOString();
46
+ }
47
+ catch (error) {
48
+ const finishedAt = new Date().toISOString();
49
+ timing.execFinishedAt = finishedAt;
50
+ result = {
51
+ executionId: execution.id,
52
+ status: "failed",
53
+ startedAt: now,
54
+ finishedAt,
55
+ outputs: {},
56
+ error: error instanceof Error ? error.message : String(error),
57
+ metadata: {
58
+ sandbox: createWorkbenchSandboxExecutionMetadata({
59
+ backend: plane.backend.name,
60
+ allocation,
61
+ capability,
62
+ handle: sandbox,
63
+ }),
64
+ },
65
+ };
66
+ }
67
+ finally {
68
+ timing.destroyStartedAt = new Date().toISOString();
69
+ await plane.destroySandbox(sandbox, options);
70
+ timing.destroyFinishedAt = new Date().toISOString();
71
+ }
72
+ result = attachSandboxLifecycleTiming(result, timing);
73
+ if (result.executionId !== execution.id) {
74
+ throw new Error(`Sandbox returned execution id ${result.executionId}, expected ${execution.id}.`);
75
+ }
76
+ if (result.status !== "succeeded") {
77
+ return { result, payloads: {} };
78
+ }
79
+ const outputPayloads = {};
80
+ for (const contract of execution.outputs) {
81
+ const ref = result.outputs[contract.name];
82
+ if (!ref) {
83
+ if (contract.required) {
84
+ throw new Error(`Sandbox result for ${execution.id} omitted required output ref ${contract.name}.`);
85
+ }
86
+ continue;
87
+ }
88
+ outputPayloads[contract.name] = await options.fileStore.readJson(ref);
89
+ }
90
+ return {
91
+ result,
92
+ payloads: validateWorkbenchExecutionOutputPayloads(execution, outputPayloads),
93
+ };
94
+ }
95
+ function attachSandboxLifecycleTiming(result, timing) {
96
+ const metadata = isJsonRecord(result.metadata) ? result.metadata : {};
97
+ const completedJob = isJsonRecord(metadata.completedJob) ? metadata.completedJob : null;
98
+ return {
99
+ ...result,
100
+ metadata: {
101
+ ...metadata,
102
+ sandboxTiming: timing,
103
+ ...(completedJob ? { completedJob: attachTimingToCompletedJob(completedJob, timing) } : {}),
104
+ ...(isJsonRecord(metadata.sandbox)
105
+ ? { sandbox: attachTimingToSandboxMetadata(metadata.sandbox, timing) }
106
+ : {}),
107
+ },
108
+ };
109
+ }
110
+ function attachTimingToCompletedJob(job, timing) {
111
+ if (!isJsonRecord(job.output)) {
112
+ return job;
113
+ }
114
+ const sandbox = isJsonRecord(job.output.sandbox)
115
+ ? attachTimingToSandboxMetadata(job.output.sandbox, timing)
116
+ : undefined;
117
+ return {
118
+ ...job,
119
+ output: {
120
+ ...job.output,
121
+ ...(sandbox ? { sandbox } : {}),
122
+ },
123
+ };
124
+ }
125
+ function attachTimingToSandboxMetadata(sandbox, timing) {
126
+ return {
127
+ ...sandbox,
128
+ timing,
129
+ };
130
+ }
131
+ export function createWorkbenchSandboxExecutionMetadata(args) {
132
+ return {
133
+ backend: args.backend,
134
+ allocation: {
135
+ ...args.allocation,
136
+ template: { ...args.allocation.template },
137
+ network: {
138
+ ...args.allocation.network,
139
+ ...(args.allocation.network.allow ? { allow: [...args.allocation.network.allow] } : {}),
140
+ },
141
+ },
142
+ capability: {
143
+ ...args.capability,
144
+ subject: { ...args.capability.subject },
145
+ inputs: args.capability.inputs.map((input) => ({ ...input })),
146
+ network: {
147
+ ...args.capability.network,
148
+ ...(args.capability.network.allow ? { allow: [...args.capability.network.allow] } : {}),
149
+ },
150
+ },
151
+ handle: {
152
+ ...args.handle,
153
+ template: { ...args.handle.template },
154
+ ...(args.handle.metadata ? { metadata: { ...args.handle.metadata } } : {}),
155
+ },
156
+ };
157
+ }
158
+ export function collectSandboxHandleScopeIssues(sandbox, allocation, execution) {
159
+ const issues = [];
160
+ if (sandbox.executionId !== execution.id) {
161
+ issues.push(`Sandbox handle execution id ${sandbox.executionId} does not match ${execution.id}.`);
162
+ }
163
+ if (sandbox.sandboxId !== allocation.sandboxId) {
164
+ issues.push(`Sandbox handle id ${sandbox.sandboxId} does not match allocation ${allocation.sandboxId}.`);
165
+ }
166
+ if (sandbox.lifecycleId !== allocation.lifecycleId) {
167
+ issues.push(`Sandbox handle lifecycle id does not match allocation ${allocation.lifecycleId}.`);
168
+ }
169
+ if (sandbox.backend !== allocation.backend) {
170
+ issues.push(`Sandbox handle backend ${sandbox.backend} does not match allocation ${allocation.backend}.`);
171
+ }
172
+ if (sandbox.template.kind !== allocation.template.kind || sandbox.template.ref !== allocation.template.ref) {
173
+ issues.push(`Sandbox handle template does not match allocation for execution ${execution.id}.`);
174
+ }
175
+ return issues;
176
+ }
177
+ export function createWorkbenchSandboxAllocation(execution, options) {
178
+ const nowMs = options.now ? Date.parse(options.now) : Date.now();
179
+ const ttlMs = options.ttlMs ?? Math.max(60_000, execution.policy.resources.timeoutMinutes * 60_000 + 60_000);
180
+ const safeExecutionId = execution.id.replace(/[^a-z0-9_]+/giu, "_");
181
+ const nonce = allocationNonce();
182
+ return {
183
+ sandboxId: options.sandboxId ?? `sbx_${safeExecutionId}_${nonce}`,
184
+ executionId: execution.id,
185
+ lifecycleId: options.lifecycleId ?? `lc_${safeExecutionId}_${nowMs}_${nonce}`,
186
+ backend: options.backend,
187
+ runnerId: options.runnerId ?? "local-runner",
188
+ template: { ...execution.sandbox },
189
+ network: {
190
+ ...execution.policy.network,
191
+ ...(execution.policy.network.allow ? { allow: [...execution.policy.network.allow] } : {}),
192
+ },
193
+ status: "allocated",
194
+ createdAt: new Date(nowMs).toISOString(),
195
+ expiresAt: new Date(nowMs + ttlMs).toISOString(),
196
+ };
197
+ }
198
+ function allocationNonce() {
199
+ const bytes = new Uint8Array(6);
200
+ if (globalThis.crypto?.getRandomValues) {
201
+ globalThis.crypto.getRandomValues(bytes);
202
+ }
203
+ else {
204
+ for (let index = 0; index < bytes.length; index += 1) {
205
+ bytes[index] = Math.floor(Math.random() * 256);
206
+ }
207
+ }
208
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
209
+ }
210
+ export function createWorkbenchExecutionCapability(execution, options = {}) {
211
+ const nowMs = options.now ? Date.parse(options.now) : Date.now();
212
+ const ttlMs = options.ttlMs ?? Math.max(60_000, execution.policy.resources.timeoutMinutes * 60_000 + 60_000);
213
+ return {
214
+ executionId: execution.id,
215
+ subject: {
216
+ tenantId: execution.policy.tenantId,
217
+ projectId: execution.projectId,
218
+ runId: execution.runId,
219
+ ...(execution.subjectId ? { subjectId: execution.subjectId } : {}),
220
+ },
221
+ inputs: execution.inputs.map((input) => ({ ...input })),
222
+ outputPrefix: options.outputPrefix ?? `executions/${execution.id}/outputs/`,
223
+ network: {
224
+ ...execution.policy.network,
225
+ ...(execution.policy.network.allow ? { allow: [...execution.policy.network.allow] } : {}),
226
+ },
227
+ expiresAt: new Date(nowMs + ttlMs).toISOString(),
228
+ };
229
+ }
230
+ export function collectExecutionCapabilityScopeIssues(capability, execution, options = {}) {
231
+ const issues = [];
232
+ if (capability.executionId !== execution.id) {
233
+ issues.push(`Capability execution id ${capability.executionId} does not match ${execution.id}.`);
234
+ }
235
+ if (capability.subject.tenantId !== execution.policy.tenantId) {
236
+ issues.push(`Capability tenant id does not match execution ${execution.id}.`);
237
+ }
238
+ if (capability.subject.projectId !== execution.projectId || capability.subject.runId !== execution.runId) {
239
+ issues.push(`Capability project/run scope does not match execution ${execution.id}.`);
240
+ }
241
+ if ((capability.subject.subjectId ?? null) !== (execution.subjectId ?? null)) {
242
+ issues.push(`Capability subject scope does not match execution ${execution.id}.`);
243
+ }
244
+ if (!capability.outputPrefix.startsWith(`executions/${execution.id}/`)) {
245
+ issues.push(`Capability output prefix must be scoped under executions/${execution.id}/.`);
246
+ }
247
+ if (networkPolicyKey(capability.network) !== networkPolicyKey(execution.policy.network)) {
248
+ issues.push(`Capability network policy does not match execution ${execution.id}.`);
249
+ }
250
+ const allowedInputs = new Set(execution.inputs.map((input) => `${input.name}\0${input.ref}\0${input.mountPath}\0${input.writable}`));
251
+ for (const input of capability.inputs) {
252
+ const key = `${input.name}\0${input.ref}\0${input.mountPath}\0${input.writable}`;
253
+ if (!allowedInputs.has(key)) {
254
+ issues.push(`Capability includes input ${input.name} outside execution ${execution.id}.`);
255
+ }
256
+ }
257
+ if (capability.inputs.length !== execution.inputs.length) {
258
+ issues.push(`Capability input count does not match execution ${execution.id}.`);
259
+ }
260
+ const nowMs = options.now ? Date.parse(options.now) : Date.now();
261
+ const expiresAtMs = Date.parse(capability.expiresAt);
262
+ if (!Number.isFinite(expiresAtMs)) {
263
+ issues.push(`Capability expiresAt must be an ISO timestamp for execution ${execution.id}.`);
264
+ }
265
+ else if (expiresAtMs <= nowMs) {
266
+ issues.push(`Capability is expired for execution ${execution.id}.`);
267
+ }
268
+ return issues;
269
+ }
270
+ function networkPolicyKey(policy) {
271
+ if (!policy || typeof policy !== "object") {
272
+ return "<invalid>";
273
+ }
274
+ return JSON.stringify({
275
+ egress: policy.egress,
276
+ ...(Array.isArray(policy.allow) ? { allow: [...policy.allow] } : {}),
277
+ });
278
+ }
279
+ export function collectSandboxAllocationScopeIssues(allocation, execution, options = {}) {
280
+ const issues = [];
281
+ if (allocation.executionId !== execution.id) {
282
+ issues.push(`Sandbox allocation execution id ${allocation.executionId} does not match ${execution.id}.`);
283
+ }
284
+ if (!allocation.sandboxId.trim()) {
285
+ issues.push(`Sandbox allocation for ${execution.id} must include a sandbox id.`);
286
+ }
287
+ if (!allocation.lifecycleId.trim()) {
288
+ issues.push(`Sandbox allocation for ${execution.id} must include a lifecycle id.`);
289
+ }
290
+ if (!allocation.backend.trim()) {
291
+ issues.push(`Sandbox allocation for ${execution.id} must include a backend.`);
292
+ }
293
+ if (!allocation.runnerId.trim()) {
294
+ issues.push(`Sandbox allocation for ${execution.id} must include a runner id.`);
295
+ }
296
+ if (allocation.template.kind !== execution.sandbox.kind || allocation.template.ref !== execution.sandbox.ref) {
297
+ issues.push(`Sandbox allocation template does not match execution ${execution.id}.`);
298
+ }
299
+ if (allocation.network.egress !== execution.policy.network.egress) {
300
+ issues.push(`Sandbox allocation network policy does not match execution ${execution.id}.`);
301
+ }
302
+ const expectedAllow = execution.policy.network.allow ?? [];
303
+ const actualAllow = allocation.network.allow ?? [];
304
+ if (JSON.stringify(actualAllow) !== JSON.stringify(expectedAllow)) {
305
+ issues.push(`Sandbox allocation network allowlist does not match execution ${execution.id}.`);
306
+ }
307
+ if (!["allocated", "running", "stopping", "stopped"].includes(allocation.status)) {
308
+ issues.push(`Sandbox allocation status ${allocation.status} is not supported for execution ${execution.id}.`);
309
+ }
310
+ const nowMs = options.now ? Date.parse(options.now) : Date.now();
311
+ const expiresAtMs = Date.parse(allocation.expiresAt);
312
+ if (!Number.isFinite(expiresAtMs)) {
313
+ issues.push(`Sandbox allocation expiresAt must be an ISO timestamp for execution ${execution.id}.`);
314
+ }
315
+ else if (expiresAtMs <= nowMs) {
316
+ issues.push(`Sandbox allocation is expired for execution ${execution.id}.`);
317
+ }
318
+ return issues;
319
+ }
320
+ function assertScopeIssues(label, issues) {
321
+ if (issues.length > 0) {
322
+ throw new Error(`${label} failed validation:\n${issues.join("\n")}`);
323
+ }
324
+ }
325
+ function isJsonRecord(value) {
326
+ return !!value && typeof value === "object" && !Array.isArray(value);
327
+ }
@@ -0,0 +1,8 @@
1
+ import type { SurfaceSnapshotFile, WorkbenchSubjectPatch } from "@workbench-ai/workbench-contract";
2
+ export interface ApplyWorkbenchSubjectPatchInput {
3
+ baseFiles: readonly SurfaceSnapshotFile[];
4
+ patch: WorkbenchSubjectPatch;
5
+ edits: readonly string[];
6
+ }
7
+ export declare function applyWorkbenchSubjectPatch(input: ApplyWorkbenchSubjectPatchInput): SurfaceSnapshotFile[];
8
+ //# sourceMappingURL=subject-patch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-patch.d.ts","sourceRoot":"","sources":["../src/subject-patch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC1C,KAAK,EAAE,qBAAqB,CAAC;IAC7B,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,+BAA+B,GAAG,mBAAmB,EAAE,CAgDxG"}
@@ -0,0 +1,63 @@
1
+ export function applyWorkbenchSubjectPatch(input) {
2
+ const issues = [];
3
+ const edits = input.edits.map(normalizeRelativePath).filter(Boolean);
4
+ const patchPaths = new Set();
5
+ for (const file of input.patch.files) {
6
+ const filePath = normalizeRelativePath(file.path);
7
+ if (!isSafeRelativePath(filePath)) {
8
+ issues.push(`Subject patch contains unsafe path ${file.path}.`);
9
+ }
10
+ if (!isAllowedEditPath(filePath, edits)) {
11
+ issues.push(`Subject patch contains path outside optimizer edits: ${file.path}.`);
12
+ }
13
+ patchPaths.add(filePath);
14
+ }
15
+ for (const fileChange of input.patch.fileChanges) {
16
+ const filePath = normalizeRelativePath(fileChange);
17
+ if (!isSafeRelativePath(filePath)) {
18
+ issues.push(`Subject patch fileChanges contains unsafe path ${fileChange}.`);
19
+ }
20
+ if (!isAllowedEditPath(filePath, edits)) {
21
+ issues.push(`Subject patch fileChanges contains path outside optimizer edits: ${fileChange}.`);
22
+ }
23
+ }
24
+ if (issues.length > 0) {
25
+ throw new Error(issues.join("\n"));
26
+ }
27
+ const patched = new Map();
28
+ for (const file of input.baseFiles) {
29
+ patched.set(normalizeRelativePath(file.path), {
30
+ ...file,
31
+ path: normalizeRelativePath(file.path),
32
+ });
33
+ }
34
+ for (const file of input.patch.files) {
35
+ patched.set(normalizeRelativePath(file.path), {
36
+ ...file,
37
+ path: normalizeRelativePath(file.path),
38
+ });
39
+ }
40
+ const baseOrder = input.baseFiles.map((file) => normalizeRelativePath(file.path));
41
+ const addedPaths = [...patchPaths].filter((filePath) => !baseOrder.includes(filePath)).sort();
42
+ return [...baseOrder, ...addedPaths]
43
+ .flatMap((filePath) => {
44
+ const file = patched.get(filePath);
45
+ return file ? [file] : [];
46
+ });
47
+ }
48
+ function isAllowedEditPath(filePath, edits) {
49
+ const normalizedPath = normalizeRelativePath(filePath);
50
+ return edits.some((entry) => {
51
+ const normalizedEditPath = normalizeRelativePath(entry).replace(/\/+$/u, "");
52
+ return normalizedPath === normalizedEditPath || normalizedPath.startsWith(`${normalizedEditPath}/`);
53
+ });
54
+ }
55
+ function isSafeRelativePath(filePath) {
56
+ const normalized = normalizeRelativePath(filePath);
57
+ return normalized.length > 0
58
+ && !normalized.startsWith("/")
59
+ && !normalized.split("/").includes("..");
60
+ }
61
+ function normalizeRelativePath(filePath) {
62
+ return filePath.replace(/\\/gu, "/").replace(/^\.\/+/u, "").replace(/\/+/gu, "/");
63
+ }
@@ -0,0 +1,18 @@
1
+ import type { SurfaceSnapshotFile, WorkbenchExecutionPurpose } from "@workbench-ai/workbench-contract";
2
+ export declare const WORKBENCH_TRACE_ROOT = ".workbench/traces";
3
+ export declare function readOutputTraceFiles(outputRoot: string, traceRoot: string): Promise<SurfaceSnapshotFile[]>;
4
+ export declare function traceFilePaths(files: readonly SurfaceSnapshotFile[]): string[];
5
+ export declare function workbenchTraceRunDirectory(args: {
6
+ sequence: number;
7
+ runId: string;
8
+ }): string;
9
+ export declare function workbenchTracePhaseDirectory(args: {
10
+ sequence: number;
11
+ runId: string;
12
+ phase: WorkbenchExecutionPurpose;
13
+ }): string;
14
+ export declare function workbenchTraceRunDirectoryName(args: {
15
+ sequence: number;
16
+ runId: string;
17
+ }): string;
18
+ //# sourceMappingURL=trace-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-files.d.ts","sourceRoot":"","sources":["../src/trace-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,kCAAkC,CAAC;AAO1C,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAiChH;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAK9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAET;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,yBAAyB,CAAC;CAClC,GAAG,MAAM,CAET;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAKT"}
@@ -0,0 +1,94 @@
1
+ import { importNodeModule, nodeBuiltin, } from "./runtime-utils.js";
2
+ export const WORKBENCH_TRACE_ROOT = ".workbench/traces";
3
+ export async function readOutputTraceFiles(outputRoot, traceRoot) {
4
+ const fs = await importNodeModule(nodeBuiltin("fs/promises"));
5
+ const path = await importNodeModule(nodeBuiltin("path"));
6
+ const decoder = new TextDecoder("utf-8", { fatal: true });
7
+ const files = [];
8
+ async function walk(directory) {
9
+ const entries = await fs.readdir(directory, { withFileTypes: true }).catch(() => []);
10
+ for (const entry of entries) {
11
+ const absolutePath = path.join(directory, entry.name);
12
+ if (entry.isDirectory()) {
13
+ const relativeDirectory = normalizeRelativePath(path.relative(outputRoot, absolutePath).replace(/\\/gu, "/"));
14
+ if (shouldSkipTraceDirectory(relativeDirectory)) {
15
+ continue;
16
+ }
17
+ await walk(absolutePath);
18
+ continue;
19
+ }
20
+ if (!entry.isFile()) {
21
+ continue;
22
+ }
23
+ const body = await fs.readFile(absolutePath);
24
+ const content = encodeTraceFileContent(body, decoder);
25
+ files.push({
26
+ path: normalizeRelativePath(`${traceRoot}/${path.relative(outputRoot, absolutePath).replace(/\\/gu, "/")}`),
27
+ kind: content.encoding === "base64" ? "binary" : "text",
28
+ encoding: content.encoding,
29
+ content: content.content,
30
+ executable: false,
31
+ });
32
+ }
33
+ }
34
+ await walk(outputRoot);
35
+ return files;
36
+ }
37
+ export function traceFilePaths(files) {
38
+ return files
39
+ .map((file) => file.path)
40
+ .filter((filePath) => filePath.startsWith(`${WORKBENCH_TRACE_ROOT}/`))
41
+ .sort();
42
+ }
43
+ export function workbenchTraceRunDirectory(args) {
44
+ return `${WORKBENCH_TRACE_ROOT}/${workbenchTraceRunDirectoryName(args)}`;
45
+ }
46
+ export function workbenchTracePhaseDirectory(args) {
47
+ return `${workbenchTraceRunDirectory(args)}/${String(tracePhaseSequence(args.phase)).padStart(6, "0")}-${args.phase}`;
48
+ }
49
+ export function workbenchTraceRunDirectoryName(args) {
50
+ const sequence = Number.isSafeInteger(args.sequence) && args.sequence >= 0
51
+ ? args.sequence
52
+ : 0;
53
+ return `${String(sequence).padStart(6, "0")}-${sanitizeTracePathSegment(args.runId)}`;
54
+ }
55
+ function shouldSkipTraceDirectory(relativeDirectory) {
56
+ return relativeDirectory === "session/home"
57
+ || relativeDirectory.startsWith("session/home/")
58
+ || relativeDirectory === "session/workspace"
59
+ || relativeDirectory.startsWith("session/workspace/")
60
+ || relativeDirectory.endsWith("/session/home")
61
+ || relativeDirectory.includes("/session/home/")
62
+ || relativeDirectory.endsWith("/session/workspace")
63
+ || relativeDirectory.includes("/session/workspace/");
64
+ }
65
+ function encodeTraceFileContent(body, utf8Decoder) {
66
+ try {
67
+ return {
68
+ encoding: "utf8",
69
+ content: utf8Decoder.decode(body),
70
+ };
71
+ }
72
+ catch {
73
+ return {
74
+ encoding: "base64",
75
+ content: body.toString("base64"),
76
+ };
77
+ }
78
+ }
79
+ function sanitizeTracePathSegment(value) {
80
+ const sanitized = value
81
+ .trim()
82
+ .replace(/[^a-z0-9_.-]+/giu, "_")
83
+ .replace(/^_+|_+$/gu, "");
84
+ return sanitized || "run";
85
+ }
86
+ function tracePhaseSequence(phase) {
87
+ if (phase === "improve") {
88
+ return 1;
89
+ }
90
+ return 2;
91
+ }
92
+ function normalizeRelativePath(filePath) {
93
+ return filePath.replace(/\\/gu, "/").replace(/^\/+/u, "").replace(/\/+/gu, "/");
94
+ }
@@ -0,0 +1,13 @@
1
+ FROM node:22-bookworm-slim AS node
2
+
3
+ FROM python:3.12-bookworm
4
+
5
+ COPY --from=node /usr/local/bin/node /usr/local/bin/node
6
+ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
7
+
8
+ RUN apt-get update \
9
+ && apt-get install -y --no-install-recommends ca-certificates fonts-dejavu git libreoffice \
10
+ && rm -rf /var/lib/apt/lists/* \
11
+ && ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
12
+ && ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \
13
+ && pip install --no-cache-dir openpyxl pandas
@@ -0,0 +1,11 @@
1
+ FROM node:22-bookworm-slim AS node
2
+
3
+ FROM python:3.12-bookworm
4
+
5
+ COPY --from=node /usr/local/bin/node /usr/local/bin/node
6
+ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
7
+
8
+ RUN apt-get update \
9
+ && apt-get install -y --no-install-recommends ca-certificates fonts-dejavu libreoffice \
10
+ && rm -rf /var/lib/apt/lists/* \
11
+ && pip install --no-cache-dir openpyxl pandas
@@ -0,0 +1,3 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ RUN npm install --global vitest@3.2.4
@@ -0,0 +1,8 @@
1
+ FROM node:22-bookworm-slim AS node
2
+
3
+ FROM python:3.12-bookworm
4
+
5
+ COPY --from=node /usr/local/bin/node /usr/local/bin/node
6
+ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
7
+
8
+ RUN pip install --no-cache-dir pytest
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@workbench-ai/workbench-core",
3
+ "version": "0.0.46",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/workbench-ai/workbench.git",
8
+ "directory": "packages/core"
9
+ },
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "environments",
21
+ "worker"
22
+ ],
23
+ "dependencies": {
24
+ "yaml": "^2.8.2",
25
+ "@workbench-ai/workbench-protocol": "0.0.46",
26
+ "@workbench-ai/workbench-contract": "0.0.46"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^24.3.1",
30
+ "typescript": "^5.9.2",
31
+ "vitest": "^3.2.4"
32
+ },
33
+ "publishConfig": {
34
+ "registry": "https://registry.npmjs.org/",
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "rm -rf dist environments && tsc -p tsconfig.json && cp -R ../../environments ./environments",
39
+ "lint": "tsc -p tsconfig.json --noEmit",
40
+ "test": "vitest run --config vitest.config.ts"
41
+ }
42
+ }