chainlesschain 0.162.17 → 0.162.19

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-VbMLpnCu.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-Tw6tLfY1.js → ActionButton-BR6BrZ2w.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Des59eT3.js → Analytics-DWN2iHDA.js} +2 -2
  6. package/src/assets/web-panel/assets/AppLayout-CyBXOO_H.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-Cu5a4b5S.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-ByvQLRhY.js → Backup-D0vrJYkS.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DC-h8Hd-.js → BaseInput-DnFk3wDi.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-D9dJ3NKb.js → Chat-DFMa9lEz.js} +3 -3
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-CO908iBt.css +1 -0
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-CpVazmEP.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-n3tD-95B.js → Checkbox-J23wKgNR.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BV9t0CmU.js → Codegen-Ca6Z4r1p.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-4KhPnXFB.js → Col-BZ3XsxN2.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-R1hZqHEr.js → Community-DhK5H5dD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-Bo4r1Z3L.js → Compact-DjJMyddV.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Dwywk8U2.js → Compliance-Bup3Rk9z.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DTBwVdcR.js → Cowork-BqsPMIpo.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-BrveD2Al.js → Cron-Ca69LFf-.js} +1 -1
  22. package/src/assets/web-panel/assets/{Crosschain-C63oZCeg.js → Crosschain-mTB21YwK.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-Cyfo7zh4.js → DID-C1_Eree4.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-_EOI1Ax4.js → Dashboard-L2OWv5io.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BWYHGHO3.js → Dropdown-CpqNwurh.js} +1 -1
  26. package/src/assets/web-panel/assets/EmailListRenderer-CVJ1j3Ll.css +1 -0
  27. package/src/assets/web-panel/assets/EmailListRenderer-Mo7D9ADF.js +1 -0
  28. package/src/assets/web-panel/assets/{Federation-CU03AB2d.js → Federation-COTeKl2R.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DECACp9C.js → FormItemContext-0oTTQ9MI.js} +1 -1
  30. package/src/assets/web-panel/assets/GenericCardRenderer-DrMs3upM.js +1 -0
  31. package/src/assets/web-panel/assets/GenericCardRenderer-DtJFcJYl.css +1 -0
  32. package/src/assets/web-panel/assets/{Git-BhpPR-ck.js → Git-C6eFUBhz.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-BtpmvOHi.js → Governance-5ivL10kw.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-DfPuEW02.js → Inference-DcERJUS_.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-CeA_2NFe.js → KnowledgeGraph-Dm1eQ_Hv.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-C_ozF-bn.js → Logs-Cjt-JTuL.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-WgBvu6Ma.js → Marketplace-fNDRN8YJ.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-CNIz8liM.js → McpTools-kKgzl9Bb.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-DEN8NgRg.js → Memory-BdJujXCH.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CP0rj-Fx.js → MobileBridge-lmvJfnpK.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-x3zDbPDI.js → MobileProjects-ZM-DVPhi.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CC4PEFJV.js → Mtc-CeoaWgzU.js} +1 -1
  43. package/src/assets/web-panel/assets/{MtcAudit-BHB0GvyW.js → MtcAudit-CRvKTLqr.js} +1 -1
  44. package/src/assets/web-panel/assets/{Multisig-BUPfLbde.js → Multisig-DnzFw69O.js} +2 -2
  45. package/src/assets/web-panel/assets/{NLProgramming-DNOkByM8.js → NLProgramming-0xP2rtGN.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-U-RIv-kE.js → Notes-tJpp3z8e.js} +1 -1
  47. package/src/assets/web-panel/assets/{NotificationSettings-B-q6tsxf.js → NotificationSettings-CRdpEoHX.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-BEH9lAIb.js +1 -0
  49. package/src/assets/web-panel/assets/OrderTableRenderer-BnOISpKI.css +1 -0
  50. package/src/assets/web-panel/assets/{Organization-DJLNuZ2_.js → Organization-Dhwy6dXT.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-BxTl0aeE.js → Overflow-COwwTMb-.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-Czzalu_0.js → P2P-79YWb-cn.js} +1 -1
  53. package/src/assets/web-panel/assets/PdhVaultBrowser-B9ZGFpn4.css +1 -0
  54. package/src/assets/web-panel/assets/PdhVaultBrowser-Dhn5S3Vs.js +7 -0
  55. package/src/assets/web-panel/assets/{Permissions-BBw06EGK.js → Permissions-ELRvDOlY.js} +3 -3
  56. package/src/assets/web-panel/assets/{PersonalDataHub-Dvaa8niQ.css → PersonalDataHub-D0ncF92t.css} +1 -1
  57. package/src/assets/web-panel/assets/PersonalDataHub-DIWbfS5k.js +1 -0
  58. package/src/assets/web-panel/assets/{Pipeline-qLLWNe2i.js → Pipeline-Dzi1zmAr.js} +1 -1
  59. package/src/assets/web-panel/assets/Privacy-BYzWkaRt.js +1 -0
  60. package/src/assets/web-panel/assets/{ProjectInit-CmJ0VPGy.js → ProjectInit-D_5DqLSS.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-D1Xm2EwO.js → ProjectSettings-DrAAYJgR.js} +1 -1
  62. package/src/assets/web-panel/assets/{Projects-DLN-awqS.js → Projects-BOrF4_X8.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-D164DGWo.js → Providers-DYLWZVe4.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-BRCH7WgH.js → QuickAsk-k76cZTaF.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-BWZhT_-2.js → Recommend-CXM4p6RB.js} +1 -1
  66. package/src/assets/web-panel/assets/Reputation-DLnLpz8L.js +1 -0
  67. package/src/assets/web-panel/assets/{Row-BbuTB-ny.js → Row-BavvTUrL.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-CLegyrrb.js → RssFeed-CtCarC8_.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-Bz41pkvW.js → Search-CJcDGc1x.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-D4NWRpgV.js → Security-DB3GmnY9.js} +3 -3
  71. package/src/assets/web-panel/assets/{Services-CP_6SXST.js → Services-BWEvVHhz.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-CdRO3MnV.js → Skeleton-BHbixeGV.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-BmghgIL3.js → Skills-DWwIjvjl.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-DjAIXxkm.js → Sla-C1Qu9o0j.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-DYk-En4R.js → SpeechSettings-BiEysZg1.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CglX98G4.js → SyncSettings-XlOLFUv2.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tasks-BcLMTSP3.js → Tasks-BKjOcYxW.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-BCs5bP8P.js → Templates-BVKjc9j-.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-B5jLtdWR.js → Tenant-C33WmSiJ.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-CZKKYYaA.js → Terminal-Dl8RJZHg.js} +1 -1
  81. package/src/assets/web-panel/assets/TimelineRenderer-CFN36N3H.js +1 -0
  82. package/src/assets/web-panel/assets/TimelineRenderer-DmztdsbF.css +1 -0
  83. package/src/assets/web-panel/assets/{Tokens-B8aHonMu.js → Tokens-Dy2kxiCA.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-D6jDjaX1.js → Trigger-C7bt0leJ.js} +1 -1
  85. package/src/assets/web-panel/assets/Trust-CVKbWEMH.js +1 -0
  86. package/src/assets/web-panel/assets/{UkeySign-B1IDXsz6.js → UkeySign-B91QGwbs.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-DHvglMzO.js → VideoEditing-C8xBqRiE.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-DAUh8j_b.js → Wallet-RW2IBbpH.js} +1 -1
  89. package/src/assets/web-panel/assets/{WebAuthn-D0Y8wGZt.js → WebAuthn-CZPv5ayn.js} +4 -4
  90. package/src/assets/web-panel/assets/{WorkflowEditor-BqZgQo4U.js → WorkflowEditor-CFCwic4a.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-CtfIYzWq.js → chat-PVfHUaHj.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-B6ZfKKgZ.js → colors-C4WA0-Iq.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item--HQOl9D1.js → compact-item-DQfLwjbC.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-Bf-YNOPI.js → createContext-31PSlu0j.js} +1 -1
  95. package/src/assets/web-panel/assets/{hasIn-D5G7UiYx.js → hasIn-Csltfg47.js} +1 -1
  96. package/src/assets/web-panel/assets/icons-AILO5ZcK.js +57 -0
  97. package/src/assets/web-panel/assets/index-3NsH3wNq.js +1 -0
  98. package/src/assets/web-panel/assets/{index-_ZD6hPvh.js → index-41v9Hflm.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-B23hk7ZY.js → index-45Iyof95.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CcbuseEC.js → index-8IVdEMKr.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CVTgMzbI.js → index-B-aRUTIJ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-OSlN9c4x.js → index-BCxhSOsr.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BrJfwd9_.js → index-BCyTEqF7.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CAPj11l4.js → index-BD-K1g5z.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DJlTZ7oK.js → index-BLwrQAlK.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-ByjAzHrC.js → index-Ba6cQs-y.js} +4 -4
  107. package/src/assets/web-panel/assets/{index-__qol0SM.js → index-BiFRGYGH.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DZyNnZb9.js → index-BqOmboiA.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-UppuQXbk.js → index-C4gzD6Ra.js} +2 -2
  110. package/src/assets/web-panel/assets/{index-2QuOKVo1.js → index-CArRkfYM.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BWOeC8v6.js → index-CE8i90GF.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B75QpKcS.js → index-CEbbRpw2.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BKsNidiT.js → index-CEtPU50W.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-g_d5tOWL.js → index-CIeFJLHG.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-ofgv0VHh.js → index-CQWE2WKS.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BRPGyig2.js → index-C_k_btzK.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-xOVop383.js → index-CbpUjcEf.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DIYydwQc.js → index-CdQUx-5Z.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CQIzU6GO.js → index-CfWk9Yzf.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CihsW323.js → index-Cgt2_bmM.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CkoQfJv0.js → index-CmzuBE2G.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BPvA_8ed.js → index-CpXm8DBk.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BtjPgDzA.js → index-DSO9eU_G.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CmVrKala.js → index-DUFwsCsD.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Dfm65OiJ.js → index-DVHR_To_.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CGG5O6Zh.js → index-DW-7sqm0.js} +1 -1
  127. package/src/assets/web-panel/assets/index-DWtc_qRj.js +1 -0
  128. package/src/assets/web-panel/assets/{index-BBSGKZ-l.js → index-DdrYhK__.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-XwovMqXr.js → index-DfbOwhkA.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-OY_eegoA.js → index-DpEj71nk.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Ca_5Za9D.js → index-DtUcq8Nn.js} +1 -1
  132. package/src/assets/web-panel/assets/{index--KnB-1F0.js → index-DuZKKJDi.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BOzNKGtD.js → index-Dymm64KM.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DzvrUpDp.js → index-P9rJg2C1.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DqJoAOhC.js → index-nAhW-NN4.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-DjUNw-1y.js → initDefaultProps-DPk1oFCk.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-Cd6xXLlf.js → motion-CzQ1k8MU.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-B_W-Y6JL.js → move-BCExaQ0x.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-BCZBLb_-.js → omit-D71nF5o_.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-CjDhGYLm.js → pickAttrs-pvCq8tx4.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-CSC2rU9-.js → placementArrow-D8lSLg_0.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-DnyswFkD.js → responsiveObserve-ZN6pVHvM.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-DujNhbuH.js → slide-VHxyZhFr.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-U2lNCQiL.js → statusUtils-B0jFTTFP.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-Ch2rQftE.js → styleChecker-Bc6FRYW9.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-5XxNwjTL.js → useFlexGapSupport-D4YDiqxr.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-pv2YfF-X.js → useFs-CQgQ7iah.js} +1 -1
  148. package/src/assets/web-panel/assets/usePersonalDataHub-LDh_Sq7B.js +1 -0
  149. package/src/assets/web-panel/assets/{vnode-CU0J2mVX.js → vnode-BwPvBDfI.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-ZII_iqXl.js → zoom-r6u1_Sq8.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
@@ -25,6 +25,34 @@ function printJson(obj) {
25
25
  console.log(JSON.stringify(obj, null, 2));
26
26
  }
27
27
 
28
+ /**
29
+ * Drain-then-exit. console.log → process.stdout.write is async for pipes;
30
+ * exiting immediately can truncate output to the parent. Use this in
31
+ * commands that must terminate even when getHub() left libuv handles
32
+ * registered (aichat-health setInterval, sidecar supervisor, etc.) — sync
33
+ * + read commands are all-done after the report, no reason to keep idling.
34
+ * Real-device repro on Xiaomi 24115RA8EC 2026-05-23.
35
+ */
36
+ function jsonAndExit(obj) {
37
+ process.stdout.write(JSON.stringify(obj, null, 2) + "\n", () =>
38
+ process.exit(0),
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Coerce a Commander string option to a positive integer, returning null if
44
+ * unset / blank / non-numeric / ≤0. Used by `--max-facts` / `--max-query-limit`
45
+ * on `cc hub ask` so on-device callers can pass smaller budgets without
46
+ * silent fallback to constructor defaults on a typo (`--max-facts abc`
47
+ * should not silently use 80).
48
+ */
49
+ function parsePositiveInt(raw) {
50
+ if (raw === undefined || raw === null || raw === "") return null;
51
+ const n = Number.parseInt(raw, 10);
52
+ if (!Number.isFinite(n) || n <= 0) return null;
53
+ return n;
54
+ }
55
+
28
56
  function fail(spinner, err, asJson) {
29
57
  if (spinner) spinner.stop();
30
58
  const msg = err && err.message ? err.message : String(err);
@@ -41,7 +69,9 @@ function fail(spinner, err, asJson) {
41
69
  async function cmdAsk(question, options) {
42
70
  const spinner = options.json ? null : ora("Asking hub...").start();
43
71
  try {
44
- const hub = await getHub();
72
+ // _getHub injection mirrors other handlers — lets unit tests stub the
73
+ // engine without standing up a real vault.
74
+ const hub = await (options._getHub || getHub)();
45
75
  if (!hub.engine) throw new Error("Analysis engine unavailable");
46
76
  // 推文 §三道锁第二把 "默认不许问云端" — acceptNonLocal 默认 false (拒云)。
47
77
  // 优先 --accept-non-local CLI 旗,其次 env CC_HUB_ALLOW_NON_LOCAL (Android UI
@@ -50,10 +80,19 @@ async function cmdAsk(question, options) {
50
80
  process.env.CC_HUB_ALLOW_NON_LOCAL === "1" ||
51
81
  process.env.CC_HUB_ALLOW_NON_LOCAL === "true";
52
82
  const acceptNonLocal = !!options.acceptNonLocal || envAllow;
53
- const result = await hub.engine.ask(question, {
83
+ // Per-call budget overrides for small-model callers (Android Qwen2.5-1.5B
84
+ // passes --max-facts 20 --max-query-limit 50 to keep prompt ~1.5K tokens
85
+ // and stay under the model's effective instruction-following window).
86
+ // Commander parses --max-facts <n> as string — coerce + drop invalid.
87
+ const maxFacts = parsePositiveInt(options.maxFacts);
88
+ const maxQueryLimit = parsePositiveInt(options.maxQueryLimit);
89
+ const askOptions = {
54
90
  useRag: options.useRag !== false,
55
91
  acceptNonLocal,
56
- });
92
+ };
93
+ if (maxFacts !== null) askOptions.maxFacts = maxFacts;
94
+ if (maxQueryLimit !== null) askOptions.maxQueryLimit = maxQueryLimit;
95
+ const result = await hub.engine.ask(question, askOptions);
57
96
  if (spinner) spinner.stop();
58
97
  if (options.json) {
59
98
  printJson(result);
@@ -203,13 +242,34 @@ async function cmdSyncAdapter(name, options) {
203
242
  if (options.input) opts.inputPath = String(options.input);
204
243
  const report = await hub.registry.syncAdapter(name, opts);
205
244
  if (spinner) spinner.succeed(`synced ${name}`);
245
+ // 2026-05-24 in-APK Android exit + flush-race fix. Real-device repro on
246
+ // Xiaomi 24115RA8EC:
247
+ //
248
+ // (1) cc subprocess sits idle in epoll_wait for the full 240s budget
249
+ // after the report is fully written, then gets destroyForcibly'd
250
+ // by Kotlin LocalCcRunner → false "timeout after 240000ms" UI
251
+ // error even though vault writes committed. Root cause: a
252
+ // lingering libuv handle in getHub() wiring (suspect: aichat-
253
+ // health setInterval or sidecar supervisor health ping) keeps
254
+ // the event loop alive forever. Long-run fix is to close those
255
+ // cleanly; until then we force-exit here.
256
+ //
257
+ // (2) First-pass force-exit landed `process.exit(0)` after a normal
258
+ // `printJson(report) = console.log(...)`. But console.log →
259
+ // process.stdout.write is ASYNC for pipes (only sync for TTY),
260
+ // so process.exit fires before the JSON buffer drains → Kotlin
261
+ // parent reads truncated/empty stdout → "parse-failed" reason
262
+ // in CcResult.Failed → UI "写入本地数据库失败".
263
+ //
264
+ // Fix: explicit drain-then-exit (jsonAndExit helper at top of file).
206
265
  if (options.json) {
207
- printJson(report);
208
- } else {
209
- logger.log(
210
- `ingested=${report.ingested} kgTriples=${report.kgTriples} ragDocs=${report.ragDocs} durationMs=${report.durationMs}`,
211
- );
266
+ jsonAndExit(report);
267
+ return;
212
268
  }
269
+ logger.log(
270
+ `ingested=${report.ingested} kgTriples=${report.kgTriples} ragDocs=${report.ragDocs} durationMs=${report.durationMs}`,
271
+ );
272
+ process.exit(0);
213
273
  } catch (err) {
214
274
  fail(spinner, err, options.json);
215
275
  }
@@ -239,6 +299,46 @@ async function cmdSyncAll(options) {
239
299
  }
240
300
  }
241
301
 
302
+ // ─── rederive ────────────────────────────────────────────────────────
303
+
304
+ async function cmdRederive(options) {
305
+ const spinner = options.json
306
+ ? null
307
+ : ora(
308
+ options.adapter
309
+ ? `re-deriving ${options.adapter} from raw_events...`
310
+ : "re-deriving all adapters from raw_events...",
311
+ ).start();
312
+ try {
313
+ const hub = await getHub();
314
+ const opts = {};
315
+ if (options.adapter) opts.adapter = String(options.adapter);
316
+ if (options.batchSize) opts.batchSize = Number(options.batchSize);
317
+ const report = await hub.registry.rederive(opts);
318
+ if (spinner) {
319
+ spinner.succeed(
320
+ `re-derived ${report.rawSeen} raw → events:${report.entityCounts.events} ` +
321
+ `persons:${report.entityCounts.persons} items:${report.entityCounts.items} ` +
322
+ `(invalid:${report.invalidCount} missing:${report.adapterMissing} ` +
323
+ `${report.durationMs}ms)`,
324
+ );
325
+ }
326
+ if (options.json) {
327
+ jsonAndExit(report);
328
+ return;
329
+ }
330
+ if (report.errors.length > 0) {
331
+ logger.log(chalk.red(`errors: ${report.errors.length}`));
332
+ for (const e of report.errors) {
333
+ logger.log(` ${chalk.red(e.adapter)}: ${e.error}`);
334
+ }
335
+ }
336
+ process.exit(0);
337
+ } catch (err) {
338
+ fail(spinner, err, options.json);
339
+ }
340
+ }
341
+
242
342
  // ─── query-events / recent-audit ─────────────────────────────────────
243
343
 
244
344
  async function cmdQueryEvents(options) {
@@ -253,16 +353,93 @@ async function cmdQueryEvents(options) {
253
353
  if (options.limit) q.limit = Number(options.limit);
254
354
  const events = hub.vault.queryEvents(q);
255
355
  if (options.json) {
256
- printJson(events);
257
- } else {
258
- logger.log(`${events.length} events:`);
259
- for (const ev of events) {
260
- const at = new Date(ev.at).toISOString();
261
- logger.log(
262
- ` ${chalk.gray(at)} ${chalk.cyan(ev.subtype)} ${ev.summary || ev.id}`,
263
- );
264
- }
356
+ jsonAndExit(events);
357
+ return;
358
+ }
359
+ logger.log(`${events.length} events:`);
360
+ for (const ev of events) {
361
+ const at = new Date(ev.at).toISOString();
362
+ logger.log(
363
+ ` ${chalk.gray(at)} ${chalk.cyan(ev.subtype)} ${ev.summary || ev.id}`,
364
+ );
365
+ }
366
+ process.exit(0);
367
+ } catch (err) {
368
+ fail(null, err, options.json);
369
+ }
370
+ }
371
+
372
+ // ─── search / facet-counts (Phase 16 Vault Browser) ──────────────────
373
+ //
374
+ // Surfaces vault.searchEvents + vault.facetCounts for headless use
375
+ // (Android LocalCcRunner.searchEvents calls this; CI smoke too).
376
+ async function cmdSearchEvents(options) {
377
+ try {
378
+ const hub = await getHub();
379
+ const q = {};
380
+ if (options.q) q.q = String(options.q);
381
+ if (options.adapter) q.adapter = options.adapter;
382
+ if (options.category) q.category = options.category;
383
+ if (options.subtype) q.subtype = options.subtype;
384
+ if (options.since) q.since = Number(options.since);
385
+ if (options.until) q.until = Number(options.until);
386
+ if (options.limit) q.limit = Number(options.limit);
387
+ // cursor passed as `--cursor "<occurredAt>:<id>"` (cli-friendly)
388
+ if (options.cursor) {
389
+ const m = String(options.cursor).match(/^(\d+):(.+)$/);
390
+ if (m) q.cursor = { occurredAt: Number(m[1]), id: m[2] };
265
391
  }
392
+ const result = hub.vault.searchEvents(q);
393
+ if (options.json) {
394
+ jsonAndExit(result);
395
+ return;
396
+ }
397
+ logger.log(
398
+ `${result.rows.length} events (mode=${result.mode}${result.shortQuery ? " shortQuery!" : ""})` +
399
+ (result.nextCursor
400
+ ? ` nextCursor=${result.nextCursor.occurredAt}:${result.nextCursor.id}`
401
+ : ""),
402
+ );
403
+ for (const ev of result.rows) {
404
+ const at = new Date(ev.occurredAt).toISOString();
405
+ const summary =
406
+ (ev.content &&
407
+ (ev.content.text || ev.content.title || ev.content.subject)) ||
408
+ ev.id;
409
+ logger.log(
410
+ ` ${chalk.gray(at)} ${chalk.cyan(ev.source.adapter)} ${chalk.dim(ev.subtype)} ${summary}`,
411
+ );
412
+ }
413
+ process.exit(0);
414
+ } catch (err) {
415
+ fail(null, err, options.json);
416
+ }
417
+ }
418
+
419
+ async function cmdFacetCounts(options) {
420
+ try {
421
+ const hub = await getHub();
422
+ const q = {};
423
+ if (options.q) q.q = String(options.q);
424
+ if (options.since) q.since = Number(options.since);
425
+ if (options.until) q.until = Number(options.until);
426
+ const result = hub.vault.facetCounts(q);
427
+ if (options.json) {
428
+ jsonAndExit(result);
429
+ return;
430
+ }
431
+ logger.log(
432
+ `total=${result.total} mode=${result.mode}${result.shortQuery ? " shortQuery!" : ""}`,
433
+ );
434
+ logger.log(chalk.bold("by category:"));
435
+ for (const [k, n] of Object.entries(result.byCategory)) {
436
+ logger.log(` ${chalk.cyan(k.padEnd(10))} ${n}`);
437
+ }
438
+ logger.log(chalk.bold("by adapter:"));
439
+ for (const [k, n] of Object.entries(result.byAdapter)) {
440
+ logger.log(` ${chalk.cyan(k.padEnd(24))} ${n}`);
441
+ }
442
+ process.exit(0);
266
443
  } catch (err) {
267
444
  fail(null, err, options.json);
268
445
  }
@@ -277,16 +454,17 @@ async function cmdRecentAudit(options) {
277
454
  if (options.limit) q.limit = Number(options.limit);
278
455
  const rows = hub.vault.queryAudit(q);
279
456
  if (options.json) {
280
- printJson(rows);
281
- } else {
282
- logger.log(`${rows.length} audit rows:`);
283
- for (const r of rows) {
284
- const at = new Date(r.at).toISOString();
285
- logger.log(
286
- ` ${chalk.gray(at)} ${chalk.cyan(r.action)} ${r.adapter || ""} ${r.eventId || ""}`,
287
- );
288
- }
457
+ jsonAndExit(rows);
458
+ return;
289
459
  }
460
+ logger.log(`${rows.length} audit rows:`);
461
+ for (const r of rows) {
462
+ const at = new Date(r.at).toISOString();
463
+ logger.log(
464
+ ` ${chalk.gray(at)} ${chalk.cyan(r.action)} ${r.adapter || ""} ${r.eventId || ""}`,
465
+ );
466
+ }
467
+ process.exit(0);
290
468
  } catch (err) {
291
469
  fail(null, err, options.json);
292
470
  }
@@ -1044,6 +1222,14 @@ export function registerHubCommand(program) {
1044
1222
  "--accept-non-local",
1045
1223
  "Allow non-local LLM (sends data off device — required when provider is not Ollama/vLLM)",
1046
1224
  )
1225
+ .option(
1226
+ "--max-facts <n>",
1227
+ "Cap facts in prompt (default 80; on-device small models e.g. Qwen2.5-1.5B should pass 20)",
1228
+ )
1229
+ .option(
1230
+ "--max-query-limit <n>",
1231
+ "Cap vault queryEvents limit (default 200; small-model callers should pass 50)",
1232
+ )
1047
1233
  .option("--json", "Output JSON")
1048
1234
  .action(cmdAsk);
1049
1235
 
@@ -1087,6 +1273,24 @@ export function registerHubCommand(program) {
1087
1273
  .option("--json", "Output JSON")
1088
1274
  .action(cmdSyncAll);
1089
1275
 
1276
+ hub
1277
+ .command("rederive")
1278
+ .description(
1279
+ "Re-derive canonical events from raw_events without re-fetching " +
1280
+ "from source (recovers orphan raws from a past sync where putBatch " +
1281
+ "silently failed, e.g. trap #25 partial-index drift).",
1282
+ )
1283
+ .option(
1284
+ "--adapter <name>",
1285
+ "Filter to one adapter; default = all registered",
1286
+ )
1287
+ .option(
1288
+ "--batch-size <n>",
1289
+ "Raws per partitionBatch+putBatch tx (default 100)",
1290
+ )
1291
+ .option("--json", "Output JSON")
1292
+ .action(cmdRederive);
1293
+
1090
1294
  hub
1091
1295
  .command("query-events")
1092
1296
  .description("Query vault events with filters")
@@ -1099,6 +1303,34 @@ export function registerHubCommand(program) {
1099
1303
  .option("--json", "Output JSON")
1100
1304
  .action(cmdQueryEvents);
1101
1305
 
1306
+ hub
1307
+ .command("search")
1308
+ .description("Vault Browser search — events_fts (FTS5) + faceted filters")
1309
+ .option("--q <text>", "Keyword (FTS5 phrase match if mode=fts5)")
1310
+ .option("--adapter <name>", "Filter by exact adapter")
1311
+ .option(
1312
+ "--category <cat>",
1313
+ "Filter by category (chat/social/email/shopping/travel/system/ai-chat/other)",
1314
+ )
1315
+ .option("--subtype <t>", "Filter by event subtype")
1316
+ .option("--since <ms>", "Start of time window (unix-ms)")
1317
+ .option("--until <ms>", "End of time window (unix-ms)")
1318
+ .option("--cursor <occurredAt:id>", "Page cursor from previous response")
1319
+ .option("--limit <n>", "Page size (max 500)", "50")
1320
+ .option("--json", "Output JSON")
1321
+ .action(cmdSearchEvents);
1322
+
1323
+ hub
1324
+ .command("facet-counts")
1325
+ .description(
1326
+ "Sidebar / chip counts — events grouped by category / adapter / subtype",
1327
+ )
1328
+ .option("--q <text>", "Keyword filter (same semantics as `cc hub search`)")
1329
+ .option("--since <ms>", "Start of time window (unix-ms)")
1330
+ .option("--until <ms>", "End of time window (unix-ms)")
1331
+ .option("--json", "Output JSON")
1332
+ .action(cmdFacetCounts);
1333
+
1102
1334
  hub
1103
1335
  .command("recent-audit")
1104
1336
  .description("Recent audit log entries")
@@ -1270,6 +1502,8 @@ export function registerHubCommand(program) {
1270
1502
  // `_wizard` / `_factoryDeps` / `_knownVendors` injected, bypassing the
1271
1503
  // real `getHub()` call. The commander wiring above is the runtime path.
1272
1504
  export const _internal = {
1505
+ cmdAsk,
1506
+ parsePositiveInt,
1273
1507
  cmdAIChatList,
1274
1508
  cmdAIChatLogin,
1275
1509
  cmdAIChatProbe,
package/src/constants.js CHANGED
@@ -145,6 +145,13 @@ export const DEFAULT_CONFIG = {
145
145
  apiKey: null,
146
146
  baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
147
147
  model: "doubao-seed-1-6-251015",
148
+ // When true, `cc ask` (provider=ollama only) overrides baseUrl to point at
149
+ // the Android LocalLlmServer (127.0.0.1:18484). User opt-in toggle:
150
+ // cc config set llm.preferAndroidLocal true
151
+ // Resolution priority in ask.js:
152
+ // --base-url > CC_HUB_OLLAMA_URL env > --prefer-android-local flag >
153
+ // this config > config.llm.baseUrl > hardcoded localhost:11434
154
+ preferAndroidLocal: false,
148
155
  },
149
156
  enterprise: {
150
157
  serverUrl: null,
@@ -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