ftown-bridge 0.9.2 → 0.9.4
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/dist/create-ftown-session.d.ts +1 -0
- package/dist/create-ftown-session.js +3 -2
- package/dist/create-ftown-session.js.map +1 -1
- package/dist/harness-cli.js +1 -1
- package/dist/harness-cli.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/install-ftown-workflows-cli.d.ts +3 -0
- package/dist/install-ftown-workflows-cli.js +30 -0
- package/dist/install-ftown-workflows-cli.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/workflow-runner-cli.d.ts +2 -0
- package/dist/workflow-runner-cli.js +315 -0
- package/dist/workflow-runner-cli.js.map +1 -0
- package/dist/workflow-runner.d.ts +162 -0
- package/dist/workflow-runner.js +305 -0
- package/dist/workflow-runner.js.map +1 -0
- package/package.json +5 -3
- package/skills/ftown-workflows/SKILL.md +282 -0
- package/skills/ftown-workflows/scripts/example.flow.mjs +122 -0
- package/skills/ftown-workflows/scripts/ftown-workflows +4 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ftown-workflows CLI.
|
|
4
|
+
*
|
|
5
|
+
* Wires the real dependencies (HTTP loopback BridgeClient, node:fs ResultStore,
|
|
6
|
+
* Date.now/setTimeout Clock, stderr Logger), loads a workflow script, and runs it
|
|
7
|
+
* through the engine in `workflow-runner.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Installed to ~/.ftown/ftown-workflows-cli.js by ftown-bridge. Must run INSIDE an
|
|
10
|
+
* ftown session: spawned children need a parent, so FTOWN_SESSION_ID is required.
|
|
11
|
+
*/
|
|
12
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
16
|
+
import { pathToFileURL } from 'node:url';
|
|
17
|
+
import { randomUUID } from 'node:crypto';
|
|
18
|
+
import { parseResultFile, runWorkflow, } from './workflow-runner.js';
|
|
19
|
+
function loadBridge() {
|
|
20
|
+
const path = join(homedir(), '.ftown', 'bridge.json');
|
|
21
|
+
const raw = readFileSync(path, 'utf8');
|
|
22
|
+
const data = JSON.parse(raw);
|
|
23
|
+
if (!data.port || !data.token) {
|
|
24
|
+
throw new Error('Invalid bridge.json (missing port or token)');
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
// ---- Real BridgeClient: HTTP to the local bridge loopback API ----
|
|
29
|
+
class HttpBridgeClient {
|
|
30
|
+
selfSessionId;
|
|
31
|
+
constructor(selfSessionId) {
|
|
32
|
+
this.selfSessionId = selfSessionId;
|
|
33
|
+
}
|
|
34
|
+
async api(method, path, body, extraHeaders) {
|
|
35
|
+
const { port, token } = loadBridge();
|
|
36
|
+
const headers = {
|
|
37
|
+
Authorization: `Bearer ${token}`,
|
|
38
|
+
...extraHeaders,
|
|
39
|
+
};
|
|
40
|
+
let payload;
|
|
41
|
+
if (body !== undefined) {
|
|
42
|
+
headers['Content-Type'] = 'application/json';
|
|
43
|
+
payload = JSON.stringify(body);
|
|
44
|
+
}
|
|
45
|
+
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
|
|
46
|
+
method,
|
|
47
|
+
headers,
|
|
48
|
+
body: payload,
|
|
49
|
+
});
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
let data = null;
|
|
52
|
+
if (text) {
|
|
53
|
+
try {
|
|
54
|
+
data = JSON.parse(text);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
data = { raw: text };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
const err = data;
|
|
62
|
+
throw new Error(err?.error ?? `HTTP ${res.status}`);
|
|
63
|
+
}
|
|
64
|
+
return { status: res.status, data };
|
|
65
|
+
}
|
|
66
|
+
async createSession(opts) {
|
|
67
|
+
const body = {
|
|
68
|
+
prompt: opts.prompt,
|
|
69
|
+
shellType: opts.shellType,
|
|
70
|
+
parentSessionId: opts.parentSessionId,
|
|
71
|
+
// Workflow children report via the result FILE, never via the mail briefing.
|
|
72
|
+
suppressBriefing: true,
|
|
73
|
+
};
|
|
74
|
+
if (opts.workingDir)
|
|
75
|
+
body.workingDir = opts.workingDir;
|
|
76
|
+
if (opts.name)
|
|
77
|
+
body.name = opts.name;
|
|
78
|
+
if (opts.model)
|
|
79
|
+
body.model = opts.model;
|
|
80
|
+
const { data } = await this.api('POST', '/api/sessions', body, {
|
|
81
|
+
'X-Ftown-Session-Id': this.selfSessionId,
|
|
82
|
+
});
|
|
83
|
+
const session = data.session;
|
|
84
|
+
if (!session?.id) {
|
|
85
|
+
throw new Error('createSession: bridge did not return a session id');
|
|
86
|
+
}
|
|
87
|
+
return { id: session.id };
|
|
88
|
+
}
|
|
89
|
+
async removeSession(id) {
|
|
90
|
+
try {
|
|
91
|
+
await this.api('DELETE', `/api/sessions/${id}`);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Swallow 404 / already-removed — cleanup must never throw.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async isRunning(id) {
|
|
98
|
+
try {
|
|
99
|
+
const { data } = await this.api('GET', `/api/sessions/${id}/running`);
|
|
100
|
+
return data.running === true;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// If we cannot reach the session, treat it as no longer running.
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ---- Real ResultStore: result files under ~/.ftown/workflows/<runId>/<stepKey>.json ----
|
|
109
|
+
class FsResultStore {
|
|
110
|
+
resultPath(runId, stepKey) {
|
|
111
|
+
return join(homedir(), '.ftown', 'workflows', runId, `${stepKey}.json`);
|
|
112
|
+
}
|
|
113
|
+
async readResult(path) {
|
|
114
|
+
let text;
|
|
115
|
+
try {
|
|
116
|
+
text = await readFile(path, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
// ENOENT (file not written yet) and any read error → not ready.
|
|
120
|
+
if (err.code === 'ENOENT')
|
|
121
|
+
return null;
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return parseResultFile(text);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ---- Real Clock ----
|
|
128
|
+
class RealClock {
|
|
129
|
+
now() {
|
|
130
|
+
return Date.now();
|
|
131
|
+
}
|
|
132
|
+
sleep(ms) {
|
|
133
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ---- Real Logger: concise lines to stderr ----
|
|
137
|
+
class StderrLogger {
|
|
138
|
+
event(ev) {
|
|
139
|
+
switch (ev.kind) {
|
|
140
|
+
case 'phase':
|
|
141
|
+
process.stderr.write(`\n[phase] ${ev.title}\n`);
|
|
142
|
+
break;
|
|
143
|
+
case 'log':
|
|
144
|
+
process.stderr.write(` ${ev.message}\n`);
|
|
145
|
+
break;
|
|
146
|
+
case 'agent-start':
|
|
147
|
+
process.stderr.write(` → start ${ev.label} (${ev.sessionId})\n`);
|
|
148
|
+
break;
|
|
149
|
+
case 'agent-done':
|
|
150
|
+
process.stderr.write(` ${ev.cached ? '⟳' : '✓'} ${ev.label}\n`);
|
|
151
|
+
break;
|
|
152
|
+
case 'agent-error':
|
|
153
|
+
process.stderr.write(` ✗ ${ev.label}: ${ev.error}\n`);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ---- argv parsing ----
|
|
159
|
+
const SHELLS = ['claude', 'cursor', 'codex', 'opencode', 'shell'];
|
|
160
|
+
function flag(args, name) {
|
|
161
|
+
const i = args.indexOf(name);
|
|
162
|
+
if (i === -1 || i + 1 >= args.length)
|
|
163
|
+
return undefined;
|
|
164
|
+
return args[i + 1];
|
|
165
|
+
}
|
|
166
|
+
function hasFlag(args, name) {
|
|
167
|
+
return args.includes(name);
|
|
168
|
+
}
|
|
169
|
+
/** Strict integer flag parser: rejects NaN / non-integer / below-min values. */
|
|
170
|
+
function parseIntFlag(raw, name, min) {
|
|
171
|
+
if (raw === undefined)
|
|
172
|
+
return undefined;
|
|
173
|
+
const n = Number(raw);
|
|
174
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < min) {
|
|
175
|
+
throw new Error(`${name} must be an integer >= ${min} (got "${raw}")`);
|
|
176
|
+
}
|
|
177
|
+
return n;
|
|
178
|
+
}
|
|
179
|
+
// Flags that consume the following argv token as their value — so positional
|
|
180
|
+
// detection must skip that token (otherwise a flag value looks like the script).
|
|
181
|
+
const VALUE_FLAGS = [
|
|
182
|
+
'--args',
|
|
183
|
+
'--workdir',
|
|
184
|
+
'--shell',
|
|
185
|
+
'--concurrency',
|
|
186
|
+
'--timeout',
|
|
187
|
+
'--max-agents',
|
|
188
|
+
'--run-id',
|
|
189
|
+
];
|
|
190
|
+
function positionals(args) {
|
|
191
|
+
const out = [];
|
|
192
|
+
for (let i = 0; i < args.length; i++) {
|
|
193
|
+
const a = args[i];
|
|
194
|
+
if (a.startsWith('--')) {
|
|
195
|
+
if (VALUE_FLAGS.includes(a))
|
|
196
|
+
i++;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
out.push(a);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
function usage() {
|
|
204
|
+
console.error(`Usage: ftown-workflows run <script.mjs> [options]
|
|
205
|
+
|
|
206
|
+
Runs a deterministic multi-session workflow. Must run INSIDE an ftown session
|
|
207
|
+
(FTOWN_SESSION_ID must be set — spawned children need a parent).
|
|
208
|
+
|
|
209
|
+
Options:
|
|
210
|
+
--args <json> JSON passed to the script as ctx.args
|
|
211
|
+
--workdir <path> Default working dir for spawned child sessions
|
|
212
|
+
--shell <type> Default child shell: ${SHELLS.join(' | ')} (default: claude)
|
|
213
|
+
--concurrency <n> Max concurrently-running child sessions (default: 4)
|
|
214
|
+
--timeout <ms> Default per-agent timeout in ms (default: 1800000)
|
|
215
|
+
--max-agents <n> Cap total agent() spawns this run (default: unbounded)
|
|
216
|
+
--run-id <id> Reuse a run id to RESUME (skips already-completed steps)
|
|
217
|
+
--json Print the workflow return value as compact JSON
|
|
218
|
+
|
|
219
|
+
Reads ~/.ftown/bridge.json (ftown-bridge must be running).`);
|
|
220
|
+
}
|
|
221
|
+
function parseShell(value) {
|
|
222
|
+
if (value === undefined)
|
|
223
|
+
return undefined;
|
|
224
|
+
if (SHELLS.includes(value))
|
|
225
|
+
return value;
|
|
226
|
+
throw new Error(`Invalid --shell "${value}" — use one of: ${SHELLS.join(', ')}`);
|
|
227
|
+
}
|
|
228
|
+
async function main() {
|
|
229
|
+
const argv = process.argv.slice(2);
|
|
230
|
+
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
231
|
+
usage();
|
|
232
|
+
process.exit(argv.length === 0 ? 1 : 0);
|
|
233
|
+
}
|
|
234
|
+
if (argv[0] !== 'run') {
|
|
235
|
+
usage();
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
const rest = argv.slice(1);
|
|
239
|
+
const scriptArg = positionals(rest)[0];
|
|
240
|
+
if (!scriptArg) {
|
|
241
|
+
throw new Error('Missing <script.mjs> — usage: ftown-workflows run <script.mjs> [options]');
|
|
242
|
+
}
|
|
243
|
+
const selfSessionId = process.env.FTOWN_SESSION_ID?.trim();
|
|
244
|
+
if (!selfSessionId) {
|
|
245
|
+
throw new Error('FTOWN_SESSION_ID is not set. ftown-workflows must run inside an ftown session ' +
|
|
246
|
+
'so spawned child sessions have a parent.');
|
|
247
|
+
}
|
|
248
|
+
// Fail fast if the bridge is down/misconfigured, instead of running a whole
|
|
249
|
+
// workflow that returns nothing but null results.
|
|
250
|
+
try {
|
|
251
|
+
loadBridge();
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
throw new Error('ftown-bridge is not running or ~/.ftown/bridge.json is invalid: ' +
|
|
255
|
+
(e instanceof Error ? e.message : String(e)));
|
|
256
|
+
}
|
|
257
|
+
const jsonOut = hasFlag(rest, '--json');
|
|
258
|
+
const runId = flag(rest, '--run-id') ?? randomUUID().slice(0, 8);
|
|
259
|
+
const concurrencyRaw = flag(rest, '--concurrency');
|
|
260
|
+
const timeoutRaw = flag(rest, '--timeout');
|
|
261
|
+
const maxAgentsRaw = flag(rest, '--max-agents');
|
|
262
|
+
const argsRaw = flag(rest, '--args');
|
|
263
|
+
let parsedArgs;
|
|
264
|
+
if (argsRaw !== undefined) {
|
|
265
|
+
try {
|
|
266
|
+
parsedArgs = JSON.parse(argsRaw);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
throw new Error('--args must be valid JSON');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const opts = {
|
|
273
|
+
runId,
|
|
274
|
+
selfSessionId,
|
|
275
|
+
args: parsedArgs,
|
|
276
|
+
defaultShell: parseShell(flag(rest, '--shell')) ?? 'claude',
|
|
277
|
+
};
|
|
278
|
+
const workdir = flag(rest, '--workdir');
|
|
279
|
+
if (workdir)
|
|
280
|
+
opts.workdir = resolve(workdir);
|
|
281
|
+
const concurrency = parseIntFlag(concurrencyRaw, '--concurrency', 1);
|
|
282
|
+
if (concurrency !== undefined)
|
|
283
|
+
opts.maxConcurrent = concurrency;
|
|
284
|
+
const timeout = parseIntFlag(timeoutRaw, '--timeout', 0);
|
|
285
|
+
if (timeout !== undefined)
|
|
286
|
+
opts.defaultTimeoutMs = timeout;
|
|
287
|
+
const maxAgents = parseIntFlag(maxAgentsRaw, '--max-agents', 0);
|
|
288
|
+
if (maxAgents !== undefined)
|
|
289
|
+
opts.maxAgents = maxAgents;
|
|
290
|
+
// Run dir holds every step's result file — print it so the user can resume.
|
|
291
|
+
const store = new FsResultStore();
|
|
292
|
+
const runDir = join(homedir(), '.ftown', 'workflows', runId);
|
|
293
|
+
mkdirSync(runDir, { recursive: true });
|
|
294
|
+
process.stderr.write(`[ftown-workflows] run ${runId}\n`);
|
|
295
|
+
process.stderr.write(`[ftown-workflows] run dir: ${runDir}\n`);
|
|
296
|
+
process.stderr.write(`[ftown-workflows] resume with: --run-id ${runId}\n`);
|
|
297
|
+
const scriptUrl = pathToFileURL(resolve(scriptArg)).href;
|
|
298
|
+
const mod = (await import(scriptUrl));
|
|
299
|
+
const deps = {
|
|
300
|
+
bridge: new HttpBridgeClient(selfSessionId),
|
|
301
|
+
store,
|
|
302
|
+
clock: new RealClock(),
|
|
303
|
+
logger: new StderrLogger(),
|
|
304
|
+
};
|
|
305
|
+
const out = await runWorkflow(deps, mod, opts);
|
|
306
|
+
if (out !== undefined) {
|
|
307
|
+
console.log(jsonOut ? JSON.stringify(out) : JSON.stringify(out, null, 2));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
main().catch((err) => {
|
|
311
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
312
|
+
console.error(`ftown-workflows: ${msg}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
});
|
|
315
|
+
//# sourceMappingURL=workflow-runner-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-runner-cli.js","sourceRoot":"","sources":["../src/workflow-runner-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ftown-workflows engine.
|
|
3
|
+
*
|
|
4
|
+
* Brings Workflow-tool-style deterministic orchestration to REAL ftown sessions:
|
|
5
|
+
* each `agent()` call spawns a real ftown session via the bridge loopback API, the
|
|
6
|
+
* child writes its result to a file, the runner polls the filesystem for that file
|
|
7
|
+
* (race-free — no inbox / Stop-hook contention), removes the session and returns it.
|
|
8
|
+
*
|
|
9
|
+
* The engine is fully dependency-injected (BridgeClient / ResultStore / Clock /
|
|
10
|
+
* Logger) so it is unit-testable without a live bridge, real fs or real timers.
|
|
11
|
+
*/
|
|
12
|
+
/** Minimal session-control surface the engine needs. Real impl = HTTP loopback API. */
|
|
13
|
+
export interface BridgeClient {
|
|
14
|
+
/** Create a session; returns its id. Mirrors POST /api/sessions. */
|
|
15
|
+
createSession(opts: SpawnSpec): Promise<{
|
|
16
|
+
id: string;
|
|
17
|
+
}>;
|
|
18
|
+
/** Remove (tombstone) a session. Mirrors DELETE /api/sessions/:id. Must not throw on 404. */
|
|
19
|
+
removeSession(id: string): Promise<void>;
|
|
20
|
+
/** True if the session PTY is alive. Mirrors GET /api/sessions/:id/running. */
|
|
21
|
+
isRunning(id: string): Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
/** Reads result files written by child sessions. Real impl = node:fs. */
|
|
24
|
+
export interface ResultStore {
|
|
25
|
+
/** Absolute path where step `stepKey` of `runId` writes its result. */
|
|
26
|
+
resultPath(runId: string, stepKey: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Returns the parsed RawResult if the file exists AND is valid JSON matching
|
|
29
|
+
* RawResult; returns null if the file is absent OR not yet valid JSON (partial
|
|
30
|
+
* write). MUST NOT throw for absent/partial files.
|
|
31
|
+
*/
|
|
32
|
+
readResult(path: string): Promise<RawResult | null>;
|
|
33
|
+
}
|
|
34
|
+
/** Time abstraction so tests run instantly (fake clock). */
|
|
35
|
+
export interface Clock {
|
|
36
|
+
now(): number;
|
|
37
|
+
sleep(ms: number): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export interface Logger {
|
|
40
|
+
/** Emitted for phase()/log() and lifecycle events. */
|
|
41
|
+
event(ev: WorkflowEvent): void;
|
|
42
|
+
}
|
|
43
|
+
export type WorkflowEvent = {
|
|
44
|
+
kind: 'phase';
|
|
45
|
+
title: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'log';
|
|
48
|
+
message: string;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'agent-start';
|
|
51
|
+
label: string;
|
|
52
|
+
phase?: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'agent-done';
|
|
56
|
+
label: string;
|
|
57
|
+
phase?: string;
|
|
58
|
+
ok: boolean;
|
|
59
|
+
cached: boolean;
|
|
60
|
+
} | {
|
|
61
|
+
kind: 'agent-error';
|
|
62
|
+
label: string;
|
|
63
|
+
phase?: string;
|
|
64
|
+
error: string;
|
|
65
|
+
};
|
|
66
|
+
export type WorkflowShell = 'claude' | 'cursor' | 'codex' | 'opencode' | 'shell';
|
|
67
|
+
/** What the engine asks BridgeClient to create. */
|
|
68
|
+
export interface SpawnSpec {
|
|
69
|
+
prompt: string;
|
|
70
|
+
shellType: WorkflowShell;
|
|
71
|
+
workingDir?: string;
|
|
72
|
+
name?: string;
|
|
73
|
+
model?: string;
|
|
74
|
+
parentSessionId: string;
|
|
75
|
+
}
|
|
76
|
+
/** Shape the child writes into its result file. */
|
|
77
|
+
export interface RawResult {
|
|
78
|
+
ok: boolean;
|
|
79
|
+
result?: unknown;
|
|
80
|
+
error?: string;
|
|
81
|
+
}
|
|
82
|
+
export interface AgentOptions {
|
|
83
|
+
label?: string;
|
|
84
|
+
phase?: string;
|
|
85
|
+
schema?: object;
|
|
86
|
+
shell?: WorkflowShell;
|
|
87
|
+
model?: string;
|
|
88
|
+
workdir?: string;
|
|
89
|
+
name?: string;
|
|
90
|
+
timeoutMs?: number;
|
|
91
|
+
pollIntervalMs?: number;
|
|
92
|
+
startupGraceMs?: number;
|
|
93
|
+
}
|
|
94
|
+
export interface Budget {
|
|
95
|
+
/** Max total agent() spawns allowed this run (null = unbounded). */
|
|
96
|
+
maxAgents: number | null;
|
|
97
|
+
/** Count of agent() spawns started so far (cached results do NOT count). */
|
|
98
|
+
spent(): number;
|
|
99
|
+
/** maxAgents - spent(), or Infinity if maxAgents is null. */
|
|
100
|
+
remaining(): number;
|
|
101
|
+
}
|
|
102
|
+
export interface WorkflowContext {
|
|
103
|
+
/**
|
|
104
|
+
* Spawn a real ftown session for `prompt`, block until its result file appears,
|
|
105
|
+
* remove the session, and return the result.
|
|
106
|
+
* - without schema: returns the result as a string. A string `result` is returned
|
|
107
|
+
* as-is; a non-string `result` is returned as a JSON string (JSON.stringify).
|
|
108
|
+
* - with schema: returns the parsed RawResult.result (validated as JSON; on parse
|
|
109
|
+
* failure the agent is treated as failed).
|
|
110
|
+
* Returns null if: the session exits without writing a valid result, the timeout
|
|
111
|
+
* elapses, ok===false, or the budget is exhausted. NEVER rejects for these — only
|
|
112
|
+
* programming errors (bad args) throw.
|
|
113
|
+
*/
|
|
114
|
+
agent(prompt: string, opts?: AgentOptions): Promise<string | unknown | null>;
|
|
115
|
+
/** Run thunks concurrently (BARRIER, respects maxConcurrent). A thunk that throws
|
|
116
|
+
* or whose agent errors resolves to null in the result array; the call never rejects. */
|
|
117
|
+
parallel<T>(thunks: Array<() => Promise<T>>): Promise<Array<T | null>>;
|
|
118
|
+
/** Run each item through all stages independently, NO barrier between stages.
|
|
119
|
+
* Stage callback signature: (prev, originalItem, index). A stage that throws drops
|
|
120
|
+
* that item to null and skips its remaining stages. */
|
|
121
|
+
pipeline(items: unknown[], ...stages: Array<(prev: unknown, item: unknown, index: number) => Promise<unknown>>): Promise<unknown[]>;
|
|
122
|
+
phase(title: string): void;
|
|
123
|
+
log(message: string): void;
|
|
124
|
+
readonly args: unknown;
|
|
125
|
+
readonly budget: Budget;
|
|
126
|
+
}
|
|
127
|
+
export interface RunOptions {
|
|
128
|
+
runId: string;
|
|
129
|
+
selfSessionId: string;
|
|
130
|
+
args?: unknown;
|
|
131
|
+
workdir?: string;
|
|
132
|
+
defaultShell?: WorkflowShell;
|
|
133
|
+
defaultTimeoutMs?: number;
|
|
134
|
+
maxConcurrent?: number;
|
|
135
|
+
maxAgents?: number | null;
|
|
136
|
+
}
|
|
137
|
+
export interface RunnerDeps {
|
|
138
|
+
bridge: BridgeClient;
|
|
139
|
+
store: ResultStore;
|
|
140
|
+
clock: Clock;
|
|
141
|
+
logger: Logger;
|
|
142
|
+
}
|
|
143
|
+
/** A loaded workflow module: default export is the script body fn, or a `run` export. */
|
|
144
|
+
export type WorkflowModule = {
|
|
145
|
+
default?: (ctx: WorkflowContext) => Promise<unknown> | unknown;
|
|
146
|
+
run?: (ctx: WorkflowContext) => Promise<unknown> | unknown;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Build the full child prompt: the user's `task`, then a clearly delimited protocol
|
|
150
|
+
* block instructing the child to write its final result as JSON to `resultFilePath`
|
|
151
|
+
* and then stop. When `schema` is provided, the block embeds the schema and says
|
|
152
|
+
* `result` must conform. The block always literally contains `resultFilePath`.
|
|
153
|
+
*/
|
|
154
|
+
export declare function buildAgentPrompt(task: string, resultFilePath: string, schema?: object): string;
|
|
155
|
+
/**
|
|
156
|
+
* Parse raw file text into RawResult. Returns null if text is empty or not valid
|
|
157
|
+
* JSON (partial write). Throws nothing. If JSON parses but lacks a boolean `ok`,
|
|
158
|
+
* treat as null (not yet complete).
|
|
159
|
+
*/
|
|
160
|
+
export declare function parseResultFile(text: string): RawResult | null;
|
|
161
|
+
/** Build + run the context, execute the module, return its return value. */
|
|
162
|
+
export declare function runWorkflow(deps: RunnerDeps, mod: WorkflowModule, opts: RunOptions): Promise<unknown>;
|