@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.
@@ -0,0 +1,1620 @@
1
+ import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineNativePlugin, defineSessionStore, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
2
+ import { Badge, Button, CodeBlock, Dot, Input, ListRow, Markdown, Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle, Textarea, WorkflowIcon } from "@agimon-ai/po-native-components";
3
+ import { createElement, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
+ import { View } from "react-native-unistyles/components/native/View";
5
+ import { Text } from "react-native-unistyles/components/native/Text";
6
+ import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
7
+ import { StyleSheet } from "react-native-unistyles";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/types/webWorkflows.ts
10
+ const WORKFLOW_RUNS_TYPE = "workflow_runs";
11
+ const WORKFLOW_CATALOG_TYPE = "workflow_catalog";
12
+ //#endregion
13
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/runDuration.ts
14
+ /**
15
+ * Local copy of the host's formatRunDuration (src/web/lib/sessionSummary.ts):
16
+ * this plugin keeps zero host-lib imports so its later move into the owning
17
+ * package is a file relocation.
18
+ */
19
+ const MINUTE_MS = 6e4;
20
+ const HOUR_MS = 60 * MINUTE_MS;
21
+ function formatRunDuration(elapsedMs) {
22
+ if (elapsedMs < MINUTE_MS) return "<1m";
23
+ if (elapsedMs < HOUR_MS) return `${Math.floor(elapsedMs / MINUTE_MS)}m`;
24
+ const hours = Math.floor(elapsedMs / HOUR_MS);
25
+ const minutes = Math.floor(elapsedMs % HOUR_MS / MINUTE_MS);
26
+ return `${hours}h ${String(minutes).padStart(2, "0")}m`;
27
+ }
28
+ //#endregion
29
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/workflowActivity.ts
30
+ const PAUSED_STATES = /* @__PURE__ */ new Set(["paused", "pause_requested"]);
31
+ function workflowRunIdentity(run) {
32
+ return `${run.workspace}/${run.runKey}`;
33
+ }
34
+ function toneOf(run) {
35
+ if (run.stage === "running") return run.executionState !== void 0 && PAUSED_STATES.has(run.executionState) ? "paused" : "running";
36
+ if (run.stage === "error") return "failed";
37
+ if (run.outcome !== void 0 && run.outcome !== "success") return run.outcome === "failed" ? "failed" : "skipped";
38
+ return "done";
39
+ }
40
+ function detailOf(run, tone) {
41
+ if (tone === "paused") return "paused; resume it from the owning session";
42
+ if (tone === "failed") return run.errorMessage ?? (run.failedJob === void 0 ? "failed" : `job '${run.failedJob}' failed`);
43
+ if (tone === "running") {
44
+ if (run.position === void 0) return "starting";
45
+ return run.position.step === void 0 ? run.position.job : `${run.position.job} · ${run.position.step}`;
46
+ }
47
+ return run.outcome ?? "done";
48
+ }
49
+ function elapsedOf(run, now) {
50
+ const start = Date.parse(run.startedAt);
51
+ if (!Number.isFinite(start)) return "";
52
+ const end = run.finishedAt === void 0 ? now : Date.parse(run.finishedAt);
53
+ return formatRunDuration(Math.max(0, (Number.isFinite(end) ? end : now) - start));
54
+ }
55
+ /** The hub's presented order is kept: it already puts live runs first. */
56
+ function workflowActivityRows(runs, now, sessionId = null) {
57
+ return runs.map((run) => {
58
+ const tone = toneOf(run);
59
+ return {
60
+ identity: workflowRunIdentity(run),
61
+ runKey: run.runKey,
62
+ name: run.displayName,
63
+ tone,
64
+ elapsed: elapsedOf(run, now),
65
+ detail: detailOf(run, tone),
66
+ ...run.ownerSessionId === void 0 || run.ownerSessionId === sessionId ? {} : { ownerSessionId: run.ownerSessionId }
67
+ };
68
+ });
69
+ }
70
+ const GROUP_OF = {
71
+ running: "running",
72
+ paused: "running",
73
+ failed: "failed",
74
+ done: "successful",
75
+ skipped: "successful"
76
+ };
77
+ const GROUP_ORDER = [
78
+ "running",
79
+ "failed",
80
+ "successful"
81
+ ];
82
+ /** Rows split by outcome, in the order the dock shows them; empty groups are dropped. */
83
+ function workflowActivityGroups(rows) {
84
+ return GROUP_ORDER.map((name) => ({
85
+ name,
86
+ rows: rows.filter((row) => GROUP_OF[row.tone] === name),
87
+ openByDefault: name !== "successful"
88
+ })).filter((group) => group.rows.length > 0);
89
+ }
90
+ //#endregion
91
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/workflowsStore.native.ts
92
+ const workflows = defineSessionStore({
93
+ runs: [],
94
+ focusedRun: void 0,
95
+ pendingLaunch: void 0
96
+ });
97
+ function isRecord$2(value) {
98
+ return typeof value === "object" && value !== null && !Array.isArray(value);
99
+ }
100
+ /** Points the workflows tab at one run; the activity dock uses this before opening the tab. */
101
+ function focusRun(sessionId, identity) {
102
+ workflows.update(sessionId, (current) => ({
103
+ ...current,
104
+ focusedRun: identity
105
+ }));
106
+ }
107
+ /** Removes a deleted run immediately while the registry channel catches up. */
108
+ function removeRun(sessionId, identity) {
109
+ workflows.update(sessionId, (current) => {
110
+ const runs = current.runs.filter((run) => workflowRunIdentity(run) !== identity);
111
+ return {
112
+ ...current,
113
+ runs,
114
+ focusedRun: current.focusedRun === identity ? runs[0] === void 0 ? void 0 : workflowRunIdentity(runs[0]) : current.focusedRun
115
+ };
116
+ });
117
+ }
118
+ /**
119
+ * Sends a launch line to the session and remembers what was already running.
120
+ *
121
+ * The cockpit's only channel to a session is a prompt frame, so a launch is
122
+ * the verb the extension registers for exactly this; see web/launchLine.ts.
123
+ */
124
+ function requestLaunch(send, sessionId, line, workflowPath) {
125
+ send(sessionId, {
126
+ type: "prompt",
127
+ message: line
128
+ });
129
+ workflows.update(sessionId, (current) => ({
130
+ ...current,
131
+ pendingLaunch: {
132
+ workflowPath,
133
+ knownRuns: current.runs.map(workflowRunIdentity)
134
+ }
135
+ }));
136
+ }
137
+ /** The plugin's session data channel: 'workflow_runs' payloads into the store. */
138
+ const workflowRunsChannel = workflows.channel({
139
+ channel: WORKFLOW_RUNS_TYPE,
140
+ parse(input) {
141
+ if (!isRecord$2(input) || !Array.isArray(input.runs)) return null;
142
+ return { runs: input.runs.filter(isRecord$2) };
143
+ },
144
+ reduce: (current, { runs }) => {
145
+ const pending = current.pendingLaunch;
146
+ if (pending === void 0) return {
147
+ ...current,
148
+ runs
149
+ };
150
+ const known = new Set(pending.knownRuns);
151
+ const arrived = runs.find((run) => !known.has(workflowRunIdentity(run)));
152
+ if (arrived === void 0) return {
153
+ ...current,
154
+ runs
155
+ };
156
+ return {
157
+ ...current,
158
+ runs,
159
+ focusedRun: workflowRunIdentity(arrived),
160
+ pendingLaunch: void 0
161
+ };
162
+ }
163
+ });
164
+ //#endregion
165
+ //#region src/extensions/workspaces/sessions/(frontend)/activity-group/workflows.native.ts
166
+ var workflows_native_default = defineActivityGroup({
167
+ keys: "w r",
168
+ icon: () => createElement(WorkflowIcon),
169
+ widgetKeys: ["workflow-mcp-progress", "workflow-mcp-follow"],
170
+ order: 30,
171
+ activeSource: {
172
+ subscribe(listener) {
173
+ const subscription = workflows.store.subscribe(listener);
174
+ return () => subscription.unsubscribe();
175
+ },
176
+ isActive(sessionId) {
177
+ return workflows.select(workflows.store.state, sessionId).runs.some((run) => run.stage === "running");
178
+ }
179
+ }
180
+ });
181
+ //#endregion
182
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/catalogStore.native.ts
183
+ const catalog = defineSessionStore({
184
+ cwd: "",
185
+ workflows: [],
186
+ warning: void 0,
187
+ open: false,
188
+ filter: "",
189
+ prompt: "",
190
+ selected: void 0,
191
+ inspected: void 0,
192
+ launch: void 0
193
+ });
194
+ function isRecord$1(value) {
195
+ return typeof value === "object" && value !== null && !Array.isArray(value);
196
+ }
197
+ function openCatalog(sessionId, prompt = "") {
198
+ catalog.update(sessionId, (current) => ({
199
+ ...current,
200
+ open: true,
201
+ prompt
202
+ }));
203
+ }
204
+ function closeCatalog(sessionId) {
205
+ catalog.update(sessionId, (current) => ({
206
+ ...current,
207
+ open: false,
208
+ prompt: "",
209
+ launch: void 0
210
+ }));
211
+ }
212
+ /** Rows whose name, path, tag or job matches the filter, case-insensitively. */
213
+ function filterCatalog(workflows, filter) {
214
+ const needle = filter.trim().toLowerCase();
215
+ if (needle === "") return [...workflows];
216
+ return workflows.filter((workflow) => [
217
+ workflow.name,
218
+ workflow.relativePath,
219
+ workflow.description,
220
+ ...workflow.tags,
221
+ ...workflow.jobs.map((job) => job.name)
222
+ ].join("\n").toLowerCase().includes(needle));
223
+ }
224
+ /** The plugin's catalog channel: 'workflow_catalog' payloads into the store. */
225
+ const workflowCatalogChannel = catalog.channel({
226
+ channel: WORKFLOW_CATALOG_TYPE,
227
+ parse(input) {
228
+ if (!isRecord$1(input) || typeof input.cwd !== "string" || !Array.isArray(input.workflows)) return null;
229
+ return {
230
+ cwd: input.cwd,
231
+ workflows: input.workflows.filter(isRecord$1),
232
+ ...typeof input.warning === "string" ? { warning: input.warning } : {}
233
+ };
234
+ },
235
+ reduce(current, payload) {
236
+ const paths = new Set(payload.workflows.map((workflow) => workflow.path));
237
+ return {
238
+ ...current,
239
+ cwd: payload.cwd,
240
+ workflows: payload.workflows,
241
+ warning: payload.warning,
242
+ selected: current.selected !== void 0 && paths.has(current.selected) ? current.selected : void 0,
243
+ inspected: current.inspected !== void 0 && paths.has(current.inspected) ? current.inspected : void 0,
244
+ launch: current.launch !== void 0 && paths.has(current.launch) ? current.launch : void 0
245
+ };
246
+ }
247
+ });
248
+ //#endregion
249
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/workflow-catalog.native.ts
250
+ var workflow_catalog_native_default = defineChannelFile(workflowCatalogChannel);
251
+ //#endregion
252
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/workflow-runs.native.ts
253
+ var workflow_runs_native_default = defineChannelFile(workflowRunsChannel);
254
+ //#endregion
255
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/launchLine.ts
256
+ /**
257
+ * The `/workflow-launch` line the dialog sends.
258
+ *
259
+ * WHY THE BUILDER LIVES HERE AND THE PARSER DOES NOT:
260
+ * Cockpit code is compiled into the host bundle and may import only this
261
+ * package's own web and type modules, so it cannot share the session's parser.
262
+ * The two are pinned together by a test that parses what this writes, which is
263
+ * the only guarantee that matters: the line has to survive the trip.
264
+ */
265
+ const WORKFLOW_LAUNCH_VERB = "/workflow-launch";
266
+ /** Reserved keys; every other pair is one of the workflow's own inputs. */
267
+ const RUNNER_KEY = "runner";
268
+ const COMMAND_KEY = "command";
269
+ const CHOICE_KEY = "choice";
270
+ /** Quotes a value that carries whitespace, which is how a value survives tokenizing. */
271
+ function quote(value) {
272
+ return /\s/.test(value) ? `"${value}"` : value;
273
+ }
274
+ function reservedPair(key, value) {
275
+ return value === void 0 || value === "" ? [] : [`${key}=${quote(value)}`];
276
+ }
277
+ function workflowLaunchLine(request) {
278
+ const pairs = [
279
+ ...reservedPair(RUNNER_KEY, request.runner),
280
+ ...reservedPair(COMMAND_KEY, request.command),
281
+ ...reservedPair(CHOICE_KEY, request.choice),
282
+ ...Object.entries(request.inputs).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${quote(value)}`)
283
+ ];
284
+ const prompt = request.prompt?.trim();
285
+ return [
286
+ WORKFLOW_LAUNCH_VERB,
287
+ quote(request.workflow),
288
+ ...pairs,
289
+ ...prompt ? [prompt] : []
290
+ ].join(" ");
291
+ }
292
+ /** The trigger that makes a prompt mandatory. */
293
+ const USER_PROMPT_TRIGGER = "user_prompt";
294
+ /** What the dialog will not let a reader send, worded the way the session would answer. */
295
+ function launchProblems(workflow, request) {
296
+ const problems = [];
297
+ if (workflow.triggers.includes(USER_PROMPT_TRIGGER) && (request.prompt ?? "").trim() === "") problems.push("This workflow is triggered by a prompt, so it needs one.");
298
+ const missing = workflow.inputs.filter((input) => input.required === true && (request.inputs[input.name] ?? "") === "").map((input) => input.name);
299
+ if (missing.length > 0) problems.push(`Missing required input${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}.`);
300
+ return problems;
301
+ }
302
+ /** The default a field starts with: what the workflow declared, or empty. */
303
+ function initialInputs(workflow) {
304
+ return Object.fromEntries(workflow.inputs.map((input) => [input.name, input.default ?? ""]));
305
+ }
306
+ /** The runner a launch starts on: the workflow's first declared one, when it declares any. */
307
+ function initialRunner(workflow) {
308
+ return workflow.runners?.[0];
309
+ }
310
+ /** The command templated steps start on: the one their steps list first. */
311
+ function initialCommand(workflow) {
312
+ return workflow.commands?.[0];
313
+ }
314
+ /** The choice a launch starts on: `default` when declared, else the first. */
315
+ function initialChoice(workflow) {
316
+ const choices = workflow.choices ?? [];
317
+ return choices.includes("default") ? "default" : choices[0];
318
+ }
319
+ //#endregion
320
+ //#region src/types/apiRoutes.ts
321
+ /**
322
+ * This package's routes, as data.
323
+ *
324
+ * No scope and no base path. The build reads both off
325
+ * `src/extensions/(backend)/api/workflow/`, so the mount is stated once, by the
326
+ * folder that creates it. The same API answers at all three scopes, because a
327
+ * global contribution cascades into workspace and session; the folder under
328
+ * `workspaces/sessions/` only re-mounts it for a session's own server.
329
+ *
330
+ * This is the one file the routes, the Hono app and the browser all read, so a
331
+ * path cannot move on one side alone. The page used to build the hub's own
332
+ * absolute URL and then rewrite its plugin prefix with String.replace to reach
333
+ * a session, which is exactly the kind of copy that can move by one character
334
+ * without anything noticing.
335
+ *
336
+ * `:workspace` and `:runKey` are Hono's own parameter syntax, because the app
337
+ * registers `path` verbatim.
338
+ */
339
+ /** One run, which every route below addresses. */
340
+ const RUN = "/runs/:workspace/:runKey";
341
+ /**
342
+ * The artifact route's parameter, as Hono spells a multi-segment match.
343
+ *
344
+ * An artifact is named by its path inside the run directory, so `reports/a.md`
345
+ * is one parameter across two segments. The client's `params` substitution
346
+ * fills a single segment and percent-encodes the slash with it, so the page
347
+ * substitutes this one itself; see the page's `_lib/terminalApi.ts`.
348
+ */
349
+ const ARTIFACT_NAME_PARAM = ":name{.+}";
350
+ /**
351
+ * The step whose pane a terminal route reads: a step ref's id. Absent, the
352
+ * route follows the run's current step, which is one of a parallel group's.
353
+ */
354
+ const STEP_TARGET_PARAM = "step";
355
+ const ARTIFACT_DOWNLOAD_PARAM = "download";
356
+ const api = createApiClient({
357
+ /**
358
+ * The terminal, as server-sent events.
359
+ *
360
+ * `stream: true` gets a URL and no call method: the consumer is the browser's
361
+ * own `EventSource`, and a relayed call would buffer a stream that never ends.
362
+ */
363
+ snapshot: {
364
+ method: "GET",
365
+ path: `${RUN}/screen`,
366
+ query: [STEP_TARGET_PARAM],
367
+ response: void 0
368
+ },
369
+ screen: {
370
+ method: "GET",
371
+ path: `${RUN}/screen/stream`,
372
+ query: [STEP_TARGET_PARAM],
373
+ stream: true
374
+ },
375
+ control: {
376
+ method: "POST",
377
+ path: `${RUN}/control`,
378
+ response: void 0
379
+ },
380
+ keys: {
381
+ method: "POST",
382
+ path: `${RUN}/keys`
383
+ },
384
+ resize: {
385
+ method: "POST",
386
+ path: `${RUN}/resize`,
387
+ response: void 0
388
+ },
389
+ remove: {
390
+ method: "DELETE",
391
+ path: RUN,
392
+ response: void 0
393
+ },
394
+ stop: {
395
+ method: "POST",
396
+ path: `${RUN}/stop`,
397
+ response: void 0
398
+ },
399
+ /** Guidance for the agent session the running step is in, like steering a Team sub-agent. */
400
+ steer: {
401
+ method: "POST",
402
+ path: `${RUN}/steer`,
403
+ response: void 0
404
+ },
405
+ /**
406
+ * Launch a workflow into the mounting session's own runtime, which then owns
407
+ * the run. A dispatcher session reaches its root's session mount through this.
408
+ */
409
+ launch: {
410
+ method: "POST",
411
+ path: "/launch",
412
+ response: void 0
413
+ },
414
+ openRunDirectory: {
415
+ method: "POST",
416
+ path: `${RUN}/open-directory`,
417
+ response: void 0
418
+ },
419
+ artifacts: {
420
+ method: "GET",
421
+ path: `${RUN}/artifacts`,
422
+ query: ["directory"],
423
+ response: void 0
424
+ },
425
+ /** One artifact: its metadata and text, or its raw bytes under `raw=1`. */
426
+ artifact: {
427
+ method: "GET",
428
+ path: `${RUN}/artifacts/${ARTIFACT_NAME_PARAM}`,
429
+ query: ["raw", ARTIFACT_DOWNLOAD_PARAM],
430
+ response: void 0
431
+ }
432
+ }, {
433
+ scopes: [
434
+ "global",
435
+ "workspace",
436
+ "session"
437
+ ],
438
+ basePath: "workflow",
439
+ transport: nativeApiTransport,
440
+ sessionAddress: sessionApiAddress
441
+ });
442
+ //#endregion
443
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/terminalApi.native.ts
444
+ /**
445
+ * The page's half of this package's hub API: one run's terminal and the files
446
+ * its run directory holds.
447
+ *
448
+ * These are adapters now, not a transport. The generated client owns the URL
449
+ * and the sealed transport with it, so nothing here spells a mount. What stays
450
+ * is this package's own vocabulary: which refusals the reader is shown, and the
451
+ * shapes the terminal expects back.
452
+ *
453
+ * A run belongs to its multiplexer rather than to a session, so the same API
454
+ * answers at the hub and inside a session's own server. Passing a session id
455
+ * addresses that session's copy and passing none addresses the hub. The deleted
456
+ * builder made that branch the other way round: it always built the hub's
457
+ * absolute URL, then rewrote the plugin prefix into the session's with
458
+ * String.replace, so a session's URL was a hub URL with its head swapped.
459
+ */
460
+ const UNREACHABLE = "The cockpit hub is unreachable.";
461
+ /** The hub, or one session's own copy of it; a null session id is the hub. */
462
+ function scoped(sessionId) {
463
+ return sessionId == null ? api.global : api.session(sessionId);
464
+ }
465
+ /** The run every route addresses, as the route table's two path parameters. */
466
+ function runParams(workspace, runKey) {
467
+ return {
468
+ workspace,
469
+ runKey
470
+ };
471
+ }
472
+ function isRecord(value) {
473
+ return typeof value === "object" && value !== null && !Array.isArray(value);
474
+ }
475
+ /**
476
+ * The message a reader is shown: the route's own words, or why there were none.
477
+ *
478
+ * `error` is optional because an answered call that simply carried the wrong
479
+ * shape reaches here too, and that result has no error field at all.
480
+ */
481
+ function messageOf(result, fallback) {
482
+ if (result.status === 0) return UNREACHABLE;
483
+ return result.error === void 0 || result.error === "" ? fallback : result.error;
484
+ }
485
+ /**
486
+ * One artifact's URL, with its path filled in.
487
+ *
488
+ * An artifact is named by its path inside the run directory, which Hono matches
489
+ * as the one multi-segment parameter `:name{.+}`. The client's own `params`
490
+ * substitution fills a single segment and percent-encodes the separating slash
491
+ * with it, which would move every nested artifact's URL, so that one segment is
492
+ * substituted here instead: each segment encoded, the slashes between them
493
+ * kept. Everything left of it still comes from the client.
494
+ */
495
+ function artifactUrl(scope, workspace, runKey, artifactPath, query) {
496
+ return scope.artifact.url({
497
+ params: runParams(workspace, runKey),
498
+ ...query === void 0 ? {} : { query }
499
+ }).replace(ARTIFACT_NAME_PARAM, artifactPath.split("/").map(encodeURIComponent).join("/"));
500
+ }
501
+ /** The pane a terminal call names, when it reads one step's rather than the run's current one. */
502
+ function stepBody(step) {
503
+ return step === void 0 ? {} : { step };
504
+ }
505
+ /** One finite screen read over the session-bound sealed transport. */
506
+ async function readScreen(workspace, runKey, sessionId, step, signal) {
507
+ const response = await nativeApiTransport(scoped(sessionId).snapshot.url({
508
+ params: runParams(workspace, runKey),
509
+ query: { [STEP_TARGET_PARAM]: step }
510
+ }), { signal });
511
+ const body = await response.json();
512
+ if (!response.ok) throw new Error(isRecord(body) && typeof body.error === "string" ? body.error : "The screen could not be read.");
513
+ if (!isRecord(body) || !Array.isArray(body.lines) || body.lines.length > 48 || !body.lines.every((line) => typeof line === "string" && line.length <= 4096) || !isRecord(body.capabilities) || typeof body.capabilities.readable !== "boolean" || typeof body.capabilities.writable !== "boolean" || typeof body.capabilities.resizable !== "boolean") throw new Error("The workflow hub returned an invalid screen.");
514
+ return body;
515
+ }
516
+ /** Takes the keyboard for one run, renewing when this page already holds it. */
517
+ async function takeControl(workspace, runKey, token, sessionId, step) {
518
+ const result = await scoped(sessionId).control({
519
+ params: runParams(workspace, runKey),
520
+ body: {
521
+ token,
522
+ ...stepBody(step)
523
+ }
524
+ });
525
+ if (result.status === 0) return {
526
+ held: false,
527
+ reason: UNREACHABLE
528
+ };
529
+ if (isRecord(result.data) && typeof result.data.held === "boolean") return result.data;
530
+ return {
531
+ held: false,
532
+ reason: messageOf(result, "The run refused the keyboard.")
533
+ };
534
+ }
535
+ async function releaseControl(workspace, runKey, token, sessionId, step) {
536
+ await scoped(sessionId).control({
537
+ params: runParams(workspace, runKey),
538
+ body: {
539
+ token,
540
+ release: true,
541
+ ...stepBody(step)
542
+ }
543
+ });
544
+ }
545
+ /** Sends literal keystrokes; the reason comes back when the lease has moved on. */
546
+ async function sendKeys(workspace, runKey, token, data, sessionId, step) {
547
+ const result = await scoped(sessionId).keys({
548
+ params: runParams(workspace, runKey),
549
+ body: {
550
+ token,
551
+ data,
552
+ ...stepBody(step)
553
+ }
554
+ });
555
+ if (result.ok) return {};
556
+ return { error: messageOf(result, "The run would not take those keys.") };
557
+ }
558
+ /** Matches the run's terminal to the viewport of whoever holds the keyboard. */
559
+ async function resizeRun(workspace, runKey, token, columns, rows, sessionId, step) {
560
+ await scoped(sessionId).resize({
561
+ params: runParams(workspace, runKey),
562
+ body: {
563
+ token,
564
+ columns,
565
+ rows,
566
+ ...stepBody(step)
567
+ }
568
+ });
569
+ }
570
+ /** Permanently removes one settled run and its run directory. */
571
+ async function deleteWorkflowRun(workspace, runKey, sessionId) {
572
+ const result = await scoped(sessionId).remove({ params: runParams(workspace, runKey) });
573
+ if (!result.ok) return { error: messageOf(result, "The workflow could not be deleted.") };
574
+ if (result.data.deleted === true) return { result: { deleted: true } };
575
+ return { error: "The workflow hub returned an invalid deletion response." };
576
+ }
577
+ /** Asks the engine running a workflow to stop it; the run settles on its own shortly after. */
578
+ async function stopWorkflowRun(workspace, runKey, sessionId) {
579
+ const result = await scoped(sessionId).stop({ params: runParams(workspace, runKey) });
580
+ if (!result.ok) return { error: messageOf(result, "The workflow could not be stopped.") };
581
+ if (result.data.requested === true) return { result: { requested: true } };
582
+ return { error: "The workflow hub returned an invalid stop response." };
583
+ }
584
+ /**
585
+ * Sends guidance to the agent session a running step is in: the run's current
586
+ * step, or `step`, the session of one step of a parallel group.
587
+ */
588
+ async function steerStep(workspace, runKey, message, sessionId, step) {
589
+ const result = await scoped(sessionId).steer({
590
+ params: runParams(workspace, runKey),
591
+ body: step === void 0 ? { message } : {
592
+ message,
593
+ step
594
+ }
595
+ });
596
+ if (!result.ok) return { error: messageOf(result, "The step would not take that guidance.") };
597
+ if (result.data.delivered === true) return { result: { delivered: true } };
598
+ return { error: "The workflow hub returned an invalid steer response." };
599
+ }
600
+ async function fetchArtifacts(workspace, runKey, sessionId, directory) {
601
+ const result = await scoped(sessionId).artifacts({
602
+ params: runParams(workspace, runKey),
603
+ query: { directory }
604
+ });
605
+ if (!result.ok) return { error: messageOf(result, "This run has no directory to read.") };
606
+ return { artifacts: result.data };
607
+ }
608
+ /** Trusted same-origin URL for browser-native media previews and downloads. */
609
+ function artifactContentUrl(workspace, runKey, path, download = false, sessionId) {
610
+ return artifactUrl(scoped(sessionId), workspace, runKey, path, {
611
+ ["raw"]: 1,
612
+ ...download ? { [ARTIFACT_DOWNLOAD_PARAM]: 1 } : {}
613
+ });
614
+ }
615
+ /**
616
+ * One artifact's metadata and text.
617
+ *
618
+ * The only route here that addresses itself rather than being called: its path
619
+ * parameter spans segments, so the URL comes from `artifactUrl` and the request
620
+ * goes over the same sealed transport the client is built on.
621
+ */
622
+ async function fetchArtifact(workspace, runKey, path, sessionId) {
623
+ try {
624
+ const response = await nativeApiTransport(artifactUrl(scoped(sessionId), workspace, runKey, path));
625
+ const body = await response.json();
626
+ if (!response.ok) {
627
+ const error = isRecord(body) && typeof body.error === "string" && body.error !== "" ? body.error : "";
628
+ return { error: error === "" ? "That file has not been written." : error };
629
+ }
630
+ return { artifact: body };
631
+ } catch {
632
+ return { error: UNREACHABLE };
633
+ }
634
+ }
635
+ const MAX_MEDIA_BYTES = 5242880;
636
+ const MAX_FRAME_BYTES = 65536;
637
+ /** Drain each UTF-8 snapshot before asking the server for another screen. */
638
+ function finiteScreenReader(load) {
639
+ let bytes = /* @__PURE__ */ new Uint8Array(0);
640
+ let cursor = 0;
641
+ let generation = 0;
642
+ let frame;
643
+ return async (from, previous, signal) => {
644
+ if (signal.aborted) throw new Error("Preview cancelled.");
645
+ let reset = previous !== String(generation) || from !== cursor;
646
+ if (!frame || cursor >= bytes.length || reset) {
647
+ const next = await load(signal);
648
+ if (signal.aborted) throw new Error("Preview cancelled.");
649
+ frame = next;
650
+ bytes = utf8(next.lines.join("\r\n"));
651
+ cursor = 0;
652
+ generation += 1;
653
+ reset = true;
654
+ }
655
+ const chunk = bytes.subarray(cursor, cursor + MAX_FRAME_BYTES);
656
+ cursor += chunk.length;
657
+ return {
658
+ chunk: base64(chunk),
659
+ offset: cursor,
660
+ reset,
661
+ generation: String(generation),
662
+ exists: frame.capabilities.readable,
663
+ hasMore: cursor < bytes.length,
664
+ ended: frame.ended === true
665
+ };
666
+ };
667
+ }
668
+ /** Raw media uses the same scoped sealed route as artifact metadata. */
669
+ async function fetchArtifactMedia(workspace, runKey, path, sessionId, signal) {
670
+ if (signal.aborted) throw new Error("Preview cancelled.");
671
+ const response = await nativeApiTransport(artifactContentUrl(workspace, runKey, path, false, sessionId), { signal });
672
+ if (signal.aborted) throw new Error("Preview cancelled.");
673
+ if (!response.ok) throw new Error("That artifact could not be read.");
674
+ const mimeType = (response.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
675
+ if (!/^(application\/pdf|image\/(png|jpeg|gif|webp|bmp)|audio\/(mpeg|mp4|wav|x-wav|ogg|webm|aac|flac)|video\/(mp4|webm|quicktime|ogg))$/.test(mimeType)) throw new Error("This artifact type cannot be previewed.");
676
+ if (Number(response.headers.get("content-length")) > MAX_MEDIA_BYTES) throw new Error("This artifact exceeds the 5 MiB preview limit.");
677
+ const bytes = await response.arrayBuffer();
678
+ if (signal.aborted) throw new Error("Preview cancelled.");
679
+ if (bytes.byteLength > MAX_MEDIA_BYTES) throw new Error("This artifact exceeds the 5 MiB preview limit.");
680
+ return {
681
+ data: base64(new Uint8Array(bytes)),
682
+ mimeType
683
+ };
684
+ }
685
+ function base64(bytes) {
686
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
687
+ const parts = [];
688
+ let chunk = "";
689
+ for (let offset = 0; offset < bytes.length; offset += 3) {
690
+ const first = bytes[offset];
691
+ const second = bytes[offset + 1];
692
+ const third = bytes[offset + 2];
693
+ chunk += alphabet[first >> 2] + alphabet[(first & 3) << 4 | (second ?? 0) >> 4] + (second === void 0 ? "=" : alphabet[(second & 15) << 2 | (third ?? 0) >> 6]) + (third === void 0 ? "=" : alphabet[third & 63]);
694
+ if (chunk.length >= 8192) {
695
+ parts.push(chunk);
696
+ chunk = "";
697
+ }
698
+ }
699
+ parts.push(chunk);
700
+ return parts.join("");
701
+ }
702
+ function utf8(text) {
703
+ const bytes = [];
704
+ for (const character of text) {
705
+ let code = character.codePointAt(0);
706
+ if (code >= 55296 && code <= 57343) code = 65533;
707
+ if (code < 128) bytes.push(code);
708
+ else if (code < 2048) bytes.push(192 | code >> 6, 128 | code & 63);
709
+ else if (code < 65536) bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
710
+ else bytes.push(240 | code >> 18, 128 | code >> 12 & 63, 128 | code >> 6 & 63, 128 | code & 63);
711
+ }
712
+ return Uint8Array.from(bytes);
713
+ }
714
+ /** Binds a save to this package's guarded sealed route and one file version. */
715
+ async function prepareArtifactSave(workspace, runKey, path, sessionId, signal) {
716
+ const load = async (headers, signal) => {
717
+ if (signal.aborted) throw new Error("Download cancelled.");
718
+ return nativeApiTransport(artifactContentUrl(workspace, runKey, path, true, sessionId), {
719
+ headers,
720
+ signal
721
+ });
722
+ };
723
+ const probe = await load({ Range: "bytes=0-0" }, signal);
724
+ if (signal.aborted) throw new Error("Download cancelled.");
725
+ const range = /^bytes 0-0\/(\d+)$/u.exec(probe.headers.get("content-range") ?? "");
726
+ const empty = probe.status === 416 && probe.headers.get("content-range") === "bytes */0" || probe.status === 200 && probe.headers.get("content-length") === "0";
727
+ if (!empty && (probe.status !== 206 || !range)) throw new Error("The file does not support bounded downloads.");
728
+ const size = empty ? 0 : Number(range[1]);
729
+ if (!Number.isSafeInteger(size) || size < 0) throw new Error("Invalid file size.");
730
+ if ((await probe.arrayBuffer()).byteLength !== (empty ? 0 : 1)) throw new Error("Invalid file response.");
731
+ const version = probe.headers.get("etag");
732
+ if (!version) throw new Error("The file has no version validator.");
733
+ return {
734
+ name: path.split(/[\\/]/u).at(-1) ?? "",
735
+ mimeType: probe.headers.get("content-type")?.split(";")[0]?.trim() || "application/octet-stream",
736
+ size,
737
+ signal,
738
+ read: async (offset, count, signal) => {
739
+ if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(count) || offset < 0 || count < 1 || count > 1048576 || offset + count > size) throw new Error("Invalid download range.");
740
+ const end = offset + count - 1;
741
+ const response = await load({
742
+ Range: `bytes=${offset}-${end}`,
743
+ "If-Match": version
744
+ }, signal);
745
+ if (signal.aborted) throw new Error("Download cancelled.");
746
+ if (response.status !== 206 || response.headers.get("etag") !== version || response.headers.get("content-range") !== `bytes ${offset}-${end}/${size}`) throw new Error("The file changed or its range response is invalid.");
747
+ const bytes = new Uint8Array(await response.arrayBuffer());
748
+ if (signal.aborted) throw new Error("Download cancelled.");
749
+ if (bytes.byteLength !== count) throw new Error("The file response is incomplete.");
750
+ return bytes;
751
+ }
752
+ };
753
+ }
754
+ //#endregion
755
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/WorkflowPanel.native.tsx
756
+ function useWorkflows(sessionId) {
757
+ const subscribe = useCallback((listener) => {
758
+ const sub = workflows.store.subscribe(listener);
759
+ return () => sub.unsubscribe();
760
+ }, []);
761
+ return useSyncExternalStore(subscribe, () => workflows.select(workflows.store.state, sessionId));
762
+ }
763
+ function workflowTab(run) {
764
+ const identity = workflowRunIdentity(run);
765
+ return {
766
+ id: `workflows-run-${run.workspace}-${run.runKey}`.replace(/[^\w-]+/g, "-"),
767
+ label: run.displayName,
768
+ retainComposer: true,
769
+ panel: (props) => /* @__PURE__ */ jsx(WorkflowPanel, {
770
+ ...props,
771
+ identity
772
+ })
773
+ };
774
+ }
775
+ function WorkflowLauncher(props) {
776
+ const subscribe = useCallback((listener) => {
777
+ const sub = catalog.store.subscribe(listener);
778
+ return () => sub.unsubscribe();
779
+ }, []);
780
+ const state = useSyncExternalStore(subscribe, () => catalog.select(catalog.store.state, props.sessionId));
781
+ const [filter, setFilter] = useState("");
782
+ const [selected, setSelected] = useState();
783
+ if (props.sessionId === null || !state.open) return null;
784
+ const workflow = state.workflows.find((item) => item.path === selected);
785
+ return /* @__PURE__ */ jsx(Sheet, {
786
+ open: true,
787
+ onOpenChange: (open) => {
788
+ if (!open) {
789
+ closeCatalog(props.sessionId);
790
+ setSelected(void 0);
791
+ }
792
+ },
793
+ children: /* @__PURE__ */ jsxs(SheetContent, { children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: workflow ? `Launch ${workflow.name}` : "Launch a workflow" }) }), /* @__PURE__ */ jsx(SheetBody, { children: workflow ? /* @__PURE__ */ jsx(WorkflowLaunch, {
794
+ ...props,
795
+ sessionId: props.sessionId,
796
+ workflow,
797
+ initialPrompt: state.prompt,
798
+ onClose: () => {
799
+ closeCatalog(props.sessionId);
800
+ setSelected(void 0);
801
+ }
802
+ }, workflow.path) : /* @__PURE__ */ jsxs(View, {
803
+ style: styles.body,
804
+ children: [
805
+ /* @__PURE__ */ jsx(Input, {
806
+ accessibilityLabel: "Find a workflow",
807
+ value: filter,
808
+ onChangeText: setFilter,
809
+ placeholder: "Find a workflow"
810
+ }),
811
+ state.warning ? /* @__PURE__ */ jsx(Text, {
812
+ style: styles.error,
813
+ children: state.warning
814
+ }) : null,
815
+ filterCatalog(state.workflows, filter).map((item) => /* @__PURE__ */ jsx(ListRow, {
816
+ title: /* @__PURE__ */ jsx(Button, {
817
+ variant: "ghost",
818
+ onPress: () => setSelected(item.path),
819
+ children: item.name || item.relativePath
820
+ }),
821
+ description: `${item.description}\n${item.tags.join(", ")} · ${item.jobs.map((job) => job.name).join(", ")}`
822
+ }, item.path)),
823
+ state.workflows.length === 0 ? /* @__PURE__ */ jsx(Text, {
824
+ style: styles.meta,
825
+ children: "No workflows reported yet."
826
+ }) : null
827
+ ]
828
+ }) })] })
829
+ });
830
+ }
831
+ function Choice({ label, options, value, onChange }) {
832
+ return /* @__PURE__ */ jsxs(View, { children: [/* @__PURE__ */ jsx(Text, {
833
+ style: styles.meta,
834
+ children: label
835
+ }), options.map((option) => /* @__PURE__ */ jsx(Button, {
836
+ variant: "outline",
837
+ accessibilityState: { selected: value === option },
838
+ onPress: () => onChange(option),
839
+ children: option
840
+ }, option))] });
841
+ }
842
+ function WorkflowLaunch({ sessionId, workflow, initialPrompt, sendSessionFrame, onClose }) {
843
+ const [prompt, setPrompt] = useState(initialPrompt);
844
+ const [inputs, setInputs] = useState(() => initialInputs(workflow));
845
+ const [runner, setRunner] = useState(() => initialRunner(workflow));
846
+ const [command, setCommand] = useState(() => initialCommand(workflow));
847
+ const [choice, setChoice] = useState(() => initialChoice(workflow));
848
+ const [error, setError] = useState();
849
+ const request = {
850
+ workflow: workflow.name || workflow.relativePath,
851
+ inputs,
852
+ runner,
853
+ command,
854
+ choice,
855
+ ...prompt.trim() === "" ? {} : { prompt: prompt.trim() }
856
+ };
857
+ const problems = launchProblems(workflow, request);
858
+ const line = workflowLaunchLine(request);
859
+ return /* @__PURE__ */ jsxs(View, {
860
+ style: styles.body,
861
+ children: [
862
+ /* @__PURE__ */ jsx(Text, {
863
+ style: styles.meta,
864
+ children: workflow.description
865
+ }),
866
+ /* @__PURE__ */ jsx(Textarea, {
867
+ accessibilityLabel: "Workflow prompt",
868
+ placeholder: "What should this workflow do?",
869
+ value: prompt,
870
+ onChangeText: setPrompt
871
+ }),
872
+ workflow.inputs.map((input) => {
873
+ const options = input.options ?? (input.type === "boolean" ? ["true", "false"] : void 0);
874
+ return /* @__PURE__ */ jsxs(View, { children: [/* @__PURE__ */ jsxs(Text, {
875
+ style: styles.meta,
876
+ children: [
877
+ input.name,
878
+ input.required ? " (required)" : "",
879
+ " · ",
880
+ input.description
881
+ ]
882
+ }), options ? /* @__PURE__ */ jsx(Choice, {
883
+ label: input.name,
884
+ options,
885
+ value: inputs[input.name],
886
+ onChange: (value) => setInputs((current) => ({
887
+ ...current,
888
+ [input.name]: value
889
+ }))
890
+ }) : /* @__PURE__ */ jsx(Input, {
891
+ accessibilityLabel: input.name,
892
+ value: inputs[input.name] ?? "",
893
+ onChangeText: (value) => setInputs((current) => ({
894
+ ...current,
895
+ [input.name]: value
896
+ }))
897
+ })] }, input.name);
898
+ }),
899
+ workflow.runners?.length ? /* @__PURE__ */ jsx(Choice, {
900
+ label: "Runner",
901
+ options: workflow.runners,
902
+ value: runner,
903
+ onChange: setRunner
904
+ }) : null,
905
+ workflow.commands?.length ? /* @__PURE__ */ jsx(Choice, {
906
+ label: "Command",
907
+ options: workflow.commands,
908
+ value: command,
909
+ onChange: setCommand
910
+ }) : null,
911
+ workflow.choices?.length ? /* @__PURE__ */ jsx(Choice, {
912
+ label: "Choice",
913
+ options: workflow.choices,
914
+ value: choice,
915
+ onChange: setChoice
916
+ }) : null,
917
+ /* @__PURE__ */ jsx(CodeBlock, { text: line }),
918
+ problems.map((problem) => /* @__PURE__ */ jsx(Text, {
919
+ style: styles.meta,
920
+ children: problem
921
+ }, problem)),
922
+ error ? /* @__PURE__ */ jsx(Text, {
923
+ style: styles.error,
924
+ children: error
925
+ }) : null,
926
+ /* @__PURE__ */ jsx(Button, {
927
+ testID: "launch-workflow-submit",
928
+ disabled: problems.length > 0,
929
+ onPress: () => {
930
+ try {
931
+ requestLaunch(sendSessionFrame, sessionId, line, workflow.path);
932
+ onClose();
933
+ } catch (cause) {
934
+ setError(cause instanceof Error ? cause.message : String(cause));
935
+ }
936
+ },
937
+ children: "Launch"
938
+ })
939
+ ]
940
+ });
941
+ }
942
+ function WorkflowPanel(props) {
943
+ const state = useWorkflows(props.sessionId);
944
+ const run = state.runs.find((item) => workflowRunIdentity(item) === props.identity);
945
+ const [confirmDelete, setConfirmDelete] = useState(false);
946
+ const [busy, setBusy] = useState(false);
947
+ const [error, setError] = useState();
948
+ const act = async (action) => {
949
+ if (busy) return;
950
+ setBusy(true);
951
+ setError(void 0);
952
+ try {
953
+ await action();
954
+ } catch (cause) {
955
+ setError(cause instanceof Error ? cause.message : String(cause));
956
+ } finally {
957
+ setBusy(false);
958
+ }
959
+ };
960
+ if (!run) return /* @__PURE__ */ jsx(Text, {
961
+ style: styles.meta,
962
+ children: "This run is no longer listed."
963
+ });
964
+ return /* @__PURE__ */ jsxs(ScrollView, {
965
+ contentContainerStyle: styles.body,
966
+ children: [
967
+ /* @__PURE__ */ jsx(Text, {
968
+ style: styles.title,
969
+ children: run.displayName
970
+ }),
971
+ /* @__PURE__ */ jsx(Badge, { children: run.executionState ?? run.outcome ?? run.stage }),
972
+ /* @__PURE__ */ jsx(Text, {
973
+ style: styles.meta,
974
+ children: run.errorMessage ?? `${run.position?.job ?? ""} ${run.position?.step ?? ""}`
975
+ }),
976
+ state.runs.length > 1 ? state.runs.map((item) => /* @__PURE__ */ jsx(Button, {
977
+ variant: "ghost",
978
+ onPress: () => {
979
+ if (props.sessionId !== null) focusRun(props.sessionId, workflowRunIdentity(item));
980
+ props.openTransientTab(workflowTab(item));
981
+ },
982
+ children: item.displayName
983
+ }, workflowRunIdentity(item))) : null,
984
+ run.executionState === "paused" || run.executionState === "pause_requested" ? /* @__PURE__ */ jsx(Text, {
985
+ style: styles.meta,
986
+ children: "Waiting to be resumed. Ask the agent in this conversation to resume it."
987
+ }) : null,
988
+ run.jobs.map((job) => /* @__PURE__ */ jsxs(View, { children: [/* @__PURE__ */ jsxs(Button, {
989
+ variant: "ghost",
990
+ onPress: () => props.openTransientTab(stepTab(run, job.name)),
991
+ children: [
992
+ job.name,
993
+ " · ",
994
+ job.status
995
+ ]
996
+ }), job.steps.map((step) => /* @__PURE__ */ jsx(ListRow, {
997
+ title: /* @__PURE__ */ jsx(Button, {
998
+ variant: "ghost",
999
+ onPress: () => props.openTransientTab(stepTab(run, job.name, step.name)),
1000
+ children: step.name
1001
+ }),
1002
+ description: step.status
1003
+ }, step.name))] }, job.name)),
1004
+ /* @__PURE__ */ jsx(Text, {
1005
+ style: styles.title,
1006
+ children: "Artifacts"
1007
+ }),
1008
+ /* @__PURE__ */ jsx(WorkflowArtifacts, {
1009
+ ...props,
1010
+ run
1011
+ }),
1012
+ error ? /* @__PURE__ */ jsx(Text, {
1013
+ style: styles.error,
1014
+ children: error
1015
+ }) : null,
1016
+ run.stage === "running" ? /* @__PURE__ */ jsx(Button, {
1017
+ variant: "danger-outline",
1018
+ disabled: busy || props.sessionId === null,
1019
+ onPress: () => void act(async () => {
1020
+ const result = await stopWorkflowRun(run.workspace, run.runKey, props.sessionId);
1021
+ if ("error" in result) throw new Error(result.error);
1022
+ }),
1023
+ children: "Stop workflow"
1024
+ }) : /* @__PURE__ */ jsx(Button, {
1025
+ variant: "danger-outline",
1026
+ disabled: props.sessionId === null,
1027
+ onPress: () => setConfirmDelete(true),
1028
+ children: "Delete run…"
1029
+ }),
1030
+ /* @__PURE__ */ jsx(Sheet, {
1031
+ open: confirmDelete,
1032
+ onOpenChange: setConfirmDelete,
1033
+ children: /* @__PURE__ */ jsxs(SheetContent, { children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: "Delete this run?" }) }), /* @__PURE__ */ jsxs(SheetBody, { children: [/* @__PURE__ */ jsx(Text, {
1034
+ style: styles.meta,
1035
+ children: "This permanently removes the settled run and its directory."
1036
+ }), /* @__PURE__ */ jsx(Button, {
1037
+ variant: "danger-outline",
1038
+ disabled: busy,
1039
+ onPress: () => void act(async () => {
1040
+ const result = await deleteWorkflowRun(run.workspace, run.runKey, props.sessionId);
1041
+ if ("error" in result) throw new Error(result.error);
1042
+ if (props.sessionId !== null) removeRun(props.sessionId, props.identity);
1043
+ setConfirmDelete(false);
1044
+ props.closeTransientTab(`workflows-run-${run.workspace}-${run.runKey}`.replace(/[^\w-]+/g, "-"));
1045
+ }),
1046
+ children: "Delete"
1047
+ })] })] })
1048
+ })
1049
+ ]
1050
+ });
1051
+ }
1052
+ function stepTab(run, job, step) {
1053
+ const workspace = run.workspace;
1054
+ const runKey = run.runKey;
1055
+ return {
1056
+ id: `workflows-step-${workspace}-${runKey}-${job}${step === void 0 ? "" : `-${step}`}`.replace(/[^\w-]+/g, "-"),
1057
+ label: `${run.displayName} › ${step ?? job}`,
1058
+ panel: (props) => /* @__PURE__ */ jsx(WorkflowStep, {
1059
+ ...props,
1060
+ workspace,
1061
+ runKey,
1062
+ job,
1063
+ step
1064
+ })
1065
+ };
1066
+ }
1067
+ function WorkflowStep({ sessionId, workspace, runKey, job, step, renderSessionTranscript, renderRunnerTerminal }) {
1068
+ const run = useWorkflows(sessionId).runs.find((item) => item.workspace === workspace && item.runKey === runKey);
1069
+ const steps = run?.jobs.find((item) => item.name === job)?.steps;
1070
+ const target = step === void 0 ? steps?.at(-1) : steps?.find((item) => item.name === step);
1071
+ const [message, setMessage] = useState("");
1072
+ const [error, setError] = useState();
1073
+ const [busy, setBusy] = useState(false);
1074
+ const ref = target?.ref;
1075
+ const session = ref?.kind === "session" ? ref.id : void 0;
1076
+ return /* @__PURE__ */ jsxs(View, {
1077
+ style: styles.screen,
1078
+ children: [
1079
+ /* @__PURE__ */ jsxs(Text, {
1080
+ style: styles.title,
1081
+ children: [
1082
+ job,
1083
+ step === void 0 ? "" : ` › ${step}`,
1084
+ " · ",
1085
+ target?.status
1086
+ ]
1087
+ }),
1088
+ session ? renderSessionTranscript?.(session) : /* @__PURE__ */ jsx(WorkflowTerminal, {
1089
+ sessionId,
1090
+ workspace,
1091
+ runKey,
1092
+ pane: ref?.kind === "pane" ? ref.id : void 0,
1093
+ renderRunnerTerminal
1094
+ }, `${sessionId}:${workspace}:${runKey}:${ref?.id ?? ""}`),
1095
+ session && run?.stage === "running" && target?.status === "running" ? /* @__PURE__ */ jsxs(View, {
1096
+ style: styles.body,
1097
+ children: [/* @__PURE__ */ jsx(Textarea, {
1098
+ accessibilityLabel: "Step guidance",
1099
+ value: message,
1100
+ onChangeText: setMessage
1101
+ }), /* @__PURE__ */ jsx(Button, {
1102
+ disabled: busy || message.trim() === "",
1103
+ onPress: () => {
1104
+ setBusy(true);
1105
+ steerStep(workspace, runKey, message.trim(), sessionId, session).then((result) => {
1106
+ if ("error" in result) setError(result.error);
1107
+ else {
1108
+ setMessage("");
1109
+ setError(void 0);
1110
+ }
1111
+ }).catch((cause) => setError(cause instanceof Error ? cause.message : String(cause))).finally(() => setBusy(false));
1112
+ },
1113
+ children: "Send guidance"
1114
+ })]
1115
+ }) : null,
1116
+ error ? /* @__PURE__ */ jsx(Text, {
1117
+ style: styles.error,
1118
+ children: error
1119
+ }) : null
1120
+ ]
1121
+ });
1122
+ }
1123
+ function WorkflowTerminal({ sessionId, workspace, runKey, pane, renderRunnerTerminal }) {
1124
+ const [capabilities, setCapabilities] = useState();
1125
+ const [token, setToken] = useState();
1126
+ const [notice, setNotice] = useState();
1127
+ const [columns, setColumns] = useState("80");
1128
+ const [rows, setRows] = useState("24");
1129
+ const [busy, setBusy] = useState(false);
1130
+ const [lifetime] = useState(() => new AbortController());
1131
+ useEffect(() => () => lifetime.abort(), [lifetime]);
1132
+ useEffect(() => {
1133
+ if (token === void 0) return;
1134
+ return () => {
1135
+ releaseControl(workspace, runKey, token, sessionId, pane).catch(() => {});
1136
+ };
1137
+ }, [
1138
+ workspace,
1139
+ runKey,
1140
+ token,
1141
+ sessionId,
1142
+ pane
1143
+ ]);
1144
+ const request = useMemo(() => ({
1145
+ key: `${sessionId}:${workspace}:${runKey}:${pane ?? ""}`,
1146
+ read: finiteScreenReader(async (signal) => {
1147
+ const frame = await readScreen(workspace, runKey, sessionId, pane, signal);
1148
+ if (!signal.aborted && !lifetime.signal.aborted) setCapabilities(frame.capabilities);
1149
+ return frame;
1150
+ }),
1151
+ write: async (text) => {
1152
+ if (token === void 0) throw new Error("Take control before typing into this run.");
1153
+ const result = await sendKeys(workspace, runKey, token, text, sessionId, pane);
1154
+ if (result.error) {
1155
+ if (!lifetime.signal.aborted) {
1156
+ setNotice(result.error);
1157
+ setToken(void 0);
1158
+ }
1159
+ throw new Error(result.error);
1160
+ }
1161
+ }
1162
+ }), [
1163
+ sessionId,
1164
+ workspace,
1165
+ runKey,
1166
+ pane,
1167
+ token,
1168
+ lifetime
1169
+ ]);
1170
+ const arm = async () => {
1171
+ setBusy(true);
1172
+ try {
1173
+ const grant = await takeControl(workspace, runKey, void 0, sessionId, pane);
1174
+ if (lifetime.signal.aborted) {
1175
+ if (grant.held && grant.token) await releaseControl(workspace, runKey, grant.token, sessionId, pane);
1176
+ return;
1177
+ }
1178
+ if (grant.held && grant.token) {
1179
+ setToken(grant.token);
1180
+ setNotice(void 0);
1181
+ } else setNotice(grant.reason ?? "The keyboard is not available for this run.");
1182
+ } catch (cause) {
1183
+ if (!lifetime.signal.aborted) setNotice(cause instanceof Error ? cause.message : String(cause));
1184
+ } finally {
1185
+ if (!lifetime.signal.aborted) setBusy(false);
1186
+ }
1187
+ };
1188
+ return /* @__PURE__ */ jsxs(View, {
1189
+ style: styles.transcript,
1190
+ children: [
1191
+ /* @__PURE__ */ jsx(Badge, { children: token ? "keyboard yours" : "watching" }),
1192
+ capabilities?.writable ? /* @__PURE__ */ jsx(Button, {
1193
+ variant: "outline",
1194
+ disabled: busy,
1195
+ onPress: () => {
1196
+ if (token) setToken(void 0);
1197
+ else arm();
1198
+ },
1199
+ children: token ? "Release keyboard" : "Take control"
1200
+ }) : null,
1201
+ /* @__PURE__ */ jsx(View, {
1202
+ style: styles.transcript,
1203
+ children: renderRunnerTerminal?.(request)
1204
+ }),
1205
+ capabilities?.reason ? /* @__PURE__ */ jsx(Text, {
1206
+ style: styles.meta,
1207
+ children: capabilities.reason
1208
+ }) : null,
1209
+ token && capabilities?.resizable ? /* @__PURE__ */ jsxs(View, { children: [
1210
+ /* @__PURE__ */ jsx(Input, {
1211
+ accessibilityLabel: "Terminal columns",
1212
+ keyboardType: "number-pad",
1213
+ value: columns,
1214
+ onChangeText: setColumns
1215
+ }),
1216
+ /* @__PURE__ */ jsx(Input, {
1217
+ accessibilityLabel: "Terminal rows",
1218
+ keyboardType: "number-pad",
1219
+ value: rows,
1220
+ onChangeText: setRows
1221
+ }),
1222
+ /* @__PURE__ */ jsx(Button, {
1223
+ variant: "outline",
1224
+ disabled: !Number.isInteger(Number(columns)) || !Number.isInteger(Number(rows)) || Number(columns) < 1 || Number(rows) < 1,
1225
+ onPress: () => {
1226
+ resizeRun(workspace, runKey, token, Number(columns), Number(rows), sessionId, pane).catch((cause) => {
1227
+ if (!lifetime.signal.aborted) setNotice(cause instanceof Error ? cause.message : String(cause));
1228
+ });
1229
+ },
1230
+ children: "Resize terminal"
1231
+ })
1232
+ ] }) : null,
1233
+ notice ? /* @__PURE__ */ jsx(Text, {
1234
+ style: styles.meta,
1235
+ children: notice
1236
+ }) : null
1237
+ ]
1238
+ });
1239
+ }
1240
+ function WorkflowArtifacts(props) {
1241
+ const { run, sessionId, openTransientTab } = props;
1242
+ const [directory, setDirectory] = useState();
1243
+ const [listing, setListing] = useState();
1244
+ const [error, setError] = useState();
1245
+ useEffect(() => {
1246
+ let active = true;
1247
+ fetchArtifacts(run.workspace, run.runKey, sessionId, directory).then((result) => {
1248
+ if (active) {
1249
+ if ("error" in result) setError(result.error);
1250
+ else {
1251
+ setListing(result.artifacts);
1252
+ setError(void 0);
1253
+ }
1254
+ }
1255
+ }).catch((cause) => {
1256
+ if (active) setError(cause instanceof Error ? cause.message : String(cause));
1257
+ });
1258
+ return () => {
1259
+ active = false;
1260
+ };
1261
+ }, [
1262
+ run.workspace,
1263
+ run.runKey,
1264
+ sessionId,
1265
+ directory,
1266
+ run.stage
1267
+ ]);
1268
+ return /* @__PURE__ */ jsxs(View, { children: [
1269
+ error ? /* @__PURE__ */ jsx(Text, {
1270
+ style: styles.error,
1271
+ children: error
1272
+ }) : null,
1273
+ directory ? /* @__PURE__ */ jsx(Button, {
1274
+ variant: "ghost",
1275
+ onPress: () => setDirectory(directory.includes("/") ? directory.slice(0, directory.lastIndexOf("/")) : void 0),
1276
+ children: "Parent directory"
1277
+ }) : null,
1278
+ listing?.artifacts.map((item) => /* @__PURE__ */ jsx(ListRow, {
1279
+ title: /* @__PURE__ */ jsx(Button, {
1280
+ variant: "ghost",
1281
+ disabled: item.kind !== "directory" && item.state !== "written" && item.state !== "empty",
1282
+ onPress: () => {
1283
+ if (item.kind === "directory") setDirectory(item.path);
1284
+ else {
1285
+ const workspace = run.workspace;
1286
+ const runKey = run.runKey;
1287
+ const path = item.path;
1288
+ openTransientTab({
1289
+ id: `workflows-artifact-${workspace}-${runKey}-${path}`.replace(/[^\w-]+/g, "-"),
1290
+ label: path,
1291
+ panel: (panelProps) => /* @__PURE__ */ jsx(ArtifactPreview, {
1292
+ ...panelProps,
1293
+ workspace,
1294
+ runKey,
1295
+ path
1296
+ })
1297
+ });
1298
+ }
1299
+ },
1300
+ children: item.path
1301
+ }),
1302
+ description: `${item.state} · ${item.description}`
1303
+ }, item.path))
1304
+ ] });
1305
+ }
1306
+ function ArtifactPreview({ sessionId, workspace, runKey, path, renderFilePreview, renderMediaPreview, nativeSaveFile }) {
1307
+ const [content, setContent] = useState();
1308
+ const [error, setError] = useState();
1309
+ const [raw, setRaw] = useState(false);
1310
+ useEffect(() => {
1311
+ let active = true;
1312
+ fetchArtifact(workspace, runKey, path, sessionId).then((result) => {
1313
+ if (active) {
1314
+ if ("error" in result) setError(result.error);
1315
+ else setContent(result.artifact);
1316
+ }
1317
+ }).catch((cause) => {
1318
+ if (active) setError(cause instanceof Error ? cause.message : String(cause));
1319
+ });
1320
+ return () => {
1321
+ active = false;
1322
+ };
1323
+ }, [
1324
+ workspace,
1325
+ runKey,
1326
+ path,
1327
+ sessionId
1328
+ ]);
1329
+ const downloadController = useRef(void 0);
1330
+ const [saving, setSaving] = useState(false);
1331
+ const [saveNote, setSaveNote] = useState();
1332
+ useEffect(() => () => downloadController.current?.abort(), [
1333
+ workspace,
1334
+ runKey,
1335
+ path,
1336
+ sessionId
1337
+ ]);
1338
+ const download = async () => {
1339
+ if (!nativeSaveFile || saving) return;
1340
+ const controller = new AbortController();
1341
+ downloadController.current = controller;
1342
+ setSaving(true);
1343
+ try {
1344
+ await nativeSaveFile(await prepareArtifactSave(workspace, runKey, path, sessionId, controller.signal));
1345
+ if (!controller.signal.aborted) setSaveNote("Saved.");
1346
+ } catch (error) {
1347
+ if (!controller.signal.aborted) setSaveNote(error instanceof Error ? error.message : String(error));
1348
+ } finally {
1349
+ setSaving(false);
1350
+ }
1351
+ };
1352
+ return /* @__PURE__ */ jsxs(ScrollView, {
1353
+ contentContainerStyle: styles.body,
1354
+ children: [
1355
+ /* @__PURE__ */ jsx(Text, {
1356
+ style: styles.title,
1357
+ children: path
1358
+ }),
1359
+ /* @__PURE__ */ jsx(Button, {
1360
+ variant: "outline",
1361
+ disabled: !nativeSaveFile || saving,
1362
+ onPress: () => void download(),
1363
+ children: "Download"
1364
+ }),
1365
+ saving ? /* @__PURE__ */ jsx(Button, {
1366
+ variant: "ghost",
1367
+ onPress: () => downloadController.current?.abort(),
1368
+ children: "Cancel download"
1369
+ }) : null,
1370
+ saveNote ? /* @__PURE__ */ jsx(Text, {
1371
+ style: styles.meta,
1372
+ children: saveNote
1373
+ }) : null,
1374
+ error ? /* @__PURE__ */ jsx(Text, {
1375
+ style: styles.error,
1376
+ children: error
1377
+ }) : null,
1378
+ content?.text !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1379
+ /* @__PURE__ */ jsx(Button, {
1380
+ variant: "outline",
1381
+ onPress: () => setRaw((value) => !value),
1382
+ children: raw ? "Preview" : "Raw source"
1383
+ }),
1384
+ !raw && /\.(md|markdown)$/i.test(path) ? /* @__PURE__ */ jsx(Markdown, { text: content.text }) : !raw && /\.(html|htm)$/i.test(path) ? renderFilePreview?.({
1385
+ key: `${sessionId}:${workspace}:${runKey}:${path}`,
1386
+ html: content.text,
1387
+ title: path
1388
+ }) : !raw && /\.(csv|tsv)$/i.test(path) ? /* @__PURE__ */ jsx(DelimitedArtifact, {
1389
+ text: content.text,
1390
+ delimiter: /\.csv$/i.test(path) ? "," : " "
1391
+ }) : /* @__PURE__ */ jsx(CodeBlock, { text: !raw && /\.json$/i.test(path) ? prettyJson(content.text) : content.text }),
1392
+ content.truncated ? /* @__PURE__ */ jsx(Text, {
1393
+ style: styles.meta,
1394
+ children: "This preview was truncated."
1395
+ }) : null
1396
+ ] }) : content ? /* @__PURE__ */ jsx(BinaryArtifactPreview, {
1397
+ sessionId,
1398
+ workspace,
1399
+ runKey,
1400
+ path,
1401
+ renderMediaPreview
1402
+ }) : /* @__PURE__ */ jsx(Text, {
1403
+ style: styles.meta,
1404
+ children: "Loading preview…"
1405
+ })
1406
+ ]
1407
+ });
1408
+ }
1409
+ function BinaryArtifactPreview({ sessionId, workspace, runKey, path, renderMediaPreview }) {
1410
+ const requestKey = JSON.stringify([
1411
+ sessionId,
1412
+ workspace,
1413
+ runKey,
1414
+ path
1415
+ ]);
1416
+ const [loaded, setLoaded] = useState();
1417
+ const media = loaded?.key === requestKey ? loaded.media : void 0;
1418
+ const error = loaded?.key === requestKey ? loaded.error : void 0;
1419
+ useEffect(() => {
1420
+ const controller = new AbortController();
1421
+ fetchArtifactMedia(workspace, runKey, path, sessionId, controller.signal).then((value) => {
1422
+ if (!controller.signal.aborted) setLoaded({
1423
+ key: requestKey,
1424
+ media: value
1425
+ });
1426
+ }).catch((cause) => {
1427
+ if (!controller.signal.aborted) setLoaded({
1428
+ key: requestKey,
1429
+ error: cause instanceof Error ? cause.message : String(cause)
1430
+ });
1431
+ });
1432
+ return () => controller.abort();
1433
+ }, [
1434
+ workspace,
1435
+ runKey,
1436
+ path,
1437
+ sessionId,
1438
+ requestKey
1439
+ ]);
1440
+ return media ? renderMediaPreview?.({
1441
+ key: `${sessionId}:${workspace}:${runKey}:${path}`,
1442
+ ...media,
1443
+ title: path,
1444
+ path
1445
+ }) : /* @__PURE__ */ jsx(Text, {
1446
+ style: styles.meta,
1447
+ children: error ?? "Loading preview…"
1448
+ });
1449
+ }
1450
+ function prettyJson(text) {
1451
+ try {
1452
+ return JSON.stringify(JSON.parse(text), null, 2);
1453
+ } catch {
1454
+ return text;
1455
+ }
1456
+ }
1457
+ function DelimitedArtifact({ text, delimiter }) {
1458
+ const rows = [];
1459
+ let row = [];
1460
+ let cell = "";
1461
+ let quoted = false;
1462
+ for (let index = 0; index <= text.length && rows.length < 500; index += 1) {
1463
+ const character = text[index] ?? "\n";
1464
+ if (quoted) {
1465
+ if (character === "\"" && text[index + 1] === "\"") {
1466
+ cell += "\"";
1467
+ index += 1;
1468
+ } else if (character === "\"") quoted = false;
1469
+ else cell += character;
1470
+ } else if (character === "\"" && cell === "") quoted = true;
1471
+ else if (character === delimiter) {
1472
+ row.push(cell);
1473
+ cell = "";
1474
+ } else if (character === "\n") {
1475
+ row.push(cell.replace(/\r$/, ""));
1476
+ rows.push(row);
1477
+ row = [];
1478
+ cell = "";
1479
+ } else cell += character;
1480
+ }
1481
+ return /* @__PURE__ */ jsx(ScrollView, {
1482
+ horizontal: true,
1483
+ children: /* @__PURE__ */ jsx(View, { children: rows.map((cells, index) => /* @__PURE__ */ jsx(View, {
1484
+ style: styles.tableRow,
1485
+ children: cells.map((value, column) => /* @__PURE__ */ jsx(Text, {
1486
+ style: styles.tableCell,
1487
+ children: value
1488
+ }, column))
1489
+ }, index)) })
1490
+ });
1491
+ }
1492
+ const styles = StyleSheet.create((theme) => ({
1493
+ screen: {
1494
+ flex: 1,
1495
+ backgroundColor: theme.colors.bg,
1496
+ padding: theme.spacing(3),
1497
+ uni__dependencies: [0]
1498
+ },
1499
+ transcript: { flex: 1 },
1500
+ tableRow: { flexDirection: "row" },
1501
+ tableCell: {
1502
+ color: theme.colors.text,
1503
+ fontSize: theme.fontSize.sm,
1504
+ padding: theme.spacing(2),
1505
+ width: theme.spacing(30),
1506
+ borderWidth: 1,
1507
+ borderColor: theme.colors.border,
1508
+ uni__dependencies: [0]
1509
+ },
1510
+ body: {
1511
+ padding: theme.spacing(3),
1512
+ gap: theme.spacing(2),
1513
+ uni__dependencies: [0]
1514
+ },
1515
+ title: {
1516
+ color: theme.colors.hi,
1517
+ fontSize: theme.fontSize.base,
1518
+ fontWeight: "600",
1519
+ uni__dependencies: [0]
1520
+ },
1521
+ meta: {
1522
+ color: theme.colors.dim,
1523
+ fontSize: theme.fontSize.sm,
1524
+ uni__dependencies: [0]
1525
+ },
1526
+ error: {
1527
+ color: theme.colors.red,
1528
+ fontSize: theme.fontSize.sm,
1529
+ uni__dependencies: [0]
1530
+ }
1531
+ }));
1532
+ //#endregion
1533
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/WorkflowLauncher.overlay.native.tsx
1534
+ var WorkflowLauncher_overlay_native_default = defineFill({ component: WorkflowLauncher });
1535
+ //#endregion
1536
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/_components/WorkflowsActivitySection.native.tsx
1537
+ function WorkflowsActivitySection({ sessionId, openSession, openTransientTab }) {
1538
+ const state = useWorkflows(sessionId);
1539
+ const [now, setNow] = useState(() => Date.now());
1540
+ const [folded, setFolded] = useState({});
1541
+ useEffect(() => {
1542
+ const timer = setInterval(() => setNow(Date.now()), 1e4);
1543
+ return () => clearInterval(timer);
1544
+ }, []);
1545
+ const groups = workflowActivityGroups(workflowActivityRows(state.runs, now, sessionId));
1546
+ return /* @__PURE__ */ jsxs(View, { children: [groups.map((group) => {
1547
+ const open = folded[group.name] ?? group.openByDefault;
1548
+ return /* @__PURE__ */ jsxs(View, { children: [/* @__PURE__ */ jsxs(Button, {
1549
+ variant: "ghost",
1550
+ accessibilityState: { expanded: open },
1551
+ onPress: () => setFolded((current) => ({
1552
+ ...current,
1553
+ [group.name]: !open
1554
+ })),
1555
+ children: [
1556
+ group.name,
1557
+ " · ",
1558
+ group.rows.length
1559
+ ]
1560
+ }), open ? group.rows.map((row) => /* @__PURE__ */ jsx(ListRow, {
1561
+ leading: /* @__PURE__ */ jsx(Dot, { tone: row.tone === "failed" ? "red" : row.tone === "running" || row.tone === "paused" ? "yellow" : row.tone === "done" ? "green" : "muted" }),
1562
+ title: /* @__PURE__ */ jsx(Button, {
1563
+ variant: "ghost",
1564
+ testID: `activity-workflow-${row.runKey}`,
1565
+ onPress: () => {
1566
+ if (row.ownerSessionId && openSession) {
1567
+ openSession(row.ownerSessionId);
1568
+ return;
1569
+ }
1570
+ const run = state.runs.find((item) => `${item.workspace}/${item.runKey}` === row.identity);
1571
+ if (sessionId !== null) focusRun(sessionId, row.identity);
1572
+ if (run) openTransientTab(workflowTab(run));
1573
+ },
1574
+ children: row.name
1575
+ }),
1576
+ description: `${row.detail} · ${row.elapsed}`
1577
+ }, row.identity)) : null] }, group.name);
1578
+ }), groups.length === 0 ? /* @__PURE__ */ jsx(Button, {
1579
+ variant: "ghost",
1580
+ testID: "activity-workflow-launch",
1581
+ disabled: sessionId === null,
1582
+ onPress: () => {
1583
+ if (sessionId !== null) openCatalog(sessionId);
1584
+ },
1585
+ children: "Launch a workflow"
1586
+ }) : null] });
1587
+ }
1588
+ //#endregion
1589
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/Workflows.activity.workflows.native.tsx
1590
+ var Workflows_activity_workflows_native_default = defineFill({ component: WorkflowsActivitySection });
1591
+ //#endregion
1592
+ //#region generated/native.ts
1593
+ const via = (identity, value) => value === void 0 ? void 0 : {
1594
+ ...identity,
1595
+ ...value
1596
+ };
1597
+ const nativePlugin = defineNativePlugin({
1598
+ id: "workflows",
1599
+ session: {
1600
+ get activityGroups() {
1601
+ return [via({ name: "workflows" }, workflows_native_default)];
1602
+ },
1603
+ get channels() {
1604
+ return [via({ channel: "workflow_catalog" }, workflow_catalog_native_default), via({ channel: "workflow_runs" }, workflow_runs_native_default)];
1605
+ },
1606
+ get fills() {
1607
+ return [via({
1608
+ slot: "overlay",
1609
+ id: "workflow-launcher"
1610
+ }, WorkflowLauncher_overlay_native_default), via({
1611
+ slot: "activity.workflows",
1612
+ id: "workflows"
1613
+ }, Workflows_activity_workflows_native_default)];
1614
+ }
1615
+ }
1616
+ });
1617
+ //#endregion
1618
+ export { nativePlugin };
1619
+
1620
+ //# sourceMappingURL=native.mjs.map