@joohw/boss-cli 0.1.4 → 0.1.7

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.
Files changed (105) hide show
  1. package/README.md +13 -20
  2. package/dist/browser/auth.d.ts +0 -1
  3. package/dist/browser/auth.d.ts.map +1 -1
  4. package/dist/browser/auth.js +3 -19
  5. package/dist/browser/auth.js.map +1 -1
  6. package/dist/browser/browser_session.d.ts +4 -0
  7. package/dist/browser/browser_session.d.ts.map +1 -1
  8. package/dist/browser/browser_session.js +65 -14
  9. package/dist/browser/browser_session.js.map +1 -1
  10. package/dist/browser/cdp_browser.d.ts +9 -0
  11. package/dist/browser/cdp_browser.d.ts.map +1 -1
  12. package/dist/browser/cdp_browser.js +243 -11
  13. package/dist/browser/cdp_browser.js.map +1 -1
  14. package/dist/browser/chat.d.ts +2 -2
  15. package/dist/browser/chat.d.ts.map +1 -1
  16. package/dist/browser/chat.js +50 -18
  17. package/dist/browser/chat.js.map +1 -1
  18. package/dist/browser/human_delay.d.ts +88 -0
  19. package/dist/browser/human_delay.d.ts.map +1 -0
  20. package/dist/browser/human_delay.js +52 -0
  21. package/dist/browser/human_delay.js.map +1 -0
  22. package/dist/browser/index.d.ts +3 -2
  23. package/dist/browser/index.d.ts.map +1 -1
  24. package/dist/browser/index.js +3 -2
  25. package/dist/browser/index.js.map +1 -1
  26. package/dist/browser/timing.d.ts +7 -0
  27. package/dist/browser/timing.d.ts.map +1 -0
  28. package/dist/browser/timing.js +34 -0
  29. package/dist/browser/timing.js.map +1 -0
  30. package/dist/cli/banner.d.ts.map +1 -1
  31. package/dist/cli/banner.js +43 -9
  32. package/dist/cli/banner.js.map +1 -1
  33. package/dist/cli/cliRouter.d.ts.map +1 -1
  34. package/dist/cli/cliRouter.js +130 -62
  35. package/dist/cli/cliRouter.js.map +1 -1
  36. package/dist/cli/index.js +14 -1
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/config.d.ts +9 -0
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/config.js +25 -0
  41. package/dist/config.js.map +1 -1
  42. package/dist/ocr/baidu_ocr.d.ts +11 -0
  43. package/dist/ocr/baidu_ocr.d.ts.map +1 -0
  44. package/dist/ocr/baidu_ocr.js +76 -0
  45. package/dist/ocr/baidu_ocr.js.map +1 -0
  46. package/dist/ocr/index.d.ts +3 -0
  47. package/dist/ocr/index.d.ts.map +1 -0
  48. package/dist/ocr/index.js +3 -0
  49. package/dist/ocr/index.js.map +1 -0
  50. package/dist/ocr/resume_ocr.d.ts +13 -0
  51. package/dist/ocr/resume_ocr.d.ts.map +1 -0
  52. package/dist/ocr/resume_ocr.js +35 -0
  53. package/dist/ocr/resume_ocr.js.map +1 -0
  54. package/dist/toolset/action.d.ts +7 -0
  55. package/dist/toolset/action.d.ts.map +1 -0
  56. package/dist/toolset/action.js +433 -0
  57. package/dist/toolset/action.js.map +1 -0
  58. package/dist/toolset/chat.d.ts +4 -0
  59. package/dist/toolset/chat.d.ts.map +1 -0
  60. package/dist/toolset/chat.js +434 -0
  61. package/dist/toolset/chat.js.map +1 -0
  62. package/dist/toolset/chat_action.d.ts +7 -0
  63. package/dist/toolset/chat_action.d.ts.map +1 -0
  64. package/dist/toolset/chat_action.js +355 -0
  65. package/dist/toolset/chat_action.js.map +1 -0
  66. package/dist/toolset/index.d.ts +18 -1
  67. package/dist/toolset/index.d.ts.map +1 -1
  68. package/dist/toolset/index.js +27 -6
  69. package/dist/toolset/index.js.map +1 -1
  70. package/dist/toolset/jd.d.ts +10 -0
  71. package/dist/toolset/jd.d.ts.map +1 -0
  72. package/dist/toolset/jd.js +418 -0
  73. package/dist/toolset/jd.js.map +1 -0
  74. package/dist/toolset/list.d.ts +4 -0
  75. package/dist/toolset/list.d.ts.map +1 -0
  76. package/dist/toolset/list.js +156 -0
  77. package/dist/toolset/list.js.map +1 -0
  78. package/dist/toolset/list_candidates.d.ts.map +1 -1
  79. package/dist/toolset/list_candidates.js +12 -19
  80. package/dist/toolset/list_candidates.js.map +1 -1
  81. package/dist/toolset/login.d.ts.map +1 -1
  82. package/dist/toolset/login.js +7 -4
  83. package/dist/toolset/login.js.map +1 -1
  84. package/dist/toolset/open_chat.d.ts.map +1 -1
  85. package/dist/toolset/open_chat.js +266 -24
  86. package/dist/toolset/open_chat.js.map +1 -1
  87. package/dist/toolset/search.d.ts +2 -0
  88. package/dist/toolset/search.d.ts.map +1 -0
  89. package/dist/toolset/search.js +174 -0
  90. package/dist/toolset/search.js.map +1 -0
  91. package/dist/toolset/send.d.ts +12 -0
  92. package/dist/toolset/send.d.ts.map +1 -0
  93. package/dist/toolset/send.js +214 -0
  94. package/dist/toolset/send.js.map +1 -0
  95. package/dist/toolset/send_message.d.ts +11 -1
  96. package/dist/toolset/send_message.d.ts.map +1 -1
  97. package/dist/toolset/send_message.js +120 -21
  98. package/dist/toolset/send_message.js.map +1 -1
  99. package/dist/toolset/skill.d.ts +13 -0
  100. package/dist/toolset/skill.d.ts.map +1 -0
  101. package/dist/toolset/skill.js +85 -0
  102. package/dist/toolset/skill.js.map +1 -0
  103. package/package.json +5 -2
  104. package/skills/README.md +11 -0
  105. package/skills/boss-cli/SKILL.md +58 -0
