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,168 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import readline from 'node:readline';
4
+ function prompt(rl, question) {
5
+ return new Promise(resolve => rl.question(question, resolve));
6
+ }
7
+ async function validateOpenRouterKey(apiKey) {
8
+ try {
9
+ const { createOpenRouter } = await import('@openrouter/ai-sdk-provider');
10
+ const { generateText } = await import('ai');
11
+ const or = createOpenRouter({ apiKey });
12
+ await generateText({
13
+ model: or('anthropic/claude-haiku-4-5'),
14
+ messages: [{ role: 'user', content: 'hi' }],
15
+ maxOutputTokens: 5,
16
+ });
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ async function validateAnthropicKey(apiKey) {
24
+ try {
25
+ const { createAnthropic } = await import('@ai-sdk/anthropic');
26
+ const { generateText } = await import('ai');
27
+ const provider = createAnthropic({ apiKey });
28
+ await generateText({
29
+ model: provider('claude-haiku-4-5-20251001'),
30
+ messages: [{ role: 'user', content: 'hi' }],
31
+ maxOutputTokens: 5,
32
+ });
33
+ return true;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ async function validateAgentMailKey(apiKey) {
40
+ try {
41
+ const { AgentMailClient } = await import('agentmail');
42
+ const client = new AgentMailClient({ apiKey });
43
+ await client.inboxes.list();
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ export function registerSetup(program) {
51
+ program
52
+ .command('setup')
53
+ .description('Interactive first-run setup — configure API keys and validate')
54
+ .option('--force', 'Overwrite existing .env', false)
55
+ .action(async (opts) => {
56
+ const envPath = path.join(process.cwd(), '.env');
57
+ if (fs.existsSync(envPath) && !opts.force) {
58
+ console.log(`.env already exists at ${envPath}`);
59
+ console.log('Run with --force to overwrite, or edit it directly.');
60
+ process.exit(0);
61
+ }
62
+ console.log('usertester setup\n');
63
+ const rl = readline.createInterface({
64
+ input: process.stdin,
65
+ output: process.stdout,
66
+ });
67
+ const envLines = [];
68
+ // --- LLM provider choice ---
69
+ console.log('Choose your LLM provider:');
70
+ console.log(' OpenRouter gives you one API key that covers Anthropic + OpenAI models.');
71
+ console.log(' Alternatively you can configure Anthropic and/or OpenAI directly.\n');
72
+ const useOpenRouter = (await prompt(rl, 'Use OpenRouter? (single API key, covers Anthropic + OpenAI) [Y/n]: ')).trim();
73
+ const preferOpenRouter = !useOpenRouter || useOpenRouter.toLowerCase() !== 'n';
74
+ if (preferOpenRouter) {
75
+ console.log('\nGet your key at: https://openrouter.ai/keys\n');
76
+ let openrouterKey = '';
77
+ while (true) {
78
+ openrouterKey = (await prompt(rl, 'OpenRouter API key (sk-or-...): ')).trim();
79
+ if (!openrouterKey) {
80
+ console.log('Key cannot be empty.');
81
+ continue;
82
+ }
83
+ process.stdout.write(' Validating... ');
84
+ const valid = await validateOpenRouterKey(openrouterKey);
85
+ if (valid) {
86
+ console.log('ok');
87
+ break;
88
+ }
89
+ else {
90
+ console.log('Invalid — check the key and try again.');
91
+ }
92
+ }
93
+ envLines.push(`OPENROUTER_API_KEY=${openrouterKey}`);
94
+ }
95
+ else {
96
+ // Direct Anthropic
97
+ console.log('\nGet your Anthropic key at: https://console.anthropic.com/settings/keys\n');
98
+ let anthropicKey = '';
99
+ while (true) {
100
+ anthropicKey = (await prompt(rl, 'Anthropic API key (sk-ant-...): ')).trim();
101
+ if (!anthropicKey) {
102
+ console.log('Key cannot be empty.');
103
+ continue;
104
+ }
105
+ process.stdout.write(' Validating... ');
106
+ const valid = await validateAnthropicKey(anthropicKey);
107
+ if (valid) {
108
+ console.log('ok');
109
+ break;
110
+ }
111
+ else {
112
+ console.log('Invalid — check the key and try again.');
113
+ }
114
+ }
115
+ envLines.push(`ANTHROPIC_API_KEY=${anthropicKey}`);
116
+ // Optional OpenAI
117
+ const addOpenAI = (await prompt(rl, 'Also configure OpenAI API key? [y/N]: ')).trim();
118
+ if (addOpenAI.toLowerCase() === 'y') {
119
+ console.log('\nGet your key at: https://platform.openai.com/api-keys\n');
120
+ let openaiKey = '';
121
+ while (true) {
122
+ openaiKey = (await prompt(rl, 'OpenAI API key (sk-...): ')).trim();
123
+ if (!openaiKey) {
124
+ console.log('Key cannot be empty.');
125
+ continue;
126
+ }
127
+ process.stdout.write(' Validating... ');
128
+ // Basic format check (full validation requires a call)
129
+ if (openaiKey.startsWith('sk-')) {
130
+ console.log('ok (format check)');
131
+ break;
132
+ }
133
+ else {
134
+ console.log('Key should start with sk-');
135
+ }
136
+ }
137
+ envLines.push(`OPENAI_API_KEY=${openaiKey}`);
138
+ }
139
+ }
140
+ // --- AgentMail ---
141
+ console.log('\nGet your AgentMail key at: https://agentmail.to/dashboard\n');
142
+ let agentmailKey = '';
143
+ while (true) {
144
+ agentmailKey = (await prompt(rl, 'AgentMail API key: ')).trim();
145
+ if (!agentmailKey) {
146
+ console.log('Key cannot be empty.');
147
+ continue;
148
+ }
149
+ process.stdout.write(' Validating... ');
150
+ const valid = await validateAgentMailKey(agentmailKey);
151
+ if (valid) {
152
+ console.log('ok');
153
+ break;
154
+ }
155
+ else {
156
+ console.log('Invalid — check the key and try again.');
157
+ }
158
+ }
159
+ envLines.push(`AGENTMAIL_API_KEY=${agentmailKey}`);
160
+ rl.close();
161
+ const envContent = envLines.join('\n') + '\n';
162
+ fs.writeFileSync(envPath, envContent);
163
+ console.log(`\nWritten to ${envPath}`);
164
+ console.log('\nYou\'re ready. Try:');
165
+ console.log(' usertester spawn --url https://yourapp.com --n 1 --message "Sign up as a new user"');
166
+ });
167
+ }
168
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * usertester spawn — launch N agents against a URL
3
+ */
4
+ import type { Command } from 'commander';
5
+ export declare function registerSpawn(program: Command): void;
@@ -0,0 +1,52 @@
1
+ import fs from 'node:fs';
2
+ import { DEFAULT_CONFIG } from '../types.js';
3
+ import { orchestrate } from '../orchestrator/index.js';
4
+ export function registerSpawn(program) {
5
+ program
6
+ .command('spawn')
7
+ .description('Spawn N AI agents as simulated users against a URL')
8
+ .requiredOption('--url <url>', 'Target URL')
9
+ .option('-n, --n <number>', 'Number of agents', '1')
10
+ .option('--message <message>', 'Task message for all agents')
11
+ .option('--messages-file <file>', 'JSON file with per-agent messages')
12
+ .option('--session <id>', 'Resume an existing session ID')
13
+ .action(async (opts) => {
14
+ const config = { ...DEFAULT_CONFIG };
15
+ // Resolve messages
16
+ let messages = [];
17
+ if (opts.messagesFile) {
18
+ try {
19
+ const raw = JSON.parse(fs.readFileSync(opts.messagesFile, 'utf-8'));
20
+ messages = raw.map(r => r.message);
21
+ }
22
+ catch (err) {
23
+ console.error(`Error reading messages file: ${err}`);
24
+ process.exit(1);
25
+ }
26
+ }
27
+ else if (opts.message) {
28
+ messages = [opts.message];
29
+ }
30
+ else {
31
+ console.error('Error: --message or --messages-file is required');
32
+ process.exit(1);
33
+ }
34
+ const n = parseInt(opts.n, 10);
35
+ if (isNaN(n) || n < 1) {
36
+ console.error('Error: --n must be a positive integer');
37
+ process.exit(1);
38
+ }
39
+ if (!config.agentmail_api_key) {
40
+ console.error('Error: AGENTMAIL_API_KEY is not set');
41
+ process.exit(1);
42
+ }
43
+ if (!config.anthropic_api_key) {
44
+ console.error('Error: ANTHROPIC_API_KEY is not set');
45
+ process.exit(1);
46
+ }
47
+ // Ensure results dir exists
48
+ fs.mkdirSync(config.results_dir, { recursive: true });
49
+ await orchestrate({ url: opts.url, messages, n, config });
50
+ });
51
+ }
52
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/cli/spawn.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAEtD,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,oDAAoD,CAAC;SACjE,cAAc,CAAC,aAAa,EAAE,YAAY,CAAC;SAC3C,MAAM,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,GAAG,CAAC;SACnD,MAAM,CAAC,qBAAqB,EAAE,6BAA6B,CAAC;SAC5D,MAAM,CAAC,wBAAwB,EAAE,mCAAmC,CAAC;SACrE,MAAM,CAAC,gBAAgB,EAAE,+BAA+B,CAAC;SACzD,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,CAAA;QAEpC,mBAAmB;QACnB,IAAI,QAAQ,GAAa,EAAE,CAAA;QAC3B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAA+B,CAAA;gBACjG,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;YACpC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAA;gBACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxB,QAAQ,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC3B,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAA;YAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QAC9B,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAA;YACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAA;YACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAA;YACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,4BAA4B;QAC5B,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAErD,MAAM,WAAW,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;AACN,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * usertester status — print current session agent states
3
+ */
4
+ import type { Command } from 'commander';
5
+ export declare function registerStatus(program: Command): void;
@@ -0,0 +1,85 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { DEFAULT_CONFIG } from '../types.js';
4
+ import { loadSession } from '../orchestrator/session.js';
5
+ const STATUS_COLORS = {
6
+ QUEUED: '\x1b[90m', // gray
7
+ SPAWNING: '\x1b[33m', // yellow
8
+ INBOX_READY: '\x1b[33m', // yellow
9
+ SIGNING_UP: '\x1b[36m', // cyan
10
+ RUNNING: '\x1b[36m', // cyan
11
+ WAITING: '\x1b[32m', // green
12
+ DONE: '\x1b[90m', // gray
13
+ FAILED: '\x1b[31m', // red
14
+ CANCELLED: '\x1b[90m', // gray
15
+ };
16
+ const RESET = '\x1b[0m';
17
+ function elapsedStr(ms) {
18
+ const s = Math.floor(ms / 1000);
19
+ if (s < 60)
20
+ return `${s}s`;
21
+ const m = Math.floor(s / 60);
22
+ return `${m}m ${s % 60}s`;
23
+ }
24
+ function colorStatus(status) {
25
+ const color = STATUS_COLORS[status] ?? '';
26
+ return `${color}${status}${RESET}`;
27
+ }
28
+ export function registerStatus(program) {
29
+ program
30
+ .command('status')
31
+ .description('Show status of all agents in the current session')
32
+ .option('--session <id>', 'Session ID (defaults to current)')
33
+ .option('--json', 'Output raw JSON')
34
+ .action((opts) => {
35
+ const config = { ...DEFAULT_CONFIG };
36
+ let sessionId = opts.session;
37
+ if (!sessionId) {
38
+ const currentLink = path.join(config.results_dir, 'current');
39
+ try {
40
+ const target = fs.readlinkSync(currentLink);
41
+ sessionId = path.basename(target);
42
+ }
43
+ catch {
44
+ console.error('No active session found. Run `usertester spawn` first.');
45
+ process.exit(1);
46
+ }
47
+ }
48
+ const state = loadSession(config.results_dir, sessionId);
49
+ if (!state) {
50
+ console.error(`Session ${sessionId} not found`);
51
+ process.exit(1);
52
+ }
53
+ if (opts.json) {
54
+ console.log(JSON.stringify(state, null, 2));
55
+ return;
56
+ }
57
+ const now = Date.now();
58
+ console.log(`Session: ${state.sessionId}`);
59
+ console.log(`URL: ${state.url}`);
60
+ console.log(`Started: ${new Date(state.startedAt).toLocaleTimeString()}`);
61
+ console.log();
62
+ console.log('AGENT STATUS ELAPSED INBOX');
63
+ console.log('─'.repeat(70));
64
+ for (const agent of state.agents) {
65
+ const elapsed = agent.startedAt ? elapsedStr(now - agent.startedAt) : '-';
66
+ const inbox = agent.inboxId ? agent.inboxId.split('@')[0] + '@...' : '-';
67
+ const status = colorStatus(agent.status);
68
+ const msg = agent.currentMessage ? ` "${agent.currentMessage.slice(0, 30)}"` : '';
69
+ console.log(`${agent.id.padEnd(14)}${status.padEnd(22)}${elapsed.padEnd(10)}${inbox}${msg}`);
70
+ if (agent.error) {
71
+ console.log(` Error: ${agent.error}`);
72
+ }
73
+ }
74
+ const summary = {
75
+ total: state.agents.length,
76
+ running: state.agents.filter(a => a.status === 'RUNNING').length,
77
+ waiting: state.agents.filter(a => a.status === 'WAITING').length,
78
+ failed: state.agents.filter(a => a.status === 'FAILED').length,
79
+ done: state.agents.filter(a => ['DONE', 'CANCELLED'].includes(a.status)).length,
80
+ };
81
+ console.log();
82
+ console.log(`${summary.running} running, ${summary.waiting} waiting, ${summary.failed} failed, ${summary.done} done`);
83
+ });
84
+ }
85
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/cli/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { CodePatch } from './proposer.js';
2
+ export interface PatchRecord {
3
+ patchId: string;
4
+ appliedAt: string;
5
+ triggerSessionId: string;
6
+ patternType: string;
7
+ file: string;
8
+ before: string;
9
+ after: string;
10
+ description: string;
11
+ }
12
+ export declare function applyPatch(patch: CodePatch, triggerSessionId: string, harnessDir: string, projectRoot: string): Promise<{
13
+ applied: boolean;
14
+ patchId?: string;
15
+ error?: string;
16
+ }>;
17
+ /**
18
+ * Read a patch record by ID.
19
+ */
20
+ export declare function readPatchRecord(harnessDir: string, patchId: string): PatchRecord | null;
21
+ /**
22
+ * List all patch records sorted by patchId ascending.
23
+ */
24
+ export declare function listPatchRecords(harnessDir: string): PatchRecord[];
25
+ /**
26
+ * Rollback a patch by writing the `before` contents back to the source file.
27
+ */
28
+ export declare function rollbackPatch(patchId: string, harnessDir: string, projectRoot: string): Promise<{
29
+ rolledBack: boolean;
30
+ error?: string;
31
+ }>;
32
+ /**
33
+ * Write a rollback using the temp file approach for atomicity.
34
+ */
35
+ export declare function rollbackPatchAtomic(patchId: string, harnessDir: string, projectRoot: string): Promise<{
36
+ rolledBack: boolean;
37
+ error?: string;
38
+ }>;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Atomically applies a validated code patch and records a PatchRecord.
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ export async function applyPatch(patch, triggerSessionId, harnessDir, projectRoot) {
7
+ // Step 1: Determine next patchId
8
+ const patchesDir = path.join(harnessDir, 'patches');
9
+ fs.mkdirSync(patchesDir, { recursive: true });
10
+ let nextN = 1;
11
+ try {
12
+ const existing = fs.readdirSync(patchesDir)
13
+ .filter(f => f.endsWith('.json'))
14
+ .map(f => {
15
+ const match = f.match(/patch_(\d+)\.json/);
16
+ return match ? parseInt(match[1], 10) : 0;
17
+ });
18
+ if (existing.length > 0) {
19
+ nextN = Math.max(...existing) + 1;
20
+ }
21
+ }
22
+ catch { }
23
+ const patchId = `patch_${String(nextN).padStart(3, '0')}`;
24
+ // Step 2: Read source file
25
+ const absoluteFilePath = path.join(projectRoot, patch.file);
26
+ let before;
27
+ try {
28
+ before = fs.readFileSync(absoluteFilePath, 'utf-8');
29
+ }
30
+ catch (err) {
31
+ return { applied: false, error: `Cannot read ${patch.file}: ${err}` };
32
+ }
33
+ // Step 3: Verify oldCode appears exactly once (double-check)
34
+ const occurrences = before.split(patch.oldCode).length - 1;
35
+ if (occurrences !== 1) {
36
+ return {
37
+ applied: false,
38
+ error: `oldCode appears ${occurrences} times in ${patch.file} (expected exactly 1)`,
39
+ };
40
+ }
41
+ // Step 4: Build new contents
42
+ const after = before.replace(patch.oldCode, patch.newCode);
43
+ // Step 5: Write PatchRecord FIRST (atomic)
44
+ const record = {
45
+ patchId,
46
+ appliedAt: new Date().toISOString(),
47
+ triggerSessionId,
48
+ patternType: patch.patternType,
49
+ file: patch.file,
50
+ before,
51
+ after,
52
+ description: patch.description,
53
+ };
54
+ const recordPath = path.join(patchesDir, `${patchId}.json`);
55
+ try {
56
+ fs.writeFileSync(recordPath, JSON.stringify(record, null, 2), 'utf-8');
57
+ }
58
+ catch (err) {
59
+ return { applied: false, error: `Cannot write patch record: ${err}` };
60
+ }
61
+ // Step 6: Write new source file atomically (.tmp then rename)
62
+ const tmpPath = absoluteFilePath + `.${patchId}.tmp`;
63
+ try {
64
+ fs.writeFileSync(tmpPath, after, 'utf-8');
65
+ fs.renameSync(tmpPath, absoluteFilePath);
66
+ }
67
+ catch (err) {
68
+ // Clean up tmp if rename failed
69
+ try {
70
+ fs.unlinkSync(tmpPath);
71
+ }
72
+ catch { }
73
+ return { applied: false, error: `Cannot write patched file: ${err}` };
74
+ }
75
+ // Step 7: Append to applier.log
76
+ const logLine = `[${record.appliedAt}] ${patchId} applied to ${patch.file}: ${patch.description} (session: ${triggerSessionId})\n`;
77
+ try {
78
+ fs.appendFileSync(path.join(harnessDir, 'applier.log'), logLine);
79
+ }
80
+ catch { }
81
+ return { applied: true, patchId };
82
+ }
83
+ /**
84
+ * Read a patch record by ID.
85
+ */
86
+ export function readPatchRecord(harnessDir, patchId) {
87
+ const recordPath = path.join(harnessDir, 'patches', `${patchId}.json`);
88
+ try {
89
+ const content = fs.readFileSync(recordPath, 'utf-8');
90
+ return JSON.parse(content);
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ /**
97
+ * List all patch records sorted by patchId ascending.
98
+ */
99
+ export function listPatchRecords(harnessDir) {
100
+ const patchesDir = path.join(harnessDir, 'patches');
101
+ try {
102
+ const files = fs.readdirSync(patchesDir)
103
+ .filter(f => f.endsWith('.json'))
104
+ .sort();
105
+ return files.map(f => {
106
+ try {
107
+ return JSON.parse(fs.readFileSync(path.join(patchesDir, f), 'utf-8'));
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }).filter((r) => r !== null);
113
+ }
114
+ catch {
115
+ return [];
116
+ }
117
+ }
118
+ /**
119
+ * Rollback a patch by writing the `before` contents back to the source file.
120
+ */
121
+ export async function rollbackPatch(patchId, harnessDir, projectRoot) {
122
+ const record = readPatchRecord(harnessDir, patchId);
123
+ if (!record) {
124
+ return { rolledBack: false, error: `Patch record not found: ${patchId}` };
125
+ }
126
+ const absoluteFilePath = path.join(projectRoot, record.file);
127
+ const tmpPath = absoluteFilePath + `.rollback-${patchId}.tmp`;
128
+ try {
129
+ fs.writeFileSync(tmpPath, record.before, 'utf-8');
130
+ fs.renameSync(tmpPath, absoluteFilePath);
131
+ }
132
+ catch (err) {
133
+ try {
134
+ fs.unlinkSync(tmpPath);
135
+ }
136
+ catch { }
137
+ return { rolledBack: false, error: `Cannot write rollback: ${err}` };
138
+ }
139
+ const logLine = `[${new Date().toISOString()}] ROLLBACK ${patchId} (file: ${record.file})\n`;
140
+ try {
141
+ fs.appendFileSync(path.join(harnessDir, 'applier.log'), logLine);
142
+ }
143
+ catch { }
144
+ return { rolledBack: true };
145
+ }
146
+ /**
147
+ * Write a rollback using the temp file approach for atomicity.
148
+ */
149
+ export async function rollbackPatchAtomic(patchId, harnessDir, projectRoot) {
150
+ return rollbackPatch(patchId, harnessDir, projectRoot);
151
+ }
152
+ //# sourceMappingURL=applier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applier.js","sourceRoot":"","sources":["../../src/harness/applier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { UsertesterConfig } from '../types.js';
2
+ import type { RetryAttempt } from '../orchestrator/retry.js';
3
+ export declare function runHarnessLoop(opts: {
4
+ sessionId: string;
5
+ agentRetryHistories: RetryAttempt[][];
6
+ agentToolsUsed: string[][];
7
+ agentProfileHits: boolean[];
8
+ agentSucceeded: boolean[];
9
+ url: string;
10
+ nAgents: number;
11
+ config: Partial<UsertesterConfig>;
12
+ harnessDir: string;
13
+ projectRoot: string;
14
+ }): Promise<void>;