usertester 0.1.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/dist/browser/agent.d.ts +33 -0
  4. package/dist/browser/agent.js +393 -0
  5. package/dist/browser/agent.js.map +1 -0
  6. package/dist/cli/cleanup.d.ts +5 -0
  7. package/dist/cli/cleanup.js +75 -0
  8. package/dist/cli/cleanup.js.map +1 -0
  9. package/dist/cli/harness.d.ts +10 -0
  10. package/dist/cli/harness.js +108 -0
  11. package/dist/cli/harness.js.map +1 -0
  12. package/dist/cli/index.d.ts +5 -0
  13. package/dist/cli/index.js +31 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/kill.d.ts +5 -0
  16. package/dist/cli/kill.js +46 -0
  17. package/dist/cli/kill.js.map +1 -0
  18. package/dist/cli/logs.d.ts +5 -0
  19. package/dist/cli/logs.js +64 -0
  20. package/dist/cli/logs.js.map +1 -0
  21. package/dist/cli/profiles.d.ts +5 -0
  22. package/dist/cli/profiles.js +67 -0
  23. package/dist/cli/profiles.js.map +1 -0
  24. package/dist/cli/send.d.ts +5 -0
  25. package/dist/cli/send.js +46 -0
  26. package/dist/cli/send.js.map +1 -0
  27. package/dist/cli/setup.d.ts +6 -0
  28. package/dist/cli/setup.js +168 -0
  29. package/dist/cli/setup.js.map +1 -0
  30. package/dist/cli/spawn.d.ts +5 -0
  31. package/dist/cli/spawn.js +52 -0
  32. package/dist/cli/spawn.js.map +1 -0
  33. package/dist/cli/status.d.ts +5 -0
  34. package/dist/cli/status.js +85 -0
  35. package/dist/cli/status.js.map +1 -0
  36. package/dist/harness/applier.d.ts +38 -0
  37. package/dist/harness/applier.js +152 -0
  38. package/dist/harness/applier.js.map +1 -0
  39. package/dist/harness/index.d.ts +14 -0
  40. package/dist/harness/index.js +110 -0
  41. package/dist/harness/index.js.map +1 -0
  42. package/dist/harness/patterns.d.ts +14 -0
  43. package/dist/harness/patterns.js +96 -0
  44. package/dist/harness/patterns.js.map +1 -0
  45. package/dist/harness/proposer.d.ts +26 -0
  46. package/dist/harness/proposer.js +181 -0
  47. package/dist/harness/proposer.js.map +1 -0
  48. package/dist/harness/traces.d.ts +29 -0
  49. package/dist/harness/traces.js +65 -0
  50. package/dist/harness/traces.js.map +1 -0
  51. package/dist/harness/validator.d.ts +6 -0
  52. package/dist/harness/validator.js +112 -0
  53. package/dist/harness/validator.js.map +1 -0
  54. package/dist/inbox/agentmail.d.ts +11 -0
  55. package/dist/inbox/agentmail.js +36 -0
  56. package/dist/inbox/agentmail.js.map +1 -0
  57. package/dist/llm/provider.d.ts +15 -0
  58. package/dist/llm/provider.js +65 -0
  59. package/dist/llm/provider.js.map +1 -0
  60. package/dist/orchestrator/agent.d.ts +17 -0
  61. package/dist/orchestrator/agent.js +195 -0
  62. package/dist/orchestrator/agent.js.map +1 -0
  63. package/dist/orchestrator/index.d.ts +7 -0
  64. package/dist/orchestrator/index.js +92 -0
  65. package/dist/orchestrator/index.js.map +1 -0
  66. package/dist/orchestrator/retry.d.ts +27 -0
  67. package/dist/orchestrator/retry.js +145 -0
  68. package/dist/orchestrator/retry.js.map +1 -0
  69. package/dist/orchestrator/session.d.ts +13 -0
  70. package/dist/orchestrator/session.js +55 -0
  71. package/dist/orchestrator/session.js.map +1 -0
  72. package/dist/output/events.d.ts +12 -0
  73. package/dist/output/events.js +81 -0
  74. package/dist/output/events.js.map +1 -0
  75. package/dist/profiles/learner.d.ts +4 -0
  76. package/dist/profiles/learner.js +168 -0
  77. package/dist/profiles/learner.js.map +1 -0
  78. package/dist/tools/captcha.d.ts +19 -0
  79. package/dist/tools/captcha.js +76 -0
  80. package/dist/tools/captcha.js.map +1 -0
  81. package/dist/tools/inbox.d.ts +30 -0
  82. package/dist/tools/inbox.js +65 -0
  83. package/dist/tools/inbox.js.map +1 -0
  84. package/dist/types.d.ts +121 -0
  85. package/dist/types.js +30 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +60 -0
  88. package/tasks.example.json +5 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Autonomous retry loop with failure classification.
