@bolloon/bolloon-agent 0.5.0 → 0.5.2

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 (121) hide show
  1. package/README.md +39 -0
  2. package/dist/agents/agent-lang.js +101 -0
  3. package/dist/agents/agent-manifest-protocol.js +4 -0
  4. package/dist/agents/execution-supervisor.js +87 -5
  5. package/dist/agents/gateway-group.js +44 -3
  6. package/dist/agents/model-policy.js +651 -0
  7. package/dist/agents/pi-sdk-tools.js +84 -33
  8. package/dist/agents/pi-sdk.js +184 -31
  9. package/dist/agents/run-store.js +53 -0
  10. package/dist/agents/task/task-runner.js +8 -1
  11. package/dist/agents/workflow-pivot-loop.js +22 -13
  12. package/dist/cli/clipboard.js +65 -0
  13. package/dist/cli/ink-app.js +145 -17
  14. package/dist/cli/log-gate.js +386 -0
  15. package/dist/cli/mention-data.js +2 -0
  16. package/dist/cli/model-selector.js +538 -0
  17. package/dist/cli/reply-hygiene.js +54 -0
  18. package/dist/cli/setup-wizard.js +686 -69
  19. package/dist/cli/startup-notice.js +160 -0
  20. package/dist/cli/stores.js +22 -1
  21. package/dist/cli/theme.js +49 -0
  22. package/dist/cli/tui-select.js +970 -0
  23. package/dist/cli-entry.js +125 -18
  24. package/dist/efficode/compress.js +78 -0
  25. package/dist/efficode/crc32.js +38 -0
  26. package/dist/efficode/did.js +66 -0
  27. package/dist/efficode/index.js +24 -0
  28. package/dist/efficode/lz77.js +170 -0
  29. package/dist/efficode/negotiate.js +187 -0
  30. package/dist/efficode/ops.js +87 -0
  31. package/dist/efficode/packet.js +271 -0
  32. package/dist/efficode/types.js +45 -0
  33. package/dist/electron-build/agents/agent-manifest-protocol.js +4 -0
  34. package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -1
  35. package/dist/electron-build/agents/execution-supervisor.js +86 -4
  36. package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
  37. package/dist/electron-build/agents/goal-store.js.map +1 -1
  38. package/dist/electron-build/agents/model-policy.js +706 -0
  39. package/dist/electron-build/agents/model-policy.js.map +1 -0
  40. package/dist/electron-build/agents/pi-sdk-tools.js +84 -33
  41. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  42. package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
  43. package/dist/electron-build/agents/pi-sdk.js +185 -31
  44. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  45. package/dist/electron-build/agents/run-store.js +55 -1
  46. package/dist/electron-build/agents/run-store.js.map +1 -1
  47. package/dist/electron-build/agents/task/task-runner.js +8 -1
  48. package/dist/electron-build/agents/task/task-runner.js.map +1 -1
  49. package/dist/electron-build/agents/workflow-pivot-loop.js +22 -13
  50. package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
  51. package/dist/electron-build/llm/config-store.js +146 -6
  52. package/dist/electron-build/llm/config-store.js.map +1 -1
  53. package/dist/electron-build/llm/connection-probe.js +764 -0
  54. package/dist/electron-build/llm/connection-probe.js.map +1 -0
  55. package/dist/electron-build/llm/custom-provider-store.js +272 -0
  56. package/dist/electron-build/llm/custom-provider-store.js.map +1 -0
  57. package/dist/electron-build/llm/data/provider-catalog-baked.js +23 -0
  58. package/dist/electron-build/llm/data/provider-catalog-baked.js.map +1 -0
  59. package/dist/electron-build/llm/model-catalog.js +778 -0
  60. package/dist/electron-build/llm/model-catalog.js.map +1 -0
  61. package/dist/electron-build/llm/model-discovery.js +1202 -0
  62. package/dist/electron-build/llm/model-discovery.js.map +1 -0
  63. package/dist/electron-build/llm/model-selection.js +1315 -0
  64. package/dist/electron-build/llm/model-selection.js.map +1 -0
  65. package/dist/electron-build/llm/pi-ai.js +751 -55
  66. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  67. package/dist/electron-build/llm/provider-catalog.js +969 -0
  68. package/dist/electron-build/llm/provider-catalog.js.map +1 -0
  69. package/dist/electron-build/llm/provider-registry.js +950 -0
  70. package/dist/electron-build/llm/provider-registry.js.map +1 -0
  71. package/dist/electron-build/llm/tool-name.js +266 -0
  72. package/dist/electron-build/llm/tool-name.js.map +1 -0
  73. package/dist/electron-build/utils/dual-source-facts.js +236 -0
  74. package/dist/electron-build/utils/dual-source-facts.js.map +1 -0
  75. package/dist/electron-build/utils/dual-source.js +32 -156
  76. package/dist/electron-build/utils/dual-source.js.map +1 -1
  77. package/dist/electron-build/utils/update-manager.js +22 -72
  78. package/dist/electron-build/utils/update-manager.js.map +1 -1
  79. package/dist/electron-build/utils/update-state.js +2 -19
  80. package/dist/electron-build/utils/update-state.js.map +1 -1
  81. package/dist/electron-build/utils/version-identity.js +193 -0
  82. package/dist/electron-build/utils/version-identity.js.map +1 -0
  83. package/dist/electron-build/utils/version-info.js +47 -101
  84. package/dist/electron-build/utils/version-info.js.map +1 -1
  85. package/dist/index.js +282 -96
  86. package/dist/ios/bolloon-web.json +10 -0
  87. package/dist/ios/mobile-contacts.js +44 -11
  88. package/dist/ios/mobile-core.js +1723 -84
  89. package/dist/ios/mobile-tasks.js +339 -0
  90. package/dist/ios/mobile-update.js +1442 -0
  91. package/dist/ios/mobile.js +308 -35
  92. package/dist/ios/routes-llm-config.js +344 -52
  93. package/dist/ios/routes-mobile-tasks.js +353 -0
  94. package/dist/ios/server.js +163 -4
  95. package/dist/ios/sw.js +1 -1
  96. package/dist/llm/config-store.js +146 -6
  97. package/dist/llm/connection-probe.js +744 -0
  98. package/dist/llm/custom-provider-store.js +224 -0
  99. package/dist/llm/data/provider-catalog-baked.js +19 -0
  100. package/dist/llm/model-catalog.js +717 -0
  101. package/dist/llm/model-discovery.js +1137 -0
  102. package/dist/llm/model-selection.js +1241 -0
  103. package/dist/llm/pi-ai.js +732 -54
  104. package/dist/llm/provider-catalog.js +898 -0
  105. package/dist/llm/provider-registry.js +917 -0
  106. package/dist/llm/tool-name.js +220 -0
  107. package/dist/setup/onboard.js +34 -8
  108. package/dist/utils/dual-source-facts.js +223 -0
  109. package/dist/utils/dual-source.js +12 -141
  110. package/dist/utils/update-manager.js +16 -59
  111. package/dist/utils/update-state.js +6 -18
  112. package/dist/utils/version-identity.js +176 -0
  113. package/dist/utils/version-info.js +9 -78
  114. package/dist/web/bolloon-web.json +10 -0
  115. package/dist/web/mobile-core.js +1721 -84
  116. package/dist/web/mobile-update.js +1442 -0
  117. package/dist/web/mobile.js +372 -35
  118. package/dist/web/routes-llm-config.js +344 -52
  119. package/dist/web/sw.js +1 -1
  120. package/package.json +1 -1
  121. package/scripts/build-web.ts +36 -0
@@ -89380,7 +89380,7 @@ ${b}
89380
89380
  });
89381
89381
 
89382
89382
  // node_modules/viem/_esm/utils/encoding/toHex.js
