@simonyea/holysheep-cli 1.7.12 → 1.7.14

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/README.md CHANGED
@@ -70,11 +70,13 @@ You'll be prompted for your API Key (`cr_xxx`), then select the tools to configu
70
70
  1. HolySheep configures OpenClaw to use HolySheep API
71
71
  2. The OpenClaw Gateway starts on **`http://127.0.0.1:18789/` by default**
72
72
  3. If `18789` is occupied, `hs setup` automatically picks the next available local port
73
- 4. Open the exact browser URL shown in the terminal and start chatting — no token required
73
+ 4. Open the exact dashboard URL shown in the terminal and start chatting — do not use a blank browser shell or `about:blank`
74
74
 
75
75
  **Default OpenClaw model:** `gpt-5.4`
76
76
 
77
77
  > **Keep the gateway window open** while using OpenClaw. The gateway must be running for the browser UI to work.
78
+ > If only `npx openclaw` is available, HolySheep will start Gateway as a direct process and will not install a persistent daemon from a temporary `npx` cache path.
79
+ > HolySheep Bridge now watches the local OpenClaw Gateway and exits automatically if the Gateway stays unavailable, so it won't keep a blank browser shell alive after OpenClaw has stopped.
78
80
 
79
81
  > **OpenClaw itself requires Node.js 20+**. If setup fails, first check `node --version`.
80
82
 
@@ -157,11 +159,13 @@ hs setup
157
159
  1. HolySheep 会自动把 OpenClaw 接到 HolySheep API
158
160
  2. 默认启动在 **`http://127.0.0.1:18789/`**
159
161
  3. 如果 `18789` 被占用,`hs setup` 会自动切换到下一个可用本地端口
160
- 4. 按终端里显示的准确地址打开浏览器,直接开始聊天,无需填写 token
162
+ 4. 按终端里显示的准确 dashboard 地址打开浏览器,直接开始聊天,不要打开空白浏览器壳窗口或 `about:blank`
161
163
 
162
164
  **OpenClaw 默认模型:** `gpt-5.4`
163
165
 
164
166
  > ⚠️ **保持 Gateway 窗口开启**,关闭后 Gateway 停止,浏览器界面无法使用。
167
+ > 如果机器上只有 `npx openclaw`,HolySheep 会直接启动 Gateway 进程,不会把 daemon 安装到临时 `npx` 缓存路径上。
168
+ > HolySheep Bridge 会持续检查本地 OpenClaw Gateway;如果 Gateway 持续不可用,Bridge 会自动退出,避免空白浏览器壳窗口一直残留。
165
169
 
166
170
  > ⚠️ **OpenClaw 自身要求 Node.js 20+**。如果配置失败,请先运行 `node --version` 检查版本。
167
171
 
@@ -205,6 +209,9 @@ A: 支持,需要 Node.js 16+。如果 `hs` 命令找不到,请重启终端
205
209
  **Q: OpenClaw Gateway 窗口可以最小化吗?**
206
210
  A: 可以最小化,但不能关闭。关闭后 Gateway 停止,需要按 `hs setup` / `hs doctor` 显示的端口重新运行 `openclaw gateway --port <端口>` 或 `npx openclaw gateway --port <端口>`。
207
211
 
212
+ **Q: 为什么浏览器会只剩黑屏 / 空白窗口?**
213
+ A: 最常见原因是 Gateway 实际没有启动成功,或者你打开了空白浏览器壳窗口而不是终端输出的 dashboard URL。先运行 `hs doctor` 检查 `gateway.port` 是否正在监听,再打开终端里输出的准确 dashboard 地址。
214
+
208
215
  **Q: 18789 端口被占用怎么办?**
209
216
  A: `hs setup` 会自动切换到下一个可用本地端口,并把准确访问地址打印出来;也可以运行 `hs doctor` 查看当前 `gateway.port` 和端口占用情况。
210
217
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.7.12",
3
+ "version": "1.7.14",
4
4
  "description": "Claude Code/Cursor/Cline API relay for China — ¥1=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