package/README.md CHANGED
@@ -46,11 +46,12 @@ npm run build
46
46
  | `boss` | 交互模式 |
47
47
  | `boss help` | 打印帮助 |
48
48
  | `boss login` | 打开登录页(不校验登录状态;需自行完成登录) |
49
- | `boss list-candidates [--unread]` | 读取「全部」聊天列表候选人;`--unread` 仅显示未读 |
50
- | `boss open-chat <姓名> [--strict]` | 打开指定联系人会话;默认包含匹配,`--strict` 为精确匹配 |
51
- | `boss send-message --text <内容> [--also-request-resume]` | 发送消息;可选在发送后触发「求简历」 |
52
- | `boss list-positions` | 读取本地 `~/.boss-cli/jd` 目录下的岗位 Markdown |
49
+ | `boss list [--unread]` | 读取「全部」聊天列表候选人;`--unread` 仅显示未读 |
50
+ | `boss chat <姓名> [--strict]` | 打开指定联系人会话;默认包含匹配,`--strict` 为精确匹配 |
51
+ | `boss send [--text <内容>] [--action <…>]` | 可只发消息、只执行 action,或**两者同次**(先发消息再 action,步骤间默认随机间隔);`request-resume` \| `agree-resume` \| `confuse-resume`(拒绝附件) |
52
+ | `boss jd` | 读取本地 `~/.boss-cli/jd` 目录下的岗位 Markdown |
53
53
 
54
+ 旧名仍可用:`list-candidates`、`open-chat`、`send-message`、`list-positions`。
54
55
 
55
56
  **交互模式**
56
57
 
@@ -65,34 +66,26 @@ npm run build
65
66
 
66
67
  ---
67
68
 
68
- ## 浏览器是否自动关闭
69
+ ## 浏览器生命周期
69
70
 
70
- 默认情况下,`boss` **不会**在命令执行结束后关闭浏览器窗口(方便你继续手动操作页面)。
71
+ 命令结束(含一次性子命令)时**不会**关闭浏览器窗口:CLI 只断开与浏览器的 CDP 连接,便于 Node 进程退出,浏览器进程仍保留;需要时请自行关闭窗口。
71
72
 
72
- 如果你希望命令执行完后**自动关闭**由本工具启动/连接的浏览器进程(避免残留进程),请在运行前设置环境变量:
73
-
74
- ```bash
75
- # macOS/Linux
76
- export BOSS_BROWSER_AUTO_CLOSE=1
77
-
78
- # Windows PowerShell
79
- $env:BOSS_BROWSER_AUTO_CLOSE="1"
80
- ```
73
+ 交互模式(无参数 `boss`)退出时同样**不会**关闭浏览器。
81
74
 
82
75
  ---
83
76
 
84
77
  ## Headless / Headful(是否显示浏览器窗口)
85
78
 
86
- - **除 `boss login` 之外**:默认 **headless**(不显示窗口)。
87
- - **`boss login`**:强制 **headful**(显示窗口,便于你扫码/验证/手动登录)。
88
- - 如果你希望其它命令也显示窗口:设置环境变量 `bosscliheadful=1`。
79
+ - 默认 **headful**(显示浏览器窗口)。
80
+ - **`boss login`**:同样为有头(便于扫码/验证/手动登录)。
81
+ - 若希望其它命令**无头**运行:设置环境变量 `BOSS_BROWSER_HEADLESS=true`(或 `1` / `yes`)。
89
82
 
