@jamiexiongr/panda-hub 0.1.19 → 0.1.21

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
@@ -2,19 +2,30 @@
2
2
 
3
3
  Published hub runtime for Panda, including the built web UI.
4
4
 
5
- ## Usage
6
-
7
- ```bash
8
- panda-hub
9
- panda-hub tailscareserv
10
- ```
11
-
12
- `tailscareserv` and `--tailscale-serve` both enable automatic `tailscale serve` publishing.
13
-
14
- When enabled, hub startup will:
15
-
16
- - detect whether `tailscale` is available and online
17
- - run `tailscale serve --bg`
5
+ ## Usage
6
+
7
+ ```bash
8
+ panda-hub
9
+ panda-hub tailscareserv
10
+ ```
11
+
12
+ `tailscareserv` and `--tailscale-serve` both enable automatic `tailscale serve` publishing.
13
+
14
+ Windows service management:
15
+
16
+ ```powershell
17
+ panda-hub service install --name=PandaHub tailscareserv
18
+ panda-hub service status
19
+ panda-hub service restart
20
+ panda-hub service uninstall
21
+ ```
22
+
23
+ `service install` stores the startup args and current `PANDA_*` environment values in the Windows service definition. If you change them later, run `service install` again to update the service.
24
+
25
+ When enabled, hub startup will:
26
+
27
+ - detect whether `tailscale` is available and online
28
+ - run `tailscale serve --bg`
18
29
  - print the generated Tailscale HTTPS URL in the startup log
19
30
 
20
31
  ## Environment
@@ -2468,12 +2468,24 @@ var devManagerServiceStatusSchema = external_exports.enum([
2468
2468
  "stopped",
2469
2469
  "degraded"
2470
2470
  ]);
2471
+ var devManagerServiceManagerSchema = external_exports.enum([
2472
+ "process",
2473
+ "windows-service"
2474
+ ]);
2475
+ var devManagerServiceControllerStatusSchema = external_exports.enum([
2476
+ "missing",
2477
+ "running",
2478
+ "stopped",
2479
+ "unknown"
2480
+ ]);
2471
2481
  var devManagerJobKindSchema = external_exports.enum([
2472
2482
  "dev-start",
2473
2483
  "dev-restart",
2474
2484
  "dev-stop",
2475
2485
  "dev-probe",
2476
2486
  "npm-publish",
2487
+ "release-service-install",
2488
+ "release-service-uninstall",
2477
2489
  "release-install-run",
2478
2490
  "apk-build"
2479
2491
  ]);
