explorbot 0.1.22 → 0.1.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "explorbot",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "CLI app built with React Ink, CodeceptJS, and Playwright",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",
@@ -21,7 +21,7 @@ export function WithWebMode(Base) {
21
21
  return state ? ActionResult.fromState(state) : null;
22
22
  },
23
23
  });
24
- const { see, context, visualClick, learn_experience } = agentTools;
24
+ const { see, context, visualClick, learnExperience } = agentTools;
25
25
  return {
26
26
  navigate: tool({
27
27
  description: 'Navigate to a URL or page description using AI-powered navigation.',
@@ -98,7 +98,7 @@ export function WithWebMode(Base) {
98
98
  see,
99
99
  context,
100
100
  visualClick,
101
- learn_experience,
101
+ learnExperience,
102
102
  };
103
103
  }
104
104
  webModePrompt() {
@@ -448,16 +448,16 @@ class Navigator {
448
448
  const s = stateManager.getCurrentState();
449
449
  return s ? ActionResult.fromState(s) : null;
450
450
  };
451
- const { learn_experience } = createAgentTools({
451
+ const { learnExperience } = createAgentTools({
452
452
  explorer: this.explorer,
453
453
  researcher: null,
454
454
  navigator: this,
455
455
  experienceTracker: this.experienceTracker,
456
456
  getState,
457
457
  });
458
- if (!learn_experience)
458
+ if (!learnExperience)
459
459
  return undefined;
460
- return { learn_experience };
460
+ return { learnExperience };
461
461
  }
462
462
  async freeSail(opts, actionResult) {
463
463
  const stateManager = this.explorer.getStateManager();
@@ -389,12 +389,12 @@ export class Pilot {
389
389
  the elements needed for the scenario. The page summary does not list every element.
390
390
  Prefer interacting with the current page over navigating away.
391
391
 
392
- If you load a recipe via learn_experience, do NOT rewrite its code in your plan — the
392
+ If you load a recipe via learnExperience, do NOT rewrite its code in your plan — the
393
393
  raw recipe is forwarded to Tester automatically. Reference it by step ("apply recipe
394
394
  steps 1–3, then…") and call out anywhere your scenario diverges from it.
395
395
 
396
396
  Be concise and specific. Tester will follow your plan.
397
- `, 'pilot.planTest', { tools: true, planningOnly: true, maxToolRoundtrips: 3, task });
397
+ `, 'pilot.planTest', { tools: true, maxToolRoundtrips: 3, task });
398
398
  }
399
399
  async reviewNewPage(task, currentState, testerConversation) {
400
400
  if (!this.conversation)
@@ -484,7 +484,7 @@ export class Pilot {
484
484
  this.conversation.addUserText(finalUserText);
485
485
  let tools;
486
486
  if (opts.tools) {
487
- tools = opts.planningOnly ? this.pickPlanningTools() : this.agentTools;
487
+ tools = this.pickPlanningTools();
488
488
  }
489
489
  if (opts.tools && opts.task) {
490
490
  tools = { ...tools, ...this.buildPreconditionTool(opts.task) };
@@ -496,7 +496,7 @@ export class Pilot {
496
496
  experimental_telemetry: { functionId },
497
497
  });
498
498
  const text = result?.response?.text || '';
499
- const learned = (result?.toolExecutions || []).filter((e) => e.toolName === 'learn_experience' && e.output?.content).map((e) => e.output.content);
499
+ const learned = (result?.toolExecutions || []).filter((e) => e.toolName === 'learnExperience' && e.output?.content).map((e) => e.output.content);
500
500
  if (learned.length === 0)
501
501
  return text;
502
502
  return dedent `
@@ -521,7 +521,7 @@ export class Pilot {
521
521
  return renderExperienceToc(toc);
522
522
  }
523
523
  pickPlanningTools() {
524
- const { see, context, verify, research, getVisitedStates, xpathCheck, learn_experience } = this.agentTools ?? {};
524
+ const { see, context, verify, research, getVisitedStates, xpathCheck, learnExperience, askUser } = this.agentTools ?? {};
525
525
  const planning = {};
526
526
  if (see)
527
527
  planning.see = see;
@@ -535,8 +535,10 @@ export class Pilot {
535
535
  planning.getVisitedStates = getVisitedStates;
536
536
  if (xpathCheck)
537
537
  planning.xpathCheck = xpathCheck;
538
- if (learn_experience)
539
- planning.learn_experience = learn_experience;
538
+ if (learnExperience)
539
+ planning.learnExperience = learnExperience;
540
+ if (askUser)
541
+ planning.askUser = askUser;
540
542
  return planning;
541
543
  }
542
544
  buildPreconditionTool(task) {
@@ -668,7 +668,7 @@ export class Tester extends TaskAgent {
668
668
  - When filling complex form with lot of actions performed, use see() to look which fields were filled and which are not
669
669
  - When verify() fails, use see() to visually confirm the result — visual confirmation is equally valid evidence
670
670
  - For visual state verification (active tabs, selected items, counts, colors), prefer see() over DOM-based verify()
671
- - When click() fails and element is visually present, use visualClick() as fallback
671
+ - When click() fails on an element you can see — or believe is there but may look different — you MUST try visualClick() before giving up; don't repeat visualClick in a row
672
672
  - If you land on a "Not Found", 404, or error page that is NOT part of the scenario, call reset() immediately to return to the initial page and try again
673
673
  - If you see a server error page (500, 503, etc.), record it with record({ notes: ["Server error on /path"], status: "fail" }) and call reset() to continue testing
674
674
  </rules>
@@ -391,7 +391,7 @@ export function createSpecialContextTools(explorer, context) {
391
391
  }),
392
392
  };
393
393
  }
394
- export function createAgentTools({ explorer, researcher, navigator, experienceTracker, getState, }) {
394
+ export function createAgentTools({ explorer, researcher, navigator, experienceTracker, getState, supervisor, }) {
395
395
  let visionDisabled = false;
396
396
  const tools = {
397
397
  see: tool({
@@ -685,41 +685,6 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
685
685
  }
686
686
  },
687
687
  }),
688
- askUser: tool({
689
- description: dedent `
690
- Ask the user for help when you're stuck or unsure how to proceed.
691
- Only available in interactive mode (TUI).
692
-
693
- Use when:
694
- - Locator-based clicks keep failing
695
- - You can't find an element that should exist
696
- - Form interaction isn't working as expected
697
- - You need clarification on what action to take
698
- `,
699
- inputSchema: z.object({
700
- question: z.string().describe('What you need help with - be specific about what failed'),
701
- context: z.string().optional().describe('Relevant context like locators tried, errors received'),
702
- }),
703
- execute: async ({ question, context }) => {
704
- if (!isInteractive()) {
705
- return {
706
- success: false,
707
- message: 'User input not available in non-interactive mode',
708
- suggestion: 'Continue with automated recovery',
709
- };
710
- }
711
- const prompt = context ? `${question}\n\nContext: ${context}\n\nYour suggestion ("skip" to continue):` : `${question}\n\nYour suggestion ("skip" to continue):`;
712
- const userInput = await pause(prompt);
713
- if (!userInput || userInput.toLowerCase() === 'skip') {
714
- return { success: false, message: 'User skipped' };
715
- }
716
- return {
717
- success: true,
718
- userSuggestion: userInput,
719
- instruction: 'Follow the user suggestion. Use interact() tool to execute.',
720
- };
721
- },
722
- }),
723
688
  back: tool({
724
689
  description: dedent `
725
690
  Navigate back to the previous page (most recent URL different from current).
@@ -834,7 +799,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
834
799
  }),
835
800
  };
836
801
  if (experienceTracker && getState) {
837
- tools.learn_experience = tool({
802
+ tools.learnExperience = tool({
838
803
  description: dedent `
839
804
  Read the full body of a specific experience section listed in <experience>.
840
805
  The TOC shows entries like "A.1 ## FLOW: ..." or "A.2 ## ACTION: ...". Pass the fileTag and sectionIndex.
@@ -857,6 +822,43 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
857
822
  },
858
823
  });
859
824
  }
825
+ if (supervisor) {
826
+ tools.askUser = tool({
827
+ description: dedent `
828
+ Ask the user for help when automated recovery is stuck or the next step is unclear.
829
+ Only available in interactive mode (TUI).
830
+
831
+ Use when:
832
+ - The Tester keeps failing the same locator/element
833
+ - An element that should exist cannot be found
834
+ - Form interaction isn't working as expected
835
+ - You need a human decision on how to proceed
836
+ `,
837
+ inputSchema: z.object({
838
+ question: z.string().describe('What you need help with - be specific about what failed'),
839
+ context: z.string().optional().describe('Relevant context like locators tried, errors received'),
840
+ }),
841
+ execute: async ({ question, context }) => {
842
+ if (!isInteractive()) {
843
+ return {
844
+ success: false,
845
+ message: 'User input not available in non-interactive mode',
846
+ suggestion: 'Continue with automated recovery',
847
+ };
848
+ }
849
+ const prompt = context ? `${question}\n\nContext: ${context}\n\nYour suggestion ("skip" to continue):` : `${question}\n\nYour suggestion ("skip" to continue):`;
850
+ const userInput = await pause(prompt);
851
+ if (!userInput || userInput.toLowerCase() === 'skip') {
852
+ return { success: false, message: 'User skipped' };
853
+ }
854
+ return {
855
+ success: true,
856
+ userSuggestion: userInput,
857
+ instruction: 'Relay this suggestion to the Tester as the next concrete step.',
858
+ };
859
+ },
860
+ });
861
+ }
860
862
  return tools;
861
863
  }
862
864
  const PAGE_DIFF_SUGGESTION = 'Analyze page diff. htmlParts shows what changed and WHERE — each part has a container selector. Use the container as context when clicking elements from the diff.';
@@ -455,7 +455,7 @@ export function renderExperienceToc(toc) {
455
455
  lines.push('Locators and step ordering worked then; the page may have changed since.');
456
456
  lines.push('Treat as a starting hypothesis, not ground truth. If a step fails, fall back to ARIA/UI-map.');
457
457
  lines.push('FLOW: = multi-step recipe (bullets + code + discovery). ACTION: = single-step snippet (one code block).');
458
- lines.push('Call learn_experience({ fileTag, sectionIndex }) to read a section when it looks relevant to the current step.');
458
+ lines.push('Call learnExperience({ fileTag, sectionIndex }) to read a section when it looks relevant to the current step.');
459
459
  lines.push('');
460
460
  for (const entry of toc) {
461
461
  lines.push(`File ${entry.fileTag} ${entry.url}:`);
@@ -170,7 +170,7 @@ export class ExplorBot {
170
170
  const state = stateManager.getCurrentState();
171
171
  return state ? ActionResult.fromState(state) : null;
172
172
  };
173
- const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState });
173
+ const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState, supervisor: true });
174
174
  return new Pilot(ai, tools, researcher, explorer, experienceTracker);
175
175
  }));
176
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "explorbot",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "CLI app built with React Ink, CodeceptJS, and Playwright",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",
@@ -22,7 +22,7 @@ export function WithWebMode<T extends Constructor>(Base: T) {
22
22
  return state ? ActionResult.fromState(state) : null;
23
23
  },
24
24
  });
25
- const { see, context, visualClick, learn_experience } = agentTools;
25
+ const { see, context, visualClick, learnExperience } = agentTools;
26
26
 
27
27
  return {
28
28
  navigate: tool({
@@ -103,7 +103,7 @@ export function WithWebMode<T extends Constructor>(Base: T) {
103
103
  see,
104
104
  context,
105
105
  visualClick,
106
- learn_experience,
106
+ learnExperience,
107
107
  };
108
108
  }
109
109
 
@@ -481,21 +481,21 @@ class Navigator implements Agent {
481
481
  return resolved;
482
482
  }
483
483
 
484
- private buildExperienceTools(): { learn_experience: unknown } | undefined {
484
+ private buildExperienceTools(): { learnExperience: unknown } | undefined {
485
485
  const stateManager = this.explorer.getStateManager();
486
486
  const getState = () => {
487
487
  const s = stateManager.getCurrentState();
488
488
  return s ? ActionResult.fromState(s) : null;
489
489
  };
490
- const { learn_experience } = createAgentTools({
490
+ const { learnExperience } = createAgentTools({
491
491
  explorer: this.explorer,
492
492
  researcher: null as unknown as Researcher,
493
493
  navigator: this,
494
494
  experienceTracker: this.experienceTracker,
495
495
  getState,
496
496
  });
497
- if (!learn_experience) return undefined;
498
- return { learn_experience };
497
+ if (!learnExperience) return undefined;
498
+ return { learnExperience };
499
499
  }
500
500
 
501
501
  async freeSail(opts?: { strategy?: 'deep' | 'shallow'; scope?: string; visitedUrls?: Set<string> }, actionResult?: ActionResult): Promise<{ target: string; reason: string } | null> {
package/src/ai/pilot.ts CHANGED
@@ -439,14 +439,14 @@ export class Pilot implements Agent {
439
439
  the elements needed for the scenario. The page summary does not list every element.
440
440
  Prefer interacting with the current page over navigating away.
441
441
 
442
- If you load a recipe via learn_experience, do NOT rewrite its code in your plan — the
442
+ If you load a recipe via learnExperience, do NOT rewrite its code in your plan — the
443
443
  raw recipe is forwarded to Tester automatically. Reference it by step ("apply recipe
444
444
  steps 1–3, then…") and call out anywhere your scenario diverges from it.
445
445
 
446
446
  Be concise and specific. Tester will follow your plan.
447
447
  `,
448
448
  'pilot.planTest',
449
- { tools: true, planningOnly: true, maxToolRoundtrips: 3, task }
449
+ { tools: true, maxToolRoundtrips: 3, task }
450
450
  );
451
451
  }
452
452
 
@@ -547,7 +547,7 @@ export class Pilot implements Agent {
547
547
  return `CHECKED: ${checked.length > 0 ? checked.join(', ') : 'none'}\nREMAINING: ${remaining.length > 0 ? remaining.join(', ') : 'none'}`;
548
548
  }
549
549
 
550
- private async sendToPilot(userText: string, functionId: string, opts: { tools?: boolean; planningOnly?: boolean; maxToolRoundtrips?: number; task?: Test } = {}): Promise<string> {
550
+ private async sendToPilot(userText: string, functionId: string, opts: { tools?: boolean; maxToolRoundtrips?: number; task?: Test } = {}): Promise<string> {
551
551
  debugLog(`sendToPilot: ${functionId}, tools: ${!!opts.tools}, roundtrips: ${opts.maxToolRoundtrips ?? 0}`);
552
552
 
553
553
  let finalUserText = userText;
@@ -560,7 +560,7 @@ export class Pilot implements Agent {
560
560
  this.conversation!.addUserText(finalUserText);
561
561
  let tools: any;
562
562
  if (opts.tools) {
563
- tools = opts.planningOnly ? this.pickPlanningTools() : this.agentTools;
563
+ tools = this.pickPlanningTools();
564
564
  }
565
565
 
566
566
  if (opts.tools && opts.task) {
@@ -574,7 +574,7 @@ export class Pilot implements Agent {
574
574
  experimental_telemetry: { functionId },
575
575
  });
576
576
  const text = result?.response?.text || '';
577
- const learned = (result?.toolExecutions || []).filter((e: any) => e.toolName === 'learn_experience' && e.output?.content).map((e: any) => e.output.content);
577
+ const learned = (result?.toolExecutions || []).filter((e: any) => e.toolName === 'learnExperience' && e.output?.content).map((e: any) => e.output.content);
578
578
  if (learned.length === 0) return text;
579
579
  return dedent`
580
580
  ${text}
@@ -598,7 +598,7 @@ export class Pilot implements Agent {
598
598
  }
599
599
 
600
600
  private pickPlanningTools() {
601
- const { see, context, verify, research, getVisitedStates, xpathCheck, learn_experience } = this.agentTools ?? {};
601
+ const { see, context, verify, research, getVisitedStates, xpathCheck, learnExperience, askUser } = this.agentTools ?? {};
602
602
  const planning: Record<string, unknown> = {};
603
603
  if (see) planning.see = see;
604
604
  if (context) planning.context = context;
@@ -606,7 +606,8 @@ export class Pilot implements Agent {
606
606
  if (research) planning.research = research;
607
607
  if (getVisitedStates) planning.getVisitedStates = getVisitedStates;
608
608
  if (xpathCheck) planning.xpathCheck = xpathCheck;
609
- if (learn_experience) planning.learn_experience = learn_experience;
609
+ if (learnExperience) planning.learnExperience = learnExperience;
610
+ if (askUser) planning.askUser = askUser;
610
611
  return planning;
611
612
  }
612
613
 
package/src/ai/tester.ts CHANGED
@@ -750,7 +750,7 @@ export class Tester extends TaskAgent implements Agent {
750
750
  - When filling complex form with lot of actions performed, use see() to look which fields were filled and which are not
751
751
  - When verify() fails, use see() to visually confirm the result — visual confirmation is equally valid evidence
752
752
  - For visual state verification (active tabs, selected items, counts, colors), prefer see() over DOM-based verify()
753
- - When click() fails and element is visually present, use visualClick() as fallback
753
+ - When click() fails on an element you can see — or believe is there but may look different — you MUST try visualClick() before giving up; don't repeat visualClick in a row
754
754
  - If you land on a "Not Found", 404, or error page that is NOT part of the scenario, call reset() immediately to return to the initial page and try again
755
755
  - If you see a server error page (500, 503, etc.), record it with record({ notes: ["Server error on /path"], status: "fail" }) and call reset() to continue testing
756
756
  </rules>
package/src/ai/tools.ts CHANGED
@@ -466,12 +466,14 @@ export function createAgentTools({
466
466
  navigator,
467
467
  experienceTracker,
468
468
  getState,
469
+ supervisor,
469
470
  }: {
470
471
  explorer: Explorer;
471
472
  researcher: Researcher;
472
473
  navigator: Navigator;
473
474
  experienceTracker?: ExperienceTracker;
474
475
  getState?: () => ActionResult | null;
476
+ supervisor?: boolean;
475
477
  }): any {
476
478
  let visionDisabled = false;
477
479
 
@@ -803,46 +805,6 @@ export function createAgentTools({
803
805
  },
804
806
  }),
805
807
 
806
- askUser: tool({
807
- description: dedent`
808
- Ask the user for help when you're stuck or unsure how to proceed.
809
- Only available in interactive mode (TUI).
810
-
811
- Use when:
812
- - Locator-based clicks keep failing
813
- - You can't find an element that should exist
814
- - Form interaction isn't working as expected
815
- - You need clarification on what action to take
816
- `,
817
- inputSchema: z.object({
818
- question: z.string().describe('What you need help with - be specific about what failed'),
819
- context: z.string().optional().describe('Relevant context like locators tried, errors received'),
820
- }),
821
- execute: async ({ question, context }) => {
822
- if (!isInteractive()) {
823
- return {
824
- success: false,
825
- message: 'User input not available in non-interactive mode',
826
- suggestion: 'Continue with automated recovery',
827
- };
828
- }
829
-
830
- const prompt = context ? `${question}\n\nContext: ${context}\n\nYour suggestion ("skip" to continue):` : `${question}\n\nYour suggestion ("skip" to continue):`;
831
-
832
- const userInput = await pause(prompt);
833
-
834
- if (!userInput || userInput.toLowerCase() === 'skip') {
835
- return { success: false, message: 'User skipped' };
836
- }
837
-
838
- return {
839
- success: true,
840
- userSuggestion: userInput,
841
- instruction: 'Follow the user suggestion. Use interact() tool to execute.',
842
- };
843
- },
844
- }),
845
-
846
808
  back: tool({
847
809
  description: dedent`
848
810
  Navigate back to the previous page (most recent URL different from current).
@@ -973,7 +935,7 @@ export function createAgentTools({
973
935
  };
974
936
 
975
937
  if (experienceTracker && getState) {
976
- tools.learn_experience = tool({
938
+ tools.learnExperience = tool({
977
939
  description: dedent`
978
940
  Read the full body of a specific experience section listed in <experience>.
979
941
  The TOC shows entries like "A.1 ## FLOW: ..." or "A.2 ## ACTION: ...". Pass the fileTag and sectionIndex.
@@ -997,6 +959,48 @@ export function createAgentTools({
997
959
  });
998
960
  }
999
961
 
962
+ if (supervisor) {
963
+ tools.askUser = tool({
964
+ description: dedent`
965
+ Ask the user for help when automated recovery is stuck or the next step is unclear.
966
+ Only available in interactive mode (TUI).
967
+
968
+ Use when:
969
+ - The Tester keeps failing the same locator/element
970
+ - An element that should exist cannot be found
971
+ - Form interaction isn't working as expected
972
+ - You need a human decision on how to proceed
973
+ `,
974
+ inputSchema: z.object({
975
+ question: z.string().describe('What you need help with - be specific about what failed'),
976
+ context: z.string().optional().describe('Relevant context like locators tried, errors received'),
977
+ }),
978
+ execute: async ({ question, context }) => {
979
+ if (!isInteractive()) {
980
+ return {
981
+ success: false,
982
+ message: 'User input not available in non-interactive mode',
983
+ suggestion: 'Continue with automated recovery',
984
+ };
985
+ }
986
+
987
+ const prompt = context ? `${question}\n\nContext: ${context}\n\nYour suggestion ("skip" to continue):` : `${question}\n\nYour suggestion ("skip" to continue):`;
988
+
989
+ const userInput = await pause(prompt);
990
+
991
+ if (!userInput || userInput.toLowerCase() === 'skip') {
992
+ return { success: false, message: 'User skipped' };
993
+ }
994
+
995
+ return {
996
+ success: true,
997
+ userSuggestion: userInput,
998
+ instruction: 'Relay this suggestion to the Tester as the next concrete step.',
999
+ };
1000
+ },
1001
+ });
1002
+ }
1003
+
1000
1004
  return tools;
1001
1005
  }
1002
1006
 
@@ -499,7 +499,7 @@ export function renderExperienceToc(toc: ExperienceTocEntry[]): string {
499
499
  lines.push('Locators and step ordering worked then; the page may have changed since.');
500
500
  lines.push('Treat as a starting hypothesis, not ground truth. If a step fails, fall back to ARIA/UI-map.');
501
501
  lines.push('FLOW: = multi-step recipe (bullets + code + discovery). ACTION: = single-step snippet (one code block).');
502
- lines.push('Call learn_experience({ fileTag, sectionIndex }) to read a section when it looks relevant to the current step.');
502
+ lines.push('Call learnExperience({ fileTag, sectionIndex }) to read a section when it looks relevant to the current step.');
503
503
  lines.push('');
504
504
  for (const entry of toc) {
505
505
  lines.push(`File ${entry.fileTag} ${entry.url}:`);
package/src/explorbot.ts CHANGED
@@ -210,7 +210,7 @@ export class ExplorBot {
210
210
  const state = stateManager.getCurrentState();
211
211
  return state ? ActionResult.fromState(state) : null;
212
212
  };
213
- const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState });
213
+ const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState, supervisor: true });
214
214
  return new Pilot(ai, tools, researcher, explorer, experienceTracker);
215
215
  }));
216
216
  }