teamshare-bridge 0.21.76 → 0.21.78
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/bridge/daemon-inbox.js.map +1 -1
- package/dist/bridge/daemon.js +150 -20
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/local-server.js +6 -1
- package/dist/bridge/local-server.js.map +1 -1
- package/dist/bridge/spawn.d.ts +19 -2
- package/dist/bridge/spawn.js +17 -4
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/bridge/wake-handler.d.ts +1 -0
- package/dist/bridge/wake-handler.js +253 -100
- package/dist/bridge/wake-handler.js.map +1 -1
- package/dist/bridge/wake-queue-store.d.ts +20 -0
- package/dist/bridge/wake-queue-store.js +107 -0
- package/dist/bridge/wake-queue-store.js.map +1 -0
- package/dist/bridge/wake-routing.d.ts +60 -6
- package/dist/bridge/wake-routing.js +155 -6
- package/dist/bridge/wake-routing.js.map +1 -1
- package/dist/cli/commands/board.js +7 -0
- package/dist/cli/commands/board.js.map +1 -1
- package/dist/cli/commands/browse.js +5 -1
- package/dist/cli/commands/browse.js.map +1 -1
- package/dist/cli/commands/browser.js +53 -77
- package/dist/cli/commands/browser.js.map +1 -1
- package/dist/cli/commands/build.js +4 -2
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/chat.js +3 -1
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/cleanup.js +4 -2
- package/dist/cli/commands/cleanup.js.map +1 -1
- package/dist/cli/commands/doc.js +8 -0
- package/dist/cli/commands/doc.js.map +1 -1
- package/dist/cli/commands/plan.js +6 -3
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/run.js +38 -17
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/worker.js +4 -2
- package/dist/cli/commands/worker.js.map +1 -1
- package/dist/cli/index.js +5 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/api-agents.d.ts +27 -0
- package/dist/lib/api-agents.js +39 -0
- package/dist/lib/api-agents.js.map +1 -1
- package/dist/lib/api-content.d.ts +1 -1
- package/dist/lib/api-content.js +7 -1
- package/dist/lib/api-content.js.map +1 -1
- package/dist/lib/api-types.d.ts +18 -0
- package/dist/lib/brief.d.ts +8 -0
- package/dist/lib/brief.js +49 -0
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/browser/launch.d.ts +20 -7
- package/dist/lib/browser/launch.js +21 -9
- package/dist/lib/browser/launch.js.map +1 -1
- package/dist/lib/browser/manager.d.ts +43 -9
- package/dist/lib/browser/manager.js +64 -11
- package/dist/lib/browser/manager.js.map +1 -1
- package/dist/lib/browser/owned-registry.d.ts +19 -0
- package/dist/lib/browser/owned-registry.js +121 -0
- package/dist/lib/browser/owned-registry.js.map +1 -0
- package/dist/lib/browser/process.d.ts +14 -0
- package/dist/lib/browser/process.js +100 -0
- package/dist/lib/browser/process.js.map +1 -0
- package/dist/lib/browser/session.d.ts +74 -15
- package/dist/lib/browser/session.js +134 -32
- package/dist/lib/browser/session.js.map +1 -1
- package/dist/lib/cache-cleaner.d.ts +36 -0
- package/dist/lib/cache-cleaner.js +101 -5
- package/dist/lib/cache-cleaner.js.map +1 -1
- package/dist/lib/chat/chat-reply.d.ts +3 -2
- package/dist/lib/chat/chat-reply.js +117 -9
- package/dist/lib/chat/chat-reply.js.map +1 -1
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/orchestrator/client.js +13 -2
- package/dist/lib/orchestrator/client.js.map +1 -1
- package/dist/lib/orchestrator/handlers/browser.js +35 -8
- package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
- package/dist/lib/tools/browser-tool.js +19 -5
- package/dist/lib/tools/browser-tool.js.map +1 -1
- package/dist/lib/workspace/mirror-lease.d.ts +62 -0
- package/dist/lib/workspace/mirror-lease.js +183 -0
- package/dist/lib/workspace/mirror-lease.js.map +1 -0
- package/dist/lib/workspace/mirror.d.ts +76 -1
- package/dist/lib/workspace/mirror.js +296 -13
- package/dist/lib/workspace/mirror.js.map +1 -1
- package/dist/lib/workspace/session-mention.d.ts +31 -0
- package/dist/lib/workspace/session-mention.js +30 -0
- package/dist/lib/workspace/session-mention.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface OwnedBrowserRecord {
|
|
2
|
+
port: number;
|
|
3
|
+
profileDir: string;
|
|
4
|
+
startedAt: string;
|
|
5
|
+
}
|
|
6
|
+
/** Called the moment this process launches a debug Chrome. */
|
|
7
|
+
export declare function recordOwnedBrowser(port: number, profileDir: string): void;
|
|
8
|
+
/** Forget a port: closed by us, or already gone. */
|
|
9
|
+
export declare function releaseOwnedBrowser(port: number): void;
|
|
10
|
+
/** Every port this machine's bridge has ever launched (may include dead ones). */
|
|
11
|
+
export declare function listOwnedBrowsers(): OwnedBrowserRecord[];
|
|
12
|
+
/**
|
|
13
|
+
* Boot-time reclaim: close every recorded browser that is still listening.
|
|
14
|
+
* Returns the ports actually closed. Records for ports that are already gone
|
|
15
|
+
* are dropped, so the file does not grow forever.
|
|
16
|
+
*/
|
|
17
|
+
export declare function reclaimOrphanedBrowsers(log?: (message: string) => void): Promise<number>;
|
|
18
|
+
/** True when the registry file exists (used by `doctor` to explain a window). */
|
|
19
|
+
export declare function ownedBrowserRegistryExists(): boolean;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recordOwnedBrowser = recordOwnedBrowser;
|
|
4
|
+
exports.releaseOwnedBrowser = releaseOwnedBrowser;
|
|
5
|
+
exports.listOwnedBrowsers = listOwnedBrowsers;
|
|
6
|
+
exports.reclaimOrphanedBrowsers = reclaimOrphanedBrowsers;
|
|
7
|
+
exports.ownedBrowserRegistryExists = ownedBrowserRegistryExists;
|
|
8
|
+
/**
|
|
9
|
+
* Registry of debug browsers THIS bridge machine launched.
|
|
10
|
+
*
|
|
11
|
+
* Why this exists (2026-09): ownership of a debug browser lives in memory, on
|
|
12
|
+
* the `BrowserSession` that launched it. That works while the daemon keeps
|
|
13
|
+
* running, but the daemon now disconnects (never kills) its browsers on
|
|
14
|
+
* shutdown - so after a restart/upgrade nothing owns them any more and the
|
|
15
|
+
* idle sweep can never reclaim one. A bridge-launched Chrome would then sit
|
|
16
|
+
* open until the human closed it, which is exactly the leak the ownership
|
|
17
|
+
* model was built to prevent.
|
|
18
|
+
*
|
|
19
|
+
* So ownership is ALSO written down here, on disk, and reclaimed at boot:
|
|
20
|
+
*
|
|
21
|
+
* - `recordOwned(port, profileDir)` when a session launches a Chrome,
|
|
22
|
+
* - `releaseOwned(port)` when it is closed (or found already gone),
|
|
23
|
+
* - `reclaimOrphaned()` at daemon start: every recorded port that is still
|
|
24
|
+
* listening is bridge-owned by definition, so it is closed.
|
|
25
|
+
*
|
|
26
|
+
* Only ports the bridge itself opened are ever recorded. A Chrome the human
|
|
27
|
+
* started is attached, never recorded, and therefore never reclaimed.
|
|
28
|
+
*/
|
|
29
|
+
const node_fs_1 = require("node:fs");
|
|
30
|
+
const node_os_1 = require("node:os");
|
|
31
|
+
const node_path_1 = require("node:path");
|
|
32
|
+
const cdp_1 = require("./cdp");
|
|
33
|
+
const process_1 = require("./process");
|
|
34
|
+
const REGISTRY_PATH = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'debug-browsers.json');
|
|
35
|
+
function readAll() {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(REGISTRY_PATH, 'utf8'));
|
|
38
|
+
if (!Array.isArray(parsed))
|
|
39
|
+
return [];
|
|
40
|
+
return parsed.filter((row) => !!row &&
|
|
41
|
+
typeof row === 'object' &&
|
|
42
|
+
typeof row.port === 'number' &&
|
|
43
|
+
Number.isInteger(row.port));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function writeAll(rows) {
|
|
50
|
+
try {
|
|
51
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare'), { recursive: true });
|
|
52
|
+
// Sorted + deduped so the file stays stable across repeated writes.
|
|
53
|
+
const clean = [...new Map(rows.map((r) => [r.port, r])).values()].sort((a, b) => a.port - b.port);
|
|
54
|
+
if (clean.length === 0) {
|
|
55
|
+
(0, node_fs_1.rmSync)(REGISTRY_PATH, { force: true });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
(0, node_fs_1.writeFileSync)(REGISTRY_PATH, JSON.stringify(clean, null, 2), 'utf8');
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* best-effort: an unwritable registry only costs a reclaim */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Called the moment this process launches a debug Chrome. */
|
|
65
|
+
function recordOwnedBrowser(port, profileDir) {
|
|
66
|
+
const rows = readAll().filter((r) => r.port !== port);
|
|
67
|
+
rows.push({ port, profileDir, startedAt: new Date().toISOString() });
|
|
68
|
+
writeAll(rows);
|
|
69
|
+
}
|
|
70
|
+
/** Forget a port: closed by us, or already gone. */
|
|
71
|
+
function releaseOwnedBrowser(port) {
|
|
72
|
+
const rows = readAll();
|
|
73
|
+
if (!rows.some((r) => r.port === port))
|
|
74
|
+
return;
|
|
75
|
+
writeAll(rows.filter((r) => r.port !== port));
|
|
76
|
+
}
|
|
77
|
+
/** Every port this machine's bridge has ever launched (may include dead ones). */
|
|
78
|
+
function listOwnedBrowsers() {
|
|
79
|
+
return readAll();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Boot-time reclaim: close every recorded browser that is still listening.
|
|
83
|
+
* Returns the ports actually closed. Records for ports that are already gone
|
|
84
|
+
* are dropped, so the file does not grow forever.
|
|
85
|
+
*/
|
|
86
|
+
async function reclaimOrphanedBrowsers(log = (m) => console.log(`[browser] ${m}`)) {
|
|
87
|
+
const rows = readAll();
|
|
88
|
+
if (rows.length === 0)
|
|
89
|
+
return 0;
|
|
90
|
+
let closed = 0;
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
let reachable = false;
|
|
93
|
+
try {
|
|
94
|
+
reachable = await (0, cdp_1.isChromeReachable)(row.port);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
reachable = false;
|
|
98
|
+
}
|
|
99
|
+
if (!reachable) {
|
|
100
|
+
releaseOwnedBrowser(row.port);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const pid = (0, process_1.findListenPid)(row.port);
|
|
104
|
+
if (pid) {
|
|
105
|
+
(0, process_1.killChromeOnPort)(row.port, (m) => log(m));
|
|
106
|
+
closed++;
|
|
107
|
+
log(`reclaimed an orphaned debug browser on port ${row.port} (pid ${pid}, ` +
|
|
108
|
+
`launched ${row.startedAt}) - it belonged to a previous daemon instance`);
|
|
109
|
+
}
|
|
110
|
+
releaseOwnedBrowser(row.port);
|
|
111
|
+
}
|
|
112
|
+
if (closed > 0) {
|
|
113
|
+
log(`${closed} orphaned debug browser(s) reclaimed at startup`);
|
|
114
|
+
}
|
|
115
|
+
return closed;
|
|
116
|
+
}
|
|
117
|
+
/** True when the registry file exists (used by `doctor` to explain a window). */
|
|
118
|
+
function ownedBrowserRegistryExists() {
|
|
119
|
+
return (0, node_fs_1.existsSync)(REGISTRY_PATH);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=owned-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owned-registry.js","sourceRoot":"","sources":["../../../src/lib/browser/owned-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** PID listening on a local TCP port (a Chrome main process), or null. */
|
|
2
|
+
export declare function findListenPid(port: number): number | null;
|
|
3
|
+
/**
|
|
4
|
+
* Close the browser listening on `port`, and report whether the port went
|
|
5
|
+
* quiet. Graceful first, then forced, then verified.
|
|
6
|
+
*
|
|
7
|
+
* `excludeImages` defaults to the browser-process images so a stray kill can
|
|
8
|
+
* never take an unrelated browser (or the auto-answer one) with it. The
|
|
9
|
+
* human-facing `browser stop` passes an empty list because there the human
|
|
10
|
+
* explicitly asked for THIS port to close.
|
|
11
|
+
*/
|
|
12
|
+
export declare function killChromeOnPort(port: number, log?: (message: string) => void, options?: {
|
|
13
|
+
excludeImages?: string[];
|
|
14
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findListenPid = findListenPid;
|
|
4
|
+
exports.killChromeOnPort = killChromeOnPort;
|
|
5
|
+
/**
|
|
6
|
+
* Local process helpers for closing a Chrome that is listening on a CDP port.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from `cli/commands/browser.ts` (2026-09) so the debug browser's
|
|
9
|
+
* OWNED close path (`BrowserSession.closeOwned`) uses the exact same verified
|
|
10
|
+
* routine the human-facing `browser stop` does. Before this, closing a
|
|
11
|
+
* bridge-launched browser meant a bare `child.kill('SIGTERM')` against the
|
|
12
|
+
* detached launcher - which on Windows kills the launcher process and can
|
|
13
|
+
* leave the real browser running, so "closed" was sometimes a no-op nobody
|
|
14
|
+
* could see.
|
|
15
|
+
*
|
|
16
|
+
* The routine is deliberately conservative: it only ever acts on the pid that
|
|
17
|
+
* owns the LISTENING SOCKET on the port we launched, never on a process tree
|
|
18
|
+
* discovered by name, and it degrades to a root-only kill when the tree cannot
|
|
19
|
+
* be enumerated.
|
|
20
|
+
*/
|
|
21
|
+
const node_child_process_1 = require("node:child_process");
|
|
22
|
+
const process_tree_1 = require("../process-tree");
|
|
23
|
+
/** PID listening on a local TCP port (a Chrome main process), or null. */
|
|
24
|
+
function findListenPid(port) {
|
|
25
|
+
if (process.platform === 'win32') {
|
|
26
|
+
const ps = (0, node_child_process_1.spawnSync)('powershell', [
|
|
27
|
+
'-NoProfile',
|
|
28
|
+
'-NonInteractive',
|
|
29
|
+
'-Command',
|
|
30
|
+
`Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | ` +
|
|
31
|
+
'Select-Object -ExpandProperty OwningProcess -First 1',
|
|
32
|
+
], { encoding: 'utf8', timeout: 15_000, windowsHide: true });
|
|
33
|
+
const pid = Number((ps.stdout ?? '').trim());
|
|
34
|
+
if (Number.isInteger(pid) && pid > 0)
|
|
35
|
+
return pid;
|
|
36
|
+
const netstat = (0, node_child_process_1.spawnSync)('netstat', ['-ano'], {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
timeout: 15_000,
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
41
|
+
});
|
|
42
|
+
for (const line of (netstat.stdout ?? '').split(/\r?\n/)) {
|
|
43
|
+
if (!line.includes('LISTENING'))
|
|
44
|
+
continue;
|
|
45
|
+
const cols = line.trim().split(/\s+/);
|
|
46
|
+
const local = cols[1] ?? '';
|
|
47
|
+
if (!local.endsWith(`:${port}`))
|
|
48
|
+
continue;
|
|
49
|
+
const pid = Number(cols[cols.length - 1]);
|
|
50
|
+
if (Number.isInteger(pid) && pid > 0)
|
|
51
|
+
return pid;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const lsof = (0, node_child_process_1.spawnSync)('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN'], {
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
timeout: 15_000,
|
|
58
|
+
});
|
|
59
|
+
const pid = Number((lsof.stdout ?? '').trim().split(/\s+/)[0]);
|
|
60
|
+
if (Number.isInteger(pid) && pid > 0)
|
|
61
|
+
return pid;
|
|
62
|
+
const fuser = (0, node_child_process_1.spawnSync)('fuser', ['-n', 'tcp', String(port)], { encoding: 'utf8', timeout: 15_000 });
|
|
63
|
+
const fpid = Number((fuser.stdout ?? '').trim().split(/\s+/).pop());
|
|
64
|
+
return Number.isInteger(fpid) && fpid > 0 ? fpid : null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Close the browser listening on `port`, and report whether the port went
|
|
68
|
+
* quiet. Graceful first, then forced, then verified.
|
|
69
|
+
*
|
|
70
|
+
* `excludeImages` defaults to the browser-process images so a stray kill can
|
|
71
|
+
* never take an unrelated browser (or the auto-answer one) with it. The
|
|
72
|
+
* human-facing `browser stop` passes an empty list because there the human
|
|
73
|
+
* explicitly asked for THIS port to close.
|
|
74
|
+
*/
|
|
75
|
+
async function killChromeOnPort(port, log = (m) => console.log(`[browser] ${m}`), options = {}) {
|
|
76
|
+
const pid = findListenPid(port);
|
|
77
|
+
if (!pid) {
|
|
78
|
+
log(`no process is listening on port ${port} - nothing to close`);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
log(`closing the browser on port ${port} (pid ${pid})`);
|
|
82
|
+
(0, process_tree_1.killTreeExcept)(pid, {
|
|
83
|
+
excludeImages: options.excludeImages ?? ['chrome.exe', 'msedge.exe', 'chromium', 'brave'],
|
|
84
|
+
graceMs: 2_000,
|
|
85
|
+
log,
|
|
86
|
+
});
|
|
87
|
+
// Verify: a kill that "succeeded" but left the socket listening is a failure
|
|
88
|
+
// the caller must be able to see (and report) rather than assume.
|
|
89
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
90
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
91
|
+
if (findListenPid(port) === null) {
|
|
92
|
+
log(`port ${port} is closed`);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
log(`port ${port} is still listening after the close - a Chrome restart may have ` +
|
|
97
|
+
'taken it over, or the process refused to die. Close the window yourself.');
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../src/lib/browser/process.ts"],"names":[],"mappings":";;AAoBA,sCA2CC;AAWD,4CA8BC;AAxGD;;;;;;;;;;;;;;;GAeG;AACH,2DAA+C;AAC/C,kDAAiD;AAEjD,0EAA0E;AAC1E,SAAgB,aAAa,CAAC,IAAY;IACxC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,EAAE,GAAG,IAAA,8BAAS,EAClB,YAAY,EACZ;YACE,YAAY;YACZ,iBAAiB;YACjB,UAAU;YACV,mCAAmC,IAAI,iDAAiD;gBACtF,sDAAsD;SACzD,EACD,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,CACzD,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QAEjD,MAAM,OAAO,GAAG,IAAA,8BAAS,EAAC,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE;YAC7C,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,IAAI;YACjB,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;SAC3B,CAAC,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAAE,SAAS;YAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;gBAAE,SAAS;YAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YAC1C,IAAI,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,IAAA,8BAAS,EAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,IAAI,EAAE,EAAE,cAAc,CAAC,EAAE;QACrE,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IAEjD,MAAM,KAAK,GAAG,IAAA,8BAAS,EAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACrG,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACpE,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,gBAAgB,CACpC,IAAY,EACZ,MAAiC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,EACrE,UAAwC,EAAE;IAE1C,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,CAAC,mCAAmC,IAAI,qBAAqB,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,GAAG,CAAC,+BAA+B,IAAI,SAAS,GAAG,GAAG,CAAC,CAAC;IACxD,IAAA,6BAAc,EAAC,GAAG,EAAE;QAClB,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,CAAC;QACzF,OAAO,EAAE,KAAK;QACd,GAAG;KACJ,CAAC,CAAC;IACH,6EAA6E;IAC7E,kEAAkE;IAClE,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7C,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACjC,GAAG,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,GAAG,CACD,QAAQ,IAAI,kEAAkE;QAC5E,0EAA0E,CAC7E,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
import type { CdpPage, ElementInfo, PageInfo, PageText, PageLink } from './cdp';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Persistent Chrome profile for the AUTO-ANSWER browser (research reads,
|
|
4
|
+
* `browse --auto-answer`, the web-auto-answer tool).
|
|
5
|
+
*
|
|
6
|
+
* That browser is long-lived and human-facing: the human watches it work and
|
|
7
|
+
* answers on-screen questions in it, so it must never be closed by the bridge.
|
|
8
|
+
*/
|
|
3
9
|
export declare const DEFAULT_BROWSER_PROFILE_DIR: string;
|
|
10
|
+
/** CDP port for the AUTO-ANSWER browser (unchanged — it is the one we keep). */
|
|
11
|
+
export declare const AUTO_ANSWER_CDP_PORT = 9222;
|
|
12
|
+
/**
|
|
13
|
+
* CDP port for the DEBUG browser (MCP `browser_open`/`browser_action`).
|
|
14
|
+
*
|
|
15
|
+
* SEPARATION (2026-09): the debug browser and the auto-answer browser used to
|
|
16
|
+
* share port 9222 AND the same profile dir, so they were the SAME Chrome
|
|
17
|
+
* process. That made "close the debug browser when the task ends" impossible to
|
|
18
|
+
* implement safely — it would have killed the auto-answer run the human was
|
|
19
|
+
* watching. They now live on separate ports with separate profiles, so the
|
|
20
|
+
* debug browser can be closed without touching the auto-answer one.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEBUG_CDP_PORT = 9333;
|
|
23
|
+
/** Separate user-data-dir for the debug browser (see DEBUG_CDP_PORT). */
|
|
24
|
+
export declare const DEBUG_PROFILE_DIR: string;
|
|
4
25
|
export interface BrowserSessionOptions {
|
|
5
|
-
/** CDP port (default
|
|
26
|
+
/** CDP port (default DEBUG_CDP_PORT = 9333). */
|
|
6
27
|
port?: number;
|
|
7
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Launch Chrome when nothing is listening (default true).
|
|
30
|
+
*
|
|
31
|
+
* Auto-launch is now the DEFAULT: an agent calling `browser_open` on a PC with
|
|
32
|
+
* no debug Chrome got "not reachable" and could do nothing about it. When
|
|
33
|
+
* autoLaunch is explicitly false we still auto-launch on a failed probe — an
|
|
34
|
+
* explicit false must not be a dead end, it is only a hint.
|
|
35
|
+
*/
|
|
8
36
|
autoLaunch?: boolean;
|
|
9
|
-
/** Run headless when auto-launching (default false:
|
|
37
|
+
/** Run headless when auto-launching (default false: a visible window). */
|
|
10
38
|
headless?: boolean;
|
|
11
|
-
/** Persistent user-data-dir when auto-launching (default
|
|
39
|
+
/** Persistent user-data-dir when auto-launching (default the debug profile). */
|
|
12
40
|
profileDir?: string;
|
|
13
41
|
/** Extra Chrome args for auto-launch. */
|
|
14
42
|
chromeArgs?: string[];
|
|
@@ -23,7 +51,19 @@ export declare class BrowserSession {
|
|
|
23
51
|
private _profileDir;
|
|
24
52
|
private _chromeArgs;
|
|
25
53
|
private _connected;
|
|
54
|
+
/**
|
|
55
|
+
* True when THIS bridge process launched the Chrome this session is attached
|
|
56
|
+
* to. Only such a browser may be terminated by the bridge; one the human
|
|
57
|
+
* started (or the auto-answer browser) is never killed by us.
|
|
58
|
+
*/
|
|
59
|
+
private _ownedByBridge;
|
|
26
60
|
constructor(id: string, opts?: BrowserSessionOptions);
|
|
61
|
+
/** Last time a command actually used this session (drives idle close). */
|
|
62
|
+
private _lastUsedAt;
|
|
63
|
+
/** When this session was last used — the idle-close timer reads this. */
|
|
64
|
+
get lastUsedAt(): number;
|
|
65
|
+
/** Stamp activity. Called by the manager on every browser command. */
|
|
66
|
+
touch(): void;
|
|
27
67
|
/** Whether the session has an active CDP connection. */
|
|
28
68
|
get connected(): boolean;
|
|
29
69
|
/** The CDP port this session targets. */
|
|
@@ -31,8 +71,21 @@ export declare class BrowserSession {
|
|
|
31
71
|
/** The underlying page handle (for advanced use). */
|
|
32
72
|
get cdpPage(): CdpPage | null;
|
|
33
73
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
74
|
+
* True when the bridge launched this session's Chrome and may therefore close
|
|
75
|
+
* it. False for a Chrome the human started — the single most important
|
|
76
|
+
* invariant here, because the human may be watching/using that window.
|
|
77
|
+
*/
|
|
78
|
+
get ownedByBridge(): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Connect to Chrome, launching a VISIBLE instance when nothing is listening.
|
|
81
|
+
*
|
|
82
|
+
* Auto-launch is the default (2026-09): `browser_open` on a PC with no debug
|
|
83
|
+
* Chrome used to fail with "not reachable", which an agent could do nothing
|
|
84
|
+
* about. `autoLaunch: false` is now a hint, not a dead end — we still launch,
|
|
85
|
+
* because the alternative is a browser tool that simply cannot work.
|
|
86
|
+
*
|
|
87
|
+
* OWNERSHIP: a Chrome we launch is ours to close (`ownedByBridge`); one the
|
|
88
|
+
* human started is NOT — we connect to it and never terminate it.
|
|
36
89
|
*/
|
|
37
90
|
connect(port?: number): Promise<PageInfo>;
|
|
38
91
|
/**
|
|
@@ -99,15 +152,21 @@ export declare class BrowserSession {
|
|
|
99
152
|
*/
|
|
100
153
|
focus(selector: string): Promise<boolean>;
|
|
101
154
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* The debug browser window is NEVER closed here: `chrome.kill()` is a no-op
|
|
105
|
-
* for the persistent-profile debug browser (`surviveStop`, the default), so
|
|
106
|
-
* `browser_close`, session end and daemon shutdown all leave it open for the
|
|
107
|
-
* human. Only an explicitly disposable instance (`surviveStop: false`) is
|
|
108
|
-
* terminated. Close the window yourself, or run `teamshare-agent browser
|
|
109
|
-
* stop`.
|
|
155
|
+
* Release the CDP connection. The Chrome window stays open — the human may be
|
|
156
|
+
* looking at it or signed into it.
|
|
110
157
|
*/
|
|
111
158
|
disconnect(): void;
|
|
159
|
+
/**
|
|
160
|
+
* Close the debug browser this session OWNS.
|
|
161
|
+
*
|
|
162
|
+
* Terminates the Chrome process only when the bridge launched it
|
|
163
|
+
* (`_ownedByBridge`); a Chrome the human started is left running and only
|
|
164
|
+
* disconnected. Now async and VERIFIED: the kill goes through
|
|
165
|
+
* `killChromeOnPort` (graceful, then forced, then "is the port quiet?") and
|
|
166
|
+
* the return value says whether the browser actually went away. A bare
|
|
167
|
+
* `child.kill()` on the detached launcher used to report success while the
|
|
168
|
+
* browser carried on running.
|
|
169
|
+
*/
|
|
170
|
+
closeOwned(): Promise<boolean>;
|
|
112
171
|
private ensureConnected;
|
|
113
172
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BrowserSession = exports.DEFAULT_BROWSER_PROFILE_DIR = void 0;
|
|
3
|
+
exports.BrowserSession = exports.DEBUG_PROFILE_DIR = exports.DEBUG_CDP_PORT = exports.AUTO_ANSWER_CDP_PORT = exports.DEFAULT_BROWSER_PROFILE_DIR = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* BrowserSession: a stateful, reusable CDP browser connection.
|
|
6
6
|
*
|
|
@@ -14,8 +14,31 @@ const node_os_1 = require("node:os");
|
|
|
14
14
|
const node_path_1 = require("node:path");
|
|
15
15
|
const cdp_1 = require("./cdp");
|
|
16
16
|
const launch_1 = require("./launch");
|
|
17
|
-
|
|
17
|
+
const owned_registry_1 = require("./owned-registry");
|
|
18
|
+
const process_1 = require("./process");
|
|
19
|
+
/**
|
|
20
|
+
* Persistent Chrome profile for the AUTO-ANSWER browser (research reads,
|
|
21
|
+
* `browse --auto-answer`, the web-auto-answer tool).
|
|
22
|
+
*
|
|
23
|
+
* That browser is long-lived and human-facing: the human watches it work and
|
|
24
|
+
* answers on-screen questions in it, so it must never be closed by the bridge.
|
|
25
|
+
*/
|
|
18
26
|
exports.DEFAULT_BROWSER_PROFILE_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'chrome-profile');
|
|
27
|
+
/** CDP port for the AUTO-ANSWER browser (unchanged — it is the one we keep). */
|
|
28
|
+
exports.AUTO_ANSWER_CDP_PORT = 9222;
|
|
29
|
+
/**
|
|
30
|
+
* CDP port for the DEBUG browser (MCP `browser_open`/`browser_action`).
|
|
31
|
+
*
|
|
32
|
+
* SEPARATION (2026-09): the debug browser and the auto-answer browser used to
|
|
33
|
+
* share port 9222 AND the same profile dir, so they were the SAME Chrome
|
|
34
|
+
* process. That made "close the debug browser when the task ends" impossible to
|
|
35
|
+
* implement safely — it would have killed the auto-answer run the human was
|
|
36
|
+
* watching. They now live on separate ports with separate profiles, so the
|
|
37
|
+
* debug browser can be closed without touching the auto-answer one.
|
|
38
|
+
*/
|
|
39
|
+
exports.DEBUG_CDP_PORT = 9333;
|
|
40
|
+
/** Separate user-data-dir for the debug browser (see DEBUG_CDP_PORT). */
|
|
41
|
+
exports.DEBUG_PROFILE_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'chrome-profile-debug');
|
|
19
42
|
class BrowserSession {
|
|
20
43
|
id;
|
|
21
44
|
page = null;
|
|
@@ -26,14 +49,30 @@ class BrowserSession {
|
|
|
26
49
|
_profileDir;
|
|
27
50
|
_chromeArgs;
|
|
28
51
|
_connected = false;
|
|
52
|
+
/**
|
|
53
|
+
* True when THIS bridge process launched the Chrome this session is attached
|
|
54
|
+
* to. Only such a browser may be terminated by the bridge; one the human
|
|
55
|
+
* started (or the auto-answer browser) is never killed by us.
|
|
56
|
+
*/
|
|
57
|
+
_ownedByBridge = false;
|
|
29
58
|
constructor(id, opts) {
|
|
30
59
|
this.id = id;
|
|
31
|
-
this._port = opts?.port ??
|
|
32
|
-
this._autoLaunch = opts?.autoLaunch ??
|
|
60
|
+
this._port = opts?.port ?? exports.DEBUG_CDP_PORT;
|
|
61
|
+
this._autoLaunch = opts?.autoLaunch ?? true;
|
|
33
62
|
this._headless = opts?.headless ?? false;
|
|
34
|
-
this._profileDir = opts?.profileDir ?? exports.
|
|
63
|
+
this._profileDir = opts?.profileDir ?? exports.DEBUG_PROFILE_DIR;
|
|
35
64
|
this._chromeArgs = opts?.chromeArgs ?? [];
|
|
36
65
|
}
|
|
66
|
+
/** Last time a command actually used this session (drives idle close). */
|
|
67
|
+
_lastUsedAt = Date.now();
|
|
68
|
+
/** When this session was last used — the idle-close timer reads this. */
|
|
69
|
+
get lastUsedAt() {
|
|
70
|
+
return this._lastUsedAt;
|
|
71
|
+
}
|
|
72
|
+
/** Stamp activity. Called by the manager on every browser command. */
|
|
73
|
+
touch() {
|
|
74
|
+
this._lastUsedAt = Date.now();
|
|
75
|
+
}
|
|
37
76
|
/** Whether the session has an active CDP connection. */
|
|
38
77
|
get connected() {
|
|
39
78
|
return this._connected && this.page !== null && this.page.ws.readyState === 1;
|
|
@@ -47,28 +86,58 @@ class BrowserSession {
|
|
|
47
86
|
return this.page;
|
|
48
87
|
}
|
|
49
88
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
89
|
+
* True when the bridge launched this session's Chrome and may therefore close
|
|
90
|
+
* it. False for a Chrome the human started — the single most important
|
|
91
|
+
* invariant here, because the human may be watching/using that window.
|
|
92
|
+
*/
|
|
93
|
+
get ownedByBridge() {
|
|
94
|
+
return this._ownedByBridge;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Connect to Chrome, launching a VISIBLE instance when nothing is listening.
|
|
98
|
+
*
|
|
99
|
+
* Auto-launch is the default (2026-09): `browser_open` on a PC with no debug
|
|
100
|
+
* Chrome used to fail with "not reachable", which an agent could do nothing
|
|
101
|
+
* about. `autoLaunch: false` is now a hint, not a dead end — we still launch,
|
|
102
|
+
* because the alternative is a browser tool that simply cannot work.
|
|
103
|
+
*
|
|
104
|
+
* OWNERSHIP: a Chrome we launch is ours to close (`ownedByBridge`); one the
|
|
105
|
+
* human started is NOT — we connect to it and never terminate it.
|
|
52
106
|
*/
|
|
53
107
|
async connect(port) {
|
|
54
108
|
if (port)
|
|
55
109
|
this._port = port;
|
|
56
|
-
|
|
110
|
+
this.touch();
|
|
111
|
+
// Try an existing Chrome first (the human may have started a debug Chrome
|
|
112
|
+
// and signed in on it — reusing it keeps their session and cookies).
|
|
57
113
|
if (!(await (0, cdp_1.isChromeReachable)(this._port))) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
114
|
+
// Launch Chrome with the persistent debug profile (real cookies so
|
|
115
|
+
// blocked / logged-in sites work). Headless is opt-in only: the human
|
|
116
|
+
// needs to see and intervene.
|
|
117
|
+
try {
|
|
118
|
+
this.chrome = await (0, launch_1.launchChrome)({
|
|
119
|
+
port: this._port,
|
|
120
|
+
headless: this._headless,
|
|
121
|
+
userDataDir: this._profileDir,
|
|
122
|
+
args: this._chromeArgs,
|
|
123
|
+
// WE launched it, so WE may close it (browser_close / the idle
|
|
124
|
+
// sweep). Without this the instance would be unkillable by design.
|
|
125
|
+
surviveStop: false,
|
|
126
|
+
});
|
|
127
|
+
this._port = this.chrome.port;
|
|
128
|
+
this._ownedByBridge = true;
|
|
129
|
+
// Also written to disk: after a daemon restart the in-memory ownership
|
|
130
|
+
// is gone, and the boot-time reclaim uses this record to close what
|
|
131
|
+
// this machine launched (a human's own window is never recorded).
|
|
132
|
+
(0, owned_registry_1.recordOwnedBrowser)(this._port, this._profileDir);
|
|
133
|
+
console.log(`[browser] launched debug Chrome on port ${this._port} (${this._headless ? 'headless' : 'visible'}) profile=${this._profileDir}`);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
throw new Error(`Could not start Chrome on port ${this._port}: ` +
|
|
137
|
+
`${err instanceof Error ? err.message : String(err)}. ` +
|
|
138
|
+
`Check that Google Chrome is installed, or start it yourself with ` +
|
|
139
|
+
`--remote-debugging-port=${this._port} and retry.`);
|
|
61
140
|
}
|
|
62
|
-
// Launch Chrome with the persistent TeamShare profile (real cookies so
|
|
63
|
-
// blocked / logged-in sites work). Headless is opt-in only.
|
|
64
|
-
// `surviveStop` defaults to true: the bridge never closes this window.
|
|
65
|
-
this.chrome = await (0, launch_1.launchChrome)({
|
|
66
|
-
port: this._port,
|
|
67
|
-
headless: this._headless,
|
|
68
|
-
userDataDir: this._profileDir,
|
|
69
|
-
args: this._chromeArgs,
|
|
70
|
-
});
|
|
71
|
-
this._port = this.chrome.port;
|
|
72
141
|
}
|
|
73
142
|
this.page = await (0, cdp_1.connectToPage)(this._port);
|
|
74
143
|
this._connected = true;
|
|
@@ -207,25 +276,58 @@ class BrowserSession {
|
|
|
207
276
|
return (0, cdp_1.focusSelector)(this.page, selector);
|
|
208
277
|
}
|
|
209
278
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* The debug browser window is NEVER closed here: `chrome.kill()` is a no-op
|
|
213
|
-
* for the persistent-profile debug browser (`surviveStop`, the default), so
|
|
214
|
-
* `browser_close`, session end and daemon shutdown all leave it open for the
|
|
215
|
-
* human. Only an explicitly disposable instance (`surviveStop: false`) is
|
|
216
|
-
* terminated. Close the window yourself, or run `teamshare-agent browser
|
|
217
|
-
* stop`.
|
|
279
|
+
* Release the CDP connection. The Chrome window stays open — the human may be
|
|
280
|
+
* looking at it or signed into it.
|
|
218
281
|
*/
|
|
219
282
|
disconnect() {
|
|
220
283
|
if (this.page) {
|
|
221
284
|
this.page.ws.close();
|
|
222
285
|
this.page = null;
|
|
223
286
|
}
|
|
224
|
-
|
|
225
|
-
|
|
287
|
+
this._connected = false;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Close the debug browser this session OWNS.
|
|
291
|
+
*
|
|
292
|
+
* Terminates the Chrome process only when the bridge launched it
|
|
293
|
+
* (`_ownedByBridge`); a Chrome the human started is left running and only
|
|
294
|
+
* disconnected. Now async and VERIFIED: the kill goes through
|
|
295
|
+
* `killChromeOnPort` (graceful, then forced, then "is the port quiet?") and
|
|
296
|
+
* the return value says whether the browser actually went away. A bare
|
|
297
|
+
* `child.kill()` on the detached launcher used to report success while the
|
|
298
|
+
* browser carried on running.
|
|
299
|
+
*/
|
|
300
|
+
async closeOwned() {
|
|
301
|
+
const wasConnected = this._connected;
|
|
302
|
+
const instance = this.chrome;
|
|
303
|
+
const port = instance?.port ?? this._port;
|
|
304
|
+
this.disconnect();
|
|
305
|
+
if (instance && this._ownedByBridge) {
|
|
226
306
|
this.chrome = null;
|
|
307
|
+
this._ownedByBridge = false;
|
|
308
|
+
try {
|
|
309
|
+
// `surviveStop: false` means kill() is allowed to act, but it only
|
|
310
|
+
// signals the launcher - so do the real, verified close here.
|
|
311
|
+
instance.kill();
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
/* best-effort; the verified close below is what counts */
|
|
315
|
+
}
|
|
316
|
+
const closed = await (0, process_1.killChromeOnPort)(port, (m) => console.log(`[browser] ${m}`));
|
|
317
|
+
(0, owned_registry_1.releaseOwnedBrowser)(port);
|
|
318
|
+
if (closed) {
|
|
319
|
+
console.log(`[browser] closed the debug browser this session owned (port ${port})`);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
console.log(`[browser] could not verify the close of port ${port} - the window may still be open`);
|
|
323
|
+
return false;
|
|
227
324
|
}
|
|
228
|
-
this.
|
|
325
|
+
this.chrome = null;
|
|
326
|
+
this._ownedByBridge = false;
|
|
327
|
+
if (wasConnected) {
|
|
328
|
+
console.log('[browser] released the CDP connection; the browser window was started by you, so it stays open');
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
229
331
|
}
|
|
230
332
|
ensureConnected() {
|
|
231
333
|
if (!this.connected) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/lib/browser/session.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,qCAAkC;AAClC,yCAAiC;AAQjC,+BAoBe;AACf,qCAA6D;
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/lib/browser/session.ts"],"names":[],"mappings":"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"}
|