donobu 5.54.0 → 5.56.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 (53) hide show
  1. package/dist/apis/FlowsApi.d.ts +95 -7
  2. package/dist/apis/FlowsApi.js +139 -11
  3. package/dist/apis/TestsApi.js +4 -3
  4. package/dist/codegen/CodeGenerator.js +4 -2
  5. package/dist/esm/apis/FlowsApi.d.ts +95 -7
  6. package/dist/esm/apis/FlowsApi.js +139 -11
  7. package/dist/esm/apis/TestsApi.js +4 -3
  8. package/dist/esm/codegen/CodeGenerator.js +4 -2
  9. package/dist/esm/managers/AdminApiController.js +4 -0
  10. package/dist/esm/managers/DonobuFlow.d.ts +111 -1
  11. package/dist/esm/managers/DonobuFlow.js +443 -24
  12. package/dist/esm/managers/DonobuFlowsManager.d.ts +14 -1
  13. package/dist/esm/managers/DonobuFlowsManager.js +28 -6
  14. package/dist/esm/models/ControlPanel.d.ts +30 -3
  15. package/dist/esm/models/CreateDonobuFlow.d.ts +1 -0
  16. package/dist/esm/models/CreateTest.d.ts +1 -0
  17. package/dist/esm/models/FlowMetadata.d.ts +6 -0
  18. package/dist/esm/models/FlowMetadata.js +3 -1
  19. package/dist/esm/models/RunMode.d.ts +1 -0
  20. package/dist/esm/models/RunMode.js +7 -1
  21. package/dist/esm/models/TestMetadata.d.ts +9 -0
  22. package/dist/esm/persistence/DonobuSqliteDb.js +3 -2
  23. package/dist/esm/tools/AcknowledgeUserInstruction.d.ts +6 -0
  24. package/dist/esm/tools/AcknowledgeUserInstruction.js +7 -0
  25. package/dist/esm/tools/ReplayableInteraction.d.ts +20 -0
  26. package/dist/esm/tools/ReplayableInteraction.js +63 -0
  27. package/dist/esm/tools/SetRunModeTool.d.ts +2 -0
  28. package/dist/esm/tools/Tool.d.ts +22 -3
  29. package/dist/esm/tools/Tool.js +21 -2
  30. package/dist/esm/tools/TriggerDonobuFlowTool.d.ts +2 -0
  31. package/dist/managers/AdminApiController.js +4 -0
  32. package/dist/managers/DonobuFlow.d.ts +111 -1
  33. package/dist/managers/DonobuFlow.js +443 -24
  34. package/dist/managers/DonobuFlowsManager.d.ts +14 -1
  35. package/dist/managers/DonobuFlowsManager.js +28 -6
  36. package/dist/models/ControlPanel.d.ts +30 -3
  37. package/dist/models/CreateDonobuFlow.d.ts +1 -0
  38. package/dist/models/CreateTest.d.ts +1 -0
  39. package/dist/models/FlowMetadata.d.ts +6 -0
  40. package/dist/models/FlowMetadata.js +3 -1
  41. package/dist/models/RunMode.d.ts +1 -0
  42. package/dist/models/RunMode.js +7 -1
  43. package/dist/models/TestMetadata.d.ts +9 -0
  44. package/dist/persistence/DonobuSqliteDb.js +3 -2
  45. package/dist/tools/AcknowledgeUserInstruction.d.ts +6 -0
  46. package/dist/tools/AcknowledgeUserInstruction.js +7 -0
  47. package/dist/tools/ReplayableInteraction.d.ts +20 -0
  48. package/dist/tools/ReplayableInteraction.js +63 -0
  49. package/dist/tools/SetRunModeTool.d.ts +2 -0
  50. package/dist/tools/Tool.d.ts +22 -3
  51. package/dist/tools/Tool.js +21 -2
  52. package/dist/tools/TriggerDonobuFlowTool.d.ts +2 -0
  53. package/package.json +1 -1
