agent-relay 3.2.22 → 4.0.1

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 (232) hide show
  1. package/README.md +5 -5
  2. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  3. package/bin/agent-relay-broker-darwin-x64 +0 -0
  4. package/bin/agent-relay-broker-linux-arm64 +0 -0
  5. package/bin/agent-relay-broker-linux-x64 +0 -0
  6. package/dist/index.cjs +6564 -2100
  7. package/dist/src/cli/bootstrap.d.ts.map +1 -1
  8. package/dist/src/cli/bootstrap.js +2 -0
  9. package/dist/src/cli/bootstrap.js.map +1 -1
  10. package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
  11. package/dist/src/cli/commands/agent-management.js +14 -4
  12. package/dist/src/cli/commands/agent-management.js.map +1 -1
  13. package/dist/src/cli/commands/core.d.ts +2 -6
  14. package/dist/src/cli/commands/core.d.ts.map +1 -1
  15. package/dist/src/cli/commands/core.js +31 -12
  16. package/dist/src/cli/commands/core.js.map +1 -1
  17. package/dist/src/cli/commands/messaging.d.ts.map +1 -1
  18. package/dist/src/cli/commands/messaging.js +10 -3
  19. package/dist/src/cli/commands/messaging.js.map +1 -1
  20. package/dist/src/cli/commands/monitoring.d.ts +2 -2
  21. package/dist/src/cli/commands/monitoring.d.ts.map +1 -1
  22. package/dist/src/cli/commands/monitoring.js +15 -6
  23. package/dist/src/cli/commands/monitoring.js.map +1 -1
  24. package/dist/src/cli/commands/on/dotfiles.d.ts +35 -0
  25. package/dist/src/cli/commands/on/dotfiles.d.ts.map +1 -0
  26. package/dist/src/cli/commands/on/dotfiles.js +157 -0
  27. package/dist/src/cli/commands/on/dotfiles.js.map +1 -0
  28. package/dist/src/cli/commands/on/prereqs.d.ts +15 -0
  29. package/dist/src/cli/commands/on/prereqs.d.ts.map +1 -0
  30. package/dist/src/cli/commands/on/prereqs.js +103 -0
  31. package/dist/src/cli/commands/on/prereqs.js.map +1 -0
  32. package/dist/src/cli/commands/on/provision.d.ts +22 -0
  33. package/dist/src/cli/commands/on/provision.d.ts.map +1 -0
  34. package/dist/src/cli/commands/on/provision.js +157 -0
  35. package/dist/src/cli/commands/on/provision.js.map +1 -0
  36. package/dist/src/cli/commands/on/relayfile-binary.d.ts +2 -0
  37. package/dist/src/cli/commands/on/relayfile-binary.d.ts.map +1 -0
  38. package/dist/src/cli/commands/on/relayfile-binary.js +208 -0
  39. package/dist/src/cli/commands/on/relayfile-binary.js.map +1 -0
  40. package/dist/src/cli/commands/on/scan.d.ts +8 -0
  41. package/dist/src/cli/commands/on/scan.d.ts.map +1 -0
  42. package/dist/src/cli/commands/on/scan.js +59 -0
  43. package/dist/src/cli/commands/on/scan.js.map +1 -0
  44. package/dist/src/cli/commands/on/services.d.ts +17 -0
  45. package/dist/src/cli/commands/on/services.d.ts.map +1 -0
  46. package/dist/src/cli/commands/on/services.js +328 -0
  47. package/dist/src/cli/commands/on/services.js.map +1 -0
  48. package/dist/src/cli/commands/on/start.d.ts +61 -0
  49. package/dist/src/cli/commands/on/start.d.ts.map +1 -0
  50. package/dist/src/cli/commands/on/start.js +1107 -0
  51. package/dist/src/cli/commands/on/start.js.map +1 -0
  52. package/dist/src/cli/commands/on/stop.d.ts +4 -0
  53. package/dist/src/cli/commands/on/stop.d.ts.map +1 -0
  54. package/dist/src/cli/commands/on/stop.js +11 -0
  55. package/dist/src/cli/commands/on/stop.js.map +1 -0
  56. package/dist/src/cli/commands/on/token.d.ts +8 -0
  57. package/dist/src/cli/commands/on/token.d.ts.map +1 -0
  58. package/dist/src/cli/commands/on/token.js +26 -0
  59. package/dist/src/cli/commands/on/token.js.map +1 -0
  60. package/dist/src/cli/commands/on/workspace.d.ts +4 -0
  61. package/dist/src/cli/commands/on/workspace.d.ts.map +1 -0
  62. package/dist/src/cli/commands/on/workspace.js +245 -0
  63. package/dist/src/cli/commands/on/workspace.js.map +1 -0
  64. package/dist/src/cli/commands/on.d.ts +10 -0
  65. package/dist/src/cli/commands/on.d.ts.map +1 -0
  66. package/dist/src/cli/commands/on.js +52 -0
  67. package/dist/src/cli/commands/on.js.map +1 -0
  68. package/dist/src/cli/commands/setup.d.ts.map +1 -1
  69. package/dist/src/cli/commands/setup.js +10 -21
  70. package/dist/src/cli/commands/setup.js.map +1 -1
  71. package/dist/src/cli/lib/bridge.js +1 -1
  72. package/dist/src/cli/lib/bridge.js.map +1 -1
  73. package/dist/src/cli/lib/broker-lifecycle.d.ts +14 -4
  74. package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
  75. package/dist/src/cli/lib/broker-lifecycle.js +82 -120
  76. package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
  77. package/dist/src/cli/lib/client-factory.d.ts +4 -4
  78. package/dist/src/cli/lib/client-factory.d.ts.map +1 -1
  79. package/dist/src/cli/lib/client-factory.js +14 -11
  80. package/dist/src/cli/lib/client-factory.js.map +1 -1
  81. package/dist/src/cli/lib/core-maintenance.d.ts.map +1 -1
  82. package/dist/src/cli/lib/core-maintenance.js +11 -22
  83. package/dist/src/cli/lib/core-maintenance.js.map +1 -1
  84. package/dist/src/cost/pricing.d.ts +18 -0
  85. package/dist/src/cost/pricing.d.ts.map +1 -0
  86. package/dist/src/cost/pricing.js +111 -0
  87. package/dist/src/cost/pricing.js.map +1 -0
  88. package/dist/src/cost/tracker.d.ts +13 -0
  89. package/dist/src/cost/tracker.d.ts.map +1 -0
  90. package/dist/src/cost/tracker.js +152 -0
  91. package/dist/src/cost/tracker.js.map +1 -0
  92. package/dist/src/cost/types.d.ts +23 -0
  93. package/dist/src/cost/types.d.ts.map +1 -0
  94. package/dist/src/cost/types.js +2 -0
  95. package/dist/src/cost/types.js.map +1 -0
  96. package/package.json +15 -12
  97. package/packages/acp-bridge/package.json +2 -2
  98. package/packages/brand/package.json +1 -1
  99. package/packages/cloud/package.json +3 -3
  100. package/packages/config/package.json +1 -1
  101. package/packages/hooks/package.json +4 -4
  102. package/packages/memory/package.json +2 -2
  103. package/packages/openclaw/package.json +2 -2
  104. package/packages/policy/package.json +2 -2
  105. package/packages/sdk/README.md +10 -3
  106. package/packages/sdk/dist/broker-path.d.ts +3 -2
  107. package/packages/sdk/dist/broker-path.d.ts.map +1 -1
  108. package/packages/sdk/dist/broker-path.js +119 -32
  109. package/packages/sdk/dist/broker-path.js.map +1 -1
  110. package/packages/sdk/dist/client.d.ts +119 -197
  111. package/packages/sdk/dist/client.d.ts.map +1 -1
  112. package/packages/sdk/dist/client.js +354 -823
  113. package/packages/sdk/dist/client.js.map +1 -1
  114. package/packages/sdk/dist/examples/example.js +2 -5
  115. package/packages/sdk/dist/examples/example.js.map +1 -1
  116. package/packages/sdk/dist/index.d.ts +3 -1
  117. package/packages/sdk/dist/index.d.ts.map +1 -1
  118. package/packages/sdk/dist/index.js +3 -1
  119. package/packages/sdk/dist/index.js.map +1 -1
  120. package/packages/sdk/dist/relay-adapter.d.ts +9 -26
  121. package/packages/sdk/dist/relay-adapter.d.ts.map +1 -1
  122. package/packages/sdk/dist/relay-adapter.js +75 -47
  123. package/packages/sdk/dist/relay-adapter.js.map +1 -1
  124. package/packages/sdk/dist/relay.d.ts +26 -6
  125. package/packages/sdk/dist/relay.d.ts.map +1 -1
  126. package/packages/sdk/dist/relay.js +213 -43
  127. package/packages/sdk/dist/relay.js.map +1 -1
  128. package/packages/sdk/dist/transport.d.ts +58 -0
  129. package/packages/sdk/dist/transport.d.ts.map +1 -0
  130. package/packages/sdk/dist/transport.js +184 -0
  131. package/packages/sdk/dist/transport.js.map +1 -0
  132. package/packages/sdk/dist/types.d.ts +69 -0
  133. package/packages/sdk/dist/types.d.ts.map +1 -0
  134. package/packages/sdk/dist/types.js +5 -0
  135. package/packages/sdk/dist/types.js.map +1 -0
  136. package/packages/sdk/dist/workflows/__tests__/channel-messenger.test.d.ts +2 -0
  137. package/packages/sdk/dist/workflows/__tests__/channel-messenger.test.d.ts.map +1 -0
  138. package/packages/sdk/dist/workflows/__tests__/channel-messenger.test.js +117 -0
  139. package/packages/sdk/dist/workflows/__tests__/channel-messenger.test.js.map +1 -0
  140. package/packages/sdk/dist/workflows/__tests__/run-summary-table.test.js +4 -3
  141. package/packages/sdk/dist/workflows/__tests__/run-summary-table.test.js.map +1 -1
  142. package/packages/sdk/dist/workflows/__tests__/step-executor.test.d.ts +2 -0
  143. package/packages/sdk/dist/workflows/__tests__/step-executor.test.d.ts.map +1 -0
  144. package/packages/sdk/dist/workflows/__tests__/step-executor.test.js +378 -0
  145. package/packages/sdk/dist/workflows/__tests__/step-executor.test.js.map +1 -0
  146. package/packages/sdk/dist/workflows/__tests__/template-resolver.test.d.ts +2 -0
  147. package/packages/sdk/dist/workflows/__tests__/template-resolver.test.d.ts.map +1 -0
  148. package/packages/sdk/dist/workflows/__tests__/template-resolver.test.js +145 -0
  149. package/packages/sdk/dist/workflows/__tests__/template-resolver.test.js.map +1 -0
  150. package/packages/sdk/dist/workflows/__tests__/verification.test.d.ts +2 -0
  151. package/packages/sdk/dist/workflows/__tests__/verification.test.d.ts.map +1 -0
  152. package/packages/sdk/dist/workflows/__tests__/verification.test.js +170 -0
  153. package/packages/sdk/dist/workflows/__tests__/verification.test.js.map +1 -0
  154. package/packages/sdk/dist/workflows/builder.d.ts +3 -2
  155. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
  156. package/packages/sdk/dist/workflows/builder.js +1 -3
  157. package/packages/sdk/dist/workflows/builder.js.map +1 -1
  158. package/packages/sdk/dist/workflows/channel-messenger.d.ts +28 -0
  159. package/packages/sdk/dist/workflows/channel-messenger.d.ts.map +1 -0
  160. package/packages/sdk/dist/workflows/channel-messenger.js +255 -0
  161. package/packages/sdk/dist/workflows/channel-messenger.js.map +1 -0
  162. package/packages/sdk/dist/workflows/index.d.ts +7 -0
  163. package/packages/sdk/dist/workflows/index.d.ts.map +1 -1
  164. package/packages/sdk/dist/workflows/index.js +7 -0
  165. package/packages/sdk/dist/workflows/index.js.map +1 -1
  166. package/packages/sdk/dist/workflows/process-spawner.d.ts +35 -0
  167. package/packages/sdk/dist/workflows/process-spawner.d.ts.map +1 -0
  168. package/packages/sdk/dist/workflows/process-spawner.js +141 -0
  169. package/packages/sdk/dist/workflows/process-spawner.js.map +1 -0
  170. package/packages/sdk/dist/workflows/run.d.ts +2 -1
  171. package/packages/sdk/dist/workflows/run.d.ts.map +1 -1
  172. package/packages/sdk/dist/workflows/run.js.map +1 -1
  173. package/packages/sdk/dist/workflows/runner.d.ts +6 -6
  174. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  175. package/packages/sdk/dist/workflows/runner.js +443 -719
  176. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  177. package/packages/sdk/dist/workflows/step-executor.d.ts +95 -0
  178. package/packages/sdk/dist/workflows/step-executor.d.ts.map +1 -0
  179. package/packages/sdk/dist/workflows/step-executor.js +393 -0
  180. package/packages/sdk/dist/workflows/step-executor.js.map +1 -0
  181. package/packages/sdk/dist/workflows/template-resolver.d.ts +33 -0
  182. package/packages/sdk/dist/workflows/template-resolver.d.ts.map +1 -0
  183. package/packages/sdk/dist/workflows/template-resolver.js +144 -0
  184. package/packages/sdk/dist/workflows/template-resolver.js.map +1 -0
  185. package/packages/sdk/dist/workflows/validator.d.ts.map +1 -1
  186. package/packages/sdk/dist/workflows/validator.js +17 -2
  187. package/packages/sdk/dist/workflows/validator.js.map +1 -1
  188. package/packages/sdk/dist/workflows/verification.d.ts +33 -0
  189. package/packages/sdk/dist/workflows/verification.d.ts.map +1 -0
  190. package/packages/sdk/dist/workflows/verification.js +122 -0
  191. package/packages/sdk/dist/workflows/verification.js.map +1 -0
  192. package/packages/sdk/package.json +2 -2
  193. package/packages/sdk/src/__tests__/unit.test.ts +100 -1
  194. package/packages/sdk/src/broker-path.ts +136 -30
  195. package/packages/sdk/src/client.ts +453 -1069
  196. package/packages/sdk/src/examples/example.ts +2 -5
  197. package/packages/sdk/src/index.ts +9 -1
  198. package/packages/sdk/src/relay-adapter.ts +75 -55
  199. package/packages/sdk/src/relay.ts +262 -55
  200. package/packages/sdk/src/transport.ts +216 -0
  201. package/packages/sdk/src/types.ts +75 -0
  202. package/packages/sdk/src/workflows/__tests__/channel-messenger.test.ts +137 -0
  203. package/packages/sdk/src/workflows/__tests__/run-summary-table.test.ts +4 -3
  204. package/packages/sdk/src/workflows/__tests__/step-executor.test.ts +444 -0
  205. package/packages/sdk/src/workflows/__tests__/template-resolver.test.ts +162 -0
  206. package/packages/sdk/src/workflows/__tests__/verification.test.ts +229 -0
  207. package/packages/sdk/src/workflows/builder.ts +6 -6
  208. package/packages/sdk/src/workflows/channel-messenger.ts +314 -0
  209. package/packages/sdk/src/workflows/index.ts +12 -0
  210. package/packages/sdk/src/workflows/process-spawner.ts +201 -0
  211. package/packages/sdk/src/workflows/run.ts +2 -1
  212. package/packages/sdk/src/workflows/runner.ts +636 -951
  213. package/packages/sdk/src/workflows/step-executor.ts +579 -0
  214. package/packages/sdk/src/workflows/template-resolver.ts +180 -0
  215. package/packages/sdk/src/workflows/validator.ts +20 -2
  216. package/packages/sdk/src/workflows/verification.ts +184 -0
  217. package/packages/sdk-py/pyproject.toml +1 -1
  218. package/packages/sdk-py/src/agent_relay/__init__.py +0 -8
  219. package/packages/sdk-py/src/agent_relay/client.py +329 -522
  220. package/packages/sdk-py/src/agent_relay/protocol.py +2 -96
  221. package/packages/sdk-py/src/agent_relay/relay.py +1 -4
  222. package/packages/sdk-py/tests/test_wait_for_api_url.py +92 -0
  223. package/packages/sdk-py/uv.lock +5388 -0
  224. package/packages/telemetry/dist/client.d.ts.map +1 -1
  225. package/packages/telemetry/dist/client.js +1 -1
  226. package/packages/telemetry/dist/client.js.map +1 -1
  227. package/packages/telemetry/package.json +1 -1
  228. package/packages/telemetry/src/client.ts +3 -10
  229. package/packages/trajectory/package.json +2 -2
  230. package/packages/user-directory/package.json +2 -2
  231. package/packages/utils/package.json +2 -2
  232. package/scripts/postinstall.js +121 -1
