donobu 5.61.3 → 5.63.0

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 (37) hide show
  1. package/dist/cli/donobu-cli.js +2 -1
  2. package/dist/esm/cli/donobu-cli.js +2 -1
  3. package/dist/esm/lib/test/testExtension.js +7 -4
  4. package/dist/esm/lib/test/utils/triageTestFailure.d.ts +0 -7
  5. package/dist/esm/lib/test/utils/triageTestFailure.js +2 -13
  6. package/dist/esm/main.d.ts +2 -1
  7. package/dist/esm/main.js +4 -1
  8. package/dist/esm/managers/DonobuFlowsManager.d.ts +1 -1
  9. package/dist/esm/managers/DonobuFlowsManager.js +4 -3
  10. package/dist/esm/persistence/flows/FlowsPersistence.d.ts +40 -0
  11. package/dist/esm/persistence/flows/FlowsPersistence.js +41 -0
  12. package/dist/esm/persistence/flows/FlowsPersistenceDonobuApi.d.ts +1 -1
  13. package/dist/esm/persistence/flows/FlowsPersistenceDonobuApi.js +3 -3
  14. package/dist/esm/persistence/flows/FlowsPersistenceSqlite.d.ts +1 -3
  15. package/dist/esm/persistence/flows/FlowsPersistenceSqlite.js +6 -7
  16. package/dist/esm/utils/Logger.d.ts +22 -0
  17. package/dist/esm/utils/Logger.js +39 -11
  18. package/dist/esm/utils/PageLogListeners.d.ts +30 -10
  19. package/dist/esm/utils/PageLogListeners.js +37 -21
  20. package/dist/lib/test/testExtension.js +7 -4
  21. package/dist/lib/test/utils/triageTestFailure.d.ts +0 -7
  22. package/dist/lib/test/utils/triageTestFailure.js +2 -13
  23. package/dist/main.d.ts +2 -1
  24. package/dist/main.js +4 -1
  25. package/dist/managers/DonobuFlowsManager.d.ts +1 -1
  26. package/dist/managers/DonobuFlowsManager.js +4 -3
  27. package/dist/persistence/flows/FlowsPersistence.d.ts +40 -0
  28. package/dist/persistence/flows/FlowsPersistence.js +41 -0
  29. package/dist/persistence/flows/FlowsPersistenceDonobuApi.d.ts +1 -1
  30. package/dist/persistence/flows/FlowsPersistenceDonobuApi.js +3 -3
  31. package/dist/persistence/flows/FlowsPersistenceSqlite.d.ts +1 -3
  32. package/dist/persistence/flows/FlowsPersistenceSqlite.js +6 -7
  33. package/dist/utils/Logger.d.ts +22 -0
  34. package/dist/utils/Logger.js +39 -11
  35. package/dist/utils/PageLogListeners.d.ts +30 -10
  36. package/dist/utils/PageLogListeners.js +37 -21
  37. package/package.json +1 -1
@@ -66,6 +66,7 @@ const healRerunGate_1 = require("../lib/test/healRerunGate");
66
66
  const donobuTestStack_1 = require("../lib/test/utils/donobuTestStack");
67
67
  const triageTestFailure_1 = require("../lib/test/utils/triageTestFailure");
68
68
  const fileUploadWorkerRegistry_1 = require("../persistence/files/fileUploadWorkerRegistry");
69
+ const FlowsPersistence_1 = require("../persistence/flows/FlowsPersistence");
69
70
  const FlowsPersistenceDonobuApi_1 = require("../persistence/flows/FlowsPersistenceDonobuApi");
70
71
  const FlowsPersistenceRegistry_1 = require("../persistence/flows/FlowsPersistenceRegistry");
71
72
  const merge_1 = require("../reporter/merge");
@@ -1292,7 +1293,7 @@ async function postProcessTriageRun(context, originalPlaywrightArgs, reportPath)
1292
1293
  const donobu = await (0, donobuTestStack_1.getOrCreateDonobuStack)();
1293
1294
  const persistence = await donobu.flowsPersistenceRegistry.get();
1294
1295
  const planBuffer = Buffer.from(persistedJson, 'utf8');
1295
- const fileId = 'treatment-plan.json';
1296
+ const fileId = FlowsPersistence_1.WELL_KNOWN_FILE_IDS.treatmentPlan;
1296
1297
  await persistence.setFlowFile(flowId, fileId, planBuffer);
1297
1298
  Logger_1.appLogger.info(`Persisted treatment plan for "${testLabel}" as "${fileId}" to Donobu stack (flow: ${flowId}).`);
1298
1299
  }
@@ -66,6 +66,7 @@ const healRerunGate_1 = require("../lib/test/healRerunGate");
66
66
  const donobuTestStack_1 = require("../lib/test/utils/donobuTestStack");
67
67
  const triageTestFailure_1 = require("../lib/test/utils/triageTestFailure");
68
68
  const fileUploadWorkerRegistry_1 = require("../persistence/files/fileUploadWorkerRegistry");
69
+ const FlowsPersistence_1 = require("../persistence/flows/FlowsPersistence");
69
70
  const FlowsPersistenceDonobuApi_1 = require("../persistence/flows/FlowsPersistenceDonobuApi");
