chainlesschain 0.162.14 → 0.162.16

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 (142) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-D34d_Nh1.js → AIOps-6yP2WySy.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Br7HxCnl.js → ActionButton-BvpIMxiU.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-bVKq79Xd.js → Analytics-Bvg88oOa.js} +1 -1
  5. package/src/assets/web-panel/assets/{AppLayout-CWSLIbAz.js → AppLayout-Do30UWbY.js} +2 -2
  6. package/src/assets/web-panel/assets/{Audit-Cmnu1qqa.js → Audit-Bfshrs5j.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-Rok20-TL.js → Backup-yKQbHMJr.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BJzs_ZtT.js → BaseInput-CxpV1ZXM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-CSYapbcq.js → Chat-y1SfGDl1.js} +1 -1
  10. package/src/assets/web-panel/assets/{Checkbox-BEa7Sr7e.js → Checkbox-Dpoth43k.js} +1 -1
  11. package/src/assets/web-panel/assets/{Codegen-C9M4e7ne.js → Codegen-DbwH-QDz.js} +1 -1
  12. package/src/assets/web-panel/assets/{Col-DU9NoUIi.js → Col-B-MGUxt2.js} +1 -1
  13. package/src/assets/web-panel/assets/{Community-DA9uz_jP.js → Community-B521TAcS.js} +1 -1
  14. package/src/assets/web-panel/assets/{Compact-3_bEraVw.js → Compact-CislP3fF.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compliance-BtF8jWUQ.js → Compliance-BXqrD-tO.js} +1 -1
  16. package/src/assets/web-panel/assets/{Cowork-BqvA7oaM.js → Cowork-DtRMjAz_.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cron-CxZy7Mzg.js → Cron-CX8dYcdW.js} +1 -1
  18. package/src/assets/web-panel/assets/{Crosschain-1DB-XRGu.js → Crosschain-DeEwabS0.js} +1 -1
  19. package/src/assets/web-panel/assets/{DID-B6Ezp1pt.js → DID-DjDE7pgB.js} +1 -1
  20. package/src/assets/web-panel/assets/{Dashboard-QDJ6VVsn.js → Dashboard-pr7dCgLq.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dropdown-CovsWjxG.js → Dropdown-DuDw9Nfe.js} +1 -1
  22. package/src/assets/web-panel/assets/{Federation-DbRxS4Y4.js → Federation-BiDyaRHe.js} +1 -1
  23. package/src/assets/web-panel/assets/{FormItemContext-9E9dNGtx.js → FormItemContext-BEF_PW-L.js} +1 -1
  24. package/src/assets/web-panel/assets/{Git-CqEpyxRZ.js → Git-DIbvoylw.js} +1 -1
  25. package/src/assets/web-panel/assets/{Governance-On47KtGq.js → Governance-FFY2QV_a.js} +1 -1
  26. package/src/assets/web-panel/assets/{Inference-RZcjcyaq.js → Inference-Clgxm2a1.js} +1 -1
  27. package/src/assets/web-panel/assets/{KnowledgeGraph-C-1rRAM9.js → KnowledgeGraph-CNmtI1QB.js} +1 -1
  28. package/src/assets/web-panel/assets/{Logs-BuunmG_r.js → Logs-ibszFmVA.js} +1 -1
  29. package/src/assets/web-panel/assets/{Marketplace-CromymyA.js → Marketplace-CeBhDY6f.js} +1 -1
  30. package/src/assets/web-panel/assets/{McpTools-5XlFExh7.js → McpTools-Bl4misFt.js} +1 -1
  31. package/src/assets/web-panel/assets/{Memory-DjnUT7YM.js → Memory-CTGi-Zcb.js} +1 -1
  32. package/src/assets/web-panel/assets/{MobileBridge-BrYIgLg6.js → MobileBridge-F7jJrt63.js} +1 -1
  33. package/src/assets/web-panel/assets/{MobileProjects-CL5V3fTm.js → MobileProjects--p2v4WUQ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Mtc-CHYJq6zK.js → Mtc-Cw0DfWPd.js} +1 -1
  35. package/src/assets/web-panel/assets/{MtcAudit-BZxUO0qt.js → MtcAudit-BNAv66VX.js} +1 -1
  36. package/src/assets/web-panel/assets/{Multisig-FZTmJgW1.js → Multisig-B4IxV-6K.js} +1 -1
  37. package/src/assets/web-panel/assets/{NLProgramming-C9Mhefph.js → NLProgramming-J5Nfy4jx.js} +1 -1
  38. package/src/assets/web-panel/assets/{Notes-W7usj-Ar.js → Notes-BQl5wafk.js} +1 -1
  39. package/src/assets/web-panel/assets/{NotificationSettings-PBuYv_Bh.js → NotificationSettings-1Sur2hZk.js} +1 -1
  40. package/src/assets/web-panel/assets/{Organization-CuYCE-rF.js → Organization-DSJGSd5V.js} +1 -1
  41. package/src/assets/web-panel/assets/{Overflow-Dojx-kzE.js → Overflow-6TgFQZGw.js} +1 -1
  42. package/src/assets/web-panel/assets/{P2P-BgIaSrLX.js → P2P-Cb-eWAUF.js} +1 -1
  43. package/src/assets/web-panel/assets/{Permissions-Byj2dkF_.js → Permissions-C_xekWqT.js} +1 -1
  44. package/src/assets/web-panel/assets/{PersonalDataHub-CMOOI13-.js → PersonalDataHub-C6X0ilxN.js} +1 -1
  45. package/src/assets/web-panel/assets/{Pipeline-CWwEOF09.js → Pipeline-CQlD8pLe.js} +1 -1
  46. package/src/assets/web-panel/assets/{Privacy-VT7gldcN.js → Privacy-DlTQcdGg.js} +1 -1
  47. package/src/assets/web-panel/assets/{ProjectInit-7UH3c3p7.js → ProjectInit-BicKKZtR.js} +1 -1
  48. package/src/assets/web-panel/assets/{ProjectSettings-DqLp-72a.js → ProjectSettings-CXTbWgIg.js} +1 -1
  49. package/src/assets/web-panel/assets/{Projects-B_54eDhH.js → Projects-Diq_IZJO.js} +1 -1
  50. package/src/assets/web-panel/assets/{Providers-BIrNfNpc.js → Providers-CKXjLBNU.js} +1 -1
  51. package/src/assets/web-panel/assets/{QuickAsk-BbYPwCso.js → QuickAsk-BnUavy-a.js} +1 -1
  52. package/src/assets/web-panel/assets/{Recommend-BF4qBssF.js → Recommend-qbDO-M0M.js} +1 -1
  53. package/src/assets/web-panel/assets/{Reputation-DPEzlC2V.js → Reputation-BnkI4I_D.js} +1 -1
  54. package/src/assets/web-panel/assets/{Row-DjHxhH1L.js → Row-sVpTpVTK.js} +1 -1
  55. package/src/assets/web-panel/assets/{RssFeed-D0_j678P.js → RssFeed-CN85L1Ph.js} +1 -1
  56. package/src/assets/web-panel/assets/{Search-DctfGehu.js → Search-Dxxlr3ob.js} +1 -1
  57. package/src/assets/web-panel/assets/{Security-BFHggeYM.js → Security-CVjWa1ad.js} +1 -1
  58. package/src/assets/web-panel/assets/{Services-CmrFMukV.js → Services-BPiPhH6q.js} +1 -1
  59. package/src/assets/web-panel/assets/{Skeleton-DR4vn_nS.js → Skeleton-BpJEB3Px.js} +1 -1
  60. package/src/assets/web-panel/assets/{Skills-DlXG2yyV.js → Skills-Cjiy1gsC.js} +1 -1
  61. package/src/assets/web-panel/assets/{Sla-4PPGL3SE.js → Sla-CW2YJRZw.js} +1 -1
  62. package/src/assets/web-panel/assets/{SpeechSettings-D9EhJOqm.js → SpeechSettings-DBGDc9cz.js} +1 -1
  63. package/src/assets/web-panel/assets/{SyncSettings-Dasmbi0p.js → SyncSettings-CWiQLmKL.js} +1 -1
  64. package/src/assets/web-panel/assets/{Tasks-vilEiuPA.js → Tasks-CVsvO2yt.js} +1 -1
  65. package/src/assets/web-panel/assets/{Templates-Ca9Rvktn.js → Templates-BEXmGilK.js} +1 -1
  66. package/src/assets/web-panel/assets/{Tenant-CEZb9gfK.js → Tenant-Dd_xNf2O.js} +1 -1
  67. package/src/assets/web-panel/assets/{Terminal-DanCBdbD.js → Terminal-JIkt-KLk.js} +1 -1
  68. package/src/assets/web-panel/assets/{Tokens-SPkClW2d.js → Tokens-Ca8aA-4X.js} +1 -1
  69. package/src/assets/web-panel/assets/{Trigger-B645yL7g.js → Trigger-CLDtLBuR.js} +1 -1
  70. package/src/assets/web-panel/assets/{Trust-D9sM_Ig0.js → Trust-DSzmH0eN.js} +1 -1
  71. package/src/assets/web-panel/assets/{UkeySign-B_Nr2K-u.js → UkeySign-D7yOYajn.js} +1 -1
  72. package/src/assets/web-panel/assets/{VideoEditing-U01Lea8j.js → VideoEditing-Dk3jUxyG.js} +1 -1
  73. package/src/assets/web-panel/assets/{Wallet-6xBySVV8.js → Wallet-BSB2tO_C.js} +1 -1
  74. package/src/assets/web-panel/assets/{WebAuthn-DbgMoBu6.js → WebAuthn-DAPZsZNw.js} +1 -1
  75. package/src/assets/web-panel/assets/{WorkflowEditor-Bz-Y6IR2.js → WorkflowEditor-DinHN1vM.js} +1 -1
  76. package/src/assets/web-panel/assets/{chat-BC_O9hag.js → chat-BLyN3kuS.js} +1 -1
  77. package/src/assets/web-panel/assets/{colors-ChlOGOvr.js → colors-B-IfN2x7.js} +1 -1
  78. package/src/assets/web-panel/assets/{compact-item-BSbAYGGF.js → compact-item-tVJNTa_f.js} +1 -1
  79. package/src/assets/web-panel/assets/{createContext-CFcZly5M.js → createContext-CeHSYhwo.js} +1 -1
  80. package/src/assets/web-panel/assets/{hasIn-BomYwwYE.js → hasIn-CP8akQuY.js} +1 -1
  81. package/src/assets/web-panel/assets/{index-QD_n54XT.js → index-3T9u2Hlb.js} +1 -1
  82. package/src/assets/web-panel/assets/{index-ozVPr1gj.js → index-B6RV21Hn.js} +1 -1
  83. package/src/assets/web-panel/assets/{index-CnxlKTDK.js → index-BAhYPqmm.js} +1 -1
  84. package/src/assets/web-panel/assets/{index-DNkth8dM.js → index-BDS9QmmD.js} +1 -1
  85. package/src/assets/web-panel/assets/{index-CoF95pYK.js → index-BRH7hf5q.js} +1 -1
  86. package/src/assets/web-panel/assets/{index-DsNQ2hqI.js → index-BZhvUfFb.js} +1 -1
  87. package/src/assets/web-panel/assets/{index-Ctx97mH-.js → index-B_pAhN3p.js} +1 -1
  88. package/src/assets/web-panel/assets/{index-BHGsFwYW.js → index-BsMpKD71.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-T5Y_9IPv.js → index-ByqjsQPb.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-D_0B3CiU.js → index-C44pVyK1.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BwFykZ5U.js → index-C6NyhfFI.js} +1 -1
  92. package/src/assets/web-panel/assets/index-C6dKiBgm.js +1 -0
  93. package/src/assets/web-panel/assets/{index-EY733h9z.js → index-COqTEK1x.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B6LJHQoE.js → index-CP5ax2Ox.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BZGdjNLA.js → index-CPdpqzTz.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DW-Ji07y.js → index-C_sA6V0-.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BEJ6YiLI.js → index-C_we-zLv.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-5Ewm6KZA.js → index-Cjg_lXkn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C0rr1X9W.js → index-CkMJbSJn.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BI1jAWcc.js → index-CmFox476.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Cmzh8gKL.js → index-CrxfAG6t.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BHi69MHF.js → index-CtgPPk47.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D0vX9jQA.js → index-CxRjf-uA.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CCRSz2cR.js → index-D0Lw2-GE.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-za1GUJBG.js → index-D338fQ78.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-slYX2rCE.js → index-D7xlQtKS.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CBhoZhCO.js → index-DISTBJJ4.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-b8GbH2Yi.js → index-DL2e2Kwh.js} +3 -3
  109. package/src/assets/web-panel/assets/{index-DXgE2VW6.js → index-DNpXi3KX.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DRp5_Xns.js → index-DVbSr5gk.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BYDvb1pi.js → index-DYEooXtQ.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BGUbtM3R.js → index-DaXMEgmQ.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Dbf5YmDX.js → index-DfzQT6CX.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-gUACAWbM.js → index-Dk_cKbpp.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-ByZQNO0A.js → index-pO_TKKcK.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-onW325hZ.js → index-q4cwusSB.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-t9u2bHpH.js → index-rdeSvPj9.js} +1 -1
  118. package/src/assets/web-panel/assets/index-vl01xWcc.js +1 -0
  119. package/src/assets/web-panel/assets/{index-Cj47XwJQ.js → index-xaqrvGuG.js} +1 -1
  120. package/src/assets/web-panel/assets/{initDefaultProps-DnadEaxu.js → initDefaultProps-Cw37W7y2.js} +1 -1
  121. package/src/assets/web-panel/assets/{motion-CC_Na0Tl.js → motion-DOWpjqMY.js} +1 -1
  122. package/src/assets/web-panel/assets/{move-C2d9Mkk9.js → move-Ha0Ha2IK.js} +1 -1
  123. package/src/assets/web-panel/assets/{omit-QvpKbF8p.js → omit-DwGrzMoG.js} +1 -1
  124. package/src/assets/web-panel/assets/{pickAttrs-Dm8r3X1_.js → pickAttrs-BRVWfNub.js} +1 -1
  125. package/src/assets/web-panel/assets/{placementArrow-DaqaVfoX.js → placementArrow-hV_RKr6c.js} +1 -1
  126. package/src/assets/web-panel/assets/{responsiveObserve-Iida9fIn.js → responsiveObserve-PWGubgde.js} +1 -1
  127. package/src/assets/web-panel/assets/{slide-YqHexXQD.js → slide-TRaJdEGF.js} +1 -1
  128. package/src/assets/web-panel/assets/{statusUtils-BGKLoeEt.js → statusUtils-Dtpf-U70.js} +1 -1
  129. package/src/assets/web-panel/assets/{styleChecker-aI-gsQO8.js → styleChecker-pWt7CTDQ.js} +1 -1
  130. package/src/assets/web-panel/assets/{useFlexGapSupport-BiOsz4rc.js → useFlexGapSupport-BojArhOx.js} +1 -1
  131. package/src/assets/web-panel/assets/{useFs-CZy7Zo2X.js → useFs-ZGDRvFRC.js} +1 -1
  132. package/src/assets/web-panel/assets/{vnode-B6WqjmE4.js → vnode-CBtr3ooD.js} +1 -1
  133. package/src/assets/web-panel/assets/{zoom-DTeTrJ2z.js → zoom-DyGC413M.js} +1 -1
  134. package/src/assets/web-panel/index.html +1 -1
  135. package/src/commands/__tests__/hub-wechat.test.js +186 -15
  136. package/src/commands/hub.js +368 -3
  137. package/src/lib/__tests__/cc-android-bridge.test.js +245 -0
  138. package/src/lib/cc-android-bridge.js +99 -55
  139. package/src/lib/personal-data-hub-aichat-wizard.js +29 -15
  140. package/src/lib/personal-data-hub-wiring.js +24 -2
  141. package/src/assets/web-panel/assets/index-BIRYt1of.js +0 -1
  142. package/src/assets/web-panel/assets/index-CZfySmWX.js +0 -1
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+
3
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
4
+ import bridgeDefault, {
5
+ invoke,
6
+ caps,
7
+ AndroidBridgeUnavailableError,
8
+ detectAndroid,
9
+ loadBridgeConfig,
10
+ _deps,
11
+ } from "../cc-android-bridge.js";
12
+
13
+ // ─── env restore helpers ──────────────────────────────────────────────
14
+
15
+ const ENV_KEYS = [
16
+ "CC_ANDROID_BRIDGE_OVERRIDE",
17
+ "CC_ANDROID_BRIDGE_CONFIG_DIR",
18
+ "PREFIX",
19
+ ];
20
+ let envSnapshot;
21
+ let depsSnapshot;
22
+
23
+ beforeEach(() => {
24
+ envSnapshot = {};
25
+ for (const k of ENV_KEYS) envSnapshot[k] = process.env[k];
26
+ depsSnapshot = {
27
+ detectAndroid: _deps.detectAndroid,
28
+ loadBridgeConfig: _deps.loadBridgeConfig,
29
+ fetch: _deps.fetch,
30
+ testInvoke: _deps.testInvoke,
31
+ };
32
+ });
33
+
34
+ afterEach(() => {
35
+ for (const k of ENV_KEYS) {
36
+ if (envSnapshot[k] === undefined) delete process.env[k];
37
+ else process.env[k] = envSnapshot[k];
38
+ }
39
+ _deps.detectAndroid = depsSnapshot.detectAndroid;
40
+ _deps.loadBridgeConfig = depsSnapshot.loadBridgeConfig;
41
+ _deps.fetch = depsSnapshot.fetch;
42
+ _deps.testInvoke = depsSnapshot.testInvoke;
43
+ });
44
+
45
+ // ─── detectAndroid ────────────────────────────────────────────────────
46
+
47
+ describe("detectAndroid", () => {
48
+ it("returns false on non-Android host", () => {
49
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
50
+ delete process.env.PREFIX;
51
+ // platform = "win32" / "linux" / "darwin" — none is "android"
52
+ expect(detectAndroid()).toBe(false);
53
+ });
54
+
55
+ it("returns true under CC_ANDROID_BRIDGE_OVERRIDE=1", () => {
56
+ process.env.CC_ANDROID_BRIDGE_OVERRIDE = "1";
57
+ expect(detectAndroid()).toBe(true);
58
+ });
59
+
60
+ it("returns true when PREFIX matches Termux Android prefix", () => {
61
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
62
+ process.env.PREFIX = "/data/data/com.chainlesschain.android/files/usr";
63
+ expect(detectAndroid()).toBe(true);
64
+ });
65
+ });
66
+
67
+ // ─── loadBridgeConfig ─────────────────────────────────────────────────
68
+
69
+ describe("loadBridgeConfig", () => {
70
+ it("returns null off-device with no PREFIX + no override dir", () => {
71
+ delete process.env.CC_ANDROID_BRIDGE_CONFIG_DIR;
72
+ delete process.env.PREFIX;
73
+ expect(loadBridgeConfig()).toBeNull();
74
+ });
75
+
76
+ it("returns parsed config from CC_ANDROID_BRIDGE_CONFIG_DIR", async () => {
77
+ const { mkdtempSync, writeFileSync, rmSync } = await import("node:fs");
78
+ const { join } = await import("node:path");
79
+ const { tmpdir } = await import("node:os");
80
+
81
+ const dir = mkdtempSync(join(tmpdir(), "cc-bridge-cfg-"));
82
+ writeFileSync(join(dir, "port"), "12345\n", "utf-8");
83
+ writeFileSync(join(dir, "token"), "deadbeef\n", "utf-8");
84
+ process.env.CC_ANDROID_BRIDGE_CONFIG_DIR = dir;
85
+
86
+ const cfg = loadBridgeConfig();
87
+ expect(cfg).toEqual({
88
+ port: 12345,
89
+ token: "deadbeef",
90
+ baseUrl: "http://127.0.0.1:12345",
91
+ });
92
+
93
+ rmSync(dir, { recursive: true, force: true });
94
+ });
95
+
96
+ it("returns null when port file missing", async () => {
97
+ const { mkdtempSync, writeFileSync, rmSync } = await import("node:fs");
98
+ const { join } = await import("node:path");
99
+ const { tmpdir } = await import("node:os");
100
+ const dir = mkdtempSync(join(tmpdir(), "cc-bridge-cfg-"));
101
+ writeFileSync(join(dir, "token"), "x", "utf-8");
102
+ process.env.CC_ANDROID_BRIDGE_CONFIG_DIR = dir;
103
+ expect(loadBridgeConfig()).toBeNull();
104
+ rmSync(dir, { recursive: true, force: true });
105
+ });
106
+ });
107
+
108
+ // ─── invoke ───────────────────────────────────────────────────────────
109
+
110
+ describe("invoke — error paths", () => {
111
+ it("throws TypeError on empty method", async () => {
112
+ await expect(invoke("")).rejects.toThrow(TypeError);
113
+ });
114
+
115
+ it("rejects ANDROID_BRIDGE_NOT_AVAILABLE off-device", async () => {
116
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
117
+ _deps.detectAndroid = () => false;
118
+ await expect(invoke("contacts.query")).rejects.toBeInstanceOf(
119
+ AndroidBridgeUnavailableError,
120
+ );
121
+ });
122
+
123
+ it("rejects when bridge config missing on-device", async () => {
124
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
125
+ _deps.detectAndroid = () => true;
126
+ _deps.loadBridgeConfig = () => null;
127
+ await expect(invoke("contacts.query")).rejects.toMatchObject({
128
+ code: "ANDROID_BRIDGE_NOT_AVAILABLE",
129
+ reason: expect.stringContaining("bridge config missing"),
130
+ });
131
+ });
132
+
133
+ it("rejects on fetch network error", async () => {
134
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
135
+ _deps.detectAndroid = () => true;
136
+ _deps.loadBridgeConfig = () => ({
137
+ port: 8237,
138
+ token: "x",
139
+ baseUrl: "http://127.0.0.1:8237",
140
+ });
141
+ _deps.fetch = vi.fn(async () => {
142
+ throw new Error("ECONNREFUSED");
143
+ });
144
+ await expect(invoke("contacts.query")).rejects.toMatchObject({
145
+ code: "ANDROID_BRIDGE_NOT_AVAILABLE",
146
+ reason: expect.stringContaining("ECONNREFUSED"),
147
+ });
148
+ });
149
+
150
+ it("rejects on non-2xx response", async () => {
151
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
152
+ _deps.detectAndroid = () => true;
153
+ _deps.loadBridgeConfig = () => ({
154
+ port: 8237,
155
+ token: "x",
156
+ baseUrl: "http://127.0.0.1:8237",
157
+ });
158
+ _deps.fetch = vi.fn(async () => ({
159
+ ok: false,
160
+ status: 401,
161
+ text: async () => "",
162
+ }));
163
+ await expect(invoke("contacts.query")).rejects.toMatchObject({
164
+ reason: expect.stringContaining("HTTP 401"),
165
+ });
166
+ });
167
+ });
168
+
169
+ describe("invoke — happy path", () => {
170
+ it("POSTs /invoke with Bearer token + JSON body, returns parsed JSON", async () => {
171
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
172
+ _deps.detectAndroid = () => true;
173
+ _deps.loadBridgeConfig = () => ({
174
+ port: 8237,
175
+ token: "secret-token",
176
+ baseUrl: "http://127.0.0.1:8237",
177
+ });
178
+ const fetchSpy = vi.fn(async (url, init) => ({
179
+ ok: true,
180
+ status: 200,
181
+ text: async () => JSON.stringify({ contacts: [{ name: "Alice" }] }),
182
+ }));
183
+ _deps.fetch = fetchSpy;
184
+
185
+ const result = await invoke("contacts.query", { since: 0 });
186
+ expect(result).toEqual({ contacts: [{ name: "Alice" }] });
187
+ expect(fetchSpy).toHaveBeenCalledOnce();
188
+ const [url, init] = fetchSpy.mock.calls[0];
189
+ expect(url).toBe("http://127.0.0.1:8237/invoke?method=contacts.query");
190
+ expect(init.method).toBe("POST");
191
+ expect(init.headers["Authorization"]).toBe("Bearer secret-token");
192
+ expect(init.headers["Content-Type"]).toBe("application/json");
193
+ expect(JSON.parse(init.body)).toEqual({ since: 0 });
194
+ });
195
+
196
+ it("uses testInvoke when CC_ANDROID_BRIDGE_OVERRIDE=1", async () => {
197
+ process.env.CC_ANDROID_BRIDGE_OVERRIDE = "1";
198
+ _deps.testInvoke = vi.fn(async (m, p) => ({ echo: m, ...p }));
199
+ const result = await invoke("foo.bar", { x: 1 });
200
+ expect(result).toEqual({ echo: "foo.bar", x: 1 });
201
+ });
202
+ });
203
+
204
+ // ─── caps ─────────────────────────────────────────────────────────────
205
+
206
+ describe("caps", () => {
207
+ it("returns available=false off-device", () => {
208
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
209
+ _deps.detectAndroid = () => false;
210
+ const r = caps();
211
+ expect(r.available).toBe(false);
212
+ expect(r.reason).toContain("not-on-android");
213
+ });
214
+
215
+ it("returns available=false when bridge server not started", () => {
216
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
217
+ _deps.detectAndroid = () => true;
218
+ _deps.loadBridgeConfig = () => null;
219
+ const r = caps();
220
+ expect(r.available).toBe(false);
221
+ expect(r.reason).toContain("bridge-server-not-started");
222
+ });
223
+
224
+ it("returns available=true with port when bridge is up", () => {
225
+ delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
226
+ _deps.detectAndroid = () => true;
227
+ _deps.loadBridgeConfig = () => ({ port: 8237, token: "x" });
228
+ const r = caps();
229
+ expect(r.available).toBe(true);
230
+ expect(r.port).toBe(8237);
231
+ });
232
+ });
233
+
234
+ // ─── default export ───────────────────────────────────────────────────
235
+
236
+ describe("default export", () => {
237
+ it("exposes invoke / caps / Error class / _deps", () => {
238
+ expect(bridgeDefault.invoke).toBe(invoke);
239
+ expect(bridgeDefault.caps).toBe(caps);
240
+ expect(bridgeDefault.AndroidBridgeUnavailableError).toBe(
241
+ AndroidBridgeUnavailableError,
242
+ );
243
+ expect(bridgeDefault._deps).toBe(_deps);
244
+ });
245
+ });
@@ -1,38 +1,39 @@
1
1
  /**
2
2
  * cc-android-bridge — Node-side facade for the Android JNI bridge.
3
3
  *
4
- * Plan A Sub-Phase A6/A7 scaffold (see `docs/design/Personal_Data_Hub_Android_Standalone_Cc.md`
5
- * §4.2 + §6.1). The eventual native binding (`cc-android-bridge.node`,
6
- * loaded from `android-app/app/src/main/cpp/`) exposes Java/Kotlin APIs to
7
- * the in-APK Node runtime: ContentResolver query, PackageManager listings,
8
- * Runtime.exec / Shizuku / Magisk su, Accessibility node tree, SAF DocumentFile
9
- * read.
4
+ * Plan A Sub-Phase A6 (see `docs/design/Personal_Data_Hub_Android_Standalone_Cc.md`).
10
5
  *
11
- * **v0.1 status**: the native .node binding is NOT yet shipped. This file is
12
- * a pure stub — every `invoke()` call rejects with ANDROID_BRIDGE_NOT_AVAILABLE.
13
- * It exists so the `cc android …` commands and `system-data-android` adapter
14
- * (A7 / A8) can land + be unit-tested ahead of the JNI work. When A6 ships
15
- * the .node binding, this file's `loadNativeBinding()` will resolve the real
16
- * module; method dispatch is already wired.
6
+ * **A6a transport (current)**: HTTP localhost. The Android app starts a
7
+ * tiny HTTP server (CcAndroidBridgeServer.kt) at App.onCreate; this file
8
+ * reads the port + token written to filesDir/.chainlesschain/bridge/ and
9
+ * forwards `invoke(method, params)` as `POST /invoke?method=<kebab>` with
10
+ * the JSON params as body and `Authorization: Bearer <token>`.
11
+ *
12
+ * Why HTTP not JNI: the cc subprocess is a separate Linux process (per
13
+ * memory android_cc_subprocess_execve_via_mksh) — JNI cannot bridge
14
+ * process boundaries. HTTP localhost gives the same UX with zero NDK
15
+ * toolchain dependency. The .so + Unix-socket path stays on the roadmap
16
+ * (A6b) for the in-process feel but is not blocking the feature.
17
17
  *
18
18
  * Detection precedence (highest first):
19
- * 1. process.env.CC_ANDROID_BRIDGE_OVERRIDE === "1" — test override; bridge
20
- * reports "available" and routes through `_deps.testInvoke` (which tests
21
- * replace with a mock). Lets us exercise the command code paths without
22
- * a real device.
19
+ * 1. process.env.CC_ANDROID_BRIDGE_OVERRIDE === "1" — test override
23
20
  * 2. process.platform === "android"
24
21
  * 3. process.env.PREFIX startsWith "/data/data/com.chainlesschain.android/"
25
22
  * (Termux $PREFIX pattern in the bundled cc).
26
23
  *
27
- * Method names are kebab-case to match the `cc android <verb>` CLI surface,
28
- * not the underlying JNI signatures (those use Java camelCase). Mapping
29
- * happens in the future native binding.
24
+ * Bridge config discovery (Android only):
25
+ * - filesDir/.chainlesschain/bridge/port — "8237" etc.
26
+ * - filesDir/.chainlesschain/bridge/token 48-hex-char auth token
27
+ * filesDir resolves to `$PREFIX/..` (Termux convention: filesDir is the
28
+ * parent of usr/).
29
+ *
30
+ * Method names are kebab-case to match the `cc android <verb>` CLI surface.
30
31
  */