89383
- function toHex(value2, opts = {}) {
89383
+ function toHex2(value2, opts = {}) {
89384
89384
  if (typeof value2 === "number" || typeof value2 === "bigint")
89385
89385
  return numberToHex(value2, opts);
89386
89386
  if (typeof value2 === "string") {
@@ -91955,7 +91955,7 @@ ${b}
91955
91955
  const bytes = keccak_2562(isHex(value2, { strict: false }) ? toBytes3(value2) : value2);
91956
91956
  if (to === "bytes")
91957
91957
  return bytes;
91958
- return toHex(bytes);
91958
+ return toHex2(bytes);
91959
91959
  }
91960
91960
  var init_keccak256 = __esm({
91961
91961
  "node_modules/viem/_esm/utils/hash/keccak256.js"() {
@@ -92641,9 +92641,9 @@ ${b}
92641
92641
  const { chainId, nonce, ...signature } = authorization;
92642
92642
  const contractAddress = authorization.address;
92643
92643
  serializedAuthorizationList.push([
92644
- chainId ? toHex(chainId) : "0x",
92644
+ chainId ? toHex2(chainId) : "0x",
92645
92645
  contractAddress,
92646
- nonce ? toHex(nonce) : "0x",
92646
+ nonce ? toHex2(nonce) : "0x",
92647
92647
  ...toYParitySignatureArray({}, signature)
92648
92648
  ]);
92649
92649
  }
@@ -92709,7 +92709,7 @@ ${b}
92709
92709
  const bytes = sha2568(isHex(value2, { strict: false }) ? toBytes3(value2) : value2);
92710
92710
  if (to === "bytes")
92711
92711
  return bytes;
92712
- return toHex(bytes);
92712
+ return toHex2(bytes);
92713
92713
  }
92714
92714
  var init_sha2562 = __esm({
92715
92715
  "node_modules/viem/_esm/utils/hash/sha256.js"() {
@@ -94158,7 +94158,7 @@ ${b}
94158
94158
  return encodeAbiParameters(encodedTypes, encodedValues);
94159
94159
  }
94160
94160
  function hashType({ primaryType, types }) {
94161
- const encodedHashType = toHex(encodeType({ primaryType, types }));
94161
+ const encodedHashType = toHex2(encodeType({ primaryType, types }));
94162
94162
  return keccak256(encodedHashType);
94163
94163
  }
94164
94164
  function encodeType({ primaryType, types }) {
@@ -94193,7 +94193,7 @@ ${b}
94193
94193
  if (type === "bytes")
94194
94194
  return [{ type: "bytes32" }, keccak256(value2)];
94195
94195
  if (type === "string")
94196
- return [{ type: "bytes32" }, keccak256(toHex(value2))];
94196
+ return [{ type: "bytes32" }, keccak256(toHex2(value2))];
94197
94197
  if (type.lastIndexOf("]") === type.length - 1) {
94198
94198
  const parsedType = type.slice(0, type.lastIndexOf("["));
94199
94199
  const typeValuePairs = value2.map((item) => encodeField({
@@ -94238,7 +94238,7 @@ ${b}
94238
94238
  // node_modules/viem/_esm/accounts/privateKeyToAccount.js
94239
94239
  function privateKeyToAccount(privateKey, options2 = {}) {
94240
94240
  const { nonceManager } = options2;
94241
- const publicKey2 = toHex(secp256k13.getPublicKey(privateKey.slice(2), false));
94241
+ const publicKey2 = toHex2(secp256k13.getPublicKey(privateKey.slice(2), false));
94242
94242
  const address = publicKeyToAddress(publicKey2);
94243
94243
  const account = toAccount({
94244
94244
  address,
@@ -94718,7 +94718,7 @@ ${b}
94718
94718
  } else {
94719
94719
  for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
94720
94720
  }
94721
- return toHex(bytes);
94721
+ return toHex2(bytes);
94722
94722
  }
94723
94723
  function bytesToBase64(bytes) {
94724
94724
  let out = "";
@@ -94969,7 +94969,7 @@ ${b}
94969
94969
  }
94970
94970
  }
94971
94971
  function deriveTokenId(agentId, serviceName) {
94972
- return BigInt(keccak256(toHex(`${agentId}:${serviceName}`)));
94972
+ return BigInt(keccak256(toHex2(`${agentId}:${serviceName}`)));
94973
94973
  }
94974
94974
  async function registerServiceOnChain(params) {
94975
94975
  try {
@@ -123383,7 +123383,7 @@ ${values.join("\n")}` : `${blockName} :`;
123383
123383
  // node_modules/process/browser.js
123384
123384
  var require_browser4 = __commonJS({
123385
123385
  "node_modules/process/browser.js"(exports, module) {
123386
- var process = module.exports = {};
123386
+ var process2 = module.exports = {};
123387
123387
  var cachedSetTimeout;
123388
123388
  var cachedClearTimeout;
123389
123389
  function defaultSetTimout() {
@@ -123488,7 +123488,7 @@ ${values.join("\n")}` : `${blockName} :`;
123488
123488
  draining = false;
123489
123489
  runClearTimeout(timeout);
123490
123490
  }
123491
- process.nextTick = function(fun) {
123491
+ process2.nextTick = function(fun) {
123492
123492
  var args = new Array(arguments.length - 1);
123493
123493
  if (arguments.length > 1) {
123494
123494
  for (var i = 1; i < arguments.length; i++) {
@@ -123507,36 +123507,36 @@ ${values.join("\n")}` : `${blockName} :`;
123507
123507
  Item.prototype.run = function() {
123508
123508
  this.fun.apply(null, this.array);
123509
123509
  };
123510
- process.title = "browser";
123511
- process.browser = true;
123512
- process.env = {};
123513
- process.argv = [];
123514
- process.version = "";
123515
- process.versions = {};
123510
+ process2.title = "browser";
123511
+ process2.browser = true;
123512
+ process2.env = {};
123513
+ process2.argv = [];
123514
+ process2.version = "";
123515
+ process2.versions = {};
123516
123516
  function noop16() {
123517
123517
  }
123518
- process.on = noop16;
123519
- process.addListener = noop16;
123520
- process.once = noop16;
123521
- process.off = noop16;
123522
- process.removeListener = noop16;
123523
- process.removeAllListeners = noop16;
123524
- process.emit = noop16;
123525
- process.prependListener = noop16;
123526
- process.prependOnceListener = noop16;
123527
- process.listeners = function(name10) {
123518
+ process2.on = noop16;
123519
+ process2.addListener = noop16;
123520
+ process2.once = noop16;
123521
+ process2.off = noop16;
123522
+ process2.removeListener = noop16;
123523
+ process2.removeAllListeners = noop16;
123524
+ process2.emit = noop16;
123525
+ process2.prependListener = noop16;
123526
+ process2.prependOnceListener = noop16;
123527
+ process2.listeners = function(name10) {
123528
123528
  return [];
123529
123529
  };
123530
- process.binding = function(name10) {
123530
+ process2.binding = function(name10) {
123531
123531
  throw new Error("process.binding is not supported");
123532
123532
  };
123533
- process.cwd = function() {
123533
+ process2.cwd = function() {
123534
123534
  return "/";
123535
123535
  };
123536
- process.chdir = function(dir) {
123536
+ process2.chdir = function(dir) {
123537
123537
  throw new Error("process.chdir is not supported");
123538
123538
  };
123539
- process.umask = function() {
123539
+ process2.umask = function() {
123540
123540
  return 0;
123541
123541
  };
123542
123542
  }
@@ -123755,7 +123755,7 @@ ${values.join("\n")}` : `${blockName} :`;
123755
123755
  var require_end_of_stream = __commonJS({
123756
123756
  "node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/end-of-stream.js"(exports, module) {
123757
123757
  "use strict";
123758
- var process = require_browser4();
123758
+ var process2 = require_browser4();
123759
123759
  var { AbortError: AbortError6, codes } = require_errors();
123760
123760
  var { ERR_INVALID_ARG_TYPE, ERR_STREAM_PREMATURE_CLOSE } = codes;
123761
123761
  var { kEmptyObject, once } = require_util();
@@ -123891,17 +123891,17 @@ ${values.join("\n")}` : `${blockName} :`;
123891
123891
  }
123892
123892
  stream.on("close", onclose);
123893
123893
  if (closed) {
123894
- process.nextTick(onclose);
123894
+ process2.nextTick(onclose);
123895
123895
  } else if (wState !== null && wState !== void 0 && wState.errorEmitted || rState !== null && rState !== void 0 && rState.errorEmitted) {
123896
123896
  if (!willEmitClose) {
123897
- process.nextTick(onclosed);
123897
+ process2.nextTick(onclosed);
123898
123898
  }
123899
123899
  } else if (!readable && (!willEmitClose || isReadable(stream)) && (writableFinished || isWritable(stream) === false)) {
123900
- process.nextTick(onclosed);
123900
+ process2.nextTick(onclosed);
123901
123901
  } else if (!writable && (!willEmitClose || isWritable(stream)) && (readableFinished || isReadable(stream) === false)) {
123902
- process.nextTick(onclosed);
123902
+ process2.nextTick(onclosed);
123903
123903
  } else if (rState && stream.req && stream.aborted) {
123904
- process.nextTick(onclosed);
123904
+ process2.nextTick(onclosed);
123905
123905
  }
123906
123906
  const cleanup = () => {
123907
123907
  callback = nop;
@@ -123929,7 +123929,7 @@ ${values.join("\n")}` : `${blockName} :`;
123929
123929
  );
123930
123930
  };
123931
123931
  if (options2.signal.aborted) {
123932
- process.nextTick(abort);
123932
+ process2.nextTick(abort);
123933
123933
  } else {
123934
123934
  addAbortListener = addAbortListener || require_util().addAbortListener;
123935
123935
  const disposable = addAbortListener(options2.signal, abort);
@@ -123956,7 +123956,7 @@ ${values.join("\n")}` : `${blockName} :`;
123956
123956
  );
123957
123957
  };
123958
123958
  if (options2.signal.aborted) {
123959
- process.nextTick(abort);
123959
+ process2.nextTick(abort);
123960
123960
  } else {
123961
123961
  addAbortListener = addAbortListener || require_util().addAbortListener;
123962
123962
  const disposable = addAbortListener(options2.signal, abort);
@@ -123969,7 +123969,7 @@ ${values.join("\n")}` : `${blockName} :`;
123969
123969
  }
123970
123970
  const resolverFn = (...args) => {
123971
123971
  if (!isAborted) {
123972
- process.nextTick(() => callback.apply(stream, args));
123972
+ process2.nextTick(() => callback.apply(stream, args));
123973
123973
  }
123974
123974
  };
123975
123975
  PromisePrototypeThen(stream[kIsClosedPromise].promise, resolverFn, resolverFn);
@@ -124007,7 +124007,7 @@ ${values.join("\n")}` : `${blockName} :`;
124007
124007
  var require_destroy = __commonJS({
124008
124008
  "node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/destroy.js"(exports, module) {
124009
124009
  "use strict";
124010
- var process = require_browser4();
124010
+ var process2 = require_browser4();
124011
124011
  var {
124012
124012
  aggregateTwoErrors,
124013
124013
  codes: { ERR_MULTIPLE_CALLBACK },
@@ -124074,9 +124074,9 @@ ${values.join("\n")}` : `${blockName} :`;
124074
124074
  cb(err3);
124075
124075
  }
124076
124076
  if (err3) {
124077
- process.nextTick(emitErrorCloseNT, self2, err3);
124077
+ process2.nextTick(emitErrorCloseNT, self2, err3);
124078
124078
  } else {
124079
- process.nextTick(emitCloseNT, self2);
124079
+ process2.nextTick(emitCloseNT, self2);
124080
124080
  }
124081
124081
  }
124082
124082
  try {
@@ -124161,7 +124161,7 @@ ${values.join("\n")}` : `${blockName} :`;
124161
124161
  r.errored = err2;
124162
124162
  }
124163
124163
  if (sync) {
124164
- process.nextTick(emitErrorNT, stream, err2);
124164
+ process2.nextTick(emitErrorNT, stream, err2);
124165
124165
  } else {
124166
124166
  emitErrorNT(stream, err2);
124167
124167
  }
@@ -124183,7 +124183,7 @@ ${values.join("\n")}` : `${blockName} :`;
124183
124183
  if (stream.listenerCount(kConstruct) > 1) {
124184
124184
  return;
124185
124185
  }
124186
- process.nextTick(constructNT, stream);
124186
+ process2.nextTick(constructNT, stream);
124187
124187
  }
124188
124188
  function constructNT(stream) {
124189
124189
  let called = false;
@@ -124207,15 +124207,15 @@ ${values.join("\n")}` : `${blockName} :`;
124207
124207
  } else if (err2) {
124208
124208
  errorOrDestroy(stream, err2, true);
124209
124209
  } else {
124210
- process.nextTick(emitConstructNT, stream);
124210
+ process2.nextTick(emitConstructNT, stream);
124211
124211
  }
124212
124212
  }
124213
124213
  try {
124214
124214
  stream._construct((err2) => {
124215
- process.nextTick(onConstruct, err2);
124215
+ process2.nextTick(onConstruct, err2);
124216
124216
  });
124217
124217
  } catch (err2) {
124218
- process.nextTick(onConstruct, err2);
124218
+ process2.nextTick(onConstruct, err2);
124219
124219
  }
124220
124220
  }
124221
124221
  function emitConstructNT(stream) {
@@ -124229,7 +124229,7 @@ ${values.join("\n")}` : `${blockName} :`;
124229
124229
  }
124230
124230
  function emitErrorCloseLegacy(stream, err2) {
124231
124231
  stream.emit("error", err2);
124232
- process.nextTick(emitCloseLegacy, stream);
124232
+ process2.nextTick(emitCloseLegacy, stream);
124233
124233
  }
124234
124234
  function destroyer(stream, err2) {
124235
124235
  if (!stream || isDestroyed(stream)) {
@@ -124250,9 +124250,9 @@ ${values.join("\n")}` : `${blockName} :`;
124250
124250
  } else if (typeof stream.close === "function") {
124251
124251
  stream.close();
124252
124252
  } else if (err2) {
124253
- process.nextTick(emitErrorCloseLegacy, stream, err2);
124253
+ process2.nextTick(emitErrorCloseLegacy, stream, err2);
124254
124254
  } else {
124255
- process.nextTick(emitCloseLegacy, stream);
124255
+ process2.nextTick(emitCloseLegacy, stream);
124256
124256
  }
124257
124257
  if (!stream.destroyed) {
124258
124258
  stream[kIsDestroyed] = true;
@@ -124894,7 +124894,7 @@ ${values.join("\n")}` : `${blockName} :`;
124894
124894
  var require_from = __commonJS({
124895
124895
  "node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/from.js"(exports, module) {
124896
124896
  "use strict";
124897
- var process = require_browser4();
124897
+ var process2 = require_browser4();
124898
124898
  var { PromisePrototypeThen, SymbolAsyncIterator, SymbolIterator } = require_primordials();
124899
124899
  var { Buffer: Buffer2 } = require_buffer();
124900
124900
  var { ERR_INVALID_ARG_TYPE, ERR_STREAM_NULL_VALUES } = require_errors().codes;
@@ -124936,9 +124936,9 @@ ${values.join("\n")}` : `${blockName} :`;
124936
124936
  readable._destroy = function(error, cb) {
124937
124937
  PromisePrototypeThen(
124938
124938
  close(error),
124939
- () => process.nextTick(cb, error),
124939
+ () => process2.nextTick(cb, error),
124940
124940
  // nextTick is here in case cb throws
124941
- (e) => process.nextTick(cb, e || error)
124941
+ (e) => process2.nextTick(cb, e || error)
124942
124942
  );
124943
124943
  };
124944
124944
  async function close(error) {
@@ -124989,7 +124989,7 @@ ${values.join("\n")}` : `${blockName} :`;
124989
124989
  var require_readable = __commonJS({
124990
124990
  "node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/readable.js"(exports, module) {
124991
124991
  "use strict";
124992
- var process = require_browser4();
124992
+ var process2 = require_browser4();
124993
124993
  var {
124994
124994
  ArrayPrototypeIndexOf,
124995
124995
  NumberIsInteger,
@@ -125374,7 +125374,7 @@ ${values.join("\n")}` : `${blockName} :`;
125374
125374
  if (!state2.emittedReadable) {
125375
125375
  debug("emitReadable", state2.flowing);
125376
125376
  state2.emittedReadable = true;
125377
- process.nextTick(emitReadable_, stream);
125377
+ process2.nextTick(emitReadable_, stream);
125378
125378
  }
125379
125379
  }
125380
125380
  function emitReadable_(stream) {
@@ -125390,7 +125390,7 @@ ${values.join("\n")}` : `${blockName} :`;
125390
125390
  function maybeReadMore(stream, state2) {
125391
125391
  if (!state2.readingMore && state2.constructed) {
125392
125392
  state2.readingMore = true;
125393
- process.nextTick(maybeReadMore_, stream, state2);
125393
+ process2.nextTick(maybeReadMore_, stream, state2);
125394
125394
  }
125395
125395
  }
125396
125396
  function maybeReadMore_(stream, state2) {
@@ -125417,9 +125417,9 @@ ${values.join("\n")}` : `${blockName} :`;
125417
125417
  }
125418
125418
  state2.pipes.push(dest);
125419
125419
  debug("pipe count=%d opts=%j", state2.pipes.length, pipeOpts);
125420
- const doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr;
125420
+ const doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process2.stdout && dest !== process2.stderr;
125421
125421
  const endFn = doEnd ? onend : unpipe;
125422
- if (state2.endEmitted) process.nextTick(endFn);
125422
+ if (state2.endEmitted) process2.nextTick(endFn);
125423
125423
  else src4.once("end", endFn);
125424
125424
  dest.on("unpipe", onunpipe);
125425
125425
  function onunpipe(readable, unpipeInfo) {
@@ -125569,7 +125569,7 @@ ${values.join("\n")}` : `${blockName} :`;
125569
125569
  if (state2.length) {
125570
125570
  emitReadable(this);
125571
125571
  } else if (!state2.reading) {
125572
- process.nextTick(nReadingNextTick, this);
125572
+ process2.nextTick(nReadingNextTick, this);
125573
125573
  }
125574
125574
  }
125575
125575
  }
@@ -125579,7 +125579,7 @@ ${values.join("\n")}` : `${blockName} :`;
125579
125579
  Readable.prototype.removeListener = function(ev, fn) {
125580
125580
  const res = Stream.prototype.removeListener.call(this, ev, fn);
125581
125581
  if (ev === "readable") {
125582
- process.nextTick(updateReadableListening, this);
125582
+ process2.nextTick(updateReadableListening, this);
125583
125583
  }
125584
125584
  return res;
125585
125585
  };
@@ -125587,7 +125587,7 @@ ${values.join("\n")}` : `${blockName} :`;
125587
125587
  Readable.prototype.removeAllListeners = function(ev) {
125588
125588
  const res = Stream.prototype.removeAllListeners.apply(this, arguments);
125589
125589
  if (ev === "readable" || ev === void 0) {
125590
- process.nextTick(updateReadableListening, this);
125590
+ process2.nextTick(updateReadableListening, this);
125591
125591
  }
125592
125592
  return res;
125593
125593
  };
@@ -125619,7 +125619,7 @@ ${values.join("\n")}` : `${blockName} :`;
125619
125619
  function resume(stream, state2) {
125620
125620
  if (!state2.resumeScheduled) {
125621
125621
  state2.resumeScheduled = true;
125622
- process.nextTick(resume_, stream, state2);
125622
+ process2.nextTick(resume_, stream, state2);
125623
125623
  }
125624
125624
  }
125625
125625
  function resume_(stream, state2) {
@@ -125896,7 +125896,7 @@ ${values.join("\n")}` : `${blockName} :`;
125896
125896
  debug("endReadable", state2.endEmitted);
125897
125897
  if (!state2.endEmitted) {
125898
125898
  state2.ended = true;
125899
- process.nextTick(endReadableNT, state2, stream);
125899
+ process2.nextTick(endReadableNT, state2, stream);
125900
125900
  }
125901
125901
  }
125902
125902
  function endReadableNT(state2, stream) {
@@ -125905,7 +125905,7 @@ ${values.join("\n")}` : `${blockName} :`;
125905
125905
  state2.endEmitted = true;
125906
125906
  stream.emit("end");
125907
125907
  if (stream.writable && stream.allowHalfOpen === false) {
125908
- process.nextTick(endWritableNT, stream);
125908
+ process2.nextTick(endWritableNT, stream);
125909
125909
  } else if (state2.autoDestroy) {
125910
125910
  const wState = stream._writableState;
125911
125911
  const autoDestroy = !wState || wState.autoDestroy && // We don't expect the writable to ever 'finish'
@@ -125955,7 +125955,7 @@ ${values.join("\n")}` : `${blockName} :`;
125955
125955
  var require_writable = __commonJS({
125956
125956
  "node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/writable.js"(exports, module) {
125957
125957
  "use strict";
125958
- var process = require_browser4();
125958
+ var process2 = require_browser4();
125959
125959
  var {
125960
125960
  ArrayPrototypeSlice,
125961
125961
  Error: Error2,
@@ -126108,7 +126108,7 @@ ${values.join("\n")}` : `${blockName} :`;
126108
126108
  err2 = new ERR_STREAM_DESTROYED("write");
126109
126109
  }
126110
126110
  if (err2) {
126111
- process.nextTick(cb, err2);
126111
+ process2.nextTick(cb, err2);
126112
126112
  errorOrDestroy(stream, err2, true);
126113
126113
  return err2;
126114
126114
  }
@@ -126198,7 +126198,7 @@ ${values.join("\n")}` : `${blockName} :`;
126198
126198
  stream._readableState.errored = er;
126199
126199
  }
126200
126200
  if (sync) {
126201
- process.nextTick(onwriteError, stream, state2, er, cb);
126201
+ process2.nextTick(onwriteError, stream, state2, er, cb);
126202
126202
  } else {
126203
126203
  onwriteError(stream, state2, er, cb);
126204
126204
  }
@@ -126216,7 +126216,7 @@ ${values.join("\n")}` : `${blockName} :`;
126216
126216
  stream,
126217
126217
  state: state2
126218
126218
  };
126219
- process.nextTick(afterWriteTick, state2.afterWriteTickInfo);
126219
+ process2.nextTick(afterWriteTick, state2.afterWriteTickInfo);
126220
126220
  }
126221
126221
  } else {
126222
126222
  afterWrite(stream, state2, 1, cb);
@@ -126353,7 +126353,7 @@ ${values.join("\n")}` : `${blockName} :`;
126353
126353
  }
126354
126354
  if (typeof cb === "function") {
126355
126355
  if (err2 || state2.finished) {
126356
- process.nextTick(cb, err2);
126356
+ process2.nextTick(cb, err2);
126357
126357
  } else {
126358
126358
  state2[kOnFinished].push(cb);
126359
126359
  }
@@ -126382,7 +126382,7 @@ ${values.join("\n")}` : `${blockName} :`;
126382
126382
  state2.prefinished = true;
126383
126383
  stream.emit("prefinish");
126384
126384
  state2.pendingcb++;
126385
- process.nextTick(finish, stream, state2);
126385
+ process2.nextTick(finish, stream, state2);
126386
126386
  }
126387
126387
  }
126388
126388
  state2.sync = true;
@@ -126411,7 +126411,7 @@ ${values.join("\n")}` : `${blockName} :`;
126411
126411
  if (state2.pendingcb === 0) {
126412
126412
  if (sync) {
126413
126413
  state2.pendingcb++;
126414
- process.nextTick(
126414
+ process2.nextTick(
126415
126415
  (stream2, state3) => {
126416
126416
  if (needFinish(state3)) {
126417
126417
  finish(stream2, state3);
@@ -126546,7 +126546,7 @@ ${values.join("\n")}` : `${blockName} :`;
126546
126546
  Writable.prototype.destroy = function(err2, cb) {
126547
126547
  const state2 = this._writableState;
126548
126548
  if (!state2.destroyed && (state2.bufferedIndex < state2.buffered.length || state2[kOnFinished].length)) {
126549
- process.nextTick(errorBuffer, state2);
126549
+ process2.nextTick(errorBuffer, state2);
126550
126550
  }
126551
126551
  destroy.call(this, err2, cb);
126552
126552
  return this;
@@ -126575,7 +126575,7 @@ ${values.join("\n")}` : `${blockName} :`;
126575
126575
  // node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/duplexify.js
126576
126576
  var require_duplexify = __commonJS({
126577
126577
  "node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/duplexify.js"(exports, module) {
126578
- var process = require_browser4();
126578
+ var process2 = require_browser4();
126579
126579
  var bufferModule = require_buffer();
126580
126580
  var {
126581
126581
  isReadable,
@@ -126688,9 +126688,9 @@ ${values.join("\n")}` : `${blockName} :`;
126688
126688
  final(async () => {
126689
126689
  try {
126690
126690
  await promise;
126691
- process.nextTick(cb, null);
126691
+ process2.nextTick(cb, null);
126692
126692
  } catch (err2) {
126693
- process.nextTick(cb, err2);
126693
+ process2.nextTick(cb, err2);
126694
126694
  }
126695
126695
  });
126696
126696
  },
@@ -126769,7 +126769,7 @@ ${values.join("\n")}` : `${blockName} :`;
126769
126769
  const _promise = promise;
126770
126770
  promise = null;
126771
126771
  const { chunk, done, cb } = await _promise;
126772
- process.nextTick(cb);
126772
+ process2.nextTick(cb);
126773
126773
  if (done) return;
126774
126774
  if (signal.aborted)
126775
126775
  throw new AbortError6(void 0, {
@@ -127160,7 +127160,7 @@ ${values.join("\n")}` : `${blockName} :`;
127160
127160
  // node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/pipeline.js
127161
127161
  var require_pipeline = __commonJS({
127162
127162
  "node_modules/@libp2p/http-utils/node_modules/readable-stream/lib/internal/streams/pipeline.js"(exports, module) {
127163
- var process = require_browser4();
127163
+ var process2 = require_browser4();
127164
127164
  var { ArrayIsArray, Promise: Promise2, SymbolAsyncIterator, SymbolDispose } = require_primordials();
127165
127165
  var eos = require_end_of_stream();
127166
127166
  var { once } = require_util();
@@ -127362,7 +127362,7 @@ ${values.join("\n")}` : `${blockName} :`;
127362
127362
  if (!error) {
127363
127363
  lastStreamCleanup.forEach((fn) => fn());
127364
127364
  }
127365
- process.nextTick(callback, error, value2);
127365
+ process2.nextTick(callback, error, value2);
127366
127366
  }
127367
127367
  }
127368
127368
  let ret;
@@ -127441,11 +127441,11 @@ ${values.join("\n")}` : `${blockName} :`;
127441
127441
  if (end) {
127442
127442
  pt.end();
127443
127443
  }
127444
- process.nextTick(finish);
127444
+ process2.nextTick(finish);
127445
127445
  },
127446
127446
  (err2) => {
127447
127447
  pt.destroy(err2);
127448
- process.nextTick(finish, err2);
127448
+ process2.nextTick(finish, err2);
127449
127449
  }
127450
127450
  );
127451
127451
  } else if (isIterable2(ret, true)) {
@@ -127526,7 +127526,7 @@ ${values.join("\n")}` : `${blockName} :`;
127526
127526
  }
127527
127527
  }
127528
127528
  if (signal !== null && signal !== void 0 && signal.aborted || outerSignal !== null && outerSignal !== void 0 && outerSignal.aborted) {
127529
- process.nextTick(abort);
127529
+ process2.nextTick(abort);
127530
127530
  }
127531
127531
  return ret;
127532
127532
  }
@@ -127547,7 +127547,7 @@ ${values.join("\n")}` : `${blockName} :`;
127547
127547
  };
127548
127548
  var endFn = endFn2;
127549
127549
  if (isReadableFinished(src4)) {
127550
- process.nextTick(endFn2);
127550
+ process2.nextTick(endFn2);
127551
127551
  } else {
127552
127552
  src4.once("end", endFn2);
127553
127553
  }
@@ -163906,7 +163906,1576 @@ ${error.stack}` : head;
163906
163906
  }
163907
163907
  }
163908
163908
 
163909
+ // src/utils/version-identity.ts
163910
+ var PKG_NAME = "@bolloon/bolloon-agent";
163911
+ var GITHUB_UPSTREAM_SLUG = "logos-42/bolloon";
163912
+ function envValue(key) {
163913
+ try {
163914
+ const cfg = globalThis?.__bolloonUpdateConfig;
163915
+ if (cfg && cfg[key] !== void 0 && cfg[key] !== null) return String(cfg[key]);
163916
+ const env = typeof process !== "undefined" ? process?.env : null;
163917
+ const v = env ? env[key] : void 0;
163918
+ return v === void 0 || v === null ? void 0 : String(v);
163919
+ } catch {
163920
+ return void 0;
163921
+ }
163922
+ }
163923
+ var NPM_REGISTRY_BASE = (envValue("BOLLOON_NPM_REGISTRY") || "https://registry.npmjs.org").replace(/\/+$/, "");
163924
+ var GITHUB_API_BASE = (envValue("BOLLOON_GITHUB_API") || "https://api.github.com").replace(/\/+$/, "");
163925
+ var DEV_BRANCH = "master";
163926
+ var DEV_REF = `refs/heads/${DEV_BRANCH}`;
163927
+ function resolveUpdateChannel(explicit, env) {
163928
+ const raw = (explicit || (env ? env.BOLLOON_UPDATE_CHANNEL : envValue("BOLLOON_UPDATE_CHANNEL")) || "").trim().toLowerCase();
163929
+ if (raw === "stable" || raw === "beta" || raw === "dev") return raw;
163930
+ return "stable";
163931
+ }
163932
+ function distTagForChannel(channel) {
163933
+ return channel === "beta" ? "beta" : "latest";
163934
+ }
163935
+ function channelKindOf(channel) {
163936
+ return channel === "dev" ? "git-ref" : "semver";
163937
+ }
163938
+ var DEV_IDENTITY_SEP = "+dev.";
163939
+ function devIdentity(baseVersion, sha4) {
163940
+ return `${baseVersionOf(baseVersion)}${DEV_IDENTITY_SEP}${String(sha4).slice(0, 7)}`;
163941
+ }
163942
+ function baseVersionOf(v) {
163943
+ return String(v || "").split(DEV_IDENTITY_SEP)[0].trim();
163944
+ }
163945
+ function isDevIdentity(v) {
163946
+ return String(v || "").includes(DEV_IDENTITY_SEP);
163947
+ }
163948
+ function devShaFromIdentity(v) {
163949
+ if (!isDevIdentity(v)) return null;
163950
+ const sha4 = String(v).split(DEV_IDENTITY_SEP)[1]?.trim();
163951
+ return sha4 ? sha4 : null;
163952
+ }
163953
+ var DEV_CHANNEL_WARNING = "\u26A0\uFE0F dev \u901A\u9053 = GitHub master HEAD \u7684\u5373\u65F6\u5FEB\u7167 (\u672A\u8D70\u53D1\u5E03\u95E8): \u53EF\u80FD\u4E2D\u65AD\u6B63\u5728\u8DD1\u7684 Goal/Run, \u4E14\u4E0D\u4FDD\u8BC1\u53EF\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A dev \u7248\u3002";
163954
+ var DEV_BACK_TO_STABLE_HINT_MOBILE = "\u4E00\u952E\u56DE\u7A33\u5B9A\u7248: \u624B\u673A\u7AEF\u300C\u66F4\u65B0\u300D\u9875 \u2192 \u5207\u56DE stable";
163955
+ function parseVersion(v) {
163956
+ const clean8 = String(v || "").trim().replace(/^v/, "").split("-")[0];
163957
+ const parts = clean8.split(".").map((p) => parseInt(p.replace(/\D.*$/, ""), 10));
163958
+ return parts.map((n) => Number.isFinite(n) ? n : 0);
163959
+ }
163960
+ function compareVersions(a, b) {
163961
+ const pa = parseVersion(a);
163962
+ const pb = parseVersion(b);
163963
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
163964
+ const x = pa[i] ?? 0;
163965
+ const y2 = pb[i] ?? 0;
163966
+ if (x < y2) return -1;
163967
+ if (x > y2) return 1;
163968
+ }
163969
+ return 0;
163970
+ }
163971
+ function isKnownVersion(v) {
163972
+ return !!v && v !== "unknown" && /^\d/.test(String(v).trim().replace(/^v/, ""));
163973
+ }
163974
+ var CHECK_STATUSES = [
163975
+ "up_to_date",
163976
+ // 确认最新
163977
+ "update_available",
163978
+ // 有新版本
163979
+ "check_skipped",
163980
+ // 节流/显式跳过, 结论来自缓存
163981
+ "offline",
163982
+ // 网络不可达 (≠ 最新)
163983
+ "registry_unavailable",
163984
+ // 可达但拿不到有效数据 (5xx / 包不存在 / 无 dist-tags)
163985
+ "local_version_unknown",
163986
+ // 读不到本地版本 (不猜 0.0.0)
163987
+ "unsupported_installation",
163988
+ // 认得出本地版本, 但这种安装方式不支持自动更新
163989
+ "github_unavailable",
163990
+ // GitHub 源不可用 (offline / rate_limited / not_found / http_error)
163991
+ "cross_check_mismatch"
163992
+ // npm 与 GitHub 两个源不一致 → 拒绝更新
163993
+ ];
163994
+ var REFUSED_STATUSES = ["offline", "registry_unavailable", "local_version_unknown", "github_unavailable", "cross_check_mismatch"];
163995
+ function describeSourceIdentity(info) {
163996
+ const src4 = info.installedDevSha ? "dev (GitHub master \u5FEB\u7167)" : "stable (npm registry)";
163997
+ const ident = info.installedDevSha ? `commit ${info.installedDevSha}` : baseVersionOf(info.packageVersion);
163998
+ const back = info.switchableTo ? ` \xB7 \u53EF\u5207\u56DE ${info.switchableTo.channel} (${info.switchableTo.source})` : "";
163999
+ return `\u5F53\u524D\u5B89\u88C5\u6E90: ${src4} \xB7 \u7248\u672C/commit: ${ident} \xB7 \u6BD4\u8F83\u8BED\u4E49: ${info.channelKind}${back}`;
164000
+ }
164001
+
164002
+ // src/utils/dual-source-facts.ts
164003
+ function toGithubReport(r) {
164004
+ if (!r.ok) {
164005
+ return {
164006
+ reachable: false,
164007
+ reason: r.reason,
164008
+ detail: r.detail,
164009
+ retryAt: r.retryAt,
164010
+ headSha: r.facts?.headSha ?? null,
164011
+ ref: r.facts?.ref ?? `refs/heads/${DEV_BRANCH}`,
164012
+ releases: r.facts?.releases.length,
164013
+ tags: r.facts?.tags.length,
164014
+ newestVersion: r.facts ? newestGithubVersion(r.facts) : null,
164015
+ partial: r.facts?.partial,
164016
+ fetchedAt: r.facts?.fetchedAt
164017
+ };
164018
+ }
164019
+ return {
164020
+ reachable: true,
164021
+ headSha: r.facts.headSha,
164022
+ ref: r.facts.ref,
164023
+ releases: r.facts.releases.length,
164024
+ tags: r.facts.tags.length,
164025
+ newestVersion: newestGithubVersion(r.facts),
164026
+ partial: r.facts.partial,
164027
+ fetchedAt: r.facts.fetchedAt
164028
+ };
164029
+ }
164030
+ function renderGithubReportLine(g) {
164031
+ if (!g) return "GitHub \u6E90: \u672A\u67E5\u8BE2";
164032
+ if (!g.reachable) {
164033
+ const retry = g.retryAt ? `, \u9650\u6D41\u91CD\u8BD5\u65F6\u95F4 ${g.retryAt}` : "";
164034
+ return `GitHub \u6E90: \u4E0D\u53EF\u8FBE \u2014 github_unavailable(${g.reason}) ${g.detail || ""}${retry}`;
164035
+ }
164036
+ return `GitHub \u6E90: \u53EF\u8FBE \xB7 master HEAD ${g.headSha ? g.headSha.slice(0, 7) : "\u672A\u77E5"} \xB7 Release ${g.releases ?? 0} \u4E2A \xB7 Tag ${g.tags ?? 0} \u4E2A \xB7 \u6700\u65B0\u7248\u672C\u6807\u7B7E ${g.newestVersion || "\u65E0"}${g.partial ? " (\u90E8\u5206\u8BF7\u6C42\u5931\u8D25)" : ""}`;
164037
+ }
164038
+ var OFFLINE_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "EAI_AGAIN", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "EHOSTUNREACH", "ENETUNREACH", "ENETDOWN", "EAI_FAIL", "UND_ERR_CONNECT_TIMEOUT", "CERT_HAS_EXPIRED"]);
164039
+ function collectNetEvidence(err2) {
164040
+ const codes = [];
164041
+ const msgs = [];
164042
+ let cur = err2;
164043
+ for (let depth = 0; cur && depth < 6; depth++) {
164044
+ const inner = Array.isArray(cur.errors) ? cur.errors : [];
164045
+ for (const e of inner) {
164046
+ if (e?.code) codes.push(String(e.code));
164047
+ if (e?.message) msgs.push(String(e.message));
164048
+ }
164049
+ if (cur.code) codes.push(String(cur.code));
164050
+ if (cur.errno) codes.push(String(cur.errno));
164051
+ if (cur.message) msgs.push(String(cur.message));
164052
+ cur = cur.cause;
164053
+ }
164054
+ const code10 = codes.find((c) => OFFLINE_CODES.has(c)) || codes[0] || "";
164055
+ const message16 = msgs.join(" \xB7 ");
164056
+ const offline = codes.some((c) => OFFLINE_CODES.has(c)) || /timeout|timed out|ENOTFOUND|getaddrinfo|network|socket hang up|超时|Failed to fetch|fetch failed|NetworkError|Load failed|ECONNREFUSED|ECONNRESET/i.test(message16);
164057
+ return { code: code10, message: message16, offline };
164058
+ }
164059
+ function classifyGithubError(err2, httpStatus, headers = {}) {
164060
+ const retryAt = headers["x-ratelimit-reset"] ? new Date(Number(headers["x-ratelimit-reset"]) * 1e3).toISOString() : null;
164061
+ const net = collectNetEvidence(err2);
164062
+ if (net.offline) {
164063
+ return { kind: "github_unavailable", reason: "offline", detail: `${net.code || net.message || "\u7F51\u7EDC\u4E0D\u53EF\u8FBE"}`, retryAt: null };
164064
+ }
164065
+ if (httpStatus === 403 || httpStatus === 429) {
164066
+ return { kind: "github_unavailable", reason: "rate_limited", detail: `GitHub \u8FD4\u56DE HTTP ${httpStatus} (\u9650\u6D41/\u6743\u9650)`, retryAt };
164067
+ }
164068
+ if (httpStatus === 404) {
164069
+ return { kind: "github_unavailable", reason: "not_found", detail: `GitHub \u4E0A\u6CA1\u6709 ${GITHUB_UPSTREAM_SLUG} \u7684\u8BE5\u9879\u8BB0\u5F55 (HTTP 404)`, retryAt: null };
164070
+ }
164071
+ if (httpStatus && httpStatus >= 400) {
164072
+ return { kind: "github_unavailable", reason: "http_error", detail: `GitHub \u8FD4\u56DE HTTP ${httpStatus}`, retryAt };
164073
+ }
164074
+ return { kind: "github_unavailable", reason: "http_error", detail: net.code || net.message || "\u672A\u77E5\u9519\u8BEF", retryAt: null };
164075
+ }
164076
+ function classifyRegistryError(err2, httpStatus) {
164077
+ const net = collectNetEvidence(err2);
164078
+ if (net.offline) {
164079
+ return { kind: "offline", detail: `${net.code || net.message || "\u7F51\u7EDC\u4E0D\u53EF\u8FBE"}` };
164080
+ }
164081
+ if (httpStatus && httpStatus >= 500) return { kind: "registry_unavailable", detail: `registry \u8FD4\u56DE HTTP ${httpStatus}` };
164082
+ if (httpStatus === 404) return { kind: "registry_unavailable", detail: `registry \u4E0A\u6CA1\u6709\u8BE5\u5305 (HTTP 404)` };
164083
+ if (httpStatus && httpStatus >= 400) return { kind: "registry_unavailable", detail: `registry \u8FD4\u56DE HTTP ${httpStatus}` };
164084
+ return { kind: "registry_unavailable", detail: net.code || net.message || "\u672A\u77E5\u9519\u8BEF" };
164085
+ }
164086
+ function newestGithubVersion(facts) {
164087
+ const all2 = [...facts.releases, ...facts.tags.map((t) => t.name)].map((n) => String(n).trim().replace(/^v/, "")).filter((n) => /^\d+\.\d+\.\d+/.test(n));
164088
+ if (all2.length === 0) return null;
164089
+ return all2.sort((a, b) => numericCompare(a, b) < 0 ? 1 : -1)[0];
164090
+ }
164091
+ function numericCompare(a, b) {
164092
+ const p = (v) => v.split("-")[0].split(".").map((x) => parseInt(x.replace(/\D.*$/, ""), 10) || 0);
164093
+ const pa = p(a);
164094
+ const pb = p(b);
164095
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
164096
+ const x = pa[i] ?? 0;
164097
+ const y2 = pb[i] ?? 0;
164098
+ if (x < y2) return -1;
164099
+ if (x > y2) return 1;
164100
+ }
164101
+ return 0;
164102
+ }
164103
+ function hasName(names, v) {
164104
+ const set = new Set(names.map((n) => String(n).trim().replace(/^v/, "")));
164105
+ return set.has(v.replace(/^v/, ""));
164106
+ }
164107
+ function crossCheckStable(registryLatest, facts) {
164108
+ const common = {
164109
+ registryLatest,
164110
+ githubVersion: newestGithubVersion(facts),
164111
+ releases: facts.releases.length,
164112
+ tags: facts.tags.length
164113
+ };
164114
+ if (!registryLatest) {
164115
+ return { ...common, kind: "no_record_at_all", hasTag: false, hasRelease: false, blocking: false, detail: "npm \u6CA1\u6709\u7ED9\u51FA latest, \u65E0\u4ECE\u4EA4\u53C9\u6821\u9A8C" };
164116
+ }
164117
+ const allNames = [...facts.releases, ...facts.tags.map((t) => t.name)];
164118
+ const hasTag = hasName(facts.tags.map((t) => t.name), registryLatest);
164119
+ const hasRelease = hasName(facts.releases, registryLatest);
164120
+ const ghNewest = common.githubVersion;
164121
+ if (ghNewest && numericCompare(ghNewest, registryLatest) > 0) {
164122
+ return {
164123
+ ...common,
164124
+ kind: "mismatch",
164125
+ hasTag,
164126
+ hasRelease,
164127
+ blocking: true,
164128
+ detail: `GitHub \u4E0A\u6709 ${ghNewest} \u7684\u53D1\u5E03\u8BB0\u5F55, \u4F46 npm registry \u7684 latest \u8FD8\u662F ${registryLatest} \u2014 \u62D2\u7EDD\u6309 GitHub \u7684\u8BB0\u5F55\u5B89\u88C5`
164129
+ };
164130
+ }
164131
+ if (hasTag || hasRelease) {
164132
+ return {
164133
+ ...common,
164134
+ kind: "agree",
164135
+ hasTag,
164136
+ hasRelease,
164137
+ blocking: false,
164138
+ detail: `npm latest=${registryLatest} \u5728 GitHub \u4E0A\u6709\u540C\u540D\u8BB0\u5F55 (${hasRelease ? `Release v${registryLatest}` : `Tag v${registryLatest}`}) \u2014 \u4E24\u4E2A\u6E90\u6307\u5411\u540C\u4E00\u7248`
164139
+ };
164140
+ }
164141
+ if (allNames.length === 0) {
164142
+ return {
164143
+ ...common,
164144
+ kind: "no_record_at_all",
164145
+ hasTag: false,
164146
+ hasRelease: false,
164147
+ blocking: false,
164148
+ detail: `GitHub \u4E0A\u6682\u65E0\u4EFB\u4F55 tag / Release (0 \u4E2A) \u2014 \u6682\u65E0\u53EF\u4EA4\u53C9\u6821\u9A8C\u7684\u8BB0\u5F55, npm \u4ECD\u662F\u6743\u5A01 (latest=${registryLatest})`
164149
+ };
164150
+ }
164151
+ return {
164152
+ ...common,
164153
+ kind: "missing_record",
164154
+ hasTag,
164155
+ hasRelease,
164156
+ blocking: false,
164157
+ detail: `npm latest=${registryLatest} \u5728 GitHub \u4E0A\u6CA1\u6709\u5BF9\u5E94\u8BB0\u5F55 (GitHub \u6700\u65B0\u7248\u672C\u6807\u7B7E ${ghNewest || "\u65E0"}; Release ${facts.releases.length} \u4E2A / Tag ${facts.tags.length} \u4E2A) \u2014 \u53D1\u5E03\u8BB0\u5F55\u7F3A Tag, \u4E0D\u963B\u585E stable \u66F4\u65B0`
164158
+ };
164159
+ }
164160
+ function compareDevSnapshots(installedSha, headSha, ref = `refs/heads/${DEV_BRANCH}`) {
164161
+ const short = (s2) => s2 ? s2.slice(0, 7) : "\u672A\u77E5";
164162
+ if (!headSha) {
164163
+ return { ref, headSha, installedSha, same: false, behind: false, detail: `${ref} \u7684 HEAD \u8BFB\u4E0D\u5230 \u2014 \u65E0\u6CD5\u5224\u5B9A dev \u5FEB\u7167\u65B0\u65E7` };
164164
+ }
164165
+ if (!installedSha) {
164166
+ return { ref, headSha, installedSha, same: false, behind: false, detail: `\u5F53\u524D\u4E0D\u662F dev \u5FEB\u7167 (stable \u5B89\u88C5) \u2014 \u5207\u5230 dev \u5C06\u5F97\u5230 ${ref} @ ${short(headSha)}` };
164167
+ }
164168
+ const same = installedSha.slice(0, 7) === headSha.slice(0, 7);
164169
+ return {
164170
+ ref,
164171
+ headSha,
164172
+ installedSha,
164173
+ same,
164174
+ behind: !same,
164175
+ detail: same ? `\u5DF2\u662F\u6700\u65B0\u7684 dev \u5FEB\u7167 (${ref} @ ${short(headSha)}) \u2014 \u540C\u4E00\u4E2A commit` : `dev \u5FEB\u7167\u843D\u540E: \u5DF2\u88C5 ${short(installedSha)} \u2192 master HEAD ${short(headSha)} (\u6309 commit sha \u5224\u5B9A, \u4E0D\u770B\u7248\u672C\u53F7)`
164176
+ };
164177
+ }
164178
+
164179
+ // src/web/mobile-update.ts
164180
+ var MOBILE_WEB_STATUSES = [
164181
+ ...CHECK_STATUSES,
164182
+ "dev_bundle_unavailable",
164183
+ "dev_sha_mismatch",
164184
+ "target_unpublished",
164185
+ "digest_mismatch",
164186
+ "web_bundle_malformed",
164187
+ "decompress_unavailable",
164188
+ "payload_too_large",
164189
+ "native_shell_not_wired"
164190
+ ];
164191
+ var MOBILE_REFUSED_STATUSES = [
164192
+ ...REFUSED_STATUSES,
164193
+ "target_unpublished",
164194
+ "dev_bundle_unavailable",
164195
+ "dev_sha_mismatch",
164196
+ "digest_mismatch",
164197
+ "web_bundle_malformed",
164198
+ "decompress_unavailable",
164199
+ "payload_too_large",
164200
+ "native_shell_not_wired"
164201
+ ];
164202
+ var MOBILE_WEB_REQUIRED_FILES = ["mobile.html", "mobile.js", "mobile-core.js", "mobile.css"];
164203
+ var MOBILE_WEB_JS_FILES = ["mobile.js", "mobile-core.js", "a2ui-client.js"];
164204
+ var MOBILE_MAX_PAYLOAD_BYTES = 80 * 1024 * 1024;
164205
+ var WEB_LAYOUT = {
164206
+ current: "current",
164207
+ staging: "staging",
164208
+ previous: "previous",
164209
+ state: "state.json"
164210
+ };
164211
+ function joinRel(...parts) {
164212
+ return parts.map((p) => String(p).replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
164213
+ }
164214
+ function createMemoryWebStore(opts = {}) {
164215
+ const files = /* @__PURE__ */ new Map();
164216
+ const dirs = /* @__PURE__ */ new Set([""]);
164217
+ const norm = (rel) => joinRel(rel);
164218
+ const isDir = (rel) => dirs.has(norm(rel));
164219
+ return {
164220
+ kind: opts.kind || "memory",
164221
+ rootLabel: opts.rootLabel || "memory://bolloon-web",
164222
+ async list(rel) {
164223
+ const d2 = norm(rel);
164224
+ const out = [];
164225
+ for (const k of files.keys()) {
164226
+ if (d2 === "") {
164227
+ if (!k.includes("/")) out.push(k);
164228
+ continue;
164229
+ }
164230
+ if (k.startsWith(d2 + "/")) {
164231
+ const rest = k.slice(d2.length + 1);
164232
+ if (!rest.includes("/")) out.push(rest);
164233
+ }
164234
+ }
164235
+ return out.sort();
164236
+ },
164237
+ async exists(rel) {
164238
+ const k = norm(rel);
164239
+ return files.has(k) || isDir(k);
164240
+ },
164241
+ async readBytes(rel) {
164242
+ const v = files.get(norm(rel));
164243
+ return v ? v.slice() : null;
164244
+ },
164245
+ async readText(rel) {
164246
+ const v = files.get(norm(rel));
164247
+ return v ? new TextDecoder().decode(v) : null;
164248
+ },
164249
+ async writeBytes(rel, data) {
164250
+ const k = norm(rel);
164251
+ const parts = k.split("/");
164252
+ for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join("/"));
164253
+ files.set(k, data.slice ? data.slice() : data);
164254
+ },
164255
+ async remove(rel) {
164256
+ const k = norm(rel);
164257
+ for (const key of Array.from(files.keys())) if (key === k || key.startsWith(k + "/")) files.delete(key);
164258
+ for (const d2 of Array.from(dirs)) if (d2 === k || d2.startsWith(k + "/")) dirs.delete(d2);
164259
+ },
164260
+ async rename(from6, to) {
164261
+ const f = norm(from6);
164262
+ const t = norm(to);
164263
+ const moves = [];
164264
+ for (const key of Array.from(files.keys())) {
164265
+ if (key === f) moves.push({ from: key, to: t });
164266
+ else if (key.startsWith(f + "/")) moves.push({ from: key, to: t + key.slice(f.length) });
164267
+ }
164268
+ await this.remove(t);
164269
+ for (const m2 of moves) {
164270
+ const v = files.get(m2.from);
164271
+ files.delete(m2.from);
164272
+ const parts = m2.to.split("/");
164273
+ for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join("/"));
164274
+ files.set(m2.to, v);
164275
+ }
164276
+ dirs.delete(f);
164277
+ dirs.add(t);
164278
+ },
164279
+ baseUrl() {
164280
+ return opts.baseUrl ?? null;
164281
+ }
164282
+ };
164283
+ }
164284
+ function createCapacitorWebStore(plugin, opts = {}) {
164285
+ const directory = opts.directory ?? "DATA";
164286
+ const basePath = (opts.basePath || "bolloon-web").replace(/^\/+|\/+$/g, "");
164287
+ const full = (rel) => joinRel(basePath, rel);
164288
+ const b64ToBytes2 = (b642) => {
164289
+ if (typeof atob === "function") {
164290
+ const bin = atob(b642);
164291
+ const out = new Uint8Array(bin.length);
164292
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
164293
+ return out;
164294
+ }
164295
+ return new Uint8Array(Buffer.from(b642, "base64"));
164296
+ };
164297
+ const bytesToB642 = (data) => {
164298
+ let bin = "";
164299
+ for (let i = 0; i < data.length; i++) bin += String.fromCharCode(data[i]);
164300
+ if (typeof btoa === "function") return btoa(bin);
164301
+ return Buffer.from(data).toString("base64");
164302
+ };
164303
+ return {
164304
+ kind: "capacitor-fs",
164305
+ rootLabel: `${String(directory)}:${basePath}`,
164306
+ async list(rel) {
164307
+ try {
164308
+ const r = await plugin.readdir({ path: full(rel), directory });
164309
+ return (r?.files || []).filter((f) => f?.type !== "directory").map((f) => String(f.name)).sort();
164310
+ } catch {
164311
+ return [];
164312
+ }
164313
+ },
164314
+ async exists(rel) {
164315
+ try {
164316
+ await plugin.stat({ path: full(rel), directory });
164317
+ return true;
164318
+ } catch {
164319
+ return false;
164320
+ }
164321
+ },
164322
+ async readBytes(rel) {
164323
+ try {
164324
+ const r = await plugin.readFile({ path: full(rel), directory });
164325
+ const data = String(r?.data || "");
164326
+ return data ? b64ToBytes2(data) : null;
164327
+ } catch {
164328
+ return null;
164329
+ }
164330
+ },
164331
+ async readText(rel) {
164332
+ try {
164333
+ const r = await plugin.readFile({ path: full(rel), directory, encoding: "utf8" });
164334
+ return r?.data === void 0 ? null : String(r.data);
164335
+ } catch {
164336
+ return null;
164337
+ }
164338
+ },
164339
+ async writeBytes(rel, data) {
164340
+ const path = full(rel);
164341
+ const parts = full(rel).split("/");
164342
+ for (let i = 1; i < parts.length; i++) {
164343
+ try {
164344
+ await plugin.mkdir({ path: parts.slice(0, i).join("/"), directory, recursive: true });
164345
+ } catch {
164346
+ }
164347
+ }
164348
+ await plugin.writeFile({ path, data: bytesToB642(data), directory });
164349
+ },
164350
+ async remove(rel) {
164351
+ try {
164352
+ await plugin.rmdir({ path: full(rel), directory, recursive: true });
164353
+ return;
164354
+ } catch {
164355
+ }
164356
+ try {
164357
+ await plugin.rmdir({ path: full(rel), directory });
164358
+ } catch {
164359
+ }
164360
+ },
164361
+ async rename(from6, to) {
164362
+ try {
164363
+ await plugin.rmdir({ path: full(to), directory, recursive: true });
164364
+ } catch {
164365
+ }
164366
+ await plugin.rename({ from: full(from6), to: full(to), directory, toDirectory: directory });
164367
+ },
164368
+ baseUrl() {
164369
+ return opts.baseUrl ?? null;
164370
+ }
164371
+ };
164372
+ }
164373
+ async function gunzipBytes(data) {
164374
+ const DS = globalThis.DecompressionStream;
164375
+ if (typeof DS !== "function") return null;
164376
+ try {
164377
+ const stream = new globalThis.Blob([data]).stream().pipeThrough(new DS("gzip"));
164378
+ const buf = await new Response(stream).arrayBuffer();
164379
+ return new Uint8Array(buf);
164380
+ } catch {
164381
+ return null;
164382
+ }
164383
+ }
164384
+ function untarBytes(buf) {
164385
+ const out = [];
164386
+ const td = new TextDecoder();
164387
+ const readStr = (off, len) => {
164388
+ let end = off;
164389
+ while (end < off + len && buf[end] !== 0) end++;
164390
+ return td.decode(buf.subarray(off, end));
164391
+ };
164392
+ const readOctal = (off, len) => {
164393
+ const s2 = readStr(off, len).trim().replace(/\0/g, "");
164394
+ const v = parseInt(s2, 8);
164395
+ return Number.isFinite(v) ? v : 0;
164396
+ };
164397
+ let p = 0;
164398
+ let paxPath = null;
164399
+ while (p + 512 <= buf.length) {
164400
+ const name0 = readStr(p, 100);
164401
+ if (!name0) break;
164402
+ const size2 = readOctal(p + 124, 12);
164403
+ const typeflag = String.fromCharCode(buf[p + 156] || 48);
164404
+ const prefix = readStr(p + 345, 155);
164405
+ const dataStart = p + 512;
164406
+ const dataEnd = dataStart + size2;
164407
+ if (dataEnd > buf.length) break;
164408
+ let name10 = prefix ? `${prefix}/${name0}` : name0;
164409
+ if (typeflag === "x" || typeflag === "g") {
164410
+ const text = td.decode(buf.subarray(dataStart, dataEnd));
164411
+ const m2 = text.match(/\d+ path=([^\n]+)\n/);
164412
+ if (m2) paxPath = m2[1];
164413
+ } else if (typeflag === "L") {
164414
+ paxPath = td.decode(buf.subarray(dataStart, dataEnd)).replace(/\0+$/, "").trim();
164415
+ } else if (typeflag === "0" || typeflag === "\0" || typeflag === "" || typeflag === "0") {
164416
+ if (paxPath) {
164417
+ name10 = paxPath;
164418
+ paxPath = null;
164419
+ }
164420
+ out.push({ path: name10.replace(/^\.\//, ""), data: buf.slice(dataStart, dataEnd) });
164421
+ } else {
164422
+ paxPath = null;
164423
+ }
164424
+ p = dataStart + Math.ceil(size2 / 512) * 512;
164425
+ }
164426
+ return out;
164427
+ }
164428
+ var WEB_PATH_PREFIXES = ["package/dist/web/", "dist/web/", "./dist/web/"];
164429
+ function isWebResourcePath(path) {
164430
+ const p = String(path).replace(/^\.\//, "");
164431
+ for (const pre of WEB_PATH_PREFIXES) {
164432
+ if (p.startsWith(pre)) return { hit: true, rel: p.slice(pre.length) };
164433
+ }
164434
+ return { hit: false, rel: "" };
164435
+ }
164436
+ function pickWebLayer(entries) {
164437
+ const files = [];
164438
+ let snapshot2 = null;
164439
+ const td = new TextDecoder();
164440
+ for (const e of entries) {
164441
+ const p = String(e.path).replace(/^\.\//, "");
164442
+ if (/(^|\/)\.bolloon-dev-snapshot\.json$/.test(p)) {
164443
+ try {
164444
+ snapshot2 = JSON.parse(td.decode(e.data));
164445
+ } catch {
164446
+ snapshot2 = null;
164447
+ }
164448
+ continue;
164449
+ }
164450
+ const w2 = isWebResourcePath(p);
164451
+ if (w2.hit && w2.rel && !w2.rel.endsWith("/")) files.push({ rel: w2.rel, data: e.data });
164452
+ }
164453
+ return { files, snapshot: snapshot2 };
164454
+ }
164455
+ function toHex(buf) {
164456
+ const b = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
164457
+ let s2 = "";
164458
+ for (let i = 0; i < b.length; i++) s2 += b[i].toString(16).padStart(2, "0");
164459
+ return s2;
164460
+ }
164461
+ function normalizeAlgo(a) {
164462
+ const k = String(a || "").toLowerCase().replace(/[^a-z0-9]/g, "");
164463
+ if (k === "sha1") return "SHA-1";
164464
+ if (k === "sha256") return "SHA-256";
164465
+ if (k === "sha512") return "SHA-512";
164466
+ return null;
164467
+ }
164468
+ async function digestHex(data, algo) {
164469
+ const c = globalThis.crypto?.subtle;
164470
+ if (!c) return null;
164471
+ const name10 = normalizeAlgo(algo);
164472
+ if (!name10) return null;
164473
+ try {
164474
+ return toHex(await c.digest(name10, data));
164475
+ } catch {
164476
+ return null;
164477
+ }
164478
+ }
164479
+ function parseSri(integrity) {
164480
+ const s2 = String(integrity || "").trim();
164481
+ const m2 = s2.match(/^(sha512|sha256|sha1)-([A-Za-z0-9+/=]+)/);
164482
+ if (!m2) return null;
164483
+ return { algo: m2[1], b64: m2[2] };
164484
+ }
164485
+ function b64Of(bytes) {
164486
+ let bin = "";
164487
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
164488
+ if (typeof btoa === "function") return btoa(bin);
164489
+ return Buffer.from(bytes).toString("base64");
164490
+ }
164491
+ var MOBILE_UPDATE_STATE_SCHEMA = "bolloon-mobile-web/1";
164492
+ function emptyMobileUpdateState() {
164493
+ return {
164494
+ schema: MOBILE_UPDATE_STATE_SCHEMA,
164495
+ currentVersion: null,
164496
+ installedChannel: null,
164497
+ installedDevSha: null,
164498
+ channelPref: null,
164499
+ installedFrom: null,
164500
+ switchableTo: null,
164501
+ lastDevSha: null,
164502
+ devRef: null,
164503
+ devCheckedAt: null,
164504
+ lastCheck: null,
164505
+ lastUpdate: null,
164506
+ lastFailure: null,
164507
+ autoPrepared: null,
164508
+ history: [],
164509
+ needsReload: false
164510
+ };
164511
+ }
164512
+ async function readMobileUpdateState(store) {
164513
+ const base6 = emptyMobileUpdateState();
164514
+ const text = await store.readText(WEB_LAYOUT.state);
164515
+ if (!text) return base6;
164516
+ try {
164517
+ const raw = JSON.parse(text);
164518
+ if (!raw || raw.schema !== MOBILE_UPDATE_STATE_SCHEMA) return base6;
164519
+ return { ...base6, ...raw, history: Array.isArray(raw.history) ? raw.history.slice(0, 20) : [] };
164520
+ } catch {
164521
+ return base6;
164522
+ }
164523
+ }
164524
+ async function writeMobileUpdateState(patch, store) {
164525
+ const cur = await readMobileUpdateState(store);
164526
+ const next = { ...cur, ...patch, schema: MOBILE_UPDATE_STATE_SCHEMA };
164527
+ await store.writeBytes(WEB_LAYOUT.state, new TextEncoder().encode(JSON.stringify(next, null, 2)));
164528
+ return next;
164529
+ }
164530
+ async function fetchJson(url, fetchImpl, timeoutMs = 15e3) {
164531
+ try {
164532
+ const ac = globalThis.AbortController ? new AbortController() : null;
164533
+ const timer = ac ? setTimeout(() => {
164534
+ try {
164535
+ ac.abort();
164536
+ } catch {
164537
+ }
164538
+ }, timeoutMs) : null;
164539
+ const res = await fetchImpl(url, { headers: { Accept: "application/json" }, signal: ac?.signal });
164540
+ const status = Number(res?.status || 0);
164541
+ const text = await res.text();
164542
+ if (timer) clearTimeout(timer);
164543
+ let json = null;
164544
+ try {
164545
+ json = JSON.parse(text);
164546
+ } catch {
164547
+ json = null;
164548
+ }
164549
+ return { ok: status >= 200 && status < 300, status, json, text };
164550
+ } catch (e) {
164551
+ return { ok: false, status: 0, json: null, text: "", err: e };
164552
+ }
164553
+ }
164554
+ async function queryMobileNpmFacts(deps, pkg) {
164555
+ if (deps.npmDoc !== void 0 && deps.npmDoc !== null) return normalizeNpmDoc(deps.npmDoc, null);
164556
+ if (deps.npmDoc === null && deps.githubFacts !== void 0) {
164557
+ return { reachable: false, kind: "offline", detail: "\u6CE8\u5165\u7684 registry \u4E8B\u5B9E\u4E3A null (\u53D7\u63A7\u4E0D\u53EF\u8FBE: offline)", latest: null, distTags: {}, versions: [], dist: null };
164558
+ }
164559
+ const base6 = (deps.registryBase || NPM_REGISTRY_BASE).replace(/\/+$/, "");
164560
+ const url = `${base6}/${encodeURIComponent(pkg).replace("%40", "@")}`;
164561
+ const fetchImpl = deps.fetchImpl || globalThis.fetch?.bind(globalThis);
164562
+ if (!fetchImpl) return { reachable: false, kind: "offline", detail: "\u672C\u673A\u6CA1\u6709 fetch (offline)", latest: null, distTags: {}, versions: [], dist: null };
164563
+ const r = await fetchJson(url, fetchImpl);
164564
+ if (!r.ok) {
164565
+ const cls = classifyRegistryError(r.err || null, r.status || void 0);
164566
+ return { reachable: false, kind: cls.kind, detail: cls.kind === "offline" ? `offline: ${cls.detail}` : `registry_unavailable: ${cls.detail}`, latest: null, distTags: {}, versions: [], dist: null };
164567
+ }
164568
+ if (!r.json) return { reachable: false, kind: "registry_unavailable", detail: "registry_unavailable: registry \u8FD4\u56DE\u4E86\u65E0\u6CD5\u89E3\u6790\u7684\u5185\u5BB9", latest: null, distTags: {}, versions: [], dist: null };
164569
+ return normalizeNpmDoc(r.json, null);
164570
+ }
164571
+ function normalizeNpmDoc(doc, detail) {
164572
+ const distTags = doc?.["dist-tags"] || {};
164573
+ const versions = Object.keys(doc?.versions || {});
164574
+ if (!distTags.latest && versions.length === 0) {
164575
+ return { reachable: false, detail: detail || "registry \u4E0A\u6CA1\u6709\u53EF\u7528\u7248\u672C", latest: null, distTags: {}, versions: [], dist: null };
164576
+ }
164577
+ const latest = distTags.latest || versions[versions.length - 1] || null;
164578
+ const v = latest ? doc?.versions?.[latest] : null;
164579
+ return {
164580
+ reachable: true,
164581
+ latest,
164582
+ distTags,
164583
+ versions,
164584
+ dist: v?.dist ? { tarball: String(v.dist.tarball || ""), shasum: v.dist.shasum ? String(v.dist.shasum) : null, integrity: v.dist.integrity ? String(v.dist.integrity) : null } : null
164585
+ };
164586
+ }
164587
+ async function queryMobileGithubFacts(deps) {
164588
+ if (deps.githubFacts !== void 0 && deps.githubFacts !== null) return deps.githubFacts;
164589
+ if (deps.githubFacts === null) return null;
164590
+ const fetchImpl = deps.fetchImpl || globalThis.fetch?.bind(globalThis);
164591
+ if (!fetchImpl) return null;
164592
+ const base6 = (deps.apiBase || GITHUB_API_BASE).replace(/\/+$/, "");
164593
+ const missing = [];
164594
+ let releases = [];
164595
+ let tags = [];
164596
+ let headSha = null;
164597
+ const failures = [];
164598
+ const note = (e, what) => {
164599
+ missing.push(what);
164600
+ failures.push({ reason: e.reason, detail: `${what}: ${e.detail}`, retryAt: e.retryAt });
164601
+ };
164602
+ const rel = await fetchJson(`${base6}/repos/${GITHUB_UPSTREAM_SLUG}/releases?per_page=100`, fetchImpl);
164603
+ if (rel.ok && Array.isArray(rel.json)) releases = rel.json.map((x) => String(x?.tag_name || "")).filter(Boolean);
164604
+ else note(classifyGithubError(rel.err || null, rel.status || void 0), "releases");
164605
+ const tg = await fetchJson(`${base6}/repos/${GITHUB_UPSTREAM_SLUG}/tags?per_page=100`, fetchImpl);
164606
+ if (tg.ok && Array.isArray(tg.json)) tags = tg.json.map((x) => ({ name: String(x?.name || ""), sha: x?.commit?.sha ? String(x.commit.sha) : null })).filter((t) => t.name);
164607
+ else note(classifyGithubError(tg.err || null, tg.status || void 0), "tags");
164608
+ const hd = await fetchJson(`${base6}/repos/${GITHUB_UPSTREAM_SLUG}/commits/${DEV_BRANCH}`, fetchImpl);
164609
+ if (hd.ok && hd.json?.sha) headSha = String(hd.json.sha);
164610
+ else if (hd.ok) note({ reason: "parse_error", detail: "commits/master \u6CA1\u6709 sha \u5B57\u6BB5", retryAt: null }, "head");
164611
+ else note(classifyGithubError(hd.err || null, hd.status || void 0), "head");
164612
+ const facts = { slug: GITHUB_UPSTREAM_SLUG, ref: DEV_REF, headSha, releases, tags, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), partial: missing.length > 0, missing };
164613
+ if (failures.length) {
164614
+ const f = failures[0];
164615
+ return { ok: false, kind: "github_unavailable", reason: f.reason, detail: f.detail, retryAt: f.retryAt, facts };
164616
+ }
164617
+ return { ok: true, facts };
164618
+ }
164619
+ async function resolveLocalWebIdentity(deps) {
164620
+ if (deps.localIdentity && isKnownVersion(deps.localIdentity.version)) {
164621
+ const devSha = devShaFromIdentity(deps.localIdentity.version);
164622
+ return { version: String(deps.localIdentity.version), channel: devSha ? "dev" : deps.localIdentity.channel || "stable", source: "shell-injected" };
164623
+ }
164624
+ const st = await readMobileUpdateState(deps.store);
164625
+ if (st.currentVersion && isKnownVersion(st.currentVersion)) {
164626
+ const devSha = devShaFromIdentity(st.currentVersion);
164627
+ return { version: st.currentVersion, channel: devSha ? "dev" : st.installedChannel || "stable", source: "ota-state" };
164628
+ }
164629
+ const url = deps.localIdentityUrl === null ? null : deps.localIdentityUrl || "./bolloon-web.json";
164630
+ const fetchImpl = deps.fetchImpl || globalThis.fetch?.bind(globalThis);
164631
+ if (url && fetchImpl) {
164632
+ const r = await fetchJson(url, fetchImpl, 8e3).catch(() => null);
164633
+ const v = r?.json?.version;
164634
+ if (r?.ok && isKnownVersion(v)) {
164635
+ const devSha = devShaFromIdentity(v);
164636
+ return { version: String(v), channel: devSha ? "dev" : r.json.channel === "dev" ? "dev" : "stable", source: "build-stamp" };
164637
+ }
164638
+ }
164639
+ return null;
164640
+ }
164641
+ function refused(s2) {
164642
+ return MOBILE_REFUSED_STATUSES.includes(s2);
164643
+ }
164644
+ function nativeWired(deps) {
164645
+ if (deps.nativeWritable === true) return true;
164646
+ if (deps.nativeWritable === false) return false;
164647
+ return deps.store.kind === "memory";
164648
+ }
164649
+ function warnFor(channel, wired = true) {
164650
+ const w2 = channelKindOf(channel) === "git-ref" ? [DEV_CHANNEL_WARNING, DEV_BACK_TO_STABLE_HINT_MOBILE] : [];
164651
+ if (!wired) w2.push(NATIVE_CEILING_LINE);
164652
+ return w2;
164653
+ }
164654
+ async function checkMobileUpdate(deps) {
164655
+ const store = deps.store;
164656
+ const now = new Date(deps.now ? deps.now() : Date.now()).toISOString();
164657
+ const local = await resolveLocalWebIdentity(deps);
164658
+ const channel = resolveUpdateChannel(deps.channel);
164659
+ const channelKind = channelKindOf(channel);
164660
+ const base6 = {
164661
+ channel,
164662
+ channelKind,
164663
+ currentVersion: local?.version || "unknown",
164664
+ installedChannel: local ? devShaFromIdentity(local.version) ? "dev" : "stable" : null,
164665
+ installedDevSha: local ? devShaFromIdentity(local.version) : null,
164666
+ latestVersion: null,
164667
+ targetIdentity: null,
164668
+ switchableTo: null,
164669
+ crossCheck: null,
164670
+ github: null,
164671
+ dev: null,
164672
+ sourceFacts: null,
164673
+ targetPublished: null,
164674
+ artifact: null,
164675
+ warnings: warnFor(channel, nativeWired(deps)),
164676
+ checkedAt: now,
164677
+ identityLine: describeSourceIdentity({
164678
+ packageVersion: local?.version || "unknown",
164679
+ installedDevSha: local ? devShaFromIdentity(local.version) : null,
164680
+ switchableTo: null,
164681
+ channelKind
164682
+ }),
164683
+ localIdentitySource: local?.source || null,
164684
+ storeKind: store.kind,
164685
+ nativeWritable: nativeWired(deps),
164686
+ effectiveAfterReload: nativeWired(deps)
164687
+ };
164688
+ if (!local || !isKnownVersion(local.version)) {
164689
+ return { ...base6, status: "local_version_unknown", reason: `\u8BFB\u4E0D\u5230\u672C\u673A web \u5C42\u7684\u7248\u672C\u8EAB\u4EFD (\u58F3\u5C42\u6CE8\u5165 / OTA state / \u6784\u5EFA\u6233 bolloon-web.json \u90FD\u6CA1\u6709) \u2014 \u4E0D\u5224\u65AD\u6709\u6CA1\u6709\u66F4\u65B0` };
164690
+ }
164691
+ const npm = await queryMobileNpmFacts(deps, deps.pkgName || PKG_NAME);
164692
+ const githubRes = await queryMobileGithubFacts(deps);
164693
+ const github = githubRes ? toGithubReport(githubRes) : null;
164694
+ const npmLatest = npm.reachable ? npm.distTags[distTagForChannel(channel)] || npm.latest : null;
164695
+ const sourceFacts = {
164696
+ npm: npm.reachable ? { reachable: true, latest: npmLatest } : { reachable: false, latest: null, detail: npm.detail },
164697
+ github: github ? {
164698
+ reachable: github.reachable,
164699
+ reason: github.reason,
164700
+ detail: github.detail,
164701
+ retryAt: github.retryAt ?? null,
164702
+ ref: github.ref,
164703
+ headSha: github.headSha,
164704
+ newestVersion: github.newestVersion,
164705
+ releases: github.releases,
164706
+ tags: github.tags
164707
+ } : null,
164708
+ crossCheck: null
164709
+ };
164710
+ const switchableTo = base6.installedDevSha ? { channel: "stable", source: "npm", target: npmLatest } : { channel: "dev", source: "github", target: github?.headSha ? github.headSha.slice(0, 7) : null };
164711
+ const identityLine = describeSourceIdentity({
164712
+ packageVersion: local.version,
164713
+ installedDevSha: base6.installedDevSha,
164714
+ switchableTo,
164715
+ channelKind
164716
+ });
164717
+ const withSources = { ...base6, github, sourceFacts, switchableTo, identityLine };
164718
+ if (channelKind === "git-ref") {
164719
+ if (!github || !github.reachable) {
164720
+ const reason = github?.reason || "offline";
164721
+ const detail = github?.detail || (githubRes === null ? "\u672C\u6B21\u672A\u67E5\u8BE2 GitHub \u6E90" : "GitHub \u6E90\u4E0D\u53EF\u8FBE");
164722
+ return {
164723
+ ...withSources,
164724
+ status: "github_unavailable",
164725
+ latestVersion: null,
164726
+ reason: `github_unavailable(${reason}): ${detail} \u2014 dev \u901A\u9053\u53EA\u6709 GitHub \u4E00\u4E2A\u6E90, \u62D2\u7EDD\u5B89\u88C5 (\u4E0D\u56DE\u843D\u5230 stable \u88C5\u4E00\u4E2A npm \u7248\u672C)`
164727
+ };
164728
+ }
164729
+ if (!github.headSha) {
164730
+ return { ...withSources, status: "github_unavailable", latestVersion: null, reason: `github_unavailable(not_found): GitHub \u4E0A\u6CA1\u6709 ${DEV_REF} \u6216\u8BFB\u4E0D\u5230 HEAD \u2014 \u62D2\u7EDD\u5B89\u88C5\u4E00\u4E2A\u8EAB\u4EFD\u4E0D\u660E\u7684\u5FEB\u7167` };
164731
+ }
164732
+ const dev = compareDevSnapshots(base6.installedDevSha, github.headSha, DEV_REF);
164733
+ const targetIdentity = devIdentity(baseVersionOf(local.version), github.headSha);
164734
+ const devBundleUrl = deps.devBundleUrl !== void 0 ? deps.devBundleUrl : envValue("BOLLOON_MOBILE_DEV_BUNDLE_URL");
164735
+ const devBundleSha256 = deps.devBundleSha256 !== void 0 ? deps.devBundleSha256 : envValue("BOLLOON_MOBILE_DEV_BUNDLE_SHA256");
164736
+ const common = { ...withSources, dev, targetIdentity, latestVersion: targetIdentity, sourceFacts: { ...sourceFacts, github: sourceFacts.github } };
164737
+ if (dev.same) {
164738
+ return { ...common, status: "up_to_date", reason: dev.detail };
164739
+ }
164740
+ if (!devBundleUrl || !/\{sha\}/.test(devBundleUrl)) {
164741
+ return {
164742
+ ...common,
164743
+ status: "dev_bundle_unavailable",
164744
+ reason: `dev_bundle_unavailable: dev \u901A\u9053\u7684 web \u5305\u8981\u7531\u6709\u6784\u5EFA\u80FD\u529B\u7684\u4E00\u7AEF\u4EA7\u51FA \u2014\u2014 GitHub master \u7684\u6E90\u7801\u5FEB\u7167\u91CC\u6CA1\u6709 dist/web (dist \u662F\u6784\u5EFA\u4EA7\u7269, \u4E0D\u5728 git \u91CC), \u624B\u673A\u7AEF\u65E0\u6CD5\u73B0\u573A\u6784\u5EFA\u3002\u8BF7\u7528\u684C\u9762\u5BFC\u51FA: npx tsx scripts/build-mobile-web-bundle.ts --channel dev (\u4EA7\u51FA dist/web + .bolloon-dev-snapshot.json \u7684 tar.gz \u4E0E\u540C\u540D .sha256), \u5E76\u628A\u5730\u5740\u914D\u5230\u624B\u673A\u7AEF BOLLOON_MOBILE_DEV_BUNDLE_URL (\u6A21\u677F\u9700\u542B {sha}, {sha} \u4F1A\u88AB\u66FF\u6362\u6210 master HEAD \u7684\u5B8C\u6574 40 \u4F4D sha)\u3002${devBundleUrl ? ` \u5F53\u524D\u914D\u7F6E\u7684\u6A21\u677F\u4E0D\u542B {sha}: ${devBundleUrl}` : " \u5F53\u524D\u6CA1\u6709\u914D\u7F6E dev web \u5305\u5730\u5740\u3002"} stable \u901A\u9053\u4E0D\u53D7\u5F71\u54CD (npm tarball \u81EA\u5E26 dist/web)\u3002`
164745
+ };
164746
+ }
164747
+ const pinned = devBundleSha256 ? String(devBundleSha256).trim().toLowerCase() : null;
164748
+ const url = String(devBundleUrl).replace("{sha}", github.headSha);
164749
+ return {
164750
+ ...common,
164751
+ status: "update_available",
164752
+ targetPublished: true,
164753
+ artifact: {
164754
+ channel: "dev",
164755
+ identity: targetIdentity,
164756
+ source: `github master @ ${github.headSha.slice(0, 7)}`,
164757
+ url,
164758
+ // pin 了才校验载荷摘要; 没 pin 就如实标"没有载荷锚点"(不给虚假的安全感)
164759
+ digest: pinned ? { algo: "sha256", hex: pinned } : null,
164760
+ expectedDevSha: github.headSha,
164761
+ expectedBundleSha256: pinned
164762
+ },
164763
+ reason: `${dev.detail} \xB7 web \u5305\u6765\u81EA ${url} (\u5305\u5185 sha \u5FC5\u987B\u7B49\u4E8E master HEAD, \u5426\u5219\u62D2\u7EDD)` + (pinned ? ` \xB7 \u8F7D\u8377\u6458\u8981\u5DF2 pin (sha256:${pinned.slice(0, 12)}\u2026)` : " \xB7 \u8F7D\u8377\u6458\u8981\u672A pin (dev \u901A\u9053\u56FA\u6709: \u53EA\u6709\u8EAB\u4EFD\u88AB GitHub \u951A\u5B9A)"),
164764
+ warnings: common.warnings.concat(pinned ? [] : [
164765
+ 'dev \u8F7D\u8377\u6458\u8981\u672A pin \u21D2 \u53EA\u4FDD\u8BC1"\u8EAB\u4EFD\u662F master HEAD \u7684\u90A3\u4E2A commit", \u4E0D\u4FDD\u8BC1"\u5B57\u8282\u5C31\u662F\u684C\u9762\u6253\u7684\u90A3\u4E00\u4EFD" (\u8981\u5F3A\u6821\u9A8C\u5C31\u628A .sha256 \u6E05\u5355\u7684\u503C\u914D\u5230 BOLLOON_MOBILE_DEV_BUNDLE_SHA256)'
164766
+ ])
164767
+ };
164768
+ }
164769
+ if (!npm.reachable) {
164770
+ const detail = npm.detail || "registry \u4E0D\u53EF\u8FBE";
164771
+ const offline = npm.kind === "offline";
164772
+ return {
164773
+ ...withSources,
164774
+ status: offline ? "offline" : "registry_unavailable",
164775
+ latestVersion: null,
164776
+ reason: `${detail}${github ? ` \xB7 ${renderGithubReportLine(github)}` : ""} \u2014 \u4E0D\u80FD\u5224\u5B9A\u4E3A\u6700\u65B0`
164777
+ };
164778
+ }
164779
+ const latest = npmLatest;
164780
+ const facts = githubRes && githubRes.ok ? githubRes.facts : null;
164781
+ const crossCheck = facts ? crossCheckStable(latest, facts) : null;
164782
+ const withCross = {
164783
+ ...withSources,
164784
+ crossCheck,
164785
+ latestVersion: latest,
164786
+ sourceFacts: { ...sourceFacts, crossCheck: crossCheck ? { kind: crossCheck.kind, blocking: crossCheck.blocking, detail: crossCheck.detail } : null }
164787
+ };
164788
+ if (crossCheck?.kind === "mismatch") {
164789
+ return {
164790
+ ...withCross,
164791
+ status: "cross_check_mismatch",
164792
+ reason: `cross_check_mismatch: ${crossCheck.detail} (npm latest=${latest} / GitHub ${crossCheck.githubVersion || "\u65E0\u8BB0\u5F55"})`
164793
+ };
164794
+ }
164795
+ if (isDevIdentity(local.version)) {
164796
+ const target = latest;
164797
+ if (!target || !npm.versions.includes(baseVersionOf(target))) {
164798
+ return { ...withCross, status: "target_unpublished", reason: `target_unpublished: registry \u4E0A\u6CA1\u6709 ${target || "\u76EE\u6807\u7248\u672C"} \u8FD9\u4E2A\u7248\u672C \u2014 \u62D2\u7EDD\u5207\u56DE (\u5F53\u524D dev \u5FEB\u7167 ${local.version} \u539F\u6837\u4FDD\u7559)` };
164799
+ }
164800
+ if (!npm.dist?.tarball) {
164801
+ return { ...withCross, status: "target_unpublished", reason: `target_unpublished: registry \u6CA1\u7ED9\u51FA ${target} \u7684\u4E0B\u8F7D\u5730\u5740 (dist.tarball \u7F3A\u5931) \u2014 \u62D2\u7EDD\u5207\u56DE` };
164802
+ }
164803
+ return {
164804
+ ...withCross,
164805
+ status: "update_available",
164806
+ targetPublished: true,
164807
+ artifact: {
164808
+ channel: "stable",
164809
+ identity: String(target),
164810
+ source: "npm registry (dist-tags.latest)",
164811
+ url: npm.dist.tarball,
164812
+ digest: npm.dist.shasum ? { algo: "sha1", hex: npm.dist.shasum } : npm.dist.integrity ? { algo: "sha512", b64: parseSri(npm.dist.integrity)?.b64 ?? null } : null
164813
+ },
164814
+ reason: `\u5F53\u524D\u88C5\u7684\u662F dev \u5FEB\u7167 ${local.version} \u2192 \u5207\u56DE stable \u7684 ${target} (${DEV_BACK_TO_STABLE_HINT_MOBILE})`
164815
+ };
164816
+ }
164817
+ if (latest && !npm.versions.includes(latest)) {
164818
+ return { ...withCross, status: "target_unpublished", targetPublished: false, reason: `target_unpublished: registry \u7684 dist-tags \u6307\u5230 ${latest}, \u4F46 versions \u91CC\u6CA1\u6709\u8FD9\u4E2A\u7248\u672C \u2014 \u62D2\u7EDD\u66F4\u65B0 (0 \u5B57\u8282\u4E0B\u8F7D)` };
164819
+ }
164820
+ if (latest && compareVersions(baseVersionOf(local.version), latest) < 0) {
164821
+ if (!npm.dist?.tarball) {
164822
+ return { ...withCross, status: "target_unpublished", targetPublished: true, reason: `target_unpublished: registry \u6CA1\u7ED9\u51FA ${latest} \u7684\u4E0B\u8F7D\u5730\u5740 (dist.tarball \u7F3A\u5931) \u2014 \u62D2\u7EDD\u66F4\u65B0` };
164823
+ }
164824
+ return {
164825
+ ...withCross,
164826
+ status: "update_available",
164827
+ targetPublished: true,
164828
+ artifact: {
164829
+ channel: "stable",
164830
+ identity: latest,
164831
+ source: "npm registry (dist-tags.latest)",
164832
+ url: npm.dist.tarball,
164833
+ digest: npm.dist.shasum ? { algo: "sha1", hex: npm.dist.shasum } : npm.dist.integrity ? { algo: "sha512", b64: parseSri(npm.dist.integrity)?.b64 ?? null } : null
164834
+ },
164835
+ reason: `\u5F53\u524D ${local.version} \u2192 \u76EE\u6807 ${latest}${crossCheck ? ` \xB7 \u4EA4\u53C9\u6821\u9A8C: ${crossCheck.kind}` : ""}`
164836
+ };
164837
+ }
164838
+ return { ...withCross, status: "up_to_date", targetPublished: latest ? npm.versions.includes(latest) : null };
164839
+ }
164840
+ async function downloadArtifact(artifact, deps) {
164841
+ const fetchImpl = deps.fetchImpl || globalThis.fetch?.bind(globalThis);
164842
+ const maxBytes = deps.maxPayloadBytes ?? MOBILE_MAX_PAYLOAD_BYTES;
164843
+ if (!fetchImpl) return { ok: false, status: "offline", detail: "\u672C\u673A\u6CA1\u6709 fetch, \u65E0\u6CD5\u4E0B\u8F7D", digest: { algo: "", matched: null } };
164844
+ let bytes;
164845
+ try {
164846
+ const res = await fetchImpl(artifact.url, { headers: { Accept: "application/octet-stream, application/gzip, */*" } });
164847
+ const status = Number(res?.status || 0);
164848
+ if (!(status >= 200 && status < 300)) {
164849
+ const cls = classifyGithubError(null, status || void 0);
164850
+ const bucket = cls.reason === "not_found" ? "target_unpublished" : cls.reason === "offline" ? "offline" : "registry_unavailable";
164851
+ return { ok: false, status: bucket, detail: `\u4E0B\u8F7D ${artifact.url} \u5931\u8D25: HTTP ${status || "\u65E0\u54CD\u5E94"} (${cls.reason})`, digest: { algo: "", matched: null } };
164852
+ }
164853
+ const lenHint = Number(res?.headers?.get?.("content-length") || 0);
164854
+ if (lenHint && lenHint > maxBytes) {
164855
+ return { ok: false, status: "payload_too_large", detail: `\u4E0B\u8F7D\u7269 ${(lenHint / 1048576).toFixed(1)} MiB \u8D85\u8FC7\u4E0A\u9650 ${(maxBytes / 1048576).toFixed(0)} MiB \u2014 \u62D2\u7EDD`, digest: { algo: "", matched: null } };
164856
+ }
164857
+ const buf = await res.arrayBuffer();
164858
+ bytes = new Uint8Array(buf);
164859
+ if (bytes.length > maxBytes) {
164860
+ return { ok: false, status: "payload_too_large", detail: `\u4E0B\u8F7D\u7269 ${(bytes.length / 1048576).toFixed(1)} MiB \u8D85\u8FC7\u4E0A\u9650 ${(maxBytes / 1048576).toFixed(0)} MiB \u2014 \u62D2\u7EDD`, digest: { algo: "", matched: null } };
164861
+ }
164862
+ } catch (e) {
164863
+ const cls = classifyGithubError(e);
164864
+ return { ok: false, status: cls.reason === "offline" ? "offline" : "registry_unavailable", detail: `\u4E0B\u8F7D ${artifact.url} \u5931\u8D25: ${cls.detail}`, digest: { algo: "", matched: null } };
164865
+ }
164866
+ if (!artifact.digest) {
164867
+ return { ok: true, detail: "\u8BE5\u6E90\u6CA1\u6709\u63D0\u4F9B\u6458\u8981 (dev web \u5305\u4EE5\u5305\u5185 sha \u4F5C\u4E3A\u8EAB\u4EFD\u6821\u9A8C, \u89C1 dev_sha_mismatch)", bytes, digest: { algo: "", matched: null } };
164868
+ }
164869
+ const algo = artifact.digest.algo;
164870
+ const want = artifact.digest.hex || artifact.digest.b64;
164871
+ if (!want) return { ok: true, detail: "\u58F0\u660E\u7684\u6458\u8981\u4E3A\u7A7A (\u8DF3\u8FC7)", bytes, digest: { algo, matched: null } };
164872
+ let got = null;
164873
+ if (artifact.digest.hex) got = await digestHex(bytes, algo);
164874
+ else {
164875
+ const bytesDigest = await digestHex(bytes, algo);
164876
+ if (bytesDigest) got = null;
164877
+ const h3 = bytesDigest;
164878
+ if (h3) {
164879
+ const pairs = h3.match(/.{2}/g) || [];
164880
+ const arr = new Uint8Array(pairs.map((x) => parseInt(x, 16)));
164881
+ got = b64Of(arr) === artifact.digest.b64 ? artifact.digest.b64 : b64Of(arr);
164882
+ }
164883
+ }
164884
+ const matched = got !== null && got.toLowerCase() === want.toLowerCase();
164885
+ if (!matched) {
164886
+ return {
164887
+ ok: false,
164888
+ status: "digest_mismatch",
164889
+ detail: `digest_mismatch: \u6458\u8981\u5BF9\u4E0D\u4E0A (${algo}) \u2014 \u58F0\u660E ${String(want).slice(0, 24)}\u2026, \u5B9E\u9645 ${String(got).slice(0, 24)}\u2026, \u62D2\u7EDD\u5B89\u88C5`,
164890
+ bytes,
164891
+ digest: { algo, hex: artifact.digest.hex ? got : null, b64: artifact.digest.b64 ? got : null, matched: false }
164892
+ };
164893
+ }
164894
+ return { ok: true, detail: `\u6458\u8981\u5DF2\u6838\u5BF9 (${algo})`, bytes, digest: { algo, hex: artifact.digest.hex ? got : null, b64: artifact.digest.b64 ? got : null, matched: true } };
164895
+ }
164896
+ function scriptRefsOf(html) {
164897
+ const out = [];
164898
+ const re = /<script[^>]*\ssrc=["']([^"']+)["']/gi;
164899
+ let m2;
164900
+ while (m2 = re.exec(html)) out.push(m2[1]);
164901
+ return out;
164902
+ }
164903
+ async function verifyWebLayer(store, prefix, opts = {}) {
164904
+ const checks = [];
164905
+ const present = {};
164906
+ for (const f of MOBILE_WEB_REQUIRED_FILES) {
164907
+ const text = await store.readText(joinRel(prefix, f));
164908
+ const bytes = text === null ? await store.readBytes(joinRel(prefix, f)) : null;
164909
+ const size2 = text !== null ? text.length : bytes?.length ?? 0;
164910
+ present[f] = String(text ?? "");
164911
+ checks.push({ name: `\u5FC5\u9700\u6587\u4EF6 ${f}`, ok: size2 > 0, detail: size2 > 0 ? `${size2} \u5B57\u8282` : "\u7F3A\u5931\u6216\u4E3A\u7A7A" });
164912
+ }
164913
+ const html = present["mobile.html"] || "";
164914
+ const refs = scriptRefsOf(html);
164915
+ const refMiss = [];
164916
+ for (const r of refs) {
164917
+ if (/^https?:|^\/\//i.test(r)) continue;
164918
+ const rel = r.replace(/^\.\//, "").split("?")[0];
164919
+ if (!await store.exists(joinRel(prefix, rel))) refMiss.push(r);
164920
+ }
164921
+ checks.push({ name: "mobile.html \u5F15\u7528\u7684\u811A\u672C\u90FD\u5728", ok: refMiss.length === 0, detail: refMiss.length ? `\u7F3A\u5931: ${refMiss.join(", ")}` : `${refs.length} \u4E2A\u5F15\u7528\u90FD\u547D\u4E2D` });
164922
+ const syntaxBad = [];
164923
+ for (const f of MOBILE_WEB_JS_FILES) {
164924
+ const src4 = await store.readText(joinRel(prefix, f));
164925
+ if (src4 === null || src4 === "") continue;
164926
+ try {
164927
+ new Function(src4);
164928
+ } catch (e) {
164929
+ syntaxBad.push(`${f}: ${String(e?.message || e).slice(0, 120)}`);
164930
+ }
164931
+ }
164932
+ checks.push({ name: "JS \u8BED\u6CD5\u81EA\u68C0 (\u771F\u7F16\u8BD1\u4E00\u6B21)", ok: syntaxBad.length === 0, detail: syntaxBad.length ? syntaxBad.join(" | ") : `${MOBILE_WEB_JS_FILES.length} \u4E2A\u6587\u4EF6\u53EF\u7F16\u8BD1` });
164933
+ const structural = checks.every((c) => c.ok);
164934
+ if (!structural) {
164935
+ return { ok: false, mode: "static-only", detail: `\u7ED3\u6784\u4E0E\u8BED\u6CD5\u68C0\u67E5\u672A\u901A\u8FC7: ${checks.filter((c) => !c.ok).map((c) => `${c.name}(${c.detail})`).join("; ")}`, checks };
164936
+ }
164937
+ const base6 = store.baseUrl();
164938
+ if (!base6) {
164939
+ return { ok: true, mode: "static-only", detail: '\u7ED3\u6784 + \u8BED\u6CD5\u901A\u8FC7; \u672C store \u6CA1\u6709\u53EF\u52A0\u8F7D URL \u21D2 \u672A\u505A\u771F\u542F\u52A8\u63A2\u6D4B (\u5982\u5B9E\u6807\u6CE8, \u4E0D\u5F53\u6210"\u5DF2\u9A8C\u8BC1\u80FD\u542F\u52A8")', checks };
164940
+ }
164941
+ if (!hasDom()) {
164942
+ return {
164943
+ ok: true,
164944
+ mode: "static-only",
164945
+ detail: "\u7ED3\u6784 + \u8BED\u6CD5\u901A\u8FC7; \u6709\u53EF\u52A0\u8F7D URL \u4F46\u672C\u73AF\u5883\u6CA1\u6709 DOM (\u975E\u6D4F\u89C8\u5668) \u21D2 \u672A\u505A\u771F\u542F\u52A8\u63A2\u6D4B (\u5982\u5B9E\u6807\u6CE8; \u771F\u63A2\u6D4B\u89C1 scripts/verify-mobile-update-ui.ts)",
164946
+ checks
164947
+ };
164948
+ }
164949
+ const url = `${String(base6).replace(/\/+$/, "")}/${joinRel(prefix, "mobile.html")}`;
164950
+ const probe = await iframeBootProbe(url, opts.bootTimeoutMs ?? 12e3);
164951
+ checks.push({ name: "\u771F\u542F\u52A8\u63A2\u6D4B (iframe \u52A0\u8F7D staged \u8D44\u6E90)", ok: probe.ok, detail: probe.detail });
164952
+ return { ok: probe.ok, mode: "iframe", detail: probe.ok ? `\u771F\u542F\u52A8\u63A2\u6D4B\u901A\u8FC7: ${probe.detail}` : `\u771F\u542F\u52A8\u63A2\u6D4B\u5931\u8D25: ${probe.detail}`, checks };
164953
+ }
164954
+ function hasDom() {
164955
+ const d2 = globalThis.document;
164956
+ return !!(d2 && typeof d2.createElement === "function");
164957
+ }
164958
+ async function iframeBootProbe(url, timeoutMs) {
164959
+ const doc = globalThis.document;
164960
+ if (!doc || typeof doc.createElement !== "function") return { ok: false, detail: "\u6CA1\u6709 DOM, \u65E0\u6CD5\u505A\u542F\u52A8\u63A2\u6D4B" };
164961
+ return await new Promise((resolve) => {
164962
+ let done = false;
164963
+ const frame = doc.createElement("iframe");
164964
+ frame.style.cssText = "position:absolute;left:-9999px;width:430px;height:932px;border:0;";
164965
+ frame.setAttribute("aria-hidden", "true");
164966
+ frame.src = url;
164967
+ const finish = (ok, detail) => {
164968
+ if (done) return;
164969
+ done = true;
164970
+ try {
164971
+ frame.remove();
164972
+ } catch {
164973
+ }
164974
+ resolve({ ok, detail });
164975
+ };
164976
+ const timer = setTimeout(() => {
164977
+ const w2 = (() => {
164978
+ try {
164979
+ return frame.contentWindow;
164980
+ } catch {
164981
+ return null;
164982
+ }
164983
+ })();
164984
+ finish(false, `\u7B49 ${timeoutMs}ms \u672A\u89C1 staged \u8D44\u6E90\u542F\u52A8 (readyState=${w2?.document?.readyState || "\u672A\u77E5"}, BolloonCore=${typeof w2?.BolloonCore})`);
164985
+ }, timeoutMs);
164986
+ frame.onload = () => {
164987
+ let tries = 0;
164988
+ const tick = () => {
164989
+ tries++;
164990
+ let w2 = null;
164991
+ try {
164992
+ w2 = frame.contentWindow;
164993
+ } catch {
164994
+ }
164995
+ const d2 = w2?.document;
164996
+ if (d2 && d2.readyState === "complete" && d2.querySelector && d2.querySelector(".tabbar") && w2.BolloonCore && typeof w2.BolloonCore === "object") {
164997
+ clearTimeout(timer);
164998
+ finish(true, `staged \u8D44\u6E90\u771F\u8D77\u6765\u4E86 (tabbar + BolloonCore, ${tries} \u6B21\u63A2\u6D4B)`);
164999
+ return;
165000
+ }
165001
+ if (tries > 60) {
165002
+ clearTimeout(timer);
165003
+ finish(false, `staged \u8D44\u6E90\u52A0\u8F7D\u5B8C\u4F46\u6CA1\u8D77\u6765 (readyState=${d2?.readyState || "\u672A\u77E5"}, tabbar=${!!d2?.querySelector?.(".tabbar")}, BolloonCore=${typeof w2?.BolloonCore})`);
165004
+ return;
165005
+ }
165006
+ setTimeout(tick, 150);
165007
+ };
165008
+ tick();
165009
+ };
165010
+ frame.onerror = () => {
165011
+ clearTimeout(timer);
165012
+ finish(false, "iframe \u52A0\u8F7D\u5931\u8D25");
165013
+ };
165014
+ try {
165015
+ doc.body.appendChild(frame);
165016
+ } catch (e) {
165017
+ clearTimeout(timer);
165018
+ finish(false, `\u6302\u8F7D iframe \u5931\u8D25: ${e?.message || e}`);
165019
+ }
165020
+ });
165021
+ }
165022
+ async function prepareMobileUpdate(deps, check) {
165023
+ const store = deps.store;
165024
+ const now = new Date(deps.now ? deps.now() : Date.now()).toISOString();
165025
+ const stage = (s2, detail) => {
165026
+ try {
165027
+ deps.onStage?.(s2, detail);
165028
+ } catch {
165029
+ }
165030
+ };
165031
+ const steps = [];
165032
+ const mark = (s2) => {
165033
+ steps.push(s2);
165034
+ };
165035
+ const r = check || await checkMobileUpdate(deps);
165036
+ const from6 = r.currentVersion;
165037
+ const to = r.artifact?.identity || r.targetIdentity || from6;
165038
+ const fail = async (stageName, status, reason, extra = {}) => {
165039
+ mark(`${stageName}: ${reason}`);
165040
+ await store.remove(WEB_LAYOUT.staging);
165041
+ await writeMobileUpdateState({
165042
+ lastCheck: { at: now, status: r.status, reason: r.reason, channel: r.channel, latestVersion: r.latestVersion },
165043
+ lastFailure: { at: now, stage: stageName, reason },
165044
+ autoPrepared: null
165045
+ }, store);
165046
+ return { ok: false, stage: "failed", failedAt: stageName, status, from: from6, to, reason, steps, fileCount: 0, digest: null, probe: null, skippedDownload: false, ...extra };
165047
+ };
165048
+ if (refused(r.status)) {
165049
+ return fail("blocked", r.status, `\u62D2\u7EDD\u66F4\u65B0 (${r.status}): ${r.reason || "\u6E90\u4E0D\u53EF\u8FBE / \u7248\u672C\u4E0D\u5B58\u5728"}`, { stage: "blocked" });
165050
+ }
165051
+ if (r.status === "up_to_date") {
165052
+ mark(`up_to_date: ${r.reason || "\u5DF2\u662F\u6700\u65B0"}`);
165053
+ return { ok: false, stage: "blocked", status: "up_to_date", from: from6, to, reason: `\u5DF2\u662F\u6700\u65B0 (${from6}), \u65E0\u9700\u66F4\u65B0`, steps, fileCount: 0, digest: null, probe: null, skippedDownload: true };
165054
+ }
165055
+ if (!r.artifact) return fail("planned", "target_unpublished", "\u6CA1\u6709\u53EF\u6267\u884C\u7684\u4E0B\u8F7D\u8BA1\u5212 (artifact \u7F3A\u5931)");
165056
+ const prevState = await readMobileUpdateState(store);
165057
+ if (prevState.autoPrepared?.identity === to && await store.exists(joinRel(WEB_LAYOUT.staging, "mobile.html"))) {
165058
+ mark(`reuse-staging: \u590D\u7528\u5DF2\u51C6\u5907\u5E76\u9A8C\u8BC1\u8FC7\u7684 ${to}`);
165059
+ const probe2 = await verifyWebLayer(store, WEB_LAYOUT.staging, { bootTimeoutMs: deps.bootTimeoutMs });
165060
+ if (probe2.ok) {
165061
+ return { ok: true, stage: "awaiting_human", status: "update_available", from: from6, to, reason: `staging \u91CC\u5DF2\u6709\u9A8C\u8BC1\u8FC7\u7684 ${to} (digest ${prevState.autoPrepared.digest || "\u65E0"}) \u2014 \u7B49\u4EBA\u5DE5\u786E\u8BA4\u5207\u6362`, steps, fileCount: 0, digest: prevState.autoPrepared.digest, probe: probe2, skippedDownload: true };
165062
+ }
165063
+ mark("reuse-staging: \u590D\u7528\u7684 staging \u6CA1\u901A\u8FC7\u9A8C\u8BC1 \u2192 \u91CD\u65B0\u4E0B\u8F7D");
165064
+ }
165065
+ stage("downloading", `${r.artifact.channel} \xB7 ${r.artifact.identity} \u2190 ${r.artifact.url}`);
165066
+ mark(`downloading: ${r.artifact.url}`);
165067
+ const dl = await downloadArtifact(r.artifact, deps);
165068
+ if (!dl.ok || !dl.bytes) {
165069
+ return fail("downloading", dl.status || "registry_unavailable", dl.detail);
165070
+ }
165071
+ stage("downloaded", `\u5DF2\u4E0B\u8F7D ${(dl.bytes.length / 1048576).toFixed(2)} MiB \xB7 ${dl.detail}`);
165072
+ mark(`downloaded: ${(dl.bytes.length / 1048576).toFixed(2)} MiB \xB7 ${dl.detail}`);
165073
+ stage("extracting", "\u89E3\u538B tar.gz \u5E76\u6311\u51FA web \u8D44\u6E90 (dist/web)");
165074
+ const tarBytes = await gunzipBytes(dl.bytes);
165075
+ if (!tarBytes) {
165076
+ return fail(
165077
+ "extracting",
165078
+ "decompress_unavailable",
165079
+ "decompress_unavailable: \u672C\u673A WebView \u6CA1\u6709 DecompressionStream('gzip') \u21D2 \u65E0\u6CD5\u89E3\u5F00 tar.gz \u2014 \u62D2\u7EDD (\u4E0D\u88C5\u534A\u4E2A)\u3002 iOS 16.4+ / Chrome 80+ \u624D\u6709; \u65E7 WebView \u8BF7\u5347\u7EA7\u7CFB\u7EDF\u6216\u8D70\u5546\u5E97\u66F4\u65B0\u6574\u4E2A App\u3002"
165080
+ );
165081
+ }
165082
+ const entries = untarBytes(tarBytes);
165083
+ const { files, snapshot: snapshot2 } = pickWebLayer(entries);
165084
+ if (files.length === 0) {
165085
+ return fail("extracting", "web_bundle_malformed", `web_bundle_malformed: \u5305\u91CC\u6CA1\u6709 dist/web/** \u8D44\u6E90 (tar \u6761\u76EE ${entries.length} \u4E2A) \u2014 \u62D2\u7EDD`);
165086
+ }
165087
+ mark(`extracting: ${files.length} \u4E2A web \u6587\u4EF6, \u5305\u5185 snapshot \u6807\u8BB0 = ${snapshot2 ? `\u6709 (sha ${String(snapshot2.sha || "").slice(0, 7)})` : "\u65E0"}`);
165088
+ if (r.artifact.channel === "dev") {
165089
+ const want = (r.artifact.expectedDevSha || "").slice(0, 7);
165090
+ const got = snapshot2?.sha ? String(snapshot2.sha).slice(0, 7) : null;
165091
+ if (!snapshot2) {
165092
+ return fail("extracting", "dev_sha_mismatch", `dev_sha_mismatch: dev web \u5305\u91CC\u6CA1\u6709 .bolloon-dev-snapshot.json \u21D2 \u65E0\u6CD5\u8BC1\u660E\u5B83\u662F ${want} \u7684\u5FEB\u7167 \u2014 \u62D2\u7EDD`);
165093
+ }
165094
+ if (snapshot2.channel !== "dev") {
165095
+ return fail("extracting", "dev_sha_mismatch", `dev_sha_mismatch: \u5305\u5185 snapshot \u6807\u7684\u662F channel=${snapshot2.channel} (\u4E0D\u662F dev) \u2014 \u62D2\u7EDD`);
165096
+ }
165097
+ if (!got || got !== want) {
165098
+ return fail("extracting", "dev_sha_mismatch", `dev_sha_mismatch: \u5305\u5185 sha=${got || "\u7A7A"} \u2260 GitHub master HEAD ${want} \u2014 \u62D2\u7EDD\u5B89\u88C5\u8EAB\u4EFD\u5BF9\u4E0D\u4E0A\u7684\u5FEB\u7167`);
165099
+ }
165100
+ if (snapshot2.identity && to && String(snapshot2.identity) !== to) {
165101
+ return fail("extracting", "dev_sha_mismatch", `dev_sha_mismatch: \u5305\u5185 identity=${snapshot2.identity} \u4E0E\u76EE\u6807\u8EAB\u4EFD ${to} \u4E0D\u4E00\u81F4 \u2014 \u62D2\u7EDD`);
165102
+ }
165103
+ }
165104
+ await store.remove(WEB_LAYOUT.staging);
165105
+ for (const f of files) await store.writeBytes(joinRel(WEB_LAYOUT.staging, f.rel), f.data);
165106
+ stage("staged", `${files.length} \u4E2A\u6587\u4EF6\u5DF2\u5199\u5165 ${store.rootLabel}/${WEB_LAYOUT.staging}`);
165107
+ mark(`staged: ${files.length} \u4E2A\u6587\u4EF6 \u2192 ${WEB_LAYOUT.staging}`);
165108
+ stage("verifying", "\u9A8C\u8BC1 staging \u80FD\u4E0D\u80FD\u542F\u52A8 (\u7ED3\u6784 + \u8BED\u6CD5 + iframe \u771F\u52A0\u8F7D)");
165109
+ const probe = await verifyWebLayer(store, WEB_LAYOUT.staging, { bootTimeoutMs: deps.bootTimeoutMs });
165110
+ mark(`verifying: ${probe.mode} \u2014 ${probe.detail}`);
165111
+ if (!probe.ok) {
165112
+ return fail("verifying", "web_bundle_malformed", `\u5207\u6362\u524D\u9A8C\u8BC1\u5931\u8D25 (staged \u8D44\u6E90\u8D77\u4E0D\u6765): ${probe.detail}`, { probe });
165113
+ }
165114
+ const digest3 = dl.digest.matched === true ? `${dl.digest.algo}:${dl.digest.hex || dl.digest.b64}` : null;
165115
+ await writeMobileUpdateState({
165116
+ lastCheck: { at: now, status: r.status, reason: r.reason, channel: r.channel, latestVersion: r.latestVersion },
165117
+ autoPrepared: { identity: to, digest: digest3, verifiedAt: now, steps: steps.slice() }
165118
+ }, store);
165119
+ return { ok: true, stage: "awaiting_human", status: "update_available", from: from6, to, reason: `\u5DF2\u51C6\u5907\u5E76\u9A8C\u8BC1 ${to} (\u7B49\u4EBA\u5DE5\u786E\u8BA4\u5207\u6362)`, steps, fileCount: files.length, digest: digest3, probe, skippedDownload: false };
165120
+ }
165121
+ async function applyMobileUpdate(opts) {
165122
+ const started = Date.now();
165123
+ const store = opts.store;
165124
+ const now = new Date(opts.now ? opts.now() : Date.now()).toISOString();
165125
+ const stage = (s2, detail) => {
165126
+ try {
165127
+ opts.onStage?.(s2, detail);
165128
+ } catch {
165129
+ }
165130
+ };
165131
+ const r = await checkMobileUpdate(opts);
165132
+ const from6 = r.currentVersion;
165133
+ const to = r.artifact?.identity || r.targetIdentity || from6;
165134
+ const steps = [];
165135
+ const finish = async (o) => {
165136
+ const out = { ...o, durationMs: Date.now() - started, steps };
165137
+ const hist = [{ at: now, from: out.from, to: out.to, stage: out.stage, reason: out.reason }];
165138
+ const st = await readMobileUpdateState(store);
165139
+ await writeMobileUpdateState({
165140
+ lastUpdate: hist[0],
165141
+ history: [...hist, ...st.history || []].slice(0, 20),
165142
+ lastFailure: out.ok ? null : { at: now, stage: out.failedAt || out.stage, reason: out.reason },
165143
+ needsReload: out.ok ? true : st.needsReload,
165144
+ ...out.ok ? {
165145
+ currentVersion: out.to,
165146
+ installedChannel: r.artifact?.channel || "stable",
165147
+ installedDevSha: devShaFromIdentity(out.to),
165148
+ installedFrom: {
165149
+ channel: r.artifact?.channel || "stable",
165150
+ source: r.artifact?.source || "unknown",
165151
+ url: r.artifact?.url || null,
165152
+ digest: out.digest,
165153
+ digestAlgo: out.digest ? out.digest.split(":")[0] : null,
165154
+ at: now
165155
+ },
165156
+ switchableTo: r.artifact?.channel === "dev" ? { channel: "stable", source: "npm", target: r.sourceFacts?.npm?.latest ?? null } : { channel: "dev", source: "github", target: r.github?.headSha ? r.github.headSha.slice(0, 7) : null },
165157
+ // 从 dev 切回 stable 时, **保留**刚离开的那个 dev 快照 sha: 用户回头想回 dev 时知道上次是哪个
165158
+ ...r.artifact?.channel === "dev" ? { lastDevSha: devShaFromIdentity(out.to), devRef: DEV_REF, devCheckedAt: now } : { lastDevSha: r.installedDevSha || null },
165159
+ autoPrepared: null
165160
+ } : {}
165161
+ }, store);
165162
+ return out;
165163
+ };
165164
+ if (refused(r.status)) {
165165
+ stage("blocked", `\u62D2\u7EDD\u66F4\u65B0 (${r.status})`);
165166
+ return finish({ ok: false, stage: "blocked", status: r.status, from: from6, to, reason: `\u62D2\u7EDD\u66F4\u65B0 (${r.status}): ${r.reason || "\u6E90\u4E0D\u53EF\u8FBE / \u7248\u672C\u4E0D\u5B58\u5728"} \u2014 \u4E00\u4E2A\u5B57\u8282\u90FD\u6CA1\u4E0B\u8F7D, \u4E5F\u6CA1\u52A8 current`, digest: null, probe: null, needsReload: false });
165167
+ }
165168
+ if (!r.artifact) {
165169
+ stage("blocked", "\u6CA1\u6709\u53EF\u6267\u884C\u7684\u66F4\u65B0");
165170
+ return finish({ ok: false, stage: "blocked", status: r.status, from: from6, to, reason: r.status === "up_to_date" ? `\u5DF2\u662F\u6700\u65B0 (${from6})` : `\u6CA1\u6709\u53EF\u6267\u884C\u7684\u66F4\u65B0 (${r.status})`, digest: null, probe: null, needsReload: false });
165171
+ }
165172
+ if (!opts.confirm) {
165173
+ stage("blocked", "\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4");
165174
+ return finish({
165175
+ ok: false,
165176
+ stage: "blocked",
165177
+ status: "update_available",
165178
+ from: from6,
165179
+ to,
165180
+ reason: "human_confirm_required: \u5207\u6362 web \u8D44\u6E90\u5C42\u4F1A\u6362\u6389\u6B63\u5728\u8DD1\u7684\u754C\u9762\u4E0E\u903B\u8F91 \u2014 \u5FC5\u987B\u6709\u4EBA\u5728\u73AF (\u667A\u80FD\u4F53\u53EA\u80FD\u81EA\u52A8\u8DD1\u5230 prepare/verify)",
165181
+ digest: null,
165182
+ probe: null,
165183
+ needsReload: false
165184
+ });
165185
+ }
165186
+ let prep = null;
165187
+ if (opts.reuseStaging) {
165188
+ stage("verifying", "\u590D\u7528 staging \u91CC\u5DF2\u51C6\u5907\u5E76\u9A8C\u8BC1\u8FC7\u7684\u8D44\u6E90");
165189
+ const probe = await verifyWebLayer(store, WEB_LAYOUT.staging, { bootTimeoutMs: opts.bootTimeoutMs });
165190
+ if (probe.ok) {
165191
+ const st = await readMobileUpdateState(store);
165192
+ const identity5 = st.autoPrepared?.identity || to;
165193
+ if (identity5 !== to) {
165194
+ stage("blocked", `staging \u7684\u8EAB\u4EFD ${identity5} \u2260 \u76EE\u6807 ${to}`);
165195
+ return finish({ ok: false, stage: "blocked", status: "web_bundle_malformed", from: from6, to, reason: `staging \u91CC\u51C6\u5907\u7684\u662F ${identity5}, \u4E0E\u672C\u6B21\u76EE\u6807 ${to} \u4E0D\u4E00\u81F4 \u2014 \u62D2\u7EDD\u5207\u6362 (\u5B81\u53EF\u4E0D\u88C5, \u4E0D\u88C5\u9519\u7684)`, digest: null, probe, needsReload: false });
165196
+ }
165197
+ prep = { ok: true, stage: "awaiting_human", status: "update_available", from: from6, to, reason: "\u590D\u7528 staging", steps: st.autoPrepared?.steps || [], fileCount: 0, digest: st.autoPrepared?.digest ?? null, probe, skippedDownload: true };
165198
+ steps.push(...(prep.steps || []).map((s2) => `auto: ${s2}`));
165199
+ }
165200
+ }
165201
+ if (!prep) {
165202
+ const p = await prepareMobileUpdate(opts, r);
165203
+ if (!p.ok) {
165204
+ return finish({ ok: false, stage: p.stage, failedAt: p.failedAt, status: p.status, from: from6, to, reason: p.reason, digest: p.digest, probe: p.probe, needsReload: false });
165205
+ }
165206
+ prep = p;
165207
+ steps.push(...p.steps);
165208
+ }
165209
+ if (!nativeWired(opts)) {
165210
+ stage("blocked", "\u539F\u751F\u58F3\u8FD8\u6CA1\u6307\u5411\u53EF\u5199\u76EE\u5F55 \u2192 \u4E0D\u5207\u6362");
165211
+ steps.push(`native_shell_not_wired: \u8D44\u6E90\u5DF2\u51C6\u5907\u5E76\u9A8C\u8BC1\u8FC7 (${WEB_LAYOUT.staging}), \u4F46\u5F53\u524D\u539F\u751F\u58F3\u4E0D\u4F1A\u52A0\u8F7D\u53EF\u5199\u76EE\u5F55\u91CC\u7684\u8D44\u6E90 \u21D2 \u5207\u6362\u53EA\u4F1A\u628A"\u80FD\u7528\u7684\u65E7\u7248\u672C"\u6362\u6210"\u58F3\u5C42\u52A0\u8F7D\u4E0D\u5230\u7684\u65B0\u7248\u672C" \u2014 \u62D2\u7EDD\u5207\u6362`);
165212
+ return finish({
165213
+ ok: false,
165214
+ stage: "blocked",
165215
+ status: "native_shell_not_wired",
165216
+ from: from6,
165217
+ to,
165218
+ reason: `native_shell_not_wired: \u624B\u673A App \u7684\u539F\u751F\u58F3\u8FD8\u6307\u5411\u5305\u5185\u8D44\u6E90 (\u6CA1\u6709\u6307\u5411\u53EF\u5199\u76EE\u5F55), \u6240\u4EE5\u8FD9\u6B21\u88C5\u5230 ${to} \u4E0D\u4F1A\u751F\u6548 \u2014 \u5DF2\u5728 ${WEB_LAYOUT.staging} \u51C6\u5907\u597D\u5E76\u901A\u8FC7\u542F\u52A8\u9A8C\u8BC1, \u4F46\u6CA1\u6709\u5207\u6362 current\u3002\u4FEE\u6CD5 (\u4E8C\u9009\u4E00, \u89C1 update-protocol \xA713.4): \u2460 \u628A\u58F3\u5C42 server.url \u6307\u5411\u672C\u673A http \u670D\u52A1\u7684\u53EF\u5199\u76EE\u5F55; \u2461 \u7B49\u539F\u751F\u5C42\u6539\u9020\u968F\u5546\u5E97/\u4FA7\u8F7D\u53D1\u4E00\u6B21\u3002` + NATIVE_CEILING_LINE,
165219
+ digest: prep.digest,
165220
+ probe: prep.probe,
165221
+ needsReload: false
165222
+ });
165223
+ }
165224
+ stage("switching", `\u539F\u5B50\u66FF\u6362: ${WEB_LAYOUT.current} \u2192 ${WEB_LAYOUT.previous}, ${WEB_LAYOUT.staging} \u2192 ${WEB_LAYOUT.current}`);
165225
+ const hadCurrent = await store.exists(joinRel(WEB_LAYOUT.current, "mobile.html"));
165226
+ let backupMade = false;
165227
+ try {
165228
+ const injectAt = opts.failSwitch === true ? "before_backup" : opts.failSwitch;
165229
+ if (injectAt === "before_backup") throw Object.assign(new Error("\u6CE8\u5165\u7684\u5207\u6362\u5931\u8D25 (\u5728\u52A8 current \u4E4B\u524D)"), { injected: true });
165230
+ await store.remove(WEB_LAYOUT.previous);
165231
+ if (hadCurrent) {
165232
+ await store.rename(WEB_LAYOUT.current, WEB_LAYOUT.previous);
165233
+ backupMade = true;
165234
+ }
165235
+ if (injectAt === "after_backup") throw Object.assign(new Error("\u6CE8\u5165\u7684\u5207\u6362\u5931\u8D25 (\u5907\u4EFD\u5DF2\u505A, staging \u672A\u4E0A\u4F4D)"), { injected: true });
165236
+ await store.rename(WEB_LAYOUT.staging, WEB_LAYOUT.current);
165237
+ steps.push(`switching: ${WEB_LAYOUT.current} \u5DF2\u66FF\u6362 (\u65E7\u8D44\u6E90\u4FDD\u7559\u5728 ${WEB_LAYOUT.previous})`);
165238
+ } catch (e) {
165239
+ const reason = String(e?.message || e);
165240
+ let rolled = false;
165241
+ try {
165242
+ await store.remove(WEB_LAYOUT.staging);
165243
+ if (backupMade) {
165244
+ await store.remove(WEB_LAYOUT.current);
165245
+ await store.rename(WEB_LAYOUT.previous, WEB_LAYOUT.current);
165246
+ rolled = true;
165247
+ } else if (hadCurrent) {
165248
+ rolled = false;
165249
+ }
165250
+ const probeAfter = rolled || hadCurrent ? await verifyWebLayer(store, WEB_LAYOUT.current, { bootTimeoutMs: opts.bootTimeoutMs }) : null;
165251
+ steps.push(`rolled_back: ${rolled ? "\u5DF2\u628A previous \u642C\u56DE current" : "current \u672A\u88AB\u6539\u52A8"} \xB7 \u542F\u52A8\u9A8C\u8BC1 ${probeAfter ? probeAfter.ok ? "\u901A\u8FC7" : "\u672A\u901A\u8FC7" : "\u8DF3\u8FC7"}`);
165252
+ stage(rolled ? "rolled_back" : "failed", `${reason}; ${rolled ? "\u5DF2\u56DE\u6EDA\u5230\u65E7\u8D44\u6E90" : "\u65E7\u8D44\u6E90\u672A\u88AB\u6539\u52A8"}`);
165253
+ return finish({
165254
+ ok: false,
165255
+ stage: rolled ? "rolled_back" : "failed",
165256
+ failedAt: "switching",
165257
+ status: "update_available",
165258
+ from: from6,
165259
+ to,
165260
+ reason: `${reason}; ${rolled ? `\u5DF2\u56DE\u6EDA\u5230 ${from6} (\u65E7\u8D44\u6E90\u56DE\u5230 current \u4E14\u9A8C\u8BC1${probeAfter?.ok ? "\u901A\u8FC7" : "\u672A\u901A\u8FC7"})` : "current \u672A\u88AB\u6539\u52A8, \u65E7\u8D44\u6E90\u4ECD\u5728"}`,
165261
+ digest: prep.digest,
165262
+ probe: probeAfter,
165263
+ needsReload: false
165264
+ });
165265
+ } catch (e2) {
165266
+ stage("failed", `${reason}; \u56DE\u6EDA\u8FC7\u7A0B\u51FA\u9519: ${e2?.message || e2}`);
165267
+ return finish({
165268
+ ok: false,
165269
+ stage: "failed",
165270
+ failedAt: "switching",
165271
+ status: "update_available",
165272
+ from: from6,
165273
+ to,
165274
+ reason: `${reason}; \u56DE\u6EDA\u8FC7\u7A0B\u51FA\u9519: ${e2?.message || e2}`,
165275
+ digest: prep.digest,
165276
+ probe: null,
165277
+ needsReload: false
165278
+ });
165279
+ }
165280
+ }
165281
+ stage("verifying", "\u5207\u6362\u540E\u518D\u9A8C\u4E00\u6B21 (\u80FD\u4E0D\u80FD\u542F\u52A8)");
165282
+ const after = await verifyWebLayer(store, WEB_LAYOUT.current, { bootTimeoutMs: opts.bootTimeoutMs });
165283
+ steps.push(`post-switch verify: ${after.mode} \u2014 ${after.detail}`);
165284
+ if (!after.ok) {
165285
+ let rolled = false;
165286
+ try {
165287
+ await store.remove(WEB_LAYOUT.current);
165288
+ if (backupMade) {
165289
+ await store.rename(WEB_LAYOUT.previous, WEB_LAYOUT.current);
165290
+ rolled = true;
165291
+ }
165292
+ stage("rolled_back", `\u5207\u6362\u540E\u9A8C\u8BC1\u5931\u8D25, ${rolled ? "\u5DF2\u56DE\u6EDA" : "\u6CA1\u6709\u53EF\u56DE\u6EDA\u7684\u65E7\u8D44\u6E90"}`);
165293
+ } catch {
165294
+ }
165295
+ return finish({
165296
+ ok: false,
165297
+ stage: rolled ? "rolled_back" : "failed",
165298
+ failedAt: "verifying",
165299
+ status: "update_available",
165300
+ from: from6,
165301
+ to,
165302
+ reason: `\u5207\u6362\u540E\u9A8C\u8BC1\u5931\u8D25: ${after.detail}; ${rolled ? `\u5DF2\u56DE\u6EDA\u5230 ${from6}` : "\u56DE\u6EDA\u5931\u8D25 \u2014 \u9700\u8981\u91CD\u88C5 App"}`,
165303
+ digest: prep.digest,
165304
+ probe: after,
165305
+ needsReload: false
165306
+ });
165307
+ }
165308
+ stage("succeeded", `\u5DF2\u5207\u5230 ${to}`);
165309
+ return finish({ ok: true, stage: "succeeded", status: "update_available", from: from6, to, reason: `\u5DF2\u66F4\u65B0\u5230 ${to} (\u91CD\u542F/\u91CD\u8F7D webview \u540E\u751F\u6548)`, digest: prep.digest, probe: after, needsReload: true });
165310
+ }
165311
+ async function rollbackMobileUpdate(opts) {
165312
+ const store = opts.store;
165313
+ const started = Date.now();
165314
+ const now = new Date(opts.now ? opts.now() : Date.now()).toISOString();
165315
+ const st = await readMobileUpdateState(store);
165316
+ const from6 = st.currentVersion || "unknown";
165317
+ const steps = [];
165318
+ const finish = async (o) => {
165319
+ const out = { ...o, durationMs: Date.now() - started, steps };
165320
+ await writeMobileUpdateState({
165321
+ lastUpdate: { at: now, from: out.from, to: out.to, stage: out.stage, reason: out.reason },
165322
+ history: [{ at: now, from: out.from, to: out.to, stage: out.stage, reason: out.reason }, ...st.history || []].slice(0, 20)
165323
+ }, store);
165324
+ return out;
165325
+ };
165326
+ if (!opts.confirm) {
165327
+ return finish({ ok: false, stage: "blocked", status: "update_available", from: from6, to: from6, reason: "human_confirm_required: \u56DE\u6EDA\u4F1A\u6362\u6389\u6B63\u5728\u8DD1\u7684 web \u8D44\u6E90 \u2014 \u5FC5\u987B\u6709\u4EBA\u5728\u73AF", digest: null, probe: null, needsReload: false });
165328
+ }
165329
+ if (!await store.exists(joinRel(WEB_LAYOUT.previous, "mobile.html"))) {
165330
+ return finish({ ok: false, stage: "blocked", status: "web_bundle_malformed", from: from6, to: from6, reason: `\u6CA1\u6709\u53EF\u56DE\u6EDA\u7684\u65E7\u8D44\u6E90 (${WEB_LAYOUT.previous} \u7A7A) \u2014 \u4E0D\u5047\u88C5\u56DE\u6EDA\u8FC7`, digest: null, probe: null, needsReload: false });
165331
+ }
165332
+ const probeBefore = await verifyWebLayer(store, WEB_LAYOUT.previous, { bootTimeoutMs: opts.bootTimeoutMs });
165333
+ steps.push(`previous \u63A2\u6D4B: ${probeBefore.detail}`);
165334
+ if (!probeBefore.ok) {
165335
+ return finish({ ok: false, stage: "blocked", status: "web_bundle_malformed", from: from6, to: from6, reason: `previous \u91CC\u7684\u65E7\u8D44\u6E90\u8D77\u4E0D\u6765 (${probeBefore.detail}) \u2014 \u56DE\u6EDA\u5B83\u53EA\u4F1A\u66F4\u7CDF, \u62D2\u7EDD`, digest: null, probe: probeBefore, needsReload: false });
165336
+ }
165337
+ try {
165338
+ await store.remove(joinRel(WEB_LAYOUT.current, ""));
165339
+ await store.rename(WEB_LAYOUT.previous, WEB_LAYOUT.current);
165340
+ } catch (e) {
165341
+ return finish({ ok: false, stage: "failed", failedAt: "switching", status: "update_available", from: from6, to: from6, reason: `\u56DE\u6EDA\u5931\u8D25: ${e?.message || e}`, digest: null, probe: null, needsReload: false });
165342
+ }
165343
+ const after = await verifyWebLayer(store, WEB_LAYOUT.current, { bootTimeoutMs: opts.bootTimeoutMs });
165344
+ steps.push(`\u56DE\u6EDA\u540E\u63A2\u6D4B: ${after.detail}`);
165345
+ return finish({
165346
+ ok: after.ok,
165347
+ stage: after.ok ? "rolled_back" : "failed",
165348
+ status: "update_available",
165349
+ from: from6,
165350
+ to: from6,
165351
+ reason: after.ok ? `\u5DF2\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u8D44\u6E90\u5C42 (\u9700\u8981\u91CD\u8F7D webview \u751F\u6548)` : `\u56DE\u6EDA\u540E\u8D44\u6E90\u8D77\u4E0D\u6765: ${after.detail}`,
165352
+ digest: null,
165353
+ probe: after,
165354
+ needsReload: after.ok
165355
+ });
165356
+ }
165357
+ var MOBILE_UPDATE_AGENT_CONTRACT = {
165358
+ schema: "bolloon-mobile-agent-update/1",
165359
+ /** 可以自动做 (只读 + 只在 staging 里写, 不影响正在跑的资源) */
165360
+ autoAllowedSteps: ["check", "prepare", "download", "digest-verify", "extract", "stage", "verify"],
165361
+ /** 必须人在环 (会换掉正在跑的界面与逻辑 ⇒ 代价不对称) */
165362
+ humanRequiredSteps: ["switch", "reload", "rollback", "channel-switch"],
165363
+ preconditions: [
165364
+ "\u672C\u673A web \u5C42\u7684\u8EAB\u4EFD\u53EF\u8BFB (\u58F3\u5C42\u6CE8\u5165 / OTA state / \u6784\u5EFA\u6233 bolloon-web.json \u4E09\u8005\u4E4B\u4E00) \u2014\u2014 \u8BFB\u4E0D\u5230\u5C31 local_version_unknown, \u4E0D\u731C",
165365
+ "\u539F\u751F\u58F3\u5DF2\u6307\u5411\u53EF\u5199\u76EE\u5F55 (native_writable) \u2014\u2014 \u5426\u5219\u8D44\u6E90\u51C6\u5907\u597D\u4E86\u4E5F\u4E0D\u4F1A\u751F\u6548, \u4E00\u5F8B native_shell_not_wired \u5E76\u8BF4\u6E05\u4FEE\u6CD5",
165366
+ "\u8BBE\u5907\u5B58\u50A8\u4F59\u91CF\u591F (\u4E0B\u8F7D\u7269 \u2264 \u4E0A\u9650, \u9ED8\u8BA4 80 MiB) \u2014\u2014 \u8D85\u4E86\u5224 payload_too_large, \u4E0D\u8D4C\u5185\u5B58",
165367
+ "\u672C\u673A WebView \u6709 DecompressionStream('gzip') (iOS 16.4+ / Chrome 80+) \u2014\u2014 \u6CA1\u6709\u5C31 decompress_unavailable",
165368
+ '\u7A33\u5B9A\u7F51\u7EDC: \u6E90\u4E0D\u53EF\u8FBE\u4E00\u5F8B offline / registry_unavailable / github_unavailable(reason) \u62D2\u7EDD, \u4E0D\u9759\u9ED8\u91CD\u8BD5\u5230"\u770B\u8D77\u6765\u6210\u529F"',
165369
+ "\u6458\u8981\u53EF\u6821\u9A8C: npm \u4FA7\u7528 dist.shasum/dist.integrity; dev \u4FA7\u7528\u5305\u5185 .bolloon-dev-snapshot.json \u7684 sha + bundleSha256"
165370
+ ],
165371
+ riskBoundaries: [
165372
+ 'iOS \u539F\u751F\u58F3\u5C42**\u4E0D\u80FD\u81EA\u66F4** (App Store \u89C4\u5219) \u21D2 \u4E0D\u627F\u8BFA"iOS \u6574\u4F53\u81EA\u52A8\u66F4\u65B0"; web \u5C42\u80FD\u66F4, \u4E8C\u8FDB\u5236\u8D70\u5546\u5E97/TestFlight',
165373
+ "Android \u4FA7\u8F7D APK \u81EA\u66F4**\u9700\u7528\u6237\u5141\u8BB8\u672A\u77E5\u6765\u6E90** \u21D2 \u5FC5\u987B\u663E\u5F0F\u544A\u77E5 + \u7528\u6237\u5728\u73AF, \u672C\u8F6E\u4E0D\u505A\u9759\u9ED8\u88C5\u5305",
165374
+ '\u5207\u6362\u524D\u5FC5\u987B\u771F\u8DD1\u4E00\u6B21"\u80FD\u4E0D\u80FD\u542F\u52A8"\u63A2\u6D4B (\u7ED3\u6784+\u8BED\u6CD5+iframe), \u63A2\u6D4B\u4E0D\u901A\u8FC7\u4E0D\u8BB8\u5207\u8FDB current',
165375
+ '\u5207\u6362\u5FC5\u987B\u539F\u5B50 (rename) \u4E14\u4FDD\u7559 previous; \u4EFB\u4F55\u4E00\u6B65\u5931\u8D25\u90FD\u8981\u56DE\u5230"\u80FD\u542F\u52A8\u7684\u65E7\u8D44\u6E90"',
165376
+ "dev \u901A\u9053\u4E0D\u4FDD\u8BC1\u53EF\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A dev \u7248 (\u4E0E\u684C\u9762\u540C\u4E00\u53E5\u8B66\u544A); \u4E00\u952E\u56DE stable \u59CB\u7EC8\u53EF\u7528",
165377
+ "\u6E90\u4E0D\u53EF\u8FBE/\u7248\u672C\u4E0D\u5B58\u5728/\u4EA4\u53C9\u6821\u9A8C\u4E0D\u4E00\u81F4/\u6458\u8981\u4E0D\u7B26 \u21D2 \u62D2\u7EDD\u5E76\u5206\u7C7B\u8BF4\u6E05, \u7EDD\u4E0D\u9759\u9ED8\u88C5\u56DE\u65E7\u7248\u3001\u7EDD\u4E0D\u5047\u88C5\u6210\u529F"
165378
+ ]
165379
+ };
165380
+ async function autoPrepareMobileUpdate(deps) {
165381
+ return prepareMobileUpdate(deps);
165382
+ }
165383
+ function renderMobileUpdateReport(r, st) {
165384
+ const L2 = [];
165385
+ L2.push(r.identityLine);
165386
+ L2.push(`\u66F4\u65B0\u5C42: web \u8D44\u6E90\u5C42 (\u754C\u9762\u4E0E\u903B\u8F91: \u5165\u7FA4 / \u53D1\u4EFB\u52A1\u516C\u544A / \u770B\u98DE\u8F6E\u8FDB\u5EA6 / \u6388\u6743\u7B7E\u540D) \xB7 store=${r.storeKind}`);
165387
+ L2.push(`\u80FD\u5207\u56DE: ${r.switchableTo ? `${r.switchableTo.channel} (${r.switchableTo.source}${r.switchableTo.target ? ` @ ${r.switchableTo.target}` : ""})` : "\u65E0 (\u672A\u77E5\u6765\u6E90)"}`);
165388
+ L2.push(`\u76EE\u6807\u7248\u672C: ${r.targetIdentity || r.latestVersion || "\u672A\u77E5"}`);
165389
+ L2.push(`\u66F4\u65B0\u901A\u9053: ${r.channel} (\u6BD4\u8F83\u8BED\u4E49: ${r.channelKind})`);
165390
+ L2.push(renderGithubReportLine(r.github));
165391
+ if (r.crossCheck) L2.push(`\u4EA4\u53C9\u6821\u9A8C: ${r.crossCheck.kind}${r.crossCheck.blocking ? " (\u963B\u585E)" : ""} \u2014 ${r.crossCheck.detail}`);
165392
+ if (r.dev) L2.push(`dev \u5224\u5B9A: ${r.dev.detail}`);
165393
+ if (r.sourceFacts?.npm) L2.push(`npm \u6E90: ${r.sourceFacts.npm.reachable ? `\u53EF\u8FBE (latest=${r.sourceFacts.npm.latest || "\u672A\u77E5"})` : `\u4E0D\u53EF\u8FBE (${r.sourceFacts.npm.detail || "\u672A\u77E5"})`}`);
165394
+ if (st?.installedFrom) L2.push(`\u5F53\u524D\u6765\u6E90: ${st.installedFrom.channel} \xB7 ${st.installedFrom.source}${st.installedFrom.digest ? ` \xB7 \u6458\u8981 ${st.installedFrom.digest}` : ""}${st.installedFrom.at ? ` \xB7 ${st.installedFrom.at}` : ""}`);
165395
+ if (st?.lastDevSha && r.installedChannel !== "dev") L2.push(`\u4E0A\u6B21 dev: commit ${st.lastDevSha} (\u5DF2\u5207\u56DE stable)`);
165396
+ L2.push(`\u7ED3\u8BBA: ${r.status}${refused(r.status) ? " \u2014 \u62D2\u7EDD (\u6CA1\u6709\u5B89\u88C5\u4EFB\u4F55\u4E1C\u897F)" : ""}`);
165397
+ if (r.reason) L2.push(`\u8BF4\u660E: ${r.reason}`);
165398
+ for (const w2 of r.warnings) L2.push(w2);
165399
+ return L2;
165400
+ }
165401
+ var NATIVE_CEILING_LINE = '\u539F\u751F\u58F3\u5C42\u4E0D\u80FD\u5728\u8FD9\u91CC\u81EA\u66F4: iOS \u53D7 App Store \u89C4\u5219\u9650\u5236 (\u4E8C\u8FDB\u5236\u8D70\u5546\u5E97/TestFlight), Android \u4FA7\u8F7D APK \u81EA\u66F4\u9700\u8981\u4F60\u5141\u8BB8"\u672A\u77E5\u6765\u6E90" \u2014\u2014 \u8FD9\u91CC\u80FD\u81EA\u52A8\u66F4\u65B0\u7684\u662F web \u8D44\u6E90\u5C42 (\u754C\u9762\u4E0E\u903B\u8F91)\u3002';
165402
+ var MOBILE_CHANNEL_KEY = "bolloon.update.channel";
165403
+ function createAutoWebStore() {
165404
+ const injected = globalThis.__bolloonWebStore;
165405
+ if (injected && typeof injected.writeBytes === "function" && typeof injected.rename === "function") return injected;
165406
+ const cap = globalThis.Capacitor;
165407
+ const fs = cap?.Plugins?.Filesystem;
165408
+ if (fs && typeof fs.writeFile === "function") {
165409
+ const base6 = globalThis.__bolloonWebBaseUrl || null;
165410
+ return createCapacitorWebStore(fs, { basePath: "bolloon-web", directory: "DATA", baseUrl: base6 });
165411
+ }
165412
+ return createMemoryWebStore({ kind: "memory", rootLabel: "memory://webview", baseUrl: globalThis.__bolloonWebBaseUrl || null });
165413
+ }
165414
+ function storedMobileChannel() {
165415
+ try {
165416
+ const v = globalThis.localStorage?.getItem?.(MOBILE_CHANNEL_KEY);
165417
+ return v ? String(v) : null;
165418
+ } catch {
165419
+ return null;
165420
+ }
165421
+ }
165422
+ async function setMobileUpdateChannel(channel, store) {
165423
+ const want = String(channel || "").toLowerCase();
165424
+ if (want !== "stable" && want !== "dev") {
165425
+ const cur = storedMobileChannel() || "stable";
165426
+ return { ok: false, channel: cur, detail: `\u672A\u77E5\u901A\u9053 "${channel}" (\u53EA\u6709 stable / dev) \u2014 \u4FDD\u6301 ${cur}` };
165427
+ }
165428
+ const prev = storedMobileChannel();
165429
+ try {
165430
+ globalThis.localStorage?.setItem?.(MOBILE_CHANNEL_KEY, want);
165431
+ } catch {
165432
+ }
165433
+ if (store) await writeMobileUpdateState({ channelPref: want }, store);
165434
+ return { ok: true, channel: want, detail: `\u901A\u9053 ${prev || "stable"} \u2192 ${want} (\u53EA\u6539\u504F\u597D, \u8FD8\u6CA1\u6709\u5B89\u88C5\u4EFB\u4F55\u4E1C\u897F; \u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u518D\u51B3\u5B9A\u88C5\u4E0D\u88C5)` };
165435
+ }
165436
+ async function reloadMobileWeb() {
165437
+ const cap = globalThis.Capacitor;
165438
+ try {
165439
+ if (cap?.Plugins?.WebView?.reload) {
165440
+ await cap.Plugins.WebView.reload();
165441
+ return { ok: true, how: "capacitor-webview", detail: "\u5DF2\u8BF7\u6C42 WebView \u91CD\u8F7D (\u65B0 web \u8D44\u6E90\u751F\u6548)" };
165442
+ }
165443
+ } catch (e) {
165444
+ return { ok: false, how: "capacitor-webview", detail: `WebView \u91CD\u8F7D\u5931\u8D25: ${String(e?.message || e)} \u2014 \u8BF7\u624B\u52A8\u91CD\u542F App` };
165445
+ }
165446
+ const loc = globalThis.location;
165447
+ if (loc && typeof loc.reload === "function") {
165448
+ loc.reload();
165449
+ return { ok: true, how: "location.reload", detail: "\u5DF2\u91CD\u8F7D\u9875\u9762" };
165450
+ }
165451
+ return { ok: false, how: "none", detail: "\u672C\u73AF\u5883\u6CA1\u6709\u53EF\u91CD\u8F7D\u7684 WebView (\u9A8C\u6536/Node) \u2014 \u672A\u5047\u88C5\u91CD\u8F7D" };
165452
+ }
165453
+
163909
165454
  // src/web/mobile-core.ts
165455
+ var __webStore = null;
165456
+ function currentWebStore() {
165457
+ if (!__webStore) __webStore = createAutoWebStore();
165458
+ return __webStore;
165459
+ }
165460
+ function injectedWebIdentity() {
165461
+ const v = globalThis.__bolloonWebIdentity;
165462
+ if (v && typeof v === "object" && typeof v.version === "string" && v.version) {
165463
+ return { version: v.version, channel: v.channel === "dev" ? "dev" : v.channel === "stable" ? "stable" : void 0 };
165464
+ }
165465
+ return null;
165466
+ }
165467
+ function mobileUpdateDeps(opts = {}) {
165468
+ return {
165469
+ // 透传: 壳层/验收可以覆盖 registryBase / apiBase / devBundleUrl / devBundleSha256 / fetchImpl …
165470
+ // (只有 store / 通道 / 身份 / 壳层判据由本层接管 —— 那几项是"设备说了算"的)
165471
+ ...opts,
165472
+ store: currentWebStore(),
165473
+ channel: opts.channel || storedMobileChannel() || void 0,
165474
+ localIdentity: opts.localIdentity !== void 0 ? opts.localIdentity : injectedWebIdentity(),
165475
+ nativeWritable: opts.nativeWritable !== void 0 ? opts.nativeWritable : void 0,
165476
+ onStage: opts.onStage
165477
+ };
165478
+ }
163910
165479
  var busHandlers = /* @__PURE__ */ new Set();
163911
165480
  function busBroadcast(msg) {
163912
165481
  for (const h3 of busHandlers) h3(msg);
@@ -164943,6 +166512,74 @@ ${error.stack}` : head;
164943
166512
  setTaskHint(text, core.contacts.storage());
164944
166513
  }
164945
166514
  },
166515
+ /**
166516
+ * web 资源层双源 OTA 更新 (2026-09-26, update-protocol §13) — 与桌面 `bolloon update` 同语义:
166517
+ * ① 身份: stable = npm dist-tag 权威 + GitHub Tag 交叉校验; dev = master `ref` + commit sha, 身份 `<版本>+dev.<sha7>`
166518
+ * ② 拒绝: 源不可达 / 版本不存在 / 交叉校验不一致 ⇒ **拒绝并说清分类**, 绝不静默装回旧版、绝不假装成功
166519
+ * ③ 可见: 当前装的是哪个源 / 哪个 sha / 能切回哪个源 (与桌面同措辞)
166520
+ * ④ 智能体: check/plan/download/verify 可程序调用; **switch / reload / rollback / 切通道必须人确认**
166521
+ *
166522
+ * 天花板 (不许承诺做不到的): iOS 原生壳层**不能自更** (App Store 规则) —— 自更的只是 web 资源层;
166523
+ * 二进制要走商店 / TestFlight。Android 侧载 APK 可自更但要用户允许未知来源 (显式告知 + 人在环)。
166524
+ */
166525
+ update: {
166526
+ /** 结论/阶段枚举与拒绝表 (给 UI 与智能体做判断, 不用猜字符串) */
166527
+ vocabulary() {
166528
+ return {
166529
+ statuses: MOBILE_WEB_STATUSES.slice(),
166530
+ refused: MOBILE_REFUSED_STATUSES.slice(),
166531
+ requiredFiles: MOBILE_WEB_REQUIRED_FILES.slice(),
166532
+ layout: { ...WEB_LAYOUT },
166533
+ nativeCeiling: NATIVE_CEILING_LINE,
166534
+ agentContract: MOBILE_UPDATE_AGENT_CONTRACT
166535
+ };
166536
+ },
166537
+ /** 可写 web 资源目录接上没有 (真机: 原生壳要指向它才生效) */
166538
+ nativeWired() {
166539
+ return nativeWired(mobileUpdateDeps());
166540
+ },
166541
+ /** 当前 web 层的身份/来源/能切回谁 (读 state + 壳层注入的构建戳) */
166542
+ async state() {
166543
+ return readMobileUpdateState(currentWebStore());
166544
+ },
166545
+ async identity() {
166546
+ return resolveLocalWebIdentity(mobileUpdateDeps());
166547
+ },
166548
+ /** 检查两源 (只读, 不下载任何东西) */
166549
+ async check(opts = {}) {
166550
+ return checkMobileUpdate(mobileUpdateDeps(opts));
166551
+ },
166552
+ /** 检查 + 说明 (报告行: 与桌面 `update --status` 同口径) */
166553
+ async report(opts = {}) {
166554
+ const r = await checkMobileUpdate(mobileUpdateDeps(opts));
166555
+ const st = await readMobileUpdateState(currentWebStore());
166556
+ return { result: r, lines: renderMobileUpdateReport(r, st) };
166557
+ },
166558
+ /** 下载 → 解压 → 落 staging → 验证可启动; **不切换** (智能体能自动跑到这一步) */
166559
+ async prepare(opts = {}) {
166560
+ return autoPrepareMobileUpdate(mobileUpdateDeps(opts));
166561
+ },
166562
+ /** 切换 (人在环: 必须 confirm) → 原子替换 + 失败回滚; 成功后提示重载 */
166563
+ async apply(opts = {}) {
166564
+ return applyMobileUpdate({ ...mobileUpdateDeps(opts), confirm: opts.confirm === true, reuseStaging: opts.reuseStaging === true });
166565
+ },
166566
+ /** 回滚到 previous (人在环) */
166567
+ async rollback(opts = {}) {
166568
+ return rollbackMobileUpdate({ ...mobileUpdateDeps(opts), confirm: opts.confirm === true });
166569
+ },
166570
+ /** 切换通道 (stable ↔ dev; 只写偏好, 不装东西) */
166571
+ async setChannel(channel) {
166572
+ return setMobileUpdateChannel(channel, currentWebStore());
166573
+ },
166574
+ /** 当前通道偏好 */
166575
+ channel() {
166576
+ return storedMobileChannel() || "stable";
166577
+ },
166578
+ /** 重载 web 层让新资源生效 (人在环) */
166579
+ async reload() {
166580
+ return reloadMobileWeb();
166581
+ }
166582
+ },
164946
166583
  message: {
164947
166584
  async send({ text, channelId }) {
164948
166585
  if (!text || !channelId) return { ok: false, error: "text \u548C channelId \u5FC5\u586B" };