@purista/harness 1.0.0
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/LICENSE +201 -0
- package/README.md +23 -0
- package/dist/agents/index.d.ts +34 -0
- package/dist/agents/index.js +301 -0
- package/dist/errors/catalog.d.ts +185 -0
- package/dist/errors/catalog.js +144 -0
- package/dist/errors/harness-error.d.ts +64 -0
- package/dist/errors/harness-error.js +58 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/redaction.d.ts +5 -0
- package/dist/errors/redaction.js +64 -0
- package/dist/harness/defineHarness.d.ts +640 -0
- package/dist/harness/defineHarness.js +176 -0
- package/dist/harness/errors.d.ts +62 -0
- package/dist/harness/errors.js +67 -0
- package/dist/harness/types.d.ts +27 -0
- package/dist/harness/types.js +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +2 -0
- package/dist/logger/json-logger.d.ts +31 -0
- package/dist/logger/json-logger.js +65 -0
- package/dist/logger/logger.d.ts +31 -0
- package/dist/logger/logger.js +1 -0
- package/dist/models/json.d.ts +6 -0
- package/dist/models/json.js +1 -0
- package/dist/models/registry.d.ts +112 -0
- package/dist/models/registry.js +286 -0
- package/dist/models/state.d.ts +64 -0
- package/dist/models/state.js +1 -0
- package/dist/ports/base-model-provider.d.ts +56 -0
- package/dist/ports/base-model-provider.js +343 -0
- package/dist/ports/capabilities.d.ts +70 -0
- package/dist/ports/capabilities.js +38 -0
- package/dist/ports/feedback.d.ts +29 -0
- package/dist/ports/feedback.js +1 -0
- package/dist/ports/harness-context.d.ts +20 -0
- package/dist/ports/harness-context.js +1 -0
- package/dist/ports/index.d.ts +6 -0
- package/dist/ports/index.js +6 -0
- package/dist/ports/model-provider.d.ts +280 -0
- package/dist/ports/model-provider.js +1 -0
- package/dist/ports/state.d.ts +72 -0
- package/dist/ports/state.js +24 -0
- package/dist/runtime/durable.d.ts +134 -0
- package/dist/runtime/durable.js +185 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/steps.d.ts +22 -0
- package/dist/runtime/steps.js +51 -0
- package/dist/sandbox/index.d.ts +111 -0
- package/dist/sandbox/index.js +165 -0
- package/dist/sessions/index.d.ts +23 -0
- package/dist/sessions/index.js +718 -0
- package/dist/skills/index.d.ts +8 -0
- package/dist/skills/index.js +88 -0
- package/dist/state/in-memory.d.ts +35 -0
- package/dist/state/in-memory.js +140 -0
- package/dist/telemetry/index.d.ts +1 -0
- package/dist/telemetry/index.js +1 -0
- package/dist/telemetry/shim.d.ts +26 -0
- package/dist/telemetry/shim.js +120 -0
- package/dist/testing/capabilities.d.ts +11 -0
- package/dist/testing/capabilities.js +20 -0
- package/dist/testing/fakeModelProvider.d.ts +25 -0
- package/dist/testing/fakeModelProvider.js +79 -0
- package/dist/testing/feedback.d.ts +10 -0
- package/dist/testing/feedback.js +24 -0
- package/dist/testing/fixtures/mcp/fake-http-server.d.ts +8 -0
- package/dist/testing/fixtures/mcp/fake-http-server.js +95 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/sandboxContract.d.ts +4 -0
- package/dist/testing/sandboxContract.js +74 -0
- package/dist/testing/sandboxSnapshot.d.ts +7 -0
- package/dist/testing/sandboxSnapshot.js +201 -0
- package/dist/testing/stateStoreContract.d.ts +2 -0
- package/dist/testing/stateStoreContract.js +109 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +123 -0
- package/dist/tools/mcp/http.d.ts +2 -0
- package/dist/tools/mcp/http.js +109 -0
- package/dist/tools/mcp/index.d.ts +2 -0
- package/dist/tools/mcp/index.js +2 -0
- package/dist/tools/mcp/runner.d.ts +74 -0
- package/dist/tools/mcp/runner.js +238 -0
- package/dist/tools/mcp/schema.d.ts +41 -0
- package/dist/tools/mcp/schema.js +251 -0
- package/dist/tools/mcp/stdio.d.ts +2 -0
- package/dist/tools/mcp/stdio.js +122 -0
- package/dist/ulid/index.d.ts +6 -0
- package/dist/ulid/index.js +35 -0
- package/dist/workflows/index.d.ts +8 -0
- package/dist/workflows/index.js +26 -0
- package/package.json +75 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { JsonValue } from '../models/json.js';
|
|
2
|
+
import type { DurableRunLease, DurableRuntime } from './durable.js';
|
|
3
|
+
/** Durable workflow context that exposes explicit checkpoint boundaries. */
|
|
4
|
+
export interface DurableWorkflowContext {
|
|
5
|
+
/** Current durable run lease. */
|
|
6
|
+
readonly lease: DurableRunLease;
|
|
7
|
+
/**
|
|
8
|
+
* Runs a JSON-serializable durable step and commits its output as a checkpoint.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const prepared = await ctx.step('prepare-inputs', async () => ({ ok: true }))
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
step<T extends JsonValue>(stepId: string, fn: () => Promise<T>): Promise<T>;
|
|
16
|
+
}
|
|
17
|
+
/** Error thrown when a durable step id is invalid or duplicated. */
|
|
18
|
+
export declare class DurableStepError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/** Creates a durable workflow context bound to an acquired runtime lease. */
|
|
22
|
+
export declare function createDurableWorkflowContext(runtime: DurableRuntime, lease: DurableRunLease): DurableWorkflowContext;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const STEP_ID_PATTERN = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
2
|
+
/** Error thrown when a durable step id is invalid or duplicated. */
|
|
3
|
+
export class DurableStepError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'DurableStepError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** Creates a durable workflow context bound to an acquired runtime lease. */
|
|
10
|
+
export function createDurableWorkflowContext(runtime, lease) {
|
|
11
|
+
const completed = new Set();
|
|
12
|
+
return {
|
|
13
|
+
lease,
|
|
14
|
+
async step(stepId, fn) {
|
|
15
|
+
validateStepId(stepId);
|
|
16
|
+
if (completed.has(stepId)) {
|
|
17
|
+
throw new DurableStepError(`Duplicate durable step id "${stepId}".`);
|
|
18
|
+
}
|
|
19
|
+
completed.add(stepId);
|
|
20
|
+
const output = await fn();
|
|
21
|
+
assertJsonSerializable(output, stepId);
|
|
22
|
+
const sequence = (lease.checkpoint?.sequence ?? 0) + completed.size;
|
|
23
|
+
const checkpoint = {
|
|
24
|
+
runId: lease.runId,
|
|
25
|
+
sessionId: lease.sessionId,
|
|
26
|
+
leaseId: lease.leaseId,
|
|
27
|
+
workerId: lease.workerId,
|
|
28
|
+
stepId,
|
|
29
|
+
input: lease.start.input,
|
|
30
|
+
attempt: lease.attempt,
|
|
31
|
+
sequence,
|
|
32
|
+
output
|
|
33
|
+
};
|
|
34
|
+
await runtime.commitCheckpoint(checkpoint);
|
|
35
|
+
return output;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function validateStepId(stepId) {
|
|
40
|
+
if (!STEP_ID_PATTERN.test(stepId)) {
|
|
41
|
+
throw new DurableStepError(`Invalid durable step id "${stepId}".`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function assertJsonSerializable(value, stepId) {
|
|
45
|
+
try {
|
|
46
|
+
JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new DurableStepError(`Durable step "${stepId}" returned a non-serializable value.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { DirEntry, ExecOptions, ExecResult, FileStat } from '../harness/types.js';
|
|
2
|
+
import type { HarnessAdapterContext } from '../ports/harness-context.js';
|
|
3
|
+
import type { AdapterCapabilities, AdapterCapability } from '../ports/capabilities.js';
|
|
4
|
+
export interface SandboxSessionBase {
|
|
5
|
+
read(path: string): Promise<Uint8Array>;
|
|
6
|
+
readText(path: string, encoding?: 'utf-8'): Promise<string>;
|
|
7
|
+
write(path: string, data: Uint8Array | string): Promise<void>;
|
|
8
|
+
remove(path: string, opts?: {
|
|
9
|
+
recursive?: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
list(path: string, opts?: {
|
|
12
|
+
recursive?: boolean;
|
|
13
|
+
glob?: string;
|
|
14
|
+
}): Promise<DirEntry[]>;
|
|
15
|
+
stat(path: string): Promise<FileStat>;
|
|
16
|
+
exists(path: string): Promise<boolean>;
|
|
17
|
+
mount(files: ReadonlyMap<string, Uint8Array | string>, atPath: string): Promise<void>;
|
|
18
|
+
readonly executor: 'available' | 'unavailable';
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export interface ExecCapableSandboxSession extends SandboxSessionBase {
|
|
22
|
+
readonly executor: 'available';
|
|
23
|
+
exec(command: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
24
|
+
}
|
|
25
|
+
export type SandboxSession = SandboxSessionBase & {
|
|
26
|
+
exec(command: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
27
|
+
};
|
|
28
|
+
type HasSandboxCapability<C extends readonly AdapterCapability[], K extends AdapterCapability> = K extends C[number] ? true : false;
|
|
29
|
+
export type SandboxSessionFor<C extends readonly AdapterCapability[]> = HasSandboxCapability<C, 'sandbox.exec'> extends true ? ExecCapableSandboxSession : SandboxSessionBase & {
|
|
30
|
+
readonly executor: 'unavailable';
|
|
31
|
+
};
|
|
32
|
+
export interface Sandbox<C extends readonly AdapterCapability[] = readonly AdapterCapability[]> extends Partial<AdapterCapabilities> {
|
|
33
|
+
readonly capabilities?: C;
|
|
34
|
+
configureHarnessContext?(context: HarnessAdapterContext): void;
|
|
35
|
+
open(opts: {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
runId: string;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
}): Promise<SandboxSessionFor<C>>;
|
|
40
|
+
}
|
|
41
|
+
/** Result produced when a sandbox adapter records a restorable checkpoint. */
|
|
42
|
+
export interface SnapshotResult {
|
|
43
|
+
/** Adapter-owned id used to resume the checkpoint later. */
|
|
44
|
+
readonly snapshotId: string;
|
|
45
|
+
/** Optional adapter metadata for observability or persistence records. */
|
|
46
|
+
readonly metadata?: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
/** Options used to open a sandbox session from a prior snapshot. */
|
|
49
|
+
export interface SandboxResumeOptions {
|
|
50
|
+
/** Snapshot id previously returned by `snapshot(...)` or `hibernate(...)`. */
|
|
51
|
+
readonly snapshotId: string;
|
|
52
|
+
/** Logical harness session id for the resumed sandbox session. */
|
|
53
|
+
readonly sessionId: string;
|
|
54
|
+
/** Harness run id requesting the resumed sandbox session. */
|
|
55
|
+
readonly runId: string;
|
|
56
|
+
/** Optional cancellation signal for adapters that support abortable resume. */
|
|
57
|
+
readonly signal?: AbortSignal;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Optional sandbox capability for creating durable session snapshots.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* if ('snapshot' in sandbox) {
|
|
65
|
+
* const result = await sandbox.snapshot(session)
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export interface SnapshotCapableSandbox {
|
|
70
|
+
snapshot(session: SandboxSession): Promise<SnapshotResult>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Optional sandbox capability for opening sessions from durable snapshots.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* if ('resume' in sandbox) {
|
|
78
|
+
* const session = await sandbox.resume({ snapshotId, sessionId, runId })
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export interface ResumeCapableSandbox {
|
|
83
|
+
resume(opts: SandboxResumeOptions): Promise<SandboxSession>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Optional sandbox capability for snapshotting and releasing active compute.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* if ('hibernate' in sandbox) {
|
|
91
|
+
* const result = await sandbox.hibernate(session)
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export interface HibernateCapableSandbox {
|
|
96
|
+
hibernate(session: SandboxSession): Promise<SnapshotResult>;
|
|
97
|
+
}
|
|
98
|
+
export declare function inMemorySandbox(): Sandbox<readonly ['sandbox.fs']>;
|
|
99
|
+
export declare function bashSandbox(opts?: {
|
|
100
|
+
network?: {
|
|
101
|
+
allow?: string[];
|
|
102
|
+
deny?: string[];
|
|
103
|
+
};
|
|
104
|
+
executionLimits?: {
|
|
105
|
+
wallClockMs?: number;
|
|
106
|
+
memoryMb?: number;
|
|
107
|
+
};
|
|
108
|
+
python?: boolean;
|
|
109
|
+
}): Sandbox<readonly ['sandbox.fs', 'sandbox.exec']>;
|
|
110
|
+
export declare function autoDetectSandbox(): Sandbox<any>;
|
|
111
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { OperationCancelledError, OperationTimeoutError, HarnessConfigError, SandboxError, SandboxNoExecutorError } from '../errors/index.js';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
function now() { return new Date().toISOString(); }
|
|
6
|
+
function normalizePath(input) {
|
|
7
|
+
if (!input.startsWith('/'))
|
|
8
|
+
throw new SandboxError('Invalid path', { reason: 'invalid_path' });
|
|
9
|
+
const normalized = path.posix.normalize(input);
|
|
10
|
+
if (!normalized.startsWith('/'))
|
|
11
|
+
throw new SandboxError('Invalid path', { reason: 'invalid_path' });
|
|
12
|
+
return normalized;
|
|
13
|
+
}
|
|
14
|
+
class MemorySandboxSession {
|
|
15
|
+
fs = new Map();
|
|
16
|
+
executor;
|
|
17
|
+
bashExec;
|
|
18
|
+
constructor(executor, bashExec) {
|
|
19
|
+
this.executor = executor;
|
|
20
|
+
this.bashExec = bashExec;
|
|
21
|
+
this.fs.set('/', { kind: 'directory', modifiedAt: now() });
|
|
22
|
+
}
|
|
23
|
+
ensureParent(filePath) {
|
|
24
|
+
const parts = normalizePath(filePath).split('/').filter(Boolean);
|
|
25
|
+
let current = '/';
|
|
26
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
27
|
+
current = current === '/' ? `/${parts[i]}` : `${current}/${parts[i]}`;
|
|
28
|
+
if (!this.fs.has(current))
|
|
29
|
+
this.fs.set(current, { kind: 'directory', modifiedAt: now() });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async read(filePath) {
|
|
33
|
+
const node = this.fs.get(normalizePath(filePath));
|
|
34
|
+
if (!node || node.kind !== 'file')
|
|
35
|
+
throw new SandboxError('File not found', { reason: 'fs_failed' });
|
|
36
|
+
return node.data;
|
|
37
|
+
}
|
|
38
|
+
async readText(filePath) { return new TextDecoder().decode(await this.read(filePath)); }
|
|
39
|
+
async write(filePath, data) {
|
|
40
|
+
const p = normalizePath(filePath);
|
|
41
|
+
this.ensureParent(p);
|
|
42
|
+
this.fs.set(p, { kind: 'file', data: typeof data === 'string' ? new TextEncoder().encode(data) : data, modifiedAt: now() });
|
|
43
|
+
}
|
|
44
|
+
async remove(filePath, opts) {
|
|
45
|
+
const p = normalizePath(filePath);
|
|
46
|
+
if (opts?.recursive) {
|
|
47
|
+
for (const key of [...this.fs.keys()]) {
|
|
48
|
+
if (key === p || key.startsWith(`${p}/`))
|
|
49
|
+
this.fs.delete(key);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.fs.delete(p);
|
|
54
|
+
}
|
|
55
|
+
async list(rootPath, opts) {
|
|
56
|
+
const root = normalizePath(rootPath);
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const [k, v] of this.fs.entries()) {
|
|
59
|
+
if (k === root)
|
|
60
|
+
continue;
|
|
61
|
+
if (!k.startsWith(root === '/' ? '/' : `${root}/`))
|
|
62
|
+
continue;
|
|
63
|
+
const relative = root === '/' ? k.slice(1) : k.slice(root.length + 1);
|
|
64
|
+
if (!opts?.recursive && relative.includes('/'))
|
|
65
|
+
continue;
|
|
66
|
+
if (opts?.glob && !new RegExp(opts.glob.replaceAll('.', '\\.').replaceAll('*', '.*')).test(k))
|
|
67
|
+
continue;
|
|
68
|
+
out.push({ name: k.split('/').at(-1) ?? '', path: k, kind: v.kind, ...(v.kind === 'file' ? { size: v.data.byteLength } : {}) });
|
|
69
|
+
}
|
|
70
|
+
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
71
|
+
}
|
|
72
|
+
async stat(filePath) {
|
|
73
|
+
const node = this.fs.get(normalizePath(filePath));
|
|
74
|
+
if (!node)
|
|
75
|
+
throw new SandboxError('Path not found', { reason: 'fs_failed' });
|
|
76
|
+
return { kind: node.kind, size: node.kind === 'file' ? node.data.byteLength : 0, modifiedAt: node.modifiedAt };
|
|
77
|
+
}
|
|
78
|
+
async exists(filePath) { return this.fs.has(normalizePath(filePath)); }
|
|
79
|
+
async mount(files, atPath) {
|
|
80
|
+
const base = normalizePath(atPath);
|
|
81
|
+
for (const [rel, data] of files.entries()) {
|
|
82
|
+
const relNorm = rel.startsWith('/') ? rel.slice(1) : rel;
|
|
83
|
+
await this.write(`${base}/${relNorm}`, data);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async exec(command, opts) {
|
|
87
|
+
if (this.executor === 'unavailable' || !this.bashExec)
|
|
88
|
+
throw new SandboxNoExecutorError('Sandbox executor unavailable.', { session_id: 'unknown' });
|
|
89
|
+
return this.bashExec(command, opts);
|
|
90
|
+
}
|
|
91
|
+
async close() { }
|
|
92
|
+
}
|
|
93
|
+
export function inMemorySandbox() {
|
|
94
|
+
return {
|
|
95
|
+
capabilities: ['sandbox.fs'],
|
|
96
|
+
async open() {
|
|
97
|
+
return new MemorySandboxSession('unavailable');
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function bashSandbox(opts) {
|
|
102
|
+
let justBash;
|
|
103
|
+
try {
|
|
104
|
+
justBash = require('just-bash');
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
throw new HarnessConfigError('just-bash is not installed', { reason: 'just_bash_not_installed' });
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
capabilities: ['sandbox.fs', 'sandbox.exec'],
|
|
111
|
+
async open() {
|
|
112
|
+
const engine = justBash.createSandbox ? await justBash.createSandbox(opts) : new justBash.Bash({ ...opts, cwd: '/workspace' });
|
|
113
|
+
const exec = async (command, execOpts) => {
|
|
114
|
+
const started = Date.now();
|
|
115
|
+
if (execOpts?.signal?.aborted)
|
|
116
|
+
throw new OperationCancelledError('Sandbox run was cancelled.', { scope: 'sandbox' });
|
|
117
|
+
if (!engine.exec)
|
|
118
|
+
throw new HarnessConfigError('just-bash exec is unavailable', { reason: 'just_bash_exec_unavailable' });
|
|
119
|
+
const timeoutMs = execOpts?.timeoutMs;
|
|
120
|
+
const controller = timeoutMs && timeoutMs > 0 ? new AbortController() : undefined;
|
|
121
|
+
const signal = controller?.signal ?? execOpts?.signal;
|
|
122
|
+
const sourceSignal = execOpts?.signal;
|
|
123
|
+
let timeoutId;
|
|
124
|
+
let abortListener;
|
|
125
|
+
const abortPromise = sourceSignal?.addEventListener
|
|
126
|
+
? new Promise((_, reject) => {
|
|
127
|
+
abortListener = () => {
|
|
128
|
+
controller?.abort();
|
|
129
|
+
reject(new OperationCancelledError('Sandbox run was cancelled.', { scope: 'sandbox' }));
|
|
130
|
+
};
|
|
131
|
+
sourceSignal.addEventListener('abort', abortListener, { once: true });
|
|
132
|
+
})
|
|
133
|
+
: undefined;
|
|
134
|
+
const timeoutPromise = timeoutMs && timeoutMs > 0
|
|
135
|
+
? new Promise((_, reject) => {
|
|
136
|
+
timeoutId = setTimeout(() => {
|
|
137
|
+
controller?.abort();
|
|
138
|
+
reject(new OperationTimeoutError('Sandbox run timed out.', { scope: 'sandbox_run', timeout_ms: timeoutMs }));
|
|
139
|
+
}, timeoutMs);
|
|
140
|
+
})
|
|
141
|
+
: undefined;
|
|
142
|
+
try {
|
|
143
|
+
const runner = engine.exec(command, { cwd: execOpts?.cwd, env: execOpts?.env, stdin: execOpts?.stdin, signal });
|
|
144
|
+
const result = await Promise.race([runner, abortPromise, timeoutPromise].filter(Boolean));
|
|
145
|
+
return { stdout: result.stdout ?? '', stderr: result.stderr ?? '', exitCode: result.exitCode ?? 0, durationSeconds: (Date.now() - started) / 1000 };
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
if (timeoutId)
|
|
149
|
+
clearTimeout(timeoutId);
|
|
150
|
+
if (abortListener)
|
|
151
|
+
sourceSignal?.removeEventListener?.('abort', abortListener);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return new MemorySandboxSession('available', exec);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export function autoDetectSandbox() {
|
|
159
|
+
try {
|
|
160
|
+
return bashSandbox();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return inMemorySandbox();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Logger } from '../logger/index.js';
|
|
2
|
+
import type { Harness, HarnessDefaults, BuilderState, TelemetryOptions } from '../harness/defineHarness.js';
|
|
3
|
+
import type { HarnessInspection } from '../ports/capabilities.js';
|
|
4
|
+
import type { Sandbox } from '../sandbox/index.js';
|
|
5
|
+
import type { StateStore } from '../ports/state.js';
|
|
6
|
+
import { type TelemetryShim } from '../telemetry/index.js';
|
|
7
|
+
type HarnessDefinition<S extends BuilderState> = {
|
|
8
|
+
name: string;
|
|
9
|
+
logger: Logger;
|
|
10
|
+
telemetry?: TelemetryOptions;
|
|
11
|
+
telemetryShim?: TelemetryShim;
|
|
12
|
+
state: StateStore;
|
|
13
|
+
sandbox: Sandbox;
|
|
14
|
+
defaults: HarnessDefaults;
|
|
15
|
+
models: NonNullable<S['models']>;
|
|
16
|
+
tools: NonNullable<S['tools']>;
|
|
17
|
+
skills: NonNullable<S['skills']>;
|
|
18
|
+
agents: NonNullable<S['agents']>;
|
|
19
|
+
workflows: NonNullable<S['workflows']>;
|
|
20
|
+
inspection: HarnessInspection;
|
|
21
|
+
};
|
|
22
|
+
export declare function createSessionHarness<S extends BuilderState>(definition: HarnessDefinition<S>): Harness<S>;
|
|
23
|
+
export {};
|