@workser/runtime 0.0.0-stage → 0.1.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 +21 -0
- package/README.md +71 -2
- package/dist/cjs/client/follow.js +103 -0
- package/dist/cjs/client/index.js +6 -0
- package/dist/cjs/contract/actions.js +223 -0
- package/dist/cjs/contract/catalog.js +335 -0
- package/dist/cjs/contract/index.js +20 -0
- package/dist/cjs/contract/ordered-feed.js +94 -0
- package/dist/cjs/contract/types.js +9 -0
- package/dist/cjs/engine/engine.js +2 -0
- package/dist/cjs/index.js +27 -0
- package/dist/cjs/internal/async-queue.js +56 -0
- package/dist/cjs/internal/sse.js +170 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/place/place.js +2 -0
- package/dist/cjs/runner/index.js +6 -0
- package/dist/cjs/runner/journal.js +82 -0
- package/dist/cjs/runner/runner-client.js +477 -0
- package/dist/client/follow.d.ts +45 -0
- package/dist/client/follow.js +100 -0
- package/dist/client/follow.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -0
- package/dist/contract/actions.d.ts +73 -0
- package/dist/contract/actions.js +217 -0
- package/dist/contract/actions.js.map +1 -0
- package/dist/contract/catalog.d.ts +97 -0
- package/dist/contract/catalog.js +328 -0
- package/dist/contract/catalog.js.map +1 -0
- package/dist/contract/index.d.ts +4 -0
- package/dist/contract/index.js +5 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/ordered-feed.d.ts +56 -0
- package/dist/contract/ordered-feed.js +91 -0
- package/dist/contract/ordered-feed.js.map +1 -0
- package/dist/contract/types.d.ts +311 -0
- package/dist/contract/types.js +9 -0
- package/dist/contract/types.js.map +1 -0
- package/dist/engine/engine.d.ts +39 -0
- package/dist/engine/engine.js +2 -0
- package/dist/engine/engine.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/async-queue.d.ts +14 -0
- package/dist/internal/async-queue.js +53 -0
- package/dist/internal/async-queue.js.map +1 -0
- package/dist/internal/sse.d.ts +57 -0
- package/dist/internal/sse.js +164 -0
- package/dist/internal/sse.js.map +1 -0
- package/dist/place/place.d.ts +81 -0
- package/dist/place/place.js +2 -0
- package/dist/place/place.js.map +1 -0
- package/dist/runner/index.d.ts +2 -0
- package/dist/runner/index.js +2 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/journal.d.ts +32 -0
- package/dist/runner/journal.js +79 -0
- package/dist/runner/journal.js.map +1 -0
- package/dist/runner/runner-client.d.ts +185 -0
- package/dist/runner/runner-client.js +473 -0
- package/dist/runner/runner-client.js.map +1 -0
- package/package.json +30 -4
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Sent Events over `fetch`, for Node 18+ and browsers alike.
|
|
3
|
+
*
|
|
4
|
+
* Not `EventSource`: it can't send an `Authorization` header (a runner's token
|
|
5
|
+
* has to go somewhere), it doesn't exist in Node, and its reconnect can't be
|
|
6
|
+
* told to resume from a cursor we choose. This follows the HTML spec's parsing
|
|
7
|
+
* rules (line endings, `data` joining, comments, `id`, `retry`).
|
|
8
|
+
*/
|
|
9
|
+
/** Incremental parser: feed it text chunks, get whole messages back. */
|
|
10
|
+
export class SseParser {
|
|
11
|
+
buffer = '';
|
|
12
|
+
data = [];
|
|
13
|
+
event = '';
|
|
14
|
+
lastId = '';
|
|
15
|
+
retry;
|
|
16
|
+
sawCr = false;
|
|
17
|
+
/** Parse one chunk. A message split across chunks is held until complete. */
|
|
18
|
+
push(chunk) {
|
|
19
|
+
const out = [];
|
|
20
|
+
let text = chunk;
|
|
21
|
+
// A `\r\n` split across two chunks: the `\n` belongs to the `\r` already
|
|
22
|
+
// treated as a line end.
|
|
23
|
+
if (this.sawCr && text.startsWith('\n'))
|
|
24
|
+
text = text.slice(1);
|
|
25
|
+
this.sawCr = false;
|
|
26
|
+
this.buffer += text;
|
|
27
|
+
for (;;) {
|
|
28
|
+
const match = /\r\n|\r|\n/.exec(this.buffer);
|
|
29
|
+
if (!match)
|
|
30
|
+
break;
|
|
31
|
+
const line = this.buffer.slice(0, match.index);
|
|
32
|
+
const endsWithLoneCr = match[0] === '\r' && match.index + 1 === this.buffer.length;
|
|
33
|
+
this.buffer = this.buffer.slice(match.index + match[0].length);
|
|
34
|
+
if (endsWithLoneCr)
|
|
35
|
+
this.sawCr = true;
|
|
36
|
+
const message = this.line(line);
|
|
37
|
+
if (message)
|
|
38
|
+
out.push(message);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
line(line) {
|
|
43
|
+
if (line === '')
|
|
44
|
+
return this.dispatch();
|
|
45
|
+
if (line.startsWith(':'))
|
|
46
|
+
return null; // a comment (servers use them as pings)
|
|
47
|
+
const colon = line.indexOf(':');
|
|
48
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
49
|
+
let value = colon === -1 ? '' : line.slice(colon + 1);
|
|
50
|
+
if (value.startsWith(' '))
|
|
51
|
+
value = value.slice(1);
|
|
52
|
+
switch (field) {
|
|
53
|
+
case 'data':
|
|
54
|
+
this.data.push(value);
|
|
55
|
+
break;
|
|
56
|
+
case 'event':
|
|
57
|
+
this.event = value;
|
|
58
|
+
break;
|
|
59
|
+
case 'id':
|
|
60
|
+
if (!value.includes('\u0000'))
|
|
61
|
+
this.lastId = value;
|
|
62
|
+
break;
|
|
63
|
+
case 'retry':
|
|
64
|
+
if (/^\d+$/.test(value))
|
|
65
|
+
this.retry = Number(value);
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
break; // unknown fields are ignored
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
dispatch() {
|
|
73
|
+
const retry = this.retry;
|
|
74
|
+
this.retry = undefined;
|
|
75
|
+
if (this.data.length === 0) {
|
|
76
|
+
this.event = '';
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const message = {
|
|
80
|
+
event: this.event || 'message',
|
|
81
|
+
data: this.data.join('\n'),
|
|
82
|
+
id: this.lastId,
|
|
83
|
+
};
|
|
84
|
+
if (retry !== undefined)
|
|
85
|
+
message.retry = retry;
|
|
86
|
+
this.data = [];
|
|
87
|
+
this.event = '';
|
|
88
|
+
return message;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Keep an SSE connection open until `signal` aborts: connect, parse, and on
|
|
93
|
+
* any drop reconnect with backoff. The caller's `url()` decides where to
|
|
94
|
+
* resume from, so reconnects continue rather than replay.
|
|
95
|
+
*/
|
|
96
|
+
export async function connectSse(options) {
|
|
97
|
+
const base = options.baseDelayMs ?? 500;
|
|
98
|
+
const max = options.maxDelayMs ?? 15_000;
|
|
99
|
+
const fatal = new Set(options.fatalStatuses ?? [401, 403, 404]);
|
|
100
|
+
let delay = base;
|
|
101
|
+
let serverRetry;
|
|
102
|
+
while (!options.signal.aborted) {
|
|
103
|
+
let failure = null;
|
|
104
|
+
try {
|
|
105
|
+
const response = await options.fetch(options.url(), {
|
|
106
|
+
headers: { Accept: 'text/event-stream', ...options.headers() },
|
|
107
|
+
signal: options.signal,
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok || !response.body) {
|
|
110
|
+
const error = new SseHttpError(response.status);
|
|
111
|
+
if (fatal.has(response.status)) {
|
|
112
|
+
options.onError?.(error);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
delay = base;
|
|
118
|
+
const parser = new SseParser();
|
|
119
|
+
const decoder = new TextDecoder();
|
|
120
|
+
const reader = response.body.getReader();
|
|
121
|
+
for (;;) {
|
|
122
|
+
const { value, done } = await reader.read();
|
|
123
|
+
if (done)
|
|
124
|
+
break;
|
|
125
|
+
for (const message of parser.push(decoder.decode(value, { stream: true }))) {
|
|
126
|
+
if (message.retry !== undefined)
|
|
127
|
+
serverRetry = message.retry;
|
|
128
|
+
options.onMessage(message);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
failure = error;
|
|
134
|
+
}
|
|
135
|
+
if (options.signal.aborted)
|
|
136
|
+
return;
|
|
137
|
+
if (failure && options.onError?.(failure) === false)
|
|
138
|
+
return;
|
|
139
|
+
await sleep(serverRetry ?? delay, options.signal);
|
|
140
|
+
delay = Math.min(delay * 2, max);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export class SseHttpError extends Error {
|
|
144
|
+
status;
|
|
145
|
+
constructor(status) {
|
|
146
|
+
super(`The stream answered ${status}.`);
|
|
147
|
+
this.status = status;
|
|
148
|
+
this.name = 'SseHttpError';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export function sleep(ms, signal) {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
if (signal?.aborted)
|
|
154
|
+
return resolve();
|
|
155
|
+
const timer = setTimeout(done, ms);
|
|
156
|
+
function done() {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
signal?.removeEventListener('abort', done);
|
|
159
|
+
resolve();
|
|
160
|
+
}
|
|
161
|
+
signal?.addEventListener('abort', done, { once: true });
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/internal/sse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { RunPlace } from '../contract/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where a run's work happens: the owner's desktop, an E2B sandbox, the
|
|
4
|
+
* project's cloud computer. An engine never touches a machine directly — it
|
|
5
|
+
* asks its Place, so the same engine loop can run on the machine itself or in
|
|
6
|
+
* our service while the machine only does the work.
|
|
7
|
+
*/
|
|
8
|
+
export interface Place {
|
|
9
|
+
/** What this place is and what it can do. */
|
|
10
|
+
info(): Promise<PlaceInfo>;
|
|
11
|
+
/** Run a command. Output streams through `onOutput`; the promise ends with the exit. */
|
|
12
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
13
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
14
|
+
/** Creates parent folders as needed. */
|
|
15
|
+
writeFile(path: string, content: Uint8Array | string): Promise<void>;
|
|
16
|
+
/** Entries directly inside `path` (not recursive). */
|
|
17
|
+
list(path: string): Promise<PlaceEntry[]>;
|
|
18
|
+
/** `git status` of the working folder, parsed. */
|
|
19
|
+
gitStatus(cwd?: string): Promise<GitStatus>;
|
|
20
|
+
/** The diff against `base` (default: the run's starting point). */
|
|
21
|
+
gitDiff(options?: {
|
|
22
|
+
cwd?: string;
|
|
23
|
+
base?: string;
|
|
24
|
+
paths?: string[];
|
|
25
|
+
}): Promise<string>;
|
|
26
|
+
/** A URL the owner can open for a port a dev server listens on. */
|
|
27
|
+
previewUrl(port: number): Promise<string>;
|
|
28
|
+
/** Stop paying for the machine while keeping its disk. */
|
|
29
|
+
pause(): Promise<void>;
|
|
30
|
+
resume(): Promise<void>;
|
|
31
|
+
/** Save the machine's state so a later run can start from it. */
|
|
32
|
+
snapshot(label?: string): Promise<{
|
|
33
|
+
snapshot_id: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface PlaceInfo {
|
|
37
|
+
place: RunPlace;
|
|
38
|
+
/** The folder the run works in. */
|
|
39
|
+
root: string;
|
|
40
|
+
os: 'linux' | 'darwin' | 'windows';
|
|
41
|
+
/** e.g. ['git', 'node', 'python', 'ffmpeg', 'browser'] */
|
|
42
|
+
tools: string[];
|
|
43
|
+
canPause: boolean;
|
|
44
|
+
canSnapshot: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface ExecOptions {
|
|
47
|
+
cwd?: string;
|
|
48
|
+
/** Added to the place's own environment. Never a provider key. */
|
|
49
|
+
env?: Record<string, string>;
|
|
50
|
+
/** Kill the command after this long. */
|
|
51
|
+
timeoutMs?: number;
|
|
52
|
+
/** Text written to the command's stdin, then closed. */
|
|
53
|
+
stdin?: string;
|
|
54
|
+
onOutput?: (chunk: {
|
|
55
|
+
stream: 'stdout' | 'stderr';
|
|
56
|
+
text: string;
|
|
57
|
+
}) => void;
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}
|
|
60
|
+
export interface ExecResult {
|
|
61
|
+
exitCode: number;
|
|
62
|
+
/** Kept to the last ~64 KB of each; the full output went through `onOutput`. */
|
|
63
|
+
stdout: string;
|
|
64
|
+
stderr: string;
|
|
65
|
+
timedOut: boolean;
|
|
66
|
+
durationMs: number;
|
|
67
|
+
}
|
|
68
|
+
export interface PlaceEntry {
|
|
69
|
+
name: string;
|
|
70
|
+
path: string;
|
|
71
|
+
type: 'file' | 'dir' | 'link';
|
|
72
|
+
size?: number;
|
|
73
|
+
}
|
|
74
|
+
export interface GitStatus {
|
|
75
|
+
branch: string | null;
|
|
76
|
+
clean: boolean;
|
|
77
|
+
files: Array<{
|
|
78
|
+
path: string;
|
|
79
|
+
change: 'added' | 'modified' | 'deleted' | 'renamed' | 'untracked';
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"place.js","sourceRoot":"","sources":["../../src/place/place.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAA6C,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runner's disk journal: events emitted but not yet accepted by core-api,
|
|
3
|
+
* so a runner killed outright (SIGKILL, a laptop lid, a sandbox pulled) loses
|
|
4
|
+
* nothing. Node only — loaded lazily by `RunnerClient` when `journalPath` is
|
|
5
|
+
* set, so the SDK still runs in browsers.
|
|
6
|
+
*
|
|
7
|
+
* Format: one JSON event per line, exactly as it will be sent (same
|
|
8
|
+
* `event_id` and `seq`). A resend after a restart is therefore a retry to
|
|
9
|
+
* core-api, which accepts an identical event as a duplicate and stores it
|
|
10
|
+
* once. The file is appended on every emit, fsync'd before each send, and
|
|
11
|
+
* rewritten (temp file + rename, so a crash mid-write can't corrupt it) to
|
|
12
|
+
* what is still unsent after core-api accepts a batch.
|
|
13
|
+
*/
|
|
14
|
+
import type { RunnerEventInput } from '../contract/catalog.js';
|
|
15
|
+
export interface JournalEvent extends RunnerEventInput {
|
|
16
|
+
data: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
export declare class EventJournal {
|
|
19
|
+
private readonly fs;
|
|
20
|
+
private readonly path;
|
|
21
|
+
private readonly pathMod;
|
|
22
|
+
private dirty;
|
|
23
|
+
private constructor();
|
|
24
|
+
static open(path: string): Promise<EventJournal>;
|
|
25
|
+
/** Events saved by an earlier process, oldest first, one per event id. */
|
|
26
|
+
load(): JournalEvent[];
|
|
27
|
+
append(event: JournalEvent): void;
|
|
28
|
+
/** Make appended lines durable. Called before each send. */
|
|
29
|
+
sync(): void;
|
|
30
|
+
/** Replace the journal with what is still unsent. */
|
|
31
|
+
rewrite(events: JournalEvent[]): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export class EventJournal {
|
|
2
|
+
fs;
|
|
3
|
+
path;
|
|
4
|
+
pathMod;
|
|
5
|
+
dirty = false;
|
|
6
|
+
constructor(fs, path, pathMod) {
|
|
7
|
+
this.fs = fs;
|
|
8
|
+
this.path = path;
|
|
9
|
+
this.pathMod = pathMod;
|
|
10
|
+
}
|
|
11
|
+
static async open(path) {
|
|
12
|
+
const fs = await import('node:fs');
|
|
13
|
+
const pathMod = await import('node:path');
|
|
14
|
+
fs.mkdirSync(pathMod.dirname(path), { recursive: true, mode: 0o700 });
|
|
15
|
+
if (!fs.existsSync(path))
|
|
16
|
+
fs.writeFileSync(path, '', { mode: 0o600 });
|
|
17
|
+
return new EventJournal(fs, path, pathMod);
|
|
18
|
+
}
|
|
19
|
+
/** Events saved by an earlier process, oldest first, one per event id. */
|
|
20
|
+
load() {
|
|
21
|
+
let raw = '';
|
|
22
|
+
try {
|
|
23
|
+
raw = this.fs.readFileSync(this.path, 'utf8');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const byId = new Map();
|
|
29
|
+
for (const line of raw.split('\n')) {
|
|
30
|
+
if (!line.trim())
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
const event = JSON.parse(line);
|
|
34
|
+
if (typeof event?.event_id === 'string' && Number.isInteger(event.seq) && typeof event.type === 'string') {
|
|
35
|
+
// A later line for the same id is a renumbering: it wins.
|
|
36
|
+
byId.set(event.event_id, { ...event, data: event.data ?? {} });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// A torn last line (killed mid-append) is skipped; its event was never
|
|
41
|
+
// acknowledged to the caller as sent, and every earlier line is whole.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...byId.values()].sort((a, b) => a.seq - b.seq);
|
|
45
|
+
}
|
|
46
|
+
append(event) {
|
|
47
|
+
this.fs.appendFileSync(this.path, `${JSON.stringify(event)}\n`, { mode: 0o600 });
|
|
48
|
+
this.dirty = true;
|
|
49
|
+
}
|
|
50
|
+
/** Make appended lines durable. Called before each send. */
|
|
51
|
+
sync() {
|
|
52
|
+
if (!this.dirty)
|
|
53
|
+
return;
|
|
54
|
+
const fd = this.fs.openSync(this.path, 'r+');
|
|
55
|
+
try {
|
|
56
|
+
this.fs.fsyncSync(fd);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
this.fs.closeSync(fd);
|
|
60
|
+
}
|
|
61
|
+
this.dirty = false;
|
|
62
|
+
}
|
|
63
|
+
/** Replace the journal with what is still unsent. */
|
|
64
|
+
rewrite(events) {
|
|
65
|
+
const temp = this.pathMod.join(this.pathMod.dirname(this.path), `.journal-${process.pid}-${Date.now()}.tmp`);
|
|
66
|
+
const body = events.map((event) => JSON.stringify(event)).join('\n');
|
|
67
|
+
const fd = this.fs.openSync(temp, 'w', 0o600);
|
|
68
|
+
try {
|
|
69
|
+
this.fs.writeSync(fd, body ? `${body}\n` : '');
|
|
70
|
+
this.fs.fsyncSync(fd);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
this.fs.closeSync(fd);
|
|
74
|
+
}
|
|
75
|
+
this.fs.renameSync(temp, this.path);
|
|
76
|
+
this.dirty = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/runner/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { TERMINAL_EVENT_TYPES } from '../contract/catalog.js';
|
|
2
|
+
import type { ArtifactRef, Ask, AskResult, Handoff, HandoffCreated, HandoffStatus, HeartbeatResult, ProjectFile, RegisterArtifact, RunBrief, RunnerCommand, RunnerToolList, ToolCall, ToolCallResult } from '../contract/types.js';
|
|
3
|
+
import { type FetchLike } from '../internal/sse.js';
|
|
4
|
+
import type { JournalEvent } from './journal.js';
|
|
5
|
+
/**
|
|
6
|
+
* Where unsent events are kept until core-api accepts them. The default is
|
|
7
|
+
* the Node disk journal (`journalPath`); a runner whose machine is somewhere
|
|
8
|
+
* else (a Trigger.dev worker driving a remote sandbox) passes its own, e.g.
|
|
9
|
+
* one that keeps the file on that machine. `sync` runs before each send and
|
|
10
|
+
* may be async; `append` and `rewrite` must keep their order themselves.
|
|
11
|
+
*/
|
|
12
|
+
export interface RunJournal {
|
|
13
|
+
load(): JournalEvent[] | Promise<JournalEvent[]>;
|
|
14
|
+
append(event: JournalEvent): void;
|
|
15
|
+
sync(): void | Promise<void>;
|
|
16
|
+
rewrite(events: JournalEvent[]): void | Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export interface RunnerClientOptions {
|
|
19
|
+
/** core-api, e.g. `https://api.workser.ai`. */
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
runId: string;
|
|
22
|
+
/** The `wsrn_` token from `POST v1/runs/:id/runner`. */
|
|
23
|
+
token: string;
|
|
24
|
+
fetch?: FetchLike;
|
|
25
|
+
/** How long an ordinary event may wait to be batched. Default 150 ms. */
|
|
26
|
+
flushMs?: number;
|
|
27
|
+
/** Heartbeat interval. Default 10 s; core-api closes a run after 3 min of silence. */
|
|
28
|
+
heartbeatMs?: number;
|
|
29
|
+
/** Something went wrong that the client handled (or couldn't). */
|
|
30
|
+
onError?: (error: RunnerError) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Keep unsent events in this file (Node only). A runner killed outright
|
|
33
|
+
* resends them on its next start — same ids and seqs, so core-api stores
|
|
34
|
+
* each once. See `journal.ts`.
|
|
35
|
+
*/
|
|
36
|
+
journalPath?: string;
|
|
37
|
+
/** A journal of your own instead of `journalPath` (see `RunJournal`). */
|
|
38
|
+
journal?: RunJournal;
|
|
39
|
+
}
|
|
40
|
+
export declare class RunnerError extends Error {
|
|
41
|
+
readonly status?: number | undefined;
|
|
42
|
+
readonly code?: string | undefined;
|
|
43
|
+
constructor(message: string, status?: number | undefined, code?: string | undefined);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Everything a Workser runner does for its run (runner protocol v1):
|
|
47
|
+
*
|
|
48
|
+
* const runner = new RunnerClient({ baseUrl, runId, token });
|
|
49
|
+
* const brief = await runner.start(); // brief + heartbeat
|
|
50
|
+
* runner.commands((cmd) => …); // stop, answers, decisions
|
|
51
|
+
* runner.emit('run.started', { engine: 'opencode' });
|
|
52
|
+
* runner.emit('step.started', { step_id: 's1', title: 'Run tests' });
|
|
53
|
+
* await runner.finish('run.completed', { summary: 'Done.' });
|
|
54
|
+
*
|
|
55
|
+
* Events are numbered by the client, from the run's cursor, in the order they
|
|
56
|
+
* are emitted, and sent in batches. A resend is safe (core-api drops copies);
|
|
57
|
+
* a gap is answered with where to resend from, and the client does.
|
|
58
|
+
*/
|
|
59
|
+
export declare class RunnerClient {
|
|
60
|
+
private readonly options;
|
|
61
|
+
private readonly baseUrl;
|
|
62
|
+
private readonly fetchFn;
|
|
63
|
+
private readonly flushMs;
|
|
64
|
+
private readonly heartbeatMs;
|
|
65
|
+
private token;
|
|
66
|
+
private nextSeq;
|
|
67
|
+
private pending;
|
|
68
|
+
private flushTimer;
|
|
69
|
+
private flushing;
|
|
70
|
+
private heartbeatTimer;
|
|
71
|
+
private readonly abort;
|
|
72
|
+
private lastCommandPos;
|
|
73
|
+
private closed;
|
|
74
|
+
private journal;
|
|
75
|
+
constructor(options: RunnerClientOptions);
|
|
76
|
+
get runId(): string;
|
|
77
|
+
/** The brief, and where to continue numbering. */
|
|
78
|
+
brief(): Promise<RunBrief>;
|
|
79
|
+
/**
|
|
80
|
+
* Pick up what an earlier process emitted but never got accepted. They go
|
|
81
|
+
* first, with their own seqs; new events continue after the highest.
|
|
82
|
+
*/
|
|
83
|
+
private recover;
|
|
84
|
+
/** Events still waiting to be accepted (a copy). */
|
|
85
|
+
get unsent(): number;
|
|
86
|
+
/** Read the brief and start heartbeating. */
|
|
87
|
+
start(): Promise<RunBrief>;
|
|
88
|
+
/**
|
|
89
|
+
* Queue one event. Checked against the catalog now, so a mistake throws
|
|
90
|
+
* here, in the runner's own code, not later in a batch.
|
|
91
|
+
*/
|
|
92
|
+
emit(type: string, data?: Record<string, unknown>): void;
|
|
93
|
+
/** Send everything queued. Resolves when core-api has it (or it was given up on). */
|
|
94
|
+
flush(): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Listen for commands. Resumes after the last one seen; on the first
|
|
97
|
+
* connect core-api starts at the oldest command not yet acked. Acks each
|
|
98
|
+
* command after `handler` resolves, unless `autoAck` is false.
|
|
99
|
+
*/
|
|
100
|
+
commands(handler: (command: RunnerCommand) => void | Promise<void>, options?: {
|
|
101
|
+
autoAck?: boolean;
|
|
102
|
+
}): Promise<void>;
|
|
103
|
+
/** Mark a command done. */
|
|
104
|
+
ack(pos: number): Promise<boolean>;
|
|
105
|
+
/** Ask the owner. The answer arrives through `commands()`. */
|
|
106
|
+
ask(ask: Ask): Promise<AskResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Put a file in the project's store: get an upload slot, PUT the bytes
|
|
109
|
+
* straight to storage, register it. Returns the stored file (its version
|
|
110
|
+
* counts up per name in the project). Large output belongs here, not in an
|
|
111
|
+
* event.
|
|
112
|
+
*
|
|
113
|
+
* `supersedes`: when another run wrote the current version of this name,
|
|
114
|
+
* name that version to replace it (otherwise core-api answers 409 — one
|
|
115
|
+
* writer per file at a time).
|
|
116
|
+
*/
|
|
117
|
+
uploadFile(name: string, bytes: Uint8Array | ArrayBuffer | string | Blob | ReadableStream<Uint8Array>, options?: {
|
|
118
|
+
kind?: ArtifactRef['kind'];
|
|
119
|
+
mime?: string;
|
|
120
|
+
supersedes?: string;
|
|
121
|
+
description?: string;
|
|
122
|
+
}): Promise<ProjectFile>;
|
|
123
|
+
/** Register a file already PUT to an upload slot from `uploadFile`. */
|
|
124
|
+
registerArtifact(file: RegisterArtifact): Promise<ProjectFile>;
|
|
125
|
+
/** A file in this run's project, with a short-lived download URL. */
|
|
126
|
+
getArtifact(artifactId: string): Promise<ProjectFile>;
|
|
127
|
+
/**
|
|
128
|
+
* Ask for a sub-job (plan Phase 3). core-api checks it (same project,
|
|
129
|
+
* depth, budget, no loops) and makes the child run; when the child ends,
|
|
130
|
+
* this run gets a `resume` command carrying the `HandoffResult`.
|
|
131
|
+
*/
|
|
132
|
+
handoff(handoff: Handoff): Promise<HandoffCreated>;
|
|
133
|
+
/** Where a sub-job this run asked for has got to. */
|
|
134
|
+
handoffStatus(childRunId: string): Promise<HandoffStatus>;
|
|
135
|
+
/** The tools this run may call (Workser's and the project's connected apps). */
|
|
136
|
+
listTools(): Promise<RunnerToolList>;
|
|
137
|
+
/**
|
|
138
|
+
* Call one tool by name. A side effect or a big spend comes back
|
|
139
|
+
* `approval_required` first; call again with the approval id once the
|
|
140
|
+
* owner has said yes (it arrives as an `approval` command).
|
|
141
|
+
*/
|
|
142
|
+
callTool(name: string, call?: ToolCall): Promise<ToolCallResult>;
|
|
143
|
+
/** Tell core-api this runner is alive. */
|
|
144
|
+
heartbeat(): Promise<HeartbeatResult>;
|
|
145
|
+
/**
|
|
146
|
+
* The token this client holds now — after `rotateToken`, the new one. For
|
|
147
|
+
* the runner to hand the same key to its engine; never log it.
|
|
148
|
+
*/
|
|
149
|
+
get currentToken(): string;
|
|
150
|
+
/** Swap to a fresh token for a long job. The old one stops working. */
|
|
151
|
+
rotateToken(): Promise<string>;
|
|
152
|
+
/** End the run: send the terminal event, flush, stop. */
|
|
153
|
+
finish(type: 'run.completed' | 'run.failed' | 'run.cancelled', data?: Record<string, unknown>, options?: {
|
|
154
|
+
timeoutMs?: number;
|
|
155
|
+
}): Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* Flush what's queued (waiting at most `timeoutMs` — a runner that can't
|
|
158
|
+
* reach core-api must still be able to exit), then stop heartbeats and the
|
|
159
|
+
* command stream.
|
|
160
|
+
*/
|
|
161
|
+
close(options?: {
|
|
162
|
+
timeoutMs?: number;
|
|
163
|
+
}): Promise<void>;
|
|
164
|
+
private scheduleFlush;
|
|
165
|
+
private startHeartbeat;
|
|
166
|
+
private drain;
|
|
167
|
+
/**
|
|
168
|
+
* core-api says the next seq it expects is `expected`, and that our batch
|
|
169
|
+
* was NOT stored (a gap, or seqs another writer already used). Batches are
|
|
170
|
+
* all-or-nothing and an identical resend is accepted as a duplicate, so a
|
|
171
|
+
* 409 never means "some of yours are stored": nothing we hold is dropped.
|
|
172
|
+
* Everything is renumbered from `expected`.
|
|
173
|
+
*
|
|
174
|
+
* It used to drop events below `expected` as "already stored". After a
|
|
175
|
+
* restart with a stale brief, those were the new runner's own events, never
|
|
176
|
+
* stored — `run.started` was lost that way (Phase 6 chaos test).
|
|
177
|
+
*/
|
|
178
|
+
private realign;
|
|
179
|
+
/** A journal of the caller's own may be async: its failure never stops the sends. */
|
|
180
|
+
private rewriteJournal;
|
|
181
|
+
private url;
|
|
182
|
+
private request;
|
|
183
|
+
private report;
|
|
184
|
+
}
|
|
185
|
+
export { TERMINAL_EVENT_TYPES };
|