bare-agent 0.2.1 → 0.2.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  # bare-agent
15
15
 
16
- **Agent orchestration in ~1500 lines. Zero required deps. MIT license.**
16
+ **Agent orchestration in ~1700 lines. Zero required deps. MIT license.**
17
17
 
18
18
  Everything between "call the LLM" and "ship the agent" — loop, plan, remember, schedule, checkpoint. Each works alone. All compose together.
19
19
 
@@ -47,6 +47,7 @@ Three layers. You use the first two. You bring the third.
47
47
  | **Planner** | Goal -> step DAG | Structured output prompt, LLM returns JSON dependency graph |
48
48
  | **State** | Task lifecycle tracking | `pending -> running -> done \| failed`, persisted to JSON file |
49
49
  | **Stream** | Event streaming | One JSON object per line to stdout, pipe-friendly, any-language |
50
+ | **Errors** | Typed error hierarchy | `BareAgentError` base, `ProviderError`, `ToolError`, `TimeoutError`, `CircuitOpenError` |
50
51
 
51
52
  ### Layer 2: EXECUTION — how the agent thinks, remembers, acts, and persist?
52
53
 
@@ -56,7 +57,9 @@ Three layers. You use the first two. You bring the third.
56
57
  | **Scheduler** | Time-triggered turns | Cron (`0 7 * * 1-5`), relative (`2h`, `30m`), persisted jobs |
57
58
  | **Memory** | Persist + search | SQLite FTS5 with BM25 (default), JSON file fallback (zero deps) |
58
59
  | **Checkpoint** | Human approval gate | You provide the transport — readline, Telegram, WebSocket |
59
- | **Retry** | Backoff on failure | Exponential/linear, retries on 429/5xx/network errors |
60
+ | **Retry** | Backoff on failure | Exponential/linear with jitter, retries on 429/5xx/network errors |
61
+ | **CircuitBreaker** | Fail-fast on repeated errors | Per-key threshold, auto half-open probe, `wrapProvider()` |
62
+ | **Fallback** | Multi-provider resilience | Tries providers in order, AggregateError if all fail |
60
63
 
61
64
  ### Layer 3: ACTUATION — you provide this
62
65
 
@@ -159,17 +162,42 @@ const loop = new Loop({
159
162
  await loop.runGoal('Book my Berlin trip for next Tuesday');
160
163
  ```
161
164
 
165
+ ### Resilient multi-provider — circuit breaker + fallback + jitter
166
+
167
+ ```javascript
168
+ const { Loop, Retry, CircuitBreaker } = require('bare-agent');
169
+ const { OpenAI, Anthropic, Fallback } = require('bare-agent/providers');
170
+
171
+ const cb = new CircuitBreaker({ threshold: 3, resetAfter: 30000 });
172
+
173
+ const provider = new Fallback([
174
+ cb.wrapProvider(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), 'openai'),
175
+ cb.wrapProvider(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }), 'anthropic'),
176
+ ]);
177
+
178
+ const loop = new Loop({
179
+ provider,
180
+ retry: new Retry({ maxAttempts: 3, jitter: 'full' }),
181
+ });
182
+
183
+ const result = await loop.run([
184
+ { role: 'user', content: 'Summarize today\'s news' }
185
+ ]);
186
+ ```
187
+
162
188
  ---
163
189
 
164
190
  ## LLM Providers
165
191
 
166
- Three built-in. All implement one method: `generate(messages, tools, options) -> { text, toolCalls, usage }`.
192
+ All implement one method: `generate(messages, tools, options) -> { text, toolCalls, usage }`.
167
193
 
168
194
  | Provider | Covers |
169
195
  |---|---|
170
196
  | **OpenAI** | OpenAI, OpenRouter, Together, Groq, vLLM, LM Studio — any OpenAI-compatible endpoint |
171
197
  | **Anthropic** | Claude models via native API |
172
198
  | **Ollama** | Local models, no API key needed |
199
+ | **CLIPipe** | Any CLI tool via stdin/stdout (claude, ollama run, etc.) |
200
+ | **Fallback** | Tries multiple providers in order — transparent to Loop |
173
201
  | **Bring your own** | Implement `generate()` — one method, full control |
174
202
 
175
203
  ## Storage
@@ -217,7 +245,7 @@ Same pattern works from Go, Rust, Java, Ruby — any language that can spawn a p
217
245
  required: 0
218
246
  optional: cron-parser (for cron expressions in scheduler)
219
247
  peer: better-sqlite3 (for SQLite memory store)
220
- total lines: ~1500
248
+ total lines: ~1700
221
249
  ```
