donobu 5.27.2 → 5.27.3

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.
@@ -108,24 +108,30 @@ export declare class DonobuFlow {
108
108
  /**
109
109
  * This method is called when a flow is complete (i.e. when {@link DonobuFlow.run} should return).
110
110
  *
111
- * Browser session state and the final metadata write are committed by
112
- * whichever code path produced the terminal state (transitionState for
113
- * tool-driven completion; the on*Failure handlers for failure paths) —
114
- * by the time we reach onComplete those have already happened.
111
+ * Browser session state and the terminal-state metadata write are
112
+ * committed by whichever code path produced the terminal state
113
+ * (transitionState for tool-driven completion; onTargetClosed /
114
+ * onPersistentGptFailure / onInsufficientQuota / onUnexpectedException
115
+ * for failure paths) — by the time we reach onComplete those have
116
+ * already happened. This method just runs the post-completion side
117
+ * effects.
115
118
  */
116
119
  private onComplete;
117
120
  /**
118
121
  * Persists the current browser session state if the flow's config has
119
- * `persistState` enabled. Must be called BEFORE every site that commits
120
- * a terminal `state` to persistence otherwise a frontend that polls
121
- * for the flow's state can observe "complete" between the metadata
122
- * write and the session-state upload, fire an eager browser-state
123
- * fetch (e.g. FlowDeveloperTools auto-loads on terminal), and 404 until
124
- * the upload finishes.
122
+ * `persistState` enabled. Must be called BEFORE the in-memory `state`
123
+ * is mutated to a terminal value at every site that produces a
124
+ * terminal state otherwise FlowCatalog.getFlowById can read the
125
+ * live FlowMetadata object (LOCAL deployments) and a frontend that
126
+ * observes the terminal state will race the (potentially network-
127
+ * bound) upload here, getting a 404 from a subsequent browser-state
128
+ * fetch.
125
129
  *
126
130
  * The browser context typically survives all-pages-closed (the read
127
- * goes against the context, not a specific page), so this is also
128
- * safe to call from the failure handlers like onTargetClosed.
131
+ * goes against the context, not a specific page), so this is safe to
132
+ * call from failure handlers like onTargetClosed. If the read does
133
+ * fail, persistSessionState catches and logs internally — it doesn't
134
+ * propagate.
129
135
  */
130
136
  private persistTerminalSessionStateIfNeeded;
131
137
  /**
@@ -215,10 +215,18 @@ class DonobuFlow {
215
215
  async onTargetClosed() {
216
216
  const result = await this.targetInspector.handleTargetClosed();
217
217
  if (!result.recovered) {
218
- this.metadata.state = 'FAILED';
218
+ // Persist browser state BEFORE flipping the in-memory `state` to
219
+ // a terminal value. FlowCatalog.getFlowById serves the *live*
220
+ // FlowMetadata object for LOCAL deployments, so the next frontend
221
+ // poll observes terminal state the moment we mutate `state`.
222
+ // If we do that before the (potentially network-bound) session
223
+ // upload, an eager browser-state fetch from the frontend (e.g.
224
+ // FlowDeveloperTools auto-loads on terminal) races the upload and
225
+ // 404s. Same rationale as the ordering in transitionState.
219
226
  Logger_1.appLogger.error(result.reason);
220
- this.metadata.result = { failed: result.reason };
221
227
  await this.persistTerminalSessionStateIfNeeded();
228
+ this.metadata.result = { failed: result.reason };
229
+ this.metadata.state = 'FAILED';
222
230
  await this.persistence.setFlowMetadata(this.metadata);
223
231
  }
224
232
  }
@@ -227,13 +235,13 @@ class DonobuFlow {
227
235
  * are internal retries). This method will mark the flow as a failure.
228
236
  */
229
237
  async onPersistentGptFailure(error) {
230
- this.metadata.state = 'FAILED';
231
238
  Logger_1.appLogger.error(`Stopped flow due to the ${this.gptClient?.config.type} GPT platform throwing an internal error!`, error);
239
+ await this.persistTerminalSessionStateIfNeeded();
232
240
  this.metadata.result = {
233
241
  failed: `Stopped flow due to the ${this.gptClient?.config.type} GPT platform throwing an internal error!`,
234
242
  context: error.message,
235
243
  };
236
- await this.persistTerminalSessionStateIfNeeded();
244
+ this.metadata.state = 'FAILED';
237
245
  await this.persistence.setFlowMetadata(this.metadata);
238
246
  }
239
247
  /**
@@ -241,15 +249,15 @@ class DonobuFlow {
241
249
  * usage quota or credits have been exhausted (HTTP 402).
242
250
  */
243
251
  async onInsufficientQuota(error) {
244
- this.metadata.state = 'FAILED';
245
252
  const platform = error.gptPlatform;
246
253
  const isDonobu = platform === 'DONOBU';
247
254
  const failedMessage = isDonobu
248
255
  ? 'Your Donobu AI credits have been exhausted. Please add more credits to your account to continue running flows.'
249
256
  : `Your ${platform} API quota has been exhausted. Please check your account's billing and usage limits; this may happen if there is a lack of funds in the account`;
250
257
  Logger_1.appLogger.error(failedMessage, error);
251
- this.metadata.result = { failed: failedMessage };
252
258
  await this.persistTerminalSessionStateIfNeeded();
259
+ this.metadata.result = { failed: failedMessage };
260
+ this.metadata.state = 'FAILED';
253
261
  await this.persistence.setFlowMetadata(this.metadata);
254
262
  }
255
263
  /**
@@ -339,21 +347,24 @@ class DonobuFlow {
339
347
  * method will mark the flow as a failure.
340
348
  */
341
349
  async onUnexpectedException(error) {
342
- this.metadata.state = 'FAILED';
343
350
  Logger_1.appLogger.error('Stopped flow due to exception!', error);
351
+ await this.persistTerminalSessionStateIfNeeded();
344
352
  this.metadata.result = {
345
353
  failed: 'Internal error 🙈',
346
354
  };
347
- await this.persistTerminalSessionStateIfNeeded();
355
+ this.metadata.state = 'FAILED';
348
356
  await this.persistence.setFlowMetadata(this.metadata);
349
357
  }
350
358
  /**
351
359
  * This method is called when a flow is complete (i.e. when {@link DonobuFlow.run} should return).
352
360
  *
353
- * Browser session state and the final metadata write are committed by
354
- * whichever code path produced the terminal state (transitionState for
355
- * tool-driven completion; the on*Failure handlers for failure paths) —
356
- * by the time we reach onComplete those have already happened.
361
+ * Browser session state and the terminal-state metadata write are
362
+ * committed by whichever code path produced the terminal state
363
+ * (transitionState for tool-driven completion; onTargetClosed /
364
+ * onPersistentGptFailure / onInsufficientQuota / onUnexpectedException
365
+ * for failure paths) — by the time we reach onComplete those have
366
+ * already happened. This method just runs the post-completion side
367
+ * effects.
357
368
  */
358
369
  async onComplete() {
359
370
  DonobuFlow.invokeFlowFinishedCallback(this.metadata.callbackUrl, this.metadata.id);
@@ -361,16 +372,19 @@ class DonobuFlow {
361
372
  }
362
373
  /**
363
374
  * Persists the current browser session state if the flow's config has
364
- * `persistState` enabled. Must be called BEFORE every site that commits
365
- * a terminal `state` to persistence otherwise a frontend that polls
366
- * for the flow's state can observe "complete" between the metadata
367
- * write and the session-state upload, fire an eager browser-state
368
- * fetch (e.g. FlowDeveloperTools auto-loads on terminal), and 404 until
369
- * the upload finishes.
375
+ * `persistState` enabled. Must be called BEFORE the in-memory `state`
376
+ * is mutated to a terminal value at every site that produces a
377
+ * terminal state otherwise FlowCatalog.getFlowById can read the
378
+ * live FlowMetadata object (LOCAL deployments) and a frontend that
379
+ * observes the terminal state will race the (potentially network-
380
+ * bound) upload here, getting a 404 from a subsequent browser-state
381
+ * fetch.
370
382
  *
371
383
  * The browser context typically survives all-pages-closed (the read
372
- * goes against the context, not a specific page), so this is also
373
- * safe to call from the failure handlers like onTargetClosed.
384
+ * goes against the context, not a specific page), so this is safe to
385
+ * call from failure handlers like onTargetClosed. If the read does
386
+ * fail, persistSessionState catches and logs internally — it doesn't
387
+ * propagate.
374
388
  */
375
389
  async persistTerminalSessionStateIfNeeded() {
376
390
  if (this.metadata.web?.browser?.persistState) {
@@ -647,10 +661,8 @@ Message: ${dialog.message()}`;
647
661
  // this, then someone polling for a flow's state may see the flow
648
662
  // finished but still see a null result.
649
663
  //
650
- // The same rationale applies to the browser session state: an eager
651
- // fetch from the frontend (e.g. FlowDeveloperTools auto-loads on
652
- // terminal state) would otherwise race the upload that onComplete
653
- // performs and 404 until the user manually refreshed.
664
+ // The same rationale applies to the browser session state see
665
+ // persistTerminalSessionStateIfNeeded for the full ordering note.
654
666
  if ((0, FlowMetadata_1.isComplete)(nextState)) {
655
667
  this.metadata.result = await this.createResultJson(nextState);
656
668
  await this.persistTerminalSessionStateIfNeeded();
@@ -108,24 +108,30 @@ export declare class DonobuFlow {
108
108
  /**
109
109
  * This method is called when a flow is complete (i.e. when {@link DonobuFlow.run} should return).
110
110
  *
111
- * Browser session state and the final metadata write are committed by
112
- * whichever code path produced the terminal state (transitionState for
113
- * tool-driven completion; the on*Failure handlers for failure paths) —
114
- * by the time we reach onComplete those have already happened.
111
+ * Browser session state and the terminal-state metadata write are
112
+ * committed by whichever code path produced the terminal state
113
+ * (transitionState for tool-driven completion; onTargetClosed /
114
+ * onPersistentGptFailure / onInsufficientQuota / onUnexpectedException
115
+ * for failure paths) — by the time we reach onComplete those have
116
+ * already happened. This method just runs the post-completion side
117
+ * effects.
115
118
  */
116
119
  private onComplete;
117
120
  /**
118
121
  * Persists the current browser session state if the flow's config has
119
- * `persistState` enabled. Must be called BEFORE every site that commits
120
- * a terminal `state` to persistence otherwise a frontend that polls
121
- * for the flow's state can observe "complete" between the metadata
122
- * write and the session-state upload, fire an eager browser-state
123
- * fetch (e.g. FlowDeveloperTools auto-loads on terminal), and 404 until
124
- * the upload finishes.
122
+ * `persistState` enabled. Must be called BEFORE the in-memory `state`
123
+ * is mutated to a terminal value at every site that produces a
124
+ * terminal state otherwise FlowCatalog.getFlowById can read the
125
+ * live FlowMetadata object (LOCAL deployments) and a frontend that
126
+ * observes the terminal state will race the (potentially network-
127
+ * bound) upload here, getting a 404 from a subsequent browser-state
128
+ * fetch.
125
129
  *
126
130
  * The browser context typically survives all-pages-closed (the read
127
- * goes against the context, not a specific page), so this is also
128
- * safe to call from the failure handlers like onTargetClosed.
131
+ * goes against the context, not a specific page), so this is safe to
132
+ * call from failure handlers like onTargetClosed. If the read does
133
+ * fail, persistSessionState catches and logs internally — it doesn't
134
+ * propagate.
129
135
  */
130
136
  private persistTerminalSessionStateIfNeeded;
131
137
  /**
@@ -215,10 +215,18 @@ class DonobuFlow {
215
215
  async onTargetClosed() {
216
216
  const result = await this.targetInspector.handleTargetClosed();
217
217
  if (!result.recovered) {
218
- this.metadata.state = 'FAILED';
218
+ // Persist browser state BEFORE flipping the in-memory `state` to
219
+ // a terminal value. FlowCatalog.getFlowById serves the *live*
220
+ // FlowMetadata object for LOCAL deployments, so the next frontend
221
+ // poll observes terminal state the moment we mutate `state`.
222
+ // If we do that before the (potentially network-bound) session
223
+ // upload, an eager browser-state fetch from the frontend (e.g.
224
+ // FlowDeveloperTools auto-loads on terminal) races the upload and
225
+ // 404s. Same rationale as the ordering in transitionState.
219
226
  Logger_1.appLogger.error(result.reason);
220
- this.metadata.result = { failed: result.reason };
221
227
  await this.persistTerminalSessionStateIfNeeded();
228
+ this.metadata.result = { failed: result.reason };
229
+ this.metadata.state = 'FAILED';
222
230
  await this.persistence.setFlowMetadata(this.metadata);
223
231
  }
224
232
  }
@@ -227,13 +235,13 @@ class DonobuFlow {
227
235
  * are internal retries). This method will mark the flow as a failure.
228
236
  */
229
237
  async onPersistentGptFailure(error) {
230
- this.metadata.state = 'FAILED';
231
238
  Logger_1.appLogger.error(`Stopped flow due to the ${this.gptClient?.config.type} GPT platform throwing an internal error!`, error);
239
+ await this.persistTerminalSessionStateIfNeeded();
232
240
  this.metadata.result = {
233
241
  failed: `Stopped flow due to the ${this.gptClient?.config.type} GPT platform throwing an internal error!`,
234
242
  context: error.message,
235
243
  };
236
- await this.persistTerminalSessionStateIfNeeded();
244
+ this.metadata.state = 'FAILED';
237
245
  await this.persistence.setFlowMetadata(this.metadata);
238
246
  }
239
247
  /**
@@ -241,15 +249,15 @@ class DonobuFlow {
241
249
  * usage quota or credits have been exhausted (HTTP 402).
242
250
  */
243
251
  async onInsufficientQuota(error) {
244
- this.metadata.state = 'FAILED';
245
252
  const platform = error.gptPlatform;
246
253
  const isDonobu = platform === 'DONOBU';
247
254
  const failedMessage = isDonobu
248
255
  ? 'Your Donobu AI credits have been exhausted. Please add more credits to your account to continue running flows.'
249
256
  : `Your ${platform} API quota has been exhausted. Please check your account's billing and usage limits; this may happen if there is a lack of funds in the account`;
250
257
  Logger_1.appLogger.error(failedMessage, error);
251
- this.metadata.result = { failed: failedMessage };
252
258
  await this.persistTerminalSessionStateIfNeeded();
259
+ this.metadata.result = { failed: failedMessage };
260
+ this.metadata.state = 'FAILED';
253
261
  await this.persistence.setFlowMetadata(this.metadata);
254
262
  }
255
263
  /**
@@ -339,21 +347,24 @@ class DonobuFlow {
339
347
  * method will mark the flow as a failure.
340
348
  */
341
349
  async onUnexpectedException(error) {
342
- this.metadata.state = 'FAILED';
343
350
  Logger_1.appLogger.error('Stopped flow due to exception!', error);
351
+ await this.persistTerminalSessionStateIfNeeded();
344
352
  this.metadata.result = {
345
353
  failed: 'Internal error 🙈',
346
354
  };
347
- await this.persistTerminalSessionStateIfNeeded();
355
+ this.metadata.state = 'FAILED';
348
356
  await this.persistence.setFlowMetadata(this.metadata);
349
357
  }
350
358
  /**
351
359
  * This method is called when a flow is complete (i.e. when {@link DonobuFlow.run} should return).
352
360
  *
353
- * Browser session state and the final metadata write are committed by
354
- * whichever code path produced the terminal state (transitionState for
355
- * tool-driven completion; the on*Failure handlers for failure paths) —
356
- * by the time we reach onComplete those have already happened.
361
+ * Browser session state and the terminal-state metadata write are
362
+ * committed by whichever code path produced the terminal state
363
+ * (transitionState for tool-driven completion; onTargetClosed /
364
+ * onPersistentGptFailure / onInsufficientQuota / onUnexpectedException
365
+ * for failure paths) — by the time we reach onComplete those have
366
+ * already happened. This method just runs the post-completion side
367
+ * effects.
357
368
  */
358
369
  async onComplete() {
359
370
  DonobuFlow.invokeFlowFinishedCallback(this.metadata.callbackUrl, this.metadata.id);
@@ -361,16 +372,19 @@ class DonobuFlow {
361
372
  }
362
373
  /**
363
374
  * Persists the current browser session state if the flow's config has
364
- * `persistState` enabled. Must be called BEFORE every site that commits
365
- * a terminal `state` to persistence otherwise a frontend that polls
366
- * for the flow's state can observe "complete" between the metadata
367
- * write and the session-state upload, fire an eager browser-state
368
- * fetch (e.g. FlowDeveloperTools auto-loads on terminal), and 404 until
369
- * the upload finishes.
375
+ * `persistState` enabled. Must be called BEFORE the in-memory `state`
376
+ * is mutated to a terminal value at every site that produces a
377
+ * terminal state otherwise FlowCatalog.getFlowById can read the
378
+ * live FlowMetadata object (LOCAL deployments) and a frontend that
379
+ * observes the terminal state will race the (potentially network-
380
+ * bound) upload here, getting a 404 from a subsequent browser-state
381
+ * fetch.
370
382
  *
371
383
  * The browser context typically survives all-pages-closed (the read
372
- * goes against the context, not a specific page), so this is also
373
- * safe to call from the failure handlers like onTargetClosed.
384
+ * goes against the context, not a specific page), so this is safe to
385
+ * call from failure handlers like onTargetClosed. If the read does
386
+ * fail, persistSessionState catches and logs internally — it doesn't
387
+ * propagate.
374
388
  */
375
389
  async persistTerminalSessionStateIfNeeded() {
376
390
  if (this.metadata.web?.browser?.persistState) {
@@ -647,10 +661,8 @@ Message: ${dialog.message()}`;
647
661
  // this, then someone polling for a flow's state may see the flow
648
662
  // finished but still see a null result.
649
663
  //
650
- // The same rationale applies to the browser session state: an eager
651
- // fetch from the frontend (e.g. FlowDeveloperTools auto-loads on
652
- // terminal state) would otherwise race the upload that onComplete
653
- // performs and 404 until the user manually refreshed.
664
+ // The same rationale applies to the browser session state see
665
+ // persistTerminalSessionStateIfNeeded for the full ordering note.
654
666
  if ((0, FlowMetadata_1.isComplete)(nextState)) {
655
667
  this.metadata.result = await this.createResultJson(nextState);
656
668
  await this.persistTerminalSessionStateIfNeeded();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "donobu",
3
- "version": "5.27.2",
3
+ "version": "5.27.3",
4
4
  "description": "Create browser automations with an LLM agent and replay them as Playwright scripts.",
5
5
  "main": "dist/main.js",
6
6
  "module": "dist/esm/main.js",