@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.
@@ -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
- var apiRoutes_default = (0, _agimon_ai_po_core_web.defineApiRoutes)({
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: (0, _agimon_ai_po_core_web.apiResponse)()
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: (0, _agimon_ai_po_core_web.apiResponse)()
2007
+ response: void 0
2003
2008
  },
2004
2009
  remove: {
2005
2010
  method: "DELETE",
2006
2011
  path: RUN,
2007
- response: (0, _agimon_ai_po_core_web.apiResponse)()
2012
+ response: void 0
2008
2013
  },
2009
2014
  stop: {
2010
2015
  method: "POST",
2011
2016
  path: `${RUN}/stop`,
2012
- response: (0, _agimon_ai_po_core_web.apiResponse)()
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: (0, _agimon_ai_po_core_web.apiResponse)()
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: (0, _agimon_ai_po_core_web.apiResponse)()
2032
+ response: void 0
2028
2033
  },
2029
2034
  openRunDirectory: {
2030
2035
  method: "POST",
2031
2036
  path: `${RUN}/open-directory`,
2032
- response: (0, _agimon_ai_po_core_web.apiResponse)()
2037
+ response: void 0
2033
2038
  },
2034
2039
  artifacts: {
2035
2040
  method: "GET",
2036
2041
  path: `${RUN}/artifacts`,
2037
2042
  query: ["directory"],
2038
- response: (0, _agimon_ai_po_core_web.apiResponse)()
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: (0, _agimon_ai_po_core_web.apiResponse)()
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(apiRoutes_default.screen.path, async (context) => {
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(apiRoutes_default.control.path, async (context) => {
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(apiRoutes_default.keys.path, async (context) => {
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(apiRoutes_default.resize.path, async (context) => {
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(apiRoutes_default.stop.path, async (context) => {
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(apiRoutes_default.steer.path, async (context) => {
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(apiRoutes_default.launch.path, async (context) => {
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(apiRoutes_default.remove.path, async (context) => {
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(apiRoutes_default.openRunDirectory.path, async (context) => {
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(apiRoutes_default.artifacts.path, async (context) => {
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(apiRoutes_default.artifact.path, async (context) => {
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}${apiRoutes_default.launch.path}`, {
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)