donobu 5.27.2 → 5.27.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/lib/page/extendPage.js +6 -1
- package/dist/esm/managers/DonobuFlow.d.ts +18 -12
- package/dist/esm/managers/DonobuFlow.js +36 -24
- package/dist/esm/reporter/render.d.ts +0 -1
- package/dist/esm/reporter/render.js +2 -5
- package/dist/esm/reporter/renderMarkdown.js +2 -2
- package/dist/esm/utils/ansi.d.ts +11 -0
- package/dist/esm/utils/ansi.js +16 -0
- package/dist/lib/page/extendPage.js +6 -1
- package/dist/managers/DonobuFlow.d.ts +18 -12
- package/dist/managers/DonobuFlow.js +36 -24
- package/dist/reporter/render.d.ts +0 -1
- package/dist/reporter/render.js +2 -5
- package/dist/reporter/renderMarkdown.js +2 -2
- package/dist/utils/ansi.d.ts +11 -0
- package/dist/utils/ansi.js +16 -0
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@ const ChangeWebBrowserTabTool_1 = require("../../tools/ChangeWebBrowserTabTool")
|
|
|
21
21
|
const CreateBrowserCookieReportTool_1 = require("../../tools/CreateBrowserCookieReportTool");
|
|
22
22
|
const GoToWebpageTool_1 = require("../../tools/GoToWebpageTool");
|
|
23
23
|
const RunAccessibilityTestTool_1 = require("../../tools/RunAccessibilityTestTool");
|
|
24
|
+
const ansi_1 = require("../../utils/ansi");
|
|
24
25
|
const BrowserUtils_1 = require("../../utils/BrowserUtils");
|
|
25
26
|
const Logger_1 = require("../../utils/Logger");
|
|
26
27
|
const MiscUtils_1 = require("../../utils/MiscUtils");
|
|
@@ -241,7 +242,11 @@ Valid options:
|
|
|
241
242
|
// All retry attempts exhausted
|
|
242
243
|
throw new ToolCallFailedException_1.ToolCallFailedException(AssertTool_1.AssertTool.NAME, {
|
|
243
244
|
isSuccessful: false,
|
|
244
|
-
|
|
245
|
+
// Strip ANSI: Playwright matchers style their messages for the
|
|
246
|
+
// terminal, but this string flows into JSON-stringified exception
|
|
247
|
+
// messages, the LLM, and HTML/markdown reports — places where the
|
|
248
|
+
// codes never render and just become visible junk.
|
|
249
|
+
forLlm: `Assertion FAILED (cached) for: ${assertion}\nPlaywright Error: ${(0, ansi_1.stripAnsi)(lastError?.message ?? '')}`,
|
|
245
250
|
metadata: {
|
|
246
251
|
cached: true,
|
|
247
252
|
steps: cached.steps,
|
|
@@ -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
|
|
112
|
-
* whichever code path produced the terminal state
|
|
113
|
-
* tool-driven completion;
|
|
114
|
-
*
|
|
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
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
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
|
|
128
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
354
|
-
* whichever code path produced the terminal state
|
|
355
|
-
* tool-driven completion;
|
|
356
|
-
*
|
|
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
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
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
|
|
373
|
-
*
|
|
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
|
|
651
|
-
//
|
|
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();
|
|
@@ -8,18 +8,15 @@
|
|
|
8
8
|
* reporter and the auto-heal orchestrator) own I/O.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.stripAnsi = stripAnsi;
|
|
12
11
|
exports.loadTriageData = loadTriageData;
|
|
13
12
|
exports.renderHtml = renderHtml;
|
|
14
13
|
const fs_1 = require("fs");
|
|
15
14
|
const path_1 = require("path");
|
|
15
|
+
const ansi_1 = require("../utils/ansi");
|
|
16
16
|
const reportWalk_1 = require("./reportWalk");
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Helpers
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
|
-
function stripAnsi(str) {
|
|
21
|
-
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
22
|
-
}
|
|
23
20
|
/**
|
|
24
21
|
* Convert ANSI SGR color codes to HTML spans. Handles the subset Playwright's
|
|
25
22
|
* expect formatter emits: red (31), green (32), dim (2), and resets (0/22/39).
|
|
@@ -559,7 +556,7 @@ function renderErrors(errors) {
|
|
|
559
556
|
html += `<pre class="snippet-block">${ansiToHtml(err.snippet)}</pre>`;
|
|
560
557
|
}
|
|
561
558
|
if (err.stack && err.stack !== err.message) {
|
|
562
|
-
html += `<details class="stack-details"><summary>Stack trace</summary><pre class="stack-block">${esc(stripAnsi(err.stack))}</pre></details>`;
|
|
559
|
+
html += `<details class="stack-details"><summary>Stack trace</summary><pre class="stack-block">${esc((0, ansi_1.stripAnsi)(err.stack))}</pre></details>`;
|
|
563
560
|
}
|
|
564
561
|
}
|
|
565
562
|
return html;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.renderMarkdown = renderMarkdown;
|
|
12
|
-
const
|
|
12
|
+
const ansi_1 = require("../utils/ansi");
|
|
13
13
|
const reportWalk_1 = require("./reportWalk");
|
|
14
14
|
function formatDuration(ms) {
|
|
15
15
|
if (ms < 1000) {
|
|
@@ -154,7 +154,7 @@ function renderMarkdown(report) {
|
|
|
154
154
|
markdown += `\n<details>\n<summary>⚠️ Error Details</summary>\n\n`;
|
|
155
155
|
markdown += `\`\`\`\n${result.error.message || 'No error message available'}\n\`\`\`\n\n`;
|
|
156
156
|
if (result.error.snippet) {
|
|
157
|
-
markdown += `**Code Snippet**:\n\`\`\`\n${(0,
|
|
157
|
+
markdown += `**Code Snippet**:\n\`\`\`\n${(0, ansi_1.stripAnsi)(result.error.snippet)}\n\`\`\`\n\n`;
|
|
158
158
|
}
|
|
159
159
|
markdown += `</details>\n\n`;
|
|
160
160
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove ANSI escape sequences (color/style codes) from a string.
|
|
3
|
+
*
|
|
4
|
+
* Playwright matchers emit terminal-styled errors. Once those messages cross
|
|
5
|
+
* into Donobu data flows — `forLlm` strings sent to the model, JSON-stringified
|
|
6
|
+
* exception messages, persisted reports — the codes never render as colors and
|
|
7
|
+
* just become visible junk (worse: `JSON.stringify` escapes the ESC byte into
|
|
8
|
+
* the 6-char literal `\u001b[..]m`). Strip at the boundary.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stripAnsi(str: string): string;
|
|
11
|
+
//# sourceMappingURL=ansi.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripAnsi = stripAnsi;
|
|
4
|
+
/**
|
|
5
|
+
* Remove ANSI escape sequences (color/style codes) from a string.
|
|
6
|
+
*
|
|
7
|
+
* Playwright matchers emit terminal-styled errors. Once those messages cross
|
|
8
|
+
* into Donobu data flows — `forLlm` strings sent to the model, JSON-stringified
|
|
9
|
+
* exception messages, persisted reports — the codes never render as colors and
|
|
10
|
+
* just become visible junk (worse: `JSON.stringify` escapes the ESC byte into
|
|
11
|
+
* the 6-char literal `\u001b[..]m`). Strip at the boundary.
|
|
12
|
+
*/
|
|
13
|
+
function stripAnsi(str) {
|
|
14
|
+
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ansi.js.map
|
|
@@ -21,6 +21,7 @@ const ChangeWebBrowserTabTool_1 = require("../../tools/ChangeWebBrowserTabTool")
|
|
|
21
21
|
const CreateBrowserCookieReportTool_1 = require("../../tools/CreateBrowserCookieReportTool");
|
|
22
22
|
const GoToWebpageTool_1 = require("../../tools/GoToWebpageTool");
|
|
23
23
|
const RunAccessibilityTestTool_1 = require("../../tools/RunAccessibilityTestTool");
|
|
24
|
+
const ansi_1 = require("../../utils/ansi");
|
|
24
25
|
const BrowserUtils_1 = require("../../utils/BrowserUtils");
|
|
25
26
|
const Logger_1 = require("../../utils/Logger");
|
|
26
27
|
const MiscUtils_1 = require("../../utils/MiscUtils");
|
|
@@ -241,7 +242,11 @@ Valid options:
|
|
|
241
242
|
// All retry attempts exhausted
|
|
242
243
|
throw new ToolCallFailedException_1.ToolCallFailedException(AssertTool_1.AssertTool.NAME, {
|
|
243
244
|
isSuccessful: false,
|
|
244
|
-
|
|
245
|
+
// Strip ANSI: Playwright matchers style their messages for the
|
|
246
|
+
// terminal, but this string flows into JSON-stringified exception
|
|
247
|
+
// messages, the LLM, and HTML/markdown reports — places where the
|
|
248
|
+
// codes never render and just become visible junk.
|
|
249
|
+
forLlm: `Assertion FAILED (cached) for: ${assertion}\nPlaywright Error: ${(0, ansi_1.stripAnsi)(lastError?.message ?? '')}`,
|
|
245
250
|
metadata: {
|
|
246
251
|
cached: true,
|
|
247
252
|
steps: cached.steps,
|
|
@@ -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
|
|
112
|
-
* whichever code path produced the terminal state
|
|
113
|
-
* tool-driven completion;
|
|
114
|
-
*
|
|
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
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
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
|
|
128
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
354
|
-
* whichever code path produced the terminal state
|
|
355
|
-
* tool-driven completion;
|
|
356
|
-
*
|
|
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
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
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
|
|
373
|
-
*
|
|
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
|
|
651
|
-
//
|
|
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/dist/reporter/render.js
CHANGED
|
@@ -8,18 +8,15 @@
|
|
|
8
8
|
* reporter and the auto-heal orchestrator) own I/O.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.stripAnsi = stripAnsi;
|
|
12
11
|
exports.loadTriageData = loadTriageData;
|
|
13
12
|
exports.renderHtml = renderHtml;
|
|
14
13
|
const fs_1 = require("fs");
|
|
15
14
|
const path_1 = require("path");
|
|
15
|
+
const ansi_1 = require("../utils/ansi");
|
|
16
16
|
const reportWalk_1 = require("./reportWalk");
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Helpers
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
|
-
function stripAnsi(str) {
|
|
21
|
-
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
22
|
-
}
|
|
23
20
|
/**
|
|
24
21
|
* Convert ANSI SGR color codes to HTML spans. Handles the subset Playwright's
|
|
25
22
|
* expect formatter emits: red (31), green (32), dim (2), and resets (0/22/39).
|
|
@@ -559,7 +556,7 @@ function renderErrors(errors) {
|
|
|
559
556
|
html += `<pre class="snippet-block">${ansiToHtml(err.snippet)}</pre>`;
|
|
560
557
|
}
|
|
561
558
|
if (err.stack && err.stack !== err.message) {
|
|
562
|
-
html += `<details class="stack-details"><summary>Stack trace</summary><pre class="stack-block">${esc(stripAnsi(err.stack))}</pre></details>`;
|
|
559
|
+
html += `<details class="stack-details"><summary>Stack trace</summary><pre class="stack-block">${esc((0, ansi_1.stripAnsi)(err.stack))}</pre></details>`;
|
|
563
560
|
}
|
|
564
561
|
}
|
|
565
562
|
return html;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.renderMarkdown = renderMarkdown;
|
|
12
|
-
const
|
|
12
|
+
const ansi_1 = require("../utils/ansi");
|
|
13
13
|
const reportWalk_1 = require("./reportWalk");
|
|
14
14
|
function formatDuration(ms) {
|
|
15
15
|
if (ms < 1000) {
|
|
@@ -154,7 +154,7 @@ function renderMarkdown(report) {
|
|
|
154
154
|
markdown += `\n<details>\n<summary>⚠️ Error Details</summary>\n\n`;
|
|
155
155
|
markdown += `\`\`\`\n${result.error.message || 'No error message available'}\n\`\`\`\n\n`;
|
|
156
156
|
if (result.error.snippet) {
|
|
157
|
-
markdown += `**Code Snippet**:\n\`\`\`\n${(0,
|
|
157
|
+
markdown += `**Code Snippet**:\n\`\`\`\n${(0, ansi_1.stripAnsi)(result.error.snippet)}\n\`\`\`\n\n`;
|
|
158
158
|
}
|
|
159
159
|
markdown += `</details>\n\n`;
|
|
160
160
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove ANSI escape sequences (color/style codes) from a string.
|
|
3
|
+
*
|
|
4
|
+
* Playwright matchers emit terminal-styled errors. Once those messages cross
|
|
5
|
+
* into Donobu data flows — `forLlm` strings sent to the model, JSON-stringified
|
|
6
|
+
* exception messages, persisted reports — the codes never render as colors and
|
|
7
|
+
* just become visible junk (worse: `JSON.stringify` escapes the ESC byte into
|
|
8
|
+
* the 6-char literal `\u001b[..]m`). Strip at the boundary.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stripAnsi(str: string): string;
|
|
11
|
+
//# sourceMappingURL=ansi.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripAnsi = stripAnsi;
|
|
4
|
+
/**
|
|
5
|
+
* Remove ANSI escape sequences (color/style codes) from a string.
|
|
6
|
+
*
|
|
7
|
+
* Playwright matchers emit terminal-styled errors. Once those messages cross
|
|
8
|
+
* into Donobu data flows — `forLlm` strings sent to the model, JSON-stringified
|
|
9
|
+
* exception messages, persisted reports — the codes never render as colors and
|
|
10
|
+
* just become visible junk (worse: `JSON.stringify` escapes the ESC byte into
|
|
11
|
+
* the 6-char literal `\u001b[..]m`). Strip at the boundary.
|
|
12
|
+
*/
|
|
13
|
+
function stripAnsi(str) {
|
|
14
|
+
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ansi.js.map
|