70
71
  const FlowsPersistenceRegistry_1 = require("../persistence/flows/FlowsPersistenceRegistry");
71
72
  const merge_1 = require("../reporter/merge");
@@ -1292,7 +1293,7 @@ async function postProcessTriageRun(context, originalPlaywrightArgs, reportPath)
1292
1293
  const donobu = await (0, donobuTestStack_1.getOrCreateDonobuStack)();
1293
1294
  const persistence = await donobu.flowsPersistenceRegistry.get();
1294
1295
  const planBuffer = Buffer.from(persistedJson, 'utf8');
1295
- const fileId = 'treatment-plan.json';
1296
+ const fileId = FlowsPersistence_1.WELL_KNOWN_FILE_IDS.treatmentPlan;
1296
1297
  await persistence.setFlowFile(flowId, fileId, planBuffer);
1297
1298
  Logger_1.appLogger.info(`Persisted treatment plan for "${testLabel}" as "${fileId}" to Donobu stack (flow: ${flowId}).`);
1298
1299
  }
@@ -31,6 +31,7 @@ const v4_1 = require("zod/v4");
31
31
  const envVars_1 = require("../../envVars");
32
32
  const DonobuFlowsManager_1 = require("../../managers/DonobuFlowsManager");
33
33
  const fileUploadWorkerRegistry_1 = require("../../persistence/files/fileUploadWorkerRegistry");
34
+ const FlowsPersistence_1 = require("../../persistence/flows/FlowsPersistence");
34
35
  const ansi_1 = require("../../utils/ansi");
35
36
  const BrowserUtils_1 = require("../../utils/BrowserUtils");
36
37
  const buildProvenance_1 = require("../../utils/buildProvenance");
@@ -446,7 +447,9 @@ exports.test = test_1.test.extend({
446
447
  // sources are captured into the flow's logBuffer. In Studio-launched flows
447
448
  // this is done by WebTargetInspector.initialize(), but that method is not
448
449
  // called during test runs, so we wire the listeners up here directly.
449
- (0, PageLogListeners_1.registerPageLogListeners)(page);
450
+ // Bound explicitly to this flow's buffer so routing does not depend on the
451
+ // ambient ALS context surviving until the events fire.
452
+ (0, PageLogListeners_1.registerPageLogListeners)(page, { flowId, logBuffer });
450
453
  // Bind the Playwright-provided `use` callback to an async resource so that
451
454
  // any microtasks scheduled inside the test body keep the flow logging
452
455
  // context. Without this, Playwright may re-use earlier async resources that
@@ -958,7 +961,7 @@ async function captureAndPersistFinalState(page, testInfo) {
958
961
  try {
959
962
  const screenshot = await (0, triageTestFailure_1.captureLivePageScreenshot)(page);
960
963
  if (screenshot) {
961
- await persistence.setFlowFile(flowId, triageTestFailure_1.TRIAGE_PERSISTENCE_FILE_IDS.finalStateScreenshot, screenshot);
964
+ await persistence.setFlowFile(flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.triage.finalStateScreenshot, screenshot);
962
965
  }
963
966
  }
964
967
  catch (error) {
@@ -991,11 +994,11 @@ async function finalizeTest(page, testInfo, logBuffer, videoOption) {
991
994
  // assertion errors with snippets/diffs) so the website can render the
992
995
  // report's summary row + error section. Playwright's TestInfoError already
993
996
  // carries the code `snippet`, so no source-file access is needed.
994
- await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'donobu-test-result.json', buildDonobuTestResult(testInfo));
997
+ await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.donobuTestResult, buildDonobuTestResult(testInfo));
995
998
  // Persist captured flow logs so they are available in the Donobu UI,
996
999
  // mirroring what DonobuFlowsManager does for Studio-launched flows.
997
1000
  if (logBuffer) {
998
- await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'logs.json', logBuffer.snapshot());
1001
+ await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.logs, logBuffer.snapshot());
999
1002
  }
1000
1003
  // Attach step-level screenshots from the flow's tool call history.
1001
1004
  // These enable the HTML report to show a visual timeline of what the
@@ -409,13 +409,6 @@ declare const TRIAGE_PERSISTENCE_FILE_IDS: {
409
409
  readonly evidence: "triage-evidence.json";
410
410
  readonly failureScreenshot: "triage-failure-screenshot.png";
411
411
  readonly baselineScreenshot: "triage-baseline-screenshot.png";
412
- /**
413
- * Live screenshot of a flow's final visual state, captured at teardown while
414
- * the page is still open. Persisted on successful runs so that a *later*
415
- * failing run can use it as a true final-state baseline — symmetric with the
416
- * failure screenshot, which is also a live end-of-test capture. Keyed per
417
- * flow, like browser state.
418
- */
419
412
  readonly finalStateScreenshot: "triage-final-state-screenshot.png";
420
413
  };
421
414
  /**
@@ -49,6 +49,7 @@ const fs = __importStar(require("fs/promises"));
49
49
  const path = __importStar(require("path"));
50
50
  const typescript_1 = __importDefault(require("typescript"));
51
51
  const v4_1 = require("zod/v4");
52
+ const FlowsPersistence_1 = require("../../../persistence/flows/FlowsPersistence");
52
53
  const AnalyzePageTextTool_1 = require("../../../tools/AnalyzePageTextTool");
53
54
  const MarkObjectiveCompleteTool_1 = require("../../../tools/MarkObjectiveCompleteTool");
54
55
  const MarkObjectiveNotCompletableTool_1 = require("../../../tools/MarkObjectiveNotCompletableTool");
@@ -329,19 +330,7 @@ const MAX_SERIALIZED_STRING_LENGTH = 10000;
329
330
  const MAX_TOOL_CALL_PARAMETERS_LENGTH = 500;
330
331
  const MAX_TOOL_CALLS_TO_INCLUDE = 12;
331
332
  const TRIAGE_EVIDENCE_SCHEMA_VERSION = 2;
332
- const TRIAGE_PERSISTENCE_FILE_IDS = {
333
- evidence: 'triage-evidence.json',
334
- failureScreenshot: 'triage-failure-screenshot.png',
335
- baselineScreenshot: 'triage-baseline-screenshot.png',
336
- /**
337
- * Live screenshot of a flow's final visual state, captured at teardown while
338
- * the page is still open. Persisted on successful runs so that a *later*
339
- * failing run can use it as a true final-state baseline — symmetric with the
340
- * failure screenshot, which is also a live end-of-test capture. Keyed per
341
- * flow, like browser state.
342
- */
343
- finalStateScreenshot: 'triage-final-state-screenshot.png',
344
- };
333
+ const TRIAGE_PERSISTENCE_FILE_IDS = FlowsPersistence_1.WELL_KNOWN_FILE_IDS.triage;
345
334
  exports.TRIAGE_PERSISTENCE_FILE_IDS = TRIAGE_PERSISTENCE_FILE_IDS;
