granclaw 0.0.1-beta.25 → 0.0.1-beta.26

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.
@@ -28,6 +28,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
28
28
  exports.STEALTH_EXTENSION_DIR = void 0;
29
29
  exports.stealthArgv = stealthArgv;
30
30
  exports.injectStealthViaCdp = injectStealthViaCdp;
31
+ exports.prewarmStealthDaemon = prewarmStealthDaemon;
31
32
  exports.__resetStealthCacheForTests = __resetStealthCacheForTests;
32
33
  const fs_1 = __importDefault(require("fs"));
33
34
  const path_1 = __importDefault(require("path"));
@@ -122,23 +123,26 @@ function stealthArgv() {
122
123
  }
123
124
  // ── CDP stealth injection ─────────────────────────────────────────────────────
124
125
  /**
125
- * Poll for the browser-level CDP WebSocket URL. Returns null if the daemon
126
- * isn't running or doesn't become ready within ~2.4 s.
126
+ * Poll for the browser-level CDP WebSocket URL.
127
+ *
128
+ * @param retries How many attempts to make (default 8, ~2.4 s). Pass 1 for
129
+ * a fast single-shot check used by the background watcher.
127
130
  */
128
- async function discoverCdpUrl(sessionId, workspaceDir) {
131
+ async function discoverCdpUrl(sessionId, workspaceDir, retries = 8) {
129
132
  const bin = process.env.AGENT_BROWSER_BIN ?? 'agent-browser';
130
- for (let i = 0; i < 8; i++) {
133
+ for (let i = 0; i < retries; i++) {
131
134
  try {
132
135
  const { stdout } = await execFileAsync(bin, ['--session', sessionId, 'get', 'cdp-url'], {
133
136
  cwd: workspaceDir,
134
- timeout: 3000,
137
+ timeout: 2000,
135
138
  });
136
139
  const url = stdout.trim();
137
140
  if (url.startsWith('ws://') || url.startsWith('wss://'))
138
141
  return url;
139
142
  }
140
143
  catch { /* daemon not ready yet */ }
141
- await new Promise((r) => setTimeout(r, 300));
144
+ if (i < retries - 1)
145
+ await new Promise((r) => setTimeout(r, 300));
142
146
  }
143
147
  return null;
144
148
  }
@@ -233,6 +237,40 @@ async function injectStealthViaCdp(sessionId, workspaceDir) {
233
237
  console.warn(`[stealth] CDP injection failed for "${sessionId}":`, err);
234
238
  }
235
239
  }
240
+ // ── Daemon pre-warm ───────────────────────────────────────────────────────────
241
+ /**
242
+ * Pre-warm the agent's browser daemon and register stealth before any
243
+ * agent navigation. Call this once at agent process startup for agents
244
+ * that have the browser tool enabled.
245
+ *
246
+ * Steps:
247
+ * 1. Start the daemon with `agent-browser open about:blank` (no-op if
248
+ * already running — agent-browser reuses the existing daemon).
249
+ * 2. Inject Page.addScriptToEvaluateOnNewDocument via CDP so every
250
+ * subsequent navigation the agent makes will have stealth running
251
+ * before the page's own scripts.
252
+ *
253
+ * This is a one-time setup. If the agent later kills its daemon via
254
+ * `browser close --all`, the next daemon start won't have stealth until
255
+ * the live view relay re-attaches (which also injects stealth).
256
+ */
257
+ async function prewarmStealthDaemon(sessionId, workspaceDir) {
258
+ if (process.env.GRANCLAW_STEALTH_DISABLED === '1')
259
+ return;
260
+ try {
261
+ const bin = process.env.AGENT_BROWSER_BIN ?? 'agent-browser';
262
+ // Boot the daemon (or no-op if already running) and land on about:blank.
263
+ // Use execFileAsync with a short timeout — we don't care about the result,
264
+ // only that the daemon is now up.
265
+ await execFileAsync(bin, ['--session', sessionId, ...stealthArgv(), 'open', 'about:blank'], { cwd: workspaceDir, timeout: 15000 });
266
+ }
267
+ catch {
268
+ // Daemon failed to start (no Chrome, wrong env, etc.) — skip silently.
269
+ return;
270
+ }
271
+ // Daemon is up. Register stealth for all future navigations.
272
+ await injectStealthViaCdp(sessionId, workspaceDir);
273
+ }
236
274
  // ── Test helpers ──────────────────────────────────────────────────────────────
237
275
  /** @internal */
238
276
  function __resetStealthCacheForTests() {
@@ -25,6 +25,7 @@ exports.stopAndRemoveAgent = stopAndRemoveAgent;
25
25
  const child_process_1 = require("child_process");
26
26
  const path_1 = __importDefault(require("path"));
27
27
  const config_js_1 = require("../config.js");
28
+ const stealth_js_1 = require("../browser/stealth.js");
28
29
  const secrets_vault_js_1 = require("../secrets-vault.js");
29
30
  exports.BASE_AGENT_PORT = Number(process.env.AGENT_BASE_PORT ?? 3100);
30
31
  /**
@@ -87,6 +88,13 @@ function startNewAgent(agent) {
87
88
  const managed = { config: agent, wsPort, bbPort: null, pid: child.pid };
88
89
  registry.set(agent.id, managed);
89
90
  console.log(`[orchestrator] agent "${agent.id}" started on ws port ${wsPort} (pid ${child.pid})`);
91
+ // Pre-warm the browser daemon for browser-capable agents so stealth is
92
+ // registered via Page.addScriptToEvaluateOnNewDocument before the agent's
93
+ // first navigation. Fire-and-forget — never blocks agent startup.
94
+ if (agent.allowedTools?.includes('browser')) {
95
+ const workspaceDir = path_1.default.resolve(config_js_1.REPO_ROOT, agent.workspaceDir);
96
+ void (0, stealth_js_1.prewarmStealthDaemon)(agent.id, workspaceDir);
97
+ }
90
98
  return managed;
91
99
  }
92
100
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granclaw",
3
- "version": "0.0.1-beta.25",
3
+ "version": "0.0.1-beta.26",
4
4
  "description": "A personal AI assistant you run on your own machine.",
5
5
  "license": "MIT",
6
6
  "repository": {