@bolloon/bolloon-agent 0.6.6 → 0.6.7
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/ha-daemon.js +85 -0
- package/dist/agents/ha-supervisor.js +146 -0
- package/dist/agents/memory-store.js +25 -0
- package/dist/agents/opportunity-match.js +15 -3
- package/dist/agents/pi-sdk-session-manager.js +8 -0
- package/dist/agents/pi-sdk.js +67 -494
- package/dist/bootstrap/context-collector.js +65 -3
- package/dist/bootstrap/persona-loader.js +26 -0
- package/dist/electron-build/agents/memory-store.js +26 -0
- package/dist/electron-build/agents/memory-store.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-manager.js +8 -0
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +65 -492
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/bootstrap/context-collector.js +66 -3
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/persona-loader.js +27 -0
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
- package/dist/electron-build/llm/pi-ai.js +16 -3
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/system-prompt/registry.js +8 -6
- package/dist/electron-build/llm/system-prompt/registry.js.map +1 -1
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/index.html +19 -4
- package/dist/ios/mobile-agent.js +267 -41
- package/dist/ios/mobile-contacts.js +0 -4
- package/dist/ios/mobile-core.js +806 -30
- package/dist/ios/mobile-memory.js +317 -0
- package/dist/ios/mobile.html +19 -4
- package/dist/ios/mobile.js +144 -30
- package/dist/ios/routes-contacts.js +94 -1
- package/dist/ios/routes-world.js +9 -1
- package/dist/ios/server.js +51 -40
- package/dist/ios/sw.js +1 -1
- package/dist/kernel/gate-scan.js +1 -1
- package/dist/kernel/ha/ha-arbiter.js +177 -0
- package/dist/kernel/ha/ha-guard.js +151 -0
- package/dist/kernel/index.js +3 -0
- package/dist/kernel/plan.js +5 -5
- package/dist/kernel/roster.js +3 -3
- package/dist/llm/pi-ai.js +16 -3
- package/dist/llm/system-prompt/registry.js +8 -6
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/mobile-agent.js +265 -44
- package/dist/web/mobile-core.js +681 -32
- package/dist/web/mobile-memory.js +317 -0
- package/dist/web/mobile.html +4 -4
- package/dist/web/mobile.js +36 -27
- package/dist/web/routes-world.js +9 -1
- package/dist/web/server.js +50 -39
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
package/dist/ios/mobile.js
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
});
|
|
70
70
|
} catch (e) {}
|
|
71
71
|
|
|
72
|
-
const TITLES = { main: '首页', friends: '
|
|
72
|
+
const TITLES = { main: '首页', friends: '通讯录', tasks: '任务', me: '我' };
|
|
73
73
|
let currentTab = 'main';
|
|
74
74
|
function switchTab(tab) {
|
|
75
75
|
currentTab = tab;
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
if (!list) return;
|
|
167
167
|
list.innerHTML = '';
|
|
168
168
|
if (!Array.isArray(peers) || peers.length === 0) {
|
|
169
|
-
list.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-muted)"
|
|
169
|
+
list.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-muted)">暂无联系人</div>';
|
|
170
170
|
const idx = $('#contacts-index'); if (idx) idx.innerHTML = '';
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
@@ -453,15 +453,15 @@
|
|
|
453
453
|
try {
|
|
454
454
|
const s = await api.get('/api/auth/status');
|
|
455
455
|
$('#me-name').textContent = s.loggedIn ? (s.name || '未登录') : '未登录';
|
|
456
|
-
$('#me-did').textContent = s.did ? ('
|
|
456
|
+
$('#me-did').textContent = s.did ? ('身份证明: ' + (s.didShort || s.did)) : '';
|
|
457
457
|
if (avatarEl && !av) avatarEl.textContent = (s.loggedIn && s.name ? s.name : 'U').charAt(0);
|
|
458
458
|
$('#login-label').textContent = s.loggedIn ? '已登录' : '登录';
|
|
459
459
|
} catch (e) { /* 默认 */ }
|
|
460
|
-
//
|
|
460
|
+
// 身份证明 (连接标识)
|
|
461
461
|
try {
|
|
462
462
|
const net = await api.get('/api/network/status');
|
|
463
463
|
const p2p = net && net.nodeId;
|
|
464
|
-
$('#me-p2p').textContent = p2p ? ('
|
|
464
|
+
$('#me-p2p').textContent = p2p ? ('连接标识: ' + p2p.slice(0, 14) + '…') : '';
|
|
465
465
|
} catch (e) { /* 未连接 */ }
|
|
466
466
|
}
|
|
467
467
|
|
|
@@ -488,10 +488,10 @@
|
|
|
488
488
|
<div class="avatar" id="identity-avatar" style="width:72px;height:72px"></div>
|
|
489
489
|
<div><div class="profile-name">${escapeHtml(name)}</div><div style="font-size:12px;color:var(--text-muted)">人类认证身份</div></div>
|
|
490
490
|
</div>
|
|
491
|
-
<div class="identity-row"><div class="k"
|
|
492
|
-
<div class="identity-row"><div class="k"
|
|
491
|
+
<div class="identity-row"><div class="k">身份证明 (全局唯一)</div><div class="v">${escapeHtml(did || '未生成')}</div></div>
|
|
492
|
+
<div class="identity-row"><div class="k">连接标识</div><div class="v">${escapeHtml(p2p || '未连接')}</div></div>
|
|
493
493
|
<div class="identity-row"><div class="k">创建时间</div><div class="v">${escapeHtml(created || '未知')}</div></div>
|
|
494
|
-
<div class="identity-row"><div class="k">说明</div><div class="v"
|
|
494
|
+
<div class="identity-row"><div class="k">说明</div><div class="v">身份证明是全局唯一的人类认证身份; 连接标识是本机的通信编号, 两者不同。</div></div>`;
|
|
495
495
|
const av = localStorage.getItem('bolloon_avatar');
|
|
496
496
|
const ia = body.querySelector('#identity-avatar');
|
|
497
497
|
if (ia) { if (av) { ia.style.backgroundImage = 'url(' + av + ')'; ia.style.backgroundSize = 'cover'; ia.textContent = ''; } else ia.textContent = (name || 'U').charAt(0); }
|
|
@@ -592,9 +592,9 @@
|
|
|
592
592
|
const cards = $('#world-feed-cards');
|
|
593
593
|
if (!feed || !cards) return;
|
|
594
594
|
try {
|
|
595
|
-
// 2026-10-
|
|
596
|
-
//
|
|
597
|
-
const data = await api.get('/api/
|
|
595
|
+
// 2026-10-16 (leo: 首页卡片按「附近 + 通讯录」动态传送): 三源聚合 (桌面机会流 + 通讯录卡 + 附近卡)
|
|
596
|
+
// 不再只拉桌面机会流; 来源不上屏 (leo: 主动推送不贴元解释文案)
|
|
597
|
+
const data = await api.get('/api/world/cards');
|
|
598
598
|
const opps = ((data && data.opportunities) || []).slice(0, 6);
|
|
599
599
|
if (!opps.length) { feed.hidden = true; return; }
|
|
600
600
|
feed.hidden = false;
|
|
@@ -748,8 +748,8 @@
|
|
|
748
748
|
allAgentCards.push({
|
|
749
749
|
id: p.id || p.publicKey?.slice(0, 12) || `peer-${Date.now()}`,
|
|
750
750
|
agentId: p.id || p.publicKey?.slice(0, 12),
|
|
751
|
-
name: p.name || '
|
|
752
|
-
desc: '
|
|
751
|
+
name: p.name || '未知联系人',
|
|
752
|
+
desc: '通讯录智能体',
|
|
753
753
|
avatar: null,
|
|
754
754
|
peer: { name: p.name || '好友', online: p.online || false },
|
|
755
755
|
status: p.online ? 'online' : 'offline',
|
|
@@ -1102,7 +1102,7 @@
|
|
|
1102
1102
|
<div style="flex:1;font-weight:600">搜索</div>
|
|
1103
1103
|
</div>
|
|
1104
1104
|
<div style="padding:12px;display:flex;flex-direction:column;gap:10px;flex:1;min-height:0">
|
|
1105
|
-
<input id="search-input" type="search" autocomplete="off" placeholder="搜智能体 /
|
|
1105
|
+
<input id="search-input" type="search" autocomplete="off" placeholder="搜智能体 / 通讯录:名称、身份证明、编号"
|
|
1106
1106
|
style="padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-hover);color:var(--text);font-size:14px">
|
|
1107
1107
|
<div id="search-hint" style="font-size:12px;color:var(--text-muted)">正在建立索引…</div>
|
|
1108
1108
|
<div id="search-results" style="flex:1;overflow:auto;-webkit-overflow-scrolling:touch"></div>
|
|
@@ -1122,10 +1122,10 @@
|
|
|
1122
1122
|
act: () => { page.remove(); openChat(c); },
|
|
1123
1123
|
})),
|
|
1124
1124
|
...peers.map((p) => ({
|
|
1125
|
-
tag: '
|
|
1125
|
+
tag: '通讯录', name: p.name || String(p.publicKey || p.id || '联系人').slice(0, 12),
|
|
1126
1126
|
sub: String(p.publicKey || p.id || p.address || '').slice(0, 28),
|
|
1127
1127
|
hay: [p.name, p.publicKey, p.id, p.address, p.did].filter(Boolean).join(' '),
|
|
1128
|
-
act: () => alert(
|
|
1128
|
+
act: () => alert(`联系人信息\n名称: ${p.name || '(未命名)'}\n编号: ${p.publicKey || p.id || '—'}\n地址: ${p.address || '—'}`),
|
|
1129
1129
|
})),
|
|
1130
1130
|
...services.map((s) => ({
|
|
1131
1131
|
tag: '全局智能体',
|
|
@@ -1142,10 +1142,10 @@
|
|
|
1142
1142
|
const render = (q) => {
|
|
1143
1143
|
const k = String(q || '').trim().toLowerCase();
|
|
1144
1144
|
const hits = rows.filter((r) => !k || r.hay.includes(k) || r.name.toLowerCase().includes(k));
|
|
1145
|
-
if (hint) hint.innerHTML = `本机智能体 ${channels.length} ·
|
|
1145
|
+
if (hint) hint.innerHTML = `本机智能体 ${channels.length} · 通讯录 ${peers.length} · 全局智能体 ${services.length}` + (k ? ` · 命中 ${hits.length}` : '');
|
|
1146
1146
|
if (!box) return;
|
|
1147
1147
|
if (!hits.length) {
|
|
1148
|
-
box.innerHTML = `<div style="padding:24px;text-align:center;color:var(--text-muted);font-size:13px">没有匹配「${escapeHtml(String(q))}
|
|
1148
|
+
box.innerHTML = `<div style="padding:24px;text-align:center;color:var(--text-muted);font-size:13px">没有匹配「${escapeHtml(String(q))}」的智能体或联系人</div>`;
|
|
1149
1149
|
return;
|
|
1150
1150
|
}
|
|
1151
1151
|
let html = '';
|
|
@@ -2135,11 +2135,12 @@
|
|
|
2135
2135
|
$('#settings-persona').addEventListener('click', () => void openPersonaPage());
|
|
2136
2136
|
}
|
|
2137
2137
|
|
|
2138
|
-
// === 身份页 (
|
|
2138
|
+
// === 身份页 (身份证明 / 签名 / 邮箱 / 手机号) ===
|
|
2139
2139
|
// 2026-10-07: 完整身份系统 — DIAP did:key (与桌面同构) + 附带条件 (邮箱/手机号/备注)
|
|
2140
|
+
// 2026-10-16 (leo: 显示端降低认知负担): 不外露 DID/Ed25519 术语 — 「身份证明」「身份密钥」「签名能力」
|
|
2140
2141
|
async function openIdentityPage() {
|
|
2141
2142
|
if ($('#identity-page')) return;
|
|
2142
|
-
const page = mkOverlayPage('identity-page', '
|
|
2143
|
+
const page = mkOverlayPage('identity-page', '身份与证明', '<div style="font-size:12px;color:var(--text-muted)">读取中…</div>');
|
|
2143
2144
|
const body = page.querySelector('#identity-page-body');
|
|
2144
2145
|
try {
|
|
2145
2146
|
const core = window.BolloonCore || (await import('./mobile-core.js')).core;
|
|
@@ -2147,8 +2148,8 @@
|
|
|
2147
2148
|
const st = await ag.identityStatus();
|
|
2148
2149
|
body.innerHTML = `
|
|
2149
2150
|
<div class="list">
|
|
2150
|
-
<div class="list-item"><span class="list-icon">${ICONS.idcard}</span><span style="flex:1"><span style="display:block"
|
|
2151
|
-
<div class="list-item"><span class="list-icon">${ICONS.chip}</span><span style="flex:1"><span style="display:block"
|
|
2151
|
+
<div class="list-item"><span class="list-icon">${ICONS.idcard}</span><span style="flex:1"><span style="display:block">身份证明</span><span class="conv-preview" style="display:block;word-break:break-all">${escapeHtml(st.did || '未生成')}</span></span></div>
|
|
2152
|
+
<div class="list-item"><span class="list-icon">${ICONS.chip}</span><span style="flex:1"><span style="display:block">身份密钥</span><span class="conv-preview" style="display:block;word-break:break-all">${st.publicKey ? escapeHtml(st.publicKey.slice(0, 32) + '…') : '(老设备哈希身份,无签名)'}</span></span></div>
|
|
2152
2153
|
<div class="list-item"><span class="list-icon">${ICONS.chip}</span><span style="flex:1"><span style="display:block">名字</span><span class="conv-preview" style="display:block">${escapeHtml(st.name || '未登录')}</span></span></div>
|
|
2153
2154
|
</div>
|
|
2154
2155
|
<div class="section-label">附带身份条件(仅存本机)</div>
|
|
@@ -2158,7 +2159,7 @@
|
|
|
2158
2159
|
<div class="list-item"><span class="list-icon">📝</span><span style="flex:1"><span style="display:block">备注</span><span class="conv-preview" id="id-note" style="display:block">${escapeHtml(st.note || '未设置')}</span></span></div>
|
|
2159
2160
|
<button class="sheet-choice" id="id-edit" style="width:100%;margin-top:8px">编辑邮箱 / 手机号 / 备注</button>
|
|
2160
2161
|
</div>
|
|
2161
|
-
<div class="section-label"
|
|
2162
|
+
<div class="section-label">签名能力</div>
|
|
2162
2163
|
<div class="list">
|
|
2163
2164
|
<button class="sheet-choice" id="id-sign-test" style="width:100%">测试签名(本地签 + 验,证明身份可签名)</button>
|
|
2164
2165
|
</div>`;
|
|
@@ -2733,6 +2734,52 @@
|
|
|
2733
2734
|
inp.click();
|
|
2734
2735
|
}
|
|
2735
2736
|
|
|
2737
|
+
// === 手机号搜索添加好友 (2026-10-15, 手机原生手机号功能) ===
|
|
2738
|
+
// 目录数据在电脑端 (human-directory.json), 手机端只转发; 电脑端不可达 → 明确报错不假装。
|
|
2739
|
+
function openPhoneSearch() {
|
|
2740
|
+
hideSheet('#addfriend-sheet');
|
|
2741
|
+
showSheet('#phone-search-sheet');
|
|
2742
|
+
const inp = $('#phone-search-input');
|
|
2743
|
+
if (inp) { inp.value = ''; inp.focus(); }
|
|
2744
|
+
const result = $('#phone-search-result');
|
|
2745
|
+
if (result) { result.style.display = 'none'; result.innerHTML = ''; }
|
|
2746
|
+
}
|
|
2747
|
+
async function doPhoneSearch() {
|
|
2748
|
+
const inp = $('#phone-search-input');
|
|
2749
|
+
const result = $('#phone-search-result');
|
|
2750
|
+
if (!inp || !result) return;
|
|
2751
|
+
const phone = (inp.value || '').replace(/[^\d+]/g, '');
|
|
2752
|
+
if (!/^\+?\d{8,15}$/.test(phone)) { result.style.display = 'block'; result.innerHTML = '<div style="color:var(--danger,#e05555);font-size:13px">请输入正确的手机号 (8-15 位数字)</div>'; return; }
|
|
2753
|
+
result.style.display = 'block';
|
|
2754
|
+
result.innerHTML = '<div style="color:var(--text-secondary);font-size:13px">搜索中…</div>';
|
|
2755
|
+
try {
|
|
2756
|
+
const r = await api.post('/api/phone/directory/lookup', { phone });
|
|
2757
|
+
if (r && r.found) {
|
|
2758
|
+
const name = r.displayName || String(r.did || '').slice(0, 12);
|
|
2759
|
+
result.innerHTML = `<div style="font-size:14px;margin-bottom:6px">✅ 找到账号: <b>${escapeHtml(name)}</b></div>
|
|
2760
|
+
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:10px">身份证明: ${escapeHtml(String(r.did || ''))}</div>
|
|
2761
|
+
<button class="sheet-choice" id="phone-add-btn" style="width:100%">发送好友申请</button>`;
|
|
2762
|
+
const btn = $('#phone-add-btn');
|
|
2763
|
+
if (btn) btn.addEventListener('click', async () => {
|
|
2764
|
+
btn.disabled = true; btn.textContent = '发送中…';
|
|
2765
|
+
const a = await api.post('/api/phone/directory/add-friend', { phone });
|
|
2766
|
+
btn.disabled = false;
|
|
2767
|
+
if (a && a.ok !== false) {
|
|
2768
|
+
btn.textContent = '✅ 已发送 (对方同意后建立好友)';
|
|
2769
|
+
if (a.friend && a.friend.requestId) result.innerHTML += `<div style="font-size:12px;color:var(--text-secondary);margin-top:6px">申请号: ${escapeHtml(String(a.friend.requestId).slice(0, 12))}… 可到「待处理申请」查看</div>`;
|
|
2770
|
+
} else {
|
|
2771
|
+
btn.textContent = '重试发送';
|
|
2772
|
+
result.innerHTML += `<div style="color:var(--danger,#e05555);font-size:12px;margin-top:6px">发送失败: ${escapeHtml((a && a.error) || '未知原因')}</div>`;
|
|
2773
|
+
}
|
|
2774
|
+
});
|
|
2775
|
+
} else {
|
|
2776
|
+
result.innerHTML = '<div style="color:var(--text-secondary);font-size:13px">未找到该手机号的账号 (对方未发布目录或已撤销)。</div>';
|
|
2777
|
+
}
|
|
2778
|
+
} catch (e) {
|
|
2779
|
+
result.innerHTML = `<div style="color:var(--danger,#e05555);font-size:13px">查询失败: ${escapeHtml((e && e.message) || e || '未知错误')}</div>`;
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2736
2783
|
// === 复制 (助记词/私钥/地址 快捷复制; 全局委托 [data-copy]) ===
|
|
2737
2784
|
async function copyText(t) {
|
|
2738
2785
|
const txt = String(t || '');
|
|
@@ -2755,7 +2802,9 @@
|
|
|
2755
2802
|
setTimeout(() => { b.textContent = old; }, 1200);
|
|
2756
2803
|
});
|
|
2757
2804
|
|
|
2758
|
-
// === 登录页 (
|
|
2805
|
+
// === 登录页 (手机号验证码登录 + 昵称兜底) ===
|
|
2806
|
+
// 2026-10-15: 手机原生手机号登录 — 输手机号 → 发验证码 → 验证通过 → 身份绑定手机号。
|
|
2807
|
+
// 验证码链路转发电脑端 /api/contacts/bind + verify (与桌面同一 OTP 通道)。
|
|
2759
2808
|
function openLoginPage() {
|
|
2760
2809
|
const page = document.createElement('div');
|
|
2761
2810
|
page.className = 'chat-page';
|
|
@@ -2766,13 +2815,61 @@
|
|
|
2766
2815
|
<div style="flex:1;font-weight:600">登录</div>
|
|
2767
2816
|
</div>
|
|
2768
2817
|
<div style="padding:12px;display:flex;flex-direction:column;gap:12px">
|
|
2769
|
-
<div style="font-size:13px;color:var(--text-secondary)"
|
|
2818
|
+
<div style="font-size:13px;color:var(--text-secondary)">手机号登录: 输手机号 → 收验证码 → 登录。身份仍由本机 DID 承载, 手机号只是登录/查找凭证。</div>
|
|
2819
|
+
<input id="login-phone" type="tel" inputmode="numeric" placeholder="手机号 (如 13812345678)" style="${_walletInput}">
|
|
2820
|
+
<div style="display:flex;gap:8px">
|
|
2821
|
+
<input id="login-code" inputmode="numeric" placeholder="验证码" style="${_walletInput};flex:1" disabled>
|
|
2822
|
+
<button id="login-sendcode" style="${_walletBtn};width:110px;flex:none">发送验证码</button>
|
|
2823
|
+
</div>
|
|
2824
|
+
<button id="login-submit" style="${_walletBtn}">手机号登录</button>
|
|
2825
|
+
<div style="border-top:1px solid var(--border);margin-top:4px;padding-top:12px;font-size:12px;color:var(--text-secondary)">或者用昵称登录 (本机身份, 不上传):</div>
|
|
2770
2826
|
<input id="login-name" placeholder="昵称 (如 觉者)" style="${_walletInput}">
|
|
2771
|
-
<button id="login-submit" style="${_walletBtn}"
|
|
2827
|
+
<button id="login-name-submit" style="${_walletBtn}">昵称登录</button>
|
|
2772
2828
|
</div>`;
|
|
2773
2829
|
document.body.appendChild(page);
|
|
2774
2830
|
$('#login-back').addEventListener('click', () => page.remove());
|
|
2775
|
-
|
|
2831
|
+
|
|
2832
|
+
// 手机号登录: 登录成功后把手机号写进身份 profile (手机号即凭证)
|
|
2833
|
+
const phoneInput = $('#login-phone');
|
|
2834
|
+
const codeInput = $('#login-code');
|
|
2835
|
+
const sendBtn = $('#login-sendcode');
|
|
2836
|
+
const submitBtn = $('#login-submit');
|
|
2837
|
+
let _pending = { contactId: '', challengeId: '' };
|
|
2838
|
+
sendBtn.addEventListener('click', async () => {
|
|
2839
|
+
const phone = (phoneInput.value || '').replace(/[^\d+]/g, '');
|
|
2840
|
+
if (!/^\+?\d{8,15}$/.test(phone)) { alert('请输入正确的手机号 (8-15 位数字)'); return; }
|
|
2841
|
+
sendBtn.disabled = true; sendBtn.textContent = '发送中…';
|
|
2842
|
+
try {
|
|
2843
|
+
const r = await api.post('/api/phone/auth/bind', { phone });
|
|
2844
|
+
if (!r || r.ok === false) { alert((r && r.error) || '发送失败'); sendBtn.disabled = false; sendBtn.textContent = '发送验证码'; return; }
|
|
2845
|
+
_pending = { contactId: String(r.contactId || ''), challengeId: String(r.challengeId || '') };
|
|
2846
|
+
codeInput.disabled = false; codeInput.focus();
|
|
2847
|
+
sendBtn.textContent = '已发送 ✓';
|
|
2848
|
+
// 本地落盘通道会回显验证码 (方便无短信设备的调试; 真 SMS 通道不回显)
|
|
2849
|
+
if (r.otpForLocalSink) { showToast ? showToast('验证码: ' + String(r.otpForLocalSink)) : alert('验证码: ' + String(r.otpForLocalSink)); }
|
|
2850
|
+
setTimeout(() => { sendBtn.disabled = false; sendBtn.textContent = '重新发送'; }, 30000);
|
|
2851
|
+
} catch (e) { alert('发送失败: ' + (e.message || e)); sendBtn.disabled = false; sendBtn.textContent = '发送验证码'; }
|
|
2852
|
+
});
|
|
2853
|
+
submitBtn.addEventListener('click', async () => {
|
|
2854
|
+
const code = (codeInput.value || '').trim();
|
|
2855
|
+
if (!_pending.contactId || !code) { alert('先发送验证码并输入验证码'); return; }
|
|
2856
|
+
submitBtn.disabled = true; submitBtn.textContent = '验证中…';
|
|
2857
|
+
try {
|
|
2858
|
+
const v = await api.post('/api/phone/auth/verify', { contactId: _pending.contactId, challengeId: _pending.challengeId, code });
|
|
2859
|
+
if (!v || v.ok === false) { alert((v && v.error) || '验证失败'); submitBtn.disabled = false; submitBtn.textContent = '手机号登录'; return; }
|
|
2860
|
+
// 验证通过 → 昵称默认取手机号, 登录本机身份, 手机号写进 profile
|
|
2861
|
+
const phone = (phoneInput.value || '').replace(/[^\d+]/g, '');
|
|
2862
|
+
await api.post('/api/auth/login', { name: '用户' + phone.slice(-4) });
|
|
2863
|
+
try { await api.post('/api/phone/profile/phone', { phone }); } catch { /* 本地 profile 更新失败不阻塞登录 */ }
|
|
2864
|
+
page.remove();
|
|
2865
|
+
await loadMe();
|
|
2866
|
+
switchTab('me');
|
|
2867
|
+
await autoSyncDesktop();
|
|
2868
|
+
} catch (e) { alert('验证失败: ' + (e.message || e)); submitBtn.disabled = false; submitBtn.textContent = '手机号登录'; }
|
|
2869
|
+
});
|
|
2870
|
+
// 手机号 → 本机身份 profile (只存本机; 电脑端目录发布仍需用户在电脑端做)
|
|
2871
|
+
// 昵称登录兜底
|
|
2872
|
+
$('#login-name-submit').addEventListener('click', async () => {
|
|
2776
2873
|
const name = (($('#login-name') || {}).value || '').trim();
|
|
2777
2874
|
try {
|
|
2778
2875
|
await api.post('/api/auth/login', { name });
|
|
@@ -2796,7 +2893,7 @@
|
|
|
2796
2893
|
bindAvatarInput();
|
|
2797
2894
|
$('#item-login').addEventListener('click', () => { openLoginPage(); });
|
|
2798
2895
|
$('#item-logout').addEventListener('click', async () => {
|
|
2799
|
-
if (!confirm('确定注销? 将清除本机登录态 (
|
|
2896
|
+
if (!confirm('确定注销? 将清除本机登录态 (身份证明保留, 不影响频道/连接)。')) return;
|
|
2800
2897
|
try { await api.post('/api/auth/logout', {}); await loadMe(); } catch (e) { alert('注销失败: ' + (e.message || e)); }
|
|
2801
2898
|
});
|
|
2802
2899
|
$('#btn-add').addEventListener('click', addFriend);
|
|
@@ -2813,8 +2910,8 @@
|
|
|
2813
2910
|
if (first) openTradeCall(first); else openTradeHistory();
|
|
2814
2911
|
});
|
|
2815
2912
|
$('#item-p2p-id').addEventListener('click', async () => {
|
|
2816
|
-
try { const net = await api.get('/api/network/status'); const p2p = net && net.nodeId; alert('
|
|
2817
|
-
catch (e) { alert('
|
|
2913
|
+
try { const net = await api.get('/api/network/status'); const p2p = net && net.nodeId; alert('连接标识 (本机通信编号):\n' + (p2p || '未连接')); }
|
|
2914
|
+
catch (e) { alert('连接标识: 获取失败'); }
|
|
2818
2915
|
});
|
|
2819
2916
|
// #1 一键入网 (全球智能体网络): 点一下 → 默认 prompt 交给智能体执行
|
|
2820
2917
|
const joinGlobalBtn = $('#item-join-global');
|
|
@@ -2838,16 +2935,33 @@
|
|
|
2838
2935
|
// #2b 连接好友 (点按式) + 附近设备 + 待处理申请
|
|
2839
2936
|
const addFriendBtn = $('#item-add-friend');
|
|
2840
2937
|
if (addFriendBtn) addFriendBtn.addEventListener('click', () => showSheet('#addfriend-sheet'));
|
|
2938
|
+
const cPhone = $('#choice-phone');
|
|
2939
|
+
if (cPhone) cPhone.addEventListener('click', () => openPhoneSearch());
|
|
2841
2940
|
const cNearby = $('#choice-nearby');
|
|
2842
2941
|
if (cNearby) cNearby.addEventListener('click', () => { hideSheet('#addfriend-sheet'); openNearbySheet('friend'); });
|
|
2843
2942
|
const cRequests = $('#choice-requests');
|
|
2844
2943
|
if (cRequests) cRequests.addEventListener('click', () => { hideSheet('#addfriend-sheet'); openRequestsSheet(); });
|
|
2944
|
+
// #2c 手机号搜索添加好友 sheet 的按键绑定
|
|
2945
|
+
const psDo = $('#phone-search-do');
|
|
2946
|
+
if (psDo) psDo.addEventListener('click', () => { void doPhoneSearch(); });
|
|
2947
|
+
const psClose = $('#phone-search-close');
|
|
2948
|
+
if (psClose) psClose.addEventListener('click', () => hideSheet('#phone-search-sheet'));
|
|
2949
|
+
const psInput = $('#phone-search-input');
|
|
2950
|
+
if (psInput) psInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); void doPhoneSearch(); } });
|
|
2845
2951
|
const nearbyBtn = $('#item-nearby');
|
|
2846
2952
|
if (nearbyBtn) nearbyBtn.addEventListener('click', () => openNearbySheet('friend'));
|
|
2847
2953
|
const nearbyRefresh = $('#nearby-refresh');
|
|
2848
2954
|
if (nearbyRefresh) nearbyRefresh.addEventListener('click', () => loadNearbyList(_nearbyMode));
|
|
2849
2955
|
const nearbyClose = $('#nearby-close');
|
|
2850
2956
|
if (nearbyClose) nearbyClose.addEventListener('click', () => hideSheet('#nearby-sheet'));
|
|
2957
|
+
// 2026-10-16 (leo: 首页卡片动态传送): P2P 收到附近卡 → 即时刷新首页 (不等 45s 轮询)
|
|
2958
|
+
try {
|
|
2959
|
+
window.BolloonCore && window.BolloonCore.events && window.BolloonCore.events.subscribe((m) => {
|
|
2960
|
+
if (m && m.type === 'world-card') loadWorldFeed();
|
|
2961
|
+
});
|
|
2962
|
+
} catch (e) { /* 事件订阅失败不影响其它功能 */ }
|
|
2963
|
+
// 连上对端即要一次卡 (动态传送主动触发; 无 peer 静默)
|
|
2964
|
+
api.get('/api/world/cards/request').then(() => {}).catch(() => {});
|
|
2851
2965
|
// 微信息 (x402 付费信息): 详情 → 购买并验真 / 只看元数据
|
|
2852
2966
|
const x402Buy = $('#x402-buy');
|
|
2853
2967
|
if (x402Buy) x402Buy.addEventListener('click', () => buyX402Info());
|
|
@@ -413,8 +413,101 @@ export function registerContactRoutes(app, opts = {}) {
|
|
|
413
413
|
res.status(500).json({ ok: false, error: String(err?.message || err) });
|
|
414
414
|
}
|
|
415
415
|
});
|
|
416
|
+
// ── 手机号目录 (2026-10-11, phone-account-lookup-design 落点 A) ────────────
|
|
417
|
+
// 隐私: 请求/响应全程只过「盲化哈希」, 明文手机号只在请求体出现一次 (发布/查询),
|
|
418
|
+
// 永不出现在响应里 (与 contacts 既有红线一致)。
|
|
419
|
+
const dirHome = () => opts.home || undefined;
|
|
420
|
+
/** 发布手机号 → DID 指针 (opt-in; 用户显式调用) */
|
|
421
|
+
app.post('/api/contacts/directory/publish', async (req, res) => {
|
|
422
|
+
try {
|
|
423
|
+
const { publishEntry, isPlausiblePhone } = await import('../agents/contacts/human-directory.js');
|
|
424
|
+
const phone = String(req.body?.phone || '');
|
|
425
|
+
const did = String(req.body?.did || ownerDidFromDisk());
|
|
426
|
+
if (!isPlausiblePhone(phone))
|
|
427
|
+
return res.status(400).json({ ok: false, error: '手机号格式不正确' });
|
|
428
|
+
const e = await publishEntry(phone, did, {
|
|
429
|
+
displayName: req.body?.displayName ? String(req.body.displayName) : undefined,
|
|
430
|
+
publicKeyHex: req.body?.publicKeyHex ? String(req.body.publicKeyHex) : undefined,
|
|
431
|
+
homeDir: dirHome(),
|
|
432
|
+
});
|
|
433
|
+
res.json({ ok: true, phoneHash: e.phoneHash.slice(0, 8) + '…', did: e.did, publicKeyHex: e.publicKeyHex || null, publishedAt: e.publishedAt });
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
res.status(500).json({ ok: false, error: String(err?.message || err) });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
/** 撤销目录条目 (软删) */
|
|
440
|
+
app.post('/api/contacts/directory/retract', async (req, res) => {
|
|
441
|
+
try {
|
|
442
|
+
const { retractEntry } = await import('../agents/contacts/human-directory.js');
|
|
443
|
+
const did = String(req.body?.did || ownerDidFromDisk());
|
|
444
|
+
const ok = await retractEntry(did, dirHome());
|
|
445
|
+
res.json({ ok, revoked: ok });
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
res.status(500).json({ ok: false, error: String(err?.message || err) });
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
/** 按手机号查账号 (在本机目录内找; 响应只回 DID/昵称, 绝不回手机号) */
|
|
452
|
+
app.post('/api/contacts/directory/lookup', async (req, res) => {
|
|
453
|
+
try {
|
|
454
|
+
const { lookupPhone, isPlausiblePhone } = await import('../agents/contacts/human-directory.js');
|
|
455
|
+
const phone = String(req.body?.phone || '');
|
|
456
|
+
if (!isPlausiblePhone(phone))
|
|
457
|
+
return res.status(400).json({ ok: false, error: '手机号格式不正确' });
|
|
458
|
+
const r = await lookupPhone(phone, dirHome());
|
|
459
|
+
if (!r.found)
|
|
460
|
+
return res.json({ ok: true, found: false, note: '未找到 (未注册或未公开)' });
|
|
461
|
+
res.json({ ok: true, found: true, did: r.did, publicKeyHex: r.publicKeyHex || null, displayName: r.displayName || null });
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
res.status(500).json({ ok: false, error: String(err?.message || err) });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
/** 按手机号直接发好友请求 (P1, 2026-10-15): directory lookup → publicKeyHex → 既有 /api/friend-request */
|
|
468
|
+
app.post('/api/contacts/directory/add-friend', async (req, res) => {
|
|
469
|
+
try {
|
|
470
|
+
const { lookupPhone, isPlausiblePhone } = await import('../agents/contacts/human-directory.js');
|
|
471
|
+
const phone = String(req.body?.phone || '');
|
|
472
|
+
if (!isPlausiblePhone(phone))
|
|
473
|
+
return res.status(400).json({ ok: false, error: '手机号格式不正确' });
|
|
474
|
+
const r = await lookupPhone(phone, dirHome());
|
|
475
|
+
if (!r.found)
|
|
476
|
+
return res.json({ ok: true, found: false, note: '未找到 (未注册或未公开)' });
|
|
477
|
+
if (!r.publicKeyHex)
|
|
478
|
+
return res.status(422).json({ ok: false, found: true, error: `对方已注册目录但未公开 P2P 公钥 (${r.did?.slice(0, 20)}…), 无法直接加好友` });
|
|
479
|
+
// 委托给既有好友请求链路 (本机端口; 与 pi-sdk accept 工具同口径)
|
|
480
|
+
const port = process.env.PORT || '54188';
|
|
481
|
+
const fr = await fetch(`http://127.0.0.1:${port}/api/friend-request`, {
|
|
482
|
+
method: 'POST',
|
|
483
|
+
headers: { 'Content-Type': 'application/json' },
|
|
484
|
+
body: JSON.stringify({
|
|
485
|
+
targetPublicKey: r.publicKeyHex,
|
|
486
|
+
name: req.body?.name ? String(req.body.name) : r.displayName || undefined,
|
|
487
|
+
message: req.body?.message ? String(req.body.message) : `来自手机号目录: 想加你为好友`,
|
|
488
|
+
note: `via-phone-directory`,
|
|
489
|
+
}),
|
|
490
|
+
});
|
|
491
|
+
const data = await fr.json();
|
|
492
|
+
res.status(fr.status).json({ ok: data.ok !== false, found: true, did: r.did, friend: data });
|
|
493
|
+
}
|
|
494
|
+
catch (err) {
|
|
495
|
+
res.status(500).json({ ok: false, error: String(err?.message || err) });
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
/** 列出已发布条目 (本机审计; 只回脱敏哈希) */
|
|
499
|
+
app.get('/api/contacts/directory/list', async (_req, res) => {
|
|
500
|
+
try {
|
|
501
|
+
const { readDirectory, summarizeEntries } = await import('../agents/contacts/human-directory.js');
|
|
502
|
+
const entries = await readDirectory(dirHome());
|
|
503
|
+
res.json({ ok: true, entries: summarizeEntries(entries) });
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
res.status(500).json({ ok: false, error: String(err?.message || err) });
|
|
507
|
+
}
|
|
508
|
+
});
|
|
416
509
|
// ── 隐藏守卫自检 (验收/测试直接用) ──────────────────────────────────────────
|
|
417
510
|
app.get('/api/contacts/_selfcheck', (_req, res) => {
|
|
418
|
-
res.json({ ok: true, plaintextGuard: looksLikePlaintextSecret({ contacts: [{ value: '+
|
|
511
|
+
res.json({ ok: true, plaintextGuard: looksLikePlaintextSecret({ contacts: [{ value: '+861****5678' }] }) });
|
|
419
512
|
});
|
|
420
513
|
}
|
package/dist/ios/routes-world.js
CHANGED
|
@@ -72,7 +72,15 @@ export function registerWorldRoutes(app) {
|
|
|
72
72
|
const minScore = Number(minScoreRaw);
|
|
73
73
|
const limitRaw = typeof req.query.limit === 'string' ? req.query.limit : '50';
|
|
74
74
|
const limit = Math.max(1, Math.min(200, parseInt(limitRaw, 10) || 50));
|
|
75
|
-
|
|
75
|
+
// 2026-10-16: 通讯录卡片 — dids=<did1>,<did2> 只返回这些 provider 投递的机会 (手机号通讯录联系人)
|
|
76
|
+
const providerDids = typeof req.query.dids === 'string'
|
|
77
|
+
? req.query.dids.split(',').map((d) => d.trim()).filter(Boolean)
|
|
78
|
+
: [];
|
|
79
|
+
const r = await scanOpportunities({
|
|
80
|
+
minScore: Number.isFinite(minScore) ? minScore : 0.4,
|
|
81
|
+
limit,
|
|
82
|
+
providerDids: providerDids.length ? providerDids : undefined,
|
|
83
|
+
});
|
|
76
84
|
if (!r.ok)
|
|
77
85
|
return json(res, 500, { ok: false, error: r.error });
|
|
78
86
|
json(res, 200, { ok: true, count: r.results.length, minScore, opportunities: r.results });
|
package/dist/ios/server.js
CHANGED
|
@@ -1669,6 +1669,30 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1669
1669
|
catch (err) {
|
|
1670
1670
|
console.warn('[runs] 对账失败 (非致命):', err?.message);
|
|
1671
1671
|
}
|
|
1672
|
+
// 2026-10-16 (leo: 双内核冗余 — 独立 kernel 守护进程): HA 双活 — 主进程以 primary 写心跳,
|
|
1673
|
+
// spawn 独立 kernel 守护进程 (standby), 主死 → daemon 接管; daemon 死 → 自动重启。
|
|
1674
|
+
// 环境变量门控: BOLLOON_HA=1 启用 (默认关 — 单节点常规运行不启动额外进程)。
|
|
1675
|
+
if (process.env.BOLLOON_HA === '1') {
|
|
1676
|
+
try {
|
|
1677
|
+
const { createHaSupervisor } = await import('../agents/ha-supervisor.js');
|
|
1678
|
+
const haSup = createHaSupervisor({
|
|
1679
|
+
log: (level, message) => {
|
|
1680
|
+
if (level === 'error')
|
|
1681
|
+
console.warn(message);
|
|
1682
|
+
else if (level === 'warn')
|
|
1683
|
+
console.warn(message);
|
|
1684
|
+
else if (process.env.BOLLOON_VERBOSE === '1')
|
|
1685
|
+
console.log(message);
|
|
1686
|
+
},
|
|
1687
|
+
});
|
|
1688
|
+
await haSup.start();
|
|
1689
|
+
globalThis.__bolloonHaSupervisor = haSup;
|
|
1690
|
+
console.log('[ha] 双活守护已启动 (primary 心跳 + standby kernel 守护进程)');
|
|
1691
|
+
}
|
|
1692
|
+
catch (err) {
|
|
1693
|
+
console.warn('[ha] 双活守护启动失败 (非致命):', err?.message);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1672
1696
|
// 2026-09-16 (M2-B): ExecutionSupervisor —— 长期执行层。
|
|
1673
1697
|
// 页面关掉、进程重启后由它把 Goal 继续下去; 跨进程排他靠 Goal lease (不是内存状态)。
|
|
1674
1698
|
// Harness 管"这一段能不能安全执行", Supervisor 管"这个目标还要不要继续执行"。
|
|
@@ -5167,56 +5191,38 @@ fetchState();
|
|
|
5167
5191
|
const judgmentHint = await buildJudgmentHint(channelForJudgment, channelId);
|
|
5168
5192
|
if (judgmentHint)
|
|
5169
5193
|
contextHint += judgmentHint;
|
|
5170
|
-
// 2026-06-10: 注入 skills
|
|
5171
|
-
//
|
|
5194
|
+
// 2026-06-10: 注入 skills 存在性提示 (2026-10-16 按需化: 删逐条列表 —
|
|
5195
|
+
// pi-sdk buildSkillsIndex 已注入前 40 技能名+描述, 这里只保留存在提示与触发语法)
|
|
5172
5196
|
try {
|
|
5173
|
-
const { loadSkillsFromPaths, defaultSkillPaths
|
|
5197
|
+
const { loadSkillsFromPaths, defaultSkillPaths } = await import('../agents/skill-loader.js');
|
|
5174
5198
|
const paths = defaultSkillPaths();
|
|
5175
5199
|
const skills = await loadSkillsFromPaths(paths);
|
|
5176
5200
|
if (skills.length > 0) {
|
|
5177
|
-
contextHint += `[系统上下文]
|
|
5178
|
-
for (const s of skills.slice(0, 20)) { // 上限 20 避免 prompt 过长
|
|
5179
|
-
const desc = (s.description || '').slice(0, 80);
|
|
5180
|
-
contextHint += ` - /${s.name}${desc ? ' — ' + desc : ''}\n`;
|
|
5181
|
-
}
|
|
5182
|
-
contextHint += '调用语法: 用户说 "/技能名 ..." 或 你回复时建议 "/技能名 ..." 让用户主动触发.\n\n';
|
|
5201
|
+
contextHint += `[系统上下文] 本机已加载 ${skills.length} 个 skills (技能名+描述见上方技能库索引; 需要详情时用 list_skills / read_skill 按需读取). 调用语法: 用户说 "/技能名 ..." 或 你回复时建议 "/技能名 ..." 让用户主动触发.\n\n`;
|
|
5183
5202
|
}
|
|
5184
5203
|
}
|
|
5185
5204
|
catch (err) {
|
|
5186
5205
|
// 静默失败 — skills 不是核心, 加载失败不阻塞
|
|
5187
5206
|
}
|
|
5188
|
-
// 2026-06-10:
|
|
5189
|
-
//
|
|
5207
|
+
// 2026-06-10: human values 存在性提示 (2026-10-16 按需化: 删 8 条摘要列表 —
|
|
5208
|
+
// buildJudgmentHint 已注入绑定判断力 + 候选, 这里只保留"库里有多少"的指针)
|
|
5190
5209
|
try {
|
|
5191
5210
|
const { loadAllJudgments } = await import('../pi-ecosystem-judgment/human-value-store.js');
|
|
5192
5211
|
const allJudgments = await loadAllJudgments().catch(() => []);
|
|
5193
|
-
// 把所有 judgment 视作软参考 (跟 buildJudgmentHint 的 candidates 同理)
|
|
5194
5212
|
if (Array.isArray(allJudgments) && allJudgments.length > 0) {
|
|
5195
|
-
contextHint += `[系统上下文]
|
|
5196
|
-
for (const j of allJudgments.slice(0, 8)) {
|
|
5197
|
-
const decision = (j.decision || '').slice(0, 80);
|
|
5198
|
-
contextHint += ` - ${decision}\n`;
|
|
5199
|
-
}
|
|
5200
|
-
contextHint += '\n';
|
|
5213
|
+
contextHint += `[系统上下文] 判断力库存 ${allJudgments.length} 条历史 judgment (绑定判断力见上方; 需要更多时用读取判断力的工具按需取, 软参考体现而非复述).\n\n`;
|
|
5201
5214
|
}
|
|
5202
5215
|
}
|
|
5203
5216
|
catch (err) {
|
|
5204
5217
|
// 静默失败
|
|
5205
5218
|
}
|
|
5206
|
-
// 2026-06-10:
|
|
5207
|
-
//
|
|
5219
|
+
// 2026-06-10: documents 存在性提示 (2026-10-16 按需化: 删逐条元数据 —
|
|
5220
|
+
// 只留计数, 用户提到某文档时用读文档工具按需读取)
|
|
5208
5221
|
try {
|
|
5209
5222
|
const { documentStore } = await import('../documents/store.js');
|
|
5210
5223
|
const docs = await documentStore.getReceivedDocuments(50).catch(() => []);
|
|
5211
5224
|
if (Array.isArray(docs) && docs.length > 0) {
|
|
5212
|
-
contextHint += `[系统上下文] 本机 documents
|
|
5213
|
-
for (const d of docs.slice(0, 10)) {
|
|
5214
|
-
const name = d.fileName || d.id || '(未命名)';
|
|
5215
|
-
const size = d.fileSize ? ` (${Math.round(d.fileSize / 1024)}KB)` : '';
|
|
5216
|
-
const sender = d.fromNodeId ? ` [来自 ${d.fromNodeIdShort || d.fromNodeId.substring(0, 8)}…]` : '';
|
|
5217
|
-
contextHint += ` - ${name}${size}${sender}\n`;
|
|
5218
|
-
}
|
|
5219
|
-
contextHint += '用户提到某文档时, 你可以调用读文档工具读取并总结.\n\n';
|
|
5225
|
+
contextHint += `[系统上下文] 本机 documents ${docs.length} 篇 (用户提到某文档时, 用读文档工具按需读取并总结).\n\n`;
|
|
5220
5226
|
}
|
|
5221
5227
|
}
|
|
5222
5228
|
catch (err) {
|
|
@@ -5282,8 +5288,10 @@ fetchState();
|
|
|
5282
5288
|
catch { /* 无 sessions 目录 */ }
|
|
5283
5289
|
}
|
|
5284
5290
|
if (summariesToRead.length > 0) {
|
|
5285
|
-
|
|
5286
|
-
|
|
5291
|
+
// 2026-10-16 按需化: 历史记忆只注尾部 800 chars 作为"上次做到哪"的锚,
|
|
5292
|
+
// 完整摘要按需用 read 工具取 (记忆是引用而非复述, 全量 2500 常驻浪费)
|
|
5293
|
+
const memBlock = summariesToRead.map(s => s.trim().slice(-800)).join('\n\n---\n\n');
|
|
5294
|
+
contextHint += `[系统上下文] 动态状态层 · chat-worksite (上次做到哪 — 本 channel 历史记忆尾部摘录, 来自 memory-compressor 摘要, 引用而非复述):\n${memBlock.slice(-800)}\n\n`;
|
|
5287
5295
|
}
|
|
5288
5296
|
else {
|
|
5289
5297
|
// 2026-08-08 (DID 目录读侧): 磁盘无摘要时, 回退读 DID 目录 memory 表 (跨设备同步来的记忆)
|
|
@@ -5294,11 +5302,11 @@ fetchState();
|
|
|
5294
5302
|
const rows = catalogMemoryRows(cat, agentId);
|
|
5295
5303
|
if (rows.length > 0) {
|
|
5296
5304
|
const memBlock = rows.slice(0, 2)
|
|
5297
|
-
.map(({ row }) => String(row.data.summary || '').slice(-
|
|
5305
|
+
.map(({ row }) => String(row.data.summary || '').slice(-800))
|
|
5298
5306
|
.filter(Boolean)
|
|
5299
5307
|
.join('\n\n---\n\n');
|
|
5300
5308
|
if (memBlock) {
|
|
5301
|
-
contextHint += `[系统上下文] 动态状态层 · chat-worksite (DID
|
|
5309
|
+
contextHint += `[系统上下文] 动态状态层 · chat-worksite (DID 目录同步记忆尾部摘录, 引用而非复述):\n${memBlock.slice(-800)}\n\n`;
|
|
5302
5310
|
}
|
|
5303
5311
|
}
|
|
5304
5312
|
}
|
|
@@ -5339,24 +5347,27 @@ fetchState();
|
|
|
5339
5347
|
}
|
|
5340
5348
|
const linkedIds = channelForJudgment?.linkedDocumentIds;
|
|
5341
5349
|
if (Array.isArray(linkedIds) && linkedIds.length > 0) {
|
|
5350
|
+
// 2026-10-16 按需化: 关联文档不再每轮全文注入 (10篇×1.5KB=15KB 常驻 prompt),
|
|
5351
|
+
// 只列文件名指针, 用户提到某文档时用读文档工具按需读取。
|
|
5352
|
+
// 关联文档本身不常在单轮对话中被用到, 全量注入是浪费。
|
|
5342
5353
|
try {
|
|
5343
5354
|
const { documentStore } = await import('../documents/store.js');
|
|
5344
|
-
|
|
5345
|
-
let loaded = 0;
|
|
5355
|
+
const names = [];
|
|
5346
5356
|
for (const docId of linkedIds.slice(0, 10)) {
|
|
5347
5357
|
const doc = await documentStore.readDocument(docId).catch(() => null);
|
|
5348
5358
|
if (!doc)
|
|
5349
5359
|
continue;
|
|
5350
5360
|
const name = doc.metadata?.fileName || docId;
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5361
|
+
names.push(name);
|
|
5362
|
+
if (names.length >= 5)
|
|
5363
|
+
break; // 指针只列前 5, 其余计数
|
|
5354
5364
|
}
|
|
5355
|
-
if (
|
|
5356
|
-
|
|
5365
|
+
if (names.length > 0) {
|
|
5366
|
+
const more = linkedIds.length > names.length ? ` (共 ${linkedIds.length} 篇)` : '';
|
|
5367
|
+
contextHint += `[系统上下文] 此 channel 关联了 ${linkedIds.length} 篇文档 — ${names.join('、')}${more}. 用户提到某篇时, 用读文档工具按需读取全文再回答.\n\n`;
|
|
5357
5368
|
}
|
|
5358
5369
|
else {
|
|
5359
|
-
contextHint +=
|
|
5370
|
+
contextHint += `[系统上下文] 此 channel 关联了 ${linkedIds.length} 篇文档 (内容读取失败, 用读文档工具按需读取).\n\n`;
|
|
5360
5371
|
}
|
|
5361
5372
|
}
|
|
5362
5373
|
catch (err) {
|
package/dist/ios/sw.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 现在: 代码类资源 (html/js/css/json) 一律 **network-first** (拿不到网才回退缓存),
|
|
7
7
|
* 缓存名带版本号, activate 时清掉所有旧缓存; 图标等静态资源仍 cache-first。
|
|
8
8
|
*/
|
|
9
|
-
const CACHE = 'bolloon-mobile-v0.6.
|
|
9
|
+
const CACHE = 'bolloon-mobile-v0.6.6';
|
|
10
10
|
const SHELL = [
|
|
11
11
|
'./mobile.html',
|
|
12
12
|
'./mobile.css',
|