granclaw 0.0.1-beta.96 → 0.0.1-beta.98
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/dist/backend/agent/browser-bin.js +96 -0
- package/dist/backend/agent/runner-pi.js +50 -4
- package/dist/backend/orchestrator/server.js +14 -0
- package/dist/backend/workflows/runner.js +30 -0
- package/dist/frontend/assets/{index-2w83F8pA.js → index-ITF3o546.js} +44 -44
- package/dist/frontend/index.html +1 -1
- package/package.json +1 -1
|
@@ -1,25 +1,120 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.registerBrowserProvider = registerBrowserProvider;
|
|
40
|
+
exports.registerBrowserKiller = registerBrowserKiller;
|
|
41
|
+
exports.killBrowser = killBrowser;
|
|
7
42
|
exports._resetBrowserProvidersForTests = _resetBrowserProvidersForTests;
|
|
8
43
|
exports.buildArgv = buildArgv;
|
|
44
|
+
exports.cdpNavigate = cdpNavigate;
|
|
9
45
|
exports.resolveBrowserBinary = resolveBrowserBinary;
|
|
10
46
|
const fs_1 = __importDefault(require("fs"));
|
|
11
47
|
const path_1 = __importDefault(require("path"));
|
|
48
|
+
const ws_1 = require("ws");
|
|
12
49
|
const stealth_js_1 = require("../browser/stealth.js");
|
|
13
50
|
const providers = [];
|
|
14
51
|
function registerBrowserProvider(provider) {
|
|
15
52
|
providers.push(provider);
|
|
16
53
|
}
|
|
54
|
+
const killers = [];
|
|
55
|
+
function registerBrowserKiller(killer) {
|
|
56
|
+
killers.push(killer);
|
|
57
|
+
}
|
|
58
|
+
async function killBrowser(agentId) {
|
|
59
|
+
for (const killer of killers) {
|
|
60
|
+
try {
|
|
61
|
+
await killer(agentId);
|
|
62
|
+
}
|
|
63
|
+
catch { }
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
fs_1.default.unlinkSync(`/tmp/granclaw-cdp-${agentId}.url`);
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
try {
|
|
70
|
+
const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
71
|
+
const bin = process.env.AGENT_BROWSER_BIN ?? 'agent-browser';
|
|
72
|
+
execFileSync(bin, ['--session', agentId, 'close'], { timeout: 5000, stdio: 'pipe' });
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
}
|
|
17
76
|
function _resetBrowserProvidersForTests() {
|
|
18
77
|
providers.length = 0;
|
|
78
|
+
killers.length = 0;
|
|
19
79
|
}
|
|
20
80
|
function buildArgv(res, command, args) {
|
|
21
81
|
return [...res.preCommandArgs, command, ...args, ...res.postCommandArgs];
|
|
22
82
|
}
|
|
83
|
+
function cdpNavigate(port, url) {
|
|
84
|
+
return new Promise(async (resolve, reject) => {
|
|
85
|
+
const timer = setTimeout(() => reject(new Error('cdpNavigate: timeout')), 15_000);
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetch(`http://127.0.0.1:${port}/json`);
|
|
88
|
+
const targets = (await res.json());
|
|
89
|
+
const page = targets.find(t => t.type === 'page');
|
|
90
|
+
if (!page) {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
reject(new Error('cdpNavigate: no page target'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const ws = new ws_1.WebSocket(page.webSocketDebuggerUrl);
|
|
96
|
+
ws.on('open', () => {
|
|
97
|
+
ws.send(JSON.stringify({ id: 1, method: 'Page.navigate', params: { url } }));
|
|
98
|
+
});
|
|
99
|
+
ws.on('message', (data) => {
|
|
100
|
+
const msg = JSON.parse(data.toString());
|
|
101
|
+
if (msg.id === 1) {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
ws.close();
|
|
104
|
+
if (msg.error)
|
|
105
|
+
reject(new Error(`cdpNavigate: ${msg.error.message}`));
|
|
106
|
+
else
|
|
107
|
+
resolve(url);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
ws.on('error', (err) => { clearTimeout(timer); reject(err); });
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
reject(err);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
23
118
|
async function resolveBrowserBinary(agentId, workspaceDir) {
|
|
24
119
|
for (const provider of providers) {
|
|
25
120
|
const resolution = await provider(agentId, workspaceDir);
|
|
@@ -39,6 +134,7 @@ async function resolveBrowserBinary(agentId, workspaceDir) {
|
|
|
39
134
|
env: {},
|
|
40
135
|
isRemote: false,
|
|
41
136
|
recordingSupported: true,
|
|
137
|
+
cdpPort: port,
|
|
42
138
|
};
|
|
43
139
|
}
|
|
44
140
|
}
|
|
@@ -413,7 +413,7 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
413
413
|
},
|
|
414
414
|
},
|
|
415
415
|
async execute(_toolCallId, params) {
|
|
416
|
-
const apiUrl = process.env.GRANCLAW_API_URL ??
|
|
416
|
+
const apiUrl = process.env.GRANCLAW_API_URL ?? `http://localhost:${process.env.PORT ?? 3001}`;
|
|
417
417
|
const url = new URL(`${apiUrl}/agents/${agent.id}/messages`);
|
|
418
418
|
if (params.contains)
|
|
419
419
|
url.searchParams.set('contains', params.contains);
|
|
@@ -564,7 +564,7 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
564
564
|
});
|
|
565
565
|
extensionFactories.push((pi) => {
|
|
566
566
|
const taskBase = () => {
|
|
567
|
-
const apiUrl = process.env.GRANCLAW_API_URL ??
|
|
567
|
+
const apiUrl = process.env.GRANCLAW_API_URL ?? `http://localhost:${process.env.PORT ?? 3001}`;
|
|
568
568
|
return `${apiUrl}/agents/${agent.id}/tasks`;
|
|
569
569
|
};
|
|
570
570
|
const fetchJson = async (url, init) => {
|
|
@@ -585,7 +585,7 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
585
585
|
parameters: {
|
|
586
586
|
type: 'object',
|
|
587
587
|
properties: {
|
|
588
|
-
status: { type: 'string', enum: ['backlog', 'in_progress', 'scheduled', 'to_review', 'done'], description: 'Filter by status (omit for all tasks)' },
|
|
588
|
+
status: { type: 'string', enum: ['backlog', 'in_progress', 'scheduled', 'to_review', 'done', 'cancelled'], description: 'Filter by status (omit for all tasks)' },
|
|
589
589
|
},
|
|
590
590
|
},
|
|
591
591
|
async execute(_id, params) {
|
|
@@ -788,6 +788,17 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
788
788
|
if (browser.recordingSupported && !browserState.handle.recordingStarted) {
|
|
789
789
|
await (0, session_manager_js_1.startRecording)(browserState.handle, browser);
|
|
790
790
|
}
|
|
791
|
+
if (command === 'open' && browser.cdpPort && args.length > 0) {
|
|
792
|
+
try {
|
|
793
|
+
await (0, browser_bin_js_1.cdpNavigate)(browser.cdpPort, args[0]);
|
|
794
|
+
(0, session_manager_js_1.appendCommand)(browserState.handle, `${command} ${args.join(' ')}`.trim());
|
|
795
|
+
return { content: [{ type: 'text', text: `Navigated to ${args[0]}` }] };
|
|
796
|
+
}
|
|
797
|
+
catch (err) {
|
|
798
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
799
|
+
return { content: [{ type: 'text', text: `browser open failed (CDP navigate): ${msg}` }] };
|
|
800
|
+
}
|
|
801
|
+
}
|
|
791
802
|
const argv = (0, browser_bin_js_1.buildArgv)(browser, command, args);
|
|
792
803
|
try {
|
|
793
804
|
const { stdout, stderr } = await execFileAsync(browser.bin, argv, {
|
|
@@ -827,6 +838,41 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
827
838
|
},
|
|
828
839
|
});
|
|
829
840
|
});
|
|
841
|
+
extensionFactories.push((pi) => {
|
|
842
|
+
pi.registerTool({
|
|
843
|
+
name: 'browser_restart',
|
|
844
|
+
label: 'Restart Browser',
|
|
845
|
+
description: 'Kill the browser process and force a fresh start on the next browser command. ' +
|
|
846
|
+
'Cookies, logins, and profile data are preserved — only the process is restarted. ' +
|
|
847
|
+
'Use when the browser is hung, unresponsive, showing stale state, or after a proxy change.',
|
|
848
|
+
promptSnippet: 'Kill and respawn the browser daemon (cookies survive)',
|
|
849
|
+
promptGuidelines: [
|
|
850
|
+
'Use when browser commands timeout repeatedly or the browser appears hung.',
|
|
851
|
+
'Use after you receive BROWSER_BLOCKED if you want a clean process before retrying.',
|
|
852
|
+
'Cookies and saved logins persist — only the process is restarted.',
|
|
853
|
+
'The next browser command after this will spawn a fresh browser automatically.',
|
|
854
|
+
],
|
|
855
|
+
parameters: { type: 'object', properties: {} },
|
|
856
|
+
async execute() {
|
|
857
|
+
try {
|
|
858
|
+
if (browserState.handle) {
|
|
859
|
+
try {
|
|
860
|
+
await (0, session_manager_js_1.finalizeSession)(browserState.handle, 'closed');
|
|
861
|
+
}
|
|
862
|
+
catch { }
|
|
863
|
+
browserState.handle = null;
|
|
864
|
+
}
|
|
865
|
+
await (0, browser_bin_js_1.killBrowser)(agent.id);
|
|
866
|
+
return { content: [{ type: 'text', text: 'Browser killed. Cookies and logins are preserved. The next browser command will spawn a fresh instance.' }] };
|
|
867
|
+
}
|
|
868
|
+
catch (err) {
|
|
869
|
+
browserState.handle = null;
|
|
870
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
871
|
+
return { content: [{ type: 'text', text: `browser_restart partial: ${msg} — next browser call will still spawn fresh.` }] };
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
});
|
|
830
876
|
extensionFactories.push((pi) => {
|
|
831
877
|
pi.registerTool({
|
|
832
878
|
name: 'request_human_browser_takeover',
|
|
@@ -943,7 +989,7 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
943
989
|
required: ['query'],
|
|
944
990
|
},
|
|
945
991
|
async execute(_toolCallId, params) {
|
|
946
|
-
const apiUrl = process.env.GRANCLAW_API_URL ??
|
|
992
|
+
const apiUrl = process.env.GRANCLAW_API_URL ?? `http://localhost:${process.env.PORT ?? 3001}`;
|
|
947
993
|
const url = `${apiUrl}/search?q=${encodeURIComponent(params.query)}`;
|
|
948
994
|
try {
|
|
949
995
|
const res = await fetch(url);
|
|
@@ -1203,6 +1203,19 @@ function createServer() {
|
|
|
1203
1203
|
}
|
|
1204
1204
|
res.json(run);
|
|
1205
1205
|
});
|
|
1206
|
+
app.post('/agents/:id/workflows/:wfId/runs/:runId/cancel', (req, res) => {
|
|
1207
|
+
const managed = (0, agent_manager_js_1.getManagedAgent)(req.params.id);
|
|
1208
|
+
if (!managed) {
|
|
1209
|
+
res.status(404).json({ error: 'Agent not found' });
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const cancelled = (0, runner_js_1.cancelWorkflowRun)(req.params.id, req.params.runId);
|
|
1213
|
+
if (!cancelled) {
|
|
1214
|
+
res.status(404).json({ error: 'Run not active or not found' });
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
res.json({ ok: true });
|
|
1218
|
+
});
|
|
1206
1219
|
app.get('/agents/:id/schedules', (req, res) => {
|
|
1207
1220
|
const managed = (0, agent_manager_js_1.getManagedAgent)(req.params.id);
|
|
1208
1221
|
if (!managed) {
|
|
@@ -1517,6 +1530,7 @@ function createServer() {
|
|
|
1517
1530
|
(0, loader_js_1.loadExtensions)({
|
|
1518
1531
|
app,
|
|
1519
1532
|
registerBrowserProvider: browser_bin_js_1.registerBrowserProvider,
|
|
1533
|
+
registerBrowserKiller: browser_bin_js_1.registerBrowserKiller,
|
|
1520
1534
|
registerCdpSession: browser_live_js_1.registerExternalCdpSession,
|
|
1521
1535
|
removeCdpSession: browser_live_js_1.removeExternalCdpSession,
|
|
1522
1536
|
registerTakeoverResolvedListener: takeover_listeners_js_1.registerTakeoverResolvedListener,
|
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cancelWorkflowRun = cancelWorkflowRun;
|
|
7
|
+
exports.getActiveRunIds = getActiveRunIds;
|
|
6
8
|
exports.executeWorkflow = executeWorkflow;
|
|
7
9
|
const child_process_1 = require("child_process");
|
|
8
10
|
const util_1 = require("util");
|
|
@@ -15,6 +17,18 @@ const runner_pi_js_1 = require("../agent/runner-pi.js");
|
|
|
15
17
|
const crypto_1 = require("crypto");
|
|
16
18
|
const messages_db_js_1 = require("../messages-db.js");
|
|
17
19
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
20
|
+
const activeRuns = new Map();
|
|
21
|
+
function cancelWorkflowRun(agentId, runId) {
|
|
22
|
+
const key = `${agentId}:${runId}`;
|
|
23
|
+
const ctrl = activeRuns.get(key);
|
|
24
|
+
if (!ctrl)
|
|
25
|
+
return false;
|
|
26
|
+
ctrl.abort();
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
function getActiveRunIds() {
|
|
30
|
+
return Array.from(activeRuns.keys());
|
|
31
|
+
}
|
|
18
32
|
function resolveTemplates(prompt, prevOutput, allResults) {
|
|
19
33
|
let resolved = prompt;
|
|
20
34
|
resolved = resolved.replace(/\{\{prev\.output\}\}/g, prevOutput !== null ? JSON.stringify(prevOutput) : 'null');
|
|
@@ -170,6 +184,9 @@ async function executeWorkflow(agentId, workflowId, trigger) {
|
|
|
170
184
|
const workspaceDir = path_1.default.resolve(config_js_1.REPO_ROOT, agent.workspaceDir);
|
|
171
185
|
const run = (0, workflows_db_js_1.createRun)(agentId, workflowId, trigger);
|
|
172
186
|
const allResults = [];
|
|
187
|
+
const abortCtrl = new AbortController();
|
|
188
|
+
const runKey = `${agentId}:${run.id}`;
|
|
189
|
+
activeRuns.set(runKey, abortCtrl);
|
|
173
190
|
const runStepMap = new Map();
|
|
174
191
|
for (const step of workflow.steps) {
|
|
175
192
|
const runStep = (0, workflows_db_js_1.createRunStep)(agentId, { runId: run.id, stepId: step.id });
|
|
@@ -178,6 +195,17 @@ async function executeWorkflow(agentId, workflowId, trigger) {
|
|
|
178
195
|
let currentStep = workflow.steps[0];
|
|
179
196
|
let prevOutput = null;
|
|
180
197
|
while (currentStep) {
|
|
198
|
+
if (abortCtrl.signal.aborted) {
|
|
199
|
+
for (const step of workflow.steps) {
|
|
200
|
+
const rsId = runStepMap.get(step.id);
|
|
201
|
+
if (!allResults.some(r => r.stepId === step.id)) {
|
|
202
|
+
(0, workflows_db_js_1.updateRunStep)(agentId, rsId, { status: 'skipped' });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
(0, workflows_db_js_1.updateRun)(agentId, run.id, { status: 'cancelled', finishedAt: Date.now() });
|
|
206
|
+
activeRuns.delete(runKey);
|
|
207
|
+
return run.id;
|
|
208
|
+
}
|
|
181
209
|
const runStepId = runStepMap.get(currentStep.id);
|
|
182
210
|
const startedAt = Date.now();
|
|
183
211
|
(0, workflows_db_js_1.updateRunStep)(agentId, runStepId, { status: 'running', startedAt });
|
|
@@ -266,12 +294,14 @@ async function executeWorkflow(agentId, workflowId, trigger) {
|
|
|
266
294
|
}
|
|
267
295
|
}
|
|
268
296
|
(0, workflows_db_js_1.updateRun)(agentId, run.id, { status: 'failed', finishedAt });
|
|
297
|
+
activeRuns.delete(runKey);
|
|
269
298
|
console.error(`[workflow-runner] step "${currentStep?.name}" failed:`, message);
|
|
270
299
|
const failSummary = `**Workflow "${workflow.name}" failed** at step "${currentStep?.name}"\n\nError: ${message}`;
|
|
271
300
|
(0, messages_db_js_1.saveMessage)({ id: (0, crypto_1.randomUUID)(), agentId, channelId: 'ui', role: 'assistant', content: failSummary });
|
|
272
301
|
return run.id;
|
|
273
302
|
}
|
|
274
303
|
}
|
|
304
|
+
activeRuns.delete(runKey);
|
|
275
305
|
(0, workflows_db_js_1.updateRun)(agentId, run.id, { status: 'completed', finishedAt: Date.now() });
|
|
276
306
|
const lastOutput = allResults[allResults.length - 1];
|
|
277
307
|
const outputPreview = typeof lastOutput?.output === 'string'
|