@voltro/plugin-ai-flows 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web.d.ts CHANGED
@@ -9,7 +9,17 @@ export declare const BriefField: Schema.Struct<{
9
9
  placeholder: Schema.optional<typeof Schema.String>;
10
10
  }>;
11
11
 
12
- export declare type BriefField = typeof BriefField.Type;
12
+ /** An INTERFACE for the same reason as {@link FlowStep} — it is reached from an
13
+ * exported table (`aiFlows.inputSchema`), so an alias to a mapped type gets
14
+ * expanded structurally into the api report, in an order that is not stable
15
+ * across build scopes. The `Equals` pin below fails to compile on any drift. */
16
+ export declare interface BriefField {
17
+ readonly key: string;
18
+ readonly label: string;
19
+ readonly type: 'text' | 'textarea' | 'number' | 'boolean';
20
+ readonly required?: boolean | undefined;
21
+ readonly placeholder?: string | undefined;
22
+ }
13
23
 
14
24
  /**
15
25
  * Does `cadence` fire at wall-clock `now`? True only on a 15-min slot matching
@@ -18,6 +28,13 @@ export declare type BriefField = typeof BriefField.Type;
18
28
  */
19
29
  export declare const cadenceMatches: (cadence: FlowCadence, now: Date) => boolean;
20
30
 
31
+ /** The comparison a `when:` performs. */
32
+ declare const ConditionOp: Schema.Literal<["truthy", "falsy", "eq", "neq", "contains"]>;
33
+
34
+ declare type ConditionOp = typeof ConditionOp.Type;
35
+
36
+ export declare const DEFAULT_FLOW_RPC_TAGS: FlowRpcTags;
37
+
21
38
  /** Schedule spec — richer than a single cron expr (weekly interval + anchor,
22
39
  * monthly weekOfMonth), evaluated by the shared `cadenceMatches` predicate. */
