donobu 5.51.0 → 5.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -602,33 +602,13 @@ Use this information to return an appropriate JSON object.`,
602
602
  }
603
603
  }
604
604
  }
605
+ const originalGoto = (0, originalGotoRegistry_1.getOriginalGoto)(page);
606
+ // The navigation itself is the only outcome the caller cares about. A
607
+ // throw here is a genuine navigation error (bad URL, timeout, net::ERR_*),
608
+ // so we record a best-effort failed tool call and re-throw to the caller.
609
+ let resp;
605
610
  try {
606
- const originalGoto = (0, originalGotoRegistry_1.getOriginalGoto)(page);
607
- const resp = await originalGoto.call(page, url, options);
608
- const pageTitle = await page.title();
609
- const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
610
- const postCallImageId = await sharedState.persistence.saveScreenShot(flowId, postCallImage);
611
- const completedAt = new Date().getTime();
612
- await sharedState.persistence.setToolCall(flowId, {
613
- id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
614
- toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
615
- parameters: {
616
- url: effectiveUrl,
617
- },
618
- outcome: {
619
- isSuccessful: true,
620
- forLlm: `Successfully navigated to ${effectiveUrl}`,
621
- metadata: {
622
- pageTitle: pageTitle,
623
- resolvedUrl: page.url(),
624
- },
625
- },
626
- postCallImageId: postCallImageId,
627
- page: effectiveUrl,
628
- startedAt: startedAt,
629
- completedAt: completedAt,
630
- });
631
- return resp;
611
+ resp = await originalGoto.call(page, url, options);
632
612
  }
633
613
  catch (error) {
634
614
  // Best-effort screenshot and tool-call recording - if the page is gone
@@ -659,6 +639,55 @@ Use this information to return an appropriate JSON object.`,
659
639
  }
660
640
  throw error;
661
641
  }
642
+ // Navigation succeeded. Everything below is best-effort Donobu telemetry
643
+ // (page title, screenshot, persisted tool call) and must never fail the
644
+ // caller's page.goto(). A SPA that self-reloads right after the `load`
645
+ // event - e.g. a feature-flag bootstrap - tears down the execution
646
+ // context, so post-navigation calls like page.title() can throw
647
+ // "Execution context was destroyed, most likely because of a navigation".
648
+ // That is normal app behavior, not a navigation failure.
649
+ try {
650
+ // page.title() evaluates in the page context, so it is the most likely
651
+ // casualty of a mid-flight reload. Capture it defensively (the CDP-based
652
+ // screenshot can still succeed via the compositor) so we can record a
653
+ // useful tool call even when the title is momentarily unavailable.
654
+ let pageTitle = '';
655
+ try {
656
+ pageTitle = await page.title();
657
+ }
658
+ catch (titleError) {
659
+ if (!PlaywrightUtils_1.PlaywrightUtils.isPageClosedError(titleError)) {
660
+ throw titleError;
661
+ }
662
+ Logger_1.appLogger.debug('page.title() failed after navigation (execution context destroyed by an in-flight reload); recording goto with an empty title.');
663
+ }
664
+ const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
665
+ const postCallImageId = await sharedState.persistence.saveScreenShot(flowId, postCallImage);
666
+ const completedAt = new Date().getTime();
667
+ await sharedState.persistence.setToolCall(flowId, {
668
+ id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
669
+ toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
670
+ parameters: {
671
+ url: effectiveUrl,
672
+ },
673
+ outcome: {
674
+ isSuccessful: true,
675
+ forLlm: `Successfully navigated to ${effectiveUrl}`,
676
+ metadata: {
677
+ pageTitle: pageTitle,
678
+ resolvedUrl: page.url(),
679
+ },
680
+ },
681
+ postCallImageId: postCallImageId,
682
+ page: effectiveUrl,
683
+ startedAt: startedAt,
684
+ completedAt: completedAt,
685
+ });
686
+ }
687
+ catch (telemetryError) {
688
+ Logger_1.appLogger.warn('Navigation succeeded but Donobu post-goto telemetry failed; this does not affect the navigation:', telemetryError);
689
+ }
690
+ return resp;
662
691
  };