@@ -2503,8 +2515,10 @@ var devManagerConfigSchema = external_exports.object({
2503
2515
  dev_web_hub_url: external_exports.string().default(""),
2504
2516
  dev_web_args: external_exports.string().default(""),
2505
2517
  release_hub_port: external_exports.number().int().positive().nullable().default(null),
2518
+ release_hub_service_name: external_exports.string().default("PandaHub"),
2506
2519
  release_hub_args: external_exports.string().default(""),
2507
2520
  release_agent_port: external_exports.number().int().positive().nullable().default(null),
2521
+ release_agent_service_name: external_exports.string().default("PandaAgent"),
2508
2522
  release_agent_hub_url: external_exports.string().default(""),
2509
2523
  release_agent_direct_base_url: external_exports.string().default(""),
2510
2524
  release_agent_ws_base_url: external_exports.string().default(""),
@@ -2532,6 +2546,10 @@ var devManagerServiceStateSchema = external_exports.object({
2532
2546
  key: devManagerServiceKeySchema,
2533
2547
  label: external_exports.string(),
2534
2548
  status: devManagerServiceStatusSchema,
2549
+ manager: devManagerServiceManagerSchema.default("process"),
2550
+ service_name: external_exports.string().nullable().default(null),
2551
+ service_registered: external_exports.boolean().default(false),
2552
+ service_status: devManagerServiceControllerStatusSchema.nullable().default(null),
2535
2553
  configured_port: external_exports.number().int().positive().nullable().default(null),
2536
2554
  detected_pids: external_exports.array(external_exports.number().int().positive()).default([]),
2537
2555
  probe: devManagerServiceProbeSchema.nullable().default(null)
@@ -2582,6 +2600,8 @@ var devManagerActionRequestSchema = external_exports.object({
2582
2600
  "stop-development",
2583
2601
  "probe-development",
2584
2602
  "publish-npm",
2603
+ "install-release-services",
2604
+ "uninstall-release-services",
2585
2605
  "install-latest-release",
2586
2606
  "build-apk"
2587
2607
  ])
@@ -8260,8 +8280,10 @@ var defaultConfig = () => ({
8260
8280
  dev_web_hub_url: `http://${LOCALHOST}:4344`,
8261
8281
  dev_web_args: "",
8262
8282
  release_hub_port: 4343,
8283
+ release_hub_service_name: "PandaHub",
8263
8284
  release_hub_args: "",
8264
8285
  release_agent_port: 4242,
8286
+ release_agent_service_name: "PandaAgent",
8265
8287
  release_agent_hub_url: `http://${LOCALHOST}:4343`,
8266
8288
  release_agent_direct_base_url: `http://${LOCALHOST}:4242`,
8267
8289
  release_agent_ws_base_url: `ws://${LOCALHOST}:4242/ws`,
@@ -8312,8 +8334,10 @@ var normalizeConfig = (input) => {
8312
8334
  dev_web_hub_url: normalizeText(input?.dev_web_hub_url) || buildHttpBaseUrl(devHubPort, defaults.dev_web_hub_url),
8313
8335
  dev_web_args: normalizeText(input?.dev_web_args),
8314
8336
  release_hub_port: releaseHubPort,
8337
+ release_hub_service_name: normalizeText(input?.release_hub_service_name) || defaults.release_hub_service_name,
8315
8338
  release_hub_args: normalizeText(input?.release_hub_args),
8316
8339
  release_agent_port: releaseAgentPort,
8340
+ release_agent_service_name: normalizeText(input?.release_agent_service_name) || defaults.release_agent_service_name,
8317
8341
  release_agent_hub_url: normalizeText(input?.release_agent_hub_url) || buildHttpBaseUrl(releaseHubPort, defaults.release_agent_hub_url),
8318
8342
  release_agent_direct_base_url: normalizeText(input?.release_agent_direct_base_url) || buildHttpBaseUrl(releaseAgentPort, defaults.release_agent_direct_base_url),
8319
8343
  release_agent_ws_base_url: normalizeText(input?.release_agent_ws_base_url) || buildWsBaseUrl(releaseAgentPort, defaults.release_agent_ws_base_url),
@@ -8493,6 +8517,60 @@ var killByPort = async (port) => {
8493
8517
  }
8494
8518
  return pids;
8495
8519
  };
8520
+ var resolveWindowsServiceControllerName = (serviceName) => {
8521
+ const normalizedName = normalizeNullableText(serviceName);
8522
+ if (!normalizedName) {
8523
+ return null;
8524
+ }
8525
+ return `${normalizedName.replace(/[^\w]/g, "").toLowerCase()}.exe`;
8526
+ };
8527
+ var queryWindowsServiceStatus = (serviceName) => {
8528
+ const normalizedName = normalizeNullableText(serviceName);
8529
+ if (process.platform !== "win32" || !normalizedName) {
8530
+ return null;
8531
+ }
8532
+ const controllerName = resolveWindowsServiceControllerName(normalizedName);
8533
+ const escapedName = normalizedName.replace(/'/g, "''");
8534
+ const escapedControllerName = (controllerName ?? normalizedName).replace(/'/g, "''");
8535
+ const result = spawnSync2(
8536
+ "powershell.exe",
8537
+ [
8538
+ "-NoProfile",
8539
+ "-Command",
8540
+ [
8541
+ `$service = Get-Service -Name '${escapedControllerName}' -ErrorAction SilentlyContinue`,
8542
+ `if ($null -eq $service) { $service = Get-Service -DisplayName '${escapedName}' -ErrorAction SilentlyContinue }`,
8543
+ `if ($null -eq $service) { '__MISSING__' } else { @{ Name = $service.Name; DisplayName = $service.DisplayName; Status = $service.Status.ToString() } | ConvertTo-Json -Compress }`
8544
+ ].join("; ")
8545
+ ],
8546
+ {
8547
+ encoding: "utf8",
8548
+ timeout: GLOBAL_COMMAND_TIMEOUT_MS,
8549
+ windowsHide: true
8550
+ }
8551
+ );
8552
+ const rawOutput = `${result.stdout ?? ""}
8553
+ ${result.stderr ?? ""}`.trim();
8554
+ let resolvedDisplayName = normalizedName;
8555
+ let resolvedControllerName = controllerName ?? normalizedName;
8556
+ let normalizedOutput = rawOutput.trim().toLowerCase();
8557
+ try {
8558
+ const parsed = JSON.parse(rawOutput);
8559
+ resolvedDisplayName = normalizeNullableText(parsed.DisplayName) ?? resolvedDisplayName;
8560
+ resolvedControllerName = normalizeNullableText(parsed.Name) ?? resolvedControllerName;
8561
+ normalizedOutput = (normalizeNullableText(parsed.Status) ?? rawOutput).trim().toLowerCase();
8562
+ } catch {
8563
+ }
8564
+ const status = normalizedOutput === "__missing__" ? "missing" : normalizedOutput === "running" ? "running" : normalizedOutput === "stopped" ? "stopped" : "unknown";
8565
+ return {
8566
+ name: normalizedName,
8567
+ displayName: resolvedDisplayName,
8568
+ controllerName: resolvedControllerName,
8569
+ installed: status !== "missing",
8570
+ status,
8571
+ rawOutput
8572
+ };
8573
+ };
8496
8574
  var probeUrl = async (url, options) => {
8497
8575
  const resolvedUrl = withProbePath(url, options?.probePath ?? "/health");
8498
8576
  if (!resolvedUrl) {
@@ -8845,6 +8923,109 @@ const killByPort = async (port) => {
8845
8923
  return pids
8846
8924
  }
8847
8925
 
8926
+ const resolveWindowsServiceControllerName = (serviceName) => {
8927
+ const normalizedName = String(serviceName || '').trim()
8928
+ if (!normalizedName) {
8929
+ return null
8930
+ }
8931
+ return normalizedName.replace(/[^\w]/g, '').toLowerCase() + '.exe'
8932
+ }
8933
+
8934
+ const queryWindowsServiceStatus = (serviceName) => {
8935
+ if (process.platform !== 'win32' || !serviceName) {
8936
+ return null
8937
+ }
8938
+ const normalizedName = String(serviceName).trim()
8939
+ const controllerName = resolveWindowsServiceControllerName(normalizedName) || normalizedName
8940
+ const escapedName = normalizedName.replace(/'/g, "''")
8941
+ const escapedControllerName = controllerName.replace(/'/g, "''")
8942
+ const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', [
8943
+ "$service = Get-Service -Name '" + escapedControllerName + "' -ErrorAction SilentlyContinue",
8944
+ "if ($null -eq $service) { $service = Get-Service -DisplayName '" + escapedName + "' -ErrorAction SilentlyContinue }",
8945
+ "if ($null -eq $service) { '__MISSING__' } else { @{ Name = $service.Name; DisplayName = $service.DisplayName; Status = $service.Status.ToString() } | ConvertTo-Json -Compress }",
8946
+ ].join('; ')], {
8947
+ encoding: 'utf8',
8948
+ timeout: 5000,
8949
+ windowsHide: true,
8950
+ })
8951
+ const rawOutput = (String(result.stdout || '') + '\n' + String(result.stderr || '')).trim()
8952
+ let resolvedDisplayName = normalizedName
8953
+ let resolvedControllerName = controllerName
8954
+ let normalizedOutput = rawOutput.trim().toLowerCase()
8955
+ try {
8956
+ const parsed = JSON.parse(rawOutput)
8957
+ resolvedDisplayName = String(parsed.DisplayName || '').trim() || resolvedDisplayName
8958
+ resolvedControllerName = String(parsed.Name || '').trim() || resolvedControllerName
8959
+ normalizedOutput = (String(parsed.Status || '').trim() || rawOutput).trim().toLowerCase()
8960
+ } catch {}
8961
+ const status =
8962
+ normalizedOutput === '__missing__'
8963
+ ? 'missing'
8964
+ : normalizedOutput === 'running'
8965
+ ? 'running'
8966
+ : normalizedOutput === 'stopped'
8967
+ ? 'stopped'
8968
+ : 'unknown'
8969
+ return {
8970
+ displayName: resolvedDisplayName,
8971
+ controllerName: resolvedControllerName,
8972
+ installed: status !== 'missing',
8973
+ status,
8974
+ rawOutput,
8975
+ }
8976
+ }
8977
+
8978
+ const waitForWindowsServiceStatus = async (serviceName, desiredStatus, timeoutMs) => {
8979
+ const startedAt = Date.now()
8980
+ let latest = queryWindowsServiceStatus(serviceName)
8981
+ while (latest && latest.status !== desiredStatus && Date.now() - startedAt < timeoutMs) {
8982
+ await wait(750)
8983
+ latest = queryWindowsServiceStatus(serviceName)
8984
+ }
8985
+ return latest
8986
+ }
8987
+
8988
+ const stopWindowsService = async (serviceName) => {
8989
+ const current = queryWindowsServiceStatus(serviceName)
8990
+ if (!current || !current.installed || current.status === 'stopped') {
8991
+ return current
8992
+ }
8993
+ const escapedName = String(current.controllerName || serviceName).replace(/'/g, "''")
8994
+ const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', "Stop-Service -Name '" + escapedName + "' -Force -ErrorAction Stop"], {
8995
+ encoding: 'utf8',
8996
+ timeout: 5000,
8997
+ windowsHide: true,
8998
+ })
8999
+ const output = (String(result.stdout || '') + '\n' + String(result.stderr || '')).trim()
9000
+ const latest = await waitForWindowsServiceStatus(serviceName, 'stopped', 20000)
9001
+ if (!latest || latest.status !== 'stopped') {
9002
+ throw new Error(output || '停止 Windows 服务失败。')
9003
+ }
9004
+ return latest
9005
+ }
9006
+
9007
+ const startWindowsService = async (serviceName) => {
9008
+ const current = queryWindowsServiceStatus(serviceName)
9009
+ if (!current || !current.installed) {
9010
+ throw new Error('Windows 服务不存在。')
9011
+ }
9012
+ if (current.status === 'running') {
9013
+ return current
9014
+ }
9015
+ const escapedName = String(current.controllerName || serviceName).replace(/'/g, "''")
9016
+ const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', "Start-Service -Name '" + escapedName + "' -ErrorAction Stop"], {
9017
+ encoding: 'utf8',
9018
+ timeout: 5000,
9019
+ windowsHide: true,
9020
+ })
9021
+ const output = (String(result.stdout || '') + '\n' + String(result.stderr || '')).trim()
9022
+ const latest = await waitForWindowsServiceStatus(serviceName, 'running', 20000)
9023
+ if (!latest || latest.status !== 'running') {
9024
+ throw new Error(output || '启动 Windows 服务失败。')
9025
+ }
9026
+ return latest
9027
+ }
9028
+
8848
9029
  const probe = async (url, probePath) => {
8849
9030
  if (!url) {
8850
9031
  return { ok: false, message: '未配置地址。' }
@@ -8957,11 +9138,30 @@ const main = async () => {
8957
9138
  await wait(payload.stopDelayMs || 1500)
8958
9139
 
8959
9140
  for (const service of payload.stopServices) {
8960
- const pids = await killByPort(service.port)
8961
- if (pids.length > 0) {
8962
- await appendLog(jobPath, 'info', '已停止 ' + service.label + ',PID: ' + pids.join(', '))
9141
+ if (service.serviceName) {
9142
+ const status = await stopWindowsService(service.serviceName)
9143
+ await appendLog(
9144
+ jobPath,
9145
+ 'info',
9146
+ status && status.installed
9147
+ ? '已停止 Windows 服务 ' + service.label + '(' + service.serviceName + ')。'
9148
+ : service.label + ' 对应的 Windows 服务未安装,回退到端口停止。',
9149
+ )
9150
+ if (!status || !status.installed) {
9151
+ const pids = await killByPort(service.port)
9152
+ if (pids.length > 0) {
9153
+ await appendLog(jobPath, 'info', '已停止 ' + service.label + ',PID: ' + pids.join(', '))
9154
+ } else {
9155
+ await appendLog(jobPath, 'info', service.label + ' 当前没有监听中的端口进程。')
9156
+ }
9157
+ }
8963
9158
  } else {
8964
- await appendLog(jobPath, 'info', service.label + ' 当前没有监听中的端口进程。')
9159
+ const pids = await killByPort(service.port)
9160
+ if (pids.length > 0) {
9161
+ await appendLog(jobPath, 'info', '已停止 ' + service.label + ',PID: ' + pids.join(', '))
9162
+ } else {
9163
+ await appendLog(jobPath, 'info', service.label + ' 当前没有监听中的端口进程。')
9164
+ }
8965
9165
  }
8966
9166
  }
8967
9167
 
@@ -8976,13 +9176,29 @@ const main = async () => {
8976
9176
 
8977
9177
  const probeResults = []
8978
9178
  for (const service of payload.startServices) {
8979
- try {
8980
- await startDetached(jobPath, service.command, service.label)
8981
- } catch (error) {
8982
- const message = error instanceof Error ? error.message : String(error)
8983
- await appendLog(jobPath, 'error', '启动 ' + service.label + ' 失败:' + message)
8984
- probeResults.push({ label: service.label, ok: false, message })
8985
- continue
9179
+ if (service.serviceName) {
9180
+ try {
9181
+ await startWindowsService(service.serviceName)
9182
+ await appendLog(
9183
+ jobPath,
9184
+ 'info',
9185
+ '已启动 Windows 服务 ' + service.label + '(' + service.serviceName + ')。',
9186
+ )
9187
+ } catch (error) {
9188
+ const message = error instanceof Error ? error.message : String(error)
9189
+ await appendLog(jobPath, 'error', '启动 ' + service.label + ' 失败:' + message)
9190
+ probeResults.push({ label: service.label, ok: false, message })
9191
+ continue
9192
+ }
9193
+ } else {
9194
+ try {
9195
+ await startDetached(jobPath, service.command, service.label)
9196
+ } catch (error) {
9197
+ const message = error instanceof Error ? error.message : String(error)
9198
+ await appendLog(jobPath, 'error', '启动 ' + service.label + ' 失败:' + message)
9199
+ probeResults.push({ label: service.label, ok: false, message })
9200
+ continue
9201
+ }
8986
9202
  }
8987
9203
 
8988
9204
  const probeResult = await waitForProbe(service.probeUrl, service.probePath, service.timeoutMs)
@@ -9125,6 +9341,50 @@ var createDevManager = ({
9125
9341
  );
9126
9342
  const buildReleaseHubCommand = (config) => buildShellCommand("panda hub", config.release_hub_args);
9127
9343
  const buildReleaseAgentCommand = (config) => buildShellCommand("panda agent", config.release_agent_args);
9344
+ const buildReleaseHubEnvOverrides = (config) => ({
9345
+ PANDA_HUB_PORT: config.release_hub_port ? String(config.release_hub_port) : null
9346
+ });
9347
+ const buildReleaseAgentEnvOverrides = (config) => ({
9348
+ PANDA_AGENT_PORT: config.release_agent_port ? String(config.release_agent_port) : null,
9349
+ PANDA_HUB_URL: config.release_agent_hub_url,
9350
+ PANDA_AGENT_DIRECT_BASE_URL: config.release_agent_direct_base_url,
9351
+ PANDA_AGENT_WS_BASE_URL: config.release_agent_ws_base_url,
9352
+ PANDA_AGENT_NAME: normalizeNullableText(config.release_agent_name)
9353
+ });
9354
+ const validateWindowsServiceName = (value, label) => {
9355
+ if (!/^[A-Za-z0-9._() \-]+$/.test(value)) {
9356
+ throw new Error(`${label} \u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u7A7A\u683C\u3001\u70B9\u3001\u4E0B\u5212\u7EBF\u3001\u5706\u62EC\u53F7\u548C\u8FDE\u5B57\u7B26\u3002`);
9357
+ }
9358
+ };
9359
+ const quoteCommandArg = (value) => {
9360
+ const normalized = value.trim();
9361
+ if (!normalized) {
9362
+ return '""';
9363
+ }
9364
+ return `"${normalized.replace(/"/g, '\\"')}"`;
9365
+ };
9366
+ const buildReleaseHubServiceInstallCommand = (config) => {
9367
+ validateWindowsServiceName(config.release_hub_service_name, "\u6B63\u5F0F\u7248 Hub \u670D\u52A1\u540D");
9368
+ const command = `panda hub service install --name=${quoteCommandArg(
9369
+ config.release_hub_service_name
9370
+ )}`;
9371
+ return buildShellCommand(command, config.release_hub_args);
9372
+ };
9373
+ const buildReleaseAgentServiceInstallCommand = (config) => {
9374
+ validateWindowsServiceName(config.release_agent_service_name, "\u6B63\u5F0F\u7248 Agent \u670D\u52A1\u540D");
9375
+ const command = `panda agent service install --name=${quoteCommandArg(
9376
+ config.release_agent_service_name
9377
+ )}`;
9378
+ return buildShellCommand(command, config.release_agent_args);
9379
+ };
9380
+ const buildReleaseHubServiceUninstallCommand = (config) => {
9381
+ validateWindowsServiceName(config.release_hub_service_name, "\u6B63\u5F0F\u7248 Hub \u670D\u52A1\u540D");
9382
+ return `panda hub service uninstall --name=${quoteCommandArg(config.release_hub_service_name)}`;
9383
+ };
9384
+ const buildReleaseAgentServiceUninstallCommand = (config) => {
9385
+ validateWindowsServiceName(config.release_agent_service_name, "\u6B63\u5F0F\u7248 Agent \u670D\u52A1\u540D");
9386
+ return `panda agent service uninstall --name=${quoteCommandArg(config.release_agent_service_name)}`;
9387
+ };
9128
9388
  const resolveServiceCommand = async (config, key) => {
9129
9389
  const projectPath = getExecutionRoot(config);
9130
9390
  if (key.startsWith("dev-")) {
@@ -9171,22 +9431,14 @@ var createDevManager = ({
9171
9431
  projectPath,
9172
9432
  command: buildReleaseHubCommand(config),
9173
9433
  nodeVersion: config.nvm_version,
9174
- env: {
9175
- PANDA_HUB_PORT: config.release_hub_port ? String(config.release_hub_port) : null
9176
- }
9434
+ env: buildReleaseHubEnvOverrides(config)
9177
9435
  });
9178
9436
  }
9179
9437
  return resolveManagedCommand({
9180
9438
  projectPath,
9181
9439
  command: buildReleaseAgentCommand(config),
9182
9440
  nodeVersion: config.nvm_version,
9183
- env: {
9184
- PANDA_AGENT_PORT: config.release_agent_port ? String(config.release_agent_port) : null,
9185
- PANDA_HUB_URL: config.release_agent_hub_url,
9186
- PANDA_AGENT_DIRECT_BASE_URL: config.release_agent_direct_base_url,
9187
- PANDA_AGENT_WS_BASE_URL: config.release_agent_ws_base_url,
9188
- PANDA_AGENT_NAME: normalizeNullableText(config.release_agent_name)
9189
- }
9441
+ env: buildReleaseAgentEnvOverrides(config)
9190
9442
  });
9191
9443
  };
9192
9444
  const getServiceSpecs = (config) => [
@@ -9220,6 +9472,7 @@ var createDevManager = ({
9220
9472
  port: config.release_hub_port,
9221
9473
  probeUrl: buildHttpBaseUrl(config.release_hub_port, ""),
9222
9474
  probePath: "/health",
9475
+ serviceName: config.release_hub_service_name,
9223
9476
  start: () => resolveServiceCommand(config, "release-hub")
9224
9477
  },
9225
9478
  {
@@ -9228,18 +9481,28 @@ var createDevManager = ({
9228
9481
  port: config.release_agent_port,
9229
9482
  probeUrl: config.release_agent_direct_base_url,
9230
9483
  probePath: "/health",
9484
+ serviceName: config.release_agent_service_name,
9231
9485
  start: () => resolveServiceCommand(config, "release-agent")
9232
9486
  }
9233
9487
  ];
9234
9488
  const buildServiceSnapshots = async (config, options) => await Promise.all(
9235
9489
  getServiceSpecs(config).map(async (service) => {
9236
9490
  const quickProbe = await probeLocalPort(service.port);
9237
- const status = quickProbe.ok ? "running" : service.port || service.probeUrl ? "stopped" : "unknown";
9491
+ const controllerStatus = queryWindowsServiceStatus(service.serviceName);
9492
+ const usesWindowsService = Boolean(
9493
+ service.key.startsWith("release-") && controllerStatus?.installed
9494
+ );
9495
+ const manager = usesWindowsService ? "windows-service" : "process";
9496
+ const status = usesWindowsService ? controllerStatus?.status === "running" ? quickProbe.ok ? "running" : "degraded" : controllerStatus?.status === "stopped" ? quickProbe.ok ? "degraded" : "stopped" : controllerStatus?.status === "missing" ? quickProbe.ok ? "running" : service.port || service.probeUrl ? "stopped" : "unknown" : quickProbe.ok ? "running" : "unknown" : quickProbe.ok ? "running" : service.port || service.probeUrl ? "stopped" : "unknown";
9238
9497
  if (options?.includeProbe !== true) {
9239
9498
  return {
9240
9499
  key: service.key,
9241
9500
  label: service.label,
9242
9501
  status,
9502
+ manager,
9503
+ service_name: service.serviceName ?? null,
9504
+ service_registered: controllerStatus?.installed ?? false,
9505
+ service_status: controllerStatus?.status ?? null,
9243
9506
  configured_port: service.port,
9244
9507
  detected_pids: [],
9245
9508
  probe: null
@@ -9249,6 +9512,10 @@ var createDevManager = ({
9249
9512
  key: service.key,
9250
9513
  label: service.label,
9251
9514
  status,
9515
+ manager,
9516
+ service_name: service.serviceName ?? null,
9517
+ service_registered: controllerStatus?.installed ?? false,
9518
+ service_status: controllerStatus?.status ?? null,
9252
9519
  configured_port: service.port,
9253
9520
  detected_pids: [],
9254
9521
  probe: quickProbe
@@ -9590,9 +9857,61 @@ var createDevManager = ({
9590
9857
  await job.succeed("APK \u7F16\u8BD1\u5B8C\u6210\u3002");
9591
9858
  });
9592
9859
  };
9860
+ const installReleaseServices = async () => {
9861
+ const { config } = await readStoredConfig(codexHome);
9862
+ const executionRoot = getExecutionRoot(config);
9863
+ return runManagedJob("release-service-install", "\u6CE8\u518C\u6216\u66F4\u65B0\u6B63\u5F0F\u7248\u670D\u52A1", async (job) => {
9864
+ if (process.platform !== "win32") {
9865
+ throw new Error("\u6B63\u5F0F\u7248\u670D\u52A1\u6CE8\u518C\u5F53\u524D\u53EA\u652F\u6301 Windows\u3002");
9866
+ }
9867
+ await job.append("info", "\u51C6\u5907\u6CE8\u518C\u6216\u66F4\u65B0\u6B63\u5F0F\u7248 Hub Windows \u670D\u52A1\u3002");
9868
+ const hubCommand = await resolveManagedCommand({
9869
+ projectPath: executionRoot,
9870
+ command: buildReleaseHubServiceInstallCommand(config),
9871
+ nodeVersion: config.nvm_version,
9872
+ env: buildReleaseHubEnvOverrides(config)
9873
+ });
9874
+ await runCommandWithLogs(hubCommand, job, "\u6CE8\u518C\u6B63\u5F0F\u7248 Hub \u670D\u52A1");
9875
+ await job.append("info", "\u51C6\u5907\u6CE8\u518C\u6216\u66F4\u65B0\u6B63\u5F0F\u7248 Agent Windows \u670D\u52A1\u3002");
9876
+ const agentCommand = await resolveManagedCommand({
9877
+ projectPath: executionRoot,
9878
+ command: buildReleaseAgentServiceInstallCommand(config),
9879
+ nodeVersion: config.nvm_version,
9880
+ env: buildReleaseAgentEnvOverrides(config)
9881
+ });
9882
+ await runCommandWithLogs(agentCommand, job, "\u6CE8\u518C\u6B63\u5F0F\u7248 Agent \u670D\u52A1");
9883
+ await job.succeed("\u6B63\u5F0F\u7248 Hub \u4E0E Agent \u670D\u52A1\u5DF2\u6CE8\u518C\u6216\u66F4\u65B0\u3002");
9884
+ });
9885
+ };
9886
+ const uninstallReleaseServices = async () => {
9887
+ const { config } = await readStoredConfig(codexHome);
9888
+ const executionRoot = getExecutionRoot(config);
9889
+ return runManagedJob("release-service-uninstall", "\u79FB\u9664\u6B63\u5F0F\u7248\u670D\u52A1", async (job) => {
9890
+ if (process.platform !== "win32") {
9891
+ throw new Error("\u6B63\u5F0F\u7248\u670D\u52A1\u79FB\u9664\u5F53\u524D\u53EA\u652F\u6301 Windows\u3002");
9892
+ }
9893
+ await job.append("info", "\u51C6\u5907\u79FB\u9664\u6B63\u5F0F\u7248 Agent Windows \u670D\u52A1\u3002");
9894
+ const agentCommand = await resolveManagedCommand({
9895
+ projectPath: executionRoot,
9896
+ command: buildReleaseAgentServiceUninstallCommand(config),
9897
+ nodeVersion: config.nvm_version
9898
+ });
9899
+ await runCommandWithLogs(agentCommand, job, "\u79FB\u9664\u6B63\u5F0F\u7248 Agent \u670D\u52A1");
9900
+ await job.append("info", "\u51C6\u5907\u79FB\u9664\u6B63\u5F0F\u7248 Hub Windows \u670D\u52A1\u3002");
9901
+ const hubCommand = await resolveManagedCommand({
9902
+ projectPath: executionRoot,
9903
+ command: buildReleaseHubServiceUninstallCommand(config),
9904
+ nodeVersion: config.nvm_version
9905
+ });
9906
+ await runCommandWithLogs(hubCommand, job, "\u79FB\u9664\u6B63\u5F0F\u7248 Hub \u670D\u52A1");
9907
+ await job.succeed("\u6B63\u5F0F\u7248 Hub \u4E0E Agent \u670D\u52A1\u5DF2\u79FB\u9664\u3002");
9908
+ });
9909
+ };
9593
9910
  const installLatestRelease = async () => {
9594
9911
  const { config } = await readStoredConfig(codexHome);
9595
9912
  const executionRoot = getExecutionRoot(config);
9913
+ const releaseHubServiceStatus = queryWindowsServiceStatus(config.release_hub_service_name);
9914
+ const releaseAgentServiceStatus = queryWindowsServiceStatus(config.release_agent_service_name);
9596
9915
  const installCommand = await resolveManagedCommand({
9597
9916
  projectPath: executionRoot,
9598
9917
  command: `npm install -g ${RELEASE_PACKAGE_NAME}@latest --registry=${NPM_REGISTRY_URL}`,
@@ -9624,17 +9943,20 @@ var createDevManager = ({
9624
9943
  stopServices: [
9625
9944
  {
9626
9945
  label: "\u6B63\u5F0F\u7248 Agent",
9627
- port: config.release_agent_port
9946
+ port: config.release_agent_port,
9947
+ serviceName: releaseAgentServiceStatus?.installed ? config.release_agent_service_name : null
9628
9948
  },
9629
9949
  {
9630
9950
  label: "\u6B63\u5F0F\u7248 Hub",
9631
- port: config.release_hub_port
9951
+ port: config.release_hub_port,
9952
+ serviceName: releaseHubServiceStatus?.installed ? config.release_hub_service_name : null
9632
9953
  }
9633
9954
  ],
9634
9955
  installCommand,
9635
9956
  startServices: [
9636
9957
  {
9637
9958
  label: "\u6B63\u5F0F\u7248 Hub",
9959
+ serviceName: releaseHubServiceStatus?.installed ? config.release_hub_service_name : null,
9638
9960
  command: releaseHubCommand,
9639
9961
  probeUrl: buildHttpBaseUrl(config.release_hub_port, ""),
9640
9962
  probePath: "/health",
@@ -9642,6 +9964,7 @@ var createDevManager = ({
9642
9964
  },
9643
9965
  {
9644
9966
  label: "\u6B63\u5F0F\u7248 Agent",
9967
+ serviceName: releaseAgentServiceStatus?.installed ? config.release_agent_service_name : null,
9645
9968
  command: releaseAgentCommand,
9646
9969
  probeUrl: config.release_agent_direct_base_url,
9647
9970
  probePath: "/health",
@@ -9681,7 +10004,7 @@ var createDevManager = ({
9681
10004
  };
9682
10005
  };
9683
10006
  const executeAction = async (action) => {
9684
- const job = action === "start-development" ? await runDevelopmentLifecycle("start") : action === "restart-development" ? await runDevelopmentLifecycle("restart") : action === "stop-development" ? await runDevelopmentLifecycle("stop") : action === "probe-development" ? await runDevelopmentLifecycle("probe") : action === "publish-npm" ? await runNpmPublish() : action === "build-apk" ? await runApkBuild() : await installLatestRelease();
10007
+ const job = action === "start-development" ? await runDevelopmentLifecycle("start") : action === "restart-development" ? await runDevelopmentLifecycle("restart") : action === "stop-development" ? await runDevelopmentLifecycle("stop") : action === "probe-development" ? await runDevelopmentLifecycle("probe") : action === "publish-npm" ? await runNpmPublish() : action === "install-release-services" ? await installReleaseServices() : action === "uninstall-release-services" ? await uninstallReleaseServices() : action === "build-apk" ? await runApkBuild() : await installLatestRelease();
9685
10008
  return {
9686
10009
  ok: true,
9687
10010
  job,
@@ -11241,7 +11564,7 @@ var startPandaSessionService = async ({
11241
11564
  return nextOverlayEntries;
11242
11565
  };
11243
11566
  const readTimelineFromRollout = async (sessionId) => {
11244
- const { readCodexTimeline } = await import("./src-EGC2EU26.mjs");
11567
+ const { readCodexTimeline } = await import("./src-M7W7LPHG.mjs");
11245
11568
  return readCodexTimeline(sessionId, {
11246
11569
  codexHome,
11247
11570
  sessionFiles: discoveredSessionFiles
@@ -12709,7 +13032,7 @@ var startPandaSessionService = async ({
12709
13032
  lastSnapshotRefreshAt = Date.now();
12710
13033
  return snapshot;
12711
13034
  }
12712
- const { discoverLocalCodexData } = await import("./src-EGC2EU26.mjs");
13035
+ const { discoverLocalCodexData } = await import("./src-M7W7LPHG.mjs");
12713
13036
  const discovery = await discoverLocalCodexData({
12714
13037
  agentId: localAgentId,
12715
13038
  agentName: localAgentName,