chainlesschain 0.162.17 → 0.162.20

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 (166) hide show
  1. package/package.json +3 -3
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-BnOiVQsQ.js → AIOps-Buh_mYlF.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-Tw6tLfY1.js → ActionButton-Dc7c0bZf.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Des59eT3.js → Analytics-CR9TWfM-.js} +2 -2
  6. package/src/assets/web-panel/assets/AppLayout-DVfanUep.js +3 -0
  7. package/src/assets/web-panel/assets/{AppLayout-BLCe1k8m.css → AppLayout-DleZFgpe.css} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-JX1upUxG.js → Audit-BxojBqRe.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-ByvQLRhY.js → Backup-CGkreaK9.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DC-h8Hd-.js → BaseInput-B-Q2ZGfz.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-D9dJ3NKb.js → Chat-Dd_VC5Av.js} +3 -3
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-B64kZfRH.js +1 -0
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-CO908iBt.css +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-n3tD-95B.js → Checkbox-NYBD1rCW.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BV9t0CmU.js → Codegen-B3Kqhgv1.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-4KhPnXFB.js → Col-2A3Bg2GC.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-R1hZqHEr.js → Community-C84SkDEJ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-Bo4r1Z3L.js → Compact-Crv3pyZJ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Dwywk8U2.js → Compliance-D4qasxoY.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DTBwVdcR.js → Cowork-Bfbvysw-.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-BrveD2Al.js → Cron-Dkjyiekd.js} +1 -1
  22. package/src/assets/web-panel/assets/{Crosschain-C63oZCeg.js → Crosschain-DbJm9Kjd.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-Cyfo7zh4.js → DID-C2HI6acG.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-_EOI1Ax4.js → Dashboard-BXMY7QVP.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BWYHGHO3.js → Dropdown-DcOykpfh.js} +1 -1
  26. package/src/assets/web-panel/assets/EmailListRenderer-CVJ1j3Ll.css +1 -0
  27. package/src/assets/web-panel/assets/EmailListRenderer-Doo52F2M.js +1 -0
  28. package/src/assets/web-panel/assets/{Federation-CU03AB2d.js → Federation-cHubZT6U.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DECACp9C.js → FormItemContext-B5OVlspi.js} +1 -1
  30. package/src/assets/web-panel/assets/GenericCardRenderer-DtJFcJYl.css +1 -0
  31. package/src/assets/web-panel/assets/GenericCardRenderer-OgA46PJq.js +1 -0
  32. package/src/assets/web-panel/assets/{Git-BhpPR-ck.js → Git-_OKH_JOq.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-BtpmvOHi.js → Governance-CTByBwSV.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-DfPuEW02.js → Inference-C0nqgySA.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-CeA_2NFe.js → KnowledgeGraph-CWW4ZIqH.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-C_ozF-bn.js → Logs-DrwqUltr.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-WgBvu6Ma.js → Marketplace-LJMRx-dm.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-CNIz8liM.js → McpTools-F_EjC8j3.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-DEN8NgRg.js → Memory-VonDem9-.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CP0rj-Fx.js → MobileBridge-CUWxIrfb.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-x3zDbPDI.js → MobileProjects-CGOo70lk.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CC4PEFJV.js → Mtc-CqUquFbQ.js} +1 -1
  43. package/src/assets/web-panel/assets/{MtcAudit-BHB0GvyW.js → MtcAudit-CRVusaip.js} +1 -1
  44. package/src/assets/web-panel/assets/{Multisig-BUPfLbde.js → Multisig-B0o8Mv8T.js} +2 -2
  45. package/src/assets/web-panel/assets/{NLProgramming-DNOkByM8.js → NLProgramming-CxQINUhu.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-U-RIv-kE.js → Notes-B6Q_apFW.js} +1 -1
  47. package/src/assets/web-panel/assets/{NotificationSettings-B-q6tsxf.js → NotificationSettings-DO-lgp6x.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-BnOISpKI.css +1 -0
  49. package/src/assets/web-panel/assets/OrderTableRenderer-DxoBoMtz.js +1 -0
  50. package/src/assets/web-panel/assets/{Organization-DJLNuZ2_.js → Organization-DqJ_KawH.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-BxTl0aeE.js → Overflow-Cg4iKODM.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-Czzalu_0.js → P2P-n5GPMrwC.js} +1 -1
  53. package/src/assets/web-panel/assets/PdhVaultBrowser-B9ZGFpn4.css +1 -0
  54. package/src/assets/web-panel/assets/PdhVaultBrowser-BdZI0EJs.js +7 -0
  55. package/src/assets/web-panel/assets/{Permissions-BBw06EGK.js → Permissions-BjtwAIGb.js} +3 -3
  56. package/src/assets/web-panel/assets/PersonalDataHub-Bsf3Wh6n.js +1 -0
  57. package/src/assets/web-panel/assets/{PersonalDataHub-Dvaa8niQ.css → PersonalDataHub-D0ncF92t.css} +1 -1
  58. package/src/assets/web-panel/assets/{Pipeline-qLLWNe2i.js → Pipeline-BZe2-Flj.js} +1 -1
  59. package/src/assets/web-panel/assets/Privacy-OJqMon4c.js +1 -0
  60. package/src/assets/web-panel/assets/{ProjectInit-CmJ0VPGy.js → ProjectInit-Dw-5SMG1.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-D1Xm2EwO.js → ProjectSettings-DxqW44cV.js} +1 -1
  62. package/src/assets/web-panel/assets/{Projects-DLN-awqS.js → Projects-CPOqs2ec.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-D164DGWo.js → Providers-hBfwscEl.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-BRCH7WgH.js → QuickAsk-DA3WFFAr.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-BWZhT_-2.js → Recommend-DMpz3URg.js} +1 -1
  66. package/src/assets/web-panel/assets/Reputation-6-z56FwC.js +1 -0
  67. package/src/assets/web-panel/assets/{Row-BbuTB-ny.js → Row-CL1Y6SfW.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-CLegyrrb.js → RssFeed-BIHU2bMf.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-Bz41pkvW.js → Search-CRAfuEX-.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-D4NWRpgV.js → Security-CVya3gvH.js} +3 -3
  71. package/src/assets/web-panel/assets/{Services-CP_6SXST.js → Services-Cxv7_umF.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-CdRO3MnV.js → Skeleton-D_GvsCtv.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-BmghgIL3.js → Skills-DyiEpcvf.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-DjAIXxkm.js → Sla-wiglQzXV.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-DYk-En4R.js → SpeechSettings-CLtovaa5.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CglX98G4.js → SyncSettings-BOgVef1O.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tasks-BcLMTSP3.js → Tasks-Ik8AX7-J.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-BCs5bP8P.js → Templates-CireiINW.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-B5jLtdWR.js → Tenant-t7jF9NkQ.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-CZKKYYaA.js → Terminal-Btq_O951.js} +1 -1
  81. package/src/assets/web-panel/assets/TimelineRenderer-DmztdsbF.css +1 -0
  82. package/src/assets/web-panel/assets/TimelineRenderer-hC_Piiwy.js +1 -0
  83. package/src/assets/web-panel/assets/{Tokens-B8aHonMu.js → Tokens-BTIqA2J1.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-D6jDjaX1.js → Trigger-Dzv2gJpK.js} +1 -1
  85. package/src/assets/web-panel/assets/Trust-B13QpWOh.js +1 -0
  86. package/src/assets/web-panel/assets/{UkeySign-B1IDXsz6.js → UkeySign-CFj3UF4G.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-DHvglMzO.js → VideoEditing-CGMOGTQb.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-DAUh8j_b.js → Wallet-BkCJ573Y.js} +1 -1
  89. package/src/assets/web-panel/assets/{WebAuthn-D0Y8wGZt.js → WebAuthn-C8dCrmGH.js} +4 -4
  90. package/src/assets/web-panel/assets/{WorkflowEditor-BqZgQo4U.js → WorkflowEditor-CclfhxwH.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-CtfIYzWq.js → chat-CW_X_A9l.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-B6ZfKKgZ.js → colors-6GtheJb2.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item--HQOl9D1.js → compact-item-tz7dJapw.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-Bf-YNOPI.js → createContext-CbiGcRCw.js} +1 -1
  95. package/src/assets/web-panel/assets/{hasIn-D5G7UiYx.js → hasIn-bmNFD3PH.js} +1 -1
  96. package/src/assets/web-panel/assets/icons-AILO5ZcK.js +57 -0
  97. package/src/assets/web-panel/assets/{index-DJlTZ7oK.js → index-B2P1c5yD.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DZyNnZb9.js → index-B3UH6whC.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CkoQfJv0.js → index-B6GgMo1k.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-ofgv0VHh.js → index-B9sJpsz3.js} +1 -1
  101. package/src/assets/web-panel/assets/index-BFkZAdio.js +1 -0
  102. package/src/assets/web-panel/assets/{index-CGG5O6Zh.js → index-BM4slfDH.js} +1 -1
  103. package/src/assets/web-panel/assets/index-BhgINPAH.js +1 -0
  104. package/src/assets/web-panel/assets/{index-UppuQXbk.js → index-BxCPkuTG.js} +2 -2
  105. package/src/assets/web-panel/assets/{index-BPvA_8ed.js → index-C5zlrlPd.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-OSlN9c4x.js → index-CBNnbQxM.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BWOeC8v6.js → index-CKP-AZD3.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DIYydwQc.js → index-CLk3xgD2.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-2QuOKVo1.js → index-CQ7aJ-8s.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-_ZD6hPvh.js → index-CTnIkosg.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Ca_5Za9D.js → index-CU0MeCwH.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B23hk7ZY.js → index-CV9aPPYM.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BtjPgDzA.js → index-CYIwY9zQ.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-XwovMqXr.js → index-Cb33JbDx.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BOzNKGtD.js → index-CgLF_zD1.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CihsW323.js → index-Cs5KWviO.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CAPj11l4.js → index-CzIQ0u4e.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DzvrUpDp.js → index-DK7ee5hC.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-g_d5tOWL.js → index-DMGJqZ-o.js} +1 -1
  120. package/src/assets/web-panel/assets/{index--KnB-1F0.js → index-Da2RUdcb.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-__qol0SM.js → index-DaK59dFw.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Dfm65OiJ.js → index-DaYfMM7r.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-B75QpKcS.js → index-De0xvTEv.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-ByjAzHrC.js → index-DeoJYTHX.js} +4 -4
  125. package/src/assets/web-panel/assets/{index-OY_eegoA.js → index-Dj1N1idV.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CcbuseEC.js → index-DvWXdCxl.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CQIzU6GO.js → index-DxHmNJ8S.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-xOVop383.js → index-DxdLksCx.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CmVrKala.js → index-KFdfLxVt.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BBSGKZ-l.js → index-S9g45I5s.js} +3 -3
  131. package/src/assets/web-panel/assets/{index-BKsNidiT.js → index-UId2JAY6.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BRPGyig2.js → index-UjxsY00y.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BrJfwd9_.js → index-b7sSMzkC.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DqJoAOhC.js → index-l2VM-W36.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CVTgMzbI.js → index-zP_o4yKV.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-DjUNw-1y.js → initDefaultProps-BTGwT0NA.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-Cd6xXLlf.js → motion-BheHkG8e.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-B_W-Y6JL.js → move-BkMMNkAw.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-BCZBLb_-.js → omit-B9zmSFf6.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-CjDhGYLm.js → pickAttrs-CFNBcA5Z.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-CSC2rU9-.js → placementArrow-B4mvrUYw.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-DnyswFkD.js → responsiveObserve-BJqsSjxJ.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-DujNhbuH.js → slide-Cj78MXSi.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-U2lNCQiL.js → statusUtils-Bk6ChXAV.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-Ch2rQftE.js → styleChecker-B_YDSNYf.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-5XxNwjTL.js → useFlexGapSupport-q90prOQL.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-pv2YfF-X.js → useFs-BtpFJ9kI.js} +1 -1
  148. package/src/assets/web-panel/assets/usePersonalDataHub-C3qhf1sQ.js +1 -0
  149. package/src/assets/web-panel/assets/{vnode-CU0J2mVX.js → vnode-Bz2j0JiG.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-ZII_iqXl.js → zoom-CzaEtel2.js} +1 -1
  151. package/src/assets/web-panel/index.html +2 -2
  152. package/src/commands/__tests__/hub-ask.test.js +136 -0
  153. package/src/commands/ask.js +110 -43
  154. package/src/commands/hub.js +260 -26
  155. package/src/constants.js +7 -0
  156. package/src/gateways/ws/personal-data-hub-protocol.js +151 -9
  157. package/src/lib/host-adb-bridge.js +430 -0
  158. package/src/lib/personal-data-hub-wiring.js +85 -0
  159. package/src/assets/web-panel/assets/AppLayout-hku5aFqc.js +0 -3
  160. package/src/assets/web-panel/assets/PersonalDataHub-CmTx090l.js +0 -1
  161. package/src/assets/web-panel/assets/Privacy-CStpViYX.js +0 -1
  162. package/src/assets/web-panel/assets/Reputation-DSTNi8ge.js +0 -1
  163. package/src/assets/web-panel/assets/Trust-DX_BvTA9.js +0 -1
  164. package/src/assets/web-panel/assets/icons-BOPtEWK4.js +0 -57
  165. package/src/assets/web-panel/assets/index-B-xjGh0B.js +0 -1
  166. package/src/assets/web-panel/assets/index-B9RskCJ5.js +0 -1
@@ -22,12 +22,103 @@ import {
22
22
  close as closeHub,
23
23
  } from "../../lib/personal-data-hub-wiring.js";
24
24
  import { getAIChatWizard } from "../../lib/personal-data-hub-aichat-wizard.js";
25
- import { existsSync, unlinkSync, readdirSync } from "node:fs";
25
+ import {
26
+ existsSync,
27
+ unlinkSync,
28
+ readdirSync,
29
+ mkdirSync,
30
+ writeFileSync,
31
+ } from "node:fs";
26
32
  import { join } from "node:path";
27
33
  import pdhPkg from "@chainlesschain/personal-data-hub";
28
34
 
29
35
  const { ingestSystemDataAndroidSnapshot } = pdhPkg;
30
36
 
37
+ /**
38
+ * If the caller didn't pass `inputPath`, try to pull a snapshot for
39
+ * this adapter off the attached Android phone via `adb shell run-as
40
+ * <pkg.debug> cat files/.chainlesschain/staging/<name>.json`. On
41
+ * success, write the JSON to `hub.hubDir/staging/` and return an
42
+ * `options` object with `inputPath` set so the adapter's _syncViaSnapshot
43
+ * picks it up.
44
+ *
45
+ * Why this is needed: every social adapter (bilibili, weibo, douyin,
46
+ * xiaohongshu, toutiao, kuaishou, qq, wechat, baidu-map, tencent-map,
47
+ * jd, meituan, pinduoduo) is snapshot-only and the Android in-app
48
+ * collectors write their JSON into the app's filesDir/.chainlesschain/
49
+ * staging/. Without an auto-pull, the desktop 同步 button can never
50
+ * reach a working state for these adapters — it just throws "needs
51
+ * opts.inputPath".
52
+ *
53
+ * Best-effort: if anything fails (no device, package not debuggable,
54
+ * snapshot file missing for that adapter, bridge module unavailable),
55
+ * return the original options unchanged so the adapter's normal error
56
+ * path fires and the UI banner shows a meaningful message.
57
+ */
58
+ async function _tryAdbAutoPullInputPath(hub, name, options) {
59
+ if (
60
+ options &&
61
+ typeof options.inputPath === "string" &&
62
+ options.inputPath.length > 0
63
+ ) {
64
+ return options; // caller already supplied a path
65
+ }
66
+ // Skip auto-pull for adapters that have a live bridge mode — pulling
67
+ // a stale snapshot file would short-circuit the bridge path which
68
+ // yields fresher / richer data (e.g. system-data-android's bridge
69
+ // mode pulls contacts + apps + sms + call_log live via ADB, while
70
+ // an Android-collected snapshot only contains contacts + apps).
71
+ // Keep this list in sync with adapters whose _syncViaBridge is
72
+ // strictly richer than their snapshot output.
73
+ // browser-history-* and vscode read desktop-local files (browser History
74
+ // SQLite + Bookmarks JSON, or VS Code workspaceStorage + state.vscdb);
75
+ // ADB snapshot is meaningless for them.
76
+ const BRIDGE_PREFERRED = new Set([
77
+ "system-data-android",
78
+ "browser-history-chrome",
79
+ "browser-history-edge",
80
+ "vscode",
81
+ "win-recent",
82
+ "git-activity",
83
+ "shell-history",
84
+ "local-files",
85
+ ]);
86
+ if (BRIDGE_PREFERRED.has(name)) {
87
+ return options || {};
88
+ }
89
+ try {
90
+ const { createHostAdbBridge } =
91
+ await import("../../lib/host-adb-bridge.js");
92
+ const bridge = createHostAdbBridge();
93
+ const content = await bridge.invoke("snapshot.read", {
94
+ fileName: `${name}.json`,
95
+ });
96
+ // Sanity: must be parseable JSON. If not, the adapter would fail
97
+ // with a confusing parse error — surface a clearer one here.
98
+ try {
99
+ JSON.parse(content);
100
+ } catch (_e) {
101
+ return options; // leave to adapter — it'll report parse error
102
+ }
103
+ const stagingDir = join(hub.hubDir, "staging");
104
+ mkdirSync(stagingDir, { recursive: true });
105
+ const stagingPath = join(
106
+ stagingDir,
107
+ `${name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`,
108
+ );
109
+ writeFileSync(stagingPath, content, "utf-8");
110
+ return {
111
+ ...(options || {}),
112
+ inputPath: stagingPath,
113
+ _autoPulledViaAdb: true,
114
+ };
115
+ } catch (_e) {
116
+ // Bridge unavailable / device not attached / file missing — let the
117
+ // adapter throw its normal error so the banner explains.
118
+ return options || {};
119
+ }
120
+ }
121
+
31
122
  async function withHub(fn) {
32
123
  try {
33
124
  const hub = await getHub();
@@ -83,10 +174,26 @@ export const PERSONAL_DATA_HUB_HANDLERS = {
83
174
  withHub((hub) => hub.registry.list()),
84
175
 
85
176
  "personal-data-hub.sync-adapter": async (msg) =>
86
- withHub(
87
- async (hub) =>
88
- await hub.registry.syncAdapter(msg.name, msg.options || {}),
89
- ),
177
+ withHub(async (hub) => {
178
+ const options = await _tryAdbAutoPullInputPath(
179
+ hub,
180
+ msg.name,
181
+ msg.options,
182
+ );
183
+ try {
184
+ return await hub.registry.syncAdapter(msg.name, options);
185
+ } finally {
186
+ // Best-effort cleanup of staging file we just wrote (don't shadow
187
+ // a real adapter error with cleanup noise).
188
+ if (options && options._autoPulledViaAdb && options.inputPath) {
189
+ try {
190
+ if (existsSync(options.inputPath)) unlinkSync(options.inputPath);
191
+ } catch (_e) {
192
+ /* ignore */
193
+ }
194
+ }
195
+ }
196
+ }),
90
197
 
91
198
  "personal-data-hub.sync-all": async (msg) =>
92
199
  withHub(async (hub) => await hub.registry.syncAll(msg.options || {})),
@@ -116,6 +223,36 @@ export const PERSONAL_DATA_HUB_HANDLERS = {
116
223
  }),
117
224
  ),
118
225
 
226
+ // Phase 16 Vault Browser — full-text + faceted search over events.
227
+ // See packages/personal-data-hub/lib/vault.js#searchEvents for the
228
+ // query/result shape. The desktop browser view + Android "我的数据" tab
229
+ // both consume this topic.
230
+ "personal-data-hub.search-events": async (msg) =>
231
+ withHub((hub) =>
232
+ hub.vault.searchEvents({
233
+ q: msg.q,
234
+ adapter: msg.adapter,
235
+ category: msg.category,
236
+ subtype: msg.subtype,
237
+ since: msg.since,
238
+ until: msg.until,
239
+ cursor: msg.cursor,
240
+ limit: msg.limit,
241
+ }),
242
+ ),
243
+
244
+ // Phase 16 Vault Browser — counts grouped by category/adapter/subtype,
245
+ // honoring the same q + since/until filters as search-events. Powers
246
+ // the sidebar badges + adapter chip counts in the browser UI.
247
+ "personal-data-hub.facet-counts": async (msg) =>
248
+ withHub((hub) =>
249
+ hub.vault.facetCounts({
250
+ q: msg.q,
251
+ since: msg.since,
252
+ until: msg.until,
253
+ }),
254
+ ),
255
+
119
256
  "personal-data-hub.recent-audit": async (msg) =>
120
257
  withHub((hub) =>
121
258
  hub.vault.queryAudit({
@@ -379,14 +516,19 @@ export const PERSONAL_DATA_HUB_STREAMING_HANDLERS = {
379
516
  } catch (_e) {}
380
517
  }
381
518
  };
519
+ const options = await _tryAdbAutoPullInputPath(hub, msg.name, msg.options);
382
520
  try {
383
- const report = await hub.registry.syncAdapter(
384
- msg.name,
385
- msg.options || {},
386
- );
521
+ const report = await hub.registry.syncAdapter(msg.name, options);
387
522
  return { result: report };
388
523
  } finally {
389
524
  hub.registry.onSyncEvent = original;
525
+ if (options && options._autoPulledViaAdb && options.inputPath) {
526
+ try {
527
+ if (existsSync(options.inputPath)) unlinkSync(options.inputPath);
528
+ } catch (_e) {
529
+ /* ignore */
530
+ }
531
+ }
390
532
  }
391
533
  },
392
534
 
@@ -0,0 +1,430 @@
1
+ /**
2
+ * host-adb-bridge — host-side `bridgeProvider` for the
3
+ * system-data-android PDH adapter (ESM mirror of the CJS module at
4
+ * desktop-app-vue/src/main/personal-data-hub/desktop-adb-bridge.js).
5
+ *
6
+ * The packaged `cc-android-bridge.js` only works INSIDE the Android cc
7
+ * subprocess (it dials a localhost HTTP server the Android app exposes).
8
+ * That path requires the user to launch the in-APK cc, which most
9
+ * users never do. This shim implements the same `invoke(method,
10
+ * params) → Promise<result>` surface but runs commands via the system
11
+ * `adb` (developer-mode USB debugging) instead.
12
+ *
13
+ * Lives in packages/cli because both `cc serve` / `cc ui` AND the
14
+ * desktop web-shell embed of the CLI WS server use this CLI's wiring
15
+ * (personal-data-hub-wiring.js) — putting the bridge here means both
16
+ * paths get the auto-engage behavior. The desktop-app-vue CJS copy is
17
+ * still used for the V5/V6 IPC code path (separate hub singleton).
18
+ *
19
+ * Methods implemented (only what system-data-android consumes):
20
+ * - `contacts.query({since?})` →
21
+ * [{ lookupKey, displayName }, ...]
22
+ * - `app.list({includeSystem?})` →
23
+ * [{ packageName }, ...]
24
+ *
25
+ * Caveats (cross-checked against the sjqz reference at C:\code\sjqz):
26
+ * - No root required for the two methods above.
27
+ * - User must approve "USB 调试授权" on the phone the first time.
28
+ * - 0 or >1 devices attached → clear typed error (UI surfaces it via
29
+ * the new SyncReport.error rendering).
30
+ * - Windows CRLF trap: JS `$` does NOT match before `\r`, only
31
+ * `\n` / EOS — every parse routine strips `\r+$` first.
32
+ */
33
+
34
+ import { execFile } from "node:child_process";
35
+ import { promisify } from "node:util";
36
+
37
+ const execFileP = promisify(execFile);
38
+
39
+ export class HostAdbBridgeUnavailableError extends Error {
40
+ constructor(reason) {
41
+ super(`HOST_ADB_BRIDGE_NOT_AVAILABLE: ${reason}`);
42
+ this.code = "HOST_ADB_BRIDGE_NOT_AVAILABLE";
43
+ this.reason = reason;
44
+ }
45
+ }
46
+
47
+ async function adb(args, opts = {}) {
48
+ const adbPath = opts.adbPath || process.env.ADB_PATH || "adb";
49
+ const fullArgs = opts.serial ? ["-s", opts.serial, ...args] : args;
50
+ try {
51
+ const { stdout, stderr } = await execFileP(adbPath, fullArgs, {
52
+ timeout: opts.timeoutMs || 30_000,
53
+ maxBuffer: 32 * 1024 * 1024,
54
+ encoding: "utf8",
55
+ });
56
+ if (stderr && /error:|failed:|protocol fault/i.test(stderr)) {
57
+ throw new HostAdbBridgeUnavailableError(stderr.trim().split("\n")[0]);
58
+ }
59
+ return stdout;
60
+ } catch (e) {
61
+ if (e.code === "ENOENT") {
62
+ throw new HostAdbBridgeUnavailableError(
63
+ "adb binary not found on PATH (install Android Platform Tools, or set ADB_PATH)",
64
+ );
65
+ }
66
+ if (e instanceof HostAdbBridgeUnavailableError) {
67
+ throw e;
68
+ }
69
+ throw new HostAdbBridgeUnavailableError(e.message);
70
+ }
71
+ }
72
+
73
+ async function listDevices(opts = {}) {
74
+ const stdout = await adb(["devices"], opts);
75
+ const lines = stdout.split("\n").slice(1);
76
+ const serials = [];
77
+ for (const rawLine of lines) {
78
+ const line = rawLine.replace(/\r+$/, "").trim();
79
+ if (!line) continue;
80
+ const [serial, state] = line.split(/\s+/);
81
+ if (state === "device") serials.push(serial);
82
+ }
83
+ return serials;
84
+ }
85
+
86
+ async function pickDevice(opts = {}) {
87
+ if (opts.serial) return opts.serial;
88
+ if (process.env.ADB_SERIAL) return process.env.ADB_SERIAL;
89
+ const serials = await listDevices(opts);
90
+ if (serials.length === 0) {
91
+ throw new HostAdbBridgeUnavailableError(
92
+ "no Android device attached (enable USB debugging + plug in cable + approve 'USB 调试授权' on phone)",
93
+ );
94
+ }
95
+ if (serials.length > 1) {
96
+ throw new HostAdbBridgeUnavailableError(
97
+ `multiple devices attached (${serials.join(", ")}); set ADB_SERIAL=<serial> to disambiguate`,
98
+ );
99
+ }
100
+ return serials[0];
101
+ }
102
+
103
+ function parseContentQueryRows(stdout) {
104
+ const rows = [];
105
+ for (const rawLine of stdout.split("\n")) {
106
+ const line = rawLine.replace(/\r+$/, "");
107
+ const m = line.match(/^Row:\s+\d+\s+(.*)$/);
108
+ if (!m) continue;
109
+ const fields = {};
110
+ const fieldRe =
111
+ /([A-Za-z_][A-Za-z0-9_]*)=([^,]*?)(?=,\s+[A-Za-z_][A-Za-z0-9_]*=|$)/g;
112
+ let fm;
113
+ while ((fm = fieldRe.exec(m[1])) !== null) {
114
+ const key = fm[1];
115
+ const val = fm[2];
116
+ fields[key] = val === "NULL" ? null : val;
117
+ }
118
+ rows.push(fields);
119
+ }
120
+ return rows;
121
+ }
122
+
123
+ async function queryContacts(_params, opts) {
124
+ const serial = await pickDevice(opts);
125
+ const stdout = await adb(
126
+ [
127
+ "shell",
128
+ "content",
129
+ "query",
130
+ "--uri",
131
+ "content://com.android.contacts/contacts",
132
+ "--projection",
133
+ "lookup:display_name",
134
+ ],
135
+ { ...opts, serial },
136
+ );
137
+ const rows = parseContentQueryRows(stdout);
138
+ return rows
139
+ .map((r) => ({
140
+ lookupKey: r.lookup || null,
141
+ displayName: r.display_name || null,
142
+ }))
143
+ .filter((c) => c.displayName);
144
+ }
145
+
146
+ async function listApps(params, opts) {
147
+ const serial = await pickDevice(opts);
148
+ const includeSystem = params && params.includeSystem === true;
149
+ const flag = includeSystem ? "" : "-3";
150
+ const argv = ["shell", "pm", "list", "packages"];
151
+ if (flag) argv.push(flag);
152
+ const stdout = await adb(argv, { ...opts, serial });
153
+ const apps = [];
154
+ for (const rawLine of stdout.split("\n")) {
155
+ const line = rawLine.replace(/\r+$/, "");
156
+ const m = line.match(/^package:(.+)$/);
157
+ if (m) apps.push({ packageName: m[1].trim() });
158
+ }
159
+ return apps;
160
+ }
161
+
162
+ /**
163
+ * Query SMS via the system content provider. No root, no permissions
164
+ * declared by host — ADB shell user already has READ_SMS-equivalent
165
+ * access. Returns one row per message. The body can contain commas;
166
+ * parseContentQueryRows treats `, <ident>=` as the field boundary so
167
+ * naturally-written text doesn't break the parse (only adversarial
168
+ * SMS containing `, X=` would).
169
+ *
170
+ * @returns {Promise<Array<{id, address, body, date, dateSent, type, threadId, read, subject}>>}
171
+ */
172
+ async function querySms(params, opts) {
173
+ const serial = await pickDevice(opts);
174
+ const stdout = await adb(
175
+ ["shell", "content", "query", "--uri", "content://sms"],
176
+ { ...opts, serial, timeoutMs: opts.timeoutMs || 120_000 },
177
+ );
178
+ const rows = parseContentQueryRows(stdout);
179
+ return rows
180
+ .map((r) => ({
181
+ id: r._id ? String(r._id) : null,
182
+ address: r.address || null,
183
+ body: r.body || null,
184
+ date: r.date ? parseInt(r.date, 10) : null,
185
+ dateSent: r.date_sent ? parseInt(r.date_sent, 10) : null,
186
+ // SMS type: 1=inbox, 2=sent, 3=draft, 4=outbox, 5=failed, 6=queued
187
+ type: r.type ? parseInt(r.type, 10) : null,
188
+ threadId: r.thread_id ? parseInt(r.thread_id, 10) : null,
189
+ read: r.read === "1" ? true : r.read === "0" ? false : null,
190
+ subject: r.subject || null,
191
+ }))
192
+ .filter((m) => m.id); // drop rows with no _id (malformed)
193
+ }
194
+
195
+ /**
196
+ * Query call log via the system content provider. Same access model
197
+ * as SMS. Returns one row per call.
198
+ *
199
+ * @returns {Promise<Array<{id, number, name, duration, date, type, geocoded}>>}
200
+ */
201
+ async function queryCallLog(params, opts) {
202
+ const serial = await pickDevice(opts);
203
+ const stdout = await adb(
204
+ ["shell", "content", "query", "--uri", "content://call_log/calls"],
205
+ { ...opts, serial, timeoutMs: opts.timeoutMs || 120_000 },
206
+ );
207
+ const rows = parseContentQueryRows(stdout);
208
+ return rows
209
+ .map((r) => ({
210
+ id: r._id ? String(r._id) : null,
211
+ number: r.number || null,
212
+ name: r.name || null,
213
+ // Call duration in seconds
214
+ duration: r.duration ? parseInt(r.duration, 10) : null,
215
+ date: r.date ? parseInt(r.date, 10) : null,
216
+ // Call type: 1=incoming, 2=outgoing, 3=missed, 4=voicemail, 5=rejected, 6=blocked
217
+ type: r.type ? parseInt(r.type, 10) : null,
218
+ geocoded: r.geocoded_location || null,
219
+ }))
220
+ .filter((c) => c.id);
221
+ }
222
+
223
+ /**
224
+ * Lists media files in well-known /sdcard subdirectories. Returns metadata
225
+ * only — file CONTENT is never read off the device through this method.
226
+ * Pure ADB shell, no root, no permissions declared (adb shell user already
227
+ * has READ_EXTERNAL_STORAGE-equivalent access).
228
+ *
229
+ * Category → directory mapping:
230
+ * photos /sdcard/DCIM/Camera
231
+ * pictures /sdcard/Pictures (screenshots, downloaded images, etc.)
232
+ * videos /sdcard/Movies
233
+ * downloads /sdcard/Download
234
+ * documents /sdcard/Documents
235
+ *
236
+ * Uses `find -printf "%s\t%T@\t%p\n"` which is supported by Android toybox.
237
+ * One subprocess per category — concurrent isn't worth it; even ~5000-file
238
+ * Pictures listings finish in <2s.
239
+ *
240
+ * @param {{category: string, since?: number}} params
241
+ * - category: one of the keys above (required)
242
+ * - since: epoch-ms; files with mtime < since are skipped
243
+ * @returns {Promise<Array<{path, size, mtimeMs, ext, category}>>}
244
+ */
245
+ const MEDIA_DIRS = {
246
+ photos: "/sdcard/DCIM/Camera",
247
+ pictures: "/sdcard/Pictures",
248
+ videos: "/sdcard/Movies",
249
+ downloads: "/sdcard/Download",
250
+ documents: "/sdcard/Documents",
251
+ };
252
+ async function listMedia(params, opts) {
253
+ const cat = params && params.category;
254
+ const dir = MEDIA_DIRS[cat];
255
+ if (!dir) {
256
+ throw new HostAdbBridgeUnavailableError(
257
+ `media.list: unknown category "${cat}". Valid: ${Object.keys(MEDIA_DIRS).join(", ")}`,
258
+ );
259
+ }
260
+ const serial = await pickDevice(opts);
261
+ const stdout = await adb(
262
+ [
263
+ "shell",
264
+ // Suppress "Permission denied" stderr noise on a few system-protected
265
+ // subdirs (e.g. .trashed). 2>/dev/null hides those rows from output.
266
+ `find ${dir} -type f -printf '%s\\t%T@\\t%p\\n' 2>/dev/null`,
267
+ ],
268
+ { ...opts, serial, timeoutMs: opts.timeoutMs || 180_000 },
269
+ );
270
+ const sinceMs = Number.isInteger(params?.since) ? params.since : 0;
271
+ const out = [];
272
+ for (const rawLine of stdout.split("\n")) {
273
+ const line = rawLine.replace(/\r+$/, "");
274
+ if (!line) continue;
275
+ // tab-separated: <size>\t<mtime_epoch_fractional>\t<path>
276
+ const tab1 = line.indexOf("\t");
277
+ const tab2 = line.indexOf("\t", tab1 + 1);
278
+ if (tab1 < 0 || tab2 < 0) continue;
279
+ const size = parseInt(line.substring(0, tab1), 10);
280
+ const mtimeSec = parseFloat(line.substring(tab1 + 1, tab2));
281
+ const path = line.substring(tab2 + 1);
282
+ if (!Number.isFinite(size) || !Number.isFinite(mtimeSec) || !path) continue;
283
+ // Filter out hidden / system files — any path segment starts with "."
284
+ // catches .thumbnails/, .trashed-*/, .nomedia, etc.
285
+ if (path.split("/").some((seg) => seg.startsWith("."))) continue;
286
+ const mtimeMs = Math.floor(mtimeSec * 1000);
287
+ if (sinceMs > 0 && mtimeMs < sinceMs) continue;
288
+ const lastDot = path.lastIndexOf(".");
289
+ const ext = lastDot >= 0 ? path.substring(lastDot + 1).toLowerCase() : "";
290
+ out.push({ path, size, mtimeMs, ext, category: cat });
291
+ }
292
+ return out;
293
+ }
294
+
295
+ /**
296
+ * List snapshot JSON files in the Android app's staging directory.
297
+ * Uses `adb shell run-as` so only works on debuggable builds (which is
298
+ * always true for `<pkg>.debug` variants). Production builds will
299
+ * surface `package not debuggable` — UI can fall back to a manual
300
+ * upload path.
301
+ *
302
+ * @param {{packageName?: string}} params
303
+ * - packageName: defaults to `com.chainlesschain.android.debug`. The
304
+ * release variant `com.chainlesschain.android` is NOT readable via
305
+ * run-as.
306
+ * @returns {Promise<Array<{name, sizeBytes}>>}
307
+ */
308
+ async function listSnapshots(params, opts) {
309
+ const serial = await pickDevice(opts);
310
+ const pkg =
311
+ (params && params.packageName) || "com.chainlesschain.android.debug";
312
+ const stdout = await adb(
313
+ ["shell", "run-as", pkg, "ls", "-la", "files/.chainlesschain/staging/"],
314
+ { ...opts, serial },
315
+ );
316
+ if (/run-as: package not debuggable/.test(stdout)) {
317
+ throw new HostAdbBridgeUnavailableError(
318
+ `${pkg} is not debuggable — only debug-variant packages expose staging via run-as`,
319
+ );
320
+ }
321
+ const out = [];
322
+ for (const rawLine of stdout.split("\n")) {
323
+ const line = rawLine.replace(/\r+$/, "").trim();
324
+ if (!line || line.startsWith("total ") || line.startsWith("d")) continue;
325
+ // ls -la row: `-rw------- 1 u0_a395 u0_a395 204110 2026-05-23 13:47 system-data-android.json`
326
+ const m = line.match(
327
+ /^[-l]\S*\s+\d+\s+\S+\s+\S+\s+(\d+)\s+\S+\s+\S+\s+(.+\.json)$/,
328
+ );
329
+ if (m) out.push({ name: m[2], sizeBytes: parseInt(m[1], 10) });
330
+ }
331
+ return out;
332
+ }
333
+
334
+ /**
335
+ * Read a snapshot file's content from the Android app's staging directory.
336
+ * Returns the raw UTF-8 text (the JSON the adapter's _syncViaSnapshot
337
+ * expects). Same debuggable-only constraint as listSnapshots.
338
+ *
339
+ * @param {{packageName?: string, fileName: string}} params
340
+ * @returns {Promise<string>}
341
+ */
342
+ async function readSnapshot(params, opts) {
343
+ if (
344
+ !params ||
345
+ typeof params.fileName !== "string" ||
346
+ !params.fileName.endsWith(".json")
347
+ ) {
348
+ throw new HostAdbBridgeUnavailableError(
349
+ "readSnapshot: params.fileName must be a .json filename inside the staging dir",
350
+ );
351
+ }
352
+ // Defense-in-depth: reject path traversal — fileName must be a bare
353
+ // basename, no slashes / dots-leading.
354
+ if (params.fileName.includes("/") || params.fileName.startsWith(".")) {
355
+ throw new HostAdbBridgeUnavailableError(
356
+ `readSnapshot: refusing suspicious fileName "${params.fileName}"`,
357
+ );
358
+ }
359
+ const serial = await pickDevice(opts);
360
+ const pkg = params.packageName || "com.chainlesschain.android.debug";
361
+ const stdout = await adb(
362
+ [
363
+ "shell",
364
+ "run-as",
365
+ pkg,
366
+ "cat",
367
+ `files/.chainlesschain/staging/${params.fileName}`,
368
+ ],
369
+ { ...opts, serial, timeoutMs: opts.timeoutMs || 60_000 },
370
+ );
371
+ if (/run-as: package not debuggable/.test(stdout)) {
372
+ throw new HostAdbBridgeUnavailableError(
373
+ `${pkg} is not debuggable — only debug variant exposes staging via run-as`,
374
+ );
375
+ }
376
+ if (/No such file or directory/.test(stdout)) {
377
+ throw new HostAdbBridgeUnavailableError(
378
+ `snapshot ${params.fileName} not found in ${pkg}'s staging dir (collector probably never ran for this adapter)`,
379
+ );
380
+ }
381
+ return stdout;
382
+ }
383
+
384
+ export function createHostAdbBridge(opts = {}) {
385
+ return {
386
+ /**
387
+ * SystemDataAndroidAdapter._bridgeAvailable() reads this; must be
388
+ * sync. We report available:true optimistically — invoke() will
389
+ * throw a typed error if ADB / device isn't usable, which the
390
+ * registry surfaces via SyncReport.error (rendered by
391
+ * PersonalDataHub.vue syncSummary).
392
+ */
393
+ caps() {
394
+ return { available: true };
395
+ },
396
+ async invoke(method, params = {}) {
397
+ switch (method) {
398
+ case "contacts.query":
399
+ return await queryContacts(params, opts);
400
+ case "app.list":
401
+ return await listApps(params, opts);
402
+ case "sms.query":
403
+ return await querySms(params, opts);
404
+ case "call.query":
405
+ return await queryCallLog(params, opts);
406
+ case "media.list":
407
+ return await listMedia(params, opts);
408
+ case "snapshot.list":
409
+ return await listSnapshots(params, opts);
410
+ case "snapshot.read":
411
+ return await readSnapshot(params, opts);
412
+ default:
413
+ throw new HostAdbBridgeUnavailableError(
414
+ `method "${method}" not implemented by host-adb-bridge`,
415
+ );
416
+ }
417
+ },
418
+ };
419
+ }
420
+
421
+ // Exposed for unit testing without spawning real adb.
422
+ export const _internals = {
423
+ parseContentQueryRows,
424
+ listDevices,
425
+ pickDevice,
426
+ queryContacts,
427
+ listApps,
428
+ listSnapshots,
429
+ readSnapshot,
430
+ };