346
335
  /**
347
336
  * Ensures text blobs captured from tool calls or error messages fit within storage
@@ -61,7 +61,7 @@ export type * from './models/ToolTemplateDataSource';
61
61
  export type { VideoSegment } from './models/VideoSegment';
62
62
  export type { EnvPersistence } from './persistence/env/EnvPersistence';
63
63
  export { type FileUploadAggregateStatus, type FileUploadPlatformStatus, getFileUploadAggregateStatus, shutdownFileUploadWorkers, } from './persistence/files/fileUploadWorkerRegistry';
64
- export type { FlowsPersistence } from './persistence/flows/FlowsPersistence';
64
+ export { type FlowsPersistence, WELL_KNOWN_FILE_IDS, } from './persistence/flows/FlowsPersistence';
65
65
  export { type PersistencePlugin, PersistencePluginRegistry, } from './persistence/PersistencePlugin';
66
66
  export type { SuitesPersistence } from './persistence/suites/SuitesPersistence';
67
67
  export type { TestsPersistence } from './persistence/tests/TestsPersistence';
@@ -79,6 +79,7 @@ export * from './utils/JsonSchemaUtils';
79
79
  export * from './utils/JsonUtils';
80
80
  export * from './utils/Logger';
81
81
  export * from './utils/MiscUtils';
82
+ export * from './utils/PageLogListeners';
82
83
  export * from './utils/PlaywrightUtils';
83
84
  export * from './utils/TargetUtils';
84
85
  export * from './utils/TemplateInterpolator';
package/dist/esm/main.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.TargetRuntimePluginRegistry = exports.PersistencePluginRegistry = exports.shutdownFileUploadWorkers = exports.getFileUploadAggregateStatus = exports.createDefaultToolRegistry = exports.ToolManager = exports.buildToolInterpolationDataSource = exports.PluginLoader = exports.InteractionVisualizer = exports.setupDonobuStack = exports.prepareToolCallsForRerun = exports.DonobuFlowsManager = exports.distillAllowedEnvVariableNames = exports.DonobuFlow = exports.AdminApiController = exports.env = exports.VercelAiGptClient = exports.OpenAiGptClient = exports.GptClientPluginRegistry = exports.GptClient = exports.GoogleGenerativeAiGptClient = exports.fixAssertFields = exports.DonobuGptClient = exports.AnthropicGptClient = void 0;
17
+ exports.TargetRuntimePluginRegistry = exports.PersistencePluginRegistry = exports.WELL_KNOWN_FILE_IDS = exports.shutdownFileUploadWorkers = exports.getFileUploadAggregateStatus = exports.createDefaultToolRegistry = exports.ToolManager = exports.buildToolInterpolationDataSource = exports.PluginLoader = exports.InteractionVisualizer = exports.setupDonobuStack = exports.prepareToolCallsForRerun = exports.DonobuFlowsManager = exports.distillAllowedEnvVariableNames = exports.DonobuFlow = exports.AdminApiController = exports.env = exports.VercelAiGptClient = exports.OpenAiGptClient = exports.GptClientPluginRegistry = exports.GptClient = exports.GoogleGenerativeAiGptClient = exports.fixAssertFields = exports.DonobuGptClient = exports.AnthropicGptClient = void 0;
18
18
  exports.startDonobuServer = startDonobuServer;
19
19
  const commander_1 = require("commander");
20
20
  const v4_1 = require("zod/v4");
@@ -94,6 +94,8 @@ __exportStar(require("./models/ToolSchema"), exports);
94
94
  var fileUploadWorkerRegistry_1 = require("./persistence/files/fileUploadWorkerRegistry");
95
95
  Object.defineProperty(exports, "getFileUploadAggregateStatus", { enumerable: true, get: function () { return fileUploadWorkerRegistry_1.getFileUploadAggregateStatus; } });
96
96
  Object.defineProperty(exports, "shutdownFileUploadWorkers", { enumerable: true, get: function () { return fileUploadWorkerRegistry_1.shutdownFileUploadWorkers; } });
97
+ var FlowsPersistence_1 = require("./persistence/flows/FlowsPersistence");
98
+ Object.defineProperty(exports, "WELL_KNOWN_FILE_IDS", { enumerable: true, get: function () { return FlowsPersistence_1.WELL_KNOWN_FILE_IDS; } });
97
99
  var PersistencePlugin_1 = require("./persistence/PersistencePlugin");
98
100
  Object.defineProperty(exports, "PersistencePluginRegistry", { enumerable: true, get: function () { return PersistencePlugin_1.PersistencePluginRegistry; } });
99
101
  var TargetRuntimePlugin_1 = require("./targets/TargetRuntimePlugin");
@@ -109,6 +111,7 @@ __exportStar(require("./utils/JsonSchemaUtils"), exports);
109
111
  __exportStar(require("./utils/JsonUtils"), exports);
110
112
  __exportStar(require("./utils/Logger"), exports);
111
113
  __exportStar(require("./utils/MiscUtils"), exports);
114
+ __exportStar(require("./utils/PageLogListeners"), exports);
112
115
  __exportStar(require("./utils/PlaywrightUtils"), exports);
113
116
  __exportStar(require("./utils/TargetUtils"), exports);
114
117
  __exportStar(require("./utils/TemplateInterpolator"), exports);
@@ -49,7 +49,7 @@ export declare class DonobuFlowsManager {
49
49
  private readonly testsPersistenceRegistry;
50
50
  static readonly DEFAULT_MESSAGE_DURATION = 2247;
51
51
  static readonly DEFAULT_MAX_TOOL_CALLS = 50;
52
- static readonly DEFAULT_BROWSER_STATE_FILENAME = "browserstate.json";
52
+ static readonly DEFAULT_BROWSER_STATE_FILENAME: "browserstate.json";
53
53
  static readonly FLOW_NAME_MAX_LENGTH = 1024;
54
54
  private readonly flowRuntime;
55
55
  private readonly flowCatalog;
@@ -55,6 +55,7 @@ const UnknownToolException_1 = require("../exceptions/UnknownToolException");
55
55
  const CreateDonobuFlow_1 = require("../models/CreateDonobuFlow");
56
56
  const GptConfig_1 = require("../models/GptConfig");
57
57
  const MetadataVersion_1 = require("../models/MetadataVersion");
58
+ const FlowsPersistence_1 = require("../persistence/flows/FlowsPersistence");
58
59
  const resolveTargetRuntime_1 = require("../targets/resolveTargetRuntime");
59
60
  const CustomToolRunnerTool_1 = require("../tools/CustomToolRunnerTool");
60
61
  const buildProvenance_1 = require("../utils/buildProvenance");
@@ -218,7 +219,7 @@ class DonobuFlowsManager {
218
219
  }
219
220
  try {
220
221
  const snapshot = logBuffer.snapshot();
221
- await flowsPersistence.setFlowFile(donobuFlow.metadata.id, 'logs.json', Buffer.from(JSON.stringify(snapshot)));
222
+ await flowsPersistence.setFlowFile(donobuFlow.metadata.id, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.logs, Buffer.from(JSON.stringify(snapshot)));
222
223
  }
223
224
  catch (error) {
224
225
  Logger_1.appLogger.error('Failed to persist flow logs:', error);
@@ -254,7 +255,7 @@ class DonobuFlowsManager {
254
255
  }
255
256
  // Otherwise, read the persisted snapshot.
256
257
  const persistence = await this.flowsPersistenceRegistry.get();
257
- const file = await persistence.getFlowFile(flowId, 'logs.json');
258
+ const file = await persistence.getFlowFile(flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.logs);
258
259
  if (file) {
259
260
  return JSON.parse(file.toString('utf8'));
260
261
  }
@@ -757,7 +758,7 @@ DonobuFlowsManager.DEFAULT_MESSAGE_DURATION = 2247;
757
758
  // If you change this value, also consider changing the frontend default
758
759
  // value in createFlowDefaults in flow-types.ts.
759
760
  DonobuFlowsManager.DEFAULT_MAX_TOOL_CALLS = 50;
760
- DonobuFlowsManager.DEFAULT_BROWSER_STATE_FILENAME = 'browserstate.json';
761
+ DonobuFlowsManager.DEFAULT_BROWSER_STATE_FILENAME = FlowsPersistence_1.WELL_KNOWN_FILE_IDS.browserState;
761
762
  // Re-exported from models/CreateDonobuFlow so existing callers can keep using
762
763
  // DonobuFlowsManager.FLOW_NAME_MAX_LENGTH. The source of truth (and the
763
764
  // rationale for keeping it in sync with the donobu-api gateway) lives there.
@@ -4,6 +4,46 @@ import type { FlowMetadata, FlowsQuery } from '../../models/FlowMetadata';
4
4
  import type { PaginatedResult } from '../../models/PaginatedResult';
5
5
  import type { ToolCall } from '../../models/ToolCall';
6
6
  import type { VideoSegment } from '../../models/VideoSegment';
7
+ /**
8
+ * Well-known per-flow persistence filenames reserved by Donobu.
9
+ *
10
+ * These names are storage contracts. Avoid reusing them for ad hoc tool
11
+ * artifacts.
12
+ */
13
+ export declare const WELL_KNOWN_FILE_IDS: {
14
+ /** Full flow video backing {@link FlowsPersistence.setVideo}. */
15
+ readonly video: "video.webm";
16
+ /** Browser storage state backing {@link FlowsPersistence.setBrowserState}. */
17
+ readonly browserState: "browserstate.json";
18
+ /** Structured flow log snapshot consumed by the Studio flow log view. */
19
+ readonly logs: "logs.json";
20
+ /** Playwright test result summary consumed by the Donobu HTML report. */
21
+ readonly donobuTestResult: "donobu-test-result.json";
22
+ /** Generated remediation plan consumed by Studio and admin tooling. */
23
+ readonly treatmentPlan: "treatment-plan.json";
24
+ /**
25
+ * Collab peer-link: written under the primary flow of a multi-user test run
26
+ * to enumerate the secondary flows belonging to the same run and the
27
+ * artifact fileIds persisted for each. Shape (versioned):
28
+ * `{ version: 1, peers: [{ role, flowId, files: { <label>: <fileId> } }] }`.
29
+ */
30
+ readonly collabPeers: "donobu-collab-peers.json";
31
+ /** Failure-triage artifacts persisted alongside a test flow. */
32
+ readonly triage: {
33
+ /** Serialized GPT triage evidence and heuristic assessment. */
34
+ readonly evidence: "triage-evidence.json";
35
+ /** Screenshot from the failed run's final page state. */
36
+ readonly failureScreenshot: "triage-failure-screenshot.png";
37
+ /** Screenshot from the latest successful baseline run. */
38
+ readonly baselineScreenshot: "triage-baseline-screenshot.png";
39
+ /**
40
+ * Live final-state screenshot captured at test teardown. Successful runs
41
+ * provide the baseline for later failures; failed runs provide the current
42
+ * failure screenshot.
43
+ */
44
+ readonly finalStateScreenshot: "triage-final-state-screenshot.png";
45
+ };
46
+ };
7
47
  /**
8
48
  * Interface for persisting data relating to DonobuFlow runs.
9
49
  */
