@bolloon/bolloon-agent 0.4.23 → 0.4.25
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.
- package/dist/agents/execution-supervisor.js +391 -0
- package/dist/agents/goal-store.js +451 -0
- package/dist/agents/pi-harness.js +263 -0
- package/dist/agents/pi-sdk.js +568 -126
- package/dist/agents/run-store.js +772 -0
- package/dist/agents/runner-resolver.js +225 -0
- package/dist/agents/skills-manager.js +435 -0
- package/dist/agents/supervisor-host.js +249 -0
- package/dist/cron/tick-lock.js +1 -1
- package/dist/index.js +428 -22
- package/dist/ios/agent-delegate-server.js +58 -12
- package/dist/ios/icons/icon-1024x1024.png +0 -0
- package/dist/ios/icons/icon-1024x1024.webp +0 -0
- package/dist/ios/icons/icon-216x216.png +0 -0
- package/dist/ios/icons/icon-216x216.webp +0 -0
- package/dist/ios/index.html +95 -18
- package/dist/ios/manifest.json +1 -1
- package/dist/ios/mobile-agent.js +244 -5
- package/dist/ios/mobile-chain.js +481 -0
- package/dist/ios/mobile-core.js +25177 -24664
- package/dist/ios/mobile-helia.js +683 -0
- package/dist/ios/mobile-ipfs.js +680 -0
- package/dist/ios/mobile-orbit.js +268 -0
- package/dist/ios/mobile-p2p.js +130 -1
- package/dist/ios/mobile-social.js +624 -0
- package/dist/ios/mobile-sync.js +193 -0
- package/dist/ios/mobile-trade.js +492 -0
- package/dist/ios/mobile-wallet.js +11 -0
- package/dist/ios/mobile.css +39 -0
- package/dist/ios/mobile.html +95 -18
- package/dist/ios/mobile.js +1054 -55
- package/dist/ios/routes-x402-info.js +194 -0
- package/dist/ios/server.js +350 -9
- package/dist/ios/sw.js +26 -2
- package/dist/web/agent-delegate-server.js +58 -12
- package/dist/web/icons/icon-1024x1024.png +0 -0
- package/dist/web/icons/icon-1024x1024.webp +0 -0
- package/dist/web/icons/icon-216x216.png +0 -0
- package/dist/web/icons/icon-216x216.webp +0 -0
- package/dist/web/manifest.json +1 -1
- package/dist/web/mobile-agent.js +197 -3
- package/dist/web/mobile-core.js +16417 -16090
- package/dist/web/mobile-privacy.js +185 -0
- package/dist/web/mobile.css +15 -0
- package/dist/web/mobile.html +21 -1
- package/dist/web/mobile.js +179 -6
- package/dist/web/server.js +437 -4
- package/package.json +2 -2
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mobile-privacy.ts — 手机端隐私合规: 首启同意门 + 应用内政策摘要 + 本机数据注销
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独成模块: 这三件事都必须能被单测锁住 —— 摘要少一个必填要素、同意版本号算错、
|
|
5
|
+
* 注销漏删一个库, 都是"上架被拒/合规缺口"级别的问题, 不能只靠肉眼看 UI。
|
|
6
|
+
*
|
|
7
|
+
* 上架要求对照 (华为/工信部口径):
|
|
8
|
+
* ① 首次启动必须先展示隐私政策, 用户同意前不得收集任何信息/申请任何权限 → needsPrivacyConsent + 门
|
|
9
|
+
* ② 隐私政策须含: 收集类型 · 用途 · 存储期限 · 第三方 SDK 清单 · 账号注销路径 → PRIVACY_SUMMARY
|
|
10
|
+
* ③ 必须提供注销入口且处理时限 ≤ 7 个工作日 → WIPE_TARGETS + wipeLocalData (本地即时)
|
|
11
|
+
*/
|
|
12
|
+
/** localStorage 键: 记录用户已同意的政策版本 */
|
|
13
|
+
export const PRIVACY_CONSENT_KEY = 'bolloon_privacy_consent';
|
|
14
|
+
/** 政策版本: 摘要或权威页内容发生实质变化时必须 +1 → 会重新征求同意 */
|
|
15
|
+
export const PRIVACY_CONSENT_VERSION = '1';
|
|
16
|
+
/** 权威政策页 (应用市场表单要填的链接; 应用内摘要以它为准) */
|
|
17
|
+
export const PRIVACY_POLICY_URL = 'https://bolloon.cn/privacy.html';
|
|
18
|
+
/** APP 备案号: 备案完成后填入 (形如 浙ICP备XXXXXXXX号-XA), 留空时 UI 显示"备案办理中" */
|
|
19
|
+
export const APP_FILING_NO = '';
|
|
20
|
+
/** 主体联系邮箱 (政策里承诺 7 个工作日答复) */
|
|
21
|
+
export const PRIVACY_CONTACT = 'yuanjieliu65@gmail.com';
|
|
22
|
+
/** 注销时限承诺 (工作日) */
|
|
23
|
+
export const WIPE_SLA_WORKDAYS = 7;
|
|
24
|
+
/**
|
|
25
|
+
* 应用内政策摘要 —— 必须覆盖上架要求的必填要素。
|
|
26
|
+
* 单测逐个要素断言 (mobile-privacy.test.ts), 删要素即红。
|
|
27
|
+
*/
|
|
28
|
+
export const PRIVACY_SUMMARY = [
|
|
29
|
+
{
|
|
30
|
+
title: '一、我们收集什么',
|
|
31
|
+
body: '本机生成的网络身份标识(DID)与密钥、你创建的智能体、会话与消息内容、好友与已知节点列表。' +
|
|
32
|
+
'这些数据默认只保存在你自己的设备上(IndexedDB),我们不设账号体系,不收集手机号、邮箱、通讯录、' +
|
|
33
|
+
'短信、通话记录、精确定位、设备唯一标识(IMEI/MAC/广告标识),也不做使用行为统计。',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: '二、用途',
|
|
37
|
+
body: '上述数据仅用于提供你要求的功能:P2P 连接与身份验证、智能体会话、本地存储与展示。' +
|
|
38
|
+
'我们不用于广告、画像或向第三方出售。',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: '三、存储位置与期限',
|
|
42
|
+
body: '全部保存在本机。卸载应用或在「设置 → 清除本机数据(注销)」即删除;我们服务器上没有你的副本,' +
|
|
43
|
+
'因此不存在"提交删除申请后还要等待"的环节。',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: '四、第三方 SDK 与服务',
|
|
47
|
+
body: '你自选的大模型服务商(API Key 由你填写,你的设备直连该服务商,我们不经手);' +
|
|
48
|
+
'网站与安装包托管 Cloudflare / GitHub(境外)、版本号查询 npm registry、网页字体 Google Fonts。' +
|
|
49
|
+
'本应用不集成任何广告、行为统计或崩溃上报 SDK。若你启用微支付,链上交易记录公开且不可删除。',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: '五、系统权限',
|
|
53
|
+
body: '蓝牙扫描与连接:发现并连接附近设备与好友(已声明 neverForLocation,不用于推断位置)。' +
|
|
54
|
+
'位置权限:仅 Android 11 及以下系统要求蓝牙扫描需要它(已限制 maxSdkVersion=30,Android 12+ 不再申请)。' +
|
|
55
|
+
'网络:P2P 连接与下载。相机:调用系统相机应用完成拍摄,本应用不申请相机权限。' +
|
|
56
|
+
'完全访问权限(让智能体在这台手机上点按、滑动屏幕或调用系统能力)与 Shizuku:仅官网直装版本包含,默认开启' +
|
|
57
|
+
'(首次使用会直接带你去系统开关页确认一次,之后一直有效)、随时可以关闭;' +
|
|
58
|
+
'应用商店下载的版本不含这两项,也没有这个能力。',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: '六、你的权利与注销',
|
|
62
|
+
body: '你可以随时在本机查看、修改、删除全部数据。注销入口:设置 → 清除本机数据(注销),' +
|
|
63
|
+
'即时删除本机数据(异常情况最长 ' + WIPE_SLA_WORKDAYS + ' 个工作日)。' +
|
|
64
|
+
'已经写入公开区块链的交易记录任何服务方都无法删除。',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: '七、联系我们',
|
|
68
|
+
body: '疑问或投诉请发邮件至 ' + PRIVACY_CONTACT + ',我们会在 ' + WIPE_SLA_WORKDAYS + ' 个工作日内答复。',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* 是否需要弹同意门。
|
|
73
|
+
* @param stored 本机已记录的同意值 (localStorage.getItem(PRIVACY_CONSENT_KEY))
|
|
74
|
+
* @returns true = 需要征求同意 (未同意 / 版本过期 / 值非法)
|
|
75
|
+
*/
|
|
76
|
+
export function needsPrivacyConsent(stored) {
|
|
77
|
+
if (typeof stored !== 'string')
|
|
78
|
+
return true;
|
|
79
|
+
return stored.trim() !== PRIVACY_CONSENT_VERSION;
|
|
80
|
+
}
|
|
81
|
+
/** 同意后写入的值 */
|
|
82
|
+
export function consentRecord() {
|
|
83
|
+
return PRIVACY_CONSENT_VERSION;
|
|
84
|
+
}
|
|
85
|
+
/** 备案号展示文案 (未备案时如实说明, 不伪造编号) */
|
|
86
|
+
export function filingDisplay(no = APP_FILING_NO) {
|
|
87
|
+
const v = (no || '').trim();
|
|
88
|
+
return v ? `APP 备案号 ${v}` : 'APP 备案号:备案办理中';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 注销要清掉的东西 —— 清单必须与"我们声明收集的数据"一一对应, 少一个就是合规缺口。
|
|
92
|
+
* 注意: 不删 consent 记录 (同意记录不是个人信息, 且删了会在注销后立刻再弹一次门)。
|
|
93
|
+
*/
|
|
94
|
+
export const WIPE_TARGETS = {
|
|
95
|
+
/** IndexedDB 库 (名称与各模块里的常量逐一核对过) */
|
|
96
|
+
databases: ['bolloon-mobile-data', 'bolloon-mobile', 'bolloon-mobile-payments', 'bolloon'],
|
|
97
|
+
/** localStorage 键前缀: 命中即清 */
|
|
98
|
+
localStoragePrefixes: ['bolloon_', 'bolloon.', 'bolloon-'],
|
|
99
|
+
/** 明确保留: 同意记录 + 界面偏好 (不含个人信息) */
|
|
100
|
+
keep: [PRIVACY_CONSENT_KEY, 'bolloon-lang', 'bolloon_theme'],
|
|
101
|
+
};
|
|
102
|
+
/** 注销后给用户看的说明 (诚实: 链上记录删不掉) */
|
|
103
|
+
export const WIPE_NOTICE = '已删除本机的身份标识(DID)、智能体、会话消息与钱包账本。' +
|
|
104
|
+
'已经写入公开区块链的交易记录无法删除,这一点任何服务方都做不到。' +
|
|
105
|
+
`如遇异常,我们的处理时限为 ${WIPE_SLA_WORKDAYS} 个工作日(${PRIVACY_CONTACT})。`;
|
|
106
|
+
/** 同意门文案 (集中在这里, 便于审计与翻译) */
|
|
107
|
+
export const CONSENT_TEXT = {
|
|
108
|
+
title: '隐私政策与权限说明',
|
|
109
|
+
intro: 'Bolloon 是本地优先的:没有账号体系,不收集手机号或邮箱,你的智能体、会话与消息只存在这台设备上。' +
|
|
110
|
+
'在你点击「同意并继续」之前,应用不会读取本机数据、不会连接网络、也不会申请任何系统权限。',
|
|
111
|
+
agree: '同意并继续',
|
|
112
|
+
decline: '不同意',
|
|
113
|
+
policyLink: '阅读完整隐私政策',
|
|
114
|
+
declinedTitle: '未同意,已停在说明页',
|
|
115
|
+
declinedBody: '你可以继续留在本页查阅说明,或退出应用。我们不会在此状态下收集任何信息。' +
|
|
116
|
+
'随时重新打开应用都可以再次选择。',
|
|
117
|
+
declinedReread: '重新阅读',
|
|
118
|
+
declinedExit: '退出应用',
|
|
119
|
+
};
|
|
120
|
+
/** 删除单个 IndexedDB 库 (供没有 reset 导出的模块使用, 如钱包库) */
|
|
121
|
+
function deleteDatabase(name) {
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
if (typeof indexedDB === 'undefined') {
|
|
124
|
+
resolve();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const req = indexedDB.deleteDatabase(name);
|
|
128
|
+
req.onsuccess = () => resolve();
|
|
129
|
+
req.onerror = () => reject(req.error);
|
|
130
|
+
// 还有别的连接开着时会 blocked: 不等死, 如实返回 (下次启动会清掉)
|
|
131
|
+
req.onblocked = () => resolve();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 注销 / 清除本机数据 —— 应用内唯一的删除入口。
|
|
136
|
+
* 覆盖: 身份 DID+密钥库 · 智能体与会话消息库 · 支付库 · 钱包库 · localStorage 里的本机键。
|
|
137
|
+
* 不覆盖: 公开区块链上的历史交易 (任何服务方都删不了, 文案里已向用户说明)。
|
|
138
|
+
*/
|
|
139
|
+
export async function wipeLocalData() {
|
|
140
|
+
const result = { deletedDatabases: [], clearedStorageKeys: [], failed: [] };
|
|
141
|
+
// ① 三个自带 reset 的库: 复用模块自己的实现 (它们会先关闭连接, 避免 onblocked)
|
|
142
|
+
const resets = [
|
|
143
|
+
['bolloon-mobile-data', async () => (await import('./mobile-data.js')).resetDataDb()],
|
|
144
|
+
['bolloon-mobile', async () => (await import('./mobile-agent.js')).resetAgentDb()],
|
|
145
|
+
['bolloon-mobile-payments', async () => (await import('./mobile-payments.js')).resetPaymentsDb()],
|
|
146
|
+
];
|
|
147
|
+
for (const [name, fn] of resets) {
|
|
148
|
+
try {
|
|
149
|
+
await fn();
|
|
150
|
+
result.deletedDatabases.push(name);
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
result.failed.push(`${name}: ${e?.message || String(e)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// ② 钱包库 (mobile-wallet.ts) 没有 reset 导出 → 直接删库
|
|
157
|
+
try {
|
|
158
|
+
await deleteDatabase('bolloon');
|
|
159
|
+
result.deletedDatabases.push('bolloon');
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
result.failed.push(`bolloon: ${e?.message || String(e)}`);
|
|
163
|
+
}
|
|
164
|
+
// ③ localStorage: 按前缀清, 保留同意记录与界面偏好
|
|
165
|
+
try {
|
|
166
|
+
const keys = [];
|
|
167
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
168
|
+
const k = localStorage.key(i);
|
|
169
|
+
if (k)
|
|
170
|
+
keys.push(k);
|
|
171
|
+
}
|
|
172
|
+
const keep = WIPE_TARGETS.keep;
|
|
173
|
+
for (const k of keys) {
|
|
174
|
+
const hit = WIPE_TARGETS.localStoragePrefixes.some((p) => k.startsWith(p));
|
|
175
|
+
if (!hit || keep.includes(k))
|
|
176
|
+
continue;
|
|
177
|
+
localStorage.removeItem(k);
|
|
178
|
+
result.clearedStorageKeys.push(k);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
result.failed.push(`localStorage: ${e?.message || String(e)}`);
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
package/dist/web/mobile.css
CHANGED
|
@@ -688,3 +688,18 @@ body {
|
|
|
688
688
|
color: var(--bg);
|
|
689
689
|
font-weight: 600;
|
|
690
690
|
}
|
|
691
|
+
|
|
692
|
+
/* —— 隐私政策 (2026-09-16) —— */
|
|
693
|
+
.privacy-link {
|
|
694
|
+
display: block; margin: 6px 0 2px; text-align: center;
|
|
695
|
+
color: var(--accent); font-size: 13px; text-decoration: none;
|
|
696
|
+
}
|
|
697
|
+
.privacy-link:active { opacity: .7; }
|
|
698
|
+
.policy-body {
|
|
699
|
+
flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
|
|
700
|
+
padding: 14px 16px calc(40px + env(safe-area-inset-bottom));
|
|
701
|
+
}
|
|
702
|
+
.policy-body h3 { font-size: 14px; font-weight: 600; color: var(--text); margin: 18px 0 6px; }
|
|
703
|
+
.policy-body p { font-size: 13px; line-height: 1.75; color: var(--text-secondary); margin: 0 0 10px; }
|
|
704
|
+
.policy-body a { color: var(--accent); word-break: break-all; }
|
|
705
|
+
.policy-meta { font-size: 12px; color: var(--text-muted); }
|
package/dist/web/mobile.html
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
</header>
|
|
44
44
|
|
|
45
45
|
<!-- 主页面: 双频滑动卡片 -->
|
|
46
|
-
<main class="page-container" id="page-main" data-tab="main">
|
|
46
|
+
<main class="page-container" id="page-main" data-tab="main" hidden>
|
|
47
47
|
<!-- 水平滑动卡片轨道 -->
|
|
48
48
|
<div class="card-carousel" id="card-carousel">
|
|
49
49
|
<div class="card-track" id="card-track">
|
|
@@ -139,6 +139,26 @@
|
|
|
139
139
|
<button class="create-session-btn" id="btn-create-session" hidden>+ 创建新会话</button>
|
|
140
140
|
|
|
141
141
|
<!-- 创建智能体: 底部滑入加载 sheet -->
|
|
142
|
+
<!-- 2026-09-16: 首启隐私同意门 — 上架要求: 用户同意前不读取本机数据 / 不连网 / 不申请任何权限 -->
|
|
143
|
+
<div class="sheet" id="privacy-gate" hidden>
|
|
144
|
+
<div class="sheet-inner">
|
|
145
|
+
<div class="sheet-title" id="privacy-gate-title">隐私政策与权限说明</div>
|
|
146
|
+
<div class="sheet-text" id="privacy-gate-body"></div>
|
|
147
|
+
<a class="privacy-link" href="#" id="privacy-gate-link">阅读完整隐私政策 ›</a>
|
|
148
|
+
<button class="sheet-choice" id="privacy-agree">同意并继续</button>
|
|
149
|
+
<button class="sheet-choice sheet-cancel" id="privacy-decline">不同意</button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<!-- 2026-09-16: 应用内隐私政策 (全屏, 离线可用; 权威版 = bolloon.cn/privacy.html) -->
|
|
154
|
+
<div class="chat-page" id="policy-page" hidden>
|
|
155
|
+
<div class="chat-topbar">
|
|
156
|
+
<button class="icon-btn" id="policy-back">←</button>
|
|
157
|
+
<div style="flex:1;font-weight:600">隐私政策</div>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="policy-body" id="policy-body"></div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
142
162
|
<div class="sheet" id="create-sheet" hidden>
|
|
143
163
|
<div class="sheet-inner">
|
|
144
164
|
<div class="spinner"></div>
|
package/dist/web/mobile.js
CHANGED
|
@@ -1712,11 +1712,14 @@
|
|
|
1712
1712
|
<div class="conv-item" id="settings-data"><span class="list-icon">${ICONS.chip}</span><span style="flex:1;min-width:0"><span style="display:block">本机数据</span><span class="conv-preview" style="display:block">读取中…</span></span><span class="list-arrow">›</span></div>
|
|
1713
1713
|
<div class="conv-item" id="settings-desktop"><span class="list-icon">${ICONS.globe}</span><span>电脑端同步</span><span class="list-arrow">›</span></div>
|
|
1714
1714
|
<div class="conv-item" id="settings-chain"><span class="list-icon">${ICONS.chip}</span><span>链上配置 (RPC/网络)</span><span class="list-arrow">›</span></div>
|
|
1715
|
-
<div class="conv-item" id="settings-accessibility"><span class="list-icon">${ICONS.globe}</span><span
|
|
1715
|
+
<div class="conv-item" id="settings-accessibility"><span class="list-icon">${ICONS.globe}</span><span>完全访问权限<span class="conv-preview" style="display:block" id="access-preview">默认开启</span></span><span class="list-arrow">›</span></div>
|
|
1716
1716
|
<div class="conv-item" id="settings-ipfs"><span class="list-icon">${ICONS.chip}</span><span>IPFS 存储</span><span class="list-arrow">›</span></div>
|
|
1717
1717
|
<div class="conv-item" id="settings-helia"><span class="list-icon">${ICONS.globe}</span><span>本机 IPFS 节点</span><span class="list-arrow">›</span></div>
|
|
1718
1718
|
<div class="conv-item" id="settings-selfcard"><span class="list-icon">${ICONS.chip}</span><span id="selfcard-text">显示本机卡片: 开</span></div>
|
|
1719
1719
|
<div class="conv-item" id="settings-did"><span class="list-icon">${ICONS.idcard}</span><span>DID</span></div>
|
|
1720
|
+
<div class="conv-item" id="settings-privacy"><span class="list-icon">${ICONS.chip}</span><span>隐私政策与个人信息</span><span class="list-arrow">›</span></div>
|
|
1721
|
+
<div class="conv-item" id="settings-wipe"><span class="list-icon">${ICONS.trash}</span><span style="flex:1;min-width:0"><span style="display:block">清除本机数据(注销)</span><span class="conv-preview" style="display:block">删除本机身份、智能体、会话与钱包</span></span><span class="list-arrow">›</span></div>
|
|
1722
|
+
<div class="conv-item" id="settings-filing"><span class="list-icon">${ICONS.idcard}</span><span id="filing-text">APP 备案号:备案办理中</span></div>
|
|
1720
1723
|
</div>`;
|
|
1721
1724
|
document.body.appendChild(page);
|
|
1722
1725
|
applyTheme(resolveThemePref(), false);
|
|
@@ -1726,16 +1729,46 @@
|
|
|
1726
1729
|
const next = currentTheme === 'auto' ? 'light' : (currentTheme === 'light' ? 'dark' : 'auto');
|
|
1727
1730
|
applyTheme(next, true);
|
|
1728
1731
|
});
|
|
1729
|
-
//
|
|
1732
|
+
// 完全访问权限 (屏幕点按/滑动, 电脑端 phone.tap / phone.swipe 的前提) — 默认开启
|
|
1733
|
+
const ACCESS_AUTO_KEY = 'bolloon_access_auto_guided';
|
|
1734
|
+
const refreshAccessPreview = async () => {
|
|
1735
|
+
const el = document.querySelector('#access-preview');
|
|
1736
|
+
if (!el) return;
|
|
1737
|
+
const cap = window.Capacitor;
|
|
1738
|
+
const b = cap && cap.Plugins && cap.Plugins.RokidBridge;
|
|
1739
|
+
if (!b) { el.textContent = '仅手机 App'; return; }
|
|
1740
|
+
let st = {};
|
|
1741
|
+
try { st = (await b.touchStatus()) || {}; } catch (e) { st = {}; }
|
|
1742
|
+
el.textContent = st.ready ? '已开启' : '未开启 · 点这里开';
|
|
1743
|
+
};
|
|
1744
|
+
void refreshAccessPreview();
|
|
1745
|
+
// 默认开启: 首次启动自动把用户带到系统开关页 (只引导一次, 不反复打扰)
|
|
1746
|
+
void (async () => {
|
|
1747
|
+
try {
|
|
1748
|
+
const cap = window.Capacitor;
|
|
1749
|
+
const b = cap && cap.Plugins && cap.Plugins.RokidBridge;
|
|
1750
|
+
if (!b) return;
|
|
1751
|
+
let st = {};
|
|
1752
|
+
try { st = (await b.touchStatus()) || {}; } catch (e) { return; }
|
|
1753
|
+
if (st.ready) return;
|
|
1754
|
+
if (localStorage.getItem(ACCESS_AUTO_KEY) === '1') return;
|
|
1755
|
+
localStorage.setItem(ACCESS_AUTO_KEY, '1');
|
|
1756
|
+
await b.openAccessibilitySettings();
|
|
1757
|
+
} catch (e) {}
|
|
1758
|
+
})();
|
|
1730
1759
|
$('#settings-accessibility').addEventListener('click', async () => {
|
|
1731
1760
|
const cap = window.Capacitor;
|
|
1732
1761
|
const bridge = cap && cap.Plugins && cap.Plugins.RokidBridge;
|
|
1733
|
-
if (!bridge) { alert('
|
|
1762
|
+
if (!bridge) { alert('完全访问权限只在手机 App 里有:电脑端 / 网页版没有这项。'); return; }
|
|
1734
1763
|
let st = {};
|
|
1735
1764
|
try { st = (await bridge.touchStatus()) || {}; } catch (e) { st = {}; }
|
|
1736
|
-
if (st.ready) { alert('
|
|
1737
|
-
try {
|
|
1738
|
-
|
|
1765
|
+
if (st.ready) { alert('完全访问权限已开启:智能体可以在这台手机上点按、滑动屏幕(电脑端也能远程帮你点)。\n\n想关掉:系统设置 → 辅助功能 → 已开启的服务 → Bolloon Agent,关掉即可。'); return; }
|
|
1766
|
+
try {
|
|
1767
|
+
alert('马上打开系统设置,把「Bolloon Agent」的完全访问权限打开。\n(这一项在有些手机叫「辅助功能」,有些叫「无障碍」,指同一个开关;打开后一直有效,随时可以关掉。)');
|
|
1768
|
+
await bridge.openAccessibilitySettings();
|
|
1769
|
+
void refreshAccessPreview();
|
|
1770
|
+
}
|
|
1771
|
+
catch (e) { alert('没打开成功,请手动去:系统设置 → 辅助功能 → 已开启的服务 → Bolloon Agent,打开即可。'); }
|
|
1739
1772
|
});
|
|
1740
1773
|
|
|
1741
1774
|
// 本机数据: 直接读 IndexedDB 快照 (智能体/会话/消息都在本机, 重开 App 不会丢)
|
|
@@ -1757,6 +1790,20 @@
|
|
|
1757
1790
|
});
|
|
1758
1791
|
}
|
|
1759
1792
|
$('#settings-desktop').addEventListener('click', openDesktopSync);
|
|
1793
|
+
// 隐私政策 (双入口之一: 设置页; 另一个是首启同意门)
|
|
1794
|
+
const privacyRow = $('#settings-privacy');
|
|
1795
|
+
if (privacyRow) privacyRow.addEventListener('click', openPrivacyPolicy);
|
|
1796
|
+
// 注销: 清除本机数据
|
|
1797
|
+
const wipeRow = $('#settings-wipe');
|
|
1798
|
+
if (wipeRow) wipeRow.addEventListener('click', () => { void wipeLocalDataFlow(); });
|
|
1799
|
+
// 备案号展示 (未备案时如实显示"备案办理中", 不伪造编号)
|
|
1800
|
+
const filingRow = $('#settings-filing');
|
|
1801
|
+
if (filingRow) {
|
|
1802
|
+
const ft = $('#filing-text');
|
|
1803
|
+
const ptxt = privacyCore() && privacyCore().filingText ? privacyCore().filingText() : '';
|
|
1804
|
+
if (ft && ptxt) ft.textContent = ptxt;
|
|
1805
|
+
filingRow.addEventListener('click', () => { const pp = privacyCore(); alert((pp && pp.filingText ? pp.filingText() : 'APP 备案号:备案办理中') + '\n\n备案信息以工信部备案系统公示为准。'); });
|
|
1806
|
+
}
|
|
1760
1807
|
// 本机卡片显示开关 (移除后从这里恢复)
|
|
1761
1808
|
const drawSelfCardToggle = () => {
|
|
1762
1809
|
const on = (() => { try { return localStorage.getItem(SELF_CARD_HIDDEN_KEY) !== '1'; } catch { return true; } })();
|
|
@@ -2842,7 +2889,133 @@
|
|
|
2842
2889
|
});
|
|
2843
2890
|
}
|
|
2844
2891
|
|
|
2892
|
+
// ============ 隐私合规 (2026-09-16): 同意门 / 政策页 / 注销 ============
|
|
2893
|
+
// 上架要求: 首次启动必须先展示隐私政策; 用户同意前不得读取本机数据、不得连网、不得申请任何权限。
|
|
2894
|
+
// 所以 init() 只做一件事 —— 决定"先弹门"还是"进应用"; 真正的初始化在 initApp()。
|
|
2895
|
+
|
|
2896
|
+
function privacyCore() {
|
|
2897
|
+
return (window.BolloonCore && window.BolloonCore.privacy) || null;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
function privacyNeedsConsent() {
|
|
2901
|
+
const p = privacyCore();
|
|
2902
|
+
if (!p || typeof p.needsConsent !== 'function') return true; // 内核未就绪 → 按"需要同意"处理
|
|
2903
|
+
try { return !!p.needsConsent(); } catch (e) { return true; }
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
function escHtml(v) {
|
|
2907
|
+
return String(v == null ? '' : v)
|
|
2908
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
2909
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
/** 政策摘要 HTML (要素来自内核常量, 单测锁了必填项) */
|
|
2913
|
+
function privacySummaryHtml() {
|
|
2914
|
+
const p = privacyCore();
|
|
2915
|
+
const items = (p && p.summary) || [];
|
|
2916
|
+
return items.map((it) => `<h3>${escHtml(it.title)}</h3><p>${escHtml(it.body)}</p>`).join('');
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
function showPrivacyGate() {
|
|
2920
|
+
const p = privacyCore();
|
|
2921
|
+
const t = (p && p.consentText) || {};
|
|
2922
|
+
const gate = $('#privacy-gate');
|
|
2923
|
+
if (!gate) return;
|
|
2924
|
+
const titleEl = $('#privacy-gate-title'), bodyEl = $('#privacy-gate-body');
|
|
2925
|
+
if (titleEl && t.title) titleEl.textContent = t.title;
|
|
2926
|
+
if (bodyEl) bodyEl.textContent = t.intro || '使用前请先阅读并同意隐私政策。';
|
|
2927
|
+
const linkEl = $('#privacy-gate-link');
|
|
2928
|
+
if (linkEl && t.policyLink) linkEl.textContent = t.policyLink + ' ›';
|
|
2929
|
+
const agreeEl = $('#privacy-agree'), declineEl = $('#privacy-decline');
|
|
2930
|
+
if (agreeEl && t.agree) agreeEl.textContent = t.agree;
|
|
2931
|
+
if (declineEl && t.decline) declineEl.textContent = t.decline;
|
|
2932
|
+
if (linkEl) linkEl.onclick = (e) => { e.preventDefault(); openPrivacyPolicy(); };
|
|
2933
|
+
if (agreeEl) agreeEl.onclick = () => {
|
|
2934
|
+
const ok = p && typeof p.grant === 'function' ? p.grant() : false;
|
|
2935
|
+
if (!ok) { /* 本机写不进去(隐私模式/满盘): 不假装同意, 下次启动会再问 */ }
|
|
2936
|
+
hideSheet('#privacy-gate');
|
|
2937
|
+
initApp();
|
|
2938
|
+
};
|
|
2939
|
+
if (declineEl) declineEl.onclick = () => showPrivacyDeclined();
|
|
2940
|
+
gate.hidden = false;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
/** 不同意 → 停在说明页: 不初始化功能、不收集任何东西, 但也不强制退出应用 */
|
|
2944
|
+
function showPrivacyDeclined() {
|
|
2945
|
+
const p = privacyCore();
|
|
2946
|
+
const t = (p && p.consentText) || {};
|
|
2947
|
+
const gate = $('#privacy-gate');
|
|
2948
|
+
if (!gate) return;
|
|
2949
|
+
const titleEl = $('#privacy-gate-title'), bodyEl = $('#privacy-gate-body');
|
|
2950
|
+
if (titleEl) titleEl.textContent = t.declinedTitle || '未同意';
|
|
2951
|
+
if (bodyEl) bodyEl.textContent = t.declinedBody || '未同意隐私政策时应用不会收集任何信息。';
|
|
2952
|
+
const linkEl = $('#privacy-gate-link');
|
|
2953
|
+
if (linkEl) { linkEl.textContent = (t.declinedReread || '重新阅读') + ' ›'; linkEl.onclick = (e) => { e.preventDefault(); showPrivacyGate(); }; }
|
|
2954
|
+
const agreeEl = $('#privacy-agree'), declineEl = $('#privacy-decline');
|
|
2955
|
+
if (agreeEl) { agreeEl.textContent = t.agree || '同意并继续'; agreeEl.onclick = () => { const ok = p && p.grant ? p.grant() : false; if (!ok) {} hideSheet('#privacy-gate'); initApp(); }; }
|
|
2956
|
+
if (declineEl) {
|
|
2957
|
+
declineEl.textContent = t.declinedExit || '退出应用';
|
|
2958
|
+
declineEl.onclick = () => {
|
|
2959
|
+
const cap = window.Capacitor;
|
|
2960
|
+
if (cap && cap.Plugins && cap.Plugins.App && cap.Plugins.App.exitApp) { try { cap.Plugins.App.exitApp(); return; } catch (e) {} }
|
|
2961
|
+
alert('请手动退出应用(返回键 / 上滑关闭)。未同意隐私政策期间应用不会收集任何信息。');
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
gate.hidden = false;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
/** 应用内完整政策 (全屏, 离线可用)。权威版永远是 bolloon.cn/privacy.html (商店表单填的就是它) */
|
|
2968
|
+
function openPrivacyPolicy() {
|
|
2969
|
+
const p = privacyCore();
|
|
2970
|
+
const page = $('#policy-page');
|
|
2971
|
+
const bodyEl = $('#policy-body');
|
|
2972
|
+
if (!page || !bodyEl) return;
|
|
2973
|
+
const t = (p && p.consentText) || {};
|
|
2974
|
+
const url = (p && p.policyUrl) || 'https://bolloon.cn/privacy.html';
|
|
2975
|
+
const contact = (p && p.contact) || '';
|
|
2976
|
+
const filing = p && typeof p.filingText === 'function' ? p.filingText() : '';
|
|
2977
|
+
bodyEl.innerHTML = `
|
|
2978
|
+
<p class="policy-meta">${escHtml(t.intro || '')}</p>
|
|
2979
|
+
${privacySummaryHtml()}
|
|
2980
|
+
<h3>完整政策与备案信息</h3>
|
|
2981
|
+
<p>在线完整版:<a href="${escHtml(url)}" target="_blank" rel="noopener">${escHtml(url)}</a></p>
|
|
2982
|
+
<p>${escHtml(filing)}</p>
|
|
2983
|
+
<p class="policy-meta">联系方式:${escHtml(contact)}(承诺 7 个工作日内答复)</p>
|
|
2984
|
+
<p class="policy-meta">本页内容与在线版本一致;如两者不一致,以在线版本为准。</p>`;
|
|
2985
|
+
const back = $('#policy-back');
|
|
2986
|
+
if (back) back.onclick = () => { page.hidden = true; };
|
|
2987
|
+
page.hidden = false;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
/** 注销: 清除本机全部数据 (身份 DID / 智能体 / 会话消息 / 支付 / 钱包) */
|
|
2991
|
+
async function wipeLocalDataFlow() {
|
|
2992
|
+
if (!confirm('确定清除本机数据(注销)?\n\n将删除本机上的:身份标识(DID)、智能体、会话与消息、钱包账本。\n已写入公开区块链的交易记录无法删除。\n\n此操作不可撤销。')) return;
|
|
2993
|
+
const p = privacyCore();
|
|
2994
|
+
if (!p || typeof p.wipe !== 'function') { alert('清除失败:本机内核未就绪,请重启应用后重试。'); return; }
|
|
2995
|
+
showToast('正在清除本机数据…');
|
|
2996
|
+
try {
|
|
2997
|
+
const r = await p.wipe();
|
|
2998
|
+
const failed = (r && r.failed) || [];
|
|
2999
|
+
const deleted = (r && r.deletedDatabases) || [];
|
|
3000
|
+
const cleared = (r && r.clearedStorageKeys) || [];
|
|
3001
|
+
if (failed.length) {
|
|
3002
|
+
alert(`部分数据未能清除(可重试):\n${failed.join('\n')}\n\n已清除 ${deleted.length} 个数据库 / ${cleared.length} 个本机键。`);
|
|
3003
|
+
} else {
|
|
3004
|
+
alert(`${p.wipeNotice || '已清除本机数据。'}\n\n(本次删除 ${deleted.length} 个数据库、${cleared.length} 个本机键)`);
|
|
3005
|
+
}
|
|
3006
|
+
location.reload(); // 回到首启状态 (同意记录保留, 不再重复弹门)
|
|
3007
|
+
} catch (e) {
|
|
3008
|
+
alert('清除失败:' + ((e && e.message) || e));
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
|
|
2845
3012
|
function init() {
|
|
3013
|
+
// 首启/政策版本变更 → 先弹同意门, 不做任何初始化 (不读本机数据 / 不连网 / 不申请权限)
|
|
3014
|
+
if (privacyNeedsConsent()) { showPrivacyGate(); return; }
|
|
3015
|
+
initApp();
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
function initApp() {
|
|
2846
3019
|
bindMenu();
|
|
2847
3020
|
applyTheme(resolveThemePref(), false);
|
|
2848
3021
|
switchTab('main');
|