granclaw 0.0.1-beta.96 → 0.0.1-beta.97

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.
@@ -6,9 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.registerBrowserProvider = registerBrowserProvider;
7
7
  exports._resetBrowserProvidersForTests = _resetBrowserProvidersForTests;
8
8
  exports.buildArgv = buildArgv;
9
+ exports.cdpNavigate = cdpNavigate;
9
10
  exports.resolveBrowserBinary = resolveBrowserBinary;
10
11
  const fs_1 = __importDefault(require("fs"));
11
12
  const path_1 = __importDefault(require("path"));
13
+ const ws_1 = require("ws");
12
14
  const stealth_js_1 = require("../browser/stealth.js");
13
15
  const providers = [];
14
16
  function registerBrowserProvider(provider) {
@@ -20,6 +22,41 @@ function _resetBrowserProvidersForTests() {
20
22
  function buildArgv(res, command, args) {
21
23
  return [...res.preCommandArgs, command, ...args, ...res.postCommandArgs];
22
24
  }
25
+ function cdpNavigate(port, url) {
26
+ return new Promise(async (resolve, reject) => {
27
+ const timer = setTimeout(() => reject(new Error('cdpNavigate: timeout')), 15_000);
28
+ try {
29
+ const res = await fetch(`http://127.0.0.1:${port}/json`);
30
+ const targets = (await res.json());
31
+ const page = targets.find(t => t.type === 'page');
32
+ if (!page) {
33
+ clearTimeout(timer);
34
+ reject(new Error('cdpNavigate: no page target'));
35
+ return;
36
+ }
37
+ const ws = new ws_1.WebSocket(page.webSocketDebuggerUrl);
38
+ ws.on('open', () => {
39
+ ws.send(JSON.stringify({ id: 1, method: 'Page.navigate', params: { url } }));
40
+ });
41
+ ws.on('message', (data) => {
42
+ const msg = JSON.parse(data.toString());
43
+ if (msg.id === 1) {
44
+ clearTimeout(timer);
45
+ ws.close();
46
+ if (msg.error)
47
+ reject(new Error(`cdpNavigate: ${msg.error.message}`));
48
+ else
49
+ resolve(url);
50
+ }
51
+ });
52
+ ws.on('error', (err) => { clearTimeout(timer); reject(err); });
53
+ }
54
+ catch (err) {
55
+ clearTimeout(timer);
56
+ reject(err);
57
+ }
58
+ });
59
+ }
23
60
  async function resolveBrowserBinary(agentId, workspaceDir) {
24
61
  for (const provider of providers) {
25
62
  const resolution = await provider(agentId, workspaceDir);
@@ -39,6 +76,7 @@ async function resolveBrowserBinary(agentId, workspaceDir) {
39
76
  env: {},
40
77
  isRemote: false,
41
78
  recordingSupported: true,
79
+ cdpPort: port,
42
80
  };
43
81
  }
44
82
  }
@@ -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, {
@@ -943,7 +954,7 @@ async function runAgent(agent, message, onChunk, options) {
943
954
  required: ['query'],
944
955
  },
945
956
  async execute(_toolCallId, params) {
946
- const apiUrl = process.env.GRANCLAW_API_URL ?? 'http://localhost:3001';
957
+ const apiUrl = process.env.GRANCLAW_API_URL ?? `http://localhost:${process.env.PORT ?? 3001}`;
947
958
  const url = `${apiUrl}/search?q=${encodeURIComponent(params.query)}`;
948
959
  try {
949
960
  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) {
@@ -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'