explorbot 0.3.4 → 0.3.5
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 +1 -1
- package/dist/src/ai/pilot.js +2 -0
- package/dist/src/ai/planner.js +1 -1
- package/dist/src/ai/provider.js +28 -2
- package/package.json +1 -1
- package/src/ai/pilot.ts +2 -0
- package/src/ai/planner.ts +1 -1
- package/src/ai/provider.ts +28 -2
package/dist/package.json
CHANGED
package/dist/src/ai/pilot.js
CHANGED
|
@@ -1005,6 +1005,8 @@ export class Pilot {
|
|
|
1005
1005
|
state), instruct Tester to verify() and finish(). If goal was already true at the start, propose
|
|
1006
1006
|
different input data so the test is meaningful. If Tester repeats the same successful action, STOP.
|
|
1007
1007
|
|
|
1008
|
+
If needed you should pick the exact item the scenario should act on (from the page, or precondition() one) and pass it to tester
|
|
1009
|
+
|
|
1008
1010
|
Action classification: GOAL-ADVANCING actions mutate the scenario's subject data (create/edit/delete/submit/verify).
|
|
1009
1011
|
VIEW-ONLY actions toggle filters/tabs/sort/collapse without changing data. One VIEW-ONLY to reveal a
|
|
1010
1012
|
target is fine; ≥2 consecutive VIEW-ONLY actions with no GOAL-ADVANCING action in between is thrashing
|
package/dist/src/ai/planner.js
CHANGED
|
@@ -24,7 +24,7 @@ const TasksSchema = z.object({
|
|
|
24
24
|
planName: z.string().describe('Short descriptive name for the test plan (e.g., "User Authentication Testing", "Product Catalog Navigation", "Form Validation Tests")'),
|
|
25
25
|
scenarios: z
|
|
26
26
|
.array(z.object({
|
|
27
|
-
scenario: z.string().describe('A single sentence describing
|
|
27
|
+
scenario: z.string().describe('A single sentence describing the behavior to test.'),
|
|
28
28
|
priority: z.enum(['critical', 'important', 'high', 'normal', 'low']).describe('Priority of the task based on business importance'),
|
|
29
29
|
startUrl: z.string().nullable().describe('Start URL for the test if different from plan URL. Use only stable feature/list/detail pages, not transient create/edit/modal URLs unless the scenario specifically starts inside that form.'),
|
|
30
30
|
steps: z.array(z.string()).describe('List of steps to perform for this scenario. Each step should be a specific action (e.g., "Open the form", "Enter required data", "Submit the form"). Keep steps atomic and actionable.'),
|
package/dist/src/ai/provider.js
CHANGED
|
@@ -2,7 +2,8 @@ import { OpenTelemetry } from '@ai-sdk/otel';
|
|
|
2
2
|
import { LangfuseSpanProcessor } from '@langfuse/otel';
|
|
3
3
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
4
4
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
5
|
-
import
|
|
5
|
+
import dedent from 'dedent';
|
|
6
|
+
import { APICallError, generateObject, generateText, isStepCount, registerTelemetry, tool } from 'ai';
|
|
6
7
|
import { z } from 'zod';
|
|
7
8
|
import { clearActivity, setActivity } from "../activity.js";
|
|
8
9
|
import { configuredModels, modelName as getModelName } from '../config.js';
|
|
@@ -382,9 +383,18 @@ export class Provider {
|
|
|
382
383
|
if (extraStop)
|
|
383
384
|
stopConditions.push(extraStop);
|
|
384
385
|
const config = this.buildGenerateConfig({ tools: toolsWithCommentary, maxOutputTokens: 16384, toolChoice: 'auto', experimental_repairToolCall: repairToolCall }, { stopWhen: stopConditions, model }, options);
|
|
386
|
+
let attemptMessages = messages;
|
|
387
|
+
let invalidRequestFeedbackAdded = false;
|
|
385
388
|
try {
|
|
386
389
|
const response = await this.withModelRequestSlot(() => withRetry(async () => {
|
|
387
|
-
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages, ...config, abortSignal: signal }), config.timeout || 30000))
|
|
390
|
+
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages: attemptMessages, ...config, abortSignal: signal }), config.timeout || 30000).catch((error) => {
|
|
391
|
+
if (!invalidRequestFeedbackAdded) {
|
|
392
|
+
const amended = withInvalidRequestFeedback(attemptMessages, error);
|
|
393
|
+
invalidRequestFeedbackAdded = amended !== attemptMessages;
|
|
394
|
+
attemptMessages = amended;
|
|
395
|
+
}
|
|
396
|
+
throw error;
|
|
397
|
+
}));
|
|
388
398
|
this.recordUsage(options.agentName || 'unknown', modelName, result.usage);
|
|
389
399
|
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
390
400
|
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
@@ -620,6 +630,22 @@ function repairToolCall(options) {
|
|
|
620
630
|
return repairChannelMarker(options);
|
|
621
631
|
return repairHarmonyChannel(options);
|
|
622
632
|
}
|
|
633
|
+
function withInvalidRequestFeedback(messages, error) {
|
|
634
|
+
if (!(error instanceof APICallError) || error.statusCode !== 400)
|
|
635
|
+
return messages;
|
|
636
|
+
tag('warning').log('Provider rejected the request as invalid — relaying its reason before the retry');
|
|
637
|
+
return [
|
|
638
|
+
...messages,
|
|
639
|
+
{
|
|
640
|
+
role: 'user',
|
|
641
|
+
content: dedent `
|
|
642
|
+
The previous request was rejected by the provider as invalid:
|
|
643
|
+
"${error.message}"
|
|
644
|
+
Fix what it describes and re-issue the request.
|
|
645
|
+
`,
|
|
646
|
+
},
|
|
647
|
+
];
|
|
648
|
+
}
|
|
623
649
|
function repairChannelMarker({ toolCall, tools }) {
|
|
624
650
|
const markerIndex = toolCall.toolName.indexOf('<|channel|>');
|
|
625
651
|
if (markerIndex <= 0)
|
package/package.json
CHANGED
package/src/ai/pilot.ts
CHANGED
|
@@ -1114,6 +1114,8 @@ export class Pilot implements Agent {
|
|
|
1114
1114
|
state), instruct Tester to verify() and finish(). If goal was already true at the start, propose
|
|
1115
1115
|
different input data so the test is meaningful. If Tester repeats the same successful action, STOP.
|
|
1116
1116
|
|
|
1117
|
+
If needed you should pick the exact item the scenario should act on (from the page, or precondition() one) and pass it to tester
|
|
1118
|
+
|
|
1117
1119
|
Action classification: GOAL-ADVANCING actions mutate the scenario's subject data (create/edit/delete/submit/verify).
|
|
1118
1120
|
VIEW-ONLY actions toggle filters/tabs/sort/collapse without changing data. One VIEW-ONLY to reveal a
|
|
1119
1121
|
target is fine; ≥2 consecutive VIEW-ONLY actions with no GOAL-ADVANCING action in between is thrashing
|
package/src/ai/planner.ts
CHANGED
|
@@ -33,7 +33,7 @@ const TasksSchema = z.object({
|
|
|
33
33
|
scenarios: z
|
|
34
34
|
.array(
|
|
35
35
|
z.object({
|
|
36
|
-
scenario: z.string().describe('A single sentence describing
|
|
36
|
+
scenario: z.string().describe('A single sentence describing the behavior to test.'),
|
|
37
37
|
priority: z.enum(['critical', 'important', 'high', 'normal', 'low']).describe('Priority of the task based on business importance'),
|
|
38
38
|
startUrl: z.string().nullable().describe('Start URL for the test if different from plan URL. Use only stable feature/list/detail pages, not transient create/edit/modal URLs unless the scenario specifically starts inside that form.'),
|
|
39
39
|
steps: z.array(z.string()).describe('List of steps to perform for this scenario. Each step should be a specific action (e.g., "Open the form", "Enter required data", "Submit the form"). Keep steps atomic and actionable.'),
|
package/src/ai/provider.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { OpenTelemetry } from '@ai-sdk/otel';
|
|
|
2
2
|
import { LangfuseSpanProcessor } from '@langfuse/otel';
|
|
3
3
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
4
4
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
5
|
-
import
|
|
5
|
+
import dedent from 'dedent';
|
|
6
|
+
import { APICallError, generateObject, generateText, isStepCount, registerTelemetry, tool } from 'ai';
|
|
6
7
|
import type { ModelMessage } from 'ai';
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import { clearActivity, setActivity } from '../activity.ts';
|
|
@@ -423,10 +424,19 @@ export class Provider {
|
|
|
423
424
|
const stopConditions: any[] = [isStepCount(maxRoundtrips)];
|
|
424
425
|
if (extraStop) stopConditions.push(extraStop);
|
|
425
426
|
const config = this.buildGenerateConfig({ tools: toolsWithCommentary, maxOutputTokens: 16384, toolChoice: 'auto', experimental_repairToolCall: repairToolCall }, { stopWhen: stopConditions, model }, options);
|
|
427
|
+
let attemptMessages = messages;
|
|
428
|
+
let invalidRequestFeedbackAdded = false;
|
|
426
429
|
try {
|
|
427
430
|
const response = await this.withModelRequestSlot(() =>
|
|
428
431
|
withRetry(async () => {
|
|
429
|
-
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages, ...config, abortSignal: signal }), config.timeout || 30000))
|
|
432
|
+
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages: attemptMessages, ...config, abortSignal: signal }), config.timeout || 30000).catch((error) => {
|
|
433
|
+
if (!invalidRequestFeedbackAdded) {
|
|
434
|
+
const amended = withInvalidRequestFeedback(attemptMessages, error);
|
|
435
|
+
invalidRequestFeedbackAdded = amended !== attemptMessages;
|
|
436
|
+
attemptMessages = amended;
|
|
437
|
+
}
|
|
438
|
+
throw error;
|
|
439
|
+
})) as any;
|
|
430
440
|
this.recordUsage(options.agentName || 'unknown', modelName, result.usage);
|
|
431
441
|
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
432
442
|
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
@@ -693,6 +703,22 @@ function repairToolCall(options: ToolCallRepairOptions): any | null {
|
|
|
693
703
|
return repairHarmonyChannel(options);
|
|
694
704
|
}
|
|
695
705
|
|
|
706
|
+
function withInvalidRequestFeedback(messages: ModelMessage[], error: unknown): ModelMessage[] {
|
|
707
|
+
if (!(error instanceof APICallError) || error.statusCode !== 400) return messages;
|
|
708
|
+
tag('warning').log('Provider rejected the request as invalid — relaying its reason before the retry');
|
|
709
|
+
return [
|
|
710
|
+
...messages,
|
|
711
|
+
{
|
|
712
|
+
role: 'user',
|
|
713
|
+
content: dedent`
|
|
714
|
+
The previous request was rejected by the provider as invalid:
|
|
715
|
+
"${error.message}"
|
|
716
|
+
Fix what it describes and re-issue the request.
|
|
717
|
+
`,
|
|
718
|
+
},
|
|
719
|
+
];
|
|
720
|
+
}
|
|
721
|
+
|
|
696
722
|
function repairChannelMarker({ toolCall, tools }: ToolCallRepairOptions): any | null {
|
|
697
723
|
const markerIndex = toolCall.toolName.indexOf('<|channel|>');
|
|
698
724
|
if (markerIndex <= 0) return null;
|