@relayflows/sdk 2.0.17 → 2.0.18

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.
Files changed (123) hide show
  1. package/dist/authored-flow-error.d.ts +21 -1
  2. package/dist/authored-flow-error.d.ts.map +1 -1
  3. package/dist/authored-flow-error.js +14 -0
  4. package/dist/authored-flow-error.js.map +1 -1
  5. package/dist/authored-flow-executor.d.ts.map +1 -1
  6. package/dist/authored-flow-executor.js +47 -3
  7. package/dist/authored-flow-executor.js.map +1 -1
  8. package/dist/authored-human.d.ts +49 -0
  9. package/dist/authored-human.d.ts.map +1 -0
  10. package/dist/authored-human.js +97 -0
  11. package/dist/authored-human.js.map +1 -0
  12. package/dist/authored-node-entry.js +3 -2
  13. package/dist/authored-node-entry.js.map +1 -1
  14. package/dist/authored-node-runner.d.ts.map +1 -1
  15. package/dist/authored-node-runner.js +15 -4
  16. package/dist/authored-node-runner.js.map +1 -1
  17. package/dist/authored-root.d.ts.map +1 -1
  18. package/dist/authored-root.js +29 -1
  19. package/dist/authored-root.js.map +1 -1
  20. package/dist/cli/answer.d.ts +17 -0
  21. package/dist/cli/answer.d.ts.map +1 -0
  22. package/dist/cli/answer.js +89 -0
  23. package/dist/cli/answer.js.map +1 -0
  24. package/dist/cli/build.js +1 -1
  25. package/dist/cli/build.js.map +1 -1
  26. package/dist/cli/check-triggers.d.ts.map +1 -1
  27. package/dist/cli/check-triggers.js +2 -0
  28. package/dist/cli/check-triggers.js.map +1 -1
  29. package/dist/cli/check.d.ts +11 -0
  30. package/dist/cli/check.d.ts.map +1 -1
  31. package/dist/cli/check.js +14 -2
  32. package/dist/cli/check.js.map +1 -1
  33. package/dist/cli/cloud-connect-cli.d.ts +54 -0
  34. package/dist/cli/cloud-connect-cli.d.ts.map +1 -0
  35. package/dist/cli/cloud-connect-cli.js +109 -0
  36. package/dist/cli/cloud-connect-cli.js.map +1 -0
  37. package/dist/cli/cloud-deploy.d.ts +3 -1
  38. package/dist/cli/cloud-deploy.d.ts.map +1 -1
  39. package/dist/cli/cloud-deploy.js +44 -6
  40. package/dist/cli/cloud-deploy.js.map +1 -1
  41. package/dist/cli/cloud-run.d.ts +2 -1
  42. package/dist/cli/cloud-run.d.ts.map +1 -1
  43. package/dist/cli/cloud-run.js +17 -3
  44. package/dist/cli/cloud-run.js.map +1 -1
  45. package/dist/cli/cloud-schedule.d.ts +3 -1
  46. package/dist/cli/cloud-schedule.d.ts.map +1 -1
  47. package/dist/cli/cloud-schedule.js +31 -6
  48. package/dist/cli/cloud-schedule.js.map +1 -1
  49. package/dist/cli/direct-run.d.ts.map +1 -1
  50. package/dist/cli/direct-run.js +5 -1
  51. package/dist/cli/direct-run.js.map +1 -1
  52. package/dist/cli/run.d.ts +22 -2
  53. package/dist/cli/run.d.ts.map +1 -1
  54. package/dist/cli/run.js +34 -1
  55. package/dist/cli/run.js.map +1 -1
  56. package/dist/cli.d.ts.map +1 -1
  57. package/dist/cli.js +114 -9
  58. package/dist/cli.js.map +1 -1
  59. package/dist/cloud-connect.d.ts +52 -0
  60. package/dist/cloud-connect.d.ts.map +1 -0
  61. package/dist/cloud-connect.js +138 -0
  62. package/dist/cloud-connect.js.map +1 -0
  63. package/dist/cloud-deploy.d.ts +19 -1
  64. package/dist/cloud-deploy.d.ts.map +1 -1
  65. package/dist/cloud-deploy.js +40 -1
  66. package/dist/cloud-deploy.js.map +1 -1
  67. package/dist/cloud-http.d.ts +2 -2
  68. package/dist/cloud-http.d.ts.map +1 -1
  69. package/dist/cloud-http.js.map +1 -1
  70. package/dist/cloud-schedule.d.ts +7 -0
  71. package/dist/cloud-schedule.d.ts.map +1 -1
  72. package/dist/cloud-schedule.js +22 -1
  73. package/dist/cloud-schedule.js.map +1 -1
  74. package/dist/failure-kinds.d.ts +1 -1
  75. package/dist/failure-kinds.d.ts.map +1 -1
  76. package/dist/failure-kinds.js +2 -0
  77. package/dist/failure-kinds.js.map +1 -1
  78. package/dist/flow-requirements.d.ts +68 -0
  79. package/dist/flow-requirements.d.ts.map +1 -0
  80. package/dist/flow-requirements.js +152 -0
  81. package/dist/flow-requirements.js.map +1 -0
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +2 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/journal-client.d.ts +11 -0
  87. package/dist/journal-client.d.ts.map +1 -1
  88. package/dist/journal-client.js +13 -0
  89. package/dist/journal-client.js.map +1 -1
  90. package/dist/progress.d.ts +2 -1
  91. package/dist/progress.d.ts.map +1 -1
  92. package/dist/progress.js +9 -3
  93. package/dist/progress.js.map +1 -1
  94. package/dist/protocol.d.ts +25 -1
  95. package/dist/protocol.d.ts.map +1 -1
  96. package/package.json +2 -2
  97. package/src/authored-flow-error.ts +29 -0
  98. package/src/authored-flow-executor.ts +54 -2
  99. package/src/authored-human.ts +150 -0
  100. package/src/authored-node-entry.ts +3 -2
  101. package/src/authored-node-runner.ts +20 -5
  102. package/src/authored-root.ts +28 -1
  103. package/src/cli/answer.ts +98 -0
  104. package/src/cli/build.ts +1 -1
  105. package/src/cli/check-triggers.ts +2 -0
  106. package/src/cli/check.ts +24 -2
  107. package/src/cli/cloud-connect-cli.ts +130 -0
  108. package/src/cli/cloud-deploy.ts +40 -6
  109. package/src/cli/cloud-run.ts +16 -4
  110. package/src/cli/cloud-schedule.ts +31 -6
  111. package/src/cli/direct-run.ts +5 -0
  112. package/src/cli/run.ts +48 -2
  113. package/src/cli.ts +103 -10
  114. package/src/cloud-connect.ts +187 -0
  115. package/src/cloud-deploy.ts +57 -2
  116. package/src/cloud-http.ts +1 -1
  117. package/src/cloud-schedule.ts +20 -3
  118. package/src/failure-kinds.ts +2 -0
  119. package/src/flow-requirements.ts +201 -0
  120. package/src/index.ts +8 -0
  121. package/src/journal-client.ts +20 -0
  122. package/src/progress.ts +11 -4
  123. package/src/protocol.ts +23 -0
