@heybox/hb-sdk 0.8.5 → 0.9.0-alpha.1

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 (29) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +2 -0
  3. package/dist/cli-chunks/{build-zYnPSPFa.cjs → build-BSeiFi0q.cjs} +5 -5
  4. package/dist/cli-chunks/{context-DWoKsM_O.cjs → context-D_L2-Hla.cjs} +2 -2
  5. package/dist/cli-chunks/{create-C9FcEn9Y.cjs → create-Dzeodh_-.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-VxeEocjt.cjs → dev-DQ7k_fu6.cjs} +119 -48
  7. package/dist/cli-chunks/{doctor-6Cf9o1hD.cjs → doctor-FIcFZiJ0.cjs} +1 -1
  8. package/dist/cli-chunks/{index-BpLYUwNe.cjs → index-CakUTosC.cjs} +2 -2
  9. package/dist/cli-chunks/{index-Brq_-WV3.cjs → index-d1cUUoR9.cjs} +15 -15
  10. package/dist/cli-chunks/{index.esm-DMvmTtIS.cjs → index.esm-CerEC339.cjs} +7 -7
  11. package/dist/cli-chunks/{login-DeZpNrkY.cjs → login-lZKDg_9k.cjs} +2 -2
  12. package/dist/cli-chunks/{project-vite-TFEwLzYM.cjs → project-vite-BM7D-pf6.cjs} +1 -1
  13. package/dist/cli-chunks/{remote-DOU11SNj.cjs → remote-9-tyxq6l.cjs} +7 -7
  14. package/dist/cli-chunks/{runtime-gate-DikSEEtm.cjs → runtime-gate-BfAGMrE2.cjs} +1 -1
  15. package/dist/cli-chunks/{runtime-permission-env-Bs_JqpTM.cjs → runtime-permission-env-B-GwDDT7.cjs} +1 -1
  16. package/dist/cli-chunks/{session-CbisOcE4.cjs → session-mMVA3395.cjs} +1 -1
  17. package/dist/cli-chunks/{skill-B82gXQhX.cjs → skill-BhUlKvyY.cjs} +2 -2
  18. package/dist/cli-chunks/{version-UEmqQ6A-.cjs → version-hsUgRL0I.cjs} +1 -1
  19. package/dist/cli.cjs +1 -1
  20. package/dist/devtools/browser-dev-host/assets/{browser-dev-host-MOwoKrAP.js → browser-dev-host-BLgy6aQ6.js} +1 -1
  21. package/dist/devtools/browser-dev-host/assets/{desktop-app-launch-BcQjavzW.js → desktop-app-launch-D32fpjYQ.js} +1 -1
  22. package/dist/devtools/browser-dev-host/assets/{index-CS-AbGTE.js → index-DMuA2oZx.js} +3 -3
  23. package/dist/devtools/browser-dev-host/index.html +2 -2
  24. package/dist/index.cjs.js +1 -1
  25. package/dist/index.esm.js +1 -1
  26. package/dist/vite.cjs.js +1 -1
  27. package/dist/vite.esm.js +1 -1
  28. package/package.json +6 -6
  29. package/skill/skill.json +4 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  本文件随 `@heybox/hb-sdk` npm 包公开分发,只记录外部小程序开发者可观察到的变化。
4
4
 
5
+ ## 0.9.0-alpha.1 - 2026-10-07
6
+
7
+ > 影响级别:常规
8
+ > 开发者动作:Android 扫码调试用户升级至 `0.9.0-alpha.1` 后重新运行 `hb-sdk dev`。
9
+ > 兼容范围:公开 SDK API 不变;补齐真实客户端扫码调试启动协议中的小程序身份字段,保留原有兼容字段。
10
+
11
+ ### 问题修复
12
+
13
+ - 修复 CLI 扫码启动协议缺少 `user_miniprogram_id`,导致 Android 1.3.396 调试容器可能无法使用原生桥接能力的问题。
14
+
15
+ ## 0.9.0-alpha.0 - 2026-10-06
16
+
17
+ > 影响级别:常规
18
+ > 开发者动作:升级至 `0.9.0-alpha.0` 并重新运行 `hb-sdk dev`;运行期间在后台调整权限后,请重启 CLI 同步。
19
+ > 兼容范围:公开 SDK API 不变;调整本地真实客户端调试的详情读取与启动票可用条件。
20
+
21
+ ### 行为调整
22
+
23
+ - `hb-sdk dev` 只在启动、绑定或权限声明变化时读取远端详情;手机重连复用成功快照,票据续签不再触发详情轮询。
24
+ - 权限校验期间或失败后,真实客户端启动票暂停提供;更新声明并校验成功或重启 CLI 后恢复。
25
+
26
+ ### 问题修复
27
+
28
+ - 修复仅修改小程序绑定时未触发详情校验的问题。
29
+ - 修复声明连续变化时,旧详情、产物摘要或启动票覆盖最新调试状态的问题。
30
+ - 修复详情校验失败后仍能通过本地启动入口获取续签票据的问题。
31
+
5
32
  ## 0.8.5 - 2026-10-02
6
33
 
7
34
  > 影响级别:必须操作
package/README.md CHANGED
@@ -26,6 +26,8 @@ npm run dev
26
26
 
27
27
  脚手架已包含 SDK、Vite 配置和常用开发命令。浏览器调试通过后,再从调试台的二维码入口使用手机小黑盒 APP 验收。