31
32
 
32
- import { createRequire } from "node:module";
33
+ import { readFileSync, existsSync } from "node:fs";
34
+ import { join, dirname } from "node:path";
33
35
 
34
36
  const ANDROID_PREFIX = "/data/data/com.chainlesschain.android/";
35
- const nodeRequire = createRequire(import.meta.url);
36
37
 
37
38
  export function detectAndroid() {
38
39
  if (process.env.CC_ANDROID_BRIDGE_OVERRIDE === "1") return true;
@@ -47,19 +48,40 @@ export function detectAndroid() {
47
48
  }
48
49
 
49
50
  /**
50
- * Lazy-load the native .node binding when we're actually on Android. Stays
51
- * null elsewhere. The binding is expected at:
52
- * $APK/lib/<abi>/cc-android-bridge.node
53
- * resolved via require.resolve("cc-android-bridge").
54
- *
55
- * Wrapped in try so a missing binding (current state pre-A6) doesn't crash
56
- * the CLI on import — `invoke()` reports it as a runtime error instead.
51
+ * Locate the Android app's filesDir from inside the in-APK cc subprocess.
52
+ * Termux convention: $PREFIX = filesDir/usr filesDir = dirname($PREFIX).
53
+ * Falls back to env var CC_ANDROID_BRIDGE_CONFIG_DIR (LAN / desktop case
54
+ * where caller has discovered the Android filesDir some other way).
55
+ */
56
+ function resolveBridgeConfigDir() {
57
+ if (process.env.CC_ANDROID_BRIDGE_CONFIG_DIR) {
58
+ return process.env.CC_ANDROID_BRIDGE_CONFIG_DIR;
59
+ }
60
+ if (
61
+ typeof process.env.PREFIX === "string" &&
62
+ process.env.PREFIX.startsWith(ANDROID_PREFIX)
63
+ ) {
64
+ return join(dirname(process.env.PREFIX), ".chainlesschain", "bridge");
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * Read the port + token written by CcAndroidBridgeServer.start() in
71
+ * filesDir/.chainlesschain/bridge/. Returns null if either file is missing
72
+ * (server not started yet, or running off-device without override).
57
73
  */
58
- export function loadNativeBinding() {
59
- if (!detectAndroid()) return null;
60
- if (process.env.CC_ANDROID_BRIDGE_OVERRIDE === "1") return null;
74
+ export function loadBridgeConfig() {
75
+ const dir = resolveBridgeConfigDir();
76
+ if (!dir) return null;
61
77
  try {
62
- return nodeRequire("cc-android-bridge");
78
+ const portPath = join(dir, "port");
79
+ const tokenPath = join(dir, "token");
80
+ if (!existsSync(portPath) || !existsSync(tokenPath)) return null;
81
+ const port = parseInt(readFileSync(portPath, "utf-8").trim(), 10);
82
+ const token = readFileSync(tokenPath, "utf-8").trim();
83
+ if (!Number.isFinite(port) || port <= 0 || !token) return null;
84
+ return { port, token, baseUrl: `http://127.0.0.1:${port}` };
63
85
  } catch (_e) {
64
86
  return null;
65
87
  }
@@ -74,17 +96,17 @@ export class AndroidBridgeUnavailableError extends Error {
74
96
  }
75
97
 
76
98
  /**
77
- * Invoke a bridge method.
99
+ * Invoke a bridge method via the HTTP transport.
78
100
  *
79
- * @param {string} method — kebab-case method name (e.g. "contacts.query",
80
- * "fs.read", "a11y.query", "app.list").
101
+ * @param {string} method — kebab-case method name (e.g. "contacts.query")
81
102
  * @param {object} [params]
82
- * @returns {Promise<any>}
103
+ * @returns {Promise<any>} Parsed JSON response from the bridge.
83
104
  *
84
105
  * Rejects with AndroidBridgeUnavailableError when:
85
- * - Not running on Android (and CC_ANDROID_BRIDGE_OVERRIDE != 1)
86
- * - Running on Android but native binding failed to load
87
- * - Method is not registered in the native binding
106
+ * - Not running on Android and CC_ANDROID_BRIDGE_OVERRIDE != 1
107
+ * - Running on Android but bridge config (port/token) missing
108
+ * - HTTP request fails or server returns non-200
109
+ * - Method name unknown (server returns {"error": "UNKNOWN_METHOD"})
88
110
  */
89
111
  export async function invoke(method, params = {}) {
90
112
  if (typeof method !== "string" || method.length === 0) {
@@ -93,7 +115,7 @@ export async function invoke(method, params = {}) {
93
115
  );
94
116
  }
95
117
 
96
- // Test path — _deps.testInvoke replaced by harness.
118
+ // Test path — harness replaces _deps.testInvoke.
97
119
  if (
98
120
  process.env.CC_ANDROID_BRIDGE_OVERRIDE === "1" &&
99
121
  typeof _deps.testInvoke === "function"
@@ -107,23 +129,45 @@ export async function invoke(method, params = {}) {
107
129
  );
108
130
  }
109
131
 
110
- const native = _deps.loadNativeBinding();
111
- if (!native) {
132
+ const config = _deps.loadBridgeConfig();
133
+ if (!config) {
134
+ throw new AndroidBridgeUnavailableError(
135
+ "bridge config missing — CcAndroidBridgeServer not started? (filesDir/.chainlesschain/bridge/{port,token})",
136
+ );
137
+ }
138
+
139
+ const url = `${config.baseUrl}/invoke?method=${encodeURIComponent(method)}`;
140
+ let resp;
141
+ try {
142
+ resp = await _deps.fetch(url, {
143
+ method: "POST",
144
+ headers: {
145
+ "Content-Type": "application/json",
146
+ Authorization: `Bearer ${config.token}`,
147
+ },
148
+ body: JSON.stringify(params || {}),
149
+ });
150
+ } catch (e) {
112
151
  throw new AndroidBridgeUnavailableError(
113
- "native binding cc-android-bridge.node missing A6 JNI module not yet bundled",
152
+ `HTTP transport error: ${e && e.message ? e.message : String(e)}`,
114
153
  );
115
154
  }
116
- if (typeof native.invoke !== "function") {
155
+ if (!resp.ok) {
156
+ throw new AndroidBridgeUnavailableError(`bridge HTTP ${resp.status}`);
157
+ }
158
+ const text = await resp.text();
159
+ try {
160
+ return JSON.parse(text);
161
+ } catch (e) {
117
162
  throw new AndroidBridgeUnavailableError(
118
- "native binding loaded but does not export invoke()",
163
+ `bridge returned non-JSON: ${text.slice(0, 200)}`,
119
164
  );
120
165
  }
121
- return await native.invoke(method, params);
122
166
  }
123
167
 
124
168
  /**
125
- * Check whether the bridge is usable right now without throwing.
126
- * Returns `{ available: boolean, reason?: string }`.
169
+ * Check whether the bridge is reachable right now without throwing.
170
+ * Returns `{ available: boolean, reason?: string, port?: number }`.
127
171
  */
128
172
  export function caps() {
129
173
  if (process.env.CC_ANDROID_BRIDGE_OVERRIDE === "1") {
@@ -135,22 +179,22 @@ export function caps() {
135
179
  reason: `not-on-android (platform=${process.platform})`,
136
180
  };
137
181
  }
138
- const native = _deps.loadNativeBinding();
139
- if (!native) {
182
+ const config = _deps.loadBridgeConfig();
183
+ if (!config) {
140
184
  return {
141
185
  available: false,
142
- reason: "native-binding-missing (A6 pending)",
186
+ reason: "bridge-server-not-started (port/token files missing)",
143
187
  };
144
188
  }
145
- return { available: true };
189
+ return { available: true, port: config.port };
146
190
  }
147
191
 
148
192
  // _deps injection seam (per [[feedback-vi-mock-cjs-interop]]). Tests reach in
149
- // and replace these to exercise both success + error paths without needing
150
- // a real Android device or the unloaded native binding.
193
+ // and replace these to exercise success / error paths without a real device.
151
194
  export const _deps = {
152
195
  detectAndroid,
153
- loadNativeBinding,
196
+ loadBridgeConfig,
197
+ fetch: globalThis.fetch.bind(globalThis),
154
198
  testInvoke: null,
155
199
  };
156
200
 
@@ -19,14 +19,26 @@ import { readFileSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
19
19
  import { join } from "node:path";
20
20
  import { createRequire } from "node:module";
21
21
 
22
- import {
23
- DEFAULT_VENDOR_SPECS,
24
- HttpClient,
25
- CookieAuthSession,
26
- } from "@chainlesschain/personal-data-hub/adapters/ai-chat-history";
27
-
28
22
  const _require = createRequire(import.meta.url);
29
23
 
24
+ // Lazy-load `@chainlesschain/personal-data-hub/adapters/ai-chat-history` —
25
+ // older nested copies of the hub package (e.g. PDH 0.2.0 left in
26
+ // `packages/cli/node_modules/@chainlesschain/personal-data-hub/` by a stale
27
+ // install) lack this subpath export. A static ESM import would crash the
28
+ // whole wiring chain at module load time; lazy `_require` lets the error
29
+ // surface only when actually called, and tolerates an older nested copy as
30
+ // long as Node's resolution eventually walks up to a version that exports
31
+ // the subpath. See sibling lazy-load at line 217 / 228 for cookie-capture-
32
+ // spec + wizard-controller (same defence).
33
+ let _aichatModule = null;
34
+ function _loadAichatModule() {
35
+ if (_aichatModule) return _aichatModule;
36
+ _aichatModule = _require(
37
+ "@chainlesschain/personal-data-hub/adapters/ai-chat-history",
38
+ );
39
+ return _aichatModule;
40
+ }
41
+
30
42
  export const ACCOUNTS_FILE = "aichat-accounts.json";
31
43
 
32
44
  /**
@@ -99,16 +111,14 @@ export function createAccountsStore({ hubDir }) {
99
111
  * spec.validateCookie(). Kept duplicated so the cli build stays ESM-pure
100
112
  * without reaching into desktop-app-vue.
101
113
  */
102
- export function createVendorAdapterBridge({
103
- specs = DEFAULT_VENDOR_SPECS,
104
- _httpClientFactory,
105
- } = {}) {
114
+ export function createVendorAdapterBridge({ specs, _httpClientFactory } = {}) {
106
115
  // DEFAULT_VENDOR_SPECS is shipped as a vendor-keyed object; tests pass an
107
116
  // array. Normalize to an array first so byVendor lookup works either way.
108
- const arr = Array.isArray(specs)
109
- ? specs
110
- : specs && typeof specs === "object"
111
- ? Object.values(specs)
117
+ const effectiveSpecs = specs ?? _loadAichatModule().DEFAULT_VENDOR_SPECS;
118
+ const arr = Array.isArray(effectiveSpecs)
119
+ ? effectiveSpecs
120
+ : effectiveSpecs && typeof effectiveSpecs === "object"
121
+ ? Object.values(effectiveSpecs)
112
122
  : null;
113
123
  if (!arr || arr.length === 0) {
114
124
  throw new Error("aichat-wizard-cli: specs required");
@@ -119,7 +129,10 @@ export function createVendorAdapterBridge({
119
129
  }
120
130
  const buildClient =
121
131
  _httpClientFactory ||
122
- ((vendor, spec) => new HttpClient({ vendor, rateLimits: spec.rateLimits }));
132
+ ((vendor, spec) => {
133
+ const { HttpClient } = _loadAichatModule();
134
+ return new HttpClient({ vendor, rateLimits: spec.rateLimits });
135
+ });
123
136
 
124
137
  async function registerVendor(vendor, cookies, _opts = {}) {
125
138
  const spec = byVendor.get(vendor);
@@ -137,6 +150,7 @@ export function createVendorAdapterBridge({
137
150
  error: err.message,
138
151
  };
139
152
  }
153
+ const { CookieAuthSession } = _loadAichatModule();
140
154
  const session = new CookieAuthSession({
141
155
  vendor,
142
156
  cookies: _jarToArray(cookies),
@@ -45,6 +45,7 @@ const {
45
45
  EmailAdapter,
46
46
  AlipayBillAdapter,
47
47
  SystemDataAndroidAdapter,
48
+ BilibiliAdapter,
48
49
  EntityResolver,
49
50
  EntityResolverEmbeddingStage,
50
51
  EntityResolverLLMStage,
@@ -61,8 +62,13 @@ import {
61
62
  } from "./personal-data-hub-aichat-wizard.js";
62
63
 
63
64
  async function loadAIChatHealthChecker() {
64
- const mod =
65
- await import("@chainlesschain/personal-data-hub/adapters/ai-chat-history/health-checker");
65
+ // Composed-at-runtime specifier so vite import-analysis (which fails
66
+ // to resolve subpath exports for dynamic imports in vitest SSR mode)
67
+ // skips this — the static scanner only descends into string literals.
68
+ const spec =
69
+ "@chainlesschain/personal-data-hub" +
70
+ "/adapters/ai-chat-history/health-checker";
71
+ const mod = await import(spec);
66
72
  return (mod.default || mod).createAIChatHealthChecker;
67
73
  }
68
74
 
@@ -263,6 +269,22 @@ async function initHub() {
263
269
  // surface the absence via list-adapters.
264
270
  }
265
271
 
272
+ // A8 v0.1 (2026-05-22) — social adapters in snapshot mode. Stateless: the
273
+ // Android UI captures a cookie via in-app WebView, runs OkHttp against the
274
+ // platform's HTTP API, parses the JSON response, writes a snapshot JSON to
275
+ // filesDir/.chainlesschain/staging/, then calls `cc hub sync-adapter <name>
276
+ // --input <path> --json` via LocalCcRunner. The adapter reads the file and
277
+ // yields events; the same registry handles KG + RAG + vault writes — no
278
+ // desktop connection required (this is the desktop-independent path the
279
+ // user repeatedly asked about). Each adapter no-ops at boot when no
280
+ // snapshot has been produced yet.
281
+ try {
282
+ const bilibili = new BilibiliAdapter();
283
+ if (!registry.has(bilibili.name)) registry.register(bilibili);
284
+ } catch (_err) {
285
+ // Continue boot
286
+ }
287
+
266
288
  // Phase 6: auto-register persisted Alipay accounts.
267
289
  const alipayAccountsPath = join(hubDir, "alipay-accounts.json");
268
290
  const alipayAccounts = loadAlipayAccounts(alipayAccountsPath);
@@ -1 +0,0 @@
1
- import{A as o}from"./Row-DjHxhH1L.js";import{S as t}from"./index-b8GbH2Yi.js";import"./responsiveObserve-Iida9fIn.js";import"./vendor-DhFY8mDK.js";import"./useFlexGapSupport-BiOsz4rc.js";import"./styleChecker-aI-gsQO8.js";import"./index-gUACAWbM.js";import"./icons-BOPtEWK4.js";const l=t(o);export{l as default};
@@ -1 +0,0 @@
1
- import{C as o}from"./Col-DU9NoUIi.js";import{S as t}from"./index-b8GbH2Yi.js";import"./index-gUACAWbM.js";import"./vendor-DhFY8mDK.js";import"./icons-BOPtEWK4.js";const s=t(o);export{s as default};