@agimon-ai/po-workflow 0.0.0-alpha.0 → 0.0.0-alpha.2
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/extensions/native.mjs +1620 -0
- package/dist/extensions/native.mjs.map +1 -0
- package/dist/extensions/server.cjs +107 -27
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +107 -27
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +18 -13
- package/dist/extensions/web.mjs.map +1 -1
- package/package.json +29 -11
- package/src/types/webWorkflowTerminal.ts +2 -0
|
@@ -48,7 +48,6 @@ let node_stream = require("node:stream");
|
|
|
48
48
|
let _agimon_ai_po_core_packageApi = require("@agimon-ai/po-core/packageApi");
|
|
49
49
|
let hono = require("hono");
|
|
50
50
|
let hono_streaming = require("hono/streaming");
|
|
51
|
-
let _agimon_ai_po_core_web = require("@agimon-ai/po-core/web");
|
|
52
51
|
let node_url = require("node:url");
|
|
53
52
|
let node_os = require("node:os");
|
|
54
53
|
node_os = __toESM(node_os, 1);
|
|
@@ -1974,13 +1973,19 @@ const ARTIFACT_NAME_PARAM = ":name{.+}";
|
|
|
1974
1973
|
* route follows the run's current step, which is one of a parallel group's.
|
|
1975
1974
|
*/
|
|
1976
1975
|
const STEP_TARGET_PARAM = "step";
|
|
1977
|
-
|
|
1976
|
+
const routes = {
|
|
1978
1977
|
/**
|
|
1979
1978
|
* The terminal, as server-sent events.
|
|
1980
1979
|
*
|
|
1981
1980
|
* `stream: true` gets a URL and no call method: the consumer is the browser's
|
|
1982
1981
|
* own `EventSource`, and a relayed call would buffer a stream that never ends.
|
|
1983
1982
|
*/
|
|
1983
|
+
snapshot: {
|
|
1984
|
+
method: "GET",
|
|
1985
|
+
path: `${RUN}/screen`,
|
|
1986
|
+
query: [STEP_TARGET_PARAM],
|
|
1987
|
+
response: void 0
|
|
1988
|
+
},
|
|
1984
1989
|
screen: {
|
|
1985
1990
|
method: "GET",
|
|
1986
1991
|
path: `${RUN}/screen/stream`,
|
|
@@ -1990,7 +1995,7 @@ var apiRoutes_default = (0, _agimon_ai_po_core_web.defineApiRoutes)({
|
|
|
1990
1995
|
control: {
|
|
1991
1996
|
method: "POST",
|
|
1992
1997
|
path: `${RUN}/control`,
|
|
1993
|
-
response:
|
|
1998
|
+
response: void 0
|
|
1994
1999
|
},
|
|
1995
2000
|
keys: {
|
|
1996
2001
|
method: "POST",
|
|
@@ -1999,23 +2004,23 @@ var apiRoutes_default = (0, _agimon_ai_po_core_web.defineApiRoutes)({
|
|
|
1999
2004
|
resize: {
|
|
2000
2005
|
method: "POST",
|
|
2001
2006
|
path: `${RUN}/resize`,
|
|
2002
|
-
response:
|
|
2007
|
+
response: void 0
|
|
2003
2008
|
},
|
|
2004
2009
|
remove: {
|
|
2005
2010
|
method: "DELETE",
|
|
2006
2011
|
path: RUN,
|
|
2007
|
-
response:
|
|
2012
|
+
response: void 0
|
|
2008
2013
|
},
|
|
2009
2014
|
stop: {
|
|
2010
2015
|
method: "POST",
|
|
2011
2016
|
path: `${RUN}/stop`,
|
|
2012
|
-
response:
|
|
2017
|
+
response: void 0
|
|
2013
2018
|
},
|
|
2014
2019
|
/** Guidance for the agent session the running step is in, like steering a Team sub-agent. */
|
|
2015
2020
|
steer: {
|
|
2016
2021
|
method: "POST",
|
|
2017
2022
|
path: `${RUN}/steer`,
|
|
2018
|
-
response:
|
|
2023
|
+
response: void 0
|
|
2019
2024
|
},
|
|
2020
2025
|
/**
|
|
2021
2026
|
* Launch a workflow into the mounting session's own runtime, which then owns
|
|
@@ -2024,27 +2029,27 @@ var apiRoutes_default = (0, _agimon_ai_po_core_web.defineApiRoutes)({
|
|
|
2024
2029
|
launch: {
|
|
2025
2030
|
method: "POST",
|
|
2026
2031
|
path: "/launch",
|
|
2027
|
-
response:
|
|
2032
|
+
response: void 0
|
|
2028
2033
|
},
|
|
2029
2034
|
openRunDirectory: {
|
|
2030
2035
|
method: "POST",
|
|
2031
2036
|
path: `${RUN}/open-directory`,
|
|
2032
|
-
response:
|
|
2037
|
+
response: void 0
|
|
2033
2038
|
},
|
|
2034
2039
|
artifacts: {
|
|
2035
2040
|
method: "GET",
|
|
2036
2041
|
path: `${RUN}/artifacts`,
|
|
2037
2042
|
query: ["directory"],
|
|
2038
|
-
response:
|
|
2043
|
+
response: void 0
|
|
2039
2044
|
},
|
|
2040
2045
|
/** One artifact: its metadata and text, or its raw bytes under `raw=1`. */
|
|
2041
2046
|
artifact: {
|
|
2042
2047
|
method: "GET",
|
|
2043
2048
|
path: `${RUN}/artifacts/${ARTIFACT_NAME_PARAM}`,
|
|
2044
2049
|
query: ["raw", "download"],
|
|
2045
|
-
response:
|
|
2050
|
+
response: void 0
|
|
2046
2051
|
}
|
|
2047
|
-
}
|
|
2052
|
+
};
|
|
2048
2053
|
//#endregion
|
|
2049
2054
|
//#region src/types/webWorkflowTerminal.ts
|
|
2050
2055
|
/**
|
|
@@ -2074,6 +2079,8 @@ const STAGES = [
|
|
|
2074
2079
|
];
|
|
2075
2080
|
/** Lines of screen the stream sends, which is a terminal's visible height plus room. */
|
|
2076
2081
|
const SCREEN_LINES = 48;
|
|
2082
|
+
/** Bound finite snapshots, including a maliciously wide terminal row. */
|
|
2083
|
+
const MAX_SCREEN_LINE_CHARS = 4096;
|
|
2077
2084
|
/** How often the stream re-reads; the service coalesces, so this is an upper bound. */
|
|
2078
2085
|
const STREAM_TICK_MS = 500;
|
|
2079
2086
|
/** How often a settled run is re-checked before the stream closes itself. */
|
|
@@ -2176,7 +2183,29 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2176
2183
|
} catch {}
|
|
2177
2184
|
};
|
|
2178
2185
|
const missing = (workspace, runKey) => ({ error: `No workflow run '${runKey}' in workspace '${workspace}'.` });
|
|
2179
|
-
app.get(
|
|
2186
|
+
app.get(routes.snapshot.path, async (context) => {
|
|
2187
|
+
const workspace = context.req.param("workspace");
|
|
2188
|
+
const runKey = context.req.param("runKey");
|
|
2189
|
+
const record = await runOf(workspace, runKey);
|
|
2190
|
+
if (record === void 0) return context.json(missing(workspace, runKey), 404);
|
|
2191
|
+
const step = stepOf(context.req.query(STEP_TARGET_PARAM));
|
|
2192
|
+
const target = {
|
|
2193
|
+
record,
|
|
2194
|
+
...step === void 0 ? {} : { step }
|
|
2195
|
+
};
|
|
2196
|
+
const capabilities = terminal.capabilities(target);
|
|
2197
|
+
const captured = capabilities.readable ? await terminal.screen(runIdentityOf(workspace, runKey, step), target, SCREEN_LINES) : [];
|
|
2198
|
+
const lines = captured.slice(-48).map((line) => line.slice(0, MAX_SCREEN_LINE_CHARS));
|
|
2199
|
+
const truncated = captured.length > SCREEN_LINES || captured.some((line) => line.length > MAX_SCREEN_LINE_CHARS);
|
|
2200
|
+
const snapshot = {
|
|
2201
|
+
lines,
|
|
2202
|
+
capabilities,
|
|
2203
|
+
...record.stage === "running" ? {} : { ended: true },
|
|
2204
|
+
...truncated ? { truncated: true } : {}
|
|
2205
|
+
};
|
|
2206
|
+
return context.json(snapshot);
|
|
2207
|
+
});
|
|
2208
|
+
app.get(routes.screen.path, async (context) => {
|
|
2180
2209
|
const workspace = context.req.param("workspace");
|
|
2181
2210
|
const runKey = context.req.param("runKey");
|
|
2182
2211
|
const record = await runOf(workspace, runKey);
|
|
@@ -2217,7 +2246,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2217
2246
|
if (latest.stage !== "running") terminal.forgetRun(identity);
|
|
2218
2247
|
});
|
|
2219
2248
|
});
|
|
2220
|
-
app.post(
|
|
2249
|
+
app.post(routes.control.path, async (context) => {
|
|
2221
2250
|
const workspace = context.req.param("workspace");
|
|
2222
2251
|
const runKey = context.req.param("runKey");
|
|
2223
2252
|
const record = await runOf(workspace, runKey);
|
|
@@ -2251,7 +2280,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2251
2280
|
};
|
|
2252
2281
|
return context.json(held);
|
|
2253
2282
|
});
|
|
2254
|
-
app.post(
|
|
2283
|
+
app.post(routes.keys.path, async (context) => {
|
|
2255
2284
|
const workspace = context.req.param("workspace");
|
|
2256
2285
|
const runKey = context.req.param("runKey");
|
|
2257
2286
|
const record = await runOf(workspace, runKey);
|
|
@@ -2269,7 +2298,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2269
2298
|
}
|
|
2270
2299
|
return context.body(null, 204);
|
|
2271
2300
|
});
|
|
2272
|
-
app.post(
|
|
2301
|
+
app.post(routes.resize.path, async (context) => {
|
|
2273
2302
|
const workspace = context.req.param("workspace");
|
|
2274
2303
|
const runKey = context.req.param("runKey");
|
|
2275
2304
|
const record = await runOf(workspace, runKey);
|
|
@@ -2288,7 +2317,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2288
2317
|
return context.json({ error: error instanceof Error ? error.message : String(error) }, 409);
|
|
2289
2318
|
}
|
|
2290
2319
|
});
|
|
2291
|
-
app.post(
|
|
2320
|
+
app.post(routes.stop.path, async (context) => {
|
|
2292
2321
|
const workspace = context.req.param("workspace");
|
|
2293
2322
|
const runKey = context.req.param("runKey");
|
|
2294
2323
|
const record = await runOf(workspace, runKey);
|
|
@@ -2301,7 +2330,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2301
2330
|
}
|
|
2302
2331
|
return context.json({ requested: true });
|
|
2303
2332
|
});
|
|
2304
|
-
app.post(
|
|
2333
|
+
app.post(routes.steer.path, async (context) => {
|
|
2305
2334
|
const workspace = context.req.param("workspace");
|
|
2306
2335
|
const runKey = context.req.param("runKey");
|
|
2307
2336
|
const record = await runOf(workspace, runKey);
|
|
@@ -2322,12 +2351,12 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2322
2351
|
}
|
|
2323
2352
|
return context.json({ delivered: true });
|
|
2324
2353
|
});
|
|
2325
|
-
app.post(
|
|
2354
|
+
app.post(routes.launch.path, async (context) => {
|
|
2326
2355
|
if (options.launch === void 0) return context.json({ error: "Launching needs the mount of a session with a workflow runtime." }, 409);
|
|
2327
2356
|
const response = await options.launch(await jsonBody(context));
|
|
2328
2357
|
return context.json(response);
|
|
2329
2358
|
});
|
|
2330
|
-
app.delete(
|
|
2359
|
+
app.delete(routes.remove.path, async (context) => {
|
|
2331
2360
|
const workspace = context.req.param("workspace");
|
|
2332
2361
|
const runKey = context.req.param("runKey");
|
|
2333
2362
|
const record = await runOf(workspace, runKey);
|
|
@@ -2345,7 +2374,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2345
2374
|
}
|
|
2346
2375
|
return context.json({ deleted: true });
|
|
2347
2376
|
});
|
|
2348
|
-
app.post(
|
|
2377
|
+
app.post(routes.openRunDirectory.path, async (context) => {
|
|
2349
2378
|
if ((0, _agimon_ai_po_core_packageApi.doomApiCallerFrom)(context.req.raw.headers)?.locality !== "local") return context.json({ error: "Opening Finder is only available on the host." }, 403);
|
|
2350
2379
|
const workspace = context.req.param("workspace");
|
|
2351
2380
|
const runKey = context.req.param("runKey");
|
|
@@ -2370,7 +2399,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2370
2399
|
return context.json({ error: error instanceof Error ? error.message : String(error) }, 409);
|
|
2371
2400
|
}
|
|
2372
2401
|
});
|
|
2373
|
-
app.get(
|
|
2402
|
+
app.get(routes.artifacts.path, async (context) => {
|
|
2374
2403
|
const workspace = context.req.param("workspace");
|
|
2375
2404
|
const runKey = context.req.param("runKey");
|
|
2376
2405
|
const record = await runOf(workspace, runKey);
|
|
@@ -2389,7 +2418,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2389
2418
|
return context.json({ error: error instanceof Error ? error.message : String(error) }, 409);
|
|
2390
2419
|
}
|
|
2391
2420
|
});
|
|
2392
|
-
app.get(
|
|
2421
|
+
app.get(routes.artifact.path, async (context) => {
|
|
2393
2422
|
const workspace = context.req.param("workspace");
|
|
2394
2423
|
const runKey = context.req.param("runKey");
|
|
2395
2424
|
const record = await runOf(workspace, runKey);
|
|
@@ -2398,7 +2427,7 @@ function createWorkflowHubApi(options = {}) {
|
|
|
2398
2427
|
const requested = context.req.param("name");
|
|
2399
2428
|
const resolved = resolveInside(runDir, requested);
|
|
2400
2429
|
if (resolved === void 0) return context.json({ error: `'${requested}' is not inside this run.` }, 400);
|
|
2401
|
-
if (context.req.query("raw") === "1") return streamArtifact(resolved, requested, context.req.header("range"), context.req.query("download") === "1") ?? context.json({ error: `'${requested}' has not been written.` }, 404);
|
|
2430
|
+
if (context.req.query("raw") === "1") return streamArtifact(resolved, requested, context.req.header("range"), context.req.query("download") === "1", context.req.header("if-match")) ?? context.json({ error: `'${requested}' has not been written.` }, 404);
|
|
2402
2431
|
const body = readArtifact(resolved, requested);
|
|
2403
2432
|
if (body === void 0) return context.json({ error: `'${requested}' has not been written.` }, 404);
|
|
2404
2433
|
return context.json(body);
|
|
@@ -2520,14 +2549,26 @@ function readArtifact(resolved, requested) {
|
|
|
2520
2549
|
};
|
|
2521
2550
|
}
|
|
2522
2551
|
/** Streams media with byte-range support so browser audio, video, and PDF controls can seek. */
|
|
2523
|
-
function streamArtifact(resolved, requested, rangeHeader, download) {
|
|
2552
|
+
function streamArtifact(resolved, requested, rangeHeader, download, expectedVersion) {
|
|
2524
2553
|
const stats = statOf(resolved);
|
|
2525
2554
|
if (stats === void 0 || stats.isDirectory()) return void 0;
|
|
2555
|
+
const version = `"${stats.dev}-${stats.ino}-${stats.size}-${stats.mtimeMs}-${stats.ctimeMs}"`;
|
|
2556
|
+
if (expectedVersion !== void 0 && expectedVersion !== version) return new Response(null, {
|
|
2557
|
+
status: 412,
|
|
2558
|
+
headers: { etag: version }
|
|
2559
|
+
});
|
|
2526
2560
|
const mimeType = artifactMimeType(requested);
|
|
2527
2561
|
let start = 0;
|
|
2528
2562
|
let end = Math.max(0, stats.size - 1);
|
|
2529
2563
|
let partial = false;
|
|
2530
2564
|
const match = rangeHeader?.match(/^bytes=(\d*)-(\d*)$/);
|
|
2565
|
+
if (rangeHeader !== void 0 && (!match || !match[1] && !match[2] || [match[1], match[2]].some((value) => value !== "" && !Number.isSafeInteger(Number(value))) || stats.size === 0)) return new Response(null, {
|
|
2566
|
+
status: 416,
|
|
2567
|
+
headers: {
|
|
2568
|
+
etag: version,
|
|
2569
|
+
"content-range": `bytes */${stats.size}`
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2531
2572
|
if (match !== null && match !== void 0 && stats.size > 0) {
|
|
2532
2573
|
const requestedStart = match[1] === "" ? void 0 : Number(match[1]);
|
|
2533
2574
|
const requestedEnd = match[2] === "" ? void 0 : Number(match[2]);
|
|
@@ -2536,7 +2577,7 @@ function streamArtifact(resolved, requested, rangeHeader, download) {
|
|
|
2536
2577
|
start = requestedStart;
|
|
2537
2578
|
end = requestedEnd === void 0 ? end : Math.min(requestedEnd, end);
|
|
2538
2579
|
}
|
|
2539
|
-
if (start >= stats.size || start > end) return new Response(null, {
|
|
2580
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start >= stats.size || start > end) return new Response(null, {
|
|
2540
2581
|
status: 416,
|
|
2541
2582
|
headers: { "content-range": `bytes */${String(stats.size)}` }
|
|
2542
2583
|
});
|
|
@@ -2544,6 +2585,7 @@ function streamArtifact(resolved, requested, rangeHeader, download) {
|
|
|
2544
2585
|
}
|
|
2545
2586
|
const filename = node_path.default.basename(requested).replace(/["\\\r\n]/g, "_");
|
|
2546
2587
|
const headers = new Headers({
|
|
2588
|
+
etag: version,
|
|
2547
2589
|
"accept-ranges": "bytes",
|
|
2548
2590
|
"content-disposition": `${download || mimeType === "text/html" ? "attachment" : "inline"}; filename="${filename}"`,
|
|
2549
2591
|
"content-length": String(stats.size === 0 ? 0 : end - start + 1),
|
|
@@ -2555,6 +2597,44 @@ function streamArtifact(resolved, requested, rangeHeader, download) {
|
|
|
2555
2597
|
status: 200,
|
|
2556
2598
|
headers
|
|
2557
2599
|
});
|
|
2600
|
+
if (expectedVersion !== void 0) {
|
|
2601
|
+
if (!partial || end - start + 1 > 1048576) return new Response(null, {
|
|
2602
|
+
status: 416,
|
|
2603
|
+
headers: {
|
|
2604
|
+
etag: version,
|
|
2605
|
+
"content-range": `bytes */${stats.size}`
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2608
|
+
const fd = node_fs.default.openSync(resolved, "r");
|
|
2609
|
+
try {
|
|
2610
|
+
const opened = node_fs.default.fstatSync(fd);
|
|
2611
|
+
if (`"${opened.dev}-${opened.ino}-${opened.size}-${opened.mtimeMs}-${opened.ctimeMs}"` !== expectedVersion) return new Response(null, {
|
|
2612
|
+
status: 412,
|
|
2613
|
+
headers: { etag: version }
|
|
2614
|
+
});
|
|
2615
|
+
const bytes = new Uint8Array(end - start + 1);
|
|
2616
|
+
let read = 0;
|
|
2617
|
+
while (read < bytes.length) {
|
|
2618
|
+
const count = node_fs.default.readSync(fd, bytes, read, bytes.length - read, start + read);
|
|
2619
|
+
if (count === 0) return new Response(null, {
|
|
2620
|
+
status: 412,
|
|
2621
|
+
headers: { etag: version }
|
|
2622
|
+
});
|
|
2623
|
+
read += count;
|
|
2624
|
+
}
|
|
2625
|
+
const after = node_fs.default.fstatSync(fd);
|
|
2626
|
+
if (after.size !== opened.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs) return new Response(null, {
|
|
2627
|
+
status: 412,
|
|
2628
|
+
headers: { etag: version }
|
|
2629
|
+
});
|
|
2630
|
+
return new Response(bytes, {
|
|
2631
|
+
status: 206,
|
|
2632
|
+
headers
|
|
2633
|
+
});
|
|
2634
|
+
} finally {
|
|
2635
|
+
node_fs.default.closeSync(fd);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2558
2638
|
const body = node_stream.Readable.toWeb(node_fs.default.createReadStream(resolved, {
|
|
2559
2639
|
start,
|
|
2560
2640
|
end
|
|
@@ -3305,7 +3385,7 @@ function createWorkflowServerRuntime(host, serverHost) {
|
|
|
3305
3385
|
const response = await serverHost.context.requestApi({
|
|
3306
3386
|
scope: "session",
|
|
3307
3387
|
sessionId: owner
|
|
3308
|
-
}, WORKFLOW_API_BASE_PATH, new Request(`${IN_PROCESS_ORIGIN}${
|
|
3388
|
+
}, WORKFLOW_API_BASE_PATH, new Request(`${IN_PROCESS_ORIGIN}${routes.launch.path}`, {
|
|
3309
3389
|
method: "POST",
|
|
3310
3390
|
headers: { "content-type": "application/json" },
|
|
3311
3391
|
body: JSON.stringify(parameters)
|