@@ -151,7 +151,8 @@ class DonobuFlowsManager {
151
151
  ? flowParams.toolCallsOnStart
152
152
  : targetRuntime.getInitialToolCalls(flowParams);
153
153
  let maxToolCalls = null;
154
- if (initialRunMode === 'AUTONOMOUS') {
154
+ if (initialRunMode === 'AUTONOMOUS' ||
155
+ initialRunMode === 'SUPERVISED') {
155
156
  maxToolCalls =
156
157
  flowParams.maxToolCalls ??
157
158
  DonobuFlowsManager.DEFAULT_MAX_TOOL_CALLS;
@@ -352,7 +353,15 @@ class DonobuFlowsManager {
352
353
  videoDisabled: config.videoDisabled,
353
354
  };
354
355
  }
355
- /** Add a proposed tool call the tool call queue for the given flow by ID. */
356
+ /**
357
+ * Add a proposed tool call to the tool call queue for the given flow by ID.
358
+ *
359
+ * This intentionally does NOT go through {@link DonobuFlow.submitUserAction}:
360
+ * it *appends* a step to `proposedToolCalls` and validates the tool name
361
+ * synchronously (throwing {@link UnknownToolException}), whereas the
362
+ * `RUN_TOOL` user action clears the queue, runs immediately, and validates
363
+ * only at run time. This is queue input, not a cooperative control interrupt.
364
+ */
356
365
  async proposeToolCall(flowId, toolName, parameters) {
357
366
  const activeFlowHandle = this.isLocallyRunning()
358
367
  ? this.flowRuntime.get(flowId)
@@ -433,6 +442,11 @@ class DonobuFlowsManager {
433
442
  * Attempts to cancel a flow by ID. If the flow is active, the flow is ended
434
443
  * with a state of `FAILED`. If the flow is not active, this method has no
435
444
  * effect.
445
+ *
446
+ * This intentionally does NOT go through {@link DonobuFlow.submitUserAction}:
447
+ * cancellation is a forceful lifecycle/teardown operation owned by the
448
+ * manager — it sets the terminal state and tears down the browser context —
449
+ * not a cooperative control interrupt handled by the run loop.
436
450
  */
437
451
  async cancelFlow(flowId) {
438
452
  const activeFlowHandle = this.isLocallyRunning()
@@ -813,12 +827,15 @@ async function validateFlowParams(flowParams, gptClient, initialRunMode, toolReg
813
827
  validateFlowName(flowParams.name);
814
828
  switch (initialRunMode) {
815
829
  case 'AUTONOMOUS':
816
- if ((flowParams.overallObjective?.trim().length ?? 0) === 0) {
817
- throw new InvalidParamValueException_1.InvalidParamValueException('overallObjective', flowParams.overallObjective, `'initialRunMode' has a value of '${initialRunMode}'`);
818
- }
830
+ case 'SUPERVISED':
831
+ // Both modes are AI-driven, so both need a GPT client and an objective
832
+ // to pursue.
819
833
  if (!gptClient) {
820
834
  throw new InvalidParamValueException_1.InvalidParamValueException('initialRunMode', initialRunMode, `no GPT client is available`);
821
835
  }
836
+ if ((flowParams.overallObjective?.trim().length ?? 0) === 0) {
837
+ throw new InvalidParamValueException_1.InvalidParamValueException('overallObjective', flowParams.overallObjective, 'an AI-driven flow needs an objective to pursue');
838
+ }
822
839
  break;
823
840
  case 'INSTRUCT':
824
841
  break;
@@ -966,7 +983,12 @@ async function prepareToolCallsForRerun(toolCalls, options, toolRegistry) {
966
983
  continue;
967
984
  }
968
985
  try {
969
- proposedToolCalls.push(tool.prepareForRerun(toolCall, options));
986
+ // A tool returns null to exclude itself from replay (e.g. a recorded
987
+ // user instruction, which is a live artifact, not a replayable action).
988
+ const prepared = tool.prepareForRerun(toolCall, options);
989
+ if (prepared) {
990
+ proposedToolCalls.push(prepared);
991
+ }
970
992
  }
971
993
  catch (e) {
972
994
  Logger_1.appLogger.warn(`Failed to prepare tool call for rerun: ${JSON.stringify(toolCall)}`, e);
@@ -1,4 +1,6 @@
1
1
  import type { State } from '../models/FlowMetadata';
2
+ import type { ProposedToolCall } from '../models/ProposedToolCall';
3
+ import type { RunMode } from '../models/RunMode';
2
4
  export type UserAction = {
3
5
  type: 'PAUSE';
4
6
  } | {
@@ -10,13 +12,38 @@ export type UserAction = {
10
12
  type: 'RUN_TOOL';
11
13
  toolName: string;
12
14
  parameters: Record<string, unknown>;
15
+ } | {
16
+ type: 'APPROVE';
17
+ } | {
18
+ type: 'REJECT';
19
+ feedback?: string;
20
+ } | {
21
+ type: 'SET_RUN_MODE';
22
+ runMode: RunMode;
23
+ approvePending?: boolean;
24
+ } | {
25
+ type: 'STEP';
26
+ instruction?: string;
27
+ } | {
28
+ type: 'RUN';
29
+ instruction?: string;
13
30
  };
14
31
  export type ControlPanelDataUpdate = {
15
32
  state: State;
33
+ runMode?: RunMode;
34
+ /** The flow's overall objective; the panel treats a non-empty value (or a
35
+ * typed instruction) as a "goal", which gates ⏩ Fast-forward and ▶ Play. */
36
+ overallObjective?: string | null;
37
+ /** The tools the flow can actually run (resolved from the ToolManager). */
38
+ allowedTools?: string[] | null;
16
39
  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[];
40
+ /** AI-proposed tool call(s) awaiting approval (SUPERVISED). */
41
+ pendingToolCalls?: ProposedToolCall[];
42
+ /** Whether the flow has a GPT client (AI available at all). Not cleanly a
43
+ * FlowMetadata field — a client can come from env/default config, not just a
44
+ * named gptConfig — so it's surfaced explicitly. Drives whether the compose
45
+ * surface and the ▶/⏩ transport are offered at all. */
46
+ hasGptClient?: boolean;
20
47
  };
21
48
  export interface ControlPanel {
22
49
  /** Cheap, idempotent render update. */
@@ -141,6 +141,7 @@ export declare const CreateDonobuFlowSchema: z.ZodObject<{
141
141
  gptConfigNameOverride: z.ZodOptional<z.ZodNullable<z.ZodString>>;
142
142
  initialRunMode: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
143
143
  AUTONOMOUS: "AUTONOMOUS";
144
+ SUPERVISED: "SUPERVISED";
144
145
  INSTRUCT: "INSTRUCT";
145
146
  DETERMINISTIC: "DETERMINISTIC";
146
147
  }>>>;
@@ -143,6 +143,7 @@ export declare const CreateTestSchema: z.ZodObject<{
143
143
  suiteId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
144
144
  nextRunMode: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
145
145
  AUTONOMOUS: "AUTONOMOUS";
146
+ SUPERVISED: "SUPERVISED";
146
147
  INSTRUCT: "INSTRUCT";
147
148
  DETERMINISTIC: "DETERMINISTIC";
148
149
  }>>>;
@@ -4,6 +4,7 @@ export declare const StateSchema: z.ZodEnum<{
4
4
  INITIALIZING: "INITIALIZING";
5
5
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
6
6
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
7
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
7
8
  PAUSED: "PAUSED";
8
9
  RESUMING: "RESUMING";
9
10
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -151,6 +152,7 @@ export declare const FlowMetadataSchema: z.ZodObject<{
151
152
  defaultMessageDuration: z.ZodNullable<z.ZodNumber>;
152
153
  runMode: z.ZodEnum<{
153
154
  AUTONOMOUS: "AUTONOMOUS";
155
+ SUPERVISED: "SUPERVISED";
154
156
  INSTRUCT: "INSTRUCT";
155
157
  DETERMINISTIC: "DETERMINISTIC";
156
158
  }>;
@@ -165,6 +167,7 @@ export declare const FlowMetadataSchema: z.ZodObject<{
165
167
  INITIALIZING: "INITIALIZING";
166
168
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
167
169
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
170
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
168
171
  PAUSED: "PAUSED";
169
172
  RESUMING: "RESUMING";
170
173
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -176,6 +179,7 @@ export declare const FlowMetadataSchema: z.ZodObject<{
176
179
  INITIALIZING: "INITIALIZING";
177
180
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
178
181
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
182
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
179
183
  PAUSED: "PAUSED";
180
184
  RESUMING: "RESUMING";
181
185
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -212,6 +216,7 @@ export declare const FlowsQuerySchema: z.ZodObject<{
212
216
  startedBefore: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
213
217
  runMode: z.ZodOptional<z.ZodEnum<{
214
218
  AUTONOMOUS: "AUTONOMOUS";
219
+ SUPERVISED: "SUPERVISED";
215
220
  INSTRUCT: "INSTRUCT";
216
221
  DETERMINISTIC: "DETERMINISTIC";
217
222
  }>>;
@@ -220,6 +225,7 @@ export declare const FlowsQuerySchema: z.ZodObject<{
220
225
  INITIALIZING: "INITIALIZING";
221
226
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
222
227
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
228
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
223
229
  PAUSED: "PAUSED";
224
230
  RESUMING: "RESUMING";
225
231
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -12,6 +12,7 @@ exports.StateSchema = v4_1.z
12
12
  'INITIALIZING',
13
13
  'QUERYING_LLM_FOR_NEXT_ACTION',
14
14
  'WAITING_ON_USER_FOR_NEXT_ACTION',
15
+ 'WAITING_FOR_APPROVAL',
15
16
  'PAUSED',
16
17
  'RESUMING',
17
18
  'RUNNING_ACTION',
@@ -22,8 +23,9 @@ exports.StateSchema = v4_1.z
22
23
  - UNSTARTED: Flow created but not yet initialized.
23
24
  - INITIALIZING: Setting up browser context and initial state.
24
25
  - RUNNING_ACTION: Executing a tool call.
25
- - QUERYING_LLM_FOR_NEXT_ACTION: AI determining next action (AUTONOMOUS mode).
26
+ - QUERYING_LLM_FOR_NEXT_ACTION: AI determining next action (AUTONOMOUS/SUPERVISED mode).
26
27
  - WAITING_ON_USER_FOR_NEXT_ACTION: Waiting for user input (INSTRUCT mode).
28
+ - WAITING_FOR_APPROVAL: An AI-proposed action is waiting for the user to approve or reject it (SUPERVISED mode).
27
29
  - PAUSED: Flow execution temporarily suspended.
28
30
  - RESUMING: Transitioning from paused to active state.
29
31
  - FAILED: Flow terminated unsuccessfully.
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod/v4';
2
2
  export declare const RunModeSchema: z.ZodEnum<{
3
3
  AUTONOMOUS: "AUTONOMOUS";
4
+ SUPERVISED: "SUPERVISED";
4
5
  INSTRUCT: "INSTRUCT";
5
6
  DETERMINISTIC: "DETERMINISTIC";
6
7
  }>;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RunModeSchema = void 0;
4
4
  const v4_1 = require("zod/v4");
5
5
  exports.RunModeSchema = v4_1.z
6
- .enum(['AUTONOMOUS', 'INSTRUCT', 'DETERMINISTIC'])
6
+ .enum(['AUTONOMOUS', 'SUPERVISED', 'INSTRUCT', 'DETERMINISTIC'])
7
7
  .describe(`The execution mode that determines how a flow operates and makes decisions:
8
8
 
9
9
  - AUTONOMOUS: The flow is driven by an AI agent (GPT) that autonomously decides what actions to
@@ -11,6 +11,12 @@ exports.RunModeSchema = v4_1.z
11
11
  determines the next appropriate action, and executes it without human intervention.
12
12
  This mode requires a valid GPT client configuration.
13
13
 
14
+ - SUPERVISED: The flow is driven by an AI agent (GPT) that proposes the next action, but a human
15
+ supervises each proposal: every AI-proposed action waits for the user to approve it,
16
+ or reject it with optional feedback, before it executes. Rejecting discards the
17
+ proposal and lets the AI try again with the feedback in mind. Like AUTONOMOUS, this
18
+ mode pursues an overall objective and requires a valid GPT client configuration.
19
+
14
20
  - INSTRUCT: The flow waits for explicit user instructions for each action. The user manually
15
21
  directs what tools to call and when, making this mode suitable for guided walkthroughs
16
22
  or when human oversight is required for each step. No GPT client is required.
@@ -141,6 +141,7 @@ export declare const TestMetadataSchema: z.ZodObject<{
141
141
  suiteId: z.ZodNullable<z.ZodString>;
142
142
  nextRunMode: z.ZodEnum<{
143
143
  AUTONOMOUS: "AUTONOMOUS";
144
+ SUPERVISED: "SUPERVISED";
144
145
  INSTRUCT: "INSTRUCT";
145
146
  DETERMINISTIC: "DETERMINISTIC";
146
147
  }>;
@@ -324,6 +325,7 @@ export declare const TestListItemSchema: z.ZodObject<{
324
325
  suiteId: z.ZodNullable<z.ZodString>;
325
326
  nextRunMode: z.ZodEnum<{
326
327
  AUTONOMOUS: "AUTONOMOUS";
328
+ SUPERVISED: "SUPERVISED";
327
329
  INSTRUCT: "INSTRUCT";
328
330
  DETERMINISTIC: "DETERMINISTIC";
329
331
  }>;
@@ -486,6 +488,7 @@ export declare const TestListItemSchema: z.ZodObject<{
486
488
  defaultMessageDuration: z.ZodNullable<z.ZodNumber>;
487
489
  runMode: z.ZodEnum<{
488
490
  AUTONOMOUS: "AUTONOMOUS";
491
+ SUPERVISED: "SUPERVISED";
489
492
  INSTRUCT: "INSTRUCT";
490
493
  DETERMINISTIC: "DETERMINISTIC";
491
494
  }>;
@@ -500,6 +503,7 @@ export declare const TestListItemSchema: z.ZodObject<{
500
503
  INITIALIZING: "INITIALIZING";
501
504
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
502
505
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
506
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
503
507
  PAUSED: "PAUSED";
504
508
  RESUMING: "RESUMING";
505
509
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -511,6 +515,7 @@ export declare const TestListItemSchema: z.ZodObject<{
511
515
  INITIALIZING: "INITIALIZING";
512
516
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
513
517
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
518
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
514
519
  PAUSED: "PAUSED";
515
520
  RESUMING: "RESUMING";
516
521
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -676,6 +681,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
676
681
  suiteId: z.ZodNullable<z.ZodString>;
677
682
  nextRunMode: z.ZodEnum<{
678
683
  AUTONOMOUS: "AUTONOMOUS";
684
+ SUPERVISED: "SUPERVISED";
679
685
  INSTRUCT: "INSTRUCT";
680
686
  DETERMINISTIC: "DETERMINISTIC";
681
687
  }>;
@@ -838,6 +844,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
838
844
  defaultMessageDuration: z.ZodNullable<z.ZodNumber>;
839
845
  runMode: z.ZodEnum<{
840
846
  AUTONOMOUS: "AUTONOMOUS";
847
+ SUPERVISED: "SUPERVISED";
841
848
  INSTRUCT: "INSTRUCT";
842
849
  DETERMINISTIC: "DETERMINISTIC";
843
850
  }>;
@@ -852,6 +859,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
852
859
  INITIALIZING: "INITIALIZING";
853
860
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
854
861
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
862
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
855
863
  PAUSED: "PAUSED";
856
864
  RESUMING: "RESUMING";
857
865
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -863,6 +871,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
863
871
  INITIALIZING: "INITIALIZING";
864
872
  QUERYING_LLM_FOR_NEXT_ACTION: "QUERYING_LLM_FOR_NEXT_ACTION";
865
873
  WAITING_ON_USER_FOR_NEXT_ACTION: "WAITING_ON_USER_FOR_NEXT_ACTION";
874
+ WAITING_FOR_APPROVAL: "WAITING_FOR_APPROVAL";
866
875
  PAUSED: "PAUSED";
867
876
  RESUMING: "RESUMING";
868
877
  RUNNING_ACTION: "RUNNING_ACTION";
@@ -471,7 +471,8 @@ CREATE INDEX IF NOT EXISTS idx_ai_queries_flow_id_started_at ON ai_queries(flow_
471
471
  // value (likely null) if no autonomous flow exists in the group.
472
472
  const newestAutonomous = [...group.flows]
473
473
  .reverse()
474
- .find((f) => f.metadata.runMode === 'AUTONOMOUS')?.metadata;
474
+ .find((f) => f.metadata.runMode === 'AUTONOMOUS' ||
475
+ f.metadata.runMode === 'SUPERVISED')?.metadata;
475
476
  const maxToolCalls = newestAutonomous?.maxToolCalls ?? newest.maxToolCalls;
476
477
  const testName = (0, displayName_1.getDisplayName)(newest, 'Untitled Test');
477
478
  const testMetadata = {
@@ -649,7 +650,7 @@ CREATE INDEX IF NOT EXISTS idx_ai_queries_flow_id_started_at ON ai_queries(flow_
649
650
  // value (likely null) if no autonomous flow exists in the group.
650
651
  const newestAutonomous = [...flows]
651
652
  .reverse()
652
- .find((f) => f.runMode === 'AUTONOMOUS');
653
+ .find((f) => f.runMode === 'AUTONOMOUS' || f.runMode === 'SUPERVISED');
653
654
  const maxToolCalls = newestAutonomous?.maxToolCalls ?? newestFlow.maxToolCalls;
654
655
  const testName = (0, displayName_1.getDisplayName)(newestFlow, 'Untitled Test');
655
656
  const testMetadata = {
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod/v4';
2
+ import type { ProposedToolCall } from '../models/ProposedToolCall';
2
3
  import type { ToolCallContext } from '../models/ToolCallContext';
3
4
  import type { ToolCallResult } from '../models/ToolCallResult';
4
5
  import { Tool } from './Tool';
@@ -20,5 +21,10 @@ export declare class AcknowledgeUserInstructionTool extends Tool<typeof Acknowle
20
21
  constructor();
21
22
  call(_context: ToolCallContext, parameters: z.infer<typeof AcknowledgeUserInstructionCoreSchema>): Promise<ToolCallResult>;
22
23
  callFromGpt(context: ToolCallContext, parameters: z.infer<typeof AcknowledgeUserInstructionGptSchema>): Promise<ToolCallResult>;
24
+ /**
25
+ * A user instruction is a live-interaction artifact, not a replayable action,
26
+ * so it is excluded from reruns and generated scripts.
27
+ */
28
+ prepareForRerun(): ProposedToolCall | null;
23
29
  }
24
30
  //# sourceMappingURL=AcknowledgeUserInstruction.d.ts.map
@@ -33,6 +33,13 @@ class AcknowledgeUserInstructionTool extends Tool_1.Tool {
33
33
  async callFromGpt(context, parameters) {
34
34
  return this.call(context, parameters);
35
35
  }
36
+ /**
37
+ * A user instruction is a live-interaction artifact, not a replayable action,
38
+ * so it is excluded from reruns and generated scripts.
39
+ */
40
+ prepareForRerun() {
41
+ return null;
42
+ }
36
43
  }
37
44
  exports.AcknowledgeUserInstructionTool = AcknowledgeUserInstructionTool;
38
45
  AcknowledgeUserInstructionTool.NAME = 'acknowledgeUserInstruction';
@@ -84,8 +84,28 @@ export declare abstract class ReplayableInteraction<CoreSchema extends z.ZodObje
84
84
  readonly coreSchema: CoreSchema;
85
85
  static readonly MAX_SELECTOR_FAILOVERS = 3;
86
86
  static readonly MAX_LOCATOR_MATCH_COUNT = 3;
87
+ /**
88
+ * How long the preview cursor takes to glide to (and dwell on) the proposed
89
+ * target, in milliseconds. {@link InteractionVisualizer.pointAt} animates the
90
+ * move over half this duration.
91
+ */
92
+ static readonly PREVIEW_CURSOR_DURATION_MILLIS = 900;
87
93
  constructor(name: string, description: string, coreSchema: CoreSchema, inputSchema: NonGptSchema, inputSchemaForGpt: GptSchema, requiresGpt?: boolean);
88
94
  call(context: ToolCallContext, parameters: z.infer<NonGptSchema>): Promise<ToolCallResult>;
95
+ /**
96
+ * SUPERVISED-mode preview: resolve the element this interaction *would*
97
+ * target (from either an annotation- or selector-based proposal) and move the
98
+ * on-screen cursor to it, without performing the interaction. Best-effort —
99
+ * an unresolvable element simply leaves the cursor where it is.
100
+ */
101
+ previewInteraction(context: ToolCallContext, parameters: Record<string, unknown>): Promise<void>;
102
+ /**
103
+ * Resolve a proposed interaction's parameters to the {@link Locator} it would
104
+ * act on, mirroring the resolution in {@link call} (selector-based) and
105
+ * {@link callFromGpt} (annotation-based) but returning only the locator and
106
+ * never invoking anything. Returns `null` if the element can't be found.
107
+ */
108
+ private resolvePreviewLocator;
89
109
  callFromGpt(context: ToolCallContext, parameters: z.infer<GptSchema>): Promise<ToolCallResult>;
90
110
  /**
91
111
  * **Concrete subclasses implement the actual user action here.**
@@ -96,6 +96,63 @@ class ReplayableInteraction extends Tool_1.Tool {
96
96
  }
97
97
  return this.callCore(context, parameters, locators, parameters.selector);
98
98
  }
99
+ /**
100
+ * SUPERVISED-mode preview: resolve the element this interaction *would*
101
+ * target (from either an annotation- or selector-based proposal) and move the
102
+ * on-screen cursor to it, without performing the interaction. Best-effort —
103
+ * an unresolvable element simply leaves the cursor where it is.
104
+ */
105
+ async previewInteraction(context, parameters) {
106
+ const page = (0, TargetUtils_1.webPage)(context);
107
+ const locator = await this.resolvePreviewLocator(context, page, parameters);
108
+ if (!locator) {
109
+ return;
110
+ }
111
+ // Point at the same visible target (or its label) the real interaction
112
+ // would, so the preview matches what approval will touch.
113
+ const pointTarget = await ReplayableInteraction.getLocatorOrItsLabel(locator.first());
114
+ // Only reveal the cursor now that we have a real target to point at, so a
115
+ // non-interactive proposal never pops a stationary cursor.
116
+ await context.targetInspector.showInteractionCursor();
117
+ await context.interactionVisualizer.pointAt(page, pointTarget.first(), undefined, ReplayableInteraction.PREVIEW_CURSOR_DURATION_MILLIS);
118
+ }
119
+ /**
120
+ * Resolve a proposed interaction's parameters to the {@link Locator} it would
121
+ * act on, mirroring the resolution in {@link call} (selector-based) and
122
+ * {@link callFromGpt} (annotation-based) but returning only the locator and
123
+ * never invoking anything. Returns `null` if the element can't be found.
124
+ */
125
+ async resolvePreviewLocator(context, page, parameters) {
126
+ // Selector-based (deterministic) proposal.
127
+ if (parameters.selector) {
128
+ const parsed = ElementSelector_1.ElementSelectorSchema.safeParse(parameters.selector);
129
+ if (!parsed.success) {
130
+ return null;
131
+ }
132
+ const locators = await ReplayableInteraction.getLocatorsOrderedByMatchCount(page, parsed.data, ReplayableInteraction.MAX_LOCATOR_MATCH_COUNT, ReplayableInteraction.MAX_SELECTOR_FAILOVERS);
133
+ return locators[0]?.locator ?? null;
134
+ }
135
+ // Annotation-based (LLM-driven) proposal.
136
+ if (typeof parameters.annotation === 'string' ||
137
+ typeof parameters.annotation === 'number') {
138
+ const elementSelector = `[${context.targetInspector.interactableElementAttribute}="${parameters.annotation}"]`;
139
+ for (const frame of page.frames()) {
140
+ if (frame.isDetached()) {
141
+ continue;
142
+ }
143
+ try {
144
+ const candidate = frame.locator(elementSelector);
145
+ if ((await candidate.count()) > 0) {
146
+ return candidate;
147
+ }
148
+ }
149
+ catch {
150
+ // Detached or cross-origin frame — skip it.
151
+ }
152
+ }
153
+ }
154
+ return null;
155
+ }
99
156
  async callFromGpt(context, parameters) {
100
157
  const page = (0, TargetUtils_1.webPage)(context);
101
158
  const elementSelector = `[${context.targetInspector.interactableElementAttribute}="${parameters.annotation}"]`;
@@ -655,4 +712,10 @@ class ReplayableInteraction extends Tool_1.Tool {
655
712
  exports.ReplayableInteraction = ReplayableInteraction;
656
713
  ReplayableInteraction.MAX_SELECTOR_FAILOVERS = 3;
657
714
  ReplayableInteraction.MAX_LOCATOR_MATCH_COUNT = 3;
715
+ /**
716
+ * How long the preview cursor takes to glide to (and dwell on) the proposed
717
+ * target, in milliseconds. {@link InteractionVisualizer.pointAt} animates the
718
+ * move over half this duration.
719
+ */
720
+ ReplayableInteraction.PREVIEW_CURSOR_DURATION_MILLIS = 900;
658
721
  //# sourceMappingURL=ReplayableInteraction.js.map
@@ -5,6 +5,7 @@ import { Tool } from './Tool';
5
5
  export declare const SetRunModeCoreSchema: z.ZodObject<{
6
6
  runMode: z.ZodEnum<{
7
7
  AUTONOMOUS: "AUTONOMOUS";
8
+ SUPERVISED: "SUPERVISED";
8
9
  INSTRUCT: "INSTRUCT";
9
10
  DETERMINISTIC: "DETERMINISTIC";
10
11
  }>;
@@ -12,6 +13,7 @@ export declare const SetRunModeCoreSchema: z.ZodObject<{
12
13
  export declare const SetRunModeGptSchema: z.ZodObject<{
13
14
  runMode: z.ZodEnum<{
14
15
  AUTONOMOUS: "AUTONOMOUS";
16
+ SUPERVISED: "SUPERVISED";
15
17
  INSTRUCT: "INSTRUCT";
16
18
  DETERMINISTIC: "DETERMINISTIC";
17
19
  }>;
@@ -36,16 +36,35 @@ export declare abstract class Tool<CallSchema extends z.ZodObject, CallFromGptSc
36
36
  * Invoke the tool as made from a GPT with the given context and parameters.
37
37
  */
38
38
  abstract callFromGpt(context: ToolCallContext, parameters: z.infer<CallFromGptSchema>): Promise<ToolCallResult>;
39
+ /**
40
+ * Move the on-screen cursor to where this tool *would* interact, WITHOUT
41
+ * performing the action. Used by SUPERVISED mode to show the user what an
42
+ * AI-proposed action would touch while it awaits their approval.
43
+ *
44
+ * The default is a no-op — only tools with a visible interaction target
45
+ * (e.g. {@link ReplayableInteraction}-derived click/type/hover tools)
46
+ * override it. Implementations must be best-effort and side-effect-free with
47
+ * respect to the page: they may move the visual cursor but must never click,
48
+ * type, navigate, or otherwise mutate page state.
49
+ *
50
+ * @param context - The active {@link ToolCallContext}.
51
+ * @param parameters - The proposed tool call's raw parameters (as proposed by
52
+ * the LLM or a user); implementations validate/resolve these themselves.
53
+ */
54
+ previewInteraction(_context: ToolCallContext, _parameters: Record<string, unknown>): Promise<void>;
39
55
  /**
40
56
  * Transform a completed tool call into a {@link ProposedToolCall} suitable
41
- * for deterministic replay / code generation.
57
+ * for deterministic replay / code generation, or `null` to exclude the call
58
+ * from replay entirely.
42
59
  *
43
60
  * The default implementation is a passthrough — `{ name, parameters }` —
44
61
  * which is correct for tools that have no replay-specific logic
45
62
  * (waits, assertions, markers, etc.). Tools that need to hoist
46
63
  * selector metadata out of their outcome, strip LLM-only fields, or
47
- * otherwise rewrite themselves override this method.
64
+ * otherwise rewrite themselves override this method. Tools that record
65
+ * live-interaction artifacts rather than replayable actions (e.g. a user
66
+ * instruction) return `null` so they don't reappear on reruns.
48
67
  */
49
- prepareForRerun(toolCall: ToolCall, _options: CodeGenerationOptions): ProposedToolCall;
68
+ prepareForRerun(toolCall: ToolCall, _options: CodeGenerationOptions): ProposedToolCall | null;
50
69
  }
51
70
  //# sourceMappingURL=Tool.d.ts.map
@@ -27,15 +27,34 @@ class Tool {
27
27
  this.controlPanelMessage = controlPanelMessage;
28
28
  this.supportedTargets = supportedTargets;
29
29
  }
30
+ /**
31
+ * Move the on-screen cursor to where this tool *would* interact, WITHOUT
32
+ * performing the action. Used by SUPERVISED mode to show the user what an
33
+ * AI-proposed action would touch while it awaits their approval.
34
+ *
35
+ * The default is a no-op — only tools with a visible interaction target
36
+ * (e.g. {@link ReplayableInteraction}-derived click/type/hover tools)
37
+ * override it. Implementations must be best-effort and side-effect-free with
38
+ * respect to the page: they may move the visual cursor but must never click,
39
+ * type, navigate, or otherwise mutate page state.
40
+ *
41
+ * @param context - The active {@link ToolCallContext}.
42
+ * @param parameters - The proposed tool call's raw parameters (as proposed by
43
+ * the LLM or a user); implementations validate/resolve these themselves.
44
+ */
45
+ async previewInteraction(_context, _parameters) { }
30
46
  /**
31
47
  * Transform a completed tool call into a {@link ProposedToolCall} suitable
32
- * for deterministic replay / code generation.
48
+ * for deterministic replay / code generation, or `null` to exclude the call
49
+ * from replay entirely.
33
50
  *
34
51
  * The default implementation is a passthrough — `{ name, parameters }` —
35
52
  * which is correct for tools that have no replay-specific logic
36
53
  * (waits, assertions, markers, etc.). Tools that need to hoist
37
54
  * selector metadata out of their outcome, strip LLM-only fields, or
38
- * otherwise rewrite themselves override this method.
55
+ * otherwise rewrite themselves override this method. Tools that record
56
+ * live-interaction artifacts rather than replayable actions (e.g. a user
57
+ * instruction) return `null` so they don't reappear on reruns.
39
58
  */
40
59
  prepareForRerun(toolCall, _options) {
41
60
  return {
@@ -138,6 +138,7 @@ export declare const TriggerDonobuFlowCoreSchema: z.ZodObject<{
138
138
  gptConfigNameOverride: z.ZodOptional<z.ZodNullable<z.ZodString>>;
139
139
  initialRunMode: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
140
140
  AUTONOMOUS: "AUTONOMOUS";
141
+ SUPERVISED: "SUPERVISED";
141
142
  INSTRUCT: "INSTRUCT";
142
143
  DETERMINISTIC: "DETERMINISTIC";
143
144
  }>>>;
@@ -286,6 +287,7 @@ export declare const TriggerDonobuFlowGptSchema: z.ZodObject<{
286
287
  gptConfigNameOverride: z.ZodOptional<z.ZodNullable<z.ZodString>>;
287
288
  initialRunMode: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
288
289
  AUTONOMOUS: "AUTONOMOUS";
290
+ SUPERVISED: "SUPERVISED";
289
291
  INSTRUCT: "INSTRUCT";
290
292
  DETERMINISTIC: "DETERMINISTIC";
291
293
  }>>>;
@@ -287,6 +287,10 @@ class AdminApiController {
287
287
  app.post('/api/flows/:flowId/cancel', this.asyncHandler(apis.flowsApi.cancelFlow.bind(apis.flowsApi)));
288
288
  app.post('/api/flows/:flowId/pause', this.asyncHandler(apis.flowsApi.pauseFlow.bind(apis.flowsApi)));
289
289
  app.post('/api/flows/:flowId/resume', this.asyncHandler(apis.flowsApi.resumeFlow.bind(apis.flowsApi)));
290
+ app.post('/api/flows/:flowId/approve', this.asyncHandler(apis.flowsApi.approveProposal.bind(apis.flowsApi)));
291
+ app.post('/api/flows/:flowId/reject', this.asyncHandler(apis.flowsApi.rejectProposal.bind(apis.flowsApi)));
292
+ app.get('/api/flows/:flowId/pending-tool-calls', this.asyncHandler(apis.flowsApi.getPendingToolCalls.bind(apis.flowsApi)));
293
+ app.post('/api/flows/:flowId/run-mode', this.asyncHandler(apis.flowsApi.setRunMode.bind(apis.flowsApi)));
290
294
  app.post('/api/flows/:flowId/tool-calls', this.asyncHandler(apis.flowsToolCallsApi.postToolCalls.bind(apis.flowsToolCallsApi)));
291
295
  }
292
296
  /**