@@ -1,3 +1,44 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WELL_KNOWN_FILE_IDS = void 0;
4
+ /**
5
+ * Well-known per-flow persistence filenames reserved by Donobu.
6
+ *
7
+ * These names are storage contracts. Avoid reusing them for ad hoc tool
8
+ * artifacts.
9
+ */
10
+ exports.WELL_KNOWN_FILE_IDS = {
11
+ /** Full flow video backing {@link FlowsPersistence.setVideo}. */
12
+ video: 'video.webm',
13
+ /** Browser storage state backing {@link FlowsPersistence.setBrowserState}. */
14
+ browserState: 'browserstate.json',
15
+ /** Structured flow log snapshot consumed by the Studio flow log view. */
16
+ logs: 'logs.json',
17
+ /** Playwright test result summary consumed by the Donobu HTML report. */
18
+ donobuTestResult: 'donobu-test-result.json',
19
+ /** Generated remediation plan consumed by Studio and admin tooling. */
20
+ treatmentPlan: 'treatment-plan.json',
21
+ /**
22
+ * Collab peer-link: written under the primary flow of a multi-user test run
23
+ * to enumerate the secondary flows belonging to the same run and the
24
+ * artifact fileIds persisted for each. Shape (versioned):
25
+ * `{ version: 1, peers: [{ role, flowId, files: { <label>: <fileId> } }] }`.
26
+ */
27
+ collabPeers: 'donobu-collab-peers.json',
28
+ /** Failure-triage artifacts persisted alongside a test flow. */
29
+ triage: {
30
+ /** Serialized GPT triage evidence and heuristic assessment. */
31
+ evidence: 'triage-evidence.json',
32
+ /** Screenshot from the failed run's final page state. */
33
+ failureScreenshot: 'triage-failure-screenshot.png',
34
+ /** Screenshot from the latest successful baseline run. */
35
+ baselineScreenshot: 'triage-baseline-screenshot.png',
36
+ /**
37
+ * Live final-state screenshot captured at test teardown. Successful runs
38
+ * provide the baseline for later failures; failed runs provide the current
39
+ * failure screenshot.
40
+ */
41
+ finalStateScreenshot: 'triage-final-state-screenshot.png',
42
+ },
43
+ };
3
44
  //# sourceMappingURL=FlowsPersistence.js.map