23
40
  export declare const FlowCadence: Schema.Struct<{
@@ -36,7 +53,25 @@ export declare const FlowCadence: Schema.Struct<{
36
53
  defaultInput: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
37
54
  }>;
38
55
 
39
- export declare type FlowCadence = typeof FlowCadence.Type;
56
+ /**
57
+ * An INTERFACE — reached from `aiFlows.cadence`; see {@link BriefField}.
58
+ *
59
+ * This one is not hypothetical: a single-package `api:report` and a
60
+ * full-monorepo build emitted `hour`/`minute` in DIFFERENT positions inside the
61
+ * structurally-expanded alias, so the api-surface gate rejected a file nobody
62
+ * had touched, showing the same members moving. That is the exact failure
63
+ * {@link FlowStep}'s comment describes.
64
+ */
65
+ export declare interface FlowCadence {
66
+ readonly frequency?: 'weekly' | 'monthly' | undefined;
67
+ readonly repeats: ReadonlyArray<number>;
68
+ readonly intervalWeeks?: number | undefined;
69
+ readonly anchorDate?: string | undefined;
70
+ readonly weekOfMonth?: 1 | 2 | 3 | 4 | 'last' | undefined;
71
+ readonly hour?: number | undefined;
72
+ readonly minute?: 0 | 15 | 30 | 45 | undefined;
73
+ readonly defaultInput?: Json | undefined;
74
+ }
40
75
 
41
76
  /** Follow-up flow spec. On success, map source → the child's brief. */
42
77
  export declare const FlowChain: Schema.Struct<{
@@ -49,7 +84,23 @@ export declare const FlowChain: Schema.Struct<{
49
84
  requireConfirmation: Schema.optional<typeof Schema.Boolean>;
50
85
  }>;
51
86
 
52
- export declare type FlowChain = typeof FlowChain.Type;
87
+ /** An INTERFACE reached from `aiFlows.chainTo`; see {@link BriefField}. */
88
+ export declare interface FlowChain {
89
+ readonly flowRef: string;
90
+ readonly mappings: ReadonlyArray<{
91
+ readonly targetKey: string;
92
+ readonly sourceKey: string;
93
+ }>;
94
+ readonly requireConfirmation?: boolean | undefined;
95
+ }
96
+
97
+ /** Per-hook wiring: which api, and which tags if the app renamed them. */
98
+ export declare interface FlowHookOptions {
99
+ /** Framework api name. Default `'app'`. */
100
+ readonly apiName?: string;
101
+ /** Override any subset of {@link DEFAULT_FLOW_RPC_TAGS}. */
102
+ readonly tags?: Partial<FlowRpcTags>;
103
+ }
53
104
 
54
105
  /** Execution mode. Replaces the legacy `allowDeviation` boolean — a mode is not
55
106
  * a boolean. `deterministic` runs the plan verbatim (resumable per step);
@@ -58,6 +109,54 @@ export declare const FlowMode: Schema.Literal<["deterministic", "agentic"]>;
58
109
 
59
110
  export declare type FlowMode = typeof FlowMode.Type;
60
111
 
112
+ /** The rpc tags the hooks call. Defaults match the convention the docs wire up
113
+ * (`api/flows/aiFlows.<verb>.{action,query}.server.ts`). */
114
+ export declare interface FlowRpcTags {
115
+ readonly launch: string;
116
+ readonly retry: string;
117
+ readonly cancel: string;
118
+ readonly respond: string;
119
+ /** Query returning ONE run row by id. */
120
+ readonly run: string;
121
+ /** Query returning the caller's recent runs. */
122
+ readonly runs: string;
123
+ /** Query returning the flow DEFINITIONS the caller may launch. */
124
+ readonly flows: string;
125
+ }
126
+
127
+ /** What `useFlowRun` hands a timeline component. */
128
+ export declare interface FlowRunState {
129
+ readonly run: FlowRunView | undefined;
130
+ readonly steps: ReadonlyArray<RunStep>;
131
+ readonly status: RunStatus | undefined;
132
+ /** `true` while the run is parked on a human step. */
133
+ readonly awaitingReview: boolean;
134
+ /** The review the run is parked on (prompt + mode + options), if any. */
135
+ readonly review: RunReview | undefined;
136
+ /** Index of the step being executed / reviewed. */
137
+ readonly currentStep: number;
138
+ /** `true` once the run can no longer change. */
139
+ readonly done: boolean;
140
+ readonly loading: boolean;
141
+ }
142
+
143
+ /** The run row shape the hooks project — the reactive columns a timeline needs. */
144
+ export declare interface FlowRunView {
145
+ readonly id: string;
146
+ readonly flowRef?: string;
147
+ readonly flowName?: string | null;
148
+ readonly status?: RunStatus;
149
+ readonly steps?: ReadonlyArray<RunStep> | null;
150
+ readonly currentStep?: number;
151
+ readonly totalSteps?: number;
152
+ readonly output?: Record<string, unknown> | null;
153
+ readonly humanResponse?: HumanResponse | null;
154
+ readonly costMicroUsd?: number;
155
+ readonly errorMessage?: string | null;
156
+ readonly startedAt?: string | null;
157
+ readonly completedAt?: string | null;
158
+ }
159
+
61
160
  /** Flow-definition lifecycle. */
62
161
  export declare const FlowStatus: Schema.Literal<["draft", "active", "archived"]>;
63
162
 
@@ -99,6 +198,29 @@ export declare const FlowStep: Schema.Struct<{
99
198
  value: typeof Schema.String;
100
199
  label: typeof Schema.String;
101
200
  }>>>;
201
+ /** Gate this step on a prior value. Absent → the step always runs. A step
202
+ * whose condition is false is SKIPPED, not failed: it produces no output, so
203
+ * anything referencing its `outputKey` sees an absent value. */
204
+ when: Schema.optional<Schema.Struct<{
205
+ ref: typeof Schema.String;
206
+ op: Schema.Literal<["truthy", "falsy", "eq", "neq", "contains"]>;
207
+ value: Schema.optional<typeof Schema.Unknown>;
208
+ }>>;
209
+ /**
210
+ * Fan-out group. CONSECUTIVE steps sharing a group name run CONCURRENTLY,
211
+ * each still journaled as its own durable step.
212
+ *
213
+ * Steps in one group may not reference each other's outputs — they have no
214
+ * order between them — and `validateFlow` refuses a flow that tries. A `human`
215
+ * step may not join a group either: it suspends the whole workflow, which is
216
+ * not a thing one branch of a fan-out can do.
217
+ */
218
+ group: Schema.optional<typeof Schema.String>;
219
+ /** `human` step only — how long the run parks awaiting this review before it
220
+ * fails, in milliseconds. Omitted → the flow's `humanTimeoutMs`, then the
221
+ * engine/plugin default (see `resolveHumanReviewTimeoutMs`). `0` means WAIT
222
+ * FOREVER (the park is slot-free, so an unbounded wait costs no worker). */
223
+ timeoutMs: Schema.optional<typeof Schema.Number>;
102
224
  }>;
103
225
 
104
226
  /**
@@ -139,6 +261,25 @@ export declare interface FlowStep {
139
261
  readonly schema?: Json | undefined;
140
262
  readonly reviewMode?: ReviewMode | undefined;
141
263
  readonly options?: ReadonlyArray<ReviewOption> | undefined;
264
+ readonly when?: StepCondition | undefined;
265
+ readonly group?: string | undefined;
266
+ readonly timeoutMs?: number | undefined;
267
+ }
268
+
269
+ /** The HITL answer, written by `respond`. */
270
+ export declare const HumanResponse: Schema.Struct<{
271
+ decision: Schema.optional<Schema.Literal<["approve", "reject"]>>;
272
+ value: Schema.optional<typeof Schema.String>;
273
+ text: Schema.optional<typeof Schema.String>;
274
+ respondedAt: Schema.optional<typeof Schema.String>;
275
+ }>;
276
+
277
+ /** An INTERFACE — reached from `aiFlowRuns.humanResponse`; see {@link BriefField}. */
278
+ export declare interface HumanResponse {
279
+ readonly decision?: 'approve' | 'reject' | undefined;
280
+ readonly value?: string | undefined;
281
+ readonly text?: string | undefined;
282
+ readonly respondedAt?: string | undefined;
142
283
  }
143
284
 
144
285
  /** A JSON value (for `params` / `input` / a `structured` step's JSON Schema). */
@@ -146,6 +287,18 @@ declare const Json: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
146
287
 
147
288
  declare type Json = typeof Json.Type;
148
289
 
290
+ export declare interface LaunchFlowInput {
291
+ readonly flowRef: string;
292
+ readonly input?: Record<string, unknown>;
293
+ readonly source?: 'manual' | 'cron';
294
+ }
295
+
296
+ export declare interface LaunchFlowResult {
297
+ readonly runId: string;
298
+ readonly requestId: string;
299
+ readonly status: RunStatus;
300
+ }
301
+
149
302
  declare const MediaInputs: Schema.Record$<typeof Schema.String, Schema.Union<[Schema.Struct<{
150
303
  from: Schema.Literal<["upload"]>;
151
304
  url: typeof Schema.String;
@@ -179,7 +332,7 @@ export declare const ReviewOption: Schema.Struct<{
179
332
  export declare type ReviewOption = typeof ReviewOption.Type;
180
333
 
181
334
  /** Human-review config projected onto a `waiting` run step. */
182
- declare const RunReview: Schema.Struct<{
335
+ export declare const RunReview: Schema.Struct<{
183
336
  mode: Schema.Literal<["approve", "choice", "text"]>;
184
337
  options: Schema.optional<Schema.Array$<Schema.Struct<{
185
338
  value: typeof Schema.String;
@@ -188,7 +341,7 @@ declare const RunReview: Schema.Struct<{
188
341
  prompt: typeof Schema.String;
189
342
  }>;
190
343
 
191
- declare type RunReview = typeof RunReview.Type;
344
+ export declare type RunReview = typeof RunReview.Type;
192
345
 
193
346
  /** Run status — ONE canonical set (the legacy vocabulary; the Voltro-port
194
347
  * additions `awaiting_human`/`completed`/`rejected` are collapsed: `waiting`
@@ -202,7 +355,7 @@ export declare const RunStep: Schema.Struct<{
202
355
  title: typeof Schema.String;
203
356
  description: Schema.optional<typeof Schema.String>;
204
357
  type: Schema.Literal<["agent", "generate", "structured", "note", "human"]>;
205
- status: Schema.Literal<["pending", "running", "waiting", "succeeded", "failed"]>;
358
+ status: Schema.Literal<["pending", "running", "waiting", "succeeded", "failed", "skipped"]>;
206
359
  startedAt: Schema.optional<typeof Schema.String>;
207
360
  completedAt: Schema.optional<typeof Schema.String>;
208
361
  /** Per-step cost in micro-USD (the app view projects to €/cents). */
@@ -216,6 +369,9 @@ export declare const RunStep: Schema.Struct<{
216
369
  text: typeof Schema.String;
217
370
  }>>;
218
371
  errorMessage: Schema.optional<typeof Schema.String>;
372
+ /** Why a `skipped` step was skipped — the rendered condition, so the timeline
373
+ * says "{{approved}} is not set" instead of leaving a silent gap. */
374
+ skipReason: Schema.optional<typeof Schema.String>;
219
375
  review: Schema.optional<Schema.Struct<{
220
376
  mode: Schema.Literal<["approve", "choice", "text"]>;
221
377
  options: Schema.optional<Schema.Array$<Schema.Struct<{
@@ -244,18 +400,161 @@ export declare interface RunStep {
244
400
  readonly text: string;
245
401
  } | undefined;
246
402
  readonly errorMessage?: string | undefined;
403
+ readonly skipReason?: string | undefined;
247
404
  readonly review?: RunReview | undefined;
248
405
  }
249
406
 
250
- declare const RunStepStatus: Schema.Literal<["pending", "running", "waiting", "succeeded", "failed"]>;
407
+ declare const RunStepStatus: Schema.Literal<["pending", "running", "waiting", "succeeded", "failed", "skipped"]>;
251
408
 
252
409
  declare type RunStepStatus = typeof RunStepStatus.Type;
253
410
 
411
+ declare const StepCondition: Schema.Struct<{
412
+ /** A brief-field key or an EARLIER step's `outputKey`. */
413
+ ref: typeof Schema.String;
414
+ op: Schema.Literal<["truthy", "falsy", "eq", "neq", "contains"]>;
415
+ /** The right-hand side for `eq` / `neq` / `contains`. Ignored by the unary ops. */
416
+ value: Schema.optional<typeof Schema.Unknown>;
417
+ }>;
418
+
419
+ /** An INTERFACE, for the same api-report reason as `FlowStep` — it is reached
420
+ * from an exported table column, and an alias to a mapped type gets expanded
421
+ * structurally in an order that is not stable across build scopes. */
422
+ declare interface StepCondition {
423
+ readonly ref: string;
424
+ readonly op: ConditionOp;
425
+ readonly value?: unknown;
426
+ }
427
+
254
428
  /** The five step types. `generate` fans out to four modalities. */
255
429
  export declare const StepType: Schema.Literal<["agent", "generate", "structured", "note", "human"]>;
256
430
 
257
431
  export declare type StepType = typeof StepType.Type;
258
432
 
433
+ /** Cooperatively cancel a run (the engine stops before its next step). */
434
+ export declare const useCancelFlow: (options?: FlowHookOptions) => {
435
+ cancel: (runId: string) => Promise<{
436
+ runId: string;
437
+ status: RunStatus;
438
+ }>;
439
+ pending: boolean;
440
+ error: unknown;
441
+ };
442
+
443
+ /**
444
+ * The whole review widget in one hook: the live run + the answer callbacks,
445
+ * with the pending review's prompt/options already unwrapped.
446
+ */
447
+ export declare const useFlowReview: (runId: string | undefined, options?: FlowHookOptions) => {
448
+ prompt: string | undefined;
449
+ mode: "text" | "approve" | "choice" | undefined;
450
+ options: readonly {
451
+ readonly value: string;
452
+ readonly label: string;
453
+ }[];
454
+ /** `true` when there is a review this caller can answer right now. */
455
+ canRespond: boolean;
456
+ respond: (runId: string, response: HumanResponse) => Promise<{
457
+ runId: string;
458
+ stepIndex?: number;
459
+ }>;
460
+ approve: (runId: string, text?: string) => Promise<{
461
+ runId: string;
462
+ stepIndex?: number;
463
+ }>;
464
+ reject: (runId: string, text?: string) => Promise<{
465
+ runId: string;
466
+ stepIndex?: number;
467
+ }>;
468
+ /** Pick one of a `choice` review's options. */
469
+ choose: (runId: string, value: string) => Promise<{
470
+ runId: string;
471
+ stepIndex?: number;
472
+ }>;
473
+ /** Answer a `text` review. */
474
+ submitText: (runId: string, text: string) => Promise<{
475
+ runId: string;
476
+ stepIndex?: number;
477
+ }>;
478
+ pending: boolean;
479
+ error: unknown;
480
+ run: FlowRunView | undefined;
481
+ steps: ReadonlyArray<RunStep>;
482
+ status: RunStatus | undefined;
483
+ awaitingReview: boolean;
484
+ review: RunReview | undefined;
485
+ currentStep: number;
486
+ done: boolean;
487
+ loading: boolean;
488
+ };
489
+
490
+ /**
491
+ * Live view of ONE flow run. The run row is reactive, so the timeline streams:
492
+ * every step transition the engine writes shows up without a refetch.
493
+ *
494
+ * ```tsx
495
+ * const { steps, awaitingReview, review } = useFlowRun(runId)
496
+ * ```
497
+ */
498
+ export declare const useFlowRun: (runId: string | undefined, options?: FlowHookOptions) => FlowRunState;
499
+
500
+ /** The caller's recent runs (whatever the app's `runs` query returns). */
501
+ export declare const useFlowRuns: (filter?: Readonly<Record<string, unknown>>, options?: FlowHookOptions) => ReadonlyArray<FlowRunView>;
502
+
503
+ /** The flow DEFINITIONS the caller can launch (the gallery / picker). */
504
+ export declare const useFlows: <T = Record<string, unknown>>(filter?: Readonly<Record<string, unknown>>, options?: FlowHookOptions) => ReadonlyArray<T>;
505
+
506
+ /** Start a run. Returns the `runId` to hand `useFlowRun`. */
507
+ export declare const useLaunchFlow: (options?: FlowHookOptions) => {
508
+ launch: (input: LaunchFlowInput) => Promise<LaunchFlowResult>;
509
+ pending: boolean;
510
+ error: unknown;
511
+ lastRun: LaunchFlowResult | undefined;
512
+ };
513
+
514
+ /**
515
+ * Answer the review a run is parked on. The server resolves WHICH human step the
516
+ * answer belongs to from the run's live timeline, so the UI never has to track a
517
+ * step index — and a flow with several reviews gets one answer per review.
518
+ *
519
+ * ```tsx
520
+ * const { approve, reject, choose, submitText } = useRespondToFlow()
521
+ * <button onClick={() => approve(runId)}>Approve</button>
522
+ * ```
523
+ */
524
+ export declare const useRespondToFlow: (options?: FlowHookOptions) => {
525
+ respond: (runId: string, response: HumanResponse) => Promise<{
526
+ runId: string;
527
+ stepIndex?: number;
528
+ }>;
529
+ approve: (runId: string, text?: string) => Promise<{
530
+ runId: string;
531
+ stepIndex?: number;
532
+ }>;
533
+ reject: (runId: string, text?: string) => Promise<{
534
+ runId: string;
535
+ stepIndex?: number;
536
+ }>;
537
+ /** Pick one of a `choice` review's options. */
538
+ choose: (runId: string, value: string) => Promise<{
539
+ runId: string;
540
+ stepIndex?: number;
541
+ }>;
542
+ /** Answer a `text` review. */
543
+ submitText: (runId: string, text: string) => Promise<{
544
+ runId: string;
545
+ stepIndex?: number;
546
+ }>;
547
+ pending: boolean;
548
+ error: unknown;
549
+ };
550
+
551
+ /** Re-run a run from its first unfinished step (same requestId → journal replay). */
552
+ export declare const useRetryFlow: (options?: FlowHookOptions) => {
553
+ retry: (runId: string) => Promise<LaunchFlowResult>;
554
+ pending: boolean;
555
+ error: unknown;
556
+ };
557
+
259
558
  /** Gallery scope. `shared` → per-subject share list; `organization` → tenant. */
260
559
  export declare const Visibility: Schema.Literal<["private", "organization", "shared"]>;
261
560
 
package/dist/web.js CHANGED
@@ -1,2 +1,97 @@
1
1
  import { n as e, t } from "./cadence-Bt6dPelT.js";
2
- export { t as cadenceMatches, e as nextRuns };
2
+ import { useAction as n, useSubscription as r } from "@voltro/client";
3
+ //#region src/web.ts
4
+ var i = {
5
+ launch: "aiFlows.launch",
6
+ retry: "aiFlows.retry",
7
+ cancel: "aiFlows.cancel",
8
+ respond: "aiFlows.respond",
9
+ run: "aiFlows.run",
10
+ runs: "aiFlows.runs",
11
+ flows: "aiFlows.flows"
12
+ }, a = (e) => e?.tags === void 0 ? i : {
13
+ ...i,
14
+ ...e.tags
15
+ }, o = (e) => e?.apiName ?? "app", s = [
16
+ "succeeded",
17
+ "failed",
18
+ "cancelled"
19
+ ], c = (e, t) => {
20
+ let n = r(o(t), a(t).run, { id: e ?? "" }, { skip: e === void 0 || e === "" }), i = "data" in n ? n.data : void 0, c = (Array.isArray(i) ? i[0] : i) ?? void 0, l = c?.steps ?? [], u = l.find((e) => e.status === "waiting");
21
+ return {
22
+ run: c,
23
+ steps: l,
24
+ status: c?.status,
25
+ awaitingReview: c?.status === "waiting",
26
+ review: u?.review,
27
+ currentStep: c?.currentStep ?? 0,
28
+ done: c?.status !== void 0 && s.includes(c.status),
29
+ loading: c === void 0
30
+ };
31
+ }, l = (e = {}, t) => {
32
+ let n = r(o(t), a(t).runs, e);
33
+ return ("data" in n ? n.data : void 0) ?? [];
34
+ }, u = (e = {}, t) => {
35
+ let n = r(o(t), a(t).flows, e);
36
+ return ("data" in n ? n.data : void 0) ?? [];
37
+ }, d = (e) => {
38
+ let t = n(o(e), a(e).launch);
39
+ return {
40
+ launch: (e) => t.run(e),
41
+ pending: t.pending,
42
+ error: t.error,
43
+ lastRun: t.lastResult
44
+ };
45
+ }, f = (e) => {
46
+ let t = n(o(e), a(e).retry);
47
+ return {
48
+ retry: (e) => t.run({ runId: e }),
49
+ pending: t.pending,
50
+ error: t.error
51
+ };
52
+ }, p = (e) => {
53
+ let t = n(o(e), a(e).cancel);
54
+ return {
55
+ cancel: (e) => t.run({ runId: e }),
56
+ pending: t.pending,
57
+ error: t.error
58
+ };
59
+ }, m = (e) => {
60
+ let t = n(o(e), a(e).respond), r = (e, n) => t.run({
61
+ runId: e,
62
+ response: n
63
+ });
64
+ return {
65
+ respond: r,
66
+ approve: (e, t) => r(e, {
67
+ decision: "approve",
68
+ ...t === void 0 ? {} : { text: t }
69
+ }),
70
+ reject: (e, t) => r(e, {
71
+ decision: "reject",
72
+ ...t === void 0 ? {} : { text: t }
73
+ }),
74
+ choose: (e, t) => r(e, {
75
+ decision: "approve",
76
+ value: t
77
+ }),
78
+ submitText: (e, t) => r(e, {
79
+ decision: "approve",
80
+ text: t
81
+ }),
82
+ pending: t.pending,
83
+ error: t.error
84
+ };
85
+ }, h = (e, t) => {
86
+ let n = c(e, t), r = m(t), i = n.review?.options ?? [];
87
+ return {
88
+ ...n,
89
+ ...r,
90
+ prompt: n.review?.prompt,
91
+ mode: n.review?.mode,
92
+ options: i,
93
+ canRespond: n.awaitingReview && e !== void 0
94
+ };
95
+ };
96
+ //#endregion
97
+ export { i as DEFAULT_FLOW_RPC_TAGS, t as cadenceMatches, e as nextRuns, p as useCancelFlow, h as useFlowReview, c as useFlowRun, l as useFlowRuns, u as useFlows, d as useLaunchFlow, m as useRespondToFlow, f as useRetryFlow };
@@ -12,6 +12,7 @@ export declare interface FlowRunPayload {
12
12
  readonly input: Record<string, unknown>;
13
13
  readonly source: 'manual' | 'cron';
14
14
  readonly requestId: string;
15
+ readonly chainPath?: ReadonlyArray<string> | undefined;
15
16
  }
16
17
 
17
18
  /**
@@ -30,16 +31,54 @@ export declare const flowRunWorkflow: Workflow<"flow.run", Schema.Struct<{
30
31
  source: Schema.Literal<["manual", "cron"]>;
31
32
  /** Correlation + idempotency key. */
32
33
  requestId: typeof Schema.String;
34
+ /**
35
+ * The chain of flows that led here, oldest first, INCLUDING this one.
36
+ *
37
+ * `chainTo` had a self-chain guard and nothing else, so A→B→A was an
38
+ * unbounded loop: each hop started a fresh child workflow with a fresh
39
+ * `requestId`, so idempotency never collapsed it and nothing counted the
40
+ * hops. The framework would happily run it until an operator noticed the
41
+ * bill.
42
+ *
43
+ * Optional so a run started by an older caller (or by hand) still decodes;
44
+ * an absent path is read as "this is the first flow", which it is.
45
+ */
46
+ chainPath: Schema.optional<Schema.Array$<typeof Schema.String>>;
33
47
  }>, Schema.Struct<{
34
48
  runId: typeof Schema.String;
35
49
  status: Schema.Literal<["succeeded", "failed", "cancelled", "waiting"]>;
36
50
  }>, typeof Schema.Never> & WorkflowMessagesCarrier<{
37
51
  readonly signals: {};
38
52
  readonly updates: {};
39
- readonly queries: {};
40
53
  }>;
41
54
 
42
- /** The signal a `human` step parks on; `respondToFlow` sends it to resume. */
43
- export declare const HUMAN_RESPONSE_SIGNAL = "flow-human-response";
55
+ /** Namespace of the signal a `human` step parks on. Never used on its own
56
+ * the signal a step actually awaits is {@link humanResponseSignalName}. */
57
+ export declare const HUMAN_RESPONSE_SIGNAL_PREFIX = "flow-human-response";
58
+
59
+ /**
60
+ * The signal name ONE human step parks on, derived from that step's position in
61
+ * the (journal-pinned) plan.
62
+ *
63
+ * **Why this is per-step and not one constant.** `awaitSignalSuspending` derives
64
+ * its `DurableDeferred` from workflow-name + signal-name per execution, so a
65
+ * single `'flow-human-response'` for every human step in a flow meant the SECOND
66
+ * review awaited the deferred the FIRST answer had already completed — it
67
+ * resolved instantly, with the first reviewer's payload, and the run finished
68
+ * carrying an approval nobody gave. Measured before the fix: a two-review flow,
69
+ * one answer, `status: 'succeeded'`, `output: { first: 'answer-one', second:
70
+ * 'answer-one' }`. The same collision aliased the wait's durable timeout clock
71
+ * (`await-signal-timeout/<name>`), so the second park inherited the first's
72
+ * expiry too.
73
+ *
74
+ * **Why the INDEX is the identity.** It must be identical on every replay and
75
+ * derivable by the responder without re-resolving the flow definition (which may
76
+ * have been edited mid-run). The step list is pinned in the run's journal at
77
+ * step 0, so a step's index is fixed for the life of the run; the responder
78
+ * reads it back off the run row's live timeline. A counter, a clock, or a
79
+ * `randomUUID` would all break replay; the authored step `id` would break the
80
+ * moment a definition is re-ordered under a live run.
81
+ */
82
+ export declare const humanResponseSignalName: (stepIndex: number) => string;
44
83
 
45
84
  export { }
package/dist/workflow.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Schema as e } from "effect";
2
2
  import { workflow as t } from "@voltro/workflow/define";
3
3
  //#region src/workflow.ts
4
- var n = "flow.run", r = "flow-human-response", i = t({
4
+ var n = "flow.run", r = "flow-human-response", i = (e) => `${r}:${e}`, a = t({
5
5
  name: n,
6
6
  payload: {
7
7
  runId: e.String,
@@ -11,7 +11,8 @@ var n = "flow.run", r = "flow-human-response", i = t({
11
11
  value: e.Unknown
12
12
  }),
13
13
  source: e.Literal("manual", "cron"),
14
- requestId: e.String
14
+ requestId: e.String,
15
+ chainPath: e.optional(e.Array(e.String))
15
16
  },
16
17
  success: e.Struct({
17
18
  runId: e.String,
@@ -20,4 +21,4 @@ var n = "flow.run", r = "flow-human-response", i = t({
20
21
  idempotencyKey: ({ requestId: e }) => `flow-run:${e}`
21
22
  });
22
23
  //#endregion
23
- export { n as FLOW_RUN_WORKFLOW, r as HUMAN_RESPONSE_SIGNAL, i as flowRunWorkflow };
24
+ export { n as FLOW_RUN_WORKFLOW, r as HUMAN_RESPONSE_SIGNAL_PREFIX, a as flowRunWorkflow, i as humanResponseSignalName };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-ai-flows",
3
- "version": "0.33.0",
3
+ "version": "0.35.0",
4
4
  "description": "AI Flows — durable multi-step AI pipelines (deterministic + agentic) with human-in-the-loop, chaining, and cadence. Author flows in code (defineFlow) or as data (visual editor rows); one engine runs both on @voltro/workflow durability, @voltro/ai generation, storage artifacts, and notifications.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -58,15 +58,16 @@
58
58
  "node": ">=24.0.0"
59
59
  },
60
60
  "dependencies": {
61
- "@voltro/ai": "0.33.0",
62
- "@voltro/database": "0.33.0",
63
- "@voltro/env": "0.33.0",
64
- "@voltro/plugin-audit": "0.33.0",
65
- "@voltro/plugin-multitenancy": "0.33.0",
66
- "@voltro/plugin-soft-delete": "0.33.0",
67
- "@voltro/protocol": "0.33.0",
68
- "@voltro/runtime": "0.33.0",
69
- "@voltro/workflow": "0.33.0"
61
+ "@voltro/ai": "0.35.0",
62
+ "@voltro/client": "0.35.0",
63
+ "@voltro/database": "0.35.0",
64
+ "@voltro/env": "0.35.0",
65
+ "@voltro/plugin-audit": "0.35.0",
66
+ "@voltro/plugin-multitenancy": "0.35.0",
67
+ "@voltro/plugin-soft-delete": "0.35.0",
68
+ "@voltro/protocol": "0.35.0",
69
+ "@voltro/runtime": "0.35.0",
70
+ "@voltro/workflow": "0.35.0"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "effect": "^3.22.0"