Skip to content

Commit 832cdf5

Browse files
authored
Merge pull request #212 from PredicateSystems/pause_resume
agent pause/resume feature
2 parents 9e60dfd + e2ac39e commit 832cdf5

6 files changed

Lines changed: 252 additions & 46 deletions

File tree

src/agents/planner-executor/extraction-keywords.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ const AMBIGUOUS_VERBS: readonly string[] = [
4141
'show',
4242
'tell',
4343
'display',
44+
'provide',
45+
'report',
46+
'give',
47+
'identify',
48+
'collect',
49+
'gather',
50+
'return',
51+
'output',
4452
];
4553

4654
/**
@@ -67,6 +75,14 @@ const EXTRACTION_PHRASES: readonly string[] = [
6775
'find the name',
6876
'how many',
6977
'how much',
78+
'sale price',
79+
'sale prices',
80+
'first 5',
81+
'first 10',
82+
'first 3',
83+
'top 5',
84+
'top 10',
85+
'top 3',
7086
];
7187

7288
/**
@@ -121,6 +137,9 @@ const CONTENT_NOUNS: readonly string[] = [
121137
'url',
122138
'image',
123139
'photo',
140+
'product',
141+
'results',
142+
'listings',
124143
];
125144

126145
/**
@@ -267,29 +286,38 @@ export function getExtractionDomainGuidance(): string {
267286
return `
268287
269288
IMPORTANT: Extraction Task Planning Rules
270-
=========================================
271-
For extraction tasks where data is already visible on the page:
289+
========================================
272290
273-
1. If the data you need is VISIBLE in the page context above:
274-
- Use EXTRACT directly as the ONLY step - no clicking needed
275-
- The EXTRACT action will read the visible text from the page
291+
STEP 1 - CHECK CURRENT URL:
292+
Before choosing an action, compare the Current URL to the goal.
293+
- Does the current page contain the data requested?
294+
- If the goal mentions a specific section/page (e.g., "show hn", "top stories", "/show"), check if the URL matches.
295+
- If you are NOT on the right page, NAVIGATE to the correct URL first.
276296
277-
2. If you need to navigate to see the data:
278-
- First CLICK or NAVIGATE to the right page
279-
- Then use EXTRACT
297+
STEP 2 - EXTRACT VISIBLE DATA:
298+
If the data is VISIBLE in the page context:
299+
- Use EXTRACT directly - no clicking needed
300+
- The EXTRACT action reads visible text from the current page
280301
281302
CRITICAL: Do NOT click on links to external sites when extracting.
282303
- Post/article titles often link to EXTERNAL sites
283304
- To extract a title that is visible, use EXTRACT directly on the current page
284305
- Only click if you need to navigate to a detail page (e.g., for comments)
285306
286-
Example for "Extract the title of the first post":
287-
{
288-
"action": "EXTRACT",
289-
"target": "first post title",
290-
"goal": "Extract the first post title from the page",
291-
"verify": []
292-
}
307+
Example - wrong page, need to navigate first:
308+
Goal: "extract the title of the first showhn post on hackernews show"
309+
Current URL: news.ycombinator.com/news (wrong page, need /show)
310+
{"action":"NAVIGATE","target":"https://news.ycombinator.com/show","verify":[{"predicate":"url_contains","args":["show"]}],"reasoning":"navigate to Show HN page"}
311+
312+
Example - on correct page, extract directly:
313+
Goal: "extract the title of the first showhn post"
314+
Current URL: news.ycombinator.com/show (correct page, data visible)
315+
{"action":"EXTRACT","target":"first ShowHN post title","goal":"Extract the title of the first ShowHN post","verify":[],"reasoning":"data is visible on current page"}
316+
317+
Example - product price on listing page:
318+
Goal: "find the price of the first laptop"
319+
Current URL: store.com/laptops (correct page, prices visible)
320+
{"action":"EXTRACT","target":"price of first laptop","goal":"Extract the price of the first laptop listing","verify":[],"reasoning":"prices are visible in listing elements"}
293321
`;
294322
}
295323

src/agents/planner-executor/plan-models.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ export type ActionType = z.infer<typeof ActionType>;
5858
* Type for a plan step.
5959
*/
6060
export interface PlanStep {
61-
id: number;
62-
goal: string;
61+
id?: number;
62+
goal?: string;
6363
action: ActionType;
6464
target?: string;
6565
intent?: string;
@@ -87,12 +87,15 @@ const HeuristicHintSchema = z.object({
8787

8888
export const PlanStepSchema = z.lazy(() =>
8989
z.object({
90-
id: z.number().describe('Step ID (1-indexed, contiguous)'),
91-
goal: z.string().describe('Human-readable goal for this step'),
90+
id: z.number().optional().describe('Step ID (1-indexed, contiguous)'),
91+
goal: z.string().optional().describe('Human-readable goal for this step'),
9292
action: ActionType.describe(
9393
'Action type: NAVIGATE, CLICK, TYPE, TYPE_AND_SUBMIT, SCROLL, PRESS, WAIT, EXTRACT, STUCK, DONE'
9494
),
95-
target: z.string().optional().describe('URL for NAVIGATE action'),
95+
target: z
96+
.union([z.string(), z.record(z.string(), z.unknown())])
97+
.optional()
98+
.describe('URL for NAVIGATE action'),
9699
intent: z.string().optional().describe('Intent hint for CLICK action'),
97100
input: z.string().optional().describe('Text for TYPE_AND_SUBMIT action'),
98101
verify: z.array(PredicateSpecSchema).default([]).describe('Verification predicates'),

src/agents/planner-executor/plan-utils.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,13 @@ function normalizeStep(step: Record<string, unknown>): Record<string, unknown> {
477477
if ('target' in normalizedStep && typeof normalizedStep.target === 'number') {
478478
normalizedStep.target = String(normalizedStep.target);
479479
}
480+
if (
481+
'target' in normalizedStep &&
482+
typeof normalizedStep.target === 'object' &&
483+
normalizedStep.target !== null
484+
) {
485+
normalizedStep.target = JSON.stringify(normalizedStep.target);
486+
}
480487
if ('target' in normalizedStep && normalizedStep.target === null) {
481488
delete normalizedStep.target;
482489
}
@@ -629,33 +636,36 @@ export function validatePlanSmoothness(plan: Plan): string[] {
629636

630637
// Check each step
631638
let prevAction: string | null = null;
639+
let prevId: number | undefined = undefined;
632640
for (const step of plan.steps) {
641+
const stepLabel = step.id ?? '?';
633642
// Check for missing verification
634643
if ((!step.verify || step.verify.length === 0) && step.required !== false) {
635-
warnings.push(`Step ${step.id} has no verification predicates`);
644+
warnings.push(`Step ${stepLabel} has no verification predicates`);
636645
}
637646

638647
// Check for consecutive same actions (might indicate loop)
639648
if (step.action === prevAction && step.action === 'CLICK') {
640-
warnings.push(`Steps ${step.id - 1} and ${step.id} both use ${step.action}`);
649+
warnings.push(`Steps ${prevId ?? '?'} and ${stepLabel} both use ${step.action}`);
641650
}
642651

643652
// Check for NAVIGATE without target
644653
if (step.action === 'NAVIGATE' && !step.target) {
645-
warnings.push(`Step ${step.id} is NAVIGATE but has no target URL`);
654+
warnings.push(`Step ${stepLabel} is NAVIGATE but has no target URL`);
646655
}
647656

648657
// Check for CLICK without intent
649658
if (step.action === 'CLICK' && !step.intent) {
650-
warnings.push(`Step ${step.id} is CLICK but has no intent hint`);
659+
warnings.push(`Step ${stepLabel} is CLICK but has no intent hint`);
651660
}
652661

653662
// Check for TYPE_AND_SUBMIT without input
654663
if (step.action === 'TYPE_AND_SUBMIT' && !step.input) {
655-
warnings.push(`Step ${step.id} is TYPE_AND_SUBMIT but has no input`);
664+
warnings.push(`Step ${stepLabel} is TYPE_AND_SUBMIT but has no input`);
656665
}
657666

658667
prevAction = step.action;
668+
prevId = step.id;
659669
}
660670

661671
return warnings;

0 commit comments

Comments
 (0)