@@ -0,0 +1,201 @@
1
+ import { helperProviders } from '@relayflows/surface/runtime';
2
+ import type { TriggerSource } from '@relayflows/surface';
3
+ import { providerDeclaration } from './provider-trigger-contract.js';
4
+ import type { FlowSpec } from './spec.js';
5
+ import { helperCall } from './yaml-helpers.js';
6
+
7
+ /**
8
+ * What a flow needs from the workspace it deploys into, read from inert
9
+ * declarations only: the header, the trigger sources, and the text of the
10
+ * body. Nothing here executes a body or opens a socket, so the same answer is
11
+ * available to `flows check`, to the hosted verbs before they submit, and to
12
+ * Cloud's deploy wizard from the source alone.
13
+ *
14
+ * Helper use is recognised exactly as `preflightHelpers` recognises it — a
15
+ * `tools.<namespace>: true` flag or a `f.<namespace>` reference in the body —
16
+ * so a flow that preflight would refuse for a missing mount is a flow whose
17
+ * integration this module names. Coding-agent harnesses come from the `cli:`
18
+ * each `f.agent`/`f.llm` call declares, else the project default, else Cloud's
19
+ * default (`claude`): the same precedence `resolveCli` applies at run time.
20
+ */
21
+
22
+ export const FLOW_HARNESSES = ['claude', 'codex', 'gemini'] as const;
23
+ export type FlowHarness = (typeof FLOW_HARNESSES)[number];
24
+
25
+ export interface FlowIntegrationRequirement {
26
+ /** Cloud integration provider id (`slack`, `github`, `linear`, …). */
27
+ provider: string;
28
+ /** `tools`: a header declaration; `source`: a trigger or deploy target; `helper`: body use without a flag, or a YAML helper step. */
29
+ from: 'tools' | 'source' | 'helper';
30
+ /** The declaration that requires it, as a reader would name it: `tools.slack`, `--on github`, `f.slack`. */
31
+ detail: string;
32
+ }
33
+
34
+ export interface FlowHarnessRequirement {
35
+ harness: FlowHarness;
36
+ /** `agent "review"`, `llm step`, `step "draft"`, or `default`. */
37
+ detail: string;
38
+ }
39
+
40
+ export interface FlowRequirements {
41
+ integrations: FlowIntegrationRequirement[];
42
+ harnesses: FlowHarness[];
43
+ /** One entry per harness, naming the first declaration that needs it. */
44
+ harnessUses: FlowHarnessRequirement[];
45
+ /** MCP servers `tools.mcp` names; the workspace must declare each in `flows.json`. */
46
+ mcp: string[];
47
+ }
48
+
49
+ export interface FlowRequirementsContext {
50
+ /** Trigger sources the deployment listens on (`--on`, or the wizard's chosen sources). */
51
+ sources?: readonly { provider: string }[];
52
+ /** Set when the deployment targets a repository: every launched run needs GitHub. */
53
+ repository?: boolean | { owner: string; name: string };
54
+ /** The nearest `flows.json` `cli`, when one applies. */
55
+ projectCli?: string;
56
+ }
57
+
58
+ /** The inert subset of an authored definition this module reads. */
59
+ export interface RequirementsFlowDefinition {
60
+ readonly header?: { readonly tools?: Readonly<Record<string, unknown>> };
61
+ readonly body?: Function;
62
+ readonly handlers?: readonly { readonly trigger: TriggerSource }[];
63
+ }
64
+
65
+ export function flowRequirements(
66
+ flow: RequirementsFlowDefinition | FlowSpec,
67
+ context: FlowRequirementsContext = {},
68
+ ): FlowRequirements {
69
+ const integrations = new Map<string, FlowIntegrationRequirement>();
70
+ const harnessUses = new Map<FlowHarness, FlowHarnessRequirement>();
71
+ const mcp = new Set<string>();
72
+ const declare = (requirement: FlowIntegrationRequirement): void => {
73
+ if (!integrations.has(requirement.provider)) integrations.set(requirement.provider, requirement);
74
+ };
75
+ const need = (harness: FlowHarness | undefined, detail: string): void => {
76
+ if (harness !== undefined && !harnessUses.has(harness)) harnessUses.set(harness, { harness, detail });
77
+ };
78
+ const fallback = harnessFromCli(context.projectCli) ?? 'claude';
79
+
80
+ if (isCompiledSpec(flow)) {
81
+ for (const step of flow.steps) {
82
+ // Preflight owns shape refusals; a malformed step is simply not a worker step here.
83
+ if (typeof step !== 'object' || step === null || (step.type !== 'llm' && step.type !== 'agent')) continue;
84
+ // A YAML helper step (`slack: { post: … }`) compiles to an agent step
85
+ // carrying a helper envelope: it needs the provider's mount, not a harness.
86
+ const helper = step.type === 'agent' ? compiledHelper(step) : undefined;
87
+ if (helper !== undefined) {
88
+ declare({ provider: helper, from: 'helper', detail: `step "${step.id}"` });
89
+ continue;
90
+ }
91
+ const named = step.type === 'agent' && step.agent !== undefined ? flow.agents?.[step.agent]?.cli : undefined;
92
+ const cli = step.cli ?? named ?? flow.cli;
93
+ need(cli === undefined ? fallback : harnessFromCli(cli), `step "${step.id}"`);
94
+ }
95
+ } else {
96
+ const tools = flow.header?.tools ?? {};
97
+ for (const { provider, namespace } of helperProviders) {
98
+ if (tools[namespace] === true) declare({ provider, from: 'tools', detail: `tools.${namespace}` });
99
+ }
100
+ for (const entry of stringList(tools['relayfile'])) {
101
+ const provider = entry.split('/')[0]?.trim();
102
+ if (provider) declare({ provider, from: 'tools', detail: 'tools.relayfile' });
103
+ }
104
+ for (const server of stringList(tools['mcp'])) mcp.add(server);
105
+ for (const handler of flow.handlers ?? []) {
106
+ const declaration = providerDeclaration(handler.trigger);
107
+ if (declaration !== undefined) {
108
+ declare({ provider: declaration.provider, from: 'source', detail: `on ${declaration.provider} ${declaration.type}` });
109
+ }
110
+ }
111
+ // Only the default body is scanned for helper and worker calls: hosted
112
+ // deployments and schedules dispatch the default body with the trigger's
113
+ // payload as input, and handler bodies are not dispatched yet (flows #301).
114
+ // A handler's *trigger* is still a requirement — it is what wakes the flow.
115
+ const text = typeof flow.body === 'function' ? Function.prototype.toString.call(flow.body) : '';
116
+ const root = contextParameter(text);
117
+ if (root !== undefined) {
118
+ for (const { provider, namespace } of helperProviders) {
119
+ if (helperReference(root, namespace).test(text)) declare({ provider, from: 'helper', detail: `f.${namespace}` });
120
+ }
121
+ for (const use of workerCalls(root, text)) need(use.cli === undefined ? fallback : harnessFromCli(use.cli), use.detail);
122
+ }
123
+ }
124
+
125
+ for (const source of context.sources ?? []) {
126
+ declare({ provider: source.provider, from: 'source', detail: `--on ${source.provider}` });
127
+ }
128
+ if (context.repository) declare({ provider: 'github', from: 'source', detail: 'deploy target' });
129
+
130
+ const uses = [...harnessUses.values()];
131
+ return {
132
+ integrations: [...integrations.values()],
133
+ harnesses: uses.map(use => use.harness),
134
+ harnessUses: uses,
135
+ mcp: [...mcp],
136
+ };
137
+ }
138
+
139
+ /** `slack (tools.slack), github (deploy target), claude (agent "review")` — the `REQUIRES` line's body. */
140
+ export function describeFlowRequirements(requirements: FlowRequirements): string {
141
+ return [
142
+ ...requirements.integrations.map(integration => `${integration.provider} (${integration.detail})`),
143
+ ...requirements.harnessUses.map(use => `${use.harness} (${use.detail})`),
144
+ ...requirements.mcp.map(server => `mcp ${server} (tools.mcp)`),
145
+ ].join(', ');
146
+ }
147
+
148
+ /** `claude`, `/opt/bin/codex`, `gemini.exe` → the Cloud harness; anything else is not one. */
149
+ export function harnessFromCli(cli: string | undefined): FlowHarness | undefined {
150
+ if (cli === undefined) return undefined;
151
+ const base = cli.trim().split(/[\\/]/u).pop()?.replace(/\.(?:exe|cmd|bat)$/iu, '').toLowerCase();
152
+ return (FLOW_HARNESSES as readonly string[]).includes(base ?? '') ? base as FlowHarness : undefined;
153
+ }
154
+
155
+ /** The provider of a compiled YAML helper step, or undefined for an ordinary agent step or a malformed envelope. */
156
+ function compiledHelper(step: Parameters<typeof helperCall>[0]): string | undefined {
157
+ try {
158
+ return helperCall(step)?.provider;
159
+ } catch {
160
+ return undefined;
161
+ }
162
+ }
163
+
164
+ function isCompiledSpec(flow: RequirementsFlowDefinition | FlowSpec): flow is FlowSpec {
165
+ return Array.isArray((flow as FlowSpec).steps) && typeof (flow as FlowSpec).version === 'string';
166
+ }
167
+
168
+ function stringList(value: unknown): string[] {
169
+ return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : [];
170
+ }
171
+
172
+ /** The body's first parameter (`f` in `async (f, input) => …`), escaped for a pattern. */
173
+ function contextParameter(body: string): string | undefined {
174
+ const parameter = body.match(/^(?:async\s+)?(?:function(?:\s+[\w$]+)?\s*)?(?:\(\s*([\w$]+)|([\w$]+)\s*=>)/u);
175
+ return (parameter?.[1] ?? parameter?.[2])?.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
176
+ }
177
+
178
+ /** Same recognition as `preflightHelpers`: `f.slack`, `f .slack`, `f["slack"]`. */
179
+ function helperReference(root: string, namespace: string): RegExp {
180
+ return new RegExp(`(?:^|[^\\w$.])${root}\\s*(?:\\.\\s*${namespace}\\b|\\[\\s*['"]${namespace}['"]\\s*\\])`, 'u');
181
+ }
182
+
183
+ interface WorkerCall { detail: string; cli?: string }
184
+
185
+ /**
186
+ * Each `f.agent(name, { cli })` / `f.llm(prompt, { cli })` call and the CLI it
187
+ * declares. The options object is read only up to the next worker call, so a
188
+ * `cli:` belongs to the call it follows; a call without one takes the default.
189
+ */
190
+ function workerCalls(root: string, body: string): WorkerCall[] {
191
+ const call = new RegExp(`(?:^|[^\\w$.])${root}\\s*\\.\\s*(agent|llm)\\s*\\(\\s*(?:(['"\`])([^'"\`]*)\\2)?`, 'gu');
192
+ const starts = [...body.matchAll(call)];
193
+ return starts.map((match, index) => {
194
+ const slice = body.slice(match.index! + match[0].length, starts[index + 1]?.index ?? body.length);
195
+ const cli = slice.match(/(?:^|[^\w$])cli\s*:\s*(['"`])([^'"`]*)\1/u)?.[2];
196
+ const detail = match[1] === 'agent'
197
+ ? (match[3] ? `agent ${JSON.stringify(match[3])}` : 'agent step')
198
+ : 'llm step';
199
+ return { detail, ...(cli === undefined ? {} : { cli }) };
200
+ });
201
+ }
package/src/index.ts CHANGED
@@ -76,6 +76,14 @@ export {
76
76
  deployToCloud, listCloudDeployments, undeployFromCloud, parseRepository, parseTriggerSource, FLOW_TRIGGER_PROVIDERS,
77
77
  type DeployToCloudInput, type CloudDeployment, type CloudDeploymentSummary, type FlowTriggerSource, type FlowTriggerProvider,
78
78
  } from './cloud-deploy.js';
79
+ export {
80
+ ensureIntegrationsConnected, integrationConnected, providerLabel,
81
+ type ConnectPrompt, type EnsureConnectionsOptions, type ConnectionsOutcome,
82
+ } from './cloud-connect.js';
83
+ export {
84
+ flowRequirements, describeFlowRequirements, harnessFromCli, FLOW_HARNESSES,
85
+ type FlowRequirements, type FlowRequirementsContext, type FlowIntegrationRequirement, type FlowHarnessRequirement, type FlowHarness,
86
+ } from './flow-requirements.js';
79
87
 
80
88
  export { canonicalize, specHash } from './canonical.js';
81
89
  export {
@@ -386,6 +386,26 @@ export class JournalClient extends EventEmitter {
386
386
  }
387
387
 
388
388
  /** Satisfy `wait.event`; a human response arrives here too. */
389
+ /**
390
+ * Park the dispatched attempt this connection holds on a human question.
391
+ * Releases the lease; the caller must not heartbeat or complete afterwards.
392
+ */
393
+ stepWait(
394
+ runId: string,
395
+ stepId: string,
396
+ attempt: number,
397
+ idempotencyKey: string,
398
+ wait: { wait_id: string; prompt: string; requested_of: string; options?: string[]; timeout_at_ms?: number },
399
+ ): Promise<VerbContract['step.wait']['result']> {
400
+ return this.request('step.wait', {
401
+ run_id: runId,
402
+ step_id: stepId,
403
+ attempt,
404
+ idempotency_key: idempotencyKey,
405
+ ...wait,
406
+ });
407
+ }
408
+
389
409
  eventEmit(runId: string, eventKey: string, payload: unknown): Promise<VerbContract['event.emit']['result']> {
390
410
  return this.request('event.emit', { run_id: runId, event_key: eventKey, payload });
391
411
  }
package/src/progress.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type { CompletionReason } from './protocol.js';
2
2
  import type { StepType } from './spec.js';
3
+ import { AuthoredHumanParked } from './authored-flow-error.js';
3
4
 
4
5
  export interface ProgressEvent {
5
- type: 'step.started' | 'step.running' | 'step.completed' | 'step.failed';
6
+ /** `step.parked`: an `f.human` reached without an answer; the run parks, nothing failed. */
7
+ type: 'step.started' | 'step.running' | 'step.completed' | 'step.failed' | 'step.parked';
6
8
  stepId: string;
7
9
  stepType: StepType;
8
10
  elapsedMs: number;
@@ -12,13 +14,15 @@ export interface ProgressEvent {
12
14
  /** Pure terminal rendering: caller owns the event source, clock, and output. */
13
15
  export function renderProgress(events: Iterable<ProgressEvent>): string[] {
14
16
  return Array.from(events, event => {
15
- const icon = { 'step.started': '○', 'step.running': '↻', 'step.completed': '✓', 'step.failed': '✗' }[event.type];
17
+ const icon = { 'step.started': '○', 'step.running': '↻', 'step.completed': '✓', 'step.failed': '✗', 'step.parked': '⏸' }[event.type];
16
18
  const state = event.type.slice('step.'.length);
19
+ // A parked `f.human` is a question, not a step type the kernel ran.
20
+ const kind = event.type === 'step.parked' ? 'human' : event.stepType;
17
21
  const agent = event.stepType === 'agent' ? ` [agent: ${state === 'started' ? 'preparing' : state}]` : '';
18
22
  const reason = event.completionReason ? ` completionReason: ${event.completionReason}` : '';
19
23
  // Agent-authored names cannot inject terminal control sequences.
20
24
  const name = event.stepId.replace(/[\x00-\x1f\x7f-\x9f]/g, '?');
21
- return `${icon} ${name} (${event.stepType})${agent} ${(Math.max(0, event.elapsedMs) / 1000).toFixed(2)}s${reason}`;
25
+ return `${icon} ${name} (${kind})${agent} ${(Math.max(0, event.elapsedMs) / 1000).toFixed(2)}s${reason}`;
22
26
  });
23
27
  }
24
28
 
@@ -45,7 +49,10 @@ export async function observeStep<T>(
45
49
  publish({ type: 'step.completed', stepId, stepType, elapsedMs: performance.now() - started, completionReason: 'success' });
46
50
  return result;
47
51
  } catch (error) {
48
- publish({ type: 'step.failed', stepId, stepType, elapsedMs: performance.now() - started });
52
+ publish({
53
+ type: error instanceof AuthoredHumanParked ? 'step.parked' : 'step.failed',
54
+ stepId, stepType, elapsedMs: performance.now() - started,
55
+ });
49
56
  throw error;
50
57
  }
51
58
  }
package/src/protocol.ts CHANGED
@@ -52,6 +52,7 @@ export type Verb =
52
52
  | 'effect.record'
53
53
  | 'effect.confirm'
54
54
  | 'step.complete'
55
+ | 'step.wait'
55
56
  | 'event.emit'
56
57
  | 'event.submit'
57
58
  | 'stream.append'
@@ -329,6 +330,27 @@ export interface StepCompleteParams {
329
330
  }
330
331
  export type StepCompleteResult = RunOutcome;
331
332
 
333
+ /**
334
+ * Park a leased attempt on a durable human question (`wait.human`, kernel
335
+ * DESIGN.md §1.5). The attempt is not completed and no iteration is charged;
336
+ * the lease is released once the wait is journaled. The answer arrives through
337
+ * `event.emit` with `event_key` = `wait_id`, which closes the wait as
338
+ * `human_responded` and makes the step runnable for a fresh attempt.
339
+ */
340
+ export interface StepWaitParams {
341
+ run_id: string;
342
+ step_id: string;
343
+ attempt: number;
344
+ idempotency_key: string;
345
+ /** Names the question for the life of the run; the kernel refuses a reuse. */
346
+ wait_id: string;
347
+ prompt: string;
348
+ requested_of: string;
349
+ options?: string[];
350
+ timeout_at_ms?: number;
351
+ }
352
+ export type StepWaitResult = RunOutcome;
353
+
332
354
  export interface EventEmitParams {
333
355
  run_id: string;
334
356
  event_key: string;
@@ -401,6 +423,7 @@ export interface VerbContract {
401
423
  'effect.record': { params: EffectRecordParams; result: EffectRecordResult };
402
424
  'effect.confirm': { params: EffectConfirmParams; result: EffectConfirmResult };
403
425
  'step.complete': { params: StepCompleteParams; result: StepCompleteResult };
426
+ 'step.wait': { params: StepWaitParams; result: StepWaitResult };
404
427
  'event.emit': { params: EventEmitParams; result: EventEmitResult };
405
428
  'event.submit': { params: EventSubmitParams; result: EventSubmitResult };
406
429
  'stream.append': { params: StreamAppendParams; result: StreamAppendResult };