tuna-agent 0.1.93 → 0.1.95

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.
@@ -711,6 +711,8 @@ export declare class BrowserClaw {
711
711
  page(targetId: string): CrawlPage;
712
712
  /** The CDP endpoint URL for this browser connection. */
713
713
  get url(): string;
714
+ /** PID of the Chrome process (null if connected to external Chrome). */
715
+ get chromePid(): number | null;
714
716
  /**
715
717
  * Stop the browser and clean up all resources.
716
718
  *
@@ -1049,6 +1049,10 @@ export class BrowserClaw {
1049
1049
  get url() {
1050
1050
  return this.cdpUrl;
1051
1051
  }
1052
+ /** PID of the Chrome process (null if connected to external Chrome). */
1053
+ get chromePid() {
1054
+ return this.chrome?.pid ?? null;
1055
+ }
1052
1056
  /**
1053
1057
  * Stop the browser and clean up all resources.
1054
1058
  *
@@ -36,6 +36,7 @@ function parseArgs() {
36
36
  }
37
37
  // ===== Browser State =====
38
38
  let browser = null;
39
+ let chromePid = null; // Track Chrome PID for synchronous cleanup
39
40
  const pages = new Map(); // targetId → CrawlPage
40
41
  async function ensureBrowser(config) {
41
42
  if (browser)
@@ -51,6 +52,7 @@ async function ensureBrowser(config) {
51
52
  cdpPort: config.cdpPort,
52
53
  });
53
54
  }
55
+ chromePid = browser.chromePid;
54
56
  return browser;
55
57
  }
56
58
  function sendResponse(res) {
@@ -406,6 +408,7 @@ async function handleToolCall(toolName, args) {
406
408
  if (browser) {
407
409
  await browser.stop();
408
410
  browser = null;
411
+ chromePid = null;
409
412
  pages.clear();
410
413
  activePageId = null;
411
414
  }
@@ -448,20 +451,39 @@ function startServer() {
448
451
  process.stderr.write('[browser-mcp] stdin closed, cleaning up browser\n');
449
452
  if (browser) {
450
453
  await browser.stop().catch(() => { });
454
+ browser = null;
455
+ chromePid = null;
451
456
  }
452
457
  process.exit(0);
453
458
  });
454
459
  // Cleanup on process exit
455
460
  process.on('SIGTERM', async () => {
456
- if (browser)
461
+ if (browser) {
457
462
  await browser.stop().catch(() => { });
463
+ browser = null;
464
+ chromePid = null;
465
+ }
458
466
  process.exit(0);
459
467
  });
460
468
  process.on('SIGINT', async () => {
461
- if (browser)
469
+ if (browser) {
462
470
  await browser.stop().catch(() => { });
471
+ browser = null;
472
+ chromePid = null;
473
+ }
463
474
  process.exit(0);
464
475
  });
476
+ // Synchronous safety net: force kill Chrome if async cleanup didn't run
477
+ // This handles cases where process exits unexpectedly (crash, SIGKILL parent, etc.)
478
+ process.on('exit', () => {
479
+ if (chromePid) {
480
+ try {
481
+ process.kill(chromePid, 'SIGKILL');
482
+ }
483
+ catch { }
484
+ chromePid = null;
485
+ }
486
+ });
465
487
  }
466
488
  // ===== Main =====
467
489
  startServer();
@@ -1,6 +1,28 @@
1
- import { spawn, execSync } from 'child_process';
1
+ import { spawn, execSync, spawnSync } from 'child_process';
2
2
  import { StringDecoder } from 'string_decoder';
3
3
  import { getMcpConfigPath } from '../mcp/setup.js';
4
+ /**
5
+ * Kill any orphaned Chrome processes launched by tuna-browser.
6
+ * Matches Chrome processes using --user-data-dir containing 'tuna-browser'.
7
+ */
8
+ function killOrphanedTunaBrowserChrome() {
9
+ try {
10
+ // Find Chrome main process PIDs with tuna-browser profile
11
+ const result = spawnSync('pgrep', ['-f', 'remote-debugging-port.*tuna-browser'], {
12
+ encoding: 'utf-8',
13
+ timeout: 3000,
14
+ });
15
+ const pids = (result.stdout || '').trim().split('\n').filter(Boolean);
16
+ for (const pid of pids) {
17
+ try {
18
+ process.kill(Number(pid), 'SIGTERM');
19
+ console.log(`[claude-cli] Killed orphaned tuna-browser Chrome (PID: ${pid})`);
20
+ }
21
+ catch { }
22
+ }
23
+ }
24
+ catch { }
25
+ }
4
26
  /** Cached absolute path to `claude` binary, resolved once at first use */
5
27
  let _claudeBinPath = null;
6
28
  function getClaudeBinPath() {
@@ -227,6 +249,8 @@ export function runClaude(options) {
227
249
  clearTimeout(timeoutTimer);
228
250
  const totalMs = Date.now() - spawnTime;
229
251
  console.log(`[claude-cli] ⏱ process closed: ${totalMs}ms total (first stdout: ${firstStdoutTime ? firstStdoutTime - spawnTime : '?'}ms, first stream: ${firstStreamEventTime ? firstStreamEventTime - spawnTime : 'none'}ms)`);
252
+ // Kill orphaned Chrome after Claude session ends (browser MCP cleanup may have failed)
253
+ killOrphanedTunaBrowserChrome();
230
254
  // Flush remaining buffer
231
255
  if (options.onStreamLine && buffer.trim()) {
232
256
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.93",
3
+ "version": "0.1.95",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"