90
83
  ```bash
91
84
  # macOS/Linux
92
- export bosscliheadful=1
85
+ export BOSS_BROWSER_HEADLESS=true
93
86
 
94
87
  # Windows PowerShell
95
- $env:bosscliheadful="1"
88
+ $env:BOSS_BROWSER_HEADLESS="true"
96
89
  ```
97
90
 
98
91
  ---
@@ -11,7 +11,6 @@ export declare function createWaitManualLoginRequiredText(action: string): strin
11
11
  export declare function isBossChatIndexUrl(url: string): boolean;
12
12
  /** 未登录时常见跳转:如 `https://www.zhipin.com/web/user/?ka=bticket` */
13
13
  export declare function isWebUserLoginUrl(url: string): boolean;
14
- export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
15
14
  export type ProbeLoggedInSignals = {
16
15
  hasNickname: boolean;
17
16
  navLoginCta: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/browser/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE3C,gBAAgB;AAChB,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D,uCAAuC;AACvC,eAAO,MAAM,wBAAwB,oDACc,CAAC;AAEpD,yBAAyB;AACzB,eAAO,MAAM,mBAAmB,0CAA0C,CAAC;AAE3E,kCAAkC;AAClC,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWvD;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUtD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrE;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AA6DF;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,GACT,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAyB7C;AAED,mDAAmD;AACnD,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/browser/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAI3C,gBAAgB;AAChB,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D,uCAAuC;AACvC,eAAO,MAAM,wBAAwB,oDACc,CAAC;AAEpD,yBAAyB;AACzB,eAAO,MAAM,mBAAmB,0CAA0C,CAAC;AAE3E,kCAAkC;AAClC,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWvD;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUtD;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AA6DF;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,GACT,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAwB7C;AAED,mDAAmD;AACnD,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E"}
@@ -1,3 +1,5 @@
1
+ import { sleepRandom } from './timing.js';
2
+ import { PROBE_LOGIN_POLL_MS } from './human_delay.js';
1
3
  /** Boss 直聘首页 */
2
4
  export const BOSS_ZHIPIN_HOME = 'https://www.zhipin.com/';
3
5
  /** 默认落地页(杭州 SEO 首页);CLI 等未配置环境变量时使用 */
@@ -35,23 +37,6 @@ export function isWebUserLoginUrl(url) {
35
37
  return false;
36
38
  }
37
39
  }
38
- export function sleep(ms, signal) {
39
- return new Promise((resolve, reject) => {
40
- if (signal?.aborted) {
41
- reject(new Error('Aborted'));
42
- return;
43
- }
44
- const id = setTimeout(() => {
45
- signal?.removeEventListener('abort', onAbort);
46
- resolve();
47
- }, ms);
48
- const onAbort = () => {
49
- clearTimeout(id);
50
- reject(new Error('Aborted'));
51
- };
52
- signal?.addEventListener('abort', onAbort, { once: true });
53
- });
54
- }
55
40
  /**
56
41
  * 必须在页面里执行的探测脚本(纯字符串)。
57
42
  * `tsx`/esbuild 转译 `page.evaluate(() => { ... })` 时可能注入 `__name(...)`,序列化到浏览器后会报 `__name is not defined`。
@@ -126,7 +111,6 @@ export async function probeLoggedInFromPage(page) {
126
111
  return { loggedIn: false, url };
127
112
  }
128
113
  const maxAttempts = 25;
129
- const delayMs = 400;
130
114
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
131
115
  const s = await probeLoggedInSignals(page);
132
116
  if (s.hasNickname || s.hasLogoutHint) {
@@ -136,7 +120,7 @@ export async function probeLoggedInFromPage(page) {
136
120
  return { loggedIn: false, url };
137
121
  }
138
122
  if (attempt < maxAttempts - 1) {
139
- await sleep(delayMs);
123
+ await sleepRandom(PROBE_LOGIN_POLL_MS.min, PROBE_LOGIN_POLL_MS.max);
140
124
  }
141
125
  }
142
126
  return { loggedIn: false, url };
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/browser/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/browser/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,gBAAgB;AAChB,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAE1D,uCAAuC;AACvC,MAAM,CAAC,MAAM,wBAAwB,GACnC,iDAAiD,CAAC;AAEpD,yBAAyB;AACzB,MAAM,CAAC,MAAM,mBAAmB,GAAG,uCAAuC,CAAC;AAE3E,kCAAkC;AAClC,MAAM,UAAU,iCAAiC,CAAC,MAAc;IAC9D,OAAO,cAAc,MAAM,8BAA8B,CAAC;AAC5D,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;QAChD,OAAO,CAAC,KAAK,iBAAiB,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAQD;;;GAGG;AACH,MAAM,8BAA8B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiDlC,CAAC;AAEN,KAAK,UAAU,oBAAoB,CAAC,IAAU;IAC5C,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,8BAA8B,CAAC,CAAyB,CAAC;AACvF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAU;IAEV,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;QAClC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,MAAM,CAAC,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;YACrC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;YAClB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAClC,CAAC;QACD,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,WAAW,CAAC,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,CAAC,GAAG,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AAClC,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,IAAU;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACvD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -18,6 +18,10 @@ export declare function disconnectBrowserSession(): Promise<void>;
18
18
  * 仅断开与浏览器的 CDP 连接,但不主动关闭浏览器进程。
19
19
  * 用于 `boss login` 这类“需要用户继续在浏览器里操作”的场景:
20
20
  * CLI 可以立刻退出,而浏览器窗口仍保留给用户完成登录。
21
+ *
22
+ * 必须在 disconnect 后对 Chrome 子进程 `unref`,否则 Node 会因子进程仍存活而无法退出。
23
+ *
24
+ * 注意:**绝不调用 `browser.close()`**——历史上在 disconnect 抛错时误走 close 会导致退出 CLI 时浏览器被关掉。
21
25
  */
