free-computer-use 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.
- package/.env.example +31 -0
- package/CHANGELOG.md +19 -0
- package/CONTRIBUTING.md +66 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/SECURITY.md +133 -0
- package/artifacts/benchmark-public.json +797 -0
- package/assets/readme/hero.svg +53 -0
- package/assets/readme/incident-demo.gif +0 -0
- package/assets/readme/incident-evidence.json +52 -0
- package/dist/actions/compiler.d.ts +5 -0
- package/dist/actions/compiler.js +16 -0
- package/dist/actions/compiler.js.map +1 -0
- package/dist/actions/executor.d.ts +38 -0
- package/dist/actions/executor.js +305 -0
- package/dist/actions/executor.js.map +1 -0
- package/dist/actions/policy.d.ts +4 -0
- package/dist/actions/policy.js +18 -0
- package/dist/actions/policy.js.map +1 -0
- package/dist/actions/schema.d.ts +4149 -0
- package/dist/actions/schema.js +59 -0
- package/dist/actions/schema.js.map +1 -0
- package/dist/adapters/generic.d.ts +14 -0
- package/dist/adapters/generic.js +33 -0
- package/dist/adapters/generic.js.map +1 -0
- package/dist/agent/Agent.d.ts +69 -0
- package/dist/agent/Agent.js +375 -0
- package/dist/agent/Agent.js.map +1 -0
- package/dist/agent/Control.d.ts +23 -0
- package/dist/agent/Control.js +51 -0
- package/dist/agent/Control.js.map +1 -0
- package/dist/agent/TokenBudget.d.ts +52 -0
- package/dist/agent/TokenBudget.js +47 -0
- package/dist/agent/TokenBudget.js.map +1 -0
- package/dist/agent/goalCriteria.d.ts +2 -0
- package/dist/agent/goalCriteria.js +13 -0
- package/dist/agent/goalCriteria.js.map +1 -0
- package/dist/browser/Browser.d.ts +72 -0
- package/dist/browser/Browser.js +378 -0
- package/dist/browser/Browser.js.map +1 -0
- package/dist/browser/DomExtractor.d.ts +6 -0
- package/dist/browser/DomExtractor.js +134 -0
- package/dist/browser/DomExtractor.js.map +1 -0
- package/dist/browser/Interaction.d.ts +43 -0
- package/dist/browser/Interaction.js +225 -0
- package/dist/browser/Interaction.js.map +1 -0
- package/dist/browser/NetworkGuardProxy.d.ts +31 -0
- package/dist/browser/NetworkGuardProxy.js +331 -0
- package/dist/browser/NetworkGuardProxy.js.map +1 -0
- package/dist/browser/Observer.d.ts +12 -0
- package/dist/browser/Observer.js +31 -0
- package/dist/browser/Observer.js.map +1 -0
- package/dist/browser/PageCompressor.d.ts +47 -0
- package/dist/browser/PageCompressor.js +62 -0
- package/dist/browser/PageCompressor.js.map +1 -0
- package/dist/browser/SecurityBoundaryError.d.ts +3 -0
- package/dist/browser/SecurityBoundaryError.js +4 -0
- package/dist/browser/SecurityBoundaryError.js.map +1 -0
- package/dist/browser/SelectorEngine.d.ts +18 -0
- package/dist/browser/SelectorEngine.js +72 -0
- package/dist/browser/SelectorEngine.js.map +1 -0
- package/dist/browser/types.d.ts +38 -0
- package/dist/browser/types.js +2 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +284 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/history/TraceStore.d.ts +29 -0
- package/dist/history/TraceStore.js +53 -0
- package/dist/history/TraceStore.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/ClaudeSubscriptionProvider.d.ts +26 -0
- package/dist/llm/ClaudeSubscriptionProvider.js +182 -0
- package/dist/llm/ClaudeSubscriptionProvider.js.map +1 -0
- package/dist/llm/CodexSubscriptionProvider.d.ts +26 -0
- package/dist/llm/CodexSubscriptionProvider.js +168 -0
- package/dist/llm/CodexSubscriptionProvider.js.map +1 -0
- package/dist/llm/FlashProvider.d.ts +27 -0
- package/dist/llm/FlashProvider.js +103 -0
- package/dist/llm/FlashProvider.js.map +1 -0
- package/dist/llm/LLMProvider.d.ts +37 -0
- package/dist/llm/LLMProvider.js +2 -0
- package/dist/llm/LLMProvider.js.map +1 -0
- package/dist/llm/cliEnvironment.d.ts +5 -0
- package/dist/llm/cliEnvironment.js +21 -0
- package/dist/llm/cliEnvironment.js.map +1 -0
- package/dist/llm/prompts.d.ts +4 -0
- package/dist/llm/prompts.js +12 -0
- package/dist/llm/prompts.js.map +1 -0
- package/dist/llm/structuredOutput.d.ts +3 -0
- package/dist/llm/structuredOutput.js +58 -0
- package/dist/llm/structuredOutput.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +243 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/profile/ProfileStore.d.ts +7 -0
- package/dist/profile/ProfileStore.js +52 -0
- package/dist/profile/ProfileStore.js.map +1 -0
- package/dist/profile/VariableResolver.d.ts +15 -0
- package/dist/profile/VariableResolver.js +50 -0
- package/dist/profile/VariableResolver.js.map +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +239 -0
- package/dist/server/index.js.map +1 -0
- package/dist/ui/app.js +132 -0
- package/dist/ui/index.html +29 -0
- package/dist/ui/logo.svg +5 -0
- package/dist/ui/results.js +59 -0
- package/dist/ui/style.css +33 -0
- package/dist/verification/Verifier.d.ts +72 -0
- package/dist/verification/Verifier.js +87 -0
- package/dist/verification/Verifier.js.map +1 -0
- package/dist/workflows/WorkflowEngine.d.ts +23 -0
- package/dist/workflows/WorkflowEngine.js +52 -0
- package/dist/workflows/WorkflowEngine.js.map +1 -0
- package/docs/ADOPTION.md +61 -0
- package/docs/ARCHITECTURE.md +65 -0
- package/docs/BENCHMARKS.md +64 -0
- package/docs/IMPLEMENTATION.md +93 -0
- package/docs/LOCAL_DATA.md +98 -0
- package/docs/LOCAL_VALIDATION.md +87 -0
- package/docs/MCP.md +81 -0
- package/docs/PROVIDERS.md +150 -0
- package/docs/RELEASE_CHECKLIST.md +37 -0
- package/docs/REQUIREMENTS.md +39 -0
- package/docs/SUPPORT_MATRIX.md +193 -0
- package/docs/THREAT_MODEL.md +108 -0
- package/docs/USEFUL_EXAMPLES.md +151 -0
- package/docs/brand-mark.svg +12 -0
- package/package.json +90 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { readFile, realpath, stat } from 'node:fs/promises';
|
|
4
|
+
import { join, sep } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { Agent } from '../agent/Agent.js';
|
|
7
|
+
import { checkedHttpURL } from '../browser/Browser.js';
|
|
8
|
+
import { TraceStore } from '../history/TraceStore.js';
|
|
9
|
+
import { ProfileStore } from '../profile/ProfileStore.js';
|
|
10
|
+
import { VariableResolver } from '../profile/VariableResolver.js';
|
|
11
|
+
import { WorkflowEngine } from '../workflows/WorkflowEngine.js';
|
|
12
|
+
import { ensureDataDirectory, runtimeConfig } from '../config.js';
|
|
13
|
+
import { PlanSchema } from '../actions/schema.js';
|
|
14
|
+
const browserURL = z.url().refine(value => { try {
|
|
15
|
+
checkedHttpURL(value);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
} }, 'Only HTTP(S) destinations without embedded credentials are supported');
|
|
21
|
+
const SitePermission = z.object({ origin: z.url().refine(value => { const url = new URL(value); return url.origin === value && !url.username && !url.password; }, 'Expected a canonical site origin') }).strict();
|
|
22
|
+
const RunRequest = z.object({ goal: z.string().min(1).max(4000), url: browserURL, allowedOrigins: z.array(browserURL).max(30).default([]), confirmation: z.enum(['sensitive', 'always', 'never']).default('sensitive'), expectText: z.string().max(2000).optional(), expectUrl: z.string().max(2000).optional(), useWorkflows: z.boolean().default(true), mode: z.enum(['normal', 'ultra']).default('normal') }).strict();
|
|
23
|
+
const Manual = z.discriminatedUnion('type', [
|
|
24
|
+
z.object({ type: z.literal('click'), x: z.number().min(0).max(10000), y: z.number().min(0).max(10000) }).strict(),
|
|
25
|
+
z.object({ type: z.literal('type'), value: z.string().max(10000) }).strict(),
|
|
26
|
+
z.object({ type: z.literal('press'), value: z.string().max(50) }).strict(),
|
|
27
|
+
z.object({ type: z.literal('scroll'), y: z.number().min(-10000).max(10000) }).strict(),
|
|
28
|
+
z.object({ type: z.literal('navigate'), url: browserURL }).strict(),
|
|
29
|
+
]);
|
|
30
|
+
async function body(req) {
|
|
31
|
+
let buffer = '';
|
|
32
|
+
for await (const chunk of req) {
|
|
33
|
+
buffer += chunk;
|
|
34
|
+
if (Buffer.byteLength(buffer) > 65536)
|
|
35
|
+
throw new Error('Request too large');
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(buffer);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error('Invalid JSON request');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const same = (a, b) => { const aa = Buffer.from(a), bb = Buffer.from(b); return aa.length === bb.length && timingSafeEqual(aa, bb); };
|
|
45
|
+
export async function startServer(options = {}) {
|
|
46
|
+
const config = runtimeConfig();
|
|
47
|
+
ensureDataDirectory(config.dataDir);
|
|
48
|
+
const store = new TraceStore(join(config.dataDir, 'history.sqlite')), profiles = new ProfileStore(join(config.dataDir, 'profile.json'));
|
|
49
|
+
const secret = randomBytes(32).toString('hex');
|
|
50
|
+
let agent, pending;
|
|
51
|
+
const fallbackRedactor = new VariableResolver(), redactLocal = (value) => fallbackRedactor.redact(agent?.variables.redact(value) ?? value);
|
|
52
|
+
const redactLocalData = (value) => JSON.parse(redactLocal(JSON.stringify(value)));
|
|
53
|
+
const previous = store.history(1)[0], savedTrace = previous ? store.get(String(previous.id)) : undefined;
|
|
54
|
+
const restored = savedTrace?.status === 'running' ? undefined : savedTrace;
|
|
55
|
+
const listeners = new Set();
|
|
56
|
+
let origin = '';
|
|
57
|
+
const server = createServer(async (req, res) => {
|
|
58
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
59
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
60
|
+
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'");
|
|
61
|
+
const send = (status, value) => { res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(value)); };
|
|
62
|
+
try {
|
|
63
|
+
if (req.headers.host !== new URL(origin).host) {
|
|
64
|
+
send(403, { error: 'Unexpected Host' });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const path = new URL(req.url ?? '/', origin).pathname;
|
|
68
|
+
if (req.method === 'GET' && path === '/') {
|
|
69
|
+
res.setHeader('Set-Cookie', `fcu_session=${secret}; HttpOnly; SameSite=Strict; Path=/`);
|
|
70
|
+
const html = (await readFile(new URL('../ui/index.html', import.meta.url), 'utf8')).replace('__CSRF_TOKEN__', secret);
|
|
71
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
72
|
+
res.end(html);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (req.method === 'GET' && ['/app.js', '/results.js', '/style.css', '/logo.svg'].includes(path)) {
|
|
76
|
+
res.writeHead(200, { 'Content-Type': path.endsWith('.js') ? 'text/javascript' : path.endsWith('.svg') ? 'image/svg+xml' : 'text/css' });
|
|
77
|
+
res.end(await readFile(new URL('../ui' + path, import.meta.url)));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const cookie = req.headers.cookie?.split(';').map(s => s.trim()).find(s => s.startsWith('fcu_session='))?.slice(12) ?? '';
|
|
81
|
+
if (!same(cookie, secret)) {
|
|
82
|
+
send(401, { error: 'Open the local dashboard first' });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (req.method === 'POST' && (!same(String(req.headers['x-fcu-token'] ?? ''), secret) || req.headers.origin !== origin)) {
|
|
86
|
+
send(403, { error: 'Local origin and CSRF token required' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (req.method === 'GET' && path === '/api/state') {
|
|
90
|
+
send(200, { active: agent?.active ?? false, paused: agent?.control.paused ?? false, pending: agent?.control.pending,
|
|
91
|
+
trace: agent?.trace ? redactLocalData({ ...agent.trace, metrics: agent.active ? { ...agent.budget.snapshot(agent.trace.actions.filter(a => a.success).length), browserActions: agent.trace.actions.filter(a => a.success).length } : agent.trace.metrics }) : restored ? redactLocalData(restored) : undefined, approvedSites: agent?.approvedSites.map(redactLocal) ?? [], state: agent?.state ? { url: redactLocal(agent.state.url), title: redactLocal(agent.state.title), hash: agent.state.hash, warnings: agent.state.warnings.map(redactLocal), elements: agent.state.elements.length } : undefined,
|
|
92
|
+
pointer: agent?.browser.interaction.snapshot(), browserUrl: agent?.browser.page ? redactLocal(agent.browser.page.url()) : undefined, events: agent?.events.map(event => redactLocalData(event)) ?? [], model: options.provider?.name ?? config.provider?.name ?? 'Local workflows only', configured: !!(options.provider ?? config.provider), limits: config.budget.limits, history: store.history(12).map(row => redactLocalData(row)), workflows: new WorkflowEngine(store).list() });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (req.method === 'GET' && path === '/api/events') {
|
|
96
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Connection': 'keep-alive' });
|
|
97
|
+
res.write(': connected\n\n');
|
|
98
|
+
listeners.add(res);
|
|
99
|
+
req.on('close', () => listeners.delete(res));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (req.method === 'GET' && path === '/api/preview') {
|
|
103
|
+
if (!agent?.browser.page || agent.browser.page.isClosed()) {
|
|
104
|
+
res.writeHead(204);
|
|
105
|
+
res.end();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const running = agent, page = running.browser.page, pageId = running.browser.interaction.pageId(page);
|
|
109
|
+
const screenshot = await page.screenshot({ type: 'jpeg', quality: 70, timeout: 2500 });
|
|
110
|
+
if (agent !== running || page !== running.browser.page || pageId !== running.browser.interaction.pageId(page)) {
|
|
111
|
+
res.writeHead(204);
|
|
112
|
+
res.end();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
res.writeHead(200, { 'Content-Type': 'image/jpeg', 'X-FCU-Page-ID': String(pageId), 'X-FCU-Run-ID': running.trace?.id ?? '' });
|
|
116
|
+
res.end(screenshot);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (req.method === 'GET' && path === '/api/profile') {
|
|
120
|
+
send(200, await profiles.load());
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (req.method === 'GET' && path === '/api/download') {
|
|
124
|
+
const query = new URL(req.url ?? '/', origin).searchParams, id = z.string().min(1).max(100).parse(query.get('id')), index = Number(z.string().regex(/^\d+$/).parse(query.get('index')));
|
|
125
|
+
const trace = agent?.trace?.id === id ? agent.trace : store.get(id), receipt = trace?.actions[index];
|
|
126
|
+
if (!receipt?.success || receipt.action.type !== 'download')
|
|
127
|
+
throw new Error('Saved download not found');
|
|
128
|
+
const data = receipt.data;
|
|
129
|
+
if (!data?.path || !data.filename)
|
|
130
|
+
throw new Error('Saved download not found');
|
|
131
|
+
const root = await realpath(join(config.dataDir, 'downloads')), file = await realpath(data.path);
|
|
132
|
+
if (!file.startsWith(root + sep) || !(await stat(file)).isFile())
|
|
133
|
+
throw new Error('Saved download is outside the download folder');
|
|
134
|
+
res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(data.filename).replace(/'/g, '%27')}` });
|
|
135
|
+
res.end(await readFile(file));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (req.method === 'POST' && path === '/api/profile') {
|
|
139
|
+
if (agent?.active)
|
|
140
|
+
throw new Error('Finish or stop the current task before changing profile');
|
|
141
|
+
await profiles.save(await body(req));
|
|
142
|
+
send(200, { saved: true });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (req.method === 'POST' && ['/api/run', '/api/replay'].includes(path)) {
|
|
146
|
+
if (agent?.active || pending)
|
|
147
|
+
throw new Error('A task is already running');
|
|
148
|
+
const input = await body(req);
|
|
149
|
+
const replay = path === '/api/replay' ? store.get(z.object({ id: z.string() }).strict().parse(input).id) : undefined;
|
|
150
|
+
if (path === '/api/replay' && !replay)
|
|
151
|
+
throw new Error('Run not found');
|
|
152
|
+
const request = replay ? RunRequest.parse({ goal: replay.goal, url: replay.url }) : RunRequest.parse(input);
|
|
153
|
+
const fresh = runtimeConfig(), runOptions = { provider: options.provider ?? fresh.provider, budget: fresh.budget, vault: await profiles.load(), useWorkflows: request.useWorkflows, confirmation: request.confirmation, mode: request.mode,
|
|
154
|
+
completionCriteria: [...(request.expectText ? [{ type: 'text_exists', value: request.expectText }] : []), ...(request.expectUrl ? [{ type: 'url_contains', value: request.expectUrl }] : [])],
|
|
155
|
+
downloadDir: join(config.dataDir, 'downloads'), browser: { visualInteraction: true, headless: !options.headed, profileDir: join(config.dataDir, 'browser'), allowedOrigins: [new URL(request.url).origin, ...request.allowedOrigins.map(url => new URL(url).origin)] } };
|
|
156
|
+
if (agent)
|
|
157
|
+
await agent.prepareForNextRun(runOptions);
|
|
158
|
+
else {
|
|
159
|
+
agent = new Agent({ store, ...runOptions });
|
|
160
|
+
agent.on('event', event => { for (const listener of listeners)
|
|
161
|
+
listener.write(`data: ${JSON.stringify(event)}\n\n`); });
|
|
162
|
+
const running = agent;
|
|
163
|
+
running.browser.interaction.on('pointer', pointer => { for (const listener of listeners)
|
|
164
|
+
listener.write(`event: pointer\ndata: ${JSON.stringify({ runId: running.trace?.id, pointer })}\n\n`); });
|
|
165
|
+
}
|
|
166
|
+
const running = agent;
|
|
167
|
+
pending = (replay ? running.replay(replay) : running.run(request.goal, request.url)).catch(error => running.event('ERROR', error instanceof Error ? error.message : 'Task failed')).finally(() => { pending = undefined; });
|
|
168
|
+
send(202, { started: true });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (req.method === 'POST' && path.startsWith('/api/control/')) {
|
|
172
|
+
if (!agent)
|
|
173
|
+
throw new Error('No browser task yet');
|
|
174
|
+
const command = path.slice('/api/control/'.length);
|
|
175
|
+
if (command === 'pause')
|
|
176
|
+
agent.control.pause();
|
|
177
|
+
else if (command === 'resume')
|
|
178
|
+
agent.control.resume();
|
|
179
|
+
else if (command === 'stop')
|
|
180
|
+
agent.control.stop();
|
|
181
|
+
else if (command === 'revoke-site')
|
|
182
|
+
agent.revokeSite(SitePermission.parse(await body(req)).origin);
|
|
183
|
+
else if (command === 'approve')
|
|
184
|
+
agent.control.approve();
|
|
185
|
+
else if (command === 'reject')
|
|
186
|
+
agent.control.reject();
|
|
187
|
+
else if (command === 'edit') {
|
|
188
|
+
const plan = PlanSchema.parse(await body(req));
|
|
189
|
+
agent.control.edit(plan);
|
|
190
|
+
}
|
|
191
|
+
else if (command === 'manual') {
|
|
192
|
+
if (agent.active && !agent.control.paused)
|
|
193
|
+
throw new Error('Pause to take manual control');
|
|
194
|
+
const action = Manual.parse(await body(req));
|
|
195
|
+
const page = agent.browser.page;
|
|
196
|
+
if (!page || page.isClosed())
|
|
197
|
+
throw new Error('No live browser');
|
|
198
|
+
const manualOptions = { timeout: 4000, checkpoint: async () => { if (agent?.active && !agent.control.paused)
|
|
199
|
+
throw new Error('Pause to take manual control'); } };
|
|
200
|
+
if (action.type === 'click')
|
|
201
|
+
await agent.browser.interaction.manualClick(action.x, action.y, manualOptions);
|
|
202
|
+
if (action.type === 'type')
|
|
203
|
+
await page.keyboard.insertText(action.value);
|
|
204
|
+
if (action.type === 'press')
|
|
205
|
+
await page.keyboard.press(action.value);
|
|
206
|
+
if (action.type === 'scroll')
|
|
207
|
+
await agent.browser.interaction.scroll(action.y, manualOptions);
|
|
208
|
+
if (action.type === 'navigate')
|
|
209
|
+
await agent.browser.navigate(action.url);
|
|
210
|
+
await agent.observe();
|
|
211
|
+
}
|
|
212
|
+
else
|
|
213
|
+
throw new Error('Unknown control');
|
|
214
|
+
send(200, { ok: true });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
send(404, { error: 'Not found' });
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
send(400, { error: (error instanceof Error ? error.message : 'Request failed').replace(/sk-[a-zA-Z0-9_-]{16,}/g, '[redacted key]') });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
await new Promise(resolve => server.listen(options.port ?? 4318, '127.0.0.1', resolve));
|
|
224
|
+
const address = server.address();
|
|
225
|
+
if (!address || typeof address === 'string')
|
|
226
|
+
throw new Error('Local server failed');
|
|
227
|
+
origin = `http://127.0.0.1:${address.port}`;
|
|
228
|
+
if (!options.quiet)
|
|
229
|
+
console.log(`FreeComputerUse dashboard: ${origin}\nModel: ${config.provider?.name ?? 'not configured'}. Browser state stays local.`);
|
|
230
|
+
const close = async () => { agent?.control.stop(); await pending; await agent?.close(); for (const response of listeners)
|
|
231
|
+
response.end(); await new Promise(resolve => server.close(() => resolve())); store.close(); };
|
|
232
|
+
const shutdown = () => void close().then(() => process.exit());
|
|
233
|
+
if (!options.quiet) {
|
|
234
|
+
process.once('SIGINT', shutdown);
|
|
235
|
+
process.once('SIGTERM', shutdown);
|
|
236
|
+
}
|
|
237
|
+
return { server, url: origin, close, getAgent: () => agent };
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/app.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {renderResults,reportHtml} from './results.js';
|
|
2
|
+
const $=id=>document.getElementById(id);
|
|
3
|
+
const token=document.querySelector('meta[name=csrf-token]').content;
|
|
4
|
+
let state={},busy=false,lastEventCount=0,previewPending=false,refreshPending=false,resultKey='',resultCount=0,approvedSitesKey='';
|
|
5
|
+
let currentRunId='',previewPageId=0,pointer,lastPointerSequence=-1,lastClickSequence=-1,approvalKey='';
|
|
6
|
+
function mobileView(view){document.body.dataset.mobileView=view;for(const button of document.querySelectorAll('[data-mobile-view]'))button.setAttribute('aria-pressed',String(button.dataset.mobileView===view));}
|
|
7
|
+
mobileView('task');for(const button of document.querySelectorAll('[data-mobile-view]'))button.onclick=()=>mobileView(button.dataset.mobileView);
|
|
8
|
+
const panel=$('browser-panel');
|
|
9
|
+
panel.append($('edit-dialog'));
|
|
10
|
+
const expanded=()=>document.fullscreenElement===panel||panel.classList.contains('expanded');
|
|
11
|
+
function fullscreenState(){const active=expanded();$('fullscreen').setAttribute('aria-pressed',String(active));$('fullscreen').setAttribute('aria-label',active?'Exit full screen browser':'Full screen browser');$('fullscreen').lastElementChild.textContent=active?'Exit full screen':'Full screen';paintPointer();}
|
|
12
|
+
$('fullscreen').onclick=protect(async()=>{if(document.fullscreenElement)await document.exitFullscreen();else if(panel.classList.contains('expanded'))panel.classList.remove('expanded');else if(document.fullscreenEnabled&&panel.requestFullscreen){try{await panel.requestFullscreen();}catch{panel.classList.add('expanded');}}else panel.classList.add('expanded');fullscreenState();});
|
|
13
|
+
document.addEventListener('fullscreenchange',fullscreenState);document.addEventListener('keydown',event=>{if(event.key==='Escape'&&panel.classList.contains('expanded')){panel.classList.remove('expanded');fullscreenState();}});
|
|
14
|
+
function openDialog(id){$(id).showModal();}
|
|
15
|
+
$('options-open').onclick=()=>openDialog('options-dialog');$('history-open').onclick=()=>openDialog('history-dialog');$('stream-open').onclick=()=>{openDialog('stream-dialog');$('events').scrollTop=$('events').scrollHeight;};$('result-open').onclick=()=>openDialog('result-dialog');
|
|
16
|
+
$('ultra').onchange=()=>{document.querySelector('.permission-note').textContent=$('ultra').checked?'Ultra mode enabled · no approval prompts.':'Website access requires your approval.';};
|
|
17
|
+
const interactionNames={moving:'Moving to the next control',click:'Clicking',doubleClick:'Double-clicking',hover:'Hovering',fill:'Typing into the field',type:'Typing',select:'Choosing an option',check:'Checking the box',uncheck:'Unchecking the box',press:'Pressing a key',scroll:'Scrolling the page',upload:'Attaching the local file',submit:'Submitting the approved form',extract:'Reading the page',idle:'Browser ready'};
|
|
18
|
+
const text=(id,value)=>{$(id).textContent=value;};
|
|
19
|
+
function notice(message,error=false){text('notice',message);$('notice').classList.toggle('error',error);}
|
|
20
|
+
async function api(path,data){const response=await fetch('/api/'+path,{method:data===undefined?'GET':'POST',headers:data===undefined?{}:{'Content-Type':'application/json','X-FCU-Token':token},body:data===undefined?undefined:JSON.stringify(data)});const body=await response.json();if(!response.ok)throw new Error(body.error||'Request failed');return body;}
|
|
21
|
+
function protect(fn){return async event=>{event?.preventDefault();try{await fn(event);}catch(error){notice(error.message,true);}};}
|
|
22
|
+
function fmt(value){return Number(value||0).toLocaleString('en-US');}
|
|
23
|
+
function contentRect(){
|
|
24
|
+
const image=$('preview'),rect=image.getBoundingClientRect();
|
|
25
|
+
if(!image.naturalWidth||!image.naturalHeight||image.hidden)return;
|
|
26
|
+
const scale=Math.min(rect.width/image.naturalWidth,rect.height/image.naturalHeight),width=image.naturalWidth*scale,height=image.naturalHeight*scale;
|
|
27
|
+
return {left:rect.left+(rect.width-width)/2,top:rect.top+(rect.height-height)/2,width,height};
|
|
28
|
+
}
|
|
29
|
+
function preparation(){
|
|
30
|
+
const latest=state.events?.at(-1);
|
|
31
|
+
if(!state.active||state.pending||state.paused||!latest||!['PLAN','REPAIR'].includes(latest.phase))return;
|
|
32
|
+
if(latest.phase==='PLAN'&&latest.message==='Validated plan')return;
|
|
33
|
+
const seconds=Math.max(0,Math.floor((Date.now()-latest.time)/1000));
|
|
34
|
+
return {seconds,label:(latest.phase==='REPAIR'?'Preparing a correction':'Preparing the next actions')+` · ${seconds}s`,detail:seconds>=15?'Waiting for the model reply; no browser action has run during this preparation.':'Waiting for the model reply.'};
|
|
35
|
+
}
|
|
36
|
+
function paintPointer(){
|
|
37
|
+
const cursor=$('agent-cursor'),ring=$('click-ring'),rect=contentRect(),stage=$('pointer-layer').getBoundingClientRect();
|
|
38
|
+
const visible=pointer?.visible&&rect&&pointer.pageId===previewPageId;
|
|
39
|
+
cursor.hidden=!visible;
|
|
40
|
+
const preparing=preparation();cursor.classList.toggle('preparing',!!preparing);
|
|
41
|
+
text('interaction-label',state.pending?'Waiting for your approval':state.paused?'You have control':preparing?preparing.label:state.active?(interactionNames[pointer?.kind]||'Planning the next browser actions'):state.trace?.failureKind==='security'?'Blocked by local browser policy':state.trace?.status==='completed'?'Task complete':state.trace?.status==='stopped'?'Task stopped':'Watch the browser work here');
|
|
42
|
+
$('interaction-label').title=preparing?.detail||'';
|
|
43
|
+
if(preparing)text('control-state',preparing.detail);
|
|
44
|
+
if(!visible){ring.hidden=true;return;}
|
|
45
|
+
const x=rect.left-stage.left+pointer.x/pointer.width*rect.width,y=rect.top-stage.top+pointer.y/pointer.height*rect.height;
|
|
46
|
+
cursor.style.transform=`translate(${x}px,${y}px)`;cursor.dataset.sequence=pointer.sequence;cursor.dataset.kind=pointer.kind;cursor.dataset.pageId=pointer.pageId;
|
|
47
|
+
const typing=pointer.kind==='fill'||pointer.kind==='type';cursor.classList.toggle('typing',typing);
|
|
48
|
+
if(['click','doubleClick','check','uncheck'].includes(pointer.kind)&&pointer.sequence!==lastClickSequence){
|
|
49
|
+
lastClickSequence=pointer.sequence;ring.hidden=false;ring.style.left=x+'px';ring.style.top=y+'px';ring.classList.remove('pulse');void ring.offsetWidth;ring.classList.add('pulse');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function acceptPointer(next,runId=currentRunId){
|
|
53
|
+
if(!next||runId!==currentRunId||next.sequence<lastPointerSequence)return;
|
|
54
|
+
pointer=next;lastPointerSequence=next.sequence;paintPointer();
|
|
55
|
+
}
|
|
56
|
+
function render(snapshot){
|
|
57
|
+
state=snapshot;const trace=state.trace,metrics=trace?.metrics||{},active=state.active,paused=state.paused;
|
|
58
|
+
if((trace?.id||'')!==currentRunId){
|
|
59
|
+
currentRunId=trace?.id||'';previewPageId=0;pointer=undefined;lastPointerSequence=-1;lastClickSequence=-1;
|
|
60
|
+
const old=$('preview').src;if(old.startsWith('blob:'))URL.revokeObjectURL(old);$('preview').removeAttribute('src');$('preview').hidden=true;$('browser-empty').hidden=false;
|
|
61
|
+
}
|
|
62
|
+
acceptPointer(state.pointer);paintPointer();
|
|
63
|
+
text('model',state.model);text('model-note',state.configured?'Batch planning. Local execution.':'Configure a provider in .env to plan new tasks.');
|
|
64
|
+
const approvedSites=state.approvedSites||[],sitesKey=JSON.stringify(approvedSites);
|
|
65
|
+
if(sitesKey!==approvedSitesKey){approvedSitesKey=sitesKey;const list=$('approved-sites');list.replaceChildren();if(!approvedSites.length){const empty=document.createElement('li');empty.className='empty';empty.textContent='No sites approved in this session.';list.append(empty);}for(const origin of approvedSites){const item=document.createElement('li'),site=document.createElement('span'),button=document.createElement('button');site.textContent=origin;button.type='button';button.textContent='Revoke';button.setAttribute('aria-label',`Revoke access to ${origin}`);button.onclick=protect(async()=>{await api('control/revoke-site',{origin});notice(`Access revoked for ${origin}.`);await refresh();});item.append(site,button);list.append(item);}}
|
|
66
|
+
const blocked=trace?.failureKind==='security',status=state.pending?'APPROVAL':paused?'PAUSED':active?'RUNNING':blocked?'BLOCKED':trace?.status?.toUpperCase()||'IDLE';text('status',status);$('status').classList.toggle('active',active);$('status').classList.toggle('blocked',blocked);
|
|
67
|
+
$('run').disabled=active||busy;$('run').firstChild.textContent=active?'Task running ':'Run task ';
|
|
68
|
+
$('pause').disabled=!active||paused;$('resume').disabled=!paused||!active;$('stop').disabled=!active;$('edit-open').disabled=!active||!paused;
|
|
69
|
+
text('page-url',state.browserUrl||state.state?.url||'No page open');text('control-state',paused?'Manual control · browser clicks enabled':active?'Agent is controlling the browser':blocked?'Blocked by local browser security policy':trace?.status==='completed'?'Task verified and completed':'Ready when you are');
|
|
70
|
+
$('manual-tools').hidden=!paused;$('preview').classList.toggle('manual',paused);
|
|
71
|
+
$('approval').hidden=!state.pending;if(state.pending){
|
|
72
|
+
const nextKey=currentRunId+JSON.stringify(state.pending)+(state.events?.filter(e=>e.phase==='HUMAN').at(-1)?.time??'');
|
|
73
|
+
if(nextKey!==approvalKey){mobileView('browser');for(const dialog of document.querySelectorAll('dialog[open]'))dialog.close();approvalKey=nextKey;}
|
|
74
|
+
text('approval-reason',state.pending.reason);text('approval-action',typeof state.pending.action==='string'?state.pending.action:JSON.stringify(state.pending.action,null,2));
|
|
75
|
+
}else approvalKey='';
|
|
76
|
+
const calls=metrics.llmCalls??trace?.calls?.length??0,actions=metrics.browserActions??trace?.actions?.filter(a=>a.success).length??0;
|
|
77
|
+
const input=metrics.inputTokens??trace?.calls?.reduce((n,c)=>n+c.usage.input,0)??0,output=metrics.outputTokens??trace?.calls?.reduce((n,c)=>n+c.usage.output,0)??0;
|
|
78
|
+
text('calls',fmt(calls));text('actions',fmt(actions));text('ratio',calls?(actions/calls).toFixed(1):actions?'local':'—');text('tokens',fmt(input+output));text('cost',metrics.estimatedCostUSD==null?'—':'$'+Number(metrics.estimatedCostUSD).toFixed(5));
|
|
79
|
+
const caps=[['input',input,state.limits?.maxInputTokens],['output',output,state.limits?.maxOutputTokens],['calls',calls,state.limits?.maxLLMCalls]].filter(([, ,limit])=>Number.isFinite(limit));
|
|
80
|
+
$('budget').hidden=!caps.length;
|
|
81
|
+
if(caps.length){const fraction=Math.min(1,Math.max(...caps.map(([,used,limit])=>used/limit)));text('budget-value',Math.round(fraction*100)+'%');$('budget-meter').style.width=fraction*100+'%';text('budget-limit',caps.map(([name,,limit])=>`${fmt(limit)} ${name}`).join(' / '));}
|
|
82
|
+
const nextResultKey=JSON.stringify([trace?.id,trace?.status,trace?.actions?.filter(a=>a.success&&a.data!==undefined).map(a=>[a.action.type,a.action.key,a.data])]);
|
|
83
|
+
if(nextResultKey!==resultKey){resultKey=nextResultKey;resultCount=renderResults($('result-output'),trace);}
|
|
84
|
+
const outputs=resultCount;
|
|
85
|
+
$('result-panel').hidden=!outputs;$('result-open').disabled=!outputs;
|
|
86
|
+
for(const id of ['result-copy','result-save'])$(id).disabled=!outputs;
|
|
87
|
+
for(const id of ['page-up','page-down'])$(id).disabled=!state.browserUrl||active&&!paused;
|
|
88
|
+
const plan=trace?.plans?.at(-1);$('plan').replaceChildren();for(const step of plan?.steps||[preparation()?'Preparing the first action batch…':'Waiting for a task.']){const li=document.createElement('li');li.textContent=step;$('plan').append(li);}
|
|
89
|
+
const observe=state.events?.filter(e=>e.phase==='OBSERVE').at(-1);text('compression',observe?.data?.reduction!=null?(observe.data.reduction*100).toFixed(1)+'% smaller':'—');
|
|
90
|
+
text('warnings',state.state?.warnings?.join('\n')||'');
|
|
91
|
+
const phase=state.events?.at(-1)?.phase;for(const span of $('loop').children)span.classList.toggle('current',span.textContent.toUpperCase()===phase);
|
|
92
|
+
const newest=state.events?.at(-1),eventKey=[state.events?.length,newest?.time,newest?.phase,newest?.message].join(':');
|
|
93
|
+
if(lastEventCount!==eventKey){lastEventCount=eventKey;const list=$('events'),oldTop=list.scrollTop,follow=!$('stream-dialog').open||list.scrollHeight-list.scrollTop-list.clientHeight<60;list.replaceChildren();for(const event of state.events||[]){const li=document.createElement('li'),badge=document.createElement('span'),message=document.createElement('span'),time=document.createElement('span');badge.className='phase '+event.phase;badge.textContent=event.phase;message.textContent=event.message;time.className='time';time.textContent=new Date(event.time).toLocaleTimeString('en-GB');li.append(badge,message,time);list.append(li);}list.scrollTop=follow?list.scrollHeight:oldTop;}
|
|
94
|
+
text('history-count',state.history?.length||0);$('history').replaceChildren();for(const run of state.history||[]){const button=document.createElement('button'),title=document.createElement('strong'),meta=document.createElement('small');button.className='history-item';title.textContent=run.goal;meta.textContent=run.status.toUpperCase()+' · '+new Date(Number(run.started)).toLocaleDateString();button.append(title,meta);button.title=run.status==='completed'?'Replay this run with no model':'Use this task again';button.disabled=active;button.onclick=protect(async()=>{$('history-dialog').close();if(run.status==='completed'){await api('replay',{id:run.id});mobileView('browser');notice('Replaying successful semantic actions.');}else{$('goal').value=run.goal;$('start-url').value=run.url;mobileView('task');notice('Task loaded. Edit it before running.');}await refresh();});$('history').append(button);}if(!state.history?.length)text('history','Your first run starts here.');
|
|
95
|
+
text('workflow-count',state.workflows?.length||0);
|
|
96
|
+
if(trace?.error)notice(trace.error,true);
|
|
97
|
+
paintPointer();
|
|
98
|
+
}
|
|
99
|
+
async function refresh(){if(refreshPending)return;refreshPending=true;try{render(await api('state'));}catch(error){notice('Connection lost: '+error.message,true);}finally{refreshPending=false;}}
|
|
100
|
+
async function preview(){
|
|
101
|
+
if(previewPending||document.hidden)return;previewPending=true;
|
|
102
|
+
try{
|
|
103
|
+
const response=await fetch('/api/preview');if(!response.ok||response.status===204)return;
|
|
104
|
+
const runId=response.headers.get('X-FCU-Run-ID'),pageId=Number(response.headers.get('X-FCU-Page-ID'));
|
|
105
|
+
if(runId!==currentRunId)return;
|
|
106
|
+
const blob=await response.blob(),url=URL.createObjectURL(blob),old=$('preview').src;
|
|
107
|
+
await new Promise((resolve,reject)=>{
|
|
108
|
+
$('preview').onload=()=>{if(old.startsWith('blob:'))URL.revokeObjectURL(old);if(runId===currentRunId){previewPageId=pageId;$('preview').hidden=false;$('browser-empty').hidden=true;paintPointer();}resolve();};
|
|
109
|
+
$('preview').onerror=()=>{URL.revokeObjectURL(url);reject(new Error('Preview image failed'));};$('preview').src=url;
|
|
110
|
+
});
|
|
111
|
+
}catch{}finally{previewPending=false;}
|
|
112
|
+
}
|
|
113
|
+
async function previewLoop(){await preview();setTimeout(previewLoop,state.active?100:state.trace?650:1500);}
|
|
114
|
+
$('task-form').onsubmit=protect(async()=>{busy=true;try{await api('run',{goal:$('goal').value,url:$('start-url').value,confirmation:$('confirmation').value,allowedOrigins:$('origins').value.split(',').map(s=>s.trim()).filter(Boolean),expectText:$('expected').value||undefined,useWorkflows:$('use-workflows').checked,mode:$('ultra').checked?'ultra':'normal'});mobileView('browser');notice('Task started. Watch the live browser.');lastEventCount=-1;await refresh();}finally{busy=false;}});
|
|
115
|
+
for(const command of ['pause','resume','stop','approve','reject'])$(command).onclick=protect(async()=>{await api('control/'+command,{});await refresh();});
|
|
116
|
+
for(const button of document.querySelectorAll('[data-close]'))button.onclick=()=>$(button.dataset.close).close();
|
|
117
|
+
$('profile-open').onclick=protect(async()=>{const profile=await api('profile');$('profile-json').value=JSON.stringify(Object.keys(profile.profile).length?profile:{profile:{firstName:'',lastName:'',email:'',phone:'',country:'',city:''},files:{}},null,2);$('profile-dialog').showModal();});
|
|
118
|
+
$('profile-form').onsubmit=protect(async()=>{await api('profile',JSON.parse($('profile-json').value));$('profile-dialog').close();notice('Profile saved locally.');});
|
|
119
|
+
$('workflows-open').onclick=()=>{$('workflow-list').replaceChildren();for(const flow of state.workflows||[]){const row=document.createElement('div'),small=document.createElement('small');row.className='workflow-row';row.textContent=flow.intent;small.textContent=flow.domain+' · '+flow.hits+' reuses';row.append(small);$('workflow-list').append(row);}if(!state.workflows?.length)text('workflow-list','Complete a task to learn its first workflow.');$('workflows-dialog').showModal();};
|
|
120
|
+
$('edit-open').onclick=()=>{$('plan-json').value=JSON.stringify(state.trace?.plans?.at(-1),null,2);$('edit-dialog').showModal();};
|
|
121
|
+
$('edit-form').onsubmit=protect(async()=>{await api('control/edit',JSON.parse($('plan-json').value));$('edit-dialog').close();notice('Edited plan validated. Resume when ready.');});
|
|
122
|
+
$('preview').onclick=protect(async event=>{if(!state.paused)return;const img=$('preview'),rect=contentRect();if(!rect||event.clientX<rect.left||event.clientY<rect.top||event.clientX>=rect.left+rect.width||event.clientY>=rect.top+rect.height)return;await api('control/manual',{type:'click',x:(event.clientX-rect.left)*img.naturalWidth/rect.width,y:(event.clientY-rect.top)*img.naturalHeight/rect.height});await preview();});
|
|
123
|
+
$('manual-type').onclick=protect(async()=>{await api('control/manual',{type:'type',value:$('manual-value').value});await preview();});
|
|
124
|
+
$('manual-enter').onclick=protect(async()=>{await api('control/manual',{type:'press',value:'Enter'});await preview();});
|
|
125
|
+
$('manual-scroll').onclick=protect(async()=>{await api('control/manual',{type:'scroll',y:500});await preview();});
|
|
126
|
+
for(const [id,y] of [['page-up',-400],['page-down',400]])$(id).onclick=protect(async()=>{await api('control/manual',{type:'scroll',y});await preview();});
|
|
127
|
+
$('result-copy').onclick=protect(async()=>{await navigator.clipboard.writeText($('result-output').innerText);text('result-copy','Copied');setTimeout(()=>text('result-copy','Copy summary'),1500);});
|
|
128
|
+
$('result-save').onclick=()=>{const blob=new Blob([reportHtml($('result-output'),state.trace?.goal)],{type:'text/html;charset=utf-8'}),url=URL.createObjectURL(blob),link=document.createElement('a');link.href=url;link.download='task-report.html';link.click();setTimeout(()=>URL.revokeObjectURL(url),1000);};
|
|
129
|
+
const stream=new EventSource('/api/events');stream.onmessage=()=>refresh();
|
|
130
|
+
stream.addEventListener('pointer',event=>{try{const next=JSON.parse(event.data);acceptPointer(next.pointer,next.runId);}catch{}});
|
|
131
|
+
new ResizeObserver(paintPointer).observe($('browser-stage'));
|
|
132
|
+
await refresh();setInterval(refresh,1500);setInterval(paintPointer,1000);previewLoop();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="csrf-token" content="__CSRF_TOKEN__"><title>FreeComputerUse · Local browser workspace</title><link rel="icon" type="image/svg+xml" href="/logo.svg"><link rel="stylesheet" href="/style.css"><script type="module" src="/app.js"></script></head>
|
|
3
|
+
<body>
|
|
4
|
+
<header><a class="brand" href="/" aria-label="FreeComputerUse home"><img class="brandmark" src="/logo.svg" width="44" height="44" alt=""><span>FreeComputerUse<span class="edition">LOCAL WORKSPACE</span></span></a><div class="header-tools"><span class="local-badge"><i></i> On your machine</span><button id="profile-open" class="quiet">Local profile <span>↗</span></button></div></header>
|
|
5
|
+
<nav class="mobile-view-switch" aria-label="Workspace view"><button data-mobile-view="task" aria-pressed="true">Task</button><button data-mobile-view="browser" aria-pressed="false">Live browser</button><button data-mobile-view="stats" aria-pressed="false">Run details</button></nav>
|
|
6
|
+
<main class="workspace">
|
|
7
|
+
<aside class="task-panel"><div class="panel-label">01 / INTENT</div><h1>Give the goal.<br><em>Let code do the clicks.</em></h1><p class="intro">Plan in batches. Execute locally. Keep the browser work moving.</p>
|
|
8
|
+
<form id="task-form"><label for="start-url">Starting page</label><input id="start-url" type="url" placeholder="https://example.com" required><label for="goal">What should happen?</label><textarea id="goal" rows="5" placeholder="Find the pricing page and extract the cheapest plan." required></textarea>
|
|
9
|
+
<button type="button" id="options-open" class="run-options">Run options <span>↗</span></button><p class="permission-note">Website access requires your approval.</p>
|
|
10
|
+
<button class="primary" id="run" type="submit" aria-label="Run task">Run task <span>↗</span></button></form>
|
|
11
|
+
<div class="notice" id="notice" role="status" aria-live="polite"></div><div class="workspace-links"><button id="history-open" class="quiet workflow-button">Recent runs <span id="history-count">0</span></button><button class="quiet workflow-button" id="workflows-open">Learned workflows <span id="workflow-count">0</span></button></div>
|
|
12
|
+
<div class="task-footer">AI discovers the workflow.<br><strong>Software runs it again.</strong></div></aside>
|
|
13
|
+
<section class="browser-panel" id="browser-panel" aria-label="Live browser"><div class="browser-toolbar"><div class="traffic"><i></i><i></i><i></i></div><span class="page-url" id="page-url">No page open</span><div class="page-scroll"><button id="page-up" aria-label="Scroll browser page up" title="Scroll page up; take control while the agent runs" disabled>↑</button><button id="page-down" aria-label="Scroll browser page down" title="Scroll page down; take control while the agent runs" disabled>↓</button></div><span class="preview-tag">LIVE</span><button id="fullscreen" aria-label="Full screen browser" aria-pressed="false">⛶ <span>Full screen</span></button></div><div class="browser-stage" id="browser-stage"><div id="browser-empty" class="browser-empty"><div class="empty-symbol" aria-hidden="true">↗</div><span class="eyebrow">A BROWSER WITH A PURPOSE</span><h2>Your next task,<br>already in motion.</h2><p>Open a page and give it a goal.<br>Watch the cursor move, click and type.</p><div class="method"><span>Move</span><b aria-hidden="true">→</b><span>Click</span><b aria-hidden="true">→</b><span>Type</span></div></div><img id="preview" alt="Live preview of the controlled Chromium browser" hidden><div id="pointer-layer" aria-hidden="true"><div id="click-ring" hidden></div><div id="agent-cursor" hidden><svg width="30" height="30" viewBox="0 0 34 34"><path d="M5 2.5C2.8 1.7 1.2 3.3 2 5.5l9.5 24c1 2.5 4.6 2.2 5.3-.4l2.8-9.5 9.5-2.8c2.6-.7 2.9-4.3.4-5.3Z" fill="#1677ff" stroke="#fff" stroke-width="2.5" stroke-linejoin="round"/></svg></div></div></div>
|
|
14
|
+
<div class="interaction-line"><span class="interaction-dot"></span><span id="interaction-label" role="status">Watch the browser work here</span><span class="interaction-note">Live cursor & browser</span></div>
|
|
15
|
+
<div class="control-bar"><span id="control-state"><i></i> Ready when you are</span><div><button id="pause" disabled>Take control</button><button id="resume" disabled>Resume agent</button><button id="stop" class="danger" disabled>Stop</button><button id="edit-open" disabled>Edit plan</button></div><div><button id="stream-open">Execution log</button><button id="result-open" disabled>View result</button></div></div><div id="manual-tools" class="manual-tools" hidden><label for="manual-value">Click the browser to focus a field, then type</label><div><input id="manual-value" placeholder="Text to enter locally"><button id="manual-type">Type</button><button id="manual-enter">Enter ↵</button><button id="manual-scroll">Scroll ↓</button></div></div>
|
|
16
|
+
<div id="approval" class="approval" role="region" aria-label="Pending approval" aria-live="polite" hidden><div><span class="eyebrow">YOUR DECISION</span><h3>Confirmation required</h3><p id="approval-reason"></p></div><details class="approval-details"><summary>Review action details</summary><pre id="approval-action"></pre></details><div class="approval-actions"><button class="primary" id="approve">Approve action</button><button id="reject">Reject</button></div></div>
|
|
17
|
+
<dialog id="stream-dialog" class="stream-dialog"><div class="dialog-heading"><h2>Execution log</h2><button data-close="stream-dialog" aria-label="Close execution log">×</button></div><p>Follow the plan, browser actions, verification and repairs as they happen.</p><div class="activity"><ol id="events"><li class="stream-empty">Observe → Plan → Execute → Verify → Repair</li></ol></div></dialog>
|
|
18
|
+
<dialog id="result-dialog"><div class="dialog-heading"><div><span class="eyebrow">WHAT THE BROWSER FOUND</span><h2>Task result</h2></div><button data-close="result-dialog" aria-label="Close task result">×</button></div><div class="result-tools"><button id="result-copy" disabled>Copy summary</button><button id="result-save" disabled>Save report ↗</button></div><section id="result-panel" class="results" hidden><div id="result-output"></div></section></dialog>
|
|
19
|
+
</section>
|
|
20
|
+
<aside class="state-panel"><div class="panel-label">02 / EXECUTION</div><div class="state-title"><h2>Agent state</h2><span id="status" class="status">IDLE</span></div><div class="loop" id="loop"><span>Observe</span><span>Plan</span><span>Execute</span><span>Verify</span><span>Repair</span></div>
|
|
21
|
+
<div class="model-block"><span class="micro">PLANNING MODEL</span><strong id="model">Loading…</strong><p id="model-note">Checking local configuration.</p></div><div class="metrics"><div><span>Model calls</span><strong id="calls">0</strong></div><div><span>Browser actions</span><strong id="actions">0</strong></div><div><span>Actions / call</span><strong id="ratio">—</strong></div><div><span>Total tokens</span><strong id="tokens">0</strong></div><div class="cost"><span>Estimated cost</span><strong id="cost">—</strong></div></div>
|
|
22
|
+
<div class="budget" id="budget" hidden><div class="section-title">OPTIONAL TOKEN LIMIT <span id="budget-value">0%</span></div><div class="meter"><i id="budget-meter"></i></div><p id="budget-limit"></p></div><div class="plan-section"><div class="section-title">CURRENT PLAN</div><ol id="plan"><li>Waiting for a task.</li></ol></div><div class="dom-stat"><span class="micro">PAGE COMPRESSION</span><strong id="compression">—</strong><p>Useful page context, fewer tokens.</p></div><div id="warnings" class="warnings"></div><p class="privacy">Profile values resolve locally.<br>Browser previews never go to the model.</p></aside>
|
|
23
|
+
</main>
|
|
24
|
+
<dialog id="options-dialog"><div class="dialog-heading"><h2>Run options</h2><button data-close="options-dialog" aria-label="Close run options">×</button></div><label for="confirmation">Human confirmation</label><select id="confirmation"><option value="sensitive">Before sensitive actions</option><option value="always">Before every action</option><option value="never">Pre-authorized actions</option></select><label for="expected">Expected final text (optional)</label><input id="expected" placeholder="Your expected result"><label for="origins">Additional allowed origins</label><input id="origins" placeholder="https://auth.example.com, https://cdn.example.com"><label class="check"><input id="use-workflows" type="checkbox" checked> Reuse learned workflows</label><label class="check"><input id="ultra" type="checkbox"> Ultra mode: skip all approvals</label><p>Normal mode asks before accessing each website. Ultra skips prompts except for sites you revoke, which require fresh approval.</p><section class="site-permissions"><h3>Approved sites this session</h3><p>Revoking a site blocks its requests. If the browser is currently using it, the task stops.</p><ul id="approved-sites"><li class="empty">No sites approved in this session.</li></ul></section></dialog>
|
|
25
|
+
<dialog id="history-dialog"><div class="dialog-heading"><h2>Recent runs</h2><button data-close="history-dialog" aria-label="Close recent runs">×</button></div><p>Replay a completed task, or load a previous goal to edit it.</p><div id="history" class="empty-list">Your first run starts here.</div></dialog>
|
|
26
|
+
<dialog id="profile-dialog"><form id="profile-form"><div class="dialog-heading"><h2>Your local profile</h2><button type="button" data-close="profile-dialog" aria-label="Close profile">×</button></div><p>Saved on this machine with restricted file permissions. The model receives field names; values resolve in the browser.</p><label for="profile-json">Profile and file aliases (JSON)</label><textarea id="profile-json" rows="15" spellcheck="false"></textarea><p class="hint">Files use absolute paths and are available only through explicit aliases such as {{files.resume}}.</p><button class="primary">Save locally</button></form></dialog>
|
|
27
|
+
<dialog id="workflows-dialog"><div class="dialog-heading"><h2>Learned workflows</h2><button data-close="workflows-dialog" aria-label="Close workflows">×</button></div><p>Successful semantic actions can run again without a model call. Matches require the same origin, path, intent and page structure.</p><div id="workflow-list"></div></dialog>
|
|
28
|
+
<dialog id="edit-dialog"><form id="edit-form"><div class="dialog-heading"><h2>Edit the execution plan</h2><button type="button" data-close="edit-dialog" aria-label="Close editor">×</button></div><p>Pause first. A validated replacement plan starts when you resume.</p><textarea id="plan-json" rows="20" spellcheck="false"></textarea><button class="primary">Use this plan</button></form></dialog>
|
|
29
|
+
</body></html>
|
package/dist/ui/logo.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
|
2
|
+
<path d="M48 13H23L9 27V45L22 58H43L56 45V33H42V40L37 45H28L23 40V32L28 27H39L48 13Z" fill="#2C4134"/>
|
|
3
|
+
<path d="M34 4L61 17L47 22L42 37L34 4Z" fill="#B9DE46"/>
|
|
4
|
+
<path d="M5 53L10 48L15 53L10 58L5 53Z" fill="#B9DE46"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const node=(tag,className,value)=>{const el=document.createElement(tag);if(className)el.className=className;if(value!==undefined)el.textContent=String(value);return el;};
|
|
2
|
+
const label=value=>String(value).replace(/([a-z])([A-Z])/g,'$1 $2').replace(/[_-]+/g,' ').replace(/^./,s=>s.toUpperCase());
|
|
3
|
+
const safeLink=value=>{try{const url=new URL(value);return ['https:','http:'].includes(url.protocol)&&!url.username&&!url.password?url.href:null;}catch{return null;}};
|
|
4
|
+
const scalar=value=>value===null?'—':typeof value==='object'?JSON.stringify(value):String(value);
|
|
5
|
+
function facts(text){
|
|
6
|
+
const lines=text.split(/\r?\n/).map(s=>s.trim()).filter(Boolean),items=[];
|
|
7
|
+
const field=/^(route|date|passengers|service|total price|price|duration|access|fare|product|name|battery(?: life)?|weight|availability|stock|status|owner|customer|invoice(?: number)?|total|currency|timezone|time zone|updated|author|licen[cs]e|version|language|departure|arrival|interchanges)$/i;
|
|
8
|
+
for(let i=0;i<lines.length-1;i++)if(field.test(lines[i])&&!field.test(lines[i+1])){items.push([lines[i],lines[++i]]);}
|
|
9
|
+
return items.length>=3?items:[];
|
|
10
|
+
}
|
|
11
|
+
function definition(items){const dl=node('dl','result-facts');for(const [key,value] of items){const item=node('div');item.append(node('dt','',label(key)),node('dd','',scalar(value)));dl.append(item);}return dl;}
|
|
12
|
+
function table(headers,rows){const wrap=node('div','result-table-wrap'),el=node('table','result-table'),head=node('thead'),tr=node('tr');for(const h of headers)tr.append(node('th','',label(h)));head.append(tr);el.append(head);const body=node('tbody');for(const row of rows){const line=node('tr');for(let i=0;i<headers.length;i++)line.append(node('td','',scalar(row[i]??'')));body.append(line);}el.append(body);wrap.append(el);return wrap;}
|
|
13
|
+
function content(value,format){
|
|
14
|
+
if(typeof value==='string'){
|
|
15
|
+
const extracted=facts(value),wrap=node('div');
|
|
16
|
+
if(extracted.length){wrap.append(definition(extracted));const details=node('details','result-source');details.append(node('summary','','View source text'),node('div','result-prose',value));wrap.append(details);}
|
|
17
|
+
else wrap.append(node('div','result-prose',value));
|
|
18
|
+
return wrap;
|
|
19
|
+
}
|
|
20
|
+
if(Array.isArray(value)){
|
|
21
|
+
if(!value.length)return node('p','result-empty','No matching items.');
|
|
22
|
+
if(value.every(Array.isArray)){const [headers,...rows]=value;return table(headers,rows);}
|
|
23
|
+
if(value.every(item=>item&&typeof item==='object'&&!Array.isArray(item))){
|
|
24
|
+
if(format==='links'){const list=node('ul','result-links');for(const item of value){const li=node('li'),url=safeLink(item.url);if(url){const a=node('a','',item.text||url);a.href=url;a.target='_blank';a.rel='noopener noreferrer';li.append(a,node('small','',url));}else li.append(node('span','',item.text||'Unavailable link'));list.append(li);}return list;}
|
|
25
|
+
const headers=[...new Set(value.flatMap(Object.keys))];return table(headers,value.map(item=>headers.map(h=>item[h])));
|
|
26
|
+
}
|
|
27
|
+
const list=node('ul','result-list');for(const item of value)list.append(node('li','',scalar(item)));return list;
|
|
28
|
+
}
|
|
29
|
+
if(value&&typeof value==='object')return definition(Object.entries(value));
|
|
30
|
+
return node('p','result-prose',scalar(value));
|
|
31
|
+
}
|
|
32
|
+
export function renderResults(container,trace){
|
|
33
|
+
container.replaceChildren();const sections=new Map(),downloads=[];
|
|
34
|
+
for(const [index,receipt] of (trace?.actions||[]).entries()){
|
|
35
|
+
if(!receipt.success||receipt.data===undefined)continue;
|
|
36
|
+
if(receipt.action.type==='download'){downloads.push({index,filename:receipt.data.filename});continue;}
|
|
37
|
+
const data=receipt.data;
|
|
38
|
+
for(const [key,value] of Object.entries(data&&typeof data==='object'&&!Array.isArray(data)?data:{result:data}))sections.set(key,{key,value,format:receipt.action.format});
|
|
39
|
+
}
|
|
40
|
+
const seen=new Set(),unique=[...sections.values()].reverse().filter(section=>{const fingerprint=JSON.stringify(section.value);if(seen.has(fingerprint))return false;seen.add(fingerprint);return true;});
|
|
41
|
+
const overview=node('div','result-overview'),status=node('span','result-status '+(trace?.status==='completed'?'complete':'partial'),trace?.status==='completed'?'Completed':trace?.status==='running'?'In progress':'Partial result');
|
|
42
|
+
overview.append(status,node('span','result-count',`${unique.length} result${unique.length===1?'':'s'} · ${downloads.length} saved file${downloads.length===1?'':'s'}`));container.append(overview);
|
|
43
|
+
if(trace?.status!=='completed')container.append(node('p','result-caveat',trace?.status==='running'?'The task is still running. These are the results collected so far.':'The task did not finish. These are the results collected before it stopped; requested files may be missing.'));
|
|
44
|
+
if(trace?.status==='failed'&&trace.error)container.append(node('p','result-error',`Stopped because: ${trace.error}`));
|
|
45
|
+
if(downloads.length){const card=node('section','result-card');card.append(node('h3','','Saved files'));for(const file of downloads){const row=node('div','result-file');row.append(node('span','file-icon','↧'),node('strong','',file.filename||'Downloaded file'));const a=node('a','file-save','Download file');a.href=`/api/download?id=${encodeURIComponent(trace.id)}&index=${file.index}`;a.download=file.filename||'';row.append(a);card.append(row);}container.append(card);}
|
|
46
|
+
for(const [index,section] of unique.entries()){
|
|
47
|
+
const card=node('section','result-card');card.append(node('h3','',label(section.key)));
|
|
48
|
+
if(typeof section.value==='string'&&!facts(section.value).length&§ion.value.length>700){const details=node('details','result-supporting');details.append(node('summary','','Read extracted page text'),content(section.value,section.format));card.append(node('p','result-prose',section.value.slice(-420)),details);}
|
|
49
|
+
else if(index>0&&typeof section.value==='string'&&!facts(section.value).length){const details=node('details','result-supporting');details.append(node('summary','','Read extracted text'),content(section.value,section.format));card.append(details);}
|
|
50
|
+
else card.append(content(section.value,section.format));
|
|
51
|
+
container.append(card);
|
|
52
|
+
}
|
|
53
|
+
if(!unique.length&&!downloads.length)container.append(node('p','result-empty','No extracted results or saved files yet.'));
|
|
54
|
+
return unique.length+downloads.length;
|
|
55
|
+
}
|
|
56
|
+
export function reportHtml(container,goal){
|
|
57
|
+
const title=node('h1','',goal||'Browser task result');
|
|
58
|
+
return '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Task report</title><style>body{max-width:960px;margin:48px auto;padding:0 24px;font:16px/1.6 "Avenir Next",sans-serif;color:#253c34;background:#f8f9f4}h1{font-size:26px}h3{font-size:18px}.result-card{margin:24px 0;padding:24px;background:white;border:1px solid #dce3d9;border-radius:12px}.result-overview{display:flex;justify-content:space-between;gap:20px}.result-status{font-weight:bold}.result-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px}dt{font-size:12px;color:#667a70}dd{margin:4px 0;font-weight:600}.result-prose{white-space:pre-wrap;overflow-wrap:anywhere}.result-table-wrap{overflow:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:12px;border-bottom:1px solid #dce3d9}.result-source{margin-top:20px}small{display:block;overflow-wrap:anywhere}.result-file{display:flex;gap:16px;align-items:center}.file-save{display:none}.result-caveat{padding:16px;background:#fff2de}.result-error{padding:16px;background:#ffe9e1;color:#7d3924;overflow-wrap:anywhere}@media print{body{margin:0}.result-card{break-inside:avoid}}</style></head><body>'+title.outerHTML+container.innerHTML+'</body></html>';
|
|
59
|
+
}
|