@bolloon/bolloon-agent 0.4.24 → 0.4.26
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 +446 -0
- package/dist/agents/external-events.js +162 -0
- package/dist/agents/goal-criteria.js +124 -0
- package/dist/agents/goal-store.js +526 -0
- package/dist/agents/pi-harness.js +263 -0
- package/dist/agents/pi-sdk.js +607 -126
- package/dist/agents/run-store.js +772 -0
- package/dist/agents/runner-resolver.js +225 -0
- package/dist/agents/skill-readiness.js +133 -0
- package/dist/agents/skill-supervisor-link.js +70 -0
- package/dist/agents/skills-manager.js +717 -0
- package/dist/agents/supervisor-host.js +249 -0
- package/dist/cli/setup-wizard.js +96 -127
- package/dist/cron/tick-lock.js +1 -1
- package/dist/electron/first-run.js +33 -2
- package/dist/electron-build/electron/first-run.js +35 -2
- package/dist/electron-build/electron/first-run.js.map +1 -1
- package/dist/index.js +549 -26
- 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 +21 -1
- package/dist/ios/manifest.json +1 -1
- package/dist/ios/mobile-agent.js +195 -1
- package/dist/ios/mobile-core.js +24876 -24723
- package/dist/ios/mobile.css +15 -0
- package/dist/ios/mobile.html +21 -1
- package/dist/ios/mobile.js +143 -0
- package/dist/ios/server.js +51 -4
- package/dist/llm/config-store.js +35 -4
- package/dist/network/agent-network.js +10 -0
- package/dist/network/goal-event-bridge.js +57 -0
- package/dist/setup/onboard.js +549 -0
- package/dist/setup/setup-store.js +592 -0
- 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 +2 -2
- package/dist/web/mobile-core.js +24884 -24726
- 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 +633 -0
- package/package.json +2 -2
package/dist/ios/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/ios/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/ios/mobile.js
CHANGED
|
@@ -1717,6 +1717,9 @@
|
|
|
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);
|
|
@@ -1757,6 +1760,20 @@
|
|
|
1757
1760
|
});
|
|
1758
1761
|
}
|
|
1759
1762
|
$('#settings-desktop').addEventListener('click', openDesktopSync);
|
|
1763
|
+
// 隐私政策 (双入口之一: 设置页; 另一个是首启同意门)
|
|
1764
|
+
const privacyRow = $('#settings-privacy');
|
|
1765
|
+
if (privacyRow) privacyRow.addEventListener('click', openPrivacyPolicy);
|
|
1766
|
+
// 注销: 清除本机数据
|
|
1767
|
+
const wipeRow = $('#settings-wipe');
|
|
1768
|
+
if (wipeRow) wipeRow.addEventListener('click', () => { void wipeLocalDataFlow(); });
|
|
1769
|
+
// 备案号展示 (未备案时如实显示"备案办理中", 不伪造编号)
|
|
1770
|
+
const filingRow = $('#settings-filing');
|
|
1771
|
+
if (filingRow) {
|
|
1772
|
+
const ft = $('#filing-text');
|
|
1773
|
+
const ptxt = privacyCore() && privacyCore().filingText ? privacyCore().filingText() : '';
|
|
1774
|
+
if (ft && ptxt) ft.textContent = ptxt;
|
|
1775
|
+
filingRow.addEventListener('click', () => { const pp = privacyCore(); alert((pp && pp.filingText ? pp.filingText() : 'APP 备案号:备案办理中') + '\n\n备案信息以工信部备案系统公示为准。'); });
|
|
1776
|
+
}
|
|
1760
1777
|
// 本机卡片显示开关 (移除后从这里恢复)
|
|
1761
1778
|
const drawSelfCardToggle = () => {
|
|
1762
1779
|
const on = (() => { try { return localStorage.getItem(SELF_CARD_HIDDEN_KEY) !== '1'; } catch { return true; } })();
|
|
@@ -2842,7 +2859,133 @@
|
|
|
2842
2859
|
});
|
|
2843
2860
|
}
|
|
2844
2861
|
|
|
2862
|
+
// ============ 隐私合规 (2026-09-16): 同意门 / 政策页 / 注销 ============
|
|
2863
|
+
// 上架要求: 首次启动必须先展示隐私政策; 用户同意前不得读取本机数据、不得连网、不得申请任何权限。
|
|
2864
|
+
// 所以 init() 只做一件事 —— 决定"先弹门"还是"进应用"; 真正的初始化在 initApp()。
|
|
2865
|
+
|
|
2866
|
+
function privacyCore() {
|
|
2867
|
+
return (window.BolloonCore && window.BolloonCore.privacy) || null;
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
function privacyNeedsConsent() {
|
|
2871
|
+
const p = privacyCore();
|
|
2872
|
+
if (!p || typeof p.needsConsent !== 'function') return true; // 内核未就绪 → 按"需要同意"处理
|
|
2873
|
+
try { return !!p.needsConsent(); } catch (e) { return true; }
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
function escHtml(v) {
|
|
2877
|
+
return String(v == null ? '' : v)
|
|
2878
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
2879
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
/** 政策摘要 HTML (要素来自内核常量, 单测锁了必填项) */
|
|
2883
|
+
function privacySummaryHtml() {
|
|
2884
|
+
const p = privacyCore();
|
|
2885
|
+
const items = (p && p.summary) || [];
|
|
2886
|
+
return items.map((it) => `<h3>${escHtml(it.title)}</h3><p>${escHtml(it.body)}</p>`).join('');
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
function showPrivacyGate() {
|
|
2890
|
+
const p = privacyCore();
|
|
2891
|
+
const t = (p && p.consentText) || {};
|
|
2892
|
+
const gate = $('#privacy-gate');
|
|
2893
|
+
if (!gate) return;
|
|
2894
|
+
const titleEl = $('#privacy-gate-title'), bodyEl = $('#privacy-gate-body');
|
|
2895
|
+
if (titleEl && t.title) titleEl.textContent = t.title;
|
|
2896
|
+
if (bodyEl) bodyEl.textContent = t.intro || '使用前请先阅读并同意隐私政策。';
|
|
2897
|
+
const linkEl = $('#privacy-gate-link');
|
|
2898
|
+
if (linkEl && t.policyLink) linkEl.textContent = t.policyLink + ' ›';
|
|
2899
|
+
const agreeEl = $('#privacy-agree'), declineEl = $('#privacy-decline');
|
|
2900
|
+
if (agreeEl && t.agree) agreeEl.textContent = t.agree;
|
|
2901
|
+
if (declineEl && t.decline) declineEl.textContent = t.decline;
|
|
2902
|
+
if (linkEl) linkEl.onclick = (e) => { e.preventDefault(); openPrivacyPolicy(); };
|
|
2903
|
+
if (agreeEl) agreeEl.onclick = () => {
|
|
2904
|
+
const ok = p && typeof p.grant === 'function' ? p.grant() : false;
|
|
2905
|
+
if (!ok) { /* 本机写不进去(隐私模式/满盘): 不假装同意, 下次启动会再问 */ }
|
|
2906
|
+
hideSheet('#privacy-gate');
|
|
2907
|
+
initApp();
|
|
2908
|
+
};
|
|
2909
|
+
if (declineEl) declineEl.onclick = () => showPrivacyDeclined();
|
|
2910
|
+
gate.hidden = false;
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
/** 不同意 → 停在说明页: 不初始化功能、不收集任何东西, 但也不强制退出应用 */
|
|
2914
|
+
function showPrivacyDeclined() {
|
|
2915
|
+
const p = privacyCore();
|
|
2916
|
+
const t = (p && p.consentText) || {};
|
|
2917
|
+
const gate = $('#privacy-gate');
|
|
2918
|
+
if (!gate) return;
|
|
2919
|
+
const titleEl = $('#privacy-gate-title'), bodyEl = $('#privacy-gate-body');
|
|
2920
|
+
if (titleEl) titleEl.textContent = t.declinedTitle || '未同意';
|
|
2921
|
+
if (bodyEl) bodyEl.textContent = t.declinedBody || '未同意隐私政策时应用不会收集任何信息。';
|
|
2922
|
+
const linkEl = $('#privacy-gate-link');
|
|
2923
|
+
if (linkEl) { linkEl.textContent = (t.declinedReread || '重新阅读') + ' ›'; linkEl.onclick = (e) => { e.preventDefault(); showPrivacyGate(); }; }
|
|
2924
|
+
const agreeEl = $('#privacy-agree'), declineEl = $('#privacy-decline');
|
|
2925
|
+
if (agreeEl) { agreeEl.textContent = t.agree || '同意并继续'; agreeEl.onclick = () => { const ok = p && p.grant ? p.grant() : false; if (!ok) {} hideSheet('#privacy-gate'); initApp(); }; }
|
|
2926
|
+
if (declineEl) {
|
|
2927
|
+
declineEl.textContent = t.declinedExit || '退出应用';
|
|
2928
|
+
declineEl.onclick = () => {
|
|
2929
|
+
const cap = window.Capacitor;
|
|
2930
|
+
if (cap && cap.Plugins && cap.Plugins.App && cap.Plugins.App.exitApp) { try { cap.Plugins.App.exitApp(); return; } catch (e) {} }
|
|
2931
|
+
alert('请手动退出应用(返回键 / 上滑关闭)。未同意隐私政策期间应用不会收集任何信息。');
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
gate.hidden = false;
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
/** 应用内完整政策 (全屏, 离线可用)。权威版永远是 bolloon.cn/privacy.html (商店表单填的就是它) */
|
|
2938
|
+
function openPrivacyPolicy() {
|
|
2939
|
+
const p = privacyCore();
|
|
2940
|
+
const page = $('#policy-page');
|
|
2941
|
+
const bodyEl = $('#policy-body');
|
|
2942
|
+
if (!page || !bodyEl) return;
|
|
2943
|
+
const t = (p && p.consentText) || {};
|
|
2944
|
+
const url = (p && p.policyUrl) || 'https://bolloon.cn/privacy.html';
|
|
2945
|
+
const contact = (p && p.contact) || '';
|
|
2946
|
+
const filing = p && typeof p.filingText === 'function' ? p.filingText() : '';
|
|
2947
|
+
bodyEl.innerHTML = `
|
|
2948
|
+
<p class="policy-meta">${escHtml(t.intro || '')}</p>
|
|
2949
|
+
${privacySummaryHtml()}
|
|
2950
|
+
<h3>完整政策与备案信息</h3>
|
|
2951
|
+
<p>在线完整版:<a href="${escHtml(url)}" target="_blank" rel="noopener">${escHtml(url)}</a></p>
|
|
2952
|
+
<p>${escHtml(filing)}</p>
|
|
2953
|
+
<p class="policy-meta">联系方式:${escHtml(contact)}(承诺 7 个工作日内答复)</p>
|
|
2954
|
+
<p class="policy-meta">本页内容与在线版本一致;如两者不一致,以在线版本为准。</p>`;
|
|
2955
|
+
const back = $('#policy-back');
|
|
2956
|
+
if (back) back.onclick = () => { page.hidden = true; };
|
|
2957
|
+
page.hidden = false;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
/** 注销: 清除本机全部数据 (身份 DID / 智能体 / 会话消息 / 支付 / 钱包) */
|
|
2961
|
+
async function wipeLocalDataFlow() {
|
|
2962
|
+
if (!confirm('确定清除本机数据(注销)?\n\n将删除本机上的:身份标识(DID)、智能体、会话与消息、钱包账本。\n已写入公开区块链的交易记录无法删除。\n\n此操作不可撤销。')) return;
|
|
2963
|
+
const p = privacyCore();
|
|
2964
|
+
if (!p || typeof p.wipe !== 'function') { alert('清除失败:本机内核未就绪,请重启应用后重试。'); return; }
|
|
2965
|
+
showToast('正在清除本机数据…');
|
|
2966
|
+
try {
|
|
2967
|
+
const r = await p.wipe();
|
|
2968
|
+
const failed = (r && r.failed) || [];
|
|
2969
|
+
const deleted = (r && r.deletedDatabases) || [];
|
|
2970
|
+
const cleared = (r && r.clearedStorageKeys) || [];
|
|
2971
|
+
if (failed.length) {
|
|
2972
|
+
alert(`部分数据未能清除(可重试):\n${failed.join('\n')}\n\n已清除 ${deleted.length} 个数据库 / ${cleared.length} 个本机键。`);
|
|
2973
|
+
} else {
|
|
2974
|
+
alert(`${p.wipeNotice || '已清除本机数据。'}\n\n(本次删除 ${deleted.length} 个数据库、${cleared.length} 个本机键)`);
|
|
2975
|
+
}
|
|
2976
|
+
location.reload(); // 回到首启状态 (同意记录保留, 不再重复弹门)
|
|
2977
|
+
} catch (e) {
|
|
2978
|
+
alert('清除失败:' + ((e && e.message) || e));
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2845
2982
|
function init() {
|
|
2983
|
+
// 首启/政策版本变更 → 先弹同意门, 不做任何初始化 (不读本机数据 / 不连网 / 不申请权限)
|
|
2984
|
+
if (privacyNeedsConsent()) { showPrivacyGate(); return; }
|
|
2985
|
+
initApp();
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
function initApp() {
|
|
2846
2989
|
bindMenu();
|
|
2847
2990
|
applyTheme(resolveThemePref(), false);
|
|
2848
2991
|
switchTab('main');
|
package/dist/ios/server.js
CHANGED
|
@@ -3020,14 +3020,61 @@ ${goalDesc}
|
|
|
3020
3020
|
// 2026-09-15: agent-delegate (manifest 协议 + agent_delegate) **启动即挂载**
|
|
3021
3021
|
// 文档 bolloon-gateway-join.md 第 3 节要求 POST /api/agent/register 可用;
|
|
3022
3022
|
// 之前只在 iroh 懒初始化 (/api/iroh/info 首次访问) 的收尾挂上 → 没触发时恒 404。
|
|
3023
|
+
//
|
|
3024
|
+
// 2026-09-15 (二修): 同时注入**真执行器** —— 被委派的一方不再是
|
|
3025
|
+
// `resultCid: mock-<ts>` 的假签收: 真的跑本地 agent session 出结果,
|
|
3026
|
+
// 并把结果存进 OrbitDB 得到真实内容寻址 CID; 无活跃 channel / 无 LLM 时如实报错。
|
|
3027
|
+
const delegateExecutor = async (req) => {
|
|
3028
|
+
try {
|
|
3029
|
+
const channels = await loadChannels();
|
|
3030
|
+
const active = (() => {
|
|
3031
|
+
try {
|
|
3032
|
+
const raw = fsSync.existsSync(path.join(process.env.HOME || '/tmp', '.bolloon', 'active-channel.json'))
|
|
3033
|
+
? fsSync.readFileSync(path.join(process.env.HOME || '/tmp', '.bolloon', 'active-channel.json'), 'utf-8') : '';
|
|
3034
|
+
return raw ? JSON.parse(raw) : null;
|
|
3035
|
+
}
|
|
3036
|
+
catch {
|
|
3037
|
+
return null;
|
|
3038
|
+
}
|
|
3039
|
+
})();
|
|
3040
|
+
const ch = channels.find((c) => c.id === active?.channelId) || channels.find((c) => c.id === active?.id) || channels[0];
|
|
3041
|
+
if (!ch)
|
|
3042
|
+
return { ok: false, summary: 'no local channel to execute with', error: 'no-channel' };
|
|
3043
|
+
const agent = await getAgentForChannel(ch.id, ch.publicKey, ch.name, ch.didDocument);
|
|
3044
|
+
const task = [
|
|
3045
|
+
`【被委派任务 · 能力 ${req.capability}】`,
|
|
3046
|
+
req.instruction,
|
|
3047
|
+
req.docPath ? `\n资料路径: ${req.docPath}` : '',
|
|
3048
|
+
req.docContent ? `\n资料内容:\n${req.docContent}` : '',
|
|
3049
|
+
`\n(来自 agent ${req.fromAgentId || 'unknown'}),请直接给出可交付结果。`,
|
|
3050
|
+
].join('');
|
|
3051
|
+
const out = await agent.prompt(task);
|
|
3052
|
+
const text = String(out || '').trim();
|
|
3053
|
+
if (!text || text.startsWith('❌') || text.startsWith('[AI 服务调用失败]')) {
|
|
3054
|
+
return { ok: false, summary: text.slice(0, 800) || '(空结果)', error: 'execution-failed' };
|
|
3055
|
+
}
|
|
3056
|
+
// 真结果 → 内容寻址存储 (CID), 失败不编造 CID, 只回摘要
|
|
3057
|
+
let cid;
|
|
3058
|
+
try {
|
|
3059
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
3060
|
+
const rec = await getCIDDatabase().save({ agentId: req.targetAgentId, type: 'context', content: text, metadata: { capability: req.capability, fromAgentId: req.fromAgentId, kind: 'delegate-result' } });
|
|
3061
|
+
cid = rec?.id;
|
|
3062
|
+
}
|
|
3063
|
+
catch { /* 存不上就不给 CID */ }
|
|
3064
|
+
return { ok: true, summary: text.slice(0, 4000), resultCid: cid };
|
|
3065
|
+
}
|
|
3066
|
+
catch (e) {
|
|
3067
|
+
return { ok: false, summary: `delegate executor failed: ${String(e?.message || e).slice(0, 300)}`, error: 'executor-error' };
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3023
3070
|
let agentDelegateMounted = false;
|
|
3024
3071
|
try {
|
|
3025
3072
|
const delegateTransport = createIrohDelegateTransport({ verbose: true });
|
|
3026
3073
|
// 注意: createAgentDelegateApp 内部声明的是绝对路径 (/api/agent/...),
|
|
3027
3074
|
// 所以挂载时**不能**再带 '/api/agent' 前缀 (否则变成 /api/agent/api/agent/... 恒 404)
|
|
3028
|
-
app.use(createAgentDelegateApp(delegateTransport));
|
|
3075
|
+
app.use(createAgentDelegateApp(delegateTransport, { execute: delegateExecutor }));
|
|
3029
3076
|
agentDelegateMounted = true;
|
|
3030
|
-
console.log('[agent-delegate] 已挂载到 /api/agent (启动即用: local-manifest / register / pick / delegate)');
|
|
3077
|
+
console.log('[agent-delegate] 已挂载到 /api/agent (启动即用: local-manifest / register / pick / delegate, 真执行器已注入)');
|
|
3031
3078
|
}
|
|
3032
3079
|
catch (e) {
|
|
3033
3080
|
console.warn('[agent-delegate] 挂载失败 (非致命):', e?.message);
|
|
@@ -6588,10 +6635,10 @@ ${goalDesc}
|
|
|
6588
6635
|
if (!agentDelegateMounted) {
|
|
6589
6636
|
try {
|
|
6590
6637
|
const delegateTransport = createIrohDelegateTransport({ verbose: true });
|
|
6591
|
-
const delegateApp = createAgentDelegateApp(delegateTransport);
|
|
6638
|
+
const delegateApp = createAgentDelegateApp(delegateTransport, { execute: delegateExecutor });
|
|
6592
6639
|
app.use(delegateApp);
|
|
6593
6640
|
agentDelegateMounted = true;
|
|
6594
|
-
console.log('[iroh API] agent-delegate app 已挂载到 /api/agent (
|
|
6641
|
+
console.log('[iroh API] agent-delegate app 已挂载到 /api/agent (补挂, 真执行器已注入)');
|
|
6595
6642
|
}
|
|
6596
6643
|
catch (e) {
|
|
6597
6644
|
console.error('[iroh API] 挂载 agent-delegate app 失败:', e);
|
package/dist/llm/config-store.js
CHANGED
|
@@ -4,11 +4,19 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import * as fsSync from 'fs';
|
|
9
|
+
/** 2026-09-16 (M1): 不再在模块加载时固定 HOME —— 独立宿主/测试注入/长驻进程都走同一个解析 */
|
|
10
|
+
function configDir() {
|
|
11
|
+
const env = process.env.BOLLOON_HOME?.trim();
|
|
12
|
+
if (env)
|
|
13
|
+
return env;
|
|
14
|
+
return path.join(process.env.HOME || os.homedir() || '/tmp', '.bolloon');
|
|
15
|
+
}
|
|
8
16
|
// 2026-08-07: llm-config.json → bolloon-config.json (统一配置文件名, Bolloon 自己有写权限)
|
|
9
17
|
// initialize() 会做一次迁移: 旧文件存在且新文件不存在 → 复制旧内容, 之后统一读写新文件
|
|
10
|
-
const CONFIG_PATH = path.join(
|
|
11
|
-
const LEGACY_CONFIG_PATH = path.join(
|
|
18
|
+
const CONFIG_PATH = path.join(configDir(), 'bolloon-config.json');
|
|
19
|
+
const LEGACY_CONFIG_PATH = path.join(configDir(), 'llm-config.json');
|
|
12
20
|
export const DEFAULT_PROVIDER_CONFIGS = {
|
|
13
21
|
openai: {
|
|
14
22
|
enabled: false,
|
|
@@ -231,6 +239,10 @@ function getDefaultConfig() {
|
|
|
231
239
|
class LLMConfigStore {
|
|
232
240
|
config = null;
|
|
233
241
|
initialized = false;
|
|
242
|
+
/** 2026-09-16: 记住加载时用的配置目录 —— 目录变了必须丢弃缓存, 否则会把 A 目录的配置写到 B 目录 */
|
|
243
|
+
loadedDir = null;
|
|
244
|
+
/** 2026-09-16: 记住加载时的文件签名 —— 外部改了配置 (repair/agent 工具/用户手改) 必须重新读, 不能一直用旧值 */
|
|
245
|
+
loadedSig = null;
|
|
234
246
|
// v0.2.15: single-flight lock around read-modify-write of `~/.bolloon/llm-config.json`.
|
|
235
247
|
// Prevents concurrent save() calls from clobbering each other when the user
|
|
236
248
|
// configures two providers back-to-back (e.g. saving gemini, then anthropic, in
|
|
@@ -244,10 +256,19 @@ class LLMConfigStore {
|
|
|
244
256
|
return next;
|
|
245
257
|
}
|
|
246
258
|
async initialize() {
|
|
259
|
+
// 目录变了 (换 HOME / 独立宿主 / 测试注入): 内存缓存作废, 重新读盘
|
|
260
|
+
const dir = configDir();
|
|
261
|
+
const sig = this.fileSignature();
|
|
262
|
+
if (this.initialized && (this.loadedDir !== dir || this.loadedSig !== sig)) {
|
|
263
|
+
this.initialized = false;
|
|
264
|
+
this.config = null;
|
|
265
|
+
}
|
|
247
266
|
if (this.initialized)
|
|
248
267
|
return;
|
|
268
|
+
this.loadedDir = dir;
|
|
269
|
+
this.loadedSig = sig;
|
|
249
270
|
try {
|
|
250
|
-
await fs.mkdir(
|
|
271
|
+
await fs.mkdir(configDir(), { recursive: true });
|
|
251
272
|
// 2026-08-07: 迁移 — 旧 llm-config.json 存在且新 bolloon-config.json 不存在时复制旧内容
|
|
252
273
|
try {
|
|
253
274
|
await fs.access(CONFIG_PATH);
|
|
@@ -289,6 +310,16 @@ class LLMConfigStore {
|
|
|
289
310
|
// 2026-08-07: mode 0o600 — 仅当前用户可读写 (含 API key 的敏感配置); Bolloon 自身进程可写
|
|
290
311
|
await fs.writeFile(CONFIG_PATH, JSON.stringify(this.config, null, 2), { mode: 0o600 });
|
|
291
312
|
}
|
|
313
|
+
/** 配置文件签名 (mtime+size); 不存在时返回 'missing' */
|
|
314
|
+
fileSignature() {
|
|
315
|
+
try {
|
|
316
|
+
const st = fsSync.statSync(configDir() + '/bolloon-config.json');
|
|
317
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return 'missing';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
292
323
|
async getConfig() {
|
|
293
324
|
await this.initialize();
|
|
294
325
|
return { ...this.config };
|
|
@@ -619,6 +619,16 @@ export class AgentMessaging {
|
|
|
619
619
|
console.warn(`[Messaging] Signature verification failed for ${signedMsg.from.substring(0, 20)}`);
|
|
620
620
|
return false;
|
|
621
621
|
}
|
|
622
|
+
// 2026-09-16 (2-C.4): 目标关联的真实外部事件 —— 签名已通过校验, 这里再按
|
|
623
|
+
// 来源 / correlation / 过期 / eventId 去重做一次严格匹配, 只唤醒"正在等这个事件"的 Goal。
|
|
624
|
+
// **不在这里启动 agent**: 只写事实 + 唤醒, 由 Supervisor 下一轮继续。
|
|
625
|
+
try {
|
|
626
|
+
const { tryDeliverGoalEvent } = await import('./goal-event-bridge.js');
|
|
627
|
+
await tryDeliverGoalEvent(signedMsg, fromPeerId);
|
|
628
|
+
}
|
|
629
|
+
catch (e) {
|
|
630
|
+
console.warn(`[Messaging] goal event bridge 失败 (不影响普通消息):`, e?.message);
|
|
631
|
+
}
|
|
622
632
|
const handler = this.messageHandlers.get(signedMsg.type);
|
|
623
633
|
if (handler) {
|
|
624
634
|
handler(data, fromPeerId, signedMsg.from);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* goal-event-bridge.ts — 把"真实入站的签名消息"翻译成 Goal 外部事件 (批次 2-C.4)
|
|
3
|
+
*
|
|
4
|
+
* 只有签名已通过校验的消息才进到这里 (来源 = 对端 DID)。这里只做解析 + 投递,
|
|
5
|
+
* 启动执行由 Supervisor 决定 —— 事件处理器不碰 agent。
|
|
6
|
+
*
|
|
7
|
+
* 识别的两种真实来源:
|
|
8
|
+
* · delegate 回包: type 含 `delegate` 或 payload 里有 delegateId/resultCid → source='delegate'
|
|
9
|
+
* · P2P 协作回复: 其它带 goalId/requestId/continuationId 的消息 → source='p2p'
|
|
10
|
+
*/
|
|
11
|
+
import { deliverExternalEvent } from '../agents/external-events.js';
|
|
12
|
+
function parsePayload(raw) {
|
|
13
|
+
if (!raw)
|
|
14
|
+
return null;
|
|
15
|
+
if (typeof raw === 'object')
|
|
16
|
+
return raw;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(String(raw));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** 从 payload 里提取目标关联字段 (支持 goalEvent 包裹或平铺两种写法) */
|
|
25
|
+
export function extractGoalEvent(signed, fromDid) {
|
|
26
|
+
const body = parsePayload(signed.payload);
|
|
27
|
+
if (!body || typeof body !== 'object')
|
|
28
|
+
return null;
|
|
29
|
+
const env = (body.goalEvent && typeof body.goalEvent === 'object') ? body.goalEvent : body;
|
|
30
|
+
const goalId = env.goalId || env.goal_id;
|
|
31
|
+
const requestId = env.requestId || env.request_id || env.correlationId;
|
|
32
|
+
const continuationId = env.continuationId || env.continuation_id;
|
|
33
|
+
const eventId = env.eventId || env.event_id;
|
|
34
|
+
if (!goalId && !requestId && !continuationId)
|
|
35
|
+
return null; // 与 Goal 无关 → 普通消息
|
|
36
|
+
if (!eventId)
|
|
37
|
+
return null; // 没有 eventId 无法去重 → 不当事件处理
|
|
38
|
+
const isDelegate = /delegate/i.test(String(signed.type)) || !!(env.delegateId || env.resultCid);
|
|
39
|
+
return {
|
|
40
|
+
source: isDelegate ? 'delegate' : 'p2p',
|
|
41
|
+
eventId: String(eventId),
|
|
42
|
+
requestId: requestId ? String(requestId) : undefined,
|
|
43
|
+
continuationId: continuationId ? String(continuationId) : undefined,
|
|
44
|
+
goalId: goalId ? String(goalId) : undefined,
|
|
45
|
+
fromDid: fromDid || signed.from,
|
|
46
|
+
eventName: env.event || env.eventName || env.kind,
|
|
47
|
+
payload: env.result ?? env.payload ?? body,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** 投递 (未匹配/不合法 → 返回 matched:false, 调用方继续走普通消息处理) */
|
|
51
|
+
export async function tryDeliverGoalEvent(signed, fromDid) {
|
|
52
|
+
const event = extractGoalEvent(signed, fromDid);
|
|
53
|
+
if (!event)
|
|
54
|
+
return { matched: false };
|
|
55
|
+
const res = await deliverExternalEvent(event);
|
|
56
|
+
return { matched: res.ok, reason: res.reason, goalId: res.goalId };
|
|
57
|
+
}
|