22
26
  export declare function detachBrowserSession(): Promise<void>;
23
27
  //# sourceMappingURL=browser_session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser_session.d.ts","sourceRoot":"","sources":["../../src/browser/browser_session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAiFpD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAGnE;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmC1D;AAED,wBAAgB,aAAa,IAAI,OAAO,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,IAAI,GAAG,IAAI,CAIxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAS/C;AAED,0BAA0B;AAC1B,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAW9D;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAuB1D"}
1
+ {"version":3,"file":"browser_session.d.ts","sourceRoot":"","sources":["../../src/browser/browser_session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAqHpD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAGnE;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqC1D;AAED,wBAAgB,aAAa,IAAI,OAAO,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,IAAI,GAAG,IAAI,CAIxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAS/C;AAED,0BAA0B;AAC1B,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAY9D;AAWD;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAuB1D"}
@@ -1,4 +1,4 @@
1
- import { connectBrowser } from './cdp_browser.js';
1
+ import { clearSpawnedChromeProcessRef, connectBrowser } from './cdp_browser.js';
2
2
  let browserRef = null;
3
3
  let pageRef = null;
4
4
  let connectPromise = null;
@@ -44,6 +44,41 @@ async function pickOrCreatePage(b) {
44
44
  }
45
45
  return pages[0];
46
46
  }
47
+ async function closeRedundantBlankPages(b, keep) {
48
+ const pages = (await b.pages()).filter((p) => !p.isClosed());
49
+ if (pages.length <= 1)
50
+ return;
51
+ const urls = await Promise.all(pages.map((p) => {
52
+ try {
53
+ return p.url();
54
+ }
55
+ catch {
56
+ return '';
57
+ }
58
+ }));
59
+ const blankPages = pages.filter((_, i) => {
60
+ const u = urls[i] ?? '';
61
+ return u === '' || u === 'about:blank';
62
+ });
63
+ if (blankPages.length === 0)
64
+ return;
65
+ const hasNonBlank = pages.some((_, i) => {
66
+ const u = urls[i] ?? '';
67
+ return u !== '' && u !== 'about:blank';
68
+ });
69
+ for (const p of blankPages) {
70
+ if (p === keep)
71
+ continue;
72
+ if (!hasNonBlank && p === blankPages[0])
73
+ continue;
74
+ try {
75
+ await p.close({ runBeforeUnload: false });
76
+ }
77
+ catch {
78
+ /* ignore */
79
+ }
80
+ }
81
+ }
47
82
  function isSessionHealthy() {
48
83
  return !!(browserRef?.connected && pageRef && !pageRef.isClosed());
49
84
  }
@@ -64,7 +99,7 @@ async function establishSession() {
64
99
  browserRef = b;
65
100
  attachDisconnectedHandler(b);
66
101
  pageRef = await pickOrCreatePage(b);
67
- console.error('[boss-cli] 已连接浏览器,当前页:', pageRef.url());
102
+ await closeRedundantBlankPages(b, pageRef);
68
103
  }
69
104
  /**
70
105
  * 在 {@link ensureBrowserSession} 之后返回当前已连接的 Browser;
@@ -85,6 +120,7 @@ export async function ensureBrowserSession() {
85
120
  pageRef = preferred;
86
121
  }
87
122
  }
123
+ await closeRedundantBlankPages(browserRef, pageRef);
88
124
  }
89
125
  catch {
90
126
  /* ignore */
@@ -92,6 +128,7 @@ export async function ensureBrowserSession() {
92
128
  return;
93
129
  }
94
130
  pageRef = await pickOrCreatePage(browserRef);
131
+ await closeRedundantBlankPages(browserRef, pageRef);
95
132
  return;
96
133
  }