663
692
  page.run = async (toolName, toolParams, options) => {
664
693
  // Thin wrapper to run a Donobu tool and throw on failure so tests can await it directly.
@@ -145,7 +145,10 @@ class DonobuFlow {
145
145
  async run() {
146
146
  while (true) {
147
147
  try {
148
- this.controlPanel.update({ state: this.metadata.state });
148
+ this.controlPanel.update({
149
+ state: this.metadata.state,
150
+ availableToolNames: this.toolManager.tools.map((t) => t.name),
151
+ });
149
152
  switch (this.metadata.state) {
150
153
  case 'UNSTARTED':
151
154
  await this.onUnstarted();
@@ -339,6 +342,14 @@ class DonobuFlow {
339
342
  });
340
343
  this.metadata.state = 'RUNNING_ACTION';
341
344
  break;
345
+ case 'RUN_TOOL':
346
+ this.proposedToolCalls.length = 0;
347
+ this.proposedToolCalls.push({
348
+ name: userAction.toolName,
349
+ parameters: userAction.parameters,
350
+ });
351
+ this.metadata.state = 'RUNNING_ACTION';
352
+ break;
342
353
  }
343
354
  await this.persistence.setFlowMetadata(this.metadata);
344
355
  }
@@ -6,10 +6,17 @@ export type UserAction = {
6
6
  userInstruction?: string;
7
7
  } | {
8
8
  type: 'END';
9
+ } | {
10
+ type: 'RUN_TOOL';
11
+ toolName: string;
12
+ parameters: Record<string, unknown>;
9
13
  };
10
14
  export type ControlPanelDataUpdate = {
11
15
  state: State;
12
16
  headline?: string;
17
+ /** Names of tools loaded in the flow's ToolManager. Surfaced to the UI so
18
+ * the control panel can offer only tools the flow can actually run. */
19
+ availableToolNames?: string[];
13
20
  };
14
21
  export interface ControlPanel {
15
22
  /** Cheap, idempotent render update. */
@@ -602,33 +602,13 @@ Use this information to return an appropriate JSON object.`,
602
602
  }
603
603
  }
604
604
  }
605
+ const originalGoto = (0, originalGotoRegistry_1.getOriginalGoto)(page);
606
+ // The navigation itself is the only outcome the caller cares about. A
607
+ // throw here is a genuine navigation error (bad URL, timeout, net::ERR_*),
608
+ // so we record a best-effort failed tool call and re-throw to the caller.
609
+ let resp;
605
610
  try {
606
- const originalGoto = (0, originalGotoRegistry_1.getOriginalGoto)(page);
607
- const resp = await originalGoto.call(page, url, options);
608
- const pageTitle = await page.title();
609
- const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
610
- const postCallImageId = await sharedState.persistence.saveScreenShot(flowId, postCallImage);
611
- const completedAt = new Date().getTime();
612
- await sharedState.persistence.setToolCall(flowId, {
613
- id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
614
- toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
615
- parameters: {
616
- url: effectiveUrl,
617
- },
618
- outcome: {
619
- isSuccessful: true,
620
- forLlm: `Successfully navigated to ${effectiveUrl}`,
621
- metadata: {
622
- pageTitle: pageTitle,
623
- resolvedUrl: page.url(),
624
- },
625
- },
626
- postCallImageId: postCallImageId,
627
- page: effectiveUrl,
628
- startedAt: startedAt,
629
- completedAt: completedAt,
630
- });
631
- return resp;
611
+ resp = await originalGoto.call(page, url, options);
632
612
  }
633
613
  catch (error) {
634
614
  // Best-effort screenshot and tool-call recording - if the page is gone
@@ -659,6 +639,55 @@ Use this information to return an appropriate JSON object.`,
659
639
  }
660
640
  throw error;
661
641
  }
642
+ // Navigation succeeded. Everything below is best-effort Donobu telemetry
643
+ // (page title, screenshot, persisted tool call) and must never fail the
644
+ // caller's page.goto(). A SPA that self-reloads right after the `load`
645
+ // event - e.g. a feature-flag bootstrap - tears down the execution
646
+ // context, so post-navigation calls like page.title() can throw
647
+ // "Execution context was destroyed, most likely because of a navigation".
648
+ // That is normal app behavior, not a navigation failure.
649
+ try {
650
+ // page.title() evaluates in the page context, so it is the most likely
651
+ // casualty of a mid-flight reload. Capture it defensively (the CDP-based
652
+ // screenshot can still succeed via the compositor) so we can record a
653
+ // useful tool call even when the title is momentarily unavailable.
654
+ let pageTitle = '';
655
+ try {
656
+ pageTitle = await page.title();
657
+ }
658
+ catch (titleError) {
659
+ if (!PlaywrightUtils_1.PlaywrightUtils.isPageClosedError(titleError)) {
660
+ throw titleError;
661
+ }
662
+ Logger_1.appLogger.debug('page.title() failed after navigation (execution context destroyed by an in-flight reload); recording goto with an empty title.');
663
+ }
664
+ const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
665
+ const postCallImageId = await sharedState.persistence.saveScreenShot(flowId, postCallImage);
666
+ const completedAt = new Date().getTime();
667
+ await sharedState.persistence.setToolCall(flowId, {
668
+ id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
669
+ toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
670
+ parameters: {
671
+ url: effectiveUrl,
672
+ },
673
+ outcome: {
674
+ isSuccessful: true,
675
+ forLlm: `Successfully navigated to ${effectiveUrl}`,
676
+ metadata: {
677
+ pageTitle: pageTitle,
678
+ resolvedUrl: page.url(),
679
+ },
680
+ },
681
+ postCallImageId: postCallImageId,
682
+ page: effectiveUrl,
683
+ startedAt: startedAt,
684
+ completedAt: completedAt,
685
+ });
686
+ }
687
+ catch (telemetryError) {
688
+ Logger_1.appLogger.warn('Navigation succeeded but Donobu post-goto telemetry failed; this does not affect the navigation:', telemetryError);
689
+ }
690
+ return resp;
662
691
  };
663
692
  page.run = async (toolName, toolParams, options) => {
664
693
  // Thin wrapper to run a Donobu tool and throw on failure so tests can await it directly.
@@ -145,7 +145,10 @@ class DonobuFlow {
145
145
  async run() {
146
146
  while (true) {
147
147
  try {
148
- this.controlPanel.update({ state: this.metadata.state });
148
+ this.controlPanel.update({
149
+ state: this.metadata.state,
150
+ availableToolNames: this.toolManager.tools.map((t) => t.name),
151
+ });
149
152
  switch (this.metadata.state) {
150
153
  case 'UNSTARTED':
151
154
  await this.onUnstarted();
@@ -339,6 +342,14 @@ class DonobuFlow {
339
342
  });
340
343
  this.metadata.state = 'RUNNING_ACTION';
341
344
  break;
345
+ case 'RUN_TOOL':
346
+ this.proposedToolCalls.length = 0;
347
+ this.proposedToolCalls.push({
348
+ name: userAction.toolName,
349
+ parameters: userAction.parameters,
350
+ });
351
+ this.metadata.state = 'RUNNING_ACTION';
352
+ break;
342
353
  }
343
354
  await this.persistence.setFlowMetadata(this.metadata);
344
355
  }
@@ -6,10 +6,17 @@ export type UserAction = {
6
6
  userInstruction?: string;
7
7
  } | {
8
8
  type: 'END';
9
+ } | {
10
+ type: 'RUN_TOOL';
11
+ toolName: string;
12
+ parameters: Record<string, unknown>;
9
13
  };
10
14
  export type ControlPanelDataUpdate = {
11
15
  state: State;
12
16
  headline?: string;
17
+ /** Names of tools loaded in the flow's ToolManager. Surfaced to the UI so
18
+ * the control panel can offer only tools the flow can actually run. */
19
+ availableToolNames?: string[];
13
20
  };
14
21
  export interface ControlPanel {
15
22
  /** Cheap, idempotent render update. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "donobu",
3
- "version": "5.51.0",
3
+ "version": "5.52.1",
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",