@@ -5,7 +5,7 @@ import type { PaginatedResult } from '../../models/PaginatedResult';
5
5
  import type { ToolCall } from '../../models/ToolCall';
6
6
  import type { VideoSegment } from '../../models/VideoSegment';
7
7
  import { FileUploadCache } from '../files/FileUploadCache';
8
- import type { FlowsPersistence } from './FlowsPersistence';
8
+ import { type FlowsPersistence } from './FlowsPersistence';
9
9
  /**
10
10
  * A {@link FlowsPersistence} implementation that persists flow data via the
11
11
  * Donobu API. The API stores the SDK's data models as opaque JSON blobs,
@@ -14,8 +14,8 @@ const MiscUtils_1 = require("../../utils/MiscUtils");
14
14
  const FileUploadCache_1 = require("../files/FileUploadCache");
15
15
  const FileUploadWorker_1 = require("../files/FileUploadWorker");
16
16
  const fileUploadWorkerRegistry_1 = require("../files/fileUploadWorkerRegistry");
17
+ const FlowsPersistence_1 = require("./FlowsPersistence");
17
18
  const PLATFORM_LABEL = 'donobu';
18
- const VIDEO_FILE_ID = 'video.webm';
19
19
  /**
20
20
  * A {@link FlowsPersistence} implementation that persists flow data via the
21
21
  * Donobu API. The API stores the SDK's data models as opaque JSON blobs,
@@ -205,10 +205,10 @@ class FlowsPersistenceDonobuApi {
205
205
  }
206
206
  // -- Screenshots ---------------------------------------------------
207
207
  async setVideo(flowId, bytes) {
208
- await this.setFlowFile(flowId, VIDEO_FILE_ID, bytes);
208
+ await this.setFlowFile(flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.video, bytes);
209
209
  }
210
210
  async getVideoSegment(flowId, startOffset, length) {
211
- const videoBuffer = await this.getFlowFile(flowId, VIDEO_FILE_ID);
211
+ const videoBuffer = await this.getFlowFile(flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.video);
212
212
  if (!videoBuffer) {
213
213
  return null;
214
214
  }
@@ -5,15 +5,13 @@ import type { FlowMetadata, FlowsQuery } from '../../models/FlowMetadata';
5
5
  import type { PaginatedResult } from '../../models/PaginatedResult';
6
6
  import type { ToolCall } from '../../models/ToolCall';
7
7
  import type { VideoSegment } from '../../models/VideoSegment';
8
- import type { FlowsPersistence } from './FlowsPersistence';
8
+ import { type FlowsPersistence } from './FlowsPersistence';
9
9
  /**
10
10
  * A persistence implementation that uses SQLite for all data storage, including binary files.
11
11
  */