97
134
  if (connectPromise) {
@@ -152,37 +189,51 @@ export async function disconnectBrowserSession() {
152
189
  }
153
190
  browserRef = null;
154
191
  pageRef = null;
192
+ clearSpawnedChromeProcessRef();
193
+ }
194
+ function unrefBrowserChildProcess(proc) {
195
+ if (!proc)
196
+ return;
197
+ try {
198
+ proc.unref();
199
+ }
200
+ catch {
201
+ /* ignore */
202
+ }
155
203
  }
156
204
  /**
157
205
  * 仅断开与浏览器的 CDP 连接,但不主动关闭浏览器进程。
158
206
  * 用于 `boss login` 这类“需要用户继续在浏览器里操作”的场景:
159
207
  * CLI 可以立刻退出,而浏览器窗口仍保留给用户完成登录。
208
+ *
209
+ * 必须在 disconnect 后对 Chrome 子进程 `unref`,否则 Node 会因子进程仍存活而无法退出。
210
+ *
211
+ * 注意:**绝不调用 `browser.close()`**——历史上在 disconnect 抛错时误走 close 会导致退出 CLI 时浏览器被关掉。
160
212
  */
161
213
  export async function detachBrowserSession() {
162
214
  const b = browserRef;
163
215
  if (!b)
164
216
  return;
217
+ let proc;
218
+ try {
219
+ proc = typeof b.process === 'function' ? b.process() : undefined;
220
+ }
221
+ catch {
222
+ proc = undefined;
223
+ }
165
224
  try {
166
225
  b.removeAllListeners('disconnected');
167
- // Puppeteer 支持 disconnect:断开连接但保留浏览器进程
168
- //(若底层不支持或抛错,则退化为 close)。
169
226
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
227
  const anyB = b;
171
228
  if (typeof anyB.disconnect === 'function') {
172
- anyB.disconnect();
173
- }
174
- else {
175
- await b.close();
229
+ await Promise.resolve(anyB.disconnect());
176
230
  }
177
231
  }
178
232
  catch {
179
- try {
180
- await b.close();
181
- }
182
- catch {
183
- /* ignore */
184
- }
233
+ /* 仍不 close;仅断开失败时依赖下方 unref 与进程退出行为 */
185
234
  }
235
+ unrefBrowserChildProcess(proc ?? null);
236
+ clearSpawnedChromeProcessRef();
186
237
  browserRef = null;
187
238
  pageRef = null;
188
239
  }
