testronaut 1.0.9 → 1.0.10

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/core/agent.js CHANGED
@@ -6,10 +6,13 @@ export async function runAgent(goals, missionName, maxTurns = 20, ) {
6
6
  const browser = new ChromeBrowser();
7
7
  await browser.start();
8
8
  const startTime = Date.now();
9
+ let result;
9
10
 
10
11
  try {
12
+ const missionResults = [];
11
13
  for (const goal of goals) {
12
14
  // console.log(`\nšŸš€ Running Agent with goal:\n${goal}\n`);
15
+ const steps = [];
13
16
  const messages = [
14
17
  {
15
18
  role: 'system',
@@ -29,31 +32,55 @@ export async function runAgent(goals, missionName, maxTurns = 20, ) {
29
32
  { role: 'user', content: goal.goal },
30
33
  ];
31
34
 
32
- const success = await turnLoop(browser, messages, maxTurns);
33
- if (!success) {
34
- console.log('šŸ›‘ Agent stopped due to failed goal.\n');
35
- const endTime = Date.now();
36
- return {
37
- missionName: missionName,
38
- status: 'failed',
39
- steps: [ /* tool steps or key moments */ ],
40
- startTime: startTime,
41
- endTime: endTime,
35
+ result = await turnLoop(
36
+ browser,
37
+ messages,
38
+ maxTurns,
39
+ 0, /* currentTurn */
40
+ 0, /* retryCount */
41
+ null, /* currentStep */
42
+ {
43
+ steps, // šŸ‘ˆ pass in
44
+ missionName, // šŸ‘ˆ pass in (for tagging)
42
45
  }
43
- ;
46
+ );
47
+
48
+ missionResults.push({
49
+ missionName,
50
+ status: result.success ? 'passed' : 'failed',
51
+ steps: JSON.parse(JSON.stringify(steps)), // šŸ‘ˆ deep-clone to avoid refs
52
+ startTime: Date.now() - 1, // set real values if you have them
53
+ endTime: Date.now(),
54
+ });
55
+
56
+ if (!result.success) {
57
+ console.log('šŸ›‘ Agent stopped due to failed goal.\n');
58
+ return missionResults;
44
59
  }
60
+
61
+ // if (!result.success) {
62
+ // console.log('šŸ›‘ Agent stopped due to failed goal.\n');
63
+ // const endTime = Date.now();
64
+ // return {
65
+ // missionName: missionName,
66
+ // status: 'failed',
67
+ // steps: result.steps,
68
+ // startTime: startTime,
69
+ // endTime: endTime,
70
+ // };
71
+ // }
45
72
  }
46
73
 
47
74
  console.log('āœ… All goals completed successfully.\n');
48
- const endTime = Date.now();
49
- return {
50
- missionName: missionName,
51
- status: 'passed',
52
- steps: [ /* tool steps or key moments */ ],
53
- startTime: startTime,
54
- endTime: endTime,
55
- }
56
- ;
75
+ // const endTime = Date.now();
76
+ return missionResults;
77
+ // return {
78
+ // missionName: missionName,
79
+ // status: 'passed',
80
+ // steps: result.steps,
81
+ // startTime: startTime,
82
+ // endTime: endTime,
83
+ // };
57
84
 
58
85
  } finally {
59
86
  await browser.close();
package/core/turnLoop.js CHANGED
@@ -19,6 +19,7 @@ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
19
19
  let totalTokensUsed = 0;
20
20
  let turnTimestamps = [];
21
21
  let shouldBackoff;
22
+ let steps = [];
22
23
 
23
24
  const pushDOMAssistant = async (browser, messages, agentMemory, { skipIfLastTool } = {}) => {
24
25
  if (skipIfLastTool && skipIfLastTool.includes(messages.at(-1)?.name)) {
@@ -55,7 +56,16 @@ const pushDOMAssistant = async (browser, messages, agentMemory, { skipIfLastTool
55
56
  });
56
57
  }
57
58
 
58
- export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, retryCount = 0) => {
59
+ export const turnLoop = async (
60
+ browser,
61
+ messages,
62
+ maxTurns,
63
+ currentTurn = 0,
64
+ retryCount = 0,
65
+ currentStep = {},
66
+ ctx = {} // šŸ‘ˆ { steps, missionName }
67
+ ) => {
68
+ const { steps = [], missionName } = ctx;
59
69
  let agentMemory = {
60
70
  lastMenuExpanded: false
61
71
  };
@@ -63,6 +73,8 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
63
73
  for (let turn = currentTurn; turn < maxTurns; turn++) {
64
74
  console.log(`\nšŸ”„ Turn ${turn + 1}/${maxTurns}`);
65
75
  let response;
76
+ // const currentStep = { turn, events: [], result: '🟔 In Progress' };
77
+ const currentStep = { turn, events: [], result: '🟔 In Progress', missionName }; // šŸ‘ˆ tag it
66
78
  try {
67
79
 
68
80
  // Recalculate the rolling window before checking cooldown
@@ -70,12 +82,15 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
70
82
 
71
83
  // Cool off check
72
84
  ({ totalTokensUsed, turnTimestamps, shouldBackoff } = await tokenUseCoolOff(totalTokensUsed, turnTimestamps));
73
- if(shouldBackoff) return await turnLoop(browser, messages, maxTurns, turn);
85
+ if(shouldBackoff) return await turnLoop(browser, messages, maxTurns, turn, currentStep);
74
86
 
75
87
  //Tool Validator
76
88
  if (!validateAndInsertMissingToolResponses(messages, { insertPlaceholders: true })) {
77
89
  console.error('āŒ Tool call structure invalid and no placeholders inserted.');
78
- return false;
90
+ currentStep.events.push('āŒ Tool call structure invalid and no placeholders inserted.')
91
+ currentStep.result = 'āŒ Failure';
92
+ steps.push(currentStep);
93
+ return steps;
79
94
  }
80
95
 
81
96
  const unrespondedCalls = messages.filter(
@@ -92,7 +107,10 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
92
107
  if (unrespondedCalls.length) {
93
108
  console.error('šŸ›‘ Detected assistant tool calls without matching tool responses:');
94
109
  console.dir(unrespondedCalls, { depth: 5 });
95
- return false;
110
+ currentStep.events.push('šŸ›‘ Detected assistant tool calls without matching tool responses')
111
+ currentStep.result = 'āŒ Failure';
112
+ steps.push(currentStep);
113
+ return steps;
96
114
  }
97
115
 
98
116
 
@@ -109,15 +127,21 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
109
127
 
110
128
  if (retryCount >= 5) {
111
129
  console.error('āŒ Too many retries. Exiting.');
112
- return false;
130
+ currentStep.events.push('āŒ Too many retries. Exiting.')
131
+ currentStep.result = 'āŒ Failure';
132
+ steps.push(currentStep);
133
+ return steps;
113
134
  }
114
135
 
115
- return await turnLoop(browser, messages, maxTurns, turn, retryCount + 1);
136
+ return await turnLoop(browser, messages, maxTurns, turn, retryCount + 1, currentStep);
116
137
  } else if (err.status === 400) {
117
138
  console.error('āŒ Bad request:', err.message);
118
139
  // console.log("current tools", toolsSchema);
119
140
  // console.log("messages: ", messages)
120
- return false;
141
+ currentStep.events.push(`āŒ Bad request: ${err.message}`)
142
+ currentStep.result = 'āŒ Failure';
143
+ steps.push(currentStep);
144
+ return steps;
121
145
  } else {
122
146
  throw err;
123
147
  }
@@ -127,10 +151,12 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
127
151
  if (usage) {
128
152
  const tokensUsed = usage.total_tokens || 0;
129
153
  console.log(`šŸ“Š Token Usage This Turn → Total: ${tokensUsed}`);
154
+ currentStep.tokensUsed = tokensUsed;
130
155
  recordTokenUsage(turnTimestamps, tokensUsed);
131
156
  ({ turnTimestamps, totalTokensUsed } = pruneOldTokenUsage(turnTimestamps));
132
157
 
133
158
  console.log(`šŸ“ˆ Running Total Tokens Used (Rolling 60s): ${totalTokensUsed}`);
159
+ currentStep.totalTokensUsed = totalTokensUsed;
134
160
  }
135
161
 
136
162
  const msg = response.choices[0].message;
@@ -145,6 +171,7 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
145
171
  lastToolName = fnName;
146
172
  const args = JSON.parse(call.function.arguments || '{}');
147
173
  console.log(`[model] → ${fnName}`, args);
174
+ currentStep.events.push(`[model] → ${fnName} ${args}`);
148
175
  // console.log('Calling tool:', fnName);
149
176
  let result;
150
177
  let errorMessage = null;
@@ -161,9 +188,19 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
161
188
  }
162
189
 
163
190
  console.log(`[tool ] ← ${fnName} result:`, errorMessage ? 'āŒ Failed' : 'āœ… Success');
191
+ currentStep.events.push(`[tool ] ← ${fnName} result: ${errorMessage ? 'āŒ Failed' : 'āœ… Success'}`)
164
192
  const truncated = result.length > 100 ? result.slice(0, 100) + '…' : result;
165
193
  console.log(`[tool ] ← ${truncated}`);
166
194
 
195
+ if (fnName === 'screenshot') {
196
+ const match = result.match(/screenshot.*?saved at: (.+\.png)/i);
197
+ if (match && match[1]) {
198
+ currentStep.screenshotPath = match[1];
199
+ currentStep.events.push(`šŸ–¼ļø Screenshot captured: ${match[1]}`);
200
+ }
201
+ }
202
+
203
+ currentStep.events.push(`[tool ] ← ${truncated}`)
167
204
  toolResponses.push({
168
205
  role: 'tool',
169
206
  tool_call_id: call.id,
@@ -178,31 +215,41 @@ export const turnLoop = async (browser, messages, maxTurns, currentTurn = 0, ret
178
215
 
179
216
  if (fnName === 'click_text' || fnName === 'click' || fnName === 'expand_menu') {
180
217
  console.log(`[auto] → Injecting DOM after menu expand...`);
218
+ currentStep.events.push(`[auto] → Injecting DOM after menu expand...`);
181
219
  const domHtml = await CHROME_TOOL_MAP.get_dom(browser, {
182
220
  limit: 100000,
183
221
  exclude: true,
184
222
  focus: [],
185
223
  }, agentMemory);
186
224
  await tokenEstimate('gpt-4o', domHtml);
187
- // fs.writeFileSync(`missions/mission_reports/debug-expanded-${Date.now()}.html`, domHtml);
188
225
  await pushDOMAssistant(browser, messages, agentMemory, {
189
226
  skipIfLastTool: ['get_dom', 'check_text']
190
227
  });
191
228
  console.log(`[auto] → DOM size after ${fnName}: ${domHtml.length} chars`);
229
+ currentStep.events.push(`[auto] → DOM size after ${fnName}: ${domHtml.length} chars`)
192
230
  }
193
231
 
194
232
  }
195
233
  messages.push(msg, ...toolResponses);
234
+ currentStep.result = 'āœ… Passed';
235
+ steps.push(currentStep);
196
236
  continue;
197
237
  }
198
238
 
199
- const isAgentDone = finalResponseHandler(msg)
200
- if (isAgentDone !== null) return isAgentDone;
239
+ const finalResponse = finalResponseHandler(msg);
240
+ if (finalResponse !== null) {
241
+ currentStep.events.push(finalResponse.finalMessage);
242
+ currentStep.result = finalResponse.success ? 'āœ… Mission Success': 'āŒ Mission Failure';
243
+ steps.push(currentStep);
244
+ return { success: finalResponse.finalMessage, steps };
245
+ }
246
+
201
247
 
202
248
  await pushDOMAssistant(browser, messages, agentMemory,{
203
249
  skipIfLastTool: ['get_dom', 'check_text']
204
250
  });
205
251
 
206
252
  console.log(`[auto] → Injected DOM for next reasoning step`);
253
+ currentStep.events.push(`[auto] → Injected DOM for next reasoning step`)
207
254
  }
208
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testronaut",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "main": "./runner/testronaut.js",
5
5
  "bin": {
6
6
  "testronaut": "./bin/cli.js"
@@ -47,6 +47,15 @@ export async function runMissions({ preMission, mission, postMission }, missionN
47
47
  console.log(`āŒ Aborting after failed goal.`);
48
48
  return;
49
49
  }
50
+ // console.log("success[0]: ", success[0]);
51
+ // console.log("success[0].steps.length: ", success[0].steps.length);
52
+ // console.log("success[0].steps[success[0].steps.length - 1].result: ", success[0].steps[success[0].steps.length - 1].result)
53
+ const missionStatus = success[0].steps[success[0].steps.length - 1].result;
54
+ // console.log("missionStatus: ", missionStatus)
55
+ if (missionStatus.toLowerCase().includes('failure')) {
56
+ // console.log("we detected the failure!")
57
+ success[0].status = 'failed';
58
+ }
50
59
  console.log('\nāœ… Mission flow complete.');
51
60
  return success;
52
61
  }
@@ -90,9 +90,7 @@ export class ChromeBrowser {
90
90
  if (innerText.includes(text)) {
91
91
  await el.scrollIntoViewIfNeeded().catch(() => {});
92
92
  await el.click();
93
- await this.page.screenshot({ path: `missions/mission_reports/screenshot_after_click_${Date.now()}.png` });
94
93
  await this.page.waitForTimeout(5000);
95
- await this.page.screenshot({ path: `screenshot_after_waiting_click_${Date.now()}.png` });
96
94
  return `clicked text "${text}"`;
97
95
 
98
96
  }
@@ -119,7 +117,6 @@ export class ChromeBrowser {
119
117
  console.log(`[click_text] → Clicking visible element with bounding box`, box);
120
118
  await el.click();
121
119
  await this.page.waitForTimeout(delayMs);
122
- await this.page.screenshot({ path: `missions/mission_reports/screenshot_after_click_${Date.now()}.png` });
123
120
  return `clicked visible text "${text}"`;
124
121
  }
125
122
  }
@@ -147,11 +144,18 @@ export class ChromeBrowser {
147
144
  await this.page.waitForSelector(selector, { timeout: 30_000 });
148
145
  await this.page.click(selector);
149
146
  await this.page.waitForTimeout(delayMs);
150
- await this.page.screenshot({ path: `missions/mission_reports/screenshot_expand_menu_${Date.now()}.png` });
151
-
152
147
  return `expanded menu using ${selector}`;
153
148
  }
154
149
 
150
+ async screenshot({ name = 'screenshot' }) {
151
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
152
+ const screenshotPath = `missions/mission_reports/screenshots/${name}_${timestamp}.png`;
153
+
154
+ await this.page.screenshot({ path: screenshotPath });
155
+
156
+ // āœ… Return relative path for HTML report
157
+ return `Screenshot saved at: ./screenshots/${name}_${timestamp}.png`;
158
+ }
155
159
 
156
160
 
157
161
  async close() {
@@ -173,7 +177,6 @@ export const CHROME_TOOL_MAP = {
173
177
  return await browser.click_text(args);
174
178
  }
175
179
  },
176
-
177
180
  expand_menu: (b, args) => b.expand_menu(args),
178
- // click_text: (b, args) => b.click_text(args),
181
+ screenshot: (b, args) => b.screenshot(args),
179
182
  };
@@ -1,52 +1,122 @@
1
1
  import fs from 'fs';
2
+ import path from 'path';
2
3
 
3
4
  export function generateHtmlReport(report, outputPath) {
4
- const { runId, startTime, endTime, summary, missions } = report;
5
+ const { runId, startTime, endTime, missions = [], summary = {} } = report;
6
+ const durationSec = ((new Date(endTime) - new Date(startTime)) / 1000).toFixed(2);
7
+
8
+ // tiny helpers
9
+ const esc = (s) => String(s ?? '')
10
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;')
11
+ .replace(/>/g, '&gt;');
12
+ const badge = (status) =>
13
+ status === 'passed' ? 'āœ… Passed' :
14
+ status === 'failed' ? 'āŒ Failed' : (status || '—');
15
+
16
+ const missionBlock = (m, idx) => {
17
+ const mDurationSec = m.endTime && m.startTime
18
+ ? ((m.endTime - m.startTime) / 1000).toFixed(2)
19
+ : '—';
5
20
 
6
- const missionHtml = missions.map(m => {
7
- const status = m.status?.toUpperCase?.() || 'UNKNOWN';
8
- const missionName = m.missionName || 'Unnamed';
9
- const file = m.file || 'N/A';
10
21
  const steps = Array.isArray(m.steps) ? m.steps : [];
22
+ const stepItems = steps.map(step => {
23
+ const events = Array.isArray(step.events) ? step.events : [];
24
+ const imgTag = step.screenshotPath
25
+ ? `<img src="${esc(step.screenshotPath)}" alt="screenshot turn ${esc(step.turn)}">`
26
+ : '';
27
+ return `
28
+ <details class="step" ${/Mission Success|Passed/.test(step.result||'') ? '' : 'open'}>
29
+ <summary>
30
+ <span class="turn">Turn ${esc((step.turn ?? 0) + 1)}</span>
31
+ <span class="step-result ${/āœ…/.test(step.result||'') ? 'ok' : 'bad'}">${esc(step.result || '')}</span>
32
+ <span class="tokens">tokens: ${esc(step.tokensUsed ?? '—')} / total: ${esc(step.totalTokensUsed ?? '—')}</span>
33
+ </summary>
34
+ <pre class="events">${esc(events.join('\n')) || '(no events)'}</pre>
35
+ ${imgTag}
36
+ </details>
37
+ `;
38
+ }).join('');
11
39
 
12
40
  return `
13
- <div class="mission ${status.toLowerCase()}">
14
- <h3>${file} - ${missionName} - <span>${status}</span></h3>
15
- <p><em>${m.startTime || 'N/A'} → ${m.endTime || 'N/A'}</em></p>
16
- <ul>
17
- ${steps.map(step => `
18
- <li>
19
- <strong>${step.tool}</strong> → ${step.output}
20
- </li>
21
- `).join('')}
22
- </ul>
23
- </div>
41
+ <details class="mission">
42
+ <summary>
43
+ <span class="name">${esc(m.missionName || path.basename(m.file || `mission_${idx+1}`))}</span>
44
+ <span class="status ${m.status === 'passed' ? 'ok' : (m.status === 'failed' ? 'bad' : '')}">${badge(m.status)}</span>
45
+ <span class="meta">steps: ${steps.length} • duration: ${mDurationSec}s • file: ${esc(m.file || '')}</span>
46
+ </summary>
47
+ <div class="steps">
48
+ ${stepItems || '<div class="empty">No steps recorded.</div>'}
49
+ </div>
50
+ </details>
24
51
  `;
25
- }).join('');
52
+ };
26
53
 
27
54
  const html = `
28
- <html>
29
- <head>
30
- <style>
31
- body { font-family: sans-serif; padding: 20px; }
32
- .passed { background-color: #e6ffed; }
33
- .failed { background-color: #ffe6e6; }
34
- .unknown { background-color: #f9f9f9; }
35
- h3 span { font-weight: bold; }
36
- ul { margin-top: 0; }
37
- </style>
38
- </head>
39
- <body>
40
- <h1>šŸ§‘ā€šŸš€ Testronaut Report</h1>
41
- <p><strong>Run ID:</strong> ${runId}</p>
42
- <p><strong>Start:</strong> ${startTime}</p>
43
- <p><strong>End:</strong> ${endTime}</p>
44
- <p><strong>Summary:</strong> ${summary.passed}/${summary.totalMissions} passed</p>
45
- ${missionHtml}
46
- </body>
47
- </html>
55
+ <!DOCTYPE html>
56
+ <html>
57
+ <head>
58
+ <meta charset="UTF-8" />
59
+ <title>Testronaut Report – ${esc(runId)}</title>
60
+ <style>
61
+ :root {
62
+ --ok: #16a34a;
63
+ --bad: #dc2626;
64
+ --muted: #6b7280;
65
+ --bg: #f8fafc;
66
+ --card: #ffffff;
67
+ --border: #e5e7eb;
68
+ --mono: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
69
+ }
70
+ body { margin:0; padding:24px; font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu; background: var(--bg); color:#111827; }
71
+ h1 { margin: 0 0 6px 0; font-size: 20px; }
72
+ .run-meta { color: var(--muted); margin-bottom: 16px; }
73
+ .summary { display:flex; gap:12px; margin: 12px 0 20px; }
74
+ .pill { border:1px solid var(--border); background: var(--card); padding:6px 10px; border-radius:999px; font-size:12px; }
75
+ .pill.ok { border-color: #bbf7d0; background:#ecfdf5; color: var(--ok); }
76
+ .pill.bad { border-color: #fecaca; background:#fef2f2; color: var(--bad); }
77
+
78
+ details { background: var(--card); border:1px solid var(--border); border-radius:10px; margin:10px 0; }
79
+ summary { cursor:pointer; padding:12px 14px; display:flex; align-items:center; gap:12px; }
80
+ .mission > summary { font-weight:600; }
81
+ .name { flex:1; }
82
+ .status.ok { color: var(--ok); }
83
+ .status.bad { color: var(--bad); }
84
+ .meta { color: var(--muted); font-size:12px; }
85
+
86
+ .steps { padding: 0 14px 12px; }
87
+ .step summary { background: #fafafa; border-bottom:1px solid var(--border); border-radius: 10px 10px 0 0; }
88
+ .turn { font-weight:600; }
89
+ .step-result.ok { color: var(--ok); }
90
+ .step-result.bad { color: var(--bad); }
91
+ .tokens { color: var(--muted); font-size:12px; margin-left:auto; }
92
+ .events { background:#0b1020; color:#e5e7eb; padding:12px; border-radius:8px; margin:12px 0 0; font: 12px/1.4 var(--mono); max-height: 280px; overflow:auto; }
93
+ img { display:block; max-width:100%; border:1px solid var(--border); border-radius:8px; margin-top:10px; }
94
+ .empty { color: var(--muted); padding: 12px; }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <h1>šŸ§‘ā€šŸš€ Testronaut Report</h1>
99
+ <div class="run-meta">
100
+ <div><strong>Run ID:</strong> ${esc(runId)}</div>
101
+ <div><strong>Start:</strong> ${esc(startTime)}</div>
102
+ <div><strong>End:</strong> ${esc(endTime)} • <strong>Duration:</strong> ${durationSec}s</div>
103
+ </div>
104
+
105
+ <div class="summary">
106
+ <div class="pill">Missions: ${esc(summary.totalMissions ?? missions.length)}</div>
107
+ <div class="pill ok">Passed: ${esc(summary.passed ?? missions.filter(m=>m.status==='passed').length)}</div>
108
+ <div class="pill bad">Failed: ${esc(summary.failed ?? missions.filter(m=>m.status==='failed').length)}</div>
109
+ </div>
110
+
111
+ ${missions.map(missionBlock).join('')}
112
+
113
+ </body>
114
+ </html>
48
115
  `;
49
116
 
50
- fs.writeFileSync(outputPath, html);
51
- console.log(`šŸ“ Report generated at: ${outputPath}`);
117
+ const out = outputPath ?? path.resolve('missions/mission_reports', `${runId}.html`);
118
+ fs.mkdirSync(path.dirname(out), { recursive: true });
119
+ fs.writeFileSync(out, html);
120
+ console.log(`šŸ“ Report generated at: ${out}`);
121
+ return out;
52
122
  }
@@ -155,7 +155,25 @@ const toolsSchema = [
155
155
  required: ['chunkIndex', 'totalChunks']
156
156
  }
157
157
  }
158
+ },
159
+ {
160
+ type: 'function',
161
+ function: {
162
+ name: 'screenshot',
163
+ description: 'Takes a screenshot of the current page',
164
+ parameters: {
165
+ type: 'object',
166
+ properties: {
167
+ label: {
168
+ type: 'string',
169
+ description: 'Optional label for the screenshot filename',
170
+ },
171
+ },
172
+ required: [],
173
+ },
174
+ },
158
175
  }
176
+
159
177
 
160
178
  ];
161
179
 
@@ -4,13 +4,13 @@ export const finalResponseHandler = (msg) => {
4
4
  console.log('\nā”ā” FINAL AGENT RESPONSE ━━━━━━━━━━━━━━━━━━━');
5
5
  console.log(msg.content);
6
6
  console.log('┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
7
- return true;
7
+ return { finalMessage: msg.content, success: true};
8
8
  }
9
9
  if (final?.startsWith('failure')) {
10
10
  console.log('\nā”ā” FINAL AGENT RESPONSE ━━━━━━━━━━━━━━━━━━━');
11
11
  console.log(msg.content);
12
12
  console.log('┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
13
- return false;
13
+ return { finalMessage: msg.content, success: false};
14
14
  }
15
15
  return null;
16
16
  }