3
+ *
4
+ * After each failed agent.execute() call:
5
+ * 1. Classifies WHY it failed (TRANSIENT / WRONG_APPROACH / CAPABILITY_GAP / ENVIRONMENT_BLOCK)
6
+ * 2. Decides recovery strategy
7
+ * 3. Retries with adjusted instruction or new tools injected
8
+ * 4. Stops when: completed, or max attempts reached
9
+ *
10
+ * Based on: Meta-Harness (arxiv:2603.28052) + Live-SWE-agent (arxiv:2511.13646)
11
+ * Key principle: raw trace beats summaries for classification (never summarize before classifying)
12
+ */
13
+ import { cheapCall } from '../llm/provider.js';
14
+ import { readInboxEmail } from '../tools/inbox.js';
15
+ import { solveTurnstile, capsolverAvailable } from '../tools/captcha.js';
16
+ export const MAX_ATTEMPTS = 5;
17
+ // Signal patterns that map to failure types
18
+ export const FAILURE_SIGNALS = [
19
+ {
20
+ pattern: /DNS|ERR_NAME_NOT_RESOLVED|net::ERR|could not resolve|unreachable/i,
21
+ type: 'WRONG_APPROACH',
22
+ hint: 'Do not navigate to external web UIs. Use available API tools instead.',
23
+ },
24
+ {
25
+ pattern: /verification code|6.digit|magic link|check.*email|inbox|sent.*code/i,
26
+ type: 'CAPABILITY_GAP',
27
+ hint: 'Use the readInboxEmail tool to retrieve the verification code from the email inbox.',
28
+ },
29
+ {
30
+ pattern: /only request this after (\d+)|too many requests|rate.?limit|429|resend.*after/i,
31
+ type: 'RATE_LIMITED',
32
+ hint: 'Rate limited — wait the specified cooldown period before retrying.',
33
+ },
34
+ {
35
+ pattern: /timeout|503|temporarily unavailable|connection failed/i,
36
+ type: 'TRANSIENT',
37
+ hint: 'Transient error — retry the same approach after a short wait.',
38
+ },
39
+ {
40
+ pattern: /captcha|CAPTCHA|bot detection|unusual traffic/i,
41
+ type: 'ENVIRONMENT_BLOCK',
42
+ hint: 'CAPTCHA detected — cannot automate this step.',
43
+ },
44
+ ];
45
+ export async function classifyFailure(agentMessage, config) {
46
+ // Fast path: check signal patterns first (no LLM needed)
47
+ for (const { pattern, type, hint } of FAILURE_SIGNALS) {
48
+ if (pattern.test(agentMessage)) {
49
+ return { type, evidence: agentMessage.slice(0, 200), recoveryHint: hint };
50
+ }
51
+ }
52
+ // Slow path: ask the cheap model to classify
53
+ const prompt = `You are classifying why a browser automation agent failed.
54
+
55
+ Agent failure message:
56
+ ${agentMessage.slice(0, 800)}
57
+
58
+ Classify into exactly one of:
59
+ - COMPLETE: the task actually succeeded (agent was wrong about failing)
60
+ - TRANSIENT: network timeout, rate limit, temporary error — retry same approach
61
+ - WRONG_APPROACH: agent used wrong method (e.g. navigated to web UI instead of using API)
62
+ - CAPABILITY_GAP: agent knew what it needed but lacked a tool to do it
63
+ - ENVIRONMENT_BLOCK: CAPTCHA, auth wall, or structural blocker — cannot automate
64
+ - ESCALATE: unclear or unrecoverable
65
+
66
+ Reply with JSON: {"type": "...", "evidence": "one sentence", "recoveryHint": "one sentence"}`;
67
+ try {
68
+ const text = await cheapCall(prompt, config, 150);
69
+ const match = text.match(/\{[\s\S]*\}/);
70
+ if (match) {
71
+ const parsed = JSON.parse(match[0]);
72
+ return {
73
+ type: parsed.type,
74
+ evidence: parsed.evidence ?? '',
75
+ recoveryHint: parsed.recoveryHint ?? '',
76
+ };
77
+ }
78
+ }
79
+ catch { }
80
+ return { type: 'ESCALATE', evidence: agentMessage.slice(0, 100), recoveryHint: 'Manual intervention needed' };
81
+ }
82
+ // Build a ToolSet to inject based on failure classification
83
+ export function selectToolsForRecovery(classification) {
84
+ const tools = {};
85
+ if (classification.type === 'CAPABILITY_GAP' ||
86
+ (classification.type === 'WRONG_APPROACH' && /email|inbox|code|verification/i.test(classification.evidence))) {
87
+ tools['readInboxEmail'] = readInboxEmail;
88
+ }
89
+ // ENVIRONMENT_BLOCK from CAPTCHA: inject solver if CapSolver is configured
90
+ if (classification.type === 'ENVIRONMENT_BLOCK' &&
91
+ /captcha|turnstile|cloudflare|verify.*human/i.test(classification.evidence) &&
92
+ capsolverAvailable()) {
93
+ tools['solveTurnstile'] = solveTurnstile;
94
+ }
95
+ return tools;
96
+ }
97
+ // Build a constraint addendum to inject into the next attempt's instruction
98
+ export function buildRetryInstruction(originalInstruction, history, memory, currentUrl) {
99
+ // --- Recovery tip takes priority over failure constraints ---
100
+ if (memory?.recoveryTips?.length && currentUrl) {
101
+ const tip = memory.recoveryTips.find(t => {
102
+ try {
103
+ return currentUrl.includes(new URL(t.url).hostname) || t.url.includes(currentUrl);
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ });
109
+ if (tip) {
110
+ return [
111
+ `App URL: ${currentUrl} — navigate here if the page is blank or shows an error.`,
112
+ ``,
113
+ `PROVEN APPROACH FOR THIS APP:`,
114
+ `The following approach previously succeeded (tools: ${tip.toolsUsed.join(', ') || 'none'}):`,
115
+ `"${tip.successApproach}"`,
116
+ ``,
117
+ `REPEAT this approach exactly. Ignore any previous context suggesting otherwise.`,
118
+ ``,
119
+ `Task: ${originalInstruction}`,
120
+ ].join('\n');
121
+ }
122
+ }
123
+ // --- Fallback: accumulate failure constraints (existing behavior) ---
124
+ if (history.length === 0)
125
+ return originalInstruction;
126
+ const constraints = history
127
+ .filter(a => a.failureType && a.failureType !== 'TRANSIENT' && a.failureType !== 'COMPLETE')
128
+ .map((a) => `Attempt ${a.attempt} failed (${a.failureType}): ${a.agentMessage.slice(0, 150)}`);
129
+ if (constraints.length === 0)
130
+ return originalInstruction;
131
+ const toolHints = history.some(a => a.toolsInjected.includes('readInboxEmail'))
132
+ ? '\n\nIMPORTANT: You have a readInboxEmail tool available. Use it to read any verification emails — do NOT try to navigate to a web-based inbox.'
133
+ : '';
134
+ // Always pin the URL so the agent never guesses if navigation fails
135
+ const urlPin = currentUrl ? `\nApp URL: ${currentUrl} — always navigate here first if the page is blank or shows an error.` : '';
136
+ return [
137
+ originalInstruction,
138
+ urlPin,
139
+ '',
140
+ '--- Previous attempt context ---',
141
+ ...constraints,
142
+ toolHints,
143
+ ].join('\n');
144
+ }
145
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/orchestrator/retry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { SessionState, AgentState, AgentStatus } from '../types.js';
2
+ export declare function createSession(opts: {
3
+ resultsDir: string;
4
+ sessionId: string;
5
+ url: string;
6
+ agentIds: string[];
7
+ }): SessionState;
8
+ export declare function getStatePath(resultsDir: string, sessionId: string): string;
9
+ export declare function saveSession(resultsDir: string, state: SessionState): void;
10
+ export declare function loadSession(resultsDir: string, sessionId: string): SessionState | null;
11
+ export declare function updateAgent(state: SessionState, agentId: string, updates: Partial<AgentState>): SessionState;
12
+ export declare function transitionAgent(state: SessionState, agentId: string, to: AgentStatus, extras?: Partial<AgentState>): SessionState;
13
+ export declare function resolveSessionId(resultsDir: string, sessionId?: string): string | null;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Session state management
3
+ * Reads/writes ~/.usertester/<session-id>/state.json (atomic)
4
+ */
5
+ import path from 'node:path';
6
+ import fs from 'node:fs';
7
+ import { writeStateAtomic, readState, getSessionDir } from '../output/events.js';
8
+ export function createSession(opts) {
9
+ const agents = opts.agentIds.map(id => ({
10
+ id,
11
+ status: 'QUEUED',
12
+ updatedAt: Date.now(),
13
+ retryCount: 0,
14
+ }));
15
+ return {
16
+ sessionId: opts.sessionId,
17
+ url: opts.url,
18
+ agents,
19
+ startedAt: Date.now(),
20
+ };
21
+ }
22
+ export function getStatePath(resultsDir, sessionId) {
23
+ return path.join(getSessionDir(resultsDir, sessionId), 'state.json');
24
+ }
25
+ export function saveSession(resultsDir, state) {
26
+ writeStateAtomic(getStatePath(resultsDir, state.sessionId), state);
27
+ }
28
+ export function loadSession(resultsDir, sessionId) {
29
+ return readState(getStatePath(resultsDir, sessionId));
30
+ }
31
+ export function updateAgent(state, agentId, updates) {
32
+ return {
33
+ ...state,
34
+ agents: state.agents.map(a => a.id === agentId
35
+ ? { ...a, ...updates, updatedAt: Date.now() }
36
+ : a),
37
+ };
38
+ }
39
+ export function transitionAgent(state, agentId, to, extras) {
40
+ return updateAgent(state, agentId, { status: to, ...extras });
41
+ }
42
+ export function resolveSessionId(resultsDir, sessionId) {
43
+ if (sessionId)
44
+ return sessionId;
45
+ // Read current symlink
46
+ try {
47
+ const currentLink = path.join(resultsDir, 'current');
48
+ const target = fs.readlinkSync(currentLink);
49
+ return path.basename(target);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/orchestrator/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAA;AAExB,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEhF,MAAM,UAAU,aAAa,CAAC,IAK7B;IACC,MAAM,MAAM,GAAiB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACpD,EAAE;QACF,MAAM,EAAE,QAAQ;QAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,UAAU,EAAE,CAAC;KACd,CAAC,CAAC,CAAA;IAEH,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,MAAM;QACN,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACtB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,SAAiB;IAChE,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,YAAY,CAAC,CAAA;AACtE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAAkB,EAAE,KAAmB;IACjE,gBAAgB,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAAkB,EAAE,SAAiB;IAC/D,OAAO,SAAS,CAAe,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,KAAmB,EACnB,OAAe,EACf,OAA4B;IAE5B,OAAO;QACL,GAAG,KAAK;QACR,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAC3B,CAAC,CAAC,EAAE,KAAK,OAAO;YACd,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;YAC7C,CAAC,CAAC,CAAC,CACN;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,KAAmB,EACnB,OAAe,EACf,EAAe,EACf,MAA4B;IAE5B,OAAO,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,SAAkB;IACrE,IAAI,SAAS;QAAE,OAAO,SAAS,CAAA;IAC/B,uBAAuB;IACvB,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;QACpD,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,CAAW,CAAA;QACrD,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { UsertesterEvent } from '../types.js';
2
+ export declare function emitEvent(event: UsertesterEvent): void;
3
+ export declare function ts(): string;
4
+ export declare function appendAgentEvent(agentDir: string, data: Record<string, unknown>): void;
5
+ export declare function appendAgentLog(agentDir: string, message: string): void;
6
+ export declare function writeStateAtomic(statePath: string, state: unknown): void;
7
+ export declare function readState<T>(statePath: string): T | null;
8
+ export declare function readPendingCommand(commandsPath: string): import('../types.js').AgentCommand | null;
9
+ export declare function writeCommand(commandsPath: string, cmd: import('../types.js').AgentCommand): void;
10
+ export declare function getSessionDir(resultsDir: string, sessionId: string): string;
11
+ export declare function getAgentDir(sessionDir: string, agentId: string): string;
12
+ export declare function initSessionDirs(resultsDir: string, sessionId: string, agentIds: string[]): void;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * NDJSON event emitter + per-agent event log writer
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ export function emitEvent(event) {
7
+ process.stdout.write(JSON.stringify(event) + '\n');
8
+ }
9
+ export function ts() {
10
+ return new Date().toISOString();
11
+ }
12
+ // --- Per-agent event log (push model) ---
13
+ export function appendAgentEvent(agentDir, data) {
14
+ const logPath = path.join(agentDir, 'events.ndjson');
15
+ fs.appendFileSync(logPath, JSON.stringify({ ...data, ts: ts() }) + '\n');
16
+ }
17
+ export function appendAgentLog(agentDir, message) {
18
+ const logPath = path.join(agentDir, 'agent.log');
19
+ fs.appendFileSync(logPath, `[${ts()}] ${message}\n`);
20
+ }
21
+ // --- State file (atomic write) ---
22
+ export function writeStateAtomic(statePath, state) {
23
+ const tmp = statePath + '.tmp';
24
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
25
+ fs.renameSync(tmp, statePath);
26
+ }
27
+ export function readState(statePath) {
28
+ try {
29
+ return JSON.parse(fs.readFileSync(statePath, 'utf-8'));
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ // --- Command mailbox ---
36
+ export function readPendingCommand(commandsPath) {
37
+ try {
38
+ const content = fs.readFileSync(commandsPath, 'utf-8').trim();
39
+ if (!content)
40
+ return null;
41
+ const lines = content.split('\n').filter(Boolean);
42
+ if (lines.length === 0)
43
+ return null;
44
+ // Read last command
45
+ const cmd = JSON.parse(lines[lines.length - 1]);
46
+ // Clear the file after reading
47
+ fs.writeFileSync(commandsPath, '');
48
+ return cmd;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ export function writeCommand(commandsPath, cmd) {
55
+ fs.appendFileSync(commandsPath, JSON.stringify(cmd) + '\n');
56
+ }
57
+ // --- Session dir layout ---
58
+ export function getSessionDir(resultsDir, sessionId) {
59
+ return path.join(resultsDir, sessionId);
60
+ }
61
+ export function getAgentDir(sessionDir, agentId) {
62
+ return path.join(sessionDir, agentId);
63
+ }
64
+ export function initSessionDirs(resultsDir, sessionId, agentIds) {
65
+ const sessionDir = getSessionDir(resultsDir, sessionId);
66
+ fs.mkdirSync(sessionDir, { recursive: true });
67
+ for (const agentId of agentIds) {
68
+ const agentDir = getAgentDir(sessionDir, agentId);
69
+ fs.mkdirSync(path.join(agentDir, 'screenshots'), { recursive: true });
70
+ // Create empty command mailbox
71
+ fs.writeFileSync(path.join(agentDir, 'commands.ndjson'), '');
72
+ }
73
+ // Symlink ~/.usertester/current → latest session
74
+ const currentLink = path.join(resultsDir, 'current');
75
+ try {
76
+ fs.unlinkSync(currentLink);
77
+ }
78
+ catch { }
79
+ fs.symlinkSync(sessionDir, currentLink);
80
+ }
81
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/output/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { ProfileFacts, SessionMemory, RecoveryTip } from '../types.js';
2
+ export declare function loadProfile(resultsDir: string, url: string, scenario: string): ProfileFacts | null;
3
+ export declare function updateProfile(resultsDir: string, url: string, scenario: string, memory: SessionMemory): Promise<void>;
4
+ export declare function updateProfileWithSuccess(resultsDir: string, tip: RecoveryTip): Promise<void>;
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Profile meta-learning system
3
+ *
4
+ * After each session, extract reusable observations from session memory
5
+ * and persist them as facts.json per (url, scenario).
6
+ *
7
+ * Next run: agent loads these hints via BrowserAgent.start(profileFacts).
8
+ * Repeated failures become high-confidence hints ("avoid CAPTCHA on form submit").
9
+ *
10
+ * Inspired by the meta-harness paper (arxiv:2603.28052): post-session outer loop
11
+ * that reads execution traces and updates the harness config.
12
+ */
13
+ import path from 'node:path';
14
+ import fs from 'node:fs';
15
+ import { cheapCall } from '../llm/provider.js';
16
+ const PROFILES_DIR_NAME = 'profiles';
17
+ function profileKey(url, scenario) {
18
+ // Stable key from url domain + scenario
19
+ const domain = new URL(url).hostname.replace(/\./g, '_');
20
+ return `${domain}_${scenario}`;
21
+ }
22
+ function getProfilePath(resultsDir, url, scenario) {
23
+ const profilesDir = path.join(resultsDir, PROFILES_DIR_NAME);
24
+ fs.mkdirSync(profilesDir, { recursive: true });
25
+ return path.join(profilesDir, `${profileKey(url, scenario)}.json`);
26
+ }
27
+ export function loadProfile(resultsDir, url, scenario) {
28
+ const profilePath = getProfilePath(resultsDir, url, scenario);
29
+ try {
30
+ return JSON.parse(fs.readFileSync(profilePath, 'utf-8'));
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ export async function updateProfile(resultsDir, url, scenario, memory) {
37
+ const profilePath = getProfilePath(resultsDir, url, scenario);
38
+ const existing = loadProfile(resultsDir, url, scenario) ?? {
39
+ url,
40
+ scenario,
41
+ harnessHints: [],
42
+ runCount: 0,
43
+ lastRunAt: Date.now(),
44
+ };
45
+ const failedActions = memory.actions.filter(a => a.result === 'failed');
46
+ if (failedActions.length === 0 && existing.harnessHints.length > 0) {
47
+ // Perfect run — bump confidence on existing hints, update run count
48
+ existing.runCount++;
49
+ existing.lastRunAt = Date.now();
50
+ existing.harnessHints = existing.harnessHints.map(h => ({
51
+ ...h,
52
+ confidence: Math.min(1.0, h.confidence + 0.1),
53
+ }));
54
+ writeProfileAtomic(profilePath, existing);
55
+ return;
56
+ }
57
+ // Extract new hints from failures using LLM
58
+ const failuresStr = failedActions
59
+ .slice(0, 10)
60
+ .map(a => `- ${a.action}: ${a.observation}`)
61
+ .join('\n');
62
+ let newHints = [];
63
+ try {
64
+ const responseText = await cheapCall(`You are analyzing failed browser automation actions to extract reusable hints for future runs.
65
+
66
+ URL: ${url}
67
+ Scenario: ${scenario}
68
+ Failed actions:
69
+ ${failuresStr}
70
+
71
+ Extract 1-3 brief, actionable hints that would help future automation avoid these failures.
72
+ Each hint should be a concrete observation (e.g., "CAPTCHA appears after 3rd form submit attempt").
73
+ Reply with JSON array: [{"observation": "...", "confidence": 0.7}]
74
+ Only include hints where confidence >= 0.5.`, undefined, 400);
75
+ const match = responseText.match(/\[[\s\S]*\]/);
76
+ if (match) {
77
+ const parsed = JSON.parse(match[0]);
78
+ newHints = parsed.map(h => ({
79
+ observation: h.observation,
80
+ confidence: Math.min(1.0, Math.max(0.0, h.confidence)),
81
+ addedAt: Date.now(),
82
+ }));
83
+ }
84
+ }
85
+ catch {
86
+ // Learner failures are non-fatal
87
+ }
88
+ // Merge: update confidence on existing similar hints, add new ones
89
+ const updated = mergeHints(existing.harnessHints, newHints);
90
+ const profile = {
91
+ ...existing,
92
+ harnessHints: updated,
93
+ runCount: existing.runCount + 1,
94
+ lastRunAt: Date.now(),
95
+ };
96
+ writeProfileAtomic(profilePath, profile);
97
+ }
98
+ function mergeHints(existing, newHints) {
99
+ const merged = [...existing];
100
+ for (const hint of newHints) {
101
+ // Check for similar existing hint (simple substring match)
102
+ const similar = merged.findIndex(h => h.observation.toLowerCase().includes(hint.observation.toLowerCase().slice(0, 20)));
103
+ if (similar >= 0) {
104
+ // Boost confidence
105
+ merged[similar] = {
106
+ ...merged[similar],
107
+ confidence: Math.min(1.0, merged[similar].confidence + 0.15),
108
+ };
109
+ }
110
+ else {
111
+ merged.push(hint);
112
+ }
113
+ }
114
+ // Keep top 10 by confidence, prune low-confidence stale hints
115
+ return merged
116
+ .filter(h => h.confidence > 0.3)
117
+ .sort((a, b) => b.confidence - a.confidence)
118
+ .slice(0, 10);
119
+ }
120
+ export async function updateProfileWithSuccess(resultsDir, tip) {
121
+ const profilePath = getProfilePath(resultsDir, tip.url, tip.scenario);
122
+ const existing = loadProfile(resultsDir, tip.url, tip.scenario) ?? {
123
+ url: tip.url,
124
+ scenario: tip.scenario,
125
+ harnessHints: [],
126
+ runCount: 0,
127
+ lastRunAt: Date.now(),
128
+ };
129
+ // MemCollab intersection: discard hints that contradict the proven approach
130
+ // A hint contradicts if it recommends an approach NOT used in the success
131
+ const filteredHints = existing.harnessHints.filter(hint => {
132
+ // Discard hints that recommend approaches explicitly absent from success
133
+ const contradicts = hint.observation.toLowerCase().includes('password instead') &&
134
+ !tip.successApproach.toLowerCase().includes('password');
135
+ return !contradicts;
136
+ });
137
+ // Add recovery tip as top-priority hint
138
+ const recoveryHint = {
139
+ observation: `PROVEN APPROACH: ${tip.successApproach.slice(0, 300)}. Tools that worked: ${tip.toolsUsed.join(', ') || 'none'}.`,
140
+ confidence: 0.97,
141
+ addedAt: tip.ts,
142
+ };
143
+ // Merge: recovery tip first, then filtered existing hints, deduped
144
+ const merged = [recoveryHint, ...filteredHints]
145
+ .filter(h => h.confidence > 0.3)
146
+ .sort((a, b) => b.confidence - a.confidence)
147
+ .slice(0, 5); // Keep only top 5 — K=3-5 is empirically sufficient
148
+ const profile = {
149
+ ...existing,
150
+ harnessHints: merged,
151
+ runCount: existing.runCount + 1,
152
+ lastRunAt: Date.now(),
153
+ };
154
+ writeProfileAtomic(profilePath, profile);
155
+ }
156
+ function extractKeywords(text) {
157
+ // Extract meaningful action words — naive but effective for O(1) comparison
158
+ const stopwords = new Set(['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'with', 'by']);
159
+ return text.toLowerCase()
160
+ .split(/\W+/)
161
+ .filter(w => w.length > 3 && !stopwords.has(w));
162
+ }
163
+ function writeProfileAtomic(profilePath, profile) {
164
+ const tmp = profilePath + '.tmp';
165
+ fs.writeFileSync(tmp, JSON.stringify(profile, null, 2));
166
+ fs.renameSync(tmp, profilePath);
167
+ }
168
+ //# sourceMappingURL=learner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learner.js","sourceRoot":"","sources":["../../src/profiles/learner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ import type { Tool } from 'ai';
3
+ declare const captchaParams: z.ZodObject<{
4
+ pageURL: z.ZodString;
5
+ siteKey: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ type CaptchaParams = z.infer<typeof captchaParams>;
8
+ type CaptchaResult = {
9
+ solved: true;
10
+ token: string;
11
+ type: string;
12
+ } | {
13
+ solved: false;
14
+ error: string;
15
+ hint: string;
16
+ };
17
+ export declare const solveTurnstile: Tool<CaptchaParams, CaptchaResult>;
18
+ export declare function capsolverAvailable(): boolean;
19
+ export {};
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+ const captchaParams = z.object({
3
+ pageURL: z.string().describe('The full URL of the page showing the CAPTCHA'),
4
+ siteKey: z.string().optional().describe('Cloudflare Turnstile site key (found in page source). Leave empty to auto-detect.'),
5
+ });
6
+ export const solveTurnstile = {
7
+ description: `Solve a Cloudflare Turnstile CAPTCHA using the CapSolver API.
8
+ Use this when you see a "Verify you are human" challenge blocking form submission.
9
+ Returns a token you can inject into the page to bypass the challenge.
10
+ Only works if CAPSOLVER_API_KEY is configured — check first before calling.`,
11
+ inputSchema: captchaParams,
12
+ execute: async ({ pageURL, siteKey }) => {
13
+ const apiKey = process.env.CAPSOLVER_API_KEY;
14
+ if (!apiKey) {
15
+ return {
16
+ solved: false,
17
+ error: 'CAPSOLVER_API_KEY not configured',
18
+ hint: 'Add CAPSOLVER_API_KEY to .env, or configure X-Usertester-Session WAF bypass in Cloudflare instead (recommended).',
19
+ };
20
+ }
21
+ try {
22
+ // CapSolver REST API — no SDK needed, direct HTTP is cleaner
23
+ const createTask = await fetch('https://api.capsolver.com/createTask', {
24
+ method: 'POST',
25
+ headers: { 'Content-Type': 'application/json' },
26
+ body: JSON.stringify({
27
+ clientKey: apiKey,
28
+ task: {
29
+ type: 'AntiTurnstileTaskProxyLess',
30
+ websiteURL: pageURL,
31
+ websiteKey: siteKey ?? '0x4AAAAAAADnPIDROrmt1Wwj', // Cloudflare default demo key fallback
32
+ },
33
+ }),
34
+ }).then(r => r.json());
35
+ if (!createTask.taskId) {
36
+ return {
37
+ solved: false,
38
+ error: createTask.errorDescription ?? 'Task creation failed',
39
+ hint: 'Check CAPSOLVER_API_KEY balance and validity.',
40
+ };
41
+ }
42
+ // Poll for result (Turnstile typically solves in 5-15 seconds)
43
+ const deadline = Date.now() + 60_000;
44
+ while (Date.now() < deadline) {
45
+ await new Promise(r => setTimeout(r, 3000));
46
+ const result = await fetch('https://api.capsolver.com/getTaskResult', {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ clientKey: apiKey, taskId: createTask.taskId }),
50
+ }).then(r => r.json());
51
+ if (result.status === 'ready' && result.solution?.token) {
52
+ return {
53
+ solved: true,
54
+ token: result.solution.token,
55
+ type: 'AntiTurnstileTaskProxyLess',
56
+ };
57
+ }
58
+ if (result.status === 'failed' || result.errorCode) {
59
+ return {
60
+ solved: false,
61
+ error: result.errorDescription ?? 'Solve failed',
62
+ hint: 'Turnstile may be in hardened mode. Try configuring X-Usertester-Session WAF bypass instead.',
63
+ };
64
+ }
65
+ }
66
+ return { solved: false, error: 'Timeout waiting for CAPTCHA solve', hint: 'CapSolver took >60s' };
67
+ }
68
+ catch (err) {
69
+ return { solved: false, error: String(err), hint: 'CapSolver API request failed' };
70
+ }
71
+ },
72
+ };
73
+ export function capsolverAvailable() {
74
+ return !!process.env.CAPSOLVER_API_KEY;
75
+ }
76
+ //# sourceMappingURL=captcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captcha.js","sourceRoot":"","sources":["../../src/tools/captcha.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * readInboxEmail tool — lets the browser agent read emails via AgentMail API
3
+ * instead of trying to navigate to a web inbox (which fails DNS).
4
+ *
5
+ * Used by the retry loop when it detects a CAPABILITY_GAP around email reading.
6
+ * Injected into Stagehand via stagehand.agent({ tools: { readInboxEmail } })
7
+ */
8
+ import { z } from 'zod';
9
+ import type { Tool } from 'ai';
10
+ declare const inboxParams: z.ZodObject<{
11
+ inboxId: z.ZodString;
12
+ subjectContains: z.ZodOptional<z.ZodString>;
13
+ waitMinutes: z.ZodOptional<z.ZodNumber>;
14
+ }, z.core.$strip>;
15
+ type InboxParams = z.infer<typeof inboxParams>;
16
+ type InboxResult = {
17
+ found: false;
18
+ error: string;
19
+ } | {
20
+ found: false;
21
+ message: string;
22
+ } | {
23
+ found: true;
24
+ subject?: string;
25
+ snippet?: string;
26
+ verificationCodes: string[];
27
+ primaryCode: string | null;
28
+ };
29
+ export declare const readInboxEmail: Tool<InboxParams, InboxResult>;
30
+ export {};