5
5
  "keywords": [
6
6
  "openai-china",
@@ -99,9 +99,14 @@ function printCheck(ok, label, detail = '') {
99
99
  function printOpenClawDetails(tool, installState, nodeMajor) {
100
100
  const details = []
101
101
  const gatewayPort = typeof tool.getGatewayPort === 'function' ? tool.getGatewayPort() : 18789
102
+ const bridgePort = typeof tool.getBridgePort === 'function' ? tool.getBridgePort() : null
102
103
  const primaryModel = typeof tool.getPrimaryModel === 'function' ? tool.getPrimaryModel() : ''
103
104
  const listeners = typeof tool.getPortListeners === 'function' ? tool.getPortListeners(gatewayPort) : []
104
105
  const foreignListeners = listeners.filter((item) => !String(item.command || '').toLowerCase().includes('openclaw'))
106
+ const launchAgent = typeof tool.getLaunchAgentDiagnosis === 'function' ? tool.getLaunchAgentDiagnosis() : null
107
+ const launchHint = Array.isArray(tool.launchSteps) && tool.launchSteps.length > 1
108
+ ? tool.launchSteps[1].cmd
109
+ : tool.launchCmd || 'npx openclaw gateway --port <port>'
105
110
 
106
111
  if (installState.detail === 'npx fallback') {
107
112
  details.push({
@@ -123,6 +128,25 @@ function printOpenClawDetails(tool, installState, nodeMajor) {
123
128
  })
124
129
  }
125
130
 
131
+ if (bridgePort) {
132
+ details.push({
133
+ level: 'info',
134
+ text: `Bridge 端口:${bridgePort}`,
135
+ })
136
+ }
137
+
138
+ if (launchAgent?.unstable) {
139
+ details.push({
140
+ level: 'warn',
141
+ text: `检测到失效的 LaunchAgent,引用了临时 npx 缓存路径:${launchAgent.unstableArg}`,
142
+ })
143
+ } else if (launchAgent?.exists) {
144
+ details.push({
145
+ level: 'ok',
146
+ text: `LaunchAgent 配置存在:${launchAgent.path}`,
147
+ })
148
+ }
149
+
126
150
  if (foreignListeners.length) {
127
151
  const occupiedBy = foreignListeners
128
152
  .slice(0, 2)
@@ -139,8 +163,10 @@ function printOpenClawDetails(tool, installState, nodeMajor) {
139
163
  })
140
164
  } else {
141
165
  details.push({
142
- level: 'info',
143
- text: `Gateway 端口 ${gatewayPort} 当前空闲;如刚完成配置,可运行 ${tool.launchCmd}`,
166
+ level: launchAgent?.unstable ? 'warn' : 'info',
167
+ text: launchAgent?.unstable
168
+ ? `Gateway 端口 ${gatewayPort} 当前未监听;这通常会导致浏览器只剩空白/黑屏窗口`
169
+ : `Gateway 端口 ${gatewayPort} 当前空闲;如刚完成配置,可运行 ${launchHint}`,
144
170
  })
145
171
  }
146
172
 
@@ -378,6 +378,9 @@ async function setup(options) {
378
378
  const hot = r.result?.hot ? chalk.cyan(' (热切换,无需重启)') : chalk.gray(' (重启终端生效)')
379
379
  console.log(` ✓ ${r.tool.name}${hot}`)
380
380
  if (r.tool.hint) console.log(` ${chalk.gray('💡 ' + r.tool.hint)}`)
381
+ if (r.result?.dashboardUrl) {
382
+ console.log(` ${chalk.gray('🌐 Dashboard:')} ${chalk.cyan.bold(r.result.dashboardUrl)}`)
383
+ }
381
384
  // 显示启动命令
382
385
  if (r.tool.launchSteps) {
383
386
  // 多步骤启动(如 openclaw)
@@ -9,6 +9,10 @@ const fetch = global.fetch || require('node-fetch')
9
9
 
10
10
  const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
11
11
  const BRIDGE_CONFIG_FILE = path.join(OPENCLAW_DIR, 'holysheep-bridge.json')
12
+ const DEFAULT_WATCHDOG_INTERVAL_MS = 3000
13
+ const DEFAULT_WATCHDOG_FAILURE_THRESHOLD = 3
14
+ const DEFAULT_WATCHDOG_STARTUP_GRACE_MS = 30000
15
+ const DEFAULT_WATCHDOG_REQUEST_TIMEOUT_MS = 1500
12
16
 
13
17
  function readBridgeConfig(configPath = BRIDGE_CONFIG_FILE) {
14
18
  return JSON.parse(fs.readFileSync(configPath, 'utf8'))
@@ -581,6 +585,93 @@ function buildModelsResponse(config) {
581
585
  }
582
586
  }
583
587
 
588
+ function isProcessAlive(pid) {
589
+ if (!Number.isInteger(pid) || pid <= 0) return null
590
+ try {
591
+ process.kill(pid, 0)
592
+ return true
593
+ } catch (error) {
594
+ if (error && error.code === 'EPERM') return true
595
+ return false
596
+ }
597
+ }
598
+
599
+ async function checkGatewayHealth(config) {
600
+ const gatewayPort = Number(config.gatewayPort)
601
+ if (!Number.isInteger(gatewayPort) || gatewayPort <= 0) {
602
+ return { ok: true, reason: 'no_gateway_port' }
603
+ }
604
+
605
+ const gatewayPid = Number(config.gatewayPid)
606
+ const pidAlive = isProcessAlive(gatewayPid)
607
+ if (pidAlive === false) {
608
+ return { ok: false, reason: 'gateway_pid_exited' }
609
+ }
610
+
611
+ const host = config.gatewayHost || '127.0.0.1'
612
+ const timeout = Number(config.watchdog?.requestTimeoutMs) || DEFAULT_WATCHDOG_REQUEST_TIMEOUT_MS
613
+
614
+ try {
615
+ const response = await fetch(`http://${host}:${gatewayPort}/`, { method: 'GET', timeout })
616
+ return response.ok
617
+ ? { ok: true, reason: 'gateway_http_ok' }
618
+ : { ok: false, reason: `gateway_http_${response.status}` }
619
+ } catch {
620
+ return { ok: false, reason: 'gateway_http_unreachable' }
621
+ }
622
+ }
623
+
624
+ function stopBridge(server, reason) {
625
+ process.stdout.write(`HolySheep OpenClaw bridge stopping: ${reason}\n`)
626
+ server.close(() => process.exit(0))
627
+ setTimeout(() => process.exit(0), 250).unref()
628
+ }
629
+
630
+ function startGatewayWatchdog(server, configPath = BRIDGE_CONFIG_FILE) {
631
+ const bridgeStartedAt = Date.now()
632
+ let consecutiveFailures = 0
633
+ let stopping = false
634
+
635
+ const timer = setInterval(async () => {
636
+ if (stopping) return
637
+
638
+ let config
639
+ try {
640
+ config = readBridgeConfig(configPath)
641
+ } catch {
642
+ stopping = true
643
+ stopBridge(server, 'bridge config missing')
644
+ return
645
+ }
646
+
647
+ const watchdog = config.watchdog || {}
648
+ if (watchdog.enabled === false) return
649
+
650
+ const startupGraceMs = Number(watchdog.startupGraceMs) || DEFAULT_WATCHDOG_STARTUP_GRACE_MS
651
+ const failureThreshold = Number(watchdog.failureThreshold) || DEFAULT_WATCHDOG_FAILURE_THRESHOLD
652
+ const health = await checkGatewayHealth(config)
653
+
654
+ if (health.ok) {
655
+ consecutiveFailures = 0
656
+ return
657
+ }
658
+
659
+ const gatewayStartedAt = Date.parse(config.gatewayStartedAt || '') || bridgeStartedAt
660
+ if (Date.now() - gatewayStartedAt < startupGraceMs) {
661
+ return
662
+ }
663
+
664
+ consecutiveFailures += 1
665
+ if (consecutiveFailures < failureThreshold) return
666
+
667
+ stopping = true
668
+ stopBridge(server, `OpenClaw Gateway unavailable (${health.reason})`)
669
+ }, DEFAULT_WATCHDOG_INTERVAL_MS)
670
+
671
+ timer.unref()
672
+ server.on('close', () => clearInterval(timer))
673
+ }
674
+
584
675
  function createBridgeServer(configPath = BRIDGE_CONFIG_FILE) {
585
676
  return http.createServer(async (req, res) => {
586
677
  if (req.method === 'OPTIONS') {
@@ -627,6 +718,7 @@ function startBridge(args = parseArgs(process.argv.slice(2))) {
627
718
  server.listen(port, host, () => {
628
719
  process.stdout.write(`HolySheep OpenClaw bridge listening on http://${host}:${port}\n`)
629
720
  })
721
+ startGatewayWatchdog(server, args.config)
630
722
 
631
723
  return server
632
724
  }
@@ -13,6 +13,8 @@ const { BRIDGE_CONFIG_FILE } = require('./openclaw-bridge')
13
13
 
14
14
  const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw')
15
15
  const CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json')
16
+ const OPENCLAW_LAUNCH_AGENTS_DIR = path.join(os.homedir(), 'Library', 'LaunchAgents')
17
+ const OPENCLAW_GATEWAY_PLIST = path.join(OPENCLAW_LAUNCH_AGENTS_DIR, 'ai.openclaw.gateway.plist')
16
18
  const isWin = process.platform === 'win32'
17
19
  const DEFAULT_BRIDGE_PORT = 18788
18
20
  const DEFAULT_GATEWAY_PORT = 18789
@@ -161,6 +163,14 @@ function writeBridgeConfig(data) {
161
163
  fs.writeFileSync(BRIDGE_CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8')
162
164
  }
163
165
 
166
+ function updateBridgeConfig(patch) {
167
+ const current = readBridgeConfig()
168
+ writeBridgeConfig({
169
+ ...current,
170
+ ...patch,
171
+ })
172
+ }
173
+
164
174
  function getConfiguredBridgePort(config = readBridgeConfig()) {
165
175
  const port = Number(config?.port)
166
176
  return Number.isInteger(port) && port > 0 ? port : DEFAULT_BRIDGE_PORT
@@ -299,6 +309,68 @@ function getDashboardCommand() {
299
309
  return `${runtime} dashboard --no-open`
300
310
  }
301
311
 
312
+ function getDashboardUrlForPort(port) {
313
+ return `http://127.0.0.1:${port}/`
314
+ }
315
+
316
+ function isNpxCachePath(value) {
317
+ return /[\\/]_npx[\\/]/i.test(String(value || ''))
318
+ }
319
+
320
+ function readOpenClawLaunchAgent() {
321
+ if (process.platform !== 'darwin') return null
322
+ try {
323
+ if (!fs.existsSync(OPENCLAW_GATEWAY_PLIST)) return null
324
+ return fs.readFileSync(OPENCLAW_GATEWAY_PLIST, 'utf8')
325
+ } catch {
326
+ return null
327
+ }
328
+ }
329
+
330
+ function parseLaunchAgentProgramArguments(content) {
331
+ const match = String(content || '').match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/i)
332
+ if (!match) return []
333
+
334
+ return Array.from(match[1].matchAll(/<string>([\s\S]*?)<\/string>/g)).map((item) => item[1])
335
+ }
336
+
337
+ function getLaunchAgentDiagnosis() {
338
+ const content = readOpenClawLaunchAgent()
339
+ if (!content) {
340
+ return { exists: false, path: OPENCLAW_GATEWAY_PLIST, unstable: false, programArguments: [] }
341
+ }
342
+
343
+ const programArguments = parseLaunchAgentProgramArguments(content)
344
+ const unstableArg = programArguments.find(isNpxCachePath) || ''
345
+
346
+ return {
347
+ exists: true,
348
+ path: OPENCLAW_GATEWAY_PLIST,
349
+ unstable: Boolean(unstableArg),
350
+ unstableArg,
351
+ programArguments,
352
+ }
353
+ }
354
+
355
+ function removeBrokenLaunchAgent() {
356
+ const diagnosis = getLaunchAgentDiagnosis()
357
+ if (!diagnosis.exists || !diagnosis.unstable) return false
358
+
359
+ try {
360
+ execSync(`launchctl bootout "gui/${process.getuid()}" "${diagnosis.path}"`, {
361
+ shell: true,
362
+ stdio: 'ignore',
363
+ })
364
+ } catch {}
365
+
366
+ try {
367
+ fs.unlinkSync(diagnosis.path)
368
+ return true
369
+ } catch {
370
+ return false
371
+ }
372
+ }
373
+
302
374
  function buildModelEntry(id) {
303
375
  return {
304
376
  id,
@@ -417,6 +489,8 @@ function _disableGatewayAuth(preferNpx = false) {
417
489
  }
418
490
 
419
491
  function _installGatewayService(port, preferNpx = false) {
492
+ if (preferNpx) return false
493
+
420
494
  const result = runOpenClaw(['gateway', 'install', '--force', '--port', String(port)], {
421
495
  preferNpx,
422
496
  timeout: 60000,
@@ -429,13 +503,15 @@ function _startGateway(port, preferNpx = false, preferService = true) {
429
503
  ? runOpenClaw(['gateway', 'start'], { preferNpx, timeout: 20000 })
430
504
  : { status: 1 }
431
505
 
506
+ let directChild = null
507
+
432
508
  if (serviceResult.status !== 0) {
433
- const child = spawnOpenClaw(['gateway', '--port', String(port)], {
509
+ directChild = spawnOpenClaw(['gateway', '--port', String(port)], {
434
510
  preferNpx,
435
511
  detached: true,
436
512
  stdio: 'ignore',
437
513
  })
438
- child.unref()
514
+ directChild.unref()
439
515
  }
440
516
 
441
517
  for (let i = 0; i < 8; i++) {
@@ -449,11 +525,19 @@ function _startGateway(port, preferNpx = false, preferService = true) {
449
525
  : `curl -sf http://127.0.0.1:${port}/ -o /dev/null --max-time 1`,
450
526
  { stdio: 'ignore', timeout: 3000 }
451
527
  )
452
- return true
528
+ return {
529
+ ok: true,
530
+ pid: directChild?.pid || null,
531
+ mode: directChild ? 'direct-process' : 'daemon',
532
+ }
453
533
  } catch {}
454
534
  }
455
535
 
456
- return false
536
+ return {
537
+ ok: false,
538
+ pid: directChild?.pid || null,
539
+ mode: directChild ? 'direct-process' : 'daemon',
540
+ }
457
541
  }
458
542
 
459
543
  function getDashboardUrl(port, preferNpx = false) {
@@ -466,7 +550,7 @@ function getDashboardUrl(port, preferNpx = false) {
466
550
  const match = output.match(/Dashboard URL:\s*(\S+)/) || output.match(/(https?:\/\/\S+)/)
467
551
  if (match) return match[1]
468
552
  }
469
- return `http://127.0.0.1:${port}/`
553
+ return getDashboardUrlForPort(port)
470
554
  }
471
555
 
472
556
  module.exports = {
@@ -499,8 +583,19 @@ module.exports = {
499
583
  throw new Error('未检测到 OpenClaw;请先全局安装,或确保 npx 可用')
500
584
  }
501
585
  this._lastRuntimeCommand = runtime.command
586
+ this._lastRuntimeVia = runtime.via
587
+
588
+ if (runtime.via === 'npx' && removeBrokenLaunchAgent()) {
589
+ console.log(chalk.gray(' → 已清理旧的 OpenClaw 守护进程配置(失效的 npx 缓存路径)'))
590
+ }
502
591
 
503
592
  const resolvedPrimaryModel = pickPrimaryModel(primaryModel, selectedModels)
593
+ const gatewayPort = findAvailableGatewayPort(DEFAULT_GATEWAY_PORT)
594
+ if (!gatewayPort) {
595
+ throw new Error(`找不到可用端口(已检查 ${DEFAULT_GATEWAY_PORT}-${DEFAULT_GATEWAY_PORT + MAX_PORT_SCAN - 1})`)
596
+ }
597
+ this._lastGatewayPort = gatewayPort
598
+
504
599
  const bridgePort = findAvailableGatewayPort(DEFAULT_BRIDGE_PORT)
505
600
  if (!bridgePort) {
506
601
  throw new Error(`找不到可用桥接端口(已检查 ${DEFAULT_BRIDGE_PORT}-${DEFAULT_BRIDGE_PORT + MAX_PORT_SCAN - 1})`)
@@ -509,10 +604,22 @@ module.exports = {
509
604
 
510
605
  writeBridgeConfig({
511
606
  port: bridgePort,
607
+ host: '127.0.0.1',
512
608
  apiKey,
513
609
  baseUrlAnthropic,
514
610
  baseUrlOpenAI,
515
611
  models: normalizeRequestedModels(selectedModels),
612
+ gatewayPort,
613
+ gatewayHost: '127.0.0.1',
614
+ gatewayPid: null,
615
+ gatewayLaunchMode: null,
616
+ watchdog: {
617
+ enabled: true,
618
+ intervalMs: 3000,
619
+ failureThreshold: 3,
620
+ startupGraceMs: 30000,
621
+ requestTimeoutMs: 1500,
622
+ },
516
623
  })
517
624
 
518
625
  console.log(chalk.gray(' → 正在启动 HolySheep Bridge...'))
@@ -523,12 +630,6 @@ module.exports = {
523
630
 
524
631
  runOpenClaw(['gateway', 'stop'], { preferNpx: runtime.via === 'npx' })
525
632
 
526
- const gatewayPort = findAvailableGatewayPort(DEFAULT_GATEWAY_PORT)
527
- if (!gatewayPort) {
528
- throw new Error(`找不到可用端口(已检查 ${DEFAULT_GATEWAY_PORT}-${DEFAULT_GATEWAY_PORT + MAX_PORT_SCAN - 1})`)
529
- }
530
- this._lastGatewayPort = gatewayPort
531
-
532
633
  if (gatewayPort !== DEFAULT_GATEWAY_PORT) {
533
634
  console.log(chalk.yellow(` ⚠️ 端口 ${DEFAULT_GATEWAY_PORT} 已占用,自动切换到 ${gatewayPort}`))
534
635
  const listeners = listPortListeners(DEFAULT_GATEWAY_PORT)
@@ -544,7 +645,7 @@ module.exports = {
544
645
  try { fs.unlinkSync(CONFIG_FILE) } catch {}
545
646
 
546
647
  console.log(chalk.gray(' → 写入配置...'))
547
- const result = runOpenClaw([
648
+ const onboardArgs = [
548
649
  'onboard',
549
650
  '--non-interactive',
550
651
  '--accept-risk',
@@ -554,8 +655,10 @@ module.exports = {
554
655
  '--custom-model-id', resolvedPrimaryModel,
555
656
  '--custom-compatibility', 'openai',
556
657
  '--gateway-port', String(gatewayPort),
557
- '--install-daemon',
558
- ], { preferNpx: runtime.via === 'npx' })
658
+ ]
659
+ if (runtime.via !== 'npx') onboardArgs.push('--install-daemon')
660
+
661
+ const result = runOpenClaw(onboardArgs, { preferNpx: runtime.via === 'npx' })
559
662
 
560
663
  if (result.status !== 0) {
561
664
  console.log(chalk.yellow(' ⚠️ onboard 失败,使用备用配置...'))
@@ -571,21 +674,33 @@ module.exports = {
571
674
 
572
675
  _disableGatewayAuth(runtime.via === 'npx')
573
676
  const serviceReady = _installGatewayService(gatewayPort, runtime.via === 'npx')
677
+ if (runtime.via === 'npx') {
678
+ console.log(chalk.gray(' → 当前仅检测到 npx openclaw,跳过 daemon 安装,改为直接启动 Gateway 进程'))
679
+ }
574
680
 
575
681
  console.log(chalk.gray(' → 正在启动 Gateway...'))
576
- const ok = _startGateway(gatewayPort, runtime.via === 'npx', serviceReady)
682
+ const gatewayState = _startGateway(gatewayPort, runtime.via === 'npx', serviceReady)
683
+ updateBridgeConfig({
684
+ gatewayPort,
685
+ gatewayPid: gatewayState.pid,
686
+ gatewayLaunchMode: gatewayState.mode,
687
+ gatewayStartedAt: new Date().toISOString(),
688
+ })
577
689
 
578
- if (ok) {
690
+ if (gatewayState.ok) {
579
691
  console.log(chalk.green(' ✓ OpenClaw Gateway 已启动'))
580
692
  } else {
581
- console.log(chalk.yellow(' ⚠️ Gateway 启动中,稍等几秒后刷新浏览器'))
693
+ console.log(chalk.yellow(' ⚠️ Gateway 未就绪;当前不要打开 about:blank 或裸浏览器壳窗口'))
582
694
  }
583
695
 
584
- const dashUrl = getDashboardUrl(gatewayPort, runtime.via === 'npx')
696
+ const dashUrl = gatewayState.ok ? getDashboardUrl(gatewayPort, runtime.via === 'npx') : getDashboardUrlForPort(gatewayPort)
585
697
  console.log(chalk.cyan('\n → 浏览器打开(推荐使用此地址):'))
586
698
  console.log(chalk.bold.cyan(` ${dashUrl}`))
587
699
  console.log(chalk.gray(` Bridge 地址: ${bridgeBaseUrl}`))
588
700
  console.log(chalk.gray(` 默认模型: ${plan.primaryRef || OPENCLAW_DEFAULT_MODEL}`))
701
+ console.log(chalk.gray(` Gateway 启动方式: ${gatewayState.mode}`))
702
+ console.log(chalk.gray(' 浏览器应直接打开 dashboard URL,不应停在 about:blank'))
703
+ console.log(chalk.gray(' Bridge 会在检测到 OpenClaw Gateway 持续不可用后自动退出'))
589
704
  console.log(chalk.gray(' 如在 Windows 上打开裸 http://127.0.0.1:PORT/ 仍报 Unauthorized,请使用上面的 dashboard 地址'))
590
705
 
591
706
  return {
@@ -593,6 +708,8 @@ module.exports = {
593
708
  hot: false,
594
709
  dashboardUrl: dashUrl,
595
710
  gatewayPort,
711
+ gatewayReady: gatewayState.ok,
712
+ gatewayLaunchMode: gatewayState.mode,
596
713
  launchCmd: getLaunchCommand(gatewayPort),
597
714
  }
598
715
  },
@@ -607,6 +724,7 @@ module.exports = {
607
724
  getGatewayPort() { return getConfiguredGatewayPort() },
608
725
  getPrimaryModel() { return getConfiguredPrimaryModel() },
609
726
  getPortListeners(port = getConfiguredGatewayPort()) { return listPortListeners(port) },
727
+ getLaunchAgentDiagnosis,
610
728
  get hint() {
611
729
  return `Bridge + Gateway 已配置,默认模型为 ${getConfiguredPrimaryModel() || OPENCLAW_DEFAULT_MODEL}`
612
730
  },
@@ -620,7 +738,8 @@ module.exports = {
620
738
  ]
621
739
  },
622
740
  get launchNote() {
623
- return `🌐 请先启动 Bridge,再启动 Gateway;最后运行 ${getDashboardCommand()}`
741
+ const runtime = module.exports._lastRuntimeVia === 'npx' ? '当前为 npx 模式,不安装常驻 daemon。' : ''
742
+ return `🌐 请先启动 Bridge,再启动 Gateway;最后运行 ${getDashboardCommand()} ${runtime}`.trim()
624
743
  },
625
744
  installCmd: 'npm install -g openclaw@latest',
626
745
  docsUrl: 'https://docs.openclaw.ai',