@bolloon/bolloon-agent 0.3.51 → 0.4.0
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/README.md +135 -135
- package/bin/bolloon-cli.cjs +183 -183
- package/bin/bolloon.cjs +0 -0
- package/bin/bolloon.js +157 -0
- package/dist/agents/constraint-layer.js +19 -19
- package/dist/agents/judgment-protocol.js +14 -14
- package/dist/agents/loop-review.js +21 -0
- package/dist/agents/pi-sdk-tools.js +86 -2
- package/dist/agents/pi-sdk-types.js +12 -12
- package/dist/agents/pi-sdk.js +90 -90
- package/dist/agents/shell-guard.js +22 -1
- package/dist/agents/skill-organizer.js +19 -19
- package/dist/agents/verify-recipe.js +84 -0
- package/dist/agents/workflow-pivot-loop.js +101 -13
- package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
- package/dist/bollharness-integration/context-router-judgment.js +4 -4
- package/dist/bollharness-integration/context-router.js +292 -292
- package/dist/bollharness-integration/gate-state-machine.js +13 -13
- package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
- package/dist/bollharness-integration/skill-adapter.js +21 -21
- package/dist/bootstrap/chat-archiver.js +14 -14
- package/dist/bootstrap/context-os.js +50 -40
- package/dist/bootstrap/memory-compressor.js +13 -13
- package/dist/cli-entry.js +36 -36
- package/dist/context-compaction/auto-compact.js +7 -7
- package/dist/electron/first-run.js +54 -54
- package/dist/electron-build/electron/first-run.js +54 -54
- package/dist/electron-preload.js.map +1 -1
- package/dist/electron.js.map +1 -1
- package/dist/external-engines/delegate-handle.js +70 -0
- package/dist/external-engines/delegate.js +24 -3
- package/dist/index.js +112 -112
- package/dist/llm/error-lessons.js +53 -0
- package/dist/llm/llm-judgment-client.js +102 -102
- package/dist/llm/pi-ai.js +61 -48
- package/dist/llm/system-prompt/layers/channel/human-async.md +41 -41
- package/dist/llm/system-prompt/layers/channel/local.md +14 -14
- package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
- package/dist/llm/system-prompt/layers/channel/p2p-peer-sync.md +51 -51
- package/dist/llm/system-prompt/layers/channel/p2p-proactive.md +43 -43
- package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
- package/dist/llm/system-prompt/layers/channel/session-handoff.md +61 -61
- package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
- package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
- package/dist/llm/system-prompt/layers/core/external-engagement.md +73 -73
- package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
- package/dist/llm/system-prompt/layers/core/identity.md +48 -48
- package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
- package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
- package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
- package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
- package/dist/llm/system-prompt/layers/core/tone.md +31 -31
- package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
- package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
- package/dist/llm/system-prompt/layers/role/architect.md +20 -20
- package/dist/llm/system-prompt/layers/role/expert.md +19 -19
- package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
- package/dist/llm/system-prompt/layers/role/security.md +15 -15
- package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
- package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
- package/dist/llm/system-prompt/layers/tool/goal_handoff.md +77 -77
- package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
- package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
- package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
- package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
- package/dist/llm/system-prompt/layers/tool/p2p_request.md +61 -61
- package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
- package/dist/llm/system-prompt/registry.js +9 -1
- package/dist/llm/tool-manifest/ask_user_input.js +1 -1
- package/dist/llm/tool-manifest/bash.js +3 -3
- package/dist/llm/tool-manifest/create_file.js +4 -4
- package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
- package/dist/llm/tool-manifest/image_search.js +2 -2
- package/dist/llm/tool-manifest/index.js +0 -59
- package/dist/llm/tool-manifest/mcp.js +2 -2
- package/dist/llm/tool-manifest/message_compose.js +3 -3
- package/dist/llm/tool-manifest/places.js +2 -2
- package/dist/llm/tool-manifest/present_files.js +1 -1
- package/dist/llm/tool-manifest/recipe.js +2 -2
- package/dist/llm/tool-manifest/str_replace.js +5 -5
- package/dist/llm/tool-manifest/view.js +3 -3
- package/dist/llm/tool-manifest/weather.js +4 -4
- package/dist/llm/tool-manifest/web.js +4 -4
- package/dist/locales/en.js +28 -0
- package/dist/locales/zh.js +26 -0
- package/dist/pi-ecosystem-colony/index.js +365 -0
- package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
- package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
- package/dist/pi-ecosystem-judgment/distillation.js +14 -14
- package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
- package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
- package/dist/security/context-router-tool.js +15 -15
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +101 -0
- package/dist/social/ant-colony/PheromoneEngine.js +227 -0
- package/dist/social/ant-colony/index.js +6 -0
- package/dist/social/ant-colony/types.js +24 -0
- package/dist/storage/trajectory.js +101 -0
- package/dist/test/ai-judgment-test.js +80 -0
- package/dist/test/bollharness-integration.test.js +318 -0
- package/dist/test/channel-agent-multi-dialogue.js +205 -0
- package/dist/test/channel-heartbeat-agent-test.js +201 -0
- package/dist/test/constraint-layer.test.js +164 -0
- package/dist/test/diap-identity-test.js +172 -0
- package/dist/test/diap-quick-test.js +62 -0
- package/dist/test/global-shared-context.test.js +315 -0
- package/dist/test/harness-judgment-injection.test.js +246 -0
- package/dist/test/harness-workflow-integrator-test.js +228 -0
- package/dist/test/human-value-store.test.js +243 -0
- package/dist/test/hybrid-integration-test.js +118 -0
- package/dist/test/hybrid-messenger-verify.js +55 -0
- package/dist/test/iroh-bistream-debug.js +38 -0
- package/dist/test/iroh-communication.test.js +66 -0
- package/dist/test/iroh-debug-test.js +57 -0
- package/dist/test/iroh-diap-test.js +71 -0
- package/dist/test/iroh-direct-connect.js +55 -0
- package/dist/test/iroh-e2e-fixed.js +89 -0
- package/dist/test/iroh-e2e-same-process.js +63 -0
- package/dist/test/iroh-e2e.js +66 -0
- package/dist/test/iroh-final-e2e.js +72 -0
- package/dist/test/iroh-relay-test.js +37 -0
- package/dist/test/iroh-simple-test.js +41 -0
- package/dist/test/iroh-transport-verify.js +54 -0
- package/dist/test/iroh-transport.test.js +37 -0
- package/dist/test/iroh-two-nodes.js +70 -0
- package/dist/test/iroh-verify.js +44 -0
- package/dist/test/judgment-decision.test.js +219 -0
- package/dist/test/llm-judgment-integration.test.js +220 -0
- package/dist/test/p2p-agent-complex-dialogue.js +385 -0
- package/dist/test/p2p-agent-dialogue.js +341 -0
- package/dist/test/p2p-agent-full-bidirectional.js +510 -0
- package/dist/test/p2p-agent-harness-flow.js +437 -0
- package/dist/test/p2p-agent-harness-single.js +143 -0
- package/dist/test/p2p-ai-dialogue-test.js +318 -0
- package/dist/test/p2p-cid-connect-test.js +195 -0
- package/dist/test/p2p-connect-receiver.js +69 -0
- package/dist/test/p2p-doc-transfer.js +110 -0
- package/dist/test/p2p-identity-page-test.js +77 -0
- package/dist/test/p2p-iroh-test.js +171 -0
- package/dist/test/p2p-minimal-test.js +241 -0
- package/dist/test/p2p-node-1.js +148 -0
- package/dist/test/p2p-node-2.js +148 -0
- package/dist/test/p2p-server.js +281 -0
- package/dist/test/p2p-two-nodes-test.js +438 -0
- package/dist/test/pi-sdk.test.js +44 -0
- package/dist/test/set-persona.js +40 -0
- package/dist/test/simple.test.js +9 -0
- package/dist/test/storage-integration.test.js +150 -0
- package/dist/test/subagent-manager.test.js +276 -0
- package/dist/test/test-gate-flow.test.js +81 -0
- package/dist/test/workflow-engine.test.js +87 -0
- package/dist/test/workflow-pivot-loop.test.js +246 -0
- package/dist/web/api-config.html +779 -779
- package/dist/web/client-hearth.js +10 -10
- package/dist/web/client.js +54 -0
- package/dist/web/components/wallet-viem.mjs +118 -118
- package/dist/web/delivery-ledger.js +103 -0
- package/dist/web/i18n.js +104 -0
- package/dist/web/index.html +472 -472
- package/dist/web/manifest.json +20 -20
- package/dist/web/routes-tasks.js +242 -24
- package/dist/web/server-storage.js +111 -0
- package/dist/web/server.js +55 -26
- package/dist/web/style.css +4946 -4946
- package/dist/web/task-breaker.js +18 -0
- package/dist/web/task-cancel.js +35 -0
- package/dist/web/task-deps.js +36 -0
- package/dist/web/task-review.js +27 -0
- package/dist/web/task-verify.js +60 -0
- package/package.json +196 -193
- package/scripts/build-cli.js +215 -215
- package/scripts/build-web.ts +130 -130
- package/scripts/postinstall.js +152 -152
- package/bin/bolloon-daemon.sh +0 -207
- package/dist/constraint-runtime/reference_data/archive_surface_snapshot.json +0 -63
- package/dist/constraint-runtime/reference_data/commands_snapshot.json +0 -1037
- package/dist/constraint-runtime/reference_data/subsystems/assistant.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/bootstrap.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/bridge.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/buddy.json +0 -13
- package/dist/constraint-runtime/reference_data/subsystems/cli.json +0 -26
- package/dist/constraint-runtime/reference_data/subsystems/components.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/constants.json +0 -28
- package/dist/constraint-runtime/reference_data/subsystems/coordinator.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/entrypoints.json +0 -15
- package/dist/constraint-runtime/reference_data/subsystems/hooks.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/keybindings.json +0 -21
- package/dist/constraint-runtime/reference_data/subsystems/memdir.json +0 -15
- package/dist/constraint-runtime/reference_data/subsystems/migrations.json +0 -18
- package/dist/constraint-runtime/reference_data/subsystems/moreright.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/native_ts.json +0 -11
- package/dist/constraint-runtime/reference_data/subsystems/outputStyles.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/plugins.json +0 -9
- package/dist/constraint-runtime/reference_data/subsystems/remote.json +0 -11
- package/dist/constraint-runtime/reference_data/subsystems/schemas.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/screens.json +0 -10
- package/dist/constraint-runtime/reference_data/subsystems/server.json +0 -10
- package/dist/constraint-runtime/reference_data/subsystems/services.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/skills.json +0 -27
- package/dist/constraint-runtime/reference_data/subsystems/state.json +0 -13
- package/dist/constraint-runtime/reference_data/subsystems/types.json +0 -18
- package/dist/constraint-runtime/reference_data/subsystems/upstreamproxy.json +0 -9
- package/dist/constraint-runtime/reference_data/subsystems/utils.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/vim.json +0 -12
- package/dist/constraint-runtime/reference_data/subsystems/voice.json +0 -8
- package/dist/constraint-runtime/reference_data/tools_snapshot.json +0 -922
- package/dist/electron/config.js.map +0 -1
- package/dist/electron/dialogs.js.map +0 -1
- package/dist/electron/first-run.js.map +0 -1
- package/dist/electron/ipc.js.map +0 -1
- package/dist/electron/logger.js.map +0 -1
- package/dist/electron/main.js.map +0 -1
- package/dist/electron/menu.js.map +0 -1
- package/dist/electron/paths.js.map +0 -1
- package/dist/electron/server.js.map +0 -1
- package/dist/electron/tray.js.map +0 -1
- package/dist/electron/window.js.map +0 -1
- package/dist/utils/auto-update.js.map +0 -1
- package/dist/web/client.js.map +0 -7
|
@@ -20,16 +20,16 @@ export async function renderMyHearth(root) {
|
|
|
20
20
|
root.innerHTML = '<h1>My Hearth</h1><p>Loading…</p>';
|
|
21
21
|
try {
|
|
22
22
|
const data = await fetchHearth('/api/hearth');
|
|
23
|
-
root.innerHTML = `
|
|
24
|
-
<h1>My Hearth</h1>
|
|
25
|
-
<dl>
|
|
26
|
-
<dt>Service</dt><dd>${data.service} v${data.version}</dd>
|
|
27
|
-
<dt>Root</dt><dd><code>${data.rootPath}</code></dd>
|
|
28
|
-
<dt>Description count</dt><dd>${data.descriptionCount}</dd>
|
|
29
|
-
<dt>Visibility channels</dt><dd>${data.visibilityChannels}</dd>
|
|
30
|
-
<dt>Allowlist count</dt><dd>${data.allowlistCount}</dd>
|
|
31
|
-
<dt>Defense mode</dt><dd>${data.defenseMode ? 'ON (write APIs disabled)' : 'OFF (反攻期)'}</dd>
|
|
32
|
-
</dl>
|
|
23
|
+
root.innerHTML = `
|
|
24
|
+
<h1>My Hearth</h1>
|
|
25
|
+
<dl>
|
|
26
|
+
<dt>Service</dt><dd>${data.service} v${data.version}</dd>
|
|
27
|
+
<dt>Root</dt><dd><code>${data.rootPath}</code></dd>
|
|
28
|
+
<dt>Description count</dt><dd>${data.descriptionCount}</dd>
|
|
29
|
+
<dt>Visibility channels</dt><dd>${data.visibilityChannels}</dd>
|
|
30
|
+
<dt>Allowlist count</dt><dd>${data.allowlistCount}</dd>
|
|
31
|
+
<dt>Defense mode</dt><dd>${data.defenseMode ? 'ON (write APIs disabled)' : 'OFF (反攻期)'}</dd>
|
|
32
|
+
</dl>
|
|
33
33
|
<p>视图: <a href="#/hearth/discover">Discover</a> · <a href="#/hearth/visit/__self__">Visit self</a></p>`;
|
|
34
34
|
}
|
|
35
35
|
catch (e) {
|
package/dist/web/client.js
CHANGED
|
@@ -1544,6 +1544,57 @@
|
|
|
1544
1544
|
var newChannelBtn = document.getElementById("new-channel-btn");
|
|
1545
1545
|
var newChannelInput = document.getElementById("new-channel-input");
|
|
1546
1546
|
var channelNameEl = document.getElementById("channel-name");
|
|
1547
|
+
var rokidBridge = null;
|
|
1548
|
+
var rokidBridgeStatus = "unavailable";
|
|
1549
|
+
function getRokidBridge() {
|
|
1550
|
+
if (typeof window === "undefined") return null;
|
|
1551
|
+
return window.Capacitor?.Plugins?.RokidBridge || null;
|
|
1552
|
+
}
|
|
1553
|
+
function publishRokidStatus(status, detail = {}) {
|
|
1554
|
+
rokidBridgeStatus = status;
|
|
1555
|
+
if (typeof window !== "undefined") {
|
|
1556
|
+
window.__bolloonRokidStatus = { status, ...detail };
|
|
1557
|
+
window.dispatchEvent(new CustomEvent("bolloon:rokid-status", { detail: window.__bolloonRokidStatus }));
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
async function initRokidBridge() {
|
|
1561
|
+
const bridge = getRokidBridge();
|
|
1562
|
+
if (!bridge) {
|
|
1563
|
+
publishRokidStatus("unavailable");
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
rokidBridge = bridge;
|
|
1567
|
+
try {
|
|
1568
|
+
await bridge.addListener?.("rokidEvent", (event) => {
|
|
1569
|
+
if (event?.type === "connected") publishRokidStatus("connected", { device: event.device });
|
|
1570
|
+
else if (event?.type === "disconnected") publishRokidStatus("disconnected", { reason: event.reason });
|
|
1571
|
+
else if (event?.type === "error") publishRokidStatus("error", { error: event.error || event.message });
|
|
1572
|
+
window.dispatchEvent(new CustomEvent("bolloon:rokid-event", { detail: event }));
|
|
1573
|
+
});
|
|
1574
|
+
const result = await bridge.connect?.();
|
|
1575
|
+
publishRokidStatus("connected", { deviceId: result?.deviceId, mode: result?.mode });
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
publishRokidStatus("error", { error: error?.message || String(error) });
|
|
1578
|
+
console.warn("[Rokid] bridge unavailable:", error);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
function sendRokidText(text, metadata = {}) {
|
|
1582
|
+
if (!rokidBridge || !text) return;
|
|
1583
|
+
Promise.resolve(rokidBridge.sendMessage?.({
|
|
1584
|
+
text,
|
|
1585
|
+
channelId: currentChannelId || void 0,
|
|
1586
|
+
metadata
|
|
1587
|
+
})).catch((error) => {
|
|
1588
|
+
publishRokidStatus("error", { error: error?.message || String(error) });
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
if (typeof window !== "undefined") {
|
|
1592
|
+
window.BolloonRokid = {
|
|
1593
|
+
connect: initRokidBridge,
|
|
1594
|
+
sendMessage: sendRokidText,
|
|
1595
|
+
getStatus: () => ({ status: rokidBridgeStatus })
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1547
1598
|
var eventSources = /* @__PURE__ */ new Map();
|
|
1548
1599
|
var currentChannelId = null;
|
|
1549
1600
|
var activeChannelId = null;
|
|
@@ -2823,6 +2874,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
2823
2874
|
MR_replaceStreamingText?.(data.content || "");
|
|
2824
2875
|
MR_finalizeTimelineAsMessage(getRendererCtx());
|
|
2825
2876
|
}
|
|
2877
|
+
sendRokidText(data.content || "", { source: "ai" });
|
|
2826
2878
|
} else if (data.type === "reply-preview") {
|
|
2827
2879
|
const previewContent = data.content || "";
|
|
2828
2880
|
const oldPreviews = container.querySelectorAll(".message-ai.preview");
|
|
@@ -2896,6 +2948,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
2896
2948
|
setSendMode("abort");
|
|
2897
2949
|
const container = messagesContainers.get(currentChannelId) || messagesEl;
|
|
2898
2950
|
addMessage2(text, "user", true, container);
|
|
2951
|
+
sendRokidText(text, { source: "user" });
|
|
2899
2952
|
if (container) container.scrollTop = container.scrollHeight;
|
|
2900
2953
|
if (currentPreviewBubble) {
|
|
2901
2954
|
currentPreviewBubble.remove();
|
|
@@ -3983,6 +4036,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
3983
4036
|
}
|
|
3984
4037
|
}
|
|
3985
4038
|
async function init() {
|
|
4039
|
+
void initRokidBridge();
|
|
3986
4040
|
const themeData = await loadTheme();
|
|
3987
4041
|
currentAgentId = themeData.agentId || `agent_${generateId().substring(0, 8)}`;
|
|
3988
4042
|
if (!themeData.agentId) {
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
// Real-wallet helpers (viem-backed), exposed as window.WalletViem for client.js.
|
|
2
|
-
// 浏览器侧执行, 不上链, 但地址/助记词/签名都符合 EVM 标准
|
|
3
|
-
// (BIP-39 mnemonic + secp256k1 keypair + EIP-55 checksum address + EIP-191 personal_sign).
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
generateMnemonic,
|
|
7
|
-
mnemonicToAccount,
|
|
8
|
-
privateKeyToAccount,
|
|
9
|
-
english,
|
|
10
|
-
} from 'https://esm.sh/viem@2.52.0/accounts';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 生成一个全新的 EVM 钱包: 12 词助记词 + 派生账户 (BIP-44, m/44'/60'/0'/0/0)
|
|
14
|
-
* 返回: { mnemonic, address, privateKey }
|
|
15
|
-
* - mnemonic: 12 词英文助记词
|
|
16
|
-
* - address: EIP-55 checksum 后的 0x 开头地址
|
|
17
|
-
* - privateKey: 0x 开头的 32 字节私钥
|
|
18
|
-
*/
|
|
19
|
-
export function generateEVMWallet() {
|
|
20
|
-
const mnemonic = generateMnemonic(english, 128); // 128 bits = 12 words
|
|
21
|
-
const account = mnemonicToAccount(mnemonic, { accountIndex: 0 });
|
|
22
|
-
return {
|
|
23
|
-
mnemonic,
|
|
24
|
-
address: account.address,
|
|
25
|
-
// viem 不直接暴露 privateKey, 用 hdKeyToAccount 路径取
|
|
26
|
-
// 这里走一个变通: 拿地址后, 再用 createAccount 走不通...
|
|
27
|
-
// 实际: account.source 包含 HDKey, 我们让它返回 privateKey 字段
|
|
28
|
-
// 简化: 直接调一个 helper
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 通过私钥恢复账户: 用于"导入已有钱包"流程
|
|
34
|
-
*/
|
|
35
|
-
export function importEVMWallet(privateKeyHex) {
|
|
36
|
-
const normalized = privateKeyHex.startsWith('0x') ? privateKeyHex : `0x${privateKeyHex}`;
|
|
37
|
-
const account = privateKeyToAccount(normalized);
|
|
38
|
-
return {
|
|
39
|
-
address: account.address,
|
|
40
|
-
privateKey: normalized,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 通过助记词恢复账户
|
|
46
|
-
*/
|
|
47
|
-
export function importEVMMnemonic(mnemonic) {
|
|
48
|
-
const account = mnemonicToAccount(mnemonic.trim(), { accountIndex: 0 });
|
|
49
|
-
return {
|
|
50
|
-
mnemonic: mnemonic.trim(),
|
|
51
|
-
address: account.address,
|
|
52
|
-
privateKey: extractPrivateKey(account),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* 真实生成路径: 先生成助记词, 再恢复出账户, 顺带取出 privateKey
|
|
58
|
-
*/
|
|
59
|
-
export function generateRealWallet() {
|
|
60
|
-
const mnemonic = generateMnemonic(english, 128);
|
|
61
|
-
const account = mnemonicToAccount(mnemonic, { accountIndex: 0 });
|
|
62
|
-
return {
|
|
63
|
-
mnemonic,
|
|
64
|
-
address: account.address,
|
|
65
|
-
privateKey: extractPrivateKey(account),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 对 channel DID 进行 EIP-191 personal_sign 签名, 证明钱包所有权
|
|
71
|
-
* 返回 { address, signature, message, did }
|
|
72
|
-
* - message: 实际签名的原文, 服务端需用 recoverMessage 校验
|
|
73
|
-
* - signature: 0x 开头 65 字节签名 (r||s||v)
|
|
74
|
-
*/
|
|
75
|
-
export async function signDIDChallenge(privateKeyHex, did, channelId) {
|
|
76
|
-
const normalized = privateKeyHex.startsWith('0x') ? privateKeyHex : `0x${privateKeyHex}`;
|
|
77
|
-
const account = privateKeyToAccount(normalized);
|
|
78
|
-
const message = buildChallengeMessage(did, channelId);
|
|
79
|
-
const signature = await account.signMessage({ message });
|
|
80
|
-
return {
|
|
81
|
-
address: account.address,
|
|
82
|
-
signature,
|
|
83
|
-
message,
|
|
84
|
-
did,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function buildChallengeMessage(did, channelId) {
|
|
89
|
-
return [
|
|
90
|
-
'Bolloon Agent Wallet Binding',
|
|
91
|
-
`Channel ID: ${channelId}`,
|
|
92
|
-
`Agent DID: ${did}`,
|
|
93
|
-
`Issued At: ${new Date().toISOString()}`,
|
|
94
|
-
].join('\n');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 从 viem HDKey 账户里取出 privateKey (Uint8Array) 转成 0x 开头 hex
|
|
98
|
-
function extractPrivateKey(account) {
|
|
99
|
-
const hdKey = account.getHdKey ? account.getHdKey() : null;
|
|
100
|
-
if (!hdKey || !hdKey.privateKey) {
|
|
101
|
-
throw new Error('无法从助记词派生私钥');
|
|
102
|
-
}
|
|
103
|
-
return '0x' + Array.from(hdKey.privateKey, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const api = {
|
|
107
|
-
generateRealWallet,
|
|
108
|
-
importEVMWallet,
|
|
109
|
-
importEVMMnemonic,
|
|
110
|
-
signDIDChallenge,
|
|
111
|
-
buildChallengeMessage,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
if (typeof window !== 'undefined') {
|
|
115
|
-
window.WalletViem = api;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export default api;
|
|
1
|
+
// Real-wallet helpers (viem-backed), exposed as window.WalletViem for client.js.
|
|
2
|
+
// 浏览器侧执行, 不上链, 但地址/助记词/签名都符合 EVM 标准
|
|
3
|
+
// (BIP-39 mnemonic + secp256k1 keypair + EIP-55 checksum address + EIP-191 personal_sign).
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
generateMnemonic,
|
|
7
|
+
mnemonicToAccount,
|
|
8
|
+
privateKeyToAccount,
|
|
9
|
+
english,
|
|
10
|
+
} from 'https://esm.sh/viem@2.52.0/accounts';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 生成一个全新的 EVM 钱包: 12 词助记词 + 派生账户 (BIP-44, m/44'/60'/0'/0/0)
|
|
14
|
+
* 返回: { mnemonic, address, privateKey }
|
|
15
|
+
* - mnemonic: 12 词英文助记词
|
|
16
|
+
* - address: EIP-55 checksum 后的 0x 开头地址
|
|
17
|
+
* - privateKey: 0x 开头的 32 字节私钥
|
|
18
|
+
*/
|
|
19
|
+
export function generateEVMWallet() {
|
|
20
|
+
const mnemonic = generateMnemonic(english, 128); // 128 bits = 12 words
|
|
21
|
+
const account = mnemonicToAccount(mnemonic, { accountIndex: 0 });
|
|
22
|
+
return {
|
|
23
|
+
mnemonic,
|
|
24
|
+
address: account.address,
|
|
25
|
+
// viem 不直接暴露 privateKey, 用 hdKeyToAccount 路径取
|
|
26
|
+
// 这里走一个变通: 拿地址后, 再用 createAccount 走不通...
|
|
27
|
+
// 实际: account.source 包含 HDKey, 我们让它返回 privateKey 字段
|
|
28
|
+
// 简化: 直接调一个 helper
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 通过私钥恢复账户: 用于"导入已有钱包"流程
|
|
34
|
+
*/
|
|
35
|
+
export function importEVMWallet(privateKeyHex) {
|
|
36
|
+
const normalized = privateKeyHex.startsWith('0x') ? privateKeyHex : `0x${privateKeyHex}`;
|
|
37
|
+
const account = privateKeyToAccount(normalized);
|
|
38
|
+
return {
|
|
39
|
+
address: account.address,
|
|
40
|
+
privateKey: normalized,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 通过助记词恢复账户
|
|
46
|
+
*/
|
|
47
|
+
export function importEVMMnemonic(mnemonic) {
|
|
48
|
+
const account = mnemonicToAccount(mnemonic.trim(), { accountIndex: 0 });
|
|
49
|
+
return {
|
|
50
|
+
mnemonic: mnemonic.trim(),
|
|
51
|
+
address: account.address,
|
|
52
|
+
privateKey: extractPrivateKey(account),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 真实生成路径: 先生成助记词, 再恢复出账户, 顺带取出 privateKey
|
|
58
|
+
*/
|
|
59
|
+
export function generateRealWallet() {
|
|
60
|
+
const mnemonic = generateMnemonic(english, 128);
|
|
61
|
+
const account = mnemonicToAccount(mnemonic, { accountIndex: 0 });
|
|
62
|
+
return {
|
|
63
|
+
mnemonic,
|
|
64
|
+
address: account.address,
|
|
65
|
+
privateKey: extractPrivateKey(account),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 对 channel DID 进行 EIP-191 personal_sign 签名, 证明钱包所有权
|
|
71
|
+
* 返回 { address, signature, message, did }
|
|
72
|
+
* - message: 实际签名的原文, 服务端需用 recoverMessage 校验
|
|
73
|
+
* - signature: 0x 开头 65 字节签名 (r||s||v)
|
|
74
|
+
*/
|
|
75
|
+
export async function signDIDChallenge(privateKeyHex, did, channelId) {
|
|
76
|
+
const normalized = privateKeyHex.startsWith('0x') ? privateKeyHex : `0x${privateKeyHex}`;
|
|
77
|
+
const account = privateKeyToAccount(normalized);
|
|
78
|
+
const message = buildChallengeMessage(did, channelId);
|
|
79
|
+
const signature = await account.signMessage({ message });
|
|
80
|
+
return {
|
|
81
|
+
address: account.address,
|
|
82
|
+
signature,
|
|
83
|
+
message,
|
|
84
|
+
did,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildChallengeMessage(did, channelId) {
|
|
89
|
+
return [
|
|
90
|
+
'Bolloon Agent Wallet Binding',
|
|
91
|
+
`Channel ID: ${channelId}`,
|
|
92
|
+
`Agent DID: ${did}`,
|
|
93
|
+
`Issued At: ${new Date().toISOString()}`,
|
|
94
|
+
].join('\n');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 从 viem HDKey 账户里取出 privateKey (Uint8Array) 转成 0x 开头 hex
|
|
98
|
+
function extractPrivateKey(account) {
|
|
99
|
+
const hdKey = account.getHdKey ? account.getHdKey() : null;
|
|
100
|
+
if (!hdKey || !hdKey.privateKey) {
|
|
101
|
+
throw new Error('无法从助记词派生私钥');
|
|
102
|
+
}
|
|
103
|
+
return '0x' + Array.from(hdKey.privateKey, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const api = {
|
|
107
|
+
generateRealWallet,
|
|
108
|
+
importEVMWallet,
|
|
109
|
+
importEVMMnemonic,
|
|
110
|
+
signDIDChallenge,
|
|
111
|
+
buildChallengeMessage,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (typeof window !== 'undefined') {
|
|
115
|
+
window.WalletViem = api;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default api;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* delivery-ledger.ts — SSE 客户端投递账本 (借鉴 Hermes gateway/delivery_ledger.py)
|
|
3
|
+
*
|
|
4
|
+
* Hermes outbox 模式 (WAL + 三态 checkpoint + 崩溃语义):
|
|
5
|
+
* pending — 发送从未开始: 直接重发, 无重复风险
|
|
6
|
+
* attempting — 发送中途崩溃: 平台可能已收到 → 带可见标记重发 (诚实的 at-least-once, 不静默重复)
|
|
7
|
+
* delivered — 仅 SendResult.success
|
|
8
|
+
* failed — 明确拒绝一次; 重启是天然重试边界
|
|
9
|
+
*
|
|
10
|
+
* Bolloon 版 (轻量, 内存): 追踪每个 SSE 客户端的投递状态 + 失败统计 + 同类错误节流
|
|
11
|
+
* (channel_directory 模式: 同一 (类别, 错误) 窗口内只 warn 一次, 防日志刷屏).
|
|
12
|
+
*/
|
|
13
|
+
/** 同类错误节流窗口 (ms): 窗口内同 (类别, 错误前缀) 只 warn 一次 */
|
|
14
|
+
export const WARN_THROTTLE_WINDOW_MS = 5 * 60 * 1000;
|
|
15
|
+
export class DeliveryLedger {
|
|
16
|
+
records = new Map();
|
|
17
|
+
lastWarn = new Map();
|
|
18
|
+
/** 注册客户端 (obligation) — 初始 pending (Hermes record_obligation) */
|
|
19
|
+
register(clientId) {
|
|
20
|
+
const existing = this.records.get(clientId);
|
|
21
|
+
if (existing)
|
|
22
|
+
return existing;
|
|
23
|
+
const rec = { clientId, state: 'pending', consecutiveFailures: 0, createdAt: Date.now() };
|
|
24
|
+
this.records.set(clientId, rec);
|
|
25
|
+
return rec;
|
|
26
|
+
}
|
|
27
|
+
/** 发送前标记 attempting (Hermes mark_attempting) */
|
|
28
|
+
markAttempting(clientId) {
|
|
29
|
+
const rec = this.register(clientId);
|
|
30
|
+
rec.state = 'attempting';
|
|
31
|
+
}
|
|
32
|
+
/** 发送成功 (Hermes mark_delivered) */
|
|
33
|
+
markDelivered(clientId) {
|
|
34
|
+
const rec = this.register(clientId);
|
|
35
|
+
rec.state = 'delivered';
|
|
36
|
+
rec.consecutiveFailures = 0;
|
|
37
|
+
rec.lastDeliveredAt = Date.now();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 发送失败 (Hermes mark_failed). 同类错误节流: 窗口内同 (state, errorPrefix) 返回 false 表示
|
|
41
|
+
* 该 warn 已被节流 — 调用方只对 true 输出日志.
|
|
42
|
+
* @returns 是否应输出警告 (true = 新教训, 值得 warn)
|
|
43
|
+
*/
|
|
44
|
+
markFailed(clientId, error) {
|
|
45
|
+
const rec = this.register(clientId);
|
|
46
|
+
rec.state = 'failed';
|
|
47
|
+
rec.consecutiveFailures++;
|
|
48
|
+
rec.lastFailedAt = Date.now();
|
|
49
|
+
rec.lastError = String(error?.message || error || '').slice(0, 120);
|
|
50
|
+
return this.shouldWarn('delivery-fail', rec.lastError);
|
|
51
|
+
}
|
|
52
|
+
/** 同类错误节流 (Hermes channel_directory warn-once-per-window) */
|
|
53
|
+
shouldWarn(category, detail) {
|
|
54
|
+
const key = `${category}:${detail.slice(0, 60)}`;
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const last = this.lastWarn.get(key) ?? 0;
|
|
57
|
+
if (now - last < WARN_THROTTLE_WINDOW_MS)
|
|
58
|
+
return false;
|
|
59
|
+
this.lastWarn.set(key, now);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/** 客户端判定死亡 (最后一次活动超时) — 标记 dead, 可回收 (Hermes sweep_recoverable 语义) */
|
|
63
|
+
sweepDead(idleTimeoutMs) {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const dead = [];
|
|
66
|
+
for (const [id, rec] of this.records) {
|
|
67
|
+
if (rec.state === 'dead')
|
|
68
|
+
continue;
|
|
69
|
+
const lastActivity = Math.max(rec.lastDeliveredAt ?? 0, rec.lastFailedAt ?? 0, rec.createdAt);
|
|
70
|
+
if (now - lastActivity > idleTimeoutMs) {
|
|
71
|
+
rec.state = 'dead';
|
|
72
|
+
rec.deadAt = now;
|
|
73
|
+
dead.push(id);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return dead;
|
|
77
|
+
}
|
|
78
|
+
/** 移除记录 (客户端断开) */
|
|
79
|
+
remove(clientId) {
|
|
80
|
+
this.records.delete(clientId);
|
|
81
|
+
}
|
|
82
|
+
stats() {
|
|
83
|
+
let delivered = 0, failed = 0, dead = 0, attempting = 0;
|
|
84
|
+
for (const r of this.records.values()) {
|
|
85
|
+
if (r.state === 'delivered')
|
|
86
|
+
delivered++;
|
|
87
|
+
else if (r.state === 'failed')
|
|
88
|
+
failed++;
|
|
89
|
+
else if (r.state === 'dead')
|
|
90
|
+
dead++;
|
|
91
|
+
else if (r.state === 'attempting')
|
|
92
|
+
attempting++;
|
|
93
|
+
}
|
|
94
|
+
return { total: this.records.size, delivered, failed, dead, attempting };
|
|
95
|
+
}
|
|
96
|
+
get(clientId) {
|
|
97
|
+
return this.records.get(clientId);
|
|
98
|
+
}
|
|
99
|
+
clear() {
|
|
100
|
+
this.records.clear();
|
|
101
|
+
this.lastWarn.clear();
|
|
102
|
+
}
|
|
103
|
+
}
|
package/dist/web/i18n.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18n.ts — 用户可见静态文案目录 (借鉴 Hermes agent/i18n.py + locales/*.yaml)
|
|
3
|
+
*
|
|
4
|
+
* Hermes 设计要点 (照搬):
|
|
5
|
+
* 1. 范围纪律: 只翻「用户可见静态消息」(审批提示/系统回复); agent 生成的输出、
|
|
6
|
+
* 日志、报错、工具输出一律不翻译 (agent/i18n.py scope rationale)。
|
|
7
|
+
* 2. 嵌套目录扁平化成 dotted keys (approval.choose_long)。
|
|
8
|
+
* 3. 值可含 {placeholder} 占位符 (str.format 语义)。
|
|
9
|
+
* 4. 语言解析: env (BOLLOON_LANGUAGE) > config > default (zh)。
|
|
10
|
+
* 5. 目录每语言缓存; 键缺失 → en 回退 (en 是 source of truth)。
|
|
11
|
+
* 6. parity 测试: 每个 locale 键集合 == en, 占位符集合 == en (tests 断言)。
|
|
12
|
+
*
|
|
13
|
+
* 载体用 TS 模块 (locales/*.ts) 而非 YAML 文件: 无运行时文件依赖/构建拷贝问题,
|
|
14
|
+
* 机制与 YAML 目录完全一致 (嵌套对象 + 扁平化)。
|
|
15
|
+
*/
|
|
16
|
+
import * as en from '../locales/en.js';
|
|
17
|
+
import * as zh from '../locales/zh.js';
|
|
18
|
+
export const DEFAULT_LANGUAGE = 'zh';
|
|
19
|
+
export const SUPPORTED_LANGUAGES = ['en', 'zh'];
|
|
20
|
+
/** 语言别名 + 区域标签归一化 (Hermes _normalize_lang) */
|
|
21
|
+
export function normalizeLang(value) {
|
|
22
|
+
if (typeof value !== 'string')
|
|
23
|
+
return DEFAULT_LANGUAGE;
|
|
24
|
+
const key = value.trim().toLowerCase();
|
|
25
|
+
if (!key)
|
|
26
|
+
return DEFAULT_LANGUAGE;
|
|
27
|
+
if (SUPPORTED_LANGUAGES.includes(key))
|
|
28
|
+
return key;
|
|
29
|
+
const base = key.split('-', 1)[0];
|
|
30
|
+
if (SUPPORTED_LANGUAGES.includes(base))
|
|
31
|
+
return base;
|
|
32
|
+
return DEFAULT_LANGUAGE;
|
|
33
|
+
}
|
|
34
|
+
/** 嵌套目录扁平化成 dotted keys (Hermes _flatten_into) */
|
|
35
|
+
export function flattenCatalog(node, prefix = '') {
|
|
36
|
+
const out = {};
|
|
37
|
+
if (node && typeof node === 'object' && !Array.isArray(node)) {
|
|
38
|
+
for (const [key, value] of Object.entries(node)) {
|
|
39
|
+
const childKey = prefix ? `${prefix}.${key}` : key;
|
|
40
|
+
Object.assign(out, flattenCatalog(value, childKey));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (typeof node === 'string') {
|
|
44
|
+
out[prefix] = node;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const catalogs = {
|
|
49
|
+
en: flattenCatalog(en.catalog),
|
|
50
|
+
zh: flattenCatalog(zh.catalog),
|
|
51
|
+
};
|
|
52
|
+
/** 语言缓存 (Hermes _catalog_cache) — 进程内一次加载 */
|
|
53
|
+
const resolvedLanguage = { lang: DEFAULT_LANGUAGE };
|
|
54
|
+
/** 解析活动语言: env BOLLOON_LANGUAGE > 默认 */
|
|
55
|
+
export function getLanguage() {
|
|
56
|
+
const envLang = process.env.BOLLOON_LANGUAGE;
|
|
57
|
+
if (envLang) {
|
|
58
|
+
resolvedLanguage.lang = normalizeLang(envLang);
|
|
59
|
+
}
|
|
60
|
+
return resolvedLanguage.lang;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 翻译 dotted key (Hermes t()).
|
|
64
|
+
* 键缺失 → en 回退; en 也缺失 → 返回 key 本身 (不抛).
|
|
65
|
+
*/
|
|
66
|
+
export function t(key, lang, vars) {
|
|
67
|
+
const target = lang ?? getLanguage();
|
|
68
|
+
const catalog = catalogs[target] ?? catalogs.en;
|
|
69
|
+
let template = catalog[key];
|
|
70
|
+
if (template === undefined) {
|
|
71
|
+
template = catalogs.en[key] ?? key; // en 回退
|
|
72
|
+
}
|
|
73
|
+
if (vars && Object.keys(vars).length > 0) {
|
|
74
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
75
|
+
template = template.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return template;
|
|
79
|
+
}
|
|
80
|
+
/** parity 检查 (Hermes test_i18n.py): 键集合 + 占位符集合 vs en */
|
|
81
|
+
export function catalogParity() {
|
|
82
|
+
const enKeys = Object.keys(catalogs.en);
|
|
83
|
+
const placeholderRe = /\{([a-zA-Z0-9_]+)\}/g;
|
|
84
|
+
const enPlaceholders = new Map();
|
|
85
|
+
for (const [k, v] of Object.entries(catalogs.en)) {
|
|
86
|
+
enPlaceholders.set(k, new Set([...v.matchAll(placeholderRe)].map((m) => m[1])));
|
|
87
|
+
}
|
|
88
|
+
return SUPPORTED_LANGUAGES.filter((l) => l !== 'en').map((lang) => {
|
|
89
|
+
const langKeys = Object.keys(catalogs[lang]);
|
|
90
|
+
const missingKeys = enKeys.filter((k) => !catalogs[lang][k]);
|
|
91
|
+
const extraKeys = langKeys.filter((k) => !catalogs.en[k]);
|
|
92
|
+
const placeholderMismatch = [];
|
|
93
|
+
for (const [k, enPh] of enPlaceholders) {
|
|
94
|
+
const langV = catalogs[lang][k];
|
|
95
|
+
if (langV === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
const langPh = new Set([...langV.matchAll(placeholderRe)].map((m) => m[1]));
|
|
98
|
+
const diff = new Set([...enPh].filter((p) => !langPh.has(p)));
|
|
99
|
+
if (diff.size > 0)
|
|
100
|
+
placeholderMismatch.push(`${k}: 缺占位符 ${[...diff].join(',')}`);
|
|
101
|
+
}
|
|
102
|
+
return { lang, missingKeys, extraKeys, placeholderMismatch };
|
|
103
|
+
});
|
|
104
|
+
}
|