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.
@@ -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 ?? 'http://localhost:3001';
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 ?? 'http://localhost:3001';
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 ?? 'http://localhost:3001';
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'