28
28
 
29
+ 手机二维码的短链先打开普通中间页,再进入专用 dev-shell。最终 `openWindow` 协议会把 CLI 校验后的用户小程序字符串 ID(UUID)写入根字段 `user_miniprogram_id`;原生不会从 URL 查询参数补读,也不能用传统小程序数字 ID 代替。匿名调试不生成小程序 ID,原生未开放的横屏能力仍返回 `supported: false`。入口排查与验证记录见 [Android 396 入口审计](./docs/android-396-launch-identity.md)。
30
+
29
31
  当前 `0.8` 已发布为稳定版本,创建命令使用 `@latest`。
30
32
 
31
33
  ### 接入已有项目
@@ -3,13 +3,13 @@
3
3
  var fs$1 = require('node:fs');
4
4
  var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
- var runtimePermissionEnv = require('./runtime-permission-env-Bs_JqpTM.cjs');
6
+ var runtimePermissionEnv = require('./runtime-permission-env-B-GwDDT7.cjs');
7
7
  require('node:crypto');
8
8
  require('node:zlib');
9
- var runtimeGate = require('./runtime-gate-DikSEEtm.cjs');
10
- var index = require('./index-Brq_-WV3.cjs');
11
- var projectVite = require('./project-vite-TFEwLzYM.cjs');
12
- require('./version-UEmqQ6A-.cjs');
9
+ var runtimeGate = require('./runtime-gate-BfAGMrE2.cjs');
10
+ var index = require('./index-d1cUUoR9.cjs');
11
+ var projectVite = require('./project-vite-BM7D-pf6.cjs');
12
+ require('./version-hsUgRL0I.cjs');
13
13
  require('node:async_hooks');
14
14
  require('node:module');
15
15
  require('path');
@@ -3,10 +3,10 @@
3
3
  var node_crypto = require('node:crypto');
4
4
  var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
- var runtimePermissionEnv = require('./runtime-permission-env-Bs_JqpTM.cjs');
6
+ var runtimePermissionEnv = require('./runtime-permission-env-B-GwDDT7.cjs');
7
7
  var fs$1 = require('node:fs');
8
8
  var sharp = require('sharp');
9
- var session = require('./session-CbisOcE4.cjs');
9
+ var session = require('./session-mMVA3395.cjs');
10
10
 