@@ -0,0 +1,579 @@
1
+ import { ChannelMessenger } from './channel-messenger.js';
2
+ import type { ProcessSpawner } from './process-spawner.js';
3
+ import { TemplateResolver } from './template-resolver.js';
4
+ import type { StepOutcome } from './trajectory.js';
5
+ import type {
6
+ AgentDefinition,
7
+ ErrorHandlingConfig,
8
+ StepCompletionMode,
9
+ VerificationCheck,
10
+ WorkflowStep,
11
+ WorkflowStepCompletionReason,
12
+ WorkflowStepRow,
13
+ WorkflowStepStatus,
14
+ } from './types.js';
15
+ import {
16
+ runVerification,
17
+ type VerificationOptions,
18
+ type VerificationResult,
19
+ type VerificationSideEffects,
20
+ } from './verification.js';
21
+
22
+ type StateLike = {
23
+ row: WorkflowStepRow;
24
+ };
25
+
26
+ export interface StepResult {
27
+ status: WorkflowStepStatus;
28
+ output: string;
29
+ exitCode?: number;
30
+ exitSignal?: string;
31
+ duration: number;
32
+ retries: number;
33
+ completionReason?: WorkflowStepCompletionReason;
34
+ error?: string;
35
+ }
36
+
37
+ export interface StepSchedule {
38
+ step: WorkflowStep;
39
+ readyAt: number;
40
+ staggerDelay: number;
41
+ }
42
+
43
+ export interface StepExecutorDeps<TState extends StateLike = StateLike> {
44
+ cwd: string;
45
+ runId?: string;
46
+ postToChannel?: (text: string) => void;
47
+ persistStepRow?: (stepId: string, patch: Partial<WorkflowStepRow>) => Promise<void>;
48
+ persistStepOutput?: (runId: string, stepName: string, output: string) => Promise<void>;
49
+ resolveTemplate?: (template: string, context: Record<string, unknown>) => string;
50
+ getStepOutput?: (stepName: string) => string | undefined;
51
+ loadStepOutput?: (runId: string, stepName: string) => string | undefined;
52
+ checkAborted?: () => void;
53
+ waitIfPaused?: () => Promise<void>;
54
+ log?: (message: string) => void;
55
+ processSpawner?: ProcessSpawner;
56
+ templateResolver?: TemplateResolver;
57
+ channelMessenger?: ChannelMessenger;
58
+ verificationRunner?: (
59
+ check: VerificationCheck,
60
+ output: string,
61
+ stepName: string,
62
+ injectedTaskText?: string,
63
+ options?: VerificationOptions,
64
+ sideEffects?: VerificationSideEffects
65
+ ) => VerificationResult;
66
+ executeStep?: (
67
+ step: WorkflowStep,
68
+ state: TState,
69
+ agentMap: Map<string, AgentDefinition>,
70
+ errorHandling?: ErrorHandlingConfig
71
+ ) => Promise<Partial<StepResult> | void>;
72
+ onStepStarted?: (step: WorkflowStep, state: TState) => Promise<void> | void;
73
+ onStepRetried?: (
74
+ step: WorkflowStep,
75
+ state: TState,
76
+ attempt: number,
77
+ maxRetries: number
78
+ ) => Promise<void> | void;
79
+ onStepCompleted?: (step: WorkflowStep, state: TState, result: StepResult) => Promise<void> | void;
80
+ onStepFailed?: (step: WorkflowStep, state: TState, result: StepResult) => Promise<void> | void;
81
+ onBeginTrack?: (steps: WorkflowStep[]) => Promise<void> | void;
82
+ onConverge?: (steps: WorkflowStep[], outcomes: StepOutcome[]) => Promise<void> | void;
83
+ markDownstreamSkipped?: (failedStepName: string) => Promise<void>;
84
+ buildCompletionMode?: (
85
+ stepName: string,
86
+ completionReason?: WorkflowStepCompletionReason
87
+ ) => StepCompletionMode | undefined;
88
+ }
89
+
90
+ export interface MonitorStepOptions<TState extends StateLike, TResult> {
91
+ maxRetries?: number;
92
+ retryDelayMs?: number;
93
+ startMessage?: string;
94
+ onStart?: (attempt: number, state: TState) => Promise<void> | void;
95
+ onRetry?: (attempt: number, maxRetries: number, state: TState) => Promise<void> | void;
96
+ execute: (attempt: number, state: TState) => Promise<TResult>;
97
+ toCompletionResult: (result: TResult, attempt: number, state: TState) => Partial<StepResult>;
98
+ onAttemptFailed?: (error: unknown, attempt: number, state: TState) => Promise<void> | void;
99
+ getFailureResult?: (error: unknown, attempt: number, state: TState) => Partial<StepResult>;
100
+ }
101
+
102
+ export class StepExecutor<TState extends StateLike = StateLike> {
103
+ private readonly templateResolver: TemplateResolver;
104
+ private readonly channelMessenger: ChannelMessenger;
105
+ private readonly verificationRunner: NonNullable<StepExecutorDeps<TState>['verificationRunner']>;
106
+
107
+ constructor(private readonly deps: StepExecutorDeps<TState>) {
108
+ this.templateResolver = deps.templateResolver ?? new TemplateResolver();
109
+ this.channelMessenger = deps.channelMessenger ?? new ChannelMessenger({ postFn: deps.postToChannel });
110
+ this.verificationRunner = deps.verificationRunner ?? runVerification;
111
+ }
112
+
113
+ findReady(
114
+ steps: WorkflowStep[],
115
+ statuses: Map<string, WorkflowStepStatus> | Map<string, TState>
116
+ ): WorkflowStep[] {
117
+ return steps.filter((step) => {
118
+ const state = statuses.get(step.name);
119
+ const status = this.getStatus(state);
120
+ if (status !== 'pending') return false;
121
+
122
+ return (step.dependsOn ?? []).every((dependency) => {
123
+ const depState = statuses.get(dependency);
124
+ const depStatus = this.getStatus(depState);
125
+ return depStatus === 'completed' || depStatus === 'skipped';
126
+ });
127
+ });
128
+ }
129
+
130
+ /** @deprecated Use {@link findReady} instead. This is an alias kept for backward compatibility. */
131
+ findReadySteps(
132
+ steps: WorkflowStep[],
133
+ statuses: Map<string, WorkflowStepStatus> | Map<string, TState>
134
+ ): WorkflowStep[] {
135
+ return this.findReady(steps, statuses);
136
+ }
137
+
138
+ scheduleStep(step: WorkflowStep, options: { readyAt?: number; staggerDelay?: number } = {}): StepSchedule {
139
+ return {
140
+ step,
141
+ readyAt: options.readyAt ?? Date.now(),
142
+ staggerDelay: options.staggerDelay ?? 0,
143
+ };
144
+ }
145
+
146
+ async startStep(step: WorkflowStep, state: TState, startMessage?: string): Promise<void> {
147
+ const startedAt = new Date().toISOString();
148
+ state.row.status = 'running';
149
+ state.row.error = undefined;
150
+ state.row.completionReason = undefined;
151
+ state.row.startedAt = startedAt;
152
+
153
+ await this.deps.persistStepRow?.(state.row.id, {
154
+ status: 'running',
155
+ error: undefined,
156
+ completionReason: undefined,
157
+ startedAt,
158
+ updatedAt: new Date().toISOString(),
159
+ });
160
+
161
+ if (startMessage) {
162
+ this.deps.postToChannel?.(startMessage);
163
+ }
164
+ await this.deps.onStepStarted?.(step, state);
165
+ }
166
+
167
+ async retryStep(step: WorkflowStep, state: TState, attempt: number, maxRetries: number): Promise<void> {
168
+ state.row.retryCount = attempt;
169
+ await this.deps.persistStepRow?.(state.row.id, {
170
+ retryCount: attempt,
171
+ updatedAt: new Date().toISOString(),
172
+ });
173
+ await this.deps.onStepRetried?.(step, state, attempt, maxRetries);
174
+ }
175
+
176
+ async completeStep(step: WorkflowStep, state: TState, result: Partial<StepResult>): Promise<StepResult> {
177
+ const completedAt = new Date().toISOString();
178
+ const finalResult: StepResult = {
179
+ status: result.status ?? 'completed',
180
+ output: result.output ?? '',
181
+ exitCode: result.exitCode,
182
+ exitSignal: result.exitSignal,
183
+ duration: result.duration ?? 0,
184
+ retries: result.retries ?? state.row.retryCount,
185
+ completionReason: result.completionReason,
186
+ error: result.error,
187
+ };
188
+
189
+ state.row.status = finalResult.status;
190
+ state.row.output = finalResult.output;
191
+ state.row.error = finalResult.error;
192
+ state.row.completionReason = finalResult.completionReason;
193
+ state.row.completedAt = completedAt;
194
+
195
+ await this.deps.persistStepRow?.(state.row.id, {
196
+ status: finalResult.status,
197
+ output: finalResult.output,
198
+ error: finalResult.error,
199
+ completionReason: finalResult.completionReason,
200
+ completedAt,
201
+ updatedAt: new Date().toISOString(),
202
+ });
203
+ if (finalResult.status === 'completed' && this.deps.runId && finalResult.output) {
204
+ await this.deps.persistStepOutput?.(this.deps.runId, step.name, finalResult.output);
205
+ }
206
+
207
+ if (finalResult.status === 'failed') {
208
+ await this.deps.onStepFailed?.(step, state, finalResult);
209
+ } else {
210
+ await this.deps.onStepCompleted?.(step, state, finalResult);
211
+ }
212
+ return finalResult;
213
+ }
214
+
215
+ async monitorStep<TResult>(
216
+ step: WorkflowStep,
217
+ state: TState,
218
+ options: MonitorStepOptions<TState, TResult>
219
+ ): Promise<StepResult> {
220
+ const maxRetries = options.maxRetries ?? 0;
221
+ const retryDelayMs = options.retryDelayMs ?? 1000;
222
+ let lastError: unknown;
223
+
224
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
225
+ this.deps.checkAborted?.();
226
+ await this.deps.waitIfPaused?.();
227
+
228
+ if (attempt > 0) {
229
+ await this.retryStep(step, state, attempt, maxRetries);
230
+ await options.onRetry?.(attempt, maxRetries, state);
231
+ if (retryDelayMs > 0) {
232
+ await delay(retryDelayMs);
233
+ }
234
+ }
235
+
236
+ const attemptStartedAt = Date.now();
237
+ await this.startStep(step, state, options.startMessage);
238
+ await options.onStart?.(attempt, state);
239
+
240
+ try {
241
+ const rawResult = await options.execute(attempt, state);
242
+ const completion = options.toCompletionResult(rawResult, attempt, state);
243
+ return await this.completeStep(step, state, {
244
+ ...completion,
245
+ duration: completion.duration ?? Date.now() - attemptStartedAt,
246
+ retries: completion.retries ?? attempt,
247
+ });
248
+ } catch (error) {
249
+ lastError = error;
250
+ await options.onAttemptFailed?.(error, attempt, state);
251
+ }
252
+ }
253
+
254
+ const failure = options.getFailureResult?.(lastError, maxRetries, state) ?? {
255
+ status: 'failed' as const,
256
+ output: '',
257
+ error: lastError instanceof Error ? lastError.message : String(lastError ?? 'Unknown error'),
258
+ retries: maxRetries,
259
+ };
260
+ return this.completeStep(step, state, {
261
+ ...failure,
262
+ status: 'failed',
263
+ });
264
+ }
265
+
266
+ async executeAll(
267
+ steps: WorkflowStep[],
268
+ agentMap: Map<string, AgentDefinition>,
269
+ errorHandling?: ErrorHandlingConfig,
270
+ providedStates?: Map<string, TState>
271
+ ): Promise<Map<string, StepResult>> {
272
+ const states = providedStates ?? this.createEphemeralStates(steps);
273
+ const strategy = normalizeStrategy(errorHandling?.strategy ?? 'fail-fast');
274
+ const results = new Map<string, StepResult>();
275
+
276
+ while (true) {
277
+ this.deps.checkAborted?.();
278
+ await this.deps.waitIfPaused?.();
279
+
280
+ const readySteps = this.findReady(steps, states);
281
+ if (readySteps.length === 0) break;
282
+
283
+ const schedules = readySteps.map((step, index) =>
284
+ this.scheduleStep(step, {
285
+ readyAt: Date.now(),
286
+ staggerDelay: readySteps.length > 3 ? index * 2_000 : 0,
287
+ })
288
+ );
289
+
290
+ if (schedules.length > 1) {
291
+ await this.deps.onBeginTrack?.(readySteps);
292
+ }
293
+
294
+ const settled = await Promise.allSettled(
295
+ schedules.map(async (schedule) => {
296
+ if (schedule.staggerDelay > 0) {
297
+ await delay(schedule.staggerDelay);
298
+ }
299
+ return this.executeScheduledStep(schedule.step, states, agentMap, errorHandling);
300
+ })
301
+ );
302
+
303
+ const batchOutcomes: StepOutcome[] = [];
304
+
305
+ for (let index = 0; index < settled.length; index += 1) {
306
+ const settledResult = settled[index];
307
+ const step = readySteps[index];
308
+ const state = states.get(step.name);
309
+
310
+ if (settledResult.status === 'fulfilled') {
311
+ const result = settledResult.value;
312
+ const outcomeStatus =
313
+ result.status === 'completed' || result.status === 'skipped' ? result.status : 'failed';
314
+ results.set(step.name, result);
315
+ batchOutcomes.push({
316
+ name: step.name,
317
+ agent: step.agent ?? 'deterministic',
318
+ status: outcomeStatus,
319
+ attempts: result.retries + 1,
320
+ output: result.output,
321
+ error: result.error,
322
+ verificationPassed: outcomeStatus === 'completed' && step.verification !== undefined,
323
+ completionMode:
324
+ result.completionReason !== undefined
325
+ ? this.deps.buildCompletionMode?.(step.name, result.completionReason)
326
+ : undefined,
327
+ });
328
+
329
+ if (result.status === 'failed') {
330
+ await this.deps.markDownstreamSkipped?.(step.name);
331
+ if (strategy === 'fail-fast') {
332
+ throw new Error(`Step "${step.name}" failed: ${result.error ?? 'unknown error'}`);
333
+ }
334
+ }
335
+ continue;
336
+ }
337
+
338
+ const error =
339
+ settledResult.reason instanceof Error ? settledResult.reason.message : String(settledResult.reason);
340
+ if (state) {
341
+ const failed =
342
+ state.row.status === 'failed'
343
+ ? {
344
+ status: 'failed' as const,
345
+ output: state.row.output ?? '',
346
+ duration: 0,
347
+ retries: state.row.retryCount,
348
+ completionReason: state.row.completionReason,
349
+ error: state.row.error ?? error,
350
+ }
351
+ : await this.completeStep(step, state, {
352
+ status: 'failed',
353
+ output: '',
354
+ error,
355
+ retries: state.row.retryCount,
356
+ });
357
+ results.set(step.name, failed);
358
+ }
359
+ batchOutcomes.push({
360
+ name: step.name,
361
+ agent: step.agent ?? 'deterministic',
362
+ status: 'failed',
363
+ attempts: (state?.row.retryCount ?? 0) + 1,
364
+ error,
365
+ });
366
+ await this.deps.markDownstreamSkipped?.(step.name);
367
+ if (strategy === 'fail-fast') {
368
+ throw new Error(`Step "${step.name}" failed: ${error}`);
369
+ }
370
+ }
371
+
372
+ if (readySteps.length > 1 && batchOutcomes.length > 0) {
373
+ await this.deps.onConverge?.(readySteps, batchOutcomes);
374
+ }
375
+ }
376
+
377
+ return results;
378
+ }
379
+
380
+ async executeOne(
381
+ step: WorkflowStep,
382
+ agentMap: Map<string, AgentDefinition>,
383
+ errorHandling?: ErrorHandlingConfig,
384
+ providedState?: TState
385
+ ): Promise<StepResult> {
386
+ const state = providedState ?? this.createEphemeralState(step);
387
+ if (this.deps.executeStep) {
388
+ const result = await this.deps.executeStep(step, state, agentMap, errorHandling);
389
+ if (state.row.status !== 'pending' && state.row.status !== 'running') {
390
+ return {
391
+ status: state.row.status,
392
+ output: state.row.output ?? '',
393
+ duration: result?.duration ?? 0,
394
+ retries: result?.retries ?? state.row.retryCount,
395
+ exitCode: result?.exitCode,
396
+ exitSignal: result?.exitSignal,
397
+ completionReason: state.row.completionReason ?? result?.completionReason,
398
+ error: state.row.error ?? result?.error,
399
+ };
400
+ }
401
+ return this.completeStep(step, state, {
402
+ status: result?.status ?? 'completed',
403
+ output: result?.output ?? '',
404
+ exitCode: result?.exitCode,
405
+ exitSignal: result?.exitSignal,
406
+ completionReason: result?.completionReason,
407
+ retries: result?.retries ?? state.row.retryCount,
408
+ duration: result?.duration ?? 0,
409
+ error: result?.error,
410
+ });
411
+ }
412
+
413
+ return this.executeWithProcessSpawner(step, state, agentMap, errorHandling);
414
+ }
415
+
416
+ async markFailed(stepName: string, error: string): Promise<void> {
417
+ this.deps.postToChannel?.(`**[${stepName}]** Failed: ${error}`);
418
+ }
419
+
420
+ buildStepOutputContext(stepStates: Map<string, TState>): Record<string, { output: string }> {
421
+ const steps: Record<string, { output: string }> = {};
422
+ for (const [name, state] of stepStates) {
423
+ if (state.row.status === 'completed' && state.row.output !== undefined) {
424
+ steps[name] = { output: state.row.output };
425
+ continue;
426
+ }
427
+ if (state.row.status === 'completed' && this.deps.runId) {
428
+ const persisted = this.deps.loadStepOutput?.(this.deps.runId, name);
429
+ if (persisted !== undefined) {
430
+ state.row.output = persisted;
431
+ steps[name] = { output: persisted };
432
+ }
433
+ }
434
+ }
435
+ return steps;
436
+ }
437
+
438
+ resolveStepTemplate(template: string, context: Record<string, unknown>): string {
439
+ if (this.deps.resolveTemplate) {
440
+ return this.deps.resolveTemplate(template, context);
441
+ }
442
+ return this.templateResolver.interpolateStepTask(template, context);
443
+ }
444
+
445
+ getChannelMessenger(): ChannelMessenger {
446
+ return this.channelMessenger;
447
+ }
448
+
449
+ runVerification(
450
+ check: VerificationCheck,
451
+ output: string,
452
+ stepName: string,
453
+ injectedTaskText?: string,
454
+ options?: VerificationOptions
455
+ ): VerificationResult {
456
+ return this.verificationRunner(check, output, stepName, injectedTaskText, {
457
+ ...options,
458
+ cwd: options?.cwd ?? this.deps.cwd,
459
+ });
460
+ }
461
+
462
+ private async executeScheduledStep(
463
+ step: WorkflowStep,
464
+ states: Map<string, TState>,
465
+ agentMap: Map<string, AgentDefinition>,
466
+ errorHandling?: ErrorHandlingConfig
467
+ ): Promise<StepResult> {
468
+ const state = states.get(step.name) ?? this.createEphemeralState(step);
469
+ if (!states.has(step.name)) {
470
+ states.set(step.name, state);
471
+ }
472
+ return this.executeOne(step, agentMap, errorHandling, state);
473
+ }
474
+
475
+ private async executeWithProcessSpawner(
476
+ step: WorkflowStep,
477
+ state: TState,
478
+ agentMap: Map<string, AgentDefinition>,
479
+ errorHandling?: ErrorHandlingConfig
480
+ ): Promise<StepResult> {
481
+ const spawner = this.deps.processSpawner;
482
+ if (!spawner) {
483
+ throw new Error(`No step execution callback or process spawner configured for step "${step.name}"`);
484
+ }
485
+
486
+ const maxRetries = step.retries ?? errorHandling?.maxRetries ?? 0;
487
+ return this.monitorStep(step, state, {
488
+ maxRetries,
489
+ retryDelayMs: errorHandling?.retryDelayMs ?? 1000,
490
+ startMessage: `**[${step.name}]** Started`,
491
+ onRetry: (attempt, total) => {
492
+ this.deps.postToChannel?.(`**[${step.name}]** Retrying (attempt ${attempt + 1}/${total + 1})`);
493
+ },
494
+ execute: async () => {
495
+ if (step.type === 'deterministic') {
496
+ const command = step.command ?? '';
497
+ return spawner.spawnShell(command, { cwd: this.deps.cwd, timeoutMs: step.timeoutMs });
498
+ }
499
+
500
+ const agent = step.agent ? agentMap.get(step.agent) : undefined;
501
+ if (!agent) {
502
+ throw new Error(`Agent "${step.agent ?? '(missing)'}" not found in config`);
503
+ }
504
+
505
+ const task = step.task ?? '';
506
+ if (agent.interactive === false) {
507
+ return spawner.spawnAgent(agent, task, { cwd: this.deps.cwd, timeoutMs: step.timeoutMs });
508
+ }
509
+ return spawner.spawnInteractive(agent, task, { cwd: this.deps.cwd, timeoutMs: step.timeoutMs });
510
+ },
511
+ toCompletionResult: (spawnResult, attempt) => {
512
+ const failOnError = step.failOnError !== false;
513
+ const failed =
514
+ failOnError &&
515
+ ((spawnResult.exitCode ?? 0) !== 0 ||
516
+ (spawnResult.exitCode === undefined && spawnResult.exitSignal !== undefined));
517
+ const output =
518
+ step.captureOutput === false
519
+ ? `Command completed (exit code ${spawnResult.exitCode ?? 0})`
520
+ : spawnResult.output;
521
+
522
+ if (failed) {
523
+ return {
524
+ status: 'failed' as const,
525
+ output,
526
+ exitCode: spawnResult.exitCode,
527
+ exitSignal: spawnResult.exitSignal,
528
+ retries: attempt,
529
+ error: spawnResult.output || `Command failed with exit code ${spawnResult.exitCode ?? 'unknown'}`,
530
+ };
531
+ }
532
+
533
+ return {
534
+ status: 'completed' as const,
535
+ output,
536
+ exitCode: spawnResult.exitCode,
537
+ exitSignal: spawnResult.exitSignal,
538
+ retries: attempt,
539
+ };
540
+ },
541
+ });
542
+ }
543
+
544
+ private createEphemeralStates(steps: WorkflowStep[]): Map<string, TState> {
545
+ return new Map(steps.map((step) => [step.name, this.createEphemeralState(step)]));
546
+ }
547
+
548
+ private createEphemeralState(step: WorkflowStep): TState {
549
+ return {
550
+ row: {
551
+ id: `step-${step.name}`,
552
+ runId: this.deps.runId ?? 'run',
553
+ stepName: step.name,
554
+ agentName: step.agent ?? null,
555
+ stepType: step.type ?? 'agent',
556
+ status: 'pending',
557
+ task: step.task ?? step.command ?? step.branch ?? '',
558
+ dependsOn: step.dependsOn ?? [],
559
+ retryCount: 0,
560
+ createdAt: new Date().toISOString(),
561
+ updatedAt: new Date().toISOString(),
562
+ },
563
+ } as TState;
564
+ }
565
+
566
+ private getStatus(state: WorkflowStepStatus | TState | undefined): WorkflowStepStatus | undefined {
567
+ if (typeof state === 'string') return state;
568
+ return state?.row.status;
569
+ }
570
+ }
571
+
572
+ function normalizeStrategy(strategy: ErrorHandlingConfig['strategy']): 'fail-fast' | 'continue' {
573
+ if (strategy === 'continue') return 'continue';
574
+ return 'fail-fast';
575
+ }
576
+
577
+ function delay(ms: number): Promise<void> {
578
+ return new Promise((resolve) => setTimeout(resolve, ms));
579
+ }