12
12
  export declare class FlowsPersistenceSqlite implements FlowsPersistence {
13
13
  private readonly db;
14
14
  private static readonly VIDEO_MIME_TYPE;
15
- private static readonly BROWSER_STATE_FILE_ID;
16
- private static readonly VIDEO_FILE_ID;
17
15
  private constructor();
18
16
  static create(db: Database.Database): Promise<FlowsPersistenceSqlite>;
19
17
  setEnvironmentDatum(key: string, value: string): Promise<void>;
@@ -7,6 +7,7 @@ const AiQuery_1 = require("../../models/AiQuery");
7
7
  const BrowserStorageState_1 = require("../../models/BrowserStorageState");
8
8
  const MiscUtils_1 = require("../../utils/MiscUtils");
9
9
  const normalizeFlowMetadata_1 = require("../normalizeFlowMetadata");
10
+ const FlowsPersistence_1 = require("./FlowsPersistence");
10
11
  /**
11
12
  * A persistence implementation that uses SQLite for all data storage, including binary files.
12
13
  */
@@ -221,13 +222,13 @@ class FlowsPersistenceSqlite {
221
222
  async setVideo(flowId, bytes) {
222
223
  // Ensure flow exists.
223
224
  await this.getFlowMetadataById(flowId);
224
- const binaryId = `${flowId}-${FlowsPersistenceSqlite.VIDEO_FILE_ID}`;
225
+ const binaryId = `${flowId}-${FlowsPersistence_1.WELL_KNOWN_FILE_IDS.video}`;
225
226
  const stmt = this.db.prepare('INSERT OR REPLACE INTO binary_files (id, flow_id, file_id, mime_type, content) VALUES (?, ?, ?, ?, ?)');
226
- stmt.run(binaryId, flowId, FlowsPersistenceSqlite.VIDEO_FILE_ID, FlowsPersistenceSqlite.VIDEO_MIME_TYPE, bytes);
227
+ stmt.run(binaryId, flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.video, FlowsPersistenceSqlite.VIDEO_MIME_TYPE, bytes);
227
228
  }
228
229
  async getVideoSegment(flowId, startOffset, length) {
229
230
  const stmt = this.db.prepare('SELECT content FROM binary_files WHERE flow_id = ? AND file_id = ?');
230
- const row = stmt.get(flowId, FlowsPersistenceSqlite.VIDEO_FILE_ID);
231
+ const row = stmt.get(flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.video);
231
232
  if (!row) {
232
233
  return null;
233
234
  }
@@ -261,10 +262,10 @@ class FlowsPersistenceSqlite {
261
262
  }
262
263
  async setBrowserState(flowId, browserState) {
263
264
  const serializedBrowserState = Buffer.from(JSON.stringify(browserState), 'utf-8');
264
- await this.setFlowFile(flowId, FlowsPersistenceSqlite.BROWSER_STATE_FILE_ID, serializedBrowserState);
265
+ await this.setFlowFile(flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.browserState, serializedBrowserState);
265
266
  }
266
267
  async getBrowserState(flowId) {
267
- const browserStateRaw = await this.getFlowFile(flowId, FlowsPersistenceSqlite.BROWSER_STATE_FILE_ID);
268
+ const browserStateRaw = await this.getFlowFile(flowId, FlowsPersistence_1.WELL_KNOWN_FILE_IDS.browserState);
268
269
  if (browserStateRaw) {
269
270
  return BrowserStorageState_1.BrowserStorageStateSchema.parse(JSON.parse(browserStateRaw.toString('utf-8')));
270
271
  }
@@ -284,6 +285,4 @@ class FlowsPersistenceSqlite {
284
285
  }
285
286
  exports.FlowsPersistenceSqlite = FlowsPersistenceSqlite;
286
287
  FlowsPersistenceSqlite.VIDEO_MIME_TYPE = 'video/webm';
287
- FlowsPersistenceSqlite.BROWSER_STATE_FILE_ID = 'browserstate.json';
288
- FlowsPersistenceSqlite.VIDEO_FILE_ID = 'video.webm';
289
288
  //# sourceMappingURL=FlowsPersistenceSqlite.js.map
@@ -2,6 +2,28 @@ import { AsyncLocalStorage } from 'async_hooks';
2
2
  import winston from 'winston';
3
3
  import type { FlowLogBuffer } from '../utils/FlowLogBuffer';
4
4
  export declare const loggingContext: AsyncLocalStorage<unknown>;
5
+ /**
6
+ * Symbol key for flow-routing metadata carried on a log info object itself.
7
+ *
8
+ * Winston loggers are Transform streams: under backpressure, an entry may be
9
+ * format-transformed and delivered to transports on a *later* event-loop
10
+ * turn, in whatever async context happens to be live at drain time. Routing
11
+ * that resolves its destination via AsyncLocalStorage at delivery time
12
+ * therefore breaks under synchronous log bursts (a few hundred entries in
13
+ * one tick): entries land in the wrong flow's buffer, stamped with the wrong
14
+ * flowId. Callers that must guarantee attribution (e.g. page listeners bound
15
+ * to a specific flow — see registerPageLogListeners) attach
16
+ * `{ flowId, logBuffer }` under this key in the log call's meta;
17
+ * {@link FlowLogBufferTransport} and the flowId format prefer it over the
18
+ * ambient context. Symbol-keyed so it survives winston's object spreads but
19
+ * is invisible to JSON serialization (file transports, persisted logs).
20
+ */
21
+ export declare const FLOW_LOG_ROUTING: unique symbol;
22
+ /** Value shape carried under {@link FLOW_LOG_ROUTING}. */
23
+ export interface FlowLogRouting {
24
+ flowId: string;
25
+ logBuffer: FlowLogBuffer;
26
+ }
5
27
  /**
6
28
  * ###################################################################################
7
29
  * # WARNING! Only use this function within the context of Playwright test fixtures! #
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.networkLogger = exports.browserLogger = exports.accessLogger = exports.appLogger = exports.loggingContext = void 0;
6
+ exports.networkLogger = exports.browserLogger = exports.accessLogger = exports.appLogger = exports.FLOW_LOG_ROUTING = exports.loggingContext = void 0;
7
7
  exports.setProcessLocalFlowId = setProcessLocalFlowId;
8
8
  exports.setProcessLocalLogBuffer = setProcessLocalLogBuffer;
9
9
  exports.logErrorWithoutStack = logErrorWithoutStack;
@@ -69,10 +69,32 @@ const errorAwareFormat = winston_1.default.format((info) => {
69
69
  });
70
70
  exports.loggingContext = new async_hooks_1.AsyncLocalStorage();
71
71
  /**
72
- * Custom Winston transport that routes log entries into the per-flow
73
- * {@link FlowLogBuffer} stored in the {@link loggingContext} async local
74
- * storage. Each logger instance creates its own transport tagged with the
75
- * appropriate source so the buffer knows the origin.
72
+ * Symbol key for flow-routing metadata carried on a log info object itself.
73
+ *
74
+ * Winston loggers are Transform streams: under backpressure, an entry may be
75
+ * format-transformed and delivered to transports on a *later* event-loop
76
+ * turn, in whatever async context happens to be live at drain time. Routing
77
+ * that resolves its destination via AsyncLocalStorage at delivery time
78
+ * therefore breaks under synchronous log bursts (a few hundred entries in
79
+ * one tick): entries land in the wrong flow's buffer, stamped with the wrong
80
+ * flowId. Callers that must guarantee attribution (e.g. page listeners bound
81
+ * to a specific flow — see registerPageLogListeners) attach
82
+ * `{ flowId, logBuffer }` under this key in the log call's meta;
83
+ * {@link FlowLogBufferTransport} and the flowId format prefer it over the
84
+ * ambient context. Symbol-keyed so it survives winston's object spreads but
85
+ * is invisible to JSON serialization (file transports, persisted logs).
86
+ */
87
+ exports.FLOW_LOG_ROUTING = Symbol('donobu.flowLogRouting');
88
+ function getInfoRouting(info) {
89
+ return info[exports.FLOW_LOG_ROUTING];
90
+ }
91
+ /**
92
+ * Custom Winston transport that routes log entries into a per-flow
93
+ * {@link FlowLogBuffer}: the one carried on the entry itself via
94
+ * {@link FLOW_LOG_ROUTING} when present, otherwise the one stored in the
95
+ * {@link loggingContext} async local storage. Each logger instance creates
96
+ * its own transport tagged with the appropriate source so the buffer knows
97
+ * the origin.
76
98
  */
77
99
  class FlowLogBufferTransport extends winston_transport_1.default {
78
100
  constructor(source) {
@@ -81,9 +103,13 @@ class FlowLogBufferTransport extends winston_transport_1.default {
81
103
  }
82
104
  log(info, callback) {
83
105
  const store = exports.loggingContext.getStore();
84
- const buffer = store?.logBuffer || processLocalLogBuffer;
106
+ const buffer = getInfoRouting(info)?.logBuffer ||
107
+ store?.logBuffer ||
108
+ processLocalLogBuffer;
85
109
  if (buffer) {
86
- buffer.push({ ...info, source: this.source });
110
+ const entry = { ...info, source: this.source };
111
+ delete entry[exports.FLOW_LOG_ROUTING];
112
+ buffer.push(entry);
87
113
  }
88
114
  callback();
89
115
  }
@@ -111,12 +137,14 @@ function setProcessLocalFlowId(flowId) {
111
137
  function setProcessLocalLogBuffer(buffer) {
112
138
  processLocalLogBuffer = buffer;
113
139
  }
114
- // Format to add the currently running flow's ID (if any) from AsyncLocalStorage
140
+ // Format to add the currently running flow's ID (if any). Prefers routing
141
+ // carried on the entry itself (immune to winston's deferred transform under
142
+ // backpressure — see FLOW_LOG_ROUTING), then the AsyncLocalStorage store,
143
+ // then the process-local slot for logs emitted before the ALS store is
144
+ // available (see note above).
115
145
  const flowIdFormat = winston_1.default.format((info) => {
116
146
  const store = exports.loggingContext.getStore();
117
- // We prefer the ALS-provided ID but fall back to the process-local slot for
118
- // logs emitted before the ALS store is available (see note above).
119
- const flowId = store?.flowId || processLocalFlowId || '-';
147
+ const flowId = getInfoRouting(info)?.flowId || store?.flowId || processLocalFlowId || '-';
120
148
  return {
121
149
  ...info,
122
150
  flowId,
@@ -1,15 +1,35 @@
1
1
  import type { Page } from 'playwright';
2
+ import type { FlowLogBuffer } from './FlowLogBuffer';
2
3
  /**
3
- * Registers Playwright page event listeners that route browser console messages
4
- * and network request/response data to the {@link browserLogger} and
5
- * {@link networkLogger} Winston loggers. These loggers include a
6
- * {@link FlowLogBufferTransport} that captures entries into the per-flow
7
- * log buffer (when one is available via AsyncLocalStorage or the process-local
8
- * fallback).
4
+ * Explicit log destination for {@link registerPageLogListeners}.
5
+ */
6
+ export interface PageLogListenerOptions {
7
+ /** Flow ID stamped onto every captured entry. */
8
+ flowId: string;
9
+ /** Buffer the captured entries are routed into. */
10
+ logBuffer: FlowLogBuffer;
11
+ }
12
+ /**
13
+ * Registers Playwright page event listeners that route browser console
14
+ * messages and network request/response data to the {@link browserLogger}
15
+ * and {@link networkLogger} Winston loggers, whose
16
+ * {@link FlowLogBufferTransport} captures entries into a per-flow log
17
+ * buffer. Called from both {@link WebTargetInspector.handleNewPage}
18
+ * (Studio-launched flows) and the Playwright test extension fixture.
19
+ *
20
+ * By default, entries are routed by the ambient AsyncLocalStorage logging
21
+ * context (or the process-local fallback). Pass `opts` to bind this page's
22
+ * listeners to an explicit flow + buffer instead. That binding is required
23
+ * whenever one test drives multiple pages with distinct flows (e.g.
24
+ * multi-user collab tests): the ambient context belongs to the primary
25
+ * page's flow, so an unbound secondary page would leak its entries —
26
+ * stamped with the wrong flowId — into the primary flow's buffer.
9
27
  *
10
- * This function is called from both {@link WebTargetInspector.handleNewPage}
11
- * (Studio-launched flows) and the Playwright test extension fixture, ensuring
12
- * identical logging behaviour across both runtime contexts.
28
+ * The binding is stamped onto each entry's meta at emit time (see
29
+ * {@link FLOW_LOG_ROUTING}) rather than scoped via AsyncLocalStorage:
30
+ * winston defers format + transport delivery under backpressure, so
31
+ * ALS-based scoping would resolve against whatever context is live at drain
32
+ * time and silently misroute entries during synchronous log bursts.
13
33
  */
14
- export declare function registerPageLogListeners(page: Page): void;
34
+ export declare function registerPageLogListeners(page: Page, opts?: PageLogListenerOptions): void;
15
35
  //# sourceMappingURL=PageLogListeners.d.ts.map