11
11
  async function inspectBasicInfoImage(bytes, sourcePath, kind) {
12
12
  const inspected = await inspectDecodedBasicInfoImage(bytes, kind);
@@ -5,7 +5,7 @@ var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
6
  var require$$0 = require('fs');
7
7
  var require$$1 = require('path');
8
- var index = require('./index-Brq_-WV3.cjs');
8
+ var index = require('./index-d1cUUoR9.cjs');
9
9
  require('node:module');
10
10
  require('os');
11
11
  require('node:crypto');
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var context = require('./context-DWoKsM_O.cjs');
3
+ var context = require('./context-D_L2-Hla.cjs');
4
4
  var node_crypto = require('node:crypto');
5
5
  var fs = require('node:fs');
6
- var runtimePermissionEnv = require('./runtime-permission-env-Bs_JqpTM.cjs');
6
+ var runtimePermissionEnv = require('./runtime-permission-env-B-GwDDT7.cjs');
7
7
  var os = require('node:os');
8
8
  var path = require('node:path');
9
9
  var net = require('node:net');
@@ -12,13 +12,13 @@ var node_http = require('node:http');
12
12
  var node_module = require('node:module');
13
13
  var undici = require('undici');
14
14
  var fs$1 = require('node:fs/promises');
15
- var index = require('./index-Brq_-WV3.cjs');
15
+ var index = require('./index-d1cUUoR9.cjs');
16
16
  var require$$0$1 = require('crypto');
17
17
  var require$$0 = require('buffer');
18
- var session = require('./session-CbisOcE4.cjs');
18
+ var session = require('./session-mMVA3395.cjs');
19
19
  var browser = require('./browser-RAy8e8cV.cjs');
20
- var projectVite = require('./project-vite-TFEwLzYM.cjs');
21
- var version = require('./version-UEmqQ6A-.cjs');
20
+ var projectVite = require('./project-vite-BM7D-pf6.cjs');
21
+ var version = require('./version-hsUgRL0I.cjs');
22
22
  require('node:zlib');
23
23
 
24
24
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -1871,7 +1871,8 @@ function createMiniProgramDevShellOpenWindowPayload(appUrl, options) {
1871
1871
  protocol_type: 'openWindow',
1872
1872
  full_screen: true,
1873
1873
  mini_program: '1',
1874
- ...(miniProgramId ? { mini_program_id: miniProgramId } : {}),
1874
+ // 保留既有调试入口字段,同时通过 user_miniprogram_id 传递用户小程序字符串 ID(UUID);原生不会从 URL 补读。
1875
+ ...(miniProgramId ? { mini_program_id: miniProgramId, user_miniprogram_id: miniProgramId } : {}),
1875
1876
  navigation_bar: { title: '' },
1876
1877
  webview: { url: devShellUrl, pull: false, refresh: false },
1877
1878
  };
@@ -1992,8 +1993,8 @@ const DEFAULT_CHANNEL_TRANSPORT = {
1992
1993
  };
1993
1994
  const IPV4_LOOPBACK_ADDRESSES = new net.BlockList();
1994
1995
  IPV4_LOOPBACK_ADDRESSES.addSubnet('127.0.0.0', 8, 'ipv4');
1995
- const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-VxeEocjt.cjs', document.baseURI).href)))('ws');
1996
- const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-VxeEocjt.cjs', document.baseURI).href)))('parse5');
1996
+ const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-DQ7k_fu6.cjs', document.baseURI).href)))('ws');
1997
+ const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-DQ7k_fu6.cjs', document.baseURI).href)))('parse5');
1997
1998
  const BROWSER_DEV_HOST_ROOT_CANDIDATES = [
1998
1999
  path.resolve(__dirname, 'devtools/browser-dev-host'),
1999
2000
  path.resolve(__dirname, '../devtools/browser-dev-host'),
@@ -5065,9 +5066,9 @@ const DEFAULT_RUNTIME_SANITIZE_MAX_DEPTH = 6;
5065
5066
  */
5066
5067
  const DEFAULT_RUNTIME_SANITIZE_MAX_ARRAY_LENGTH = 50;
5067
5068
  /**
5068
- * 默认最大字符串长度。
5069
+ * 默认不限制字符串长度,避免截断诊断 JSON 或错误现场。
5069
5070
  */
5070
- const DEFAULT_RUNTIME_SANITIZE_MAX_STRING_LENGTH = 2000;
5071
+ const DEFAULT_RUNTIME_SANITIZE_MAX_STRING_LENGTH = Number.POSITIVE_INFINITY;
5071
5072
  /**
5072
5073
  * 判断未知值是否为普通对象。
5073
5074
  *
@@ -9448,10 +9449,6 @@ const MAX_DEPTH = 6;
9448
9449
  * 最大数组长度。
9449
9450
  */
9450
9451
  const MAX_ARRAY_LENGTH = 50;
9451
- /**
9452
- * 最大字符串长度。
9453
- */
9454
- const MAX_STRING_LENGTH = 2000;
9455
9452
  /**
9456
9453
  * 安全序列化错误对象。
9457
9454
  *
@@ -9493,7 +9490,7 @@ function sanitizeTelemetryValue(value, key = '', depth = 0, seen = new WeakSet()
9493
9490
  return value;
9494
9491
  }
9495
9492
  if (typeof value === 'string') {
9496
- return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH)}...` : value;
9493
+ return value;
9497
9494
  }
9498
9495
  if (typeof value === 'bigint') {
9499
9496
  return value.toString();
@@ -11092,7 +11089,7 @@ function assertNodeRuntimeCapabilities(ctx, settings) {
11092
11089
  function createNodeRealtimeDriverRegistry() {
11093
11090
  const registry = createRuntimeRealtimeDriverRegistry();
11094
11091
  registry.registerLazy('node-websocket', async () => {
11095
- const mod = await Promise.resolve().then(function () { return require('./index.esm-DMvmTtIS.cjs'); });
11092
+ const mod = await Promise.resolve().then(function () { return require('./index.esm-CerEC339.cjs'); });
11096
11093
  return mod.createNodeWebSocketRuntimeDriver();
11097
11094
  });
11098
11095
  return registry;
@@ -15218,7 +15215,7 @@ const COMPANION_ARTIFACT_ACCESS_TOKEN_ENV = 'HB_SDK_COMPANION_ARTIFACT_ACCESS_TO
15218
15215
  // 本地 PC 调试 manifest 的专用令牌:开窗时扩展还没拿到 LocalDevelopmentBinding,只能靠启动 URI 带来。
15219
15216
  const MINI_PROGRAM_MANIFEST_ACCESS_TOKEN_ENV = 'HB_SDK_MINI_PROGRAM_MANIFEST_ACCESS_TOKEN';
15220
15217
  const COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER = 'x-hb-sdk-companion-dev-token';
15221
- /** Dev Context 结果缓存 TTL 默认值;手机端重连不再 1:1 产生远端请求。 */
15218
+ /** 失败上下文的缓存窗口;成功上下文复用到绑定或声明变化。 */
15222
15219
  const DEFAULT_DEV_CONTEXT_CACHE_TTL_MS = 30_000;
15223
15220
  /** 覆盖 Dev Context 缓存 TTL(毫秒);设为 0 关闭缓存。 */
15224
15221
  const DEV_CONTEXT_CACHE_TTL_ENV = 'HB_SDK_DEV_CONTEXT_TTL_MS';
@@ -15244,7 +15241,10 @@ const DEV_CONTEXT_RATE_LIMIT_CACHE_MAX_MS = 300_000;
15244
15241
  * `hb-sdk dev` 启动拖住几分钟却依然恢复不了。
15245
15242
  */
15246
15243
  const DEV_CONTEXT_STARTUP_RATE_LIMIT_WAIT_MAX_MS = 30_000;
15247
- /** 读取 Dev Context 缓存 TTL;非法值回落到默认 30s,0 表示关闭缓存。 */
15244
+ /** 新声明已替代在途请求;旧结果不应用,也不作为远端失败提示。 */
15245
+ class DevContextSupersededError extends Error {
15246
+ }
15247
+ /** 读取 Dev Context 缓存 TTL;非法值回落到默认 30 秒,0 表示关闭缓存。 */
15248
15248
  function resolveDevContextCacheTtlMs(env = process.env) {
15249
15249
  const raw = env[DEV_CONTEXT_CACHE_TTL_ENV];
15250
15250
  if (raw === undefined || raw.trim() === '')
@@ -15394,12 +15394,16 @@ async function runDevCommand(options, runtime = {}) {
15394
15394
  * 读取 Dev Context。
15395
15395
  *
15396
15396
  * key 里带上当前 `package.json` 绑定的 miniProgramId:项目绑定变更会直接换成另一个缓存 key,
15397
- * 立即重新请求远端,而不是等 30s TTL(限流时 300s)过期后才发现换了小程序。
15397
+ * 立即重新请求远端,而不是等缓存过期后才发现换了小程序。
15398
15398
  * 读绑定是本地文件读取,不打远端,符合「只读本地快照」的高频读范式。
15399
15399
  */
15400
- const readDevLaunchContext = async () => {
15400
+ const readDevLaunchContext = async (reuseSnapshot = false) => {
15401
15401
  const boundMiniProgramId = await context.readBoundMiniProgramId(projectRoot).catch(() => undefined);
15402
- return devContextCache.read(`${projectRoot}\0${boundMiniProgramId ?? ''}`);
15402
+ const key = `${projectRoot}\0${boundMiniProgramId ?? ''}`;
15403
+ const snapshot = reuseSnapshot ? devContextCache.peek(key) : undefined;
15404
+ if (snapshot?.context.source === 'remote' && !snapshot.context.rateLimit?.limited)
15405
+ return snapshot;
15406
+ return devContextCache.read(key);
15403
15407
  };
15404
15408
  let { context: devLaunchContext, miniProgram } = await readDevLaunchContext();
15405
15409
  let lastRateLimitHint = '';
@@ -15435,6 +15439,7 @@ async function runDevCommand(options, runtime = {}) {
15435
15439
  }
15436
15440
  }
15437
15441
  let currentDevContext = devLaunchContext;
15442
+ let devContextGeneration = 0;
15438
15443
  let contextRevision = 1;
15439
15444
  let launchRuntimePermissions = context.createMobileDevSessionRuntimePermissions(devLaunchContext);
15440
15445
  const mobileDevContextUrlsByOrigin = new Map();
@@ -15569,7 +15574,10 @@ async function runDevCommand(options, runtime = {}) {
15569
15574
  },
15570
15575
  readMobileDevSessionContext: async () => {
15571
15576
  if (currentDevContext.source === 'remote') {
15572
- const refreshed = await readDevLaunchContext();
15577
+ const generation = devContextGeneration;
15578
+ const refreshed = await readDevLaunchContext(true);
15579
+ if (generation !== devContextGeneration)
15580
+ return currentDevContext;
15573
15581
  if (refreshed.context.rateLimit?.limited) {
15574
15582
  // 被限流时不再替换上下文,只透出限流状态;手机端据此停止重连。
15575
15583
  applyRateLimitState(refreshed.context.rateLimit);
@@ -15613,7 +15621,10 @@ async function runDevCommand(options, runtime = {}) {
15613
15621
  companionDeclared: currentDevContext.declaredPermissions?.companion?.enabled === true && runtimePermissionEnv.hasMiniappDesktopPlatform(currentDevContext.platforms),
15614
15622
  miniProgramId: devLaunchContext.miniProgramId,
15615
15623
  refreshPermissions: async () => {
15624
+ const generation = devContextGeneration;
15616
15625
  const refreshed = await readDevLaunchContext();
15626
+ if (generation !== devContextGeneration)
15627
+ throw new DevContextSupersededError();
15617
15628
  // 限流必须原样抛出,才能走 30s 起、上限 300s 的退避与「请勿反复重启」提示。
15618
15629
  if (refreshed.context.rateLimit?.limited) {
15619
15630
  throw index.createCliRateLimitedErrorFromState(refreshed.context.rateLimit);
@@ -15644,7 +15655,10 @@ async function runDevCommand(options, runtime = {}) {
15644
15655
  },
15645
15656
  onReadiness: devReadiness => browserDevHost.updateBootstrap({ devReadiness }),
15646
15657
  // 主动失效:项目绑定变更或开发者点重试后立即重新请求(key 会带上新绑定,这里清空全部)。
15647
- onInvalidate: () => devContextCache.invalidate(),
15658
+ onInvalidate: () => {
15659
+ devContextGeneration += 1;
15660
+ devContextCache.invalidate();
15661
+ },
15648
15662
  // 限流状态与恢复都要透出给调试页与宿主页。
15649
15663
  onRateLimit: state => applyRateLimitState(state),
15650
15664
  projectRoot,
@@ -15674,10 +15688,11 @@ async function runDevCommand(options, runtime = {}) {
15674
15688
  * `refresh()` 会清空缓存并立刻打一次远端 `detail`(密集写入可达数十次/分钟)。
15675
15689
  */
15676
15690
  let lastDeclarationFingerprint;
15691
+ const readBindingFingerprint = () => context.readBoundMiniProgramIdFromPackageJson(JSON.parse(fs.readFileSync(packagePath, 'utf8')));
15677
15692
  const readDeclarationFingerprint = () => JSON.stringify([
15678
15693
  runtimePermissionEnv.readMiniappPermissionsFromPackageJson(projectRoot, runtimePermissionEnv.resolveMiniappSdkVersion()).permissions ?? null,
15679
15694
  runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot),
15680
- context.readBoundMiniProgramId(projectRoot),
15695
+ readBindingFingerprint(),
15681
15696
  ]);
15682
15697
  // 先记录启动时已生效的声明,这样与调试声明无关的写入(版本号、依赖等)不会触发远端刷新。
15683
15698
  try {
@@ -15692,7 +15707,7 @@ async function runDevCommand(options, runtime = {}) {
15692
15707
  const local = runtimePermissionEnv.readMiniappPermissionsFromPackageJson(projectRoot, runtimePermissionEnv.resolveMiniappSdkVersion());
15693
15708
  const platforms = runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot);
15694
15709
  // 与调试声明无关的写入(版本号、依赖等)不触发远端刷新。
15695
- const fingerprint = JSON.stringify([local.permissions ?? null, platforms, context.readBoundMiniProgramId(projectRoot)]);
15710
+ const fingerprint = JSON.stringify([local.permissions ?? null, platforms, readBindingFingerprint()]);
15696
15711
  if (fingerprint === lastDeclarationFingerprint)
15697
15712
  return;
15698
15713
  lastDeclarationFingerprint = fingerprint;
@@ -16093,14 +16108,17 @@ function startLocalDevLaunchTokenSession(options) {
16093
16108
  let hasReportedTokenFailure = false;
16094
16109
  let refreshTimer;
16095
16110
  let companionDeclared = options.companionDeclared;
16096
- let permissionsRefreshAt = Date.now() + 30_000;
16111
+ let permissionsRefreshPending = false;
16112
+ let permissionsRefreshFailure;
16113
+ let permissionsGeneration = 0;
16114
+ let validatedPermissionsGeneration = 0;
16097
16115
  let publishedToken;
16098
16116
  let publishedDigest;
16099
16117
  let ticket;
16100
16118
  let pending;
16101
16119
  let refreshing = false;
16102
16120
  let consecutiveFailures = 0;
16103
- const getTicket = async (companionManifestSha256) => {
16121
+ const acquireTicket = async (companionManifestSha256) => {
16104
16122
  if (closed)
16105
16123
  return undefined;
16106
16124
  if (ticket && (companionManifestSha256 === undefined || ticket.companionManifestSha256 === companionManifestSha256) && ticket.refreshAt > Date.now())
@@ -16110,11 +16128,14 @@ function startLocalDevLaunchTokenSession(options) {
16110
16128
  const issueTicket = (companionManifestSha256) => {
16111
16129
  if (pending && pending.digest === companionManifestSha256)
16112
16130
  return pending.promise;
16131
+ const generation = permissionsGeneration;
16113
16132
  const promise = issueLocalDevLaunchToken({
16114
16133
  ...options,
16115
16134
  ...(companionManifestSha256 ? { companionManifestSha256 } : {}),
16116
16135
  })
16117
16136
  .then(issued => {
16137
+ if (closed || generation !== permissionsGeneration)
16138
+ throw new DevContextSupersededError();
16118
16139
  ticket = issued;
16119
16140
  return issued;
16120
16141
  }).finally(() => { if (pending?.promise === promise)
@@ -16122,16 +16143,50 @@ function startLocalDevLaunchTokenSession(options) {
16122
16143
  pending = { digest: companionManifestSha256, promise };
16123
16144
  return promise;
16124
16145
  };
16146
+ const getTicket = async (companionManifestSha256) => {
16147
+ if (closed)
16148
+ return undefined;
16149
+ const generation = permissionsGeneration;
16150
+ const assertPermissionsReady = () => {
16151
+ if (generation !== permissionsGeneration)
16152
+ throw new DevContextSupersededError();
16153
+ if (permissionsRefreshFailure)
16154
+ throw permissionsRefreshFailure.error;
16155
+ if (validatedPermissionsGeneration !== permissionsGeneration)
16156
+ throw new Error('远端上下文正在刷新,请稍后再试');
16157
+ };
16158
+ assertPermissionsReady();
16159
+ const issued = await acquireTicket(companionManifestSha256);
16160
+ if (closed)
16161
+ return undefined;
16162
+ assertPermissionsReady();
16163
+ return issued;
16164
+ };
16125
16165
  const refresh = async () => {
16126
16166
  if (refreshing || closed)
16127
16167
  return;
16128
16168
  refreshing = true;
16169
+ const generation = permissionsGeneration;
16129
16170
  try {
16130
- if (options.refreshPermissions && Date.now() >= permissionsRefreshAt) {
16131
- // A-3:无论成败都推进,否则权限刷新失败后每一轮重试都会再打一次远端 detail。
16132
- permissionsRefreshAt = Date.now() + 30_000;
16133
- companionDeclared = await options.refreshPermissions();
16171
+ if (options.refreshPermissions && permissionsRefreshPending) {
16172
+ permissionsRefreshPending = false;
16173
+ try {
16174
+ const nextCompanionDeclared = await options.refreshPermissions();
16175
+ if (generation !== permissionsGeneration || closed)
16176
+ return;
16177
+ companionDeclared = nextCompanionDeclared;
16178
+ permissionsRefreshFailure = undefined;
16179
+ validatedPermissionsGeneration = generation;
16180
+ }
16181
+ catch (error) {
16182
+ if (generation !== permissionsGeneration || error instanceof DevContextSupersededError)
16183
+ return;
16184
+ permissionsRefreshFailure = { error };
16185
+ throw error;
16186
+ }
16134
16187
  }
16188
+ if (!options.refreshPermissions)
16189
+ validatedPermissionsGeneration = generation;
16135
16190
  let manifest;
16136
16191
  let snapshotError;
16137
16192
  try {
@@ -16144,44 +16199,51 @@ function startLocalDevLaunchTokenSession(options) {
16144
16199
  catch (error) {
16145
16200
  snapshotError = error;
16146
16201
  }
16202
+ if (closed || generation !== permissionsGeneration)
16203
+ return;
16147
16204
  const digest = manifest?.artifacts.length ? manifest.digest : undefined;
16148
- const issued = await getTicket(digest);
16205
+ const issued = await acquireTicket(digest);
16149
16206
  if (!issued)
16150
16207
  return;
16151
- if (closed)
16208
+ if (closed || generation !== permissionsGeneration)
16152
16209
  return;
16153
- if (publishedToken !== issued.token || publishedDigest !== digest) {
16210
+ if (!permissionsRefreshFailure && (publishedToken !== issued.token || publishedDigest !== digest)) {
16154
16211
  publishedToken = issued.token;
16155
16212
  publishedDigest = digest;
16156
16213
  options.onToken(issued.token, digest, issued.expiresAt);
16157
16214
  }
16158
16215
  options.onReadiness({
16159
16216
  browser: { state: 'ready' },
16160
- mobile: { state: 'ready' },
16161
- companion: snapshotError
16162
- ? { state: snapshotError instanceof Error && snapshotError.message.includes('artifact-updating') ? 'pending' : 'blocked', reason: safeCompanionFailureReason(snapshotError) }
16163
- : manifest?.artifacts.length
16164
- ? companionDeclared
16165
- ? { state: 'ready', digest, artifacts: manifest.artifacts, reason: '本地产物和启动票已就绪,PC 授权将在打开后验证' }
16166
- : { state: 'blocked', reason: 'Companion 本地权限或桌面平台声明未开启' }
16167
- : { state: 'not-configured', reason: '未配置本地 Companion 产物' },
16217
+ mobile: permissionsRefreshFailure
16218
+ ? { state: 'blocked', reason: '远端上下文刷新失败,请更新声明或重新启动调试' }
16219
+ : { state: 'ready' },
16220
+ companion: permissionsRefreshFailure
16221
+ ? { state: 'blocked', reason: '远端上下文刷新失败,请更新声明或重新启动调试' }
16222
+ : snapshotError
16223
+ ? { state: snapshotError instanceof Error && snapshotError.message.includes('artifact-updating') ? 'pending' : 'blocked', reason: safeCompanionFailureReason(snapshotError) }
16224
+ : manifest?.artifacts.length
16225
+ ? companionDeclared
16226
+ ? { state: 'ready', digest, artifacts: manifest.artifacts, reason: '本地产物和启动票已就绪,PC 授权将在打开后验证' }
16227
+ : { state: 'blocked', reason: 'Companion 本地权限或桌面平台声明未开启' }
16228
+ : { state: 'not-configured', reason: '未配置本地 Companion 产物' },
16168
16229
  });
16169
16230
  if (snapshotError)
16170
16231
  options.logger.debug(`PC Companion 快照校验失败:${index.readRedactedErrorMessage(snapshotError, { verbose: true })}`);
16171
16232
  const ttl = Math.max(0, issued.expiresAt - Date.now());
16172
- scheduleExpiration(ttl);
16233
+ if (!permissionsRefreshFailure)
16234
+ scheduleExpiration(ttl);
16173
16235
  schedule(Math.min(2_000, Math.max(1_000, Math.floor(ttl * 0.8))));
16174
- if (hasReportedTokenFailure) {
16236
+ if (hasReportedTokenFailure && !permissionsRefreshFailure) {
16175
16237
  hasReportedTokenFailure = false;
16176
16238
  options.logger.success('本地调试凭证已恢复');
16177
16239
  }
16178
- if (consecutiveFailures > 0) {
16240
+ if (consecutiveFailures > 0 && !permissionsRefreshFailure) {
16179
16241
  consecutiveFailures = 0;
16180
16242
  options.onRateLimit?.(null);
16181
16243
  }
16182
16244
  }
16183
16245
  catch (error) {
16184
- if (closed)
16246
+ if (closed || generation !== permissionsGeneration || error instanceof DevContextSupersededError)
16185
16247
  return;
16186
16248
  // A2 + A3:限流类错误走 30s 起、上限 300s 的退避,并明确提示不要反复重启。
16187
16249
  const rateLimitState = index.readCliRateLimitState(error);
@@ -16207,6 +16269,12 @@ function startLocalDevLaunchTokenSession(options) {
16207
16269
  }
16208
16270
  finally {
16209
16271
  refreshing = false;
16272
+ // 请求期间有新声明时立即处理最新一代,避免被并发保护丢掉刷新事件。
16273
+ if (permissionsRefreshPending && !closed) {
16274
+ if (refreshTimer)
16275
+ clearTimeout(refreshTimer);
16276
+ schedule(0);
16277
+ }
16210
16278
  }
16211
16279
  };
16212
16280
  const schedule = (delayMs) => {
@@ -16235,7 +16303,10 @@ function startLocalDevLaunchTokenSession(options) {
16235
16303
  refresh: () => {
16236
16304
  // 项目绑定变更或开发者主动重试:主动失效 Dev Context 缓存后立即重新请求。
16237
16305
  options.onInvalidate?.();
16238
- permissionsRefreshAt = 0;
16306
+ permissionsGeneration += 1;
16307
+ permissionsRefreshPending = true;
16308
+ ticket = undefined;
16309
+ pending = undefined;
16239
16310
  publishedDigest = undefined;
16240
16311
  if (refreshTimer)
16241
16312
  clearTimeout(refreshTimer);
@@ -3,7 +3,7 @@
3
3
  var fs = require('node:fs/promises');
4
4
  var os = require('node:os');
5
5
  var path = require('node:path');
6
- var index = require('./index-Brq_-WV3.cjs');
6
+ var index = require('./index-d1cUUoR9.cjs');
7
7
  require('node:module');
8
8
  require('node:fs');
9
9
  require('path');
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var index$2 = require('./index-Brq_-WV3.cjs');
3
+ var index$2 = require('./index-d1cUUoR9.cjs');
4
4
  var require$$0$3 = require('fs');
5
5
  var require$$0$2 = require('crypto');
6
6
  var require$$1$2 = require('path');
7
7
  var require$$0$5 = require('assert');
8
8
  var require$$0$4 = require('events');
9
9
  var require$$1$1 = require('util');
10
- var runtimePermissionEnv = require('./runtime-permission-env-Bs_JqpTM.cjs');
10
+ var runtimePermissionEnv = require('./runtime-permission-env-B-GwDDT7.cjs');
11
11
  var require$$0$7 = require('net');
12
12
  var require$$0$6 = require('url');
13
13
  var require$$2$1 = require('http');
@@ -235,19 +235,19 @@ function requireArgument () {
235
235
 
236
236
  var command = {};
237
237
 
238
- const require$5 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-Brq_-WV3.cjs', document.baseURI).href)));
238
+ const require$5 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-d1cUUoR9.cjs', document.baseURI).href)));
239
239
  function __require$4() { return require$5("node:events"); }
240
240
 
241
- const require$4 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-Brq_-WV3.cjs', document.baseURI).href)));
241
+ const require$4 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-d1cUUoR9.cjs', document.baseURI).href)));
242
242
  function __require$3() { return require$4("node:child_process"); }
243
243
 
244
- const require$3 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-Brq_-WV3.cjs', document.baseURI).href)));
244
+ const require$3 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-d1cUUoR9.cjs', document.baseURI).href)));
245
245
  function __require$2() { return require$3("node:path"); }
246
246
 
247
- const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-Brq_-WV3.cjs', document.baseURI).href)));
247
+ const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-d1cUUoR9.cjs', document.baseURI).href)));
248
248
  function __require$1() { return require$2("node:fs"); }
249
249
 
250
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-Brq_-WV3.cjs', document.baseURI).href)));
250
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/index-d1cUUoR9.cjs', document.baseURI).href)));
251
251
  function __require() { return require$1("node:process"); }
252
252
 
253
253
  var help = {};
@@ -13554,7 +13554,7 @@ function getPathname(pathWithQuery) {
13554
13554
  }
13555
13555
 
13556
13556
  const CLI_VERSION_PLACEHOLDER = ['__HB', 'SDK', 'CLI', 'VERSION__'].join('_');
13557
- const BUILT_CLI_VERSION = '0.8.5';
13557
+ const BUILT_CLI_VERSION = '0.9.0-alpha.1';
13558
13558
  const PACKAGE_JSON_CANDIDATES = [
13559
13559
  path.resolve(__dirname, '..', '..', 'package.json'),
13560
13560
  path.resolve(__dirname, '..', 'package.json'),
@@ -13913,39 +13913,39 @@ function createCommandLoggerResolver(options) {
13913
13913
  };
13914
13914
  }
13915
13915
  const defaultClearLoginStatus = async (...args) => {
13916
- const { clearLoginStatus } = await Promise.resolve().then(function () { return require('./login-DeZpNrkY.cjs'); });
13916
+ const { clearLoginStatus } = await Promise.resolve().then(function () { return require('./login-lZKDg_9k.cjs'); });
13917
13917
  return clearLoginStatus(...args);
13918
13918
  };
13919
13919
  const defaultLoginToHeybox = async (...args) => {
13920
- const { loginToHeybox } = await Promise.resolve().then(function () { return require('./login-DeZpNrkY.cjs'); });
13920
+ const { loginToHeybox } = await Promise.resolve().then(function () { return require('./login-lZKDg_9k.cjs'); });
13921
13921
  return loginToHeybox(...args);
13922
13922
  };
13923
13923
  const defaultPrintLoginStatus = async (...args) => {
13924
- const { printLoginStatus } = await Promise.resolve().then(function () { return require('./login-DeZpNrkY.cjs'); });
13924
+ const { printLoginStatus } = await Promise.resolve().then(function () { return require('./login-lZKDg_9k.cjs'); });
13925
13925
  return printLoginStatus(...args);
13926
13926
  };
13927
13927
  const defaultRunBuildCommand = async (...args) => {
13928
- const { runBuildCommand } = await Promise.resolve().then(function () { return require('./build-zYnPSPFa.cjs'); });
13928
+ const { runBuildCommand } = await Promise.resolve().then(function () { return require('./build-BSeiFi0q.cjs'); });
13929
13929
  return runBuildCommand(...args);
13930
13930
  };
13931
13931
  const defaultRunCreateCommand = async (...args) => {
13932
- const { runCreateCommand } = await Promise.resolve().then(function () { return require('./create-C9FcEn9Y.cjs'); });
13932
+ const { runCreateCommand } = await Promise.resolve().then(function () { return require('./create-Dzeodh_-.cjs'); });
13933
13933
  return runCreateCommand(...args);
13934
13934
  };
13935
13935
  const defaultRunDevCommand = async (...args) => {
13936
- const { runDevCommand } = await Promise.resolve().then(function () { return require('./dev-VxeEocjt.cjs'); }).then(function (n) { return n.dev; });
13936
+ const { runDevCommand } = await Promise.resolve().then(function () { return require('./dev-DQ7k_fu6.cjs'); }).then(function (n) { return n.dev; });
13937
13937
  return runDevCommand(...args);
13938
13938
  };
13939
13939
  const defaultRunDoctorCommand = async (...args) => {
13940
- const { runDoctorCommand } = await Promise.resolve().then(function () { return require('./doctor-6Cf9o1hD.cjs'); });
13940
+ const { runDoctorCommand } = await Promise.resolve().then(function () { return require('./doctor-FIcFZiJ0.cjs'); });
13941
13941
  return runDoctorCommand(...args);
13942
13942
  };
13943
13943
  const defaultRunSkillInstallCommand = async (...args) => {
13944
- const { runSkillInstallCommand } = await Promise.resolve().then(function () { return require('./skill-B82gXQhX.cjs'); });
13944
+ const { runSkillInstallCommand } = await Promise.resolve().then(function () { return require('./skill-BhUlKvyY.cjs'); });
13945
13945
  return runSkillInstallCommand(...args);
13946
13946
  };
13947
13947
  const defaultRunRemoteCommand = async (...args) => {
13948
- const { runRemoteCommand } = await Promise.resolve().then(function () { return require('./remote-DOU11SNj.cjs'); });
13948
+ const { runRemoteCommand } = await Promise.resolve().then(function () { return require('./remote-9-tyxq6l.cjs'); });
13949
13949
  return runRemoteCommand(...args);
13950
13950
  };
13951
13951
  function resolveStandaloneLogger(options, verbose) {
@@ -11,8 +11,8 @@ var require$$0$4 = require('url');
11
11
  var require$$0 = require('zlib');
12
12
  var require$$0$1 = require('buffer');
13
13
  var require$$1 = require('util');
14
- var index = require('./index-Brq_-WV3.cjs');
15
- var dev = require('./dev-VxeEocjt.cjs');
14
+ var index = require('./index-d1cUUoR9.cjs');
15
+ var dev = require('./dev-DQ7k_fu6.cjs');
16
16
  require('node:module');
17
17
  require('node:fs');
18
18
  require('node:fs/promises');
@@ -23,12 +23,12 @@ require('node:crypto');
23
23
  require('readline');
24
24
  require('tty');
25
25
  require('assert');
26
- require('./context-DWoKsM_O.cjs');
27
- require('./runtime-permission-env-Bs_JqpTM.cjs');
28
- require('./version-UEmqQ6A-.cjs');
26
+ require('./context-D_L2-Hla.cjs');
27
+ require('./runtime-permission-env-B-GwDDT7.cjs');
28
+ require('./version-hsUgRL0I.cjs');
29
29
  require('node:async_hooks');
30
30
  require('sharp');
31
- require('./session-CbisOcE4.cjs');
31
+ require('./session-mMVA3395.cjs');
32
32
  require('fs');
33
33
  require('constants');
34
34
  require('node:os');
@@ -42,7 +42,7 @@ require('node:buffer');
42
42
  require('node:url');
43
43
  require('node:util');
44
44
  require('node:child_process');
45
- require('./project-vite-TFEwLzYM.cjs');
45
+ require('./project-vite-BM7D-pf6.cjs');
46
46
  require('node:zlib');
47
47
 
48
48
  var bufferUtil = {exports: {}};
@@ -3,9 +3,9 @@
3
3
  var promises = require('node:readline/promises');
4
4
  var node_crypto = require('node:crypto');
5
5
  var node_http = require('node:http');
6
- var session = require('./session-CbisOcE4.cjs');
6
+ var session = require('./session-mMVA3395.cjs');
7
7
  var browser = require('./browser-RAy8e8cV.cjs');
8
- var index = require('./index-Brq_-WV3.cjs');
8
+ var index = require('./index-d1cUUoR9.cjs');
9
9
  require('node:path');
10
10
  require('fs');
11
11
  require('constants');
@@ -5,7 +5,7 @@ var fs$1 = require('node:fs/promises');
5
5
  var node_module = require('node:module');
6
6
  var path = require('node:path');
7
7
  var node_url = require('node:url');
8
- var index = require('./index-Brq_-WV3.cjs');
8
+ var index = require('./index-d1cUUoR9.cjs');
9
9
 
10
10
  function findProjectRoot(startDir) {
11
11
  let current = path.resolve(startDir);