@@ -1 +1 @@
1
- {"version":3,"file":"browser_session.js","sourceRoot":"","sources":["../../src/browser/browser_session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"browser_session.js","sourceRoot":"","sources":["../../src/browser/browser_session.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,7 @@
1
1
  import { type Browser, type CDPSession, type Page } from 'puppeteer-core';
2
+ export declare function clearSpawnedChromeProcessRef(): void;
3
+ /** 最近一次启动是否为无头(仅本进程内、与当前会话一致时有效)。 */
4
+ export declare function wasLastChromeLaunchHeadless(): boolean;
2
5
  /** 减轻「正受到自动测试软件的控制」提示与常见自动化特征(非万能,站点仍可能用其它方式检测)。手动开 Chrome 并接 CDP 时可复用。 */
3
6
  export declare const LAUNCH_ARGS_LESS_AUTOMATION: readonly ["--disable-blink-features=AutomationControlled", "--disable-infobars"];
4
7
  /** 仅用于本地调试:尽量放宽同源/CORS 限制,便于跨域 iframe/canvas 处理。 */
@@ -27,7 +30,13 @@ export type ConnectBrowserOptions = {
27
30
  *
28
31
  * 若以上均未设置,会按系统尝试常见 Chrome / Edge / Chromium 安装路径。
29
32
  * - `BOSS_BROWSER_HEADLESS` — 设为 `true` 时启用无头;默认**有界面**。
33
+ * - `BOSS_BROWSER_VIEWPORT_WIDTH` / `BOSS_BROWSER_VIEWPORT_HEIGHT` — 启动时显式指定视口;未设置时不覆盖浏览器窗口尺寸
30
34
  */
35
+ /** 启动浏览器时的默认视口(与环境变量一致);截图恢复时 `viewport()` 为 null 也可用其兜底。 */
36
+ export declare function defaultViewportFromEnv(): {
37
+ width: number;
38
+ height: number;
39
+ };
31
40
  export declare function connectBrowser(options?: ConnectBrowserOptions): Promise<Browser>;
32
41
  /** 对某一页创建原生 CDP Session(需要低层域如 `Network.*`、`Fetch.*` 时使用)。 */
33
42
  export declare function createPageCDPSession(page: Page): Promise<CDPSession>;
@@ -1 +1 @@
1
- {"version":3,"file":"cdp_browser.d.ts","sourceRoot":"","sources":["../../src/browser/cdp_browser.ts"],"names":[],"mappings":"AAEA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA4CrF,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B,kFAG9B,CAAC;AAEX,oDAAoD;AACpD,eAAO,MAAM,0BAA0B,yEAG7B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG;IAClC,yBAAyB;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,OAAO,CAAC,CAyC1F;AAED,8DAA8D;AAC9D,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAE1E"}
1
+ {"version":3,"file":"cdp_browser.d.ts","sourceRoot":"","sources":["../../src/browser/cdp_browser.ts"],"names":[],"mappings":"AAKA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAYrF,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAED,qCAAqC;AACrC,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAmLD,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B,kFAG9B,CAAC;AAEX,oDAAoD;AACpD,eAAO,MAAM,0BAA0B,yEAG7B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG;IAClC,yBAAyB;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,6DAA6D;AAC7D,wBAAgB,sBAAsB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAO1E;AAYD,wBAAsB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,OAAO,CAAC,CA0H1F;AAED,8DAA8D;AAC9D,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAE1E"}
@@ -1,7 +1,140 @@
1
+ import { spawn } from 'node:child_process';
1
2
  import { existsSync } from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
4
+ import readline from 'node:readline';
2
5
  import path from 'node:path';
3
6
  import puppeteer from 'puppeteer-core';
4
7
  import { BROWSER_USER_DATA_DIR, ensureAppDataLayout } from '../config.js';
8
+ /** 与 @puppeteer/browsers 一致,解析 Chrome 启动日志中的 CDP WebSocket URL(可能在 stdout 或 stderr)。 */
9
+ const CDP_WEBSOCKET_ENDPOINT_REGEX = /^DevTools listening on (ws:\/\/.*)$/;
10
+ const LAUNCH_READY_MS = 30_000;
11
+ let spawnedChromeChild = null;
12
+ /** 最近一次 `connectBrowser` 是否以无头方式启动(`browser.process()` 在 connect 模式下不可用,供 login 等逻辑判断)。 */
13
+ let lastChromeLaunchHeadless = false;
14
+ export function clearSpawnedChromeProcessRef() {
15
+ spawnedChromeChild = null;
16
+ }
17
+ /** 最近一次启动是否为无头(仅本进程内、与当前会话一致时有效)。 */
18
+ export function wasLastChromeLaunchHeadless() {
19
+ return lastChromeLaunchHeadless;
20
+ }
21
+ /**
22
+ * Chromium 在启用远程调试时会在用户数据目录写入 `DevToolsActivePort`(首行端口、次行 path)。
23
+ * 当同一 user-data-dir 已有实例在跑时,新起的进程会通过单例立刻退出(常为代码 0),
24
+ * 且本进程的 stdout 可能拿不到 `DevTools listening on`(尤其 Windows),此时应读本文件接到已有实例。
25
+ */
26
+ async function readDevToolsEndpointFromUserDataDir(userDataDir, maxWaitMs) {
27
+ const portPath = path.join(userDataDir, 'DevToolsActivePort');
28
+ const deadline = Date.now() + maxWaitMs;
29
+ let delay = 40;
30
+ while (Date.now() < deadline) {
31
+ if (existsSync(portPath)) {
32
+ try {
33
+ const fileContent = await readFile(portPath, 'ascii');
34
+ const lines = fileContent
35
+ .split('\n')
36
+ .map((l) => l.trim())
37
+ .filter((l) => l.length > 0);
38
+ const rawPort = lines[0];
39
+ const rawPath = lines[1];
40
+ if (rawPort && rawPath) {
41
+ const port = Number.parseInt(rawPort, 10);
42
+ if (Number.isFinite(port) && port > 0 && port <= 65535) {
43
+ return `ws://127.0.0.1:${port}${rawPath}`;
44
+ }
45
+ }
46
+ }
47
+ catch {
48
+ /* 文件可能尚未写完,重试 */
49
+ }
50
+ }
51
+ await new Promise((r) => setTimeout(r, delay));
52
+ delay = Math.min(delay + 40, 200);
53
+ }
54
+ return undefined;
55
+ }
56
+ function waitForDevToolsWebSocketUrl(proc, userDataDir, timeoutMs) {
57
+ const streams = [proc.stdout, proc.stderr].filter((s) => s != null);
58
+ if (streams.length === 0) {
59
+ return Promise.reject(new Error('浏览器子进程无 stdout/stderr,无法获取 CDP 地址'));
60
+ }
61
+ return new Promise((resolve, reject) => {
62
+ const rls = [];
63
+ let settled = false;
64
+ let timer;
65
+ const cleanup = () => {
66
+ for (const rl of rls) {
67
+ try {
68
+ rl.close();
69
+ }
70
+ catch {
71
+ /* ignore */
72
+ }
73
+ }
74
+ rls.length = 0;
75
+ };
76
+ const finish = (fn) => {
77
+ if (settled)
78
+ return;
79
+ settled = true;
80
+ if (timer !== undefined) {
81
+ clearTimeout(timer);
82
+ timer = undefined;
83
+ }
84
+ proc.off('exit', onExit);
85
+ proc.off('error', onProcError);
86
+ cleanup();
87
+ fn();
88
+ };
89
+ timer = setTimeout(() => {
90
+ finish(() => {
91
+ reject(new Error(`等待 Chrome 输出 DevTools 地址超时(${timeoutMs}ms)`));
92
+ });
93
+ }, timeoutMs);
94
+ const onExit = (code) => {
95
+ if (timer !== undefined) {
96
+ clearTimeout(timer);
97
+ timer = undefined;
98
+ }
99
+ void (async () => {
100
+ if (code === 0) {
101
+ const fromFile = await readDevToolsEndpointFromUserDataDir(userDataDir, 4000);
102
+ if (fromFile) {
103
+ finish(() => {
104
+ resolve(fromFile);
105
+ });
106
+ return;
107
+ }
108
+ }
109
+ finish(() => {
110
+ reject(new Error(code === 0
111
+ ? '无法连接浏览器:新进程已退出(代码 0)且未在本地用户数据目录发现 DevTools 调试端口。若该配置正被「手动打开」的 Chrome 占用(无远程调试),请先关闭该窗口;或结束仍由上次 boss-cli 留下的浏览器后再试。'
112
+ : `浏览器进程在就绪前退出(代码 ${code ?? 'unknown'})`));
113
+ });
114
+ })();
115
+ };
116
+ const onProcError = (err) => {
117
+ finish(() => {
118
+ reject(err);
119
+ });
120
+ };
121
+ const onLine = (line) => {
122
+ const m = line.trim().match(CDP_WEBSOCKET_ENDPOINT_REGEX);
123
+ if (m?.[1]) {
124
+ finish(() => {
125
+ resolve(m[1]);
126
+ });
127
+ }
128
+ };
129
+ proc.once('exit', onExit);
130
+ proc.once('error', onProcError);
131
+ for (const s of streams) {
132
+ const rl = readline.createInterface(s);
133
+ rls.push(rl);
134
+ rl.on('line', onLine);
135
+ }
136
+ });
137
+ }
5
138
  /** 在未配置路径时,尝试常见安装位置(Chrome / Edge / Chromium)。 */
6
139
  function findLocalChromiumExecutable() {
7
140
  const candidates = [];
@@ -55,7 +188,26 @@ export const LAUNCH_ARGS_ALLOW_ALL_CORS = [
55
188
  *
56
189
  * 若以上均未设置,会按系统尝试常见 Chrome / Edge / Chromium 安装路径。
57
190
  * - `BOSS_BROWSER_HEADLESS` — 设为 `true` 时启用无头;默认**有界面**。
191
+ * - `BOSS_BROWSER_VIEWPORT_WIDTH` / `BOSS_BROWSER_VIEWPORT_HEIGHT` — 启动时显式指定视口;未设置时不覆盖浏览器窗口尺寸
58
192
  */
193
+ /** 启动浏览器时的默认视口(与环境变量一致);截图恢复时 `viewport()` 为 null 也可用其兜底。 */
194
+ export function defaultViewportFromEnv() {
195
+ const w = Number.parseInt(process.env.BOSS_BROWSER_VIEWPORT_WIDTH?.trim() ?? '', 10);
196
+ const h = Number.parseInt(process.env.BOSS_BROWSER_VIEWPORT_HEIGHT?.trim() ?? '', 10);
197
+ return {
198
+ width: Number.isFinite(w) && w > 0 ? w : 1280,
199
+ height: Number.isFinite(h) && h > 0 ? h : 1200,
200
+ };
201
+ }
202
+ /** 仅在显式配置了视口环境变量时返回启动视口;否则返回 null,不覆盖浏览器实际窗口尺寸。 */
203
+ function launchViewportFromEnv() {
204
+ const rawW = process.env.BOSS_BROWSER_VIEWPORT_WIDTH?.trim() ?? '';
205
+ const rawH = process.env.BOSS_BROWSER_VIEWPORT_HEIGHT?.trim() ?? '';
206
+ if (!rawW && !rawH) {
207
+ return null;
208
+ }
209
+ return defaultViewportFromEnv();
210
+ }
59
211
  export async function connectBrowser(options = {}) {
60
212
  const executablePath = options.executablePath?.trim() ||
61
213
  process.env.CHROME_PATH?.trim() ||
@@ -73,20 +225,100 @@ export async function connectBrowser(options = {}) {
73
225
  const headless = options.headless ?? process.env.BOSS_BROWSER_HEADLESS === 'true';
74
226
  const allowAllCors = options.allowAllCors ?? process.env.BOSS_BROWSER_ALLOW_ALL_CORS === 'true';
75
227
  const disableGpu = process.env.BOSS_BROWSER_DISABLE_GPU === 'true';
76
- return puppeteer.launch({
77
- executablePath,
228
+ clearSpawnedChromeProcessRef();
229
+ lastChromeLaunchHeadless = !!headless;
230
+ /**
231
+ * 优先直连已存在实例:若当前 user-data-dir 下已有开启远程调试的浏览器,
232
+ * 直接 connect,避免每次都 spawn 新进程导致额外空白窗口/闪窗。
233
+ */
234
+ const existingWsUrl = await readDevToolsEndpointFromUserDataDir(userDataDir, 700);
235
+ if (existingWsUrl) {
236
+ try {
237
+ return await puppeteer.connect({
238
+ browserWSEndpoint: existingWsUrl,
239
+ defaultViewport: launchViewportFromEnv(),
240
+ });
241
+ }
242
+ catch {
243
+ // 端口文件可能陈旧或实例刚退出,回退到 spawn + connect。
244
+ }
245
+ }
246
+ const userArgs = [
247
+ ...LAUNCH_ARGS_LESS_AUTOMATION,
248
+ ...(disableGpu ? ['--disable-gpu'] : []),
249
+ ...(allowAllCors ? LAUNCH_ARGS_ALLOW_ALL_CORS : []),
250
+ ...(profileDirectory ? [`--profile-directory=${profileDirectory}`] : []),
251
+ ];
252
+ let chromeArgs = puppeteer
253
+ .defaultArgs({
254
+ browser: 'chrome',
78
255
  userDataDir,
79
256
  headless,
80
- /** 去掉 Chrome 默认的 `--enable-automation`,可消除顶部「正受到自动测试软件的控制」提示 */
81
- ignoreDefaultArgs: ['--enable-automation'],
82
- defaultViewport: headless ? { width: 1280, height: 800 } : null,
83
- args: [
84
- ...LAUNCH_ARGS_LESS_AUTOMATION,
85
- ...(disableGpu ? ['--disable-gpu'] : []),
86
- ...(allowAllCors ? LAUNCH_ARGS_ALLOW_ALL_CORS : []),
87
- ...(profileDirectory ? [`--profile-directory=${profileDirectory}`] : []),
88
- ],
257
+ args: userArgs,
258
+ })
259
+ .filter((a) => a !== '--enable-automation' && a !== 'about:blank' && a !== 'data:,');
260
+ if (!chromeArgs.some((a) => a.startsWith('--remote-debugging-'))) {
261
+ chromeArgs.push('--remote-debugging-port=0');
262
+ }
263
+ /**
264
+ * 不使用 `puppeteer.launch()`:其依赖的 `@puppeteer/browsers` 会在 **Node 进程 `exit` kill 浏览器子进程**,
265
+ * 导致交互模式 / `npm run dev` 退出时窗口被一并关掉。改为自行 `spawn` + `connect`,退出时只断 CDP,浏览器可保留。
266
+ */
267
+ const proc = spawn(executablePath, chromeArgs, {
268
+ detached: true,
269
+ env: process.env,
270
+ stdio: ['ignore', 'pipe', 'pipe'],
89
271
  });
272
+ spawnedChromeChild = proc;
273
+ let wsUrl;
274
+ try {
275
+ wsUrl = await waitForDevToolsWebSocketUrl(proc, userDataDir, LAUNCH_READY_MS);
276
+ }
277
+ catch (e) {
278
+ try {
279
+ proc.kill();
280
+ }
281
+ catch {
282
+ /* ignore */
283
+ }
284
+ clearSpawnedChromeProcessRef();
285
+ throw e;
286
+ }
287
+ try {
288
+ proc.stdout?.resume();
289
+ proc.stderr?.resume();
290
+ }
291
+ catch {
292
+ /* ignore */
293
+ }
294
+ /** 单例移交时子进程已退出,无句柄可 unref;仅在本进程真正拉起 Chrome 时 unref,避免拖住 Node 退出。 */
295
+ if (proc.exitCode === null && proc.signalCode === null) {
296
+ try {
297
+ proc.unref();
298
+ }
299
+ catch {
300
+ /* ignore */
301
+ }
302
+ }
303
+ else {
304
+ clearSpawnedChromeProcessRef();
305
+ }
306
+ try {
307
+ return await puppeteer.connect({
308
+ browserWSEndpoint: wsUrl,
309
+ defaultViewport: launchViewportFromEnv(),
310
+ });
311
+ }
312
+ catch (e) {
313
+ try {
314
+ proc.kill();
315
+ }
316
+ catch {
317
+ /* ignore */
318
+ }
319
+ clearSpawnedChromeProcessRef();
320
+ throw e;
321
+ }
90
322
  }
91
323
  /** 对某一页创建原生 CDP Session(需要低层域如 `Network.*`、`Fetch.*` 时使用)。 */
92
324
  export async function createPageCDPSession(page) {