222
250
 
223
251
  ## Status
package/index.js CHANGED
@@ -9,6 +9,15 @@ const { Memory } = require('./src/memory');
9
9
  const { Stream } = require('./src/stream');
10
10
  const { Retry } = require('./src/retry');
11
11
  const { runPlan } = require('./src/run-plan');
12
+ const { CircuitBreaker } = require('./src/circuit-breaker');
13
+ const {
14
+ BareAgentError,
15
+ ProviderError,
16
+ ToolError,
17
+ TimeoutError,
18
+ ValidationError,
19
+ CircuitOpenError,
20
+ } = require('./src/errors');
12
21
 
13
22
  module.exports = {
14
23
  Loop,
@@ -20,4 +29,11 @@ module.exports = {
20
29
  Stream,
21
30
  Retry,
22
31
  runPlan,
32
+ CircuitBreaker,
33
+ BareAgentError,
34
+ ProviderError,
35
+ ToolError,
36
+ TimeoutError,
37
+ ValidationError,
38
+ CircuitOpenError,
23
39
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "bare-agent",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "files": [
5
5
  "index.js",
6
6
  "src/",
7
7
  "bin/"
8
8
  ],
9
- "description": "Lightweight, composable agent orchestration. ~800 lines, 0 required deps.",
9
+ "description": "Lightweight, composable agent orchestration. ~1700 lines, 0 required deps.",
10
10
  "license": "MIT",
11
11
  "author": "hamr0",
12
12
  "repository": {
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ const { CircuitOpenError } = require('./errors');
4
+
5
+ class CircuitBreaker {
6
+ /**
7
+ * @param {object} [options={}]
8
+ * @param {number} [options.threshold=5] - Failures before opening.
9
+ * @param {number} [options.resetAfter=60000] - Ms before half-open probe.
10
+ * @param {function} [options.onStateChange] - Callback(key, from, to).
11
+ */
12
+ constructor(options = {}) {
13
+ this.threshold = options.threshold || 5;
14
+ this.resetAfter = options.resetAfter || 60000;
15
+ this.onStateChange = options.onStateChange || null;
16
+ this._keys = new Map();
17
+ }
18
+
19
+ _getEntry(key) {
20
+ if (!this._keys.has(key)) {
21
+ this._keys.set(key, { state: 'closed', failures: 0, openedAt: 0, generation: 0 });
22
+ }
23
+ return this._keys.get(key);
24
+ }
25
+
26
+ _setState(entry, key, newState) {
27
+ const from = entry.state;
28
+ if (from === newState) return;
29
+ entry.state = newState;
30
+ this.onStateChange?.(key, from, newState);
31
+ }
32
+
33
+ /**
34
+ * Execute fn through the circuit breaker.
35
+ * @param {function} fn - Async function to call.
36
+ * @param {string} [key='default'] - Circuit key for per-key isolation.
37
+ * @returns {Promise<*>}
38
+ * @throws {CircuitOpenError} When circuit is open.
39
+ */
40
+ async call(fn, key = 'default') {
41
+ const entry = this._getEntry(key);
42
+
43
+ if (entry.state === 'open') {
44
+ if (Date.now() - entry.openedAt >= this.resetAfter) {
45
+ this._setState(entry, key, 'half-open');
46
+ entry.generation++;
47
+ } else {
48
+ throw new CircuitOpenError(`Circuit "${key}" is open`);
49
+ }
50
+ }
51
+
52
+ const gen = entry.generation;
53
+
54
+ try {
55
+ const result = await fn();
56
+ // Only close if still same generation (prevents stale half-open races)
57
+ if (entry.generation === gen) {
58
+ entry.failures = 0;
59
+ if (entry.state === 'half-open') {
60
+ this._setState(entry, key, 'closed');
61
+ }
62
+ }
63
+ return result;
64
+ } catch (err) {
65
+ if (entry.generation === gen) {
66
+ entry.failures++;
67
+ if (entry.state === 'half-open') {
68
+ entry.openedAt = Date.now();
69
+ this._setState(entry, key, 'open');
70
+ } else if (entry.failures >= this.threshold) {
71
+ entry.openedAt = Date.now();
72
+ this._setState(entry, key, 'open');
73
+ }
74
+ }
75
+ throw err;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get current state for a key.
81
+ * @param {string} [key='default']
82
+ * @returns {'closed'|'open'|'half-open'}
83
+ */
84
+ getState(key = 'default') {
85
+ return this._getEntry(key).state;
86
+ }
87
+
88
+ /**
89
+ * Force reset a key to closed.
90
+ * @param {string} [key='default']
91
+ */
92
+ reset(key = 'default') {
93
+ const entry = this._getEntry(key);
94
+ entry.failures = 0;
95
+ entry.openedAt = 0;
96
+ this._setState(entry, key, 'closed');
97
+ }
98
+
99
+ /**
100
+ * Wrap a provider so generate() goes through the circuit breaker.
101
+ * @param {object} provider - Provider with generate().
102
+ * @param {string} [key] - Circuit key.
103
+ * @returns {object} Wrapped provider with generate().
104
+ */
105
+ wrapProvider(provider, key) {
106
+ return {
107
+ generate: (...args) => this.call(() => provider.generate(...args), key),
108
+ };
109
+ }
110
+ }
111
+
112
+ module.exports = { CircuitBreaker };
package/src/errors.js ADDED
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ class BareAgentError extends Error {
4
+ constructor(message, { code, retryable = false, context = {} } = {}) {
5
+ super(message);
6
+ this.name = this.constructor.name;
7
+ this.code = code || undefined;
8
+ this.retryable = retryable;
9
+ this.context = context;
10
+ }
11
+ }
12
+
13
+ class ProviderError extends BareAgentError {
14
+ constructor(message, { status, body, context = {} } = {}) {
15
+ const retryable = status === 429 || (status >= 500 && status <= 504);
16
+ super(message, { code: 'PROVIDER_ERROR', retryable, context });
17
+ this.status = status;
18
+ this.body = body;
19
+ }
20
+ }
21
+
22
+ class ToolError extends BareAgentError {
23
+ constructor(message, opts = {}) {
24
+ super(message, { code: 'TOOL_ERROR', retryable: false, ...opts });
25
+ }
26
+ }
27
+
28
+ class TimeoutError extends BareAgentError {
29
+ constructor(message, opts = {}) {
30
+ super(message || 'Operation timed out', { code: 'ETIMEDOUT', retryable: true, ...opts });
31
+ }
32
+ }
33
+
34
+ class ValidationError extends BareAgentError {
35
+ constructor(message, opts = {}) {
36
+ super(message, { code: 'VALIDATION_ERROR', retryable: false, ...opts });
37
+ }
38
+ }
39
+
40
+ class CircuitOpenError extends BareAgentError {
41
+ constructor(message, opts = {}) {
42
+ super(message || 'Circuit breaker is open', { code: 'CIRCUIT_OPEN', retryable: true, ...opts });
43
+ }
44
+ }
45
+
46
+ module.exports = {
47
+ BareAgentError,
48
+ ProviderError,
49
+ ToolError,
50
+ TimeoutError,
51
+ ValidationError,
52
+ CircuitOpenError,
53
+ };
package/src/loop.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { ToolError } = require('./errors');
4
+
3
5
  class Loop {
4
6
  /**
5
7
  * @param {object} options
@@ -135,7 +137,8 @@ class Loop {
135
137
  msgs.push({ role: 'tool', tool_call_id: tc.id, content });
136
138
  this.stream?.emit({ type: 'loop:tool_result', data: { tool: tc.name, result: content } });
137
139
  } catch (err) {
138
- const errMsg = `[Loop] Tool error: ${err.message}`;
140
+ const toolErr = err instanceof ToolError ? err : new ToolError(err.message, { context: { tool: tc.name } });
141
+ const errMsg = `[Loop] Tool error: ${toolErr.message}`;
139
142
  msgs.push({ role: 'tool', tool_call_id: tc.id, content: errMsg });
140
143
  this.stream?.emit({ type: 'loop:tool_result', data: { tool: tc.name, error: errMsg } });
141
144
  }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const https = require('https');
4
+ const { ProviderError } = require('./errors');
4
5
 
5
6
  class AnthropicProvider {
6
7
  /**
@@ -123,10 +124,10 @@ class AnthropicProvider {
123
124
  try {
124
125
  const parsed = JSON.parse(chunks);
125
126
  if (res.statusCode >= 400) {
126
- const err = new Error(`[AnthropicProvider] ${parsed.error?.message || `HTTP ${res.statusCode}`}`);
127
- err.status = res.statusCode;
128
- err.body = parsed;
129
- return reject(err);
127
+ return reject(new ProviderError(
128
+ `[AnthropicProvider] ${parsed.error?.message || `HTTP ${res.statusCode}`}`,
129
+ { status: res.statusCode, body: parsed }
130
+ ));
130
131
  }
131
132
  resolve(parsed);
132
133
  } catch (e) {
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { spawn } = require('child_process');
4
+ const { ProviderError } = require('./errors');
4
5
 
5
6
  class CLIPipeProvider {
6
7
  /**
@@ -91,17 +92,17 @@ class CLIPipeProvider {
91
92
  child.stderr.on('data', d => { stderr += d; });
92
93
 
93
94
  child.on('error', err => {
94
- reject(new Error(`[CLIPipeProvider] failed to spawn "${this.command}": ${err.message}`));
95
+ reject(new ProviderError(`[CLIPipeProvider] failed to spawn "${this.command}": ${err.message}`, { status: 0 }));
95
96
  });
96
97
 
97
98
  child.on('close', code => {
98
99
  if (killed) return; // timeout already rejected
99
100
  if (code !== 0) {
100
- return reject(new Error(`[CLIPipeProvider] process exited with code ${code}: ${stderr.trim()}`));
101
+ return reject(new ProviderError(`[CLIPipeProvider] process exited with code ${code}: ${stderr.trim()}`, { status: code }));
101
102
  }
102
103
  const text = stdout.trim();
103
104
  if (!text) {
104
- return reject(new Error('[CLIPipeProvider] process produced no output'));
105
+ return reject(new ProviderError('[CLIPipeProvider] process produced no output', { status: 0 }));
105
106
  }
106
107
  resolve(text);
107
108
  });
@@ -113,7 +114,7 @@ class CLIPipeProvider {
113
114
  setTimeout(() => {
114
115
  try { child.kill('SIGKILL'); } catch (_) {}
115
116
  }, 1000);
116
- reject(new Error(`[CLIPipeProvider] timed out after ${this.timeout}ms`));
117
+ reject(new ProviderError(`[CLIPipeProvider] timed out after ${this.timeout}ms`, { status: 0 }));
117
118
  }, this.timeout);
118
119
 
119
120
  child.on('close', () => clearTimeout(timer));
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ class FallbackProvider {
4
+ /**
5
+ * Provider that tries multiple providers in order.
6
+ * @param {Array<object>} providers - Ordered list of providers with generate().
7
+ * @param {object} [options={}]
8
+ * @param {function} [options.shouldFallback] - (error, index) => boolean. Return false to stop.
9
+ * @param {function} [options.onFallback] - (error, fromIndex, toIndex) callback.
10
+ * @throws {Error} `[FallbackProvider] requires at least one provider` — when providers is empty.
11
+ */
12
+ constructor(providers, options = {}) {
13
+ if (!Array.isArray(providers) || providers.length === 0) {
14
+ throw new Error('[FallbackProvider] requires at least one provider');
15
+ }
16
+ this.providers = providers;
17
+ this.shouldFallback = options.shouldFallback || (() => true);
18
+ this.onFallback = options.onFallback || null;
19
+ }
20
+
21
+ /**
22
+ * Generate using first available provider.
23
+ * @param {Array<object>} messages
24
+ * @param {Array<object>} [tools=[]]
25
+ * @param {object} [options={}]
26
+ * @returns {Promise<{text: string, toolCalls: Array, usage: object}>}
27
+ * @throws {AggregateError} When all providers fail.
28
+ */
29
+ async generate(messages, tools = [], options = {}) {
30
+ const errors = [];
31
+
32
+ for (let i = 0; i < this.providers.length; i++) {
33
+ try {
34
+ return await this.providers[i].generate(messages, tools, options);
35
+ } catch (err) {
36
+ errors.push(err);
37
+ if (i < this.providers.length - 1) {
38
+ if (!this.shouldFallback(err, i)) throw err;
39
+ this.onFallback?.(err, i, i + 1);
40
+ }
41
+ }
42
+ }
43
+
44
+ throw new AggregateError(errors, '[FallbackProvider] all providers failed');
45
+ }
46
+ }
47
+
48
+ module.exports = { FallbackProvider };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const http = require('http');
4
+ const { ProviderError } = require('./errors');
4
5
 
5
6
  class OllamaProvider {
6
7
  constructor(options = {}) {
@@ -67,9 +68,10 @@ class OllamaProvider {
67
68
  try {
68
69
  const parsed = JSON.parse(chunks);
69
70
  if (res.statusCode >= 400) {
70
- const err = new Error(`[OllamaProvider] ${parsed.error || `HTTP ${res.statusCode}`}`);
71
- err.status = res.statusCode;
72
- return reject(err);
71
+ return reject(new ProviderError(
72
+ `[OllamaProvider] ${parsed.error || `HTTP ${res.statusCode}`}`,
73
+ { status: res.statusCode, body: parsed }
74
+ ));
73
75
  }
74
76
  resolve(parsed);
75
77
  } catch (e) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const https = require('https');
4
4
  const http = require('http');
5
+ const { ProviderError } = require('./errors');
5
6
 
6
7
  class OpenAIProvider {
7
8
  constructor(options = {}) {
@@ -70,10 +71,10 @@ class OpenAIProvider {
70
71
  try {
71
72
  const parsed = JSON.parse(chunks);
72
73
  if (res.statusCode >= 400) {
73
- const err = new Error(`[OpenAIProvider] ${parsed.error?.message || `HTTP ${res.statusCode}`}`);
74
- err.status = res.statusCode;
75
- err.body = parsed;
76
- return reject(err);
74
+ return reject(new ProviderError(
75
+ `[OpenAIProvider] ${parsed.error?.message || `HTTP ${res.statusCode}`}`,
76
+ { status: res.statusCode, body: parsed }
77
+ ));
77
78
  }
78
79
  resolve(parsed);
79
80
  } catch (e) {
package/src/providers.js CHANGED
@@ -4,10 +4,12 @@ const { OpenAIProvider } = require('./provider-openai');
4
4
  const { AnthropicProvider } = require('./provider-anthropic');
5
5
  const { OllamaProvider } = require('./provider-ollama');
6
6
  const { CLIPipeProvider } = require('./provider-clipipe');
7
+ const { FallbackProvider } = require('./provider-fallback');
7
8
 
8
9
  module.exports = {
9
10
  OpenAI: OpenAIProvider,
10
11
  Anthropic: AnthropicProvider,
11
12
  Ollama: OllamaProvider,
12
13
  CLIPipe: CLIPipeProvider,
14
+ Fallback: FallbackProvider,
13
15
  };
package/src/retry.js CHANGED
@@ -1,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ const { TimeoutError } = require('./errors');
4
+
3
5
  const DEFAULT_RETRY_ON = (err) => {
6
+ if (err.retryable === true) return true;
7
+ if (err.retryable === false) return false;
4
8
  const status = err.status || err.statusCode;
5
9
  if (status === 429 || (status >= 500 && status <= 504)) return true;
6
10
  const code = err.code;
@@ -14,6 +18,7 @@ class Retry {
14
18
  this.backoff = options.backoff || 'exponential';
15
19
  this.timeout = options.timeout || 60000;
16
20
  this.retryOn = options.retryOn || DEFAULT_RETRY_ON;
21
+ this.jitter = options.jitter !== undefined ? options.jitter : false;
17
22
  }
18
23
 
19
24
  /**
@@ -21,7 +26,7 @@ class Retry {
21
26
  * @param {() => Promise<*>} fn - Async function to execute.
22
27
  * @param {object} [options={}] - Per-call overrides for maxAttempts, retryOn, timeout.
23
28
  * @returns {Promise<*>} The result of fn().
24
- * @throws {Error} `[Retry] Timeout` — when an individual attempt exceeds the timeout.
29
+ * @throws {TimeoutError} When an individual attempt exceeds the timeout.
25
30
  * @throws {Error} Rethrows the last error when maxAttempts is exhausted or error is not retryable.
26
31
  */
27
32
  async call(fn, options = {}) {
@@ -32,7 +37,7 @@ class Retry {
32
37
  for (let attempt = 1; attempt <= max; attempt++) {
33
38
  try {
34
39
  const result = await (timeout
35
- ? Promise.race([fn(), new Promise((_, rej) => setTimeout(() => rej(Object.assign(new Error('[Retry] Timeout'), { code: 'ETIMEDOUT' })), timeout))])
40
+ ? Promise.race([fn(), new Promise((_, rej) => setTimeout(() => rej(new TimeoutError('[Retry] Timeout')), timeout))])
36
41
  : fn());
37
42
  return result;
38
43
  } catch (err) {
@@ -44,9 +49,30 @@ class Retry {
44
49
  }
45
50
 
46
51
  _delay(attempt) {
47
- if (typeof this.backoff === 'number') return this.backoff;
48
- if (this.backoff === 'linear') return attempt * 1000;
49
- return Math.min(2 ** (attempt - 1) * 1000, 30000); // exponential, cap 30s
52
+ let base;
53
+ if (typeof this.backoff === 'number') {
54
+ base = this.backoff;
55
+ } else if (this.backoff === 'linear') {
56
+ base = attempt * 1000;
57
+ } else {
58
+ base = Math.min(2 ** (attempt - 1) * 1000, 30000); // exponential, cap 30s
59
+ }
60
+ return this._applyJitter(base);
61
+ }
62
+
63
+ _applyJitter(base) {
64
+ if (this.jitter === false || this.jitter === 0) return base;
65
+ if (this.jitter === 'full') {
66
+ return Math.floor(Math.random() * base);
67
+ }
68
+ if (this.jitter === 'equal') {
69
+ return Math.floor(base / 2 + Math.random() * (base / 2));
70
+ }
71
+ if (typeof this.jitter === 'number') {
72
+ const spread = base * this.jitter;
73
+ return Math.floor(base - spread + Math.random() * spread);
74
+ }
75
+ return base;
50
76
  }
51
77
  }
52
78
 
package/src/run-plan.js CHANGED
@@ -48,7 +48,7 @@ async function runPlan(steps, executeFn, options = {}) {
48
48
  }
49
49
  }
50
50
 
51
- const { concurrency = Infinity, stateMachine, onStepStart, onStepDone, onStepFail, onWaveStart } = options;
51
+ const { concurrency = Infinity, stateMachine, onStepStart, onStepDone, onStepFail, onWaveStart, stepRetry } = options;
52
52
 
53
53
  let waveNumber = 0;
54
54
 
@@ -95,7 +95,9 @@ async function runPlan(steps, executeFn, options = {}) {
95
95
  onStepStart?.(step);
96
96
 
97
97
  try {
98
- const result = await executeFn(step);
98
+ const result = stepRetry
99
+ ? await stepRetry.call(() => executeFn(step))
100
+ : await executeFn(step);
99
101
  entry.status = 'done';
100
102
  entry.result = result;
101
103
  stateMachine?.transition(step.id, 'complete', result);