@bolloon/bolloon-agent 0.3.12 → 0.3.14
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/pi-sdk-tools.js +147 -0
- package/dist/agents/pi-sdk.js +21 -0
- package/dist/agents/x402/x402Pay.js +281 -0
- package/dist/electron/config.js +14 -9
- package/dist/electron/dialogs.js +53 -16
- package/dist/electron/first-run.js +65 -24
- package/dist/electron/ipc.js +14 -10
- package/dist/electron/logger.js +44 -7
- package/dist/electron/main.js +45 -42
- package/dist/electron/menu.js +18 -13
- package/dist/electron/paths.js +54 -12
- package/dist/electron/server.js +57 -18
- package/dist/electron/tray.js +53 -15
- package/dist/electron/window.js +61 -22
- package/dist/electron-preload.js +19 -16
- package/dist/electron.js +4 -1
- package/dist/utils/auto-update.js +51 -12
- package/dist/web/client.js +129 -3
- package/dist/web/index.html +14 -0
- package/dist/web/server-storage.js +41 -9
- package/dist/web/server.js +71 -0
- package/dist/web/style.css +25 -0
- package/package.json +21 -2
|
@@ -17,35 +17,67 @@ import { saveWindow as saveSessionWindow, loadWindow as loadSessionWindow } from
|
|
|
17
17
|
let lastChannelsJson = '';
|
|
18
18
|
// 写盘保护: 任何调用 saveChannels 后更新
|
|
19
19
|
let lastChannelsWriteAt = 0;
|
|
20
|
+
// 2026-07-24: 简单的互斥锁, 防止并发 loadChannels→modify→saveChannels 的经典 read-modify-write 竞争
|
|
21
|
+
let channelsLock = Promise.resolve();
|
|
20
22
|
export function getLastChannelsWriteAt() {
|
|
21
23
|
return lastChannelsWriteAt;
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
/** 对 channels 执行原子化的 read-modify-write, 自带互斥锁 */
|
|
26
|
+
export async function updateChannels(fn) {
|
|
27
|
+
channelsLock = channelsLock.then(async () => {
|
|
28
|
+
const chs = await rawLoadChannels();
|
|
29
|
+
const result = fn(chs);
|
|
30
|
+
await rawSaveChannels(result);
|
|
31
|
+
return result;
|
|
32
|
+
});
|
|
33
|
+
return channelsLock;
|
|
34
|
+
}
|
|
35
|
+
async function rawLoadChannels() {
|
|
24
36
|
try {
|
|
25
37
|
const data = await fs.readFile(CHANNELS_PATH, 'utf-8');
|
|
26
38
|
return JSON.parse(data);
|
|
27
39
|
}
|
|
28
|
-
catch {
|
|
40
|
+
catch (readErr) {
|
|
41
|
+
// 2026-07-24: 主文件损坏时尝试从 .tmp 恢复
|
|
42
|
+
if (readErr?.code !== 'ENOENT') {
|
|
43
|
+
console.warn('[loadChannels] channels.json 解析失败, 尝试从 .tmp 恢复:', readErr?.message?.slice(0, 80));
|
|
44
|
+
try {
|
|
45
|
+
const tmpData = await fs.readFile(CHANNELS_PATH + '.tmp', 'utf-8');
|
|
46
|
+
const recovered = JSON.parse(tmpData);
|
|
47
|
+
console.log(`[loadChannels] 从 .tmp 恢复成功: ${recovered.length} 个 channel`);
|
|
48
|
+
// 立即把恢复的内容写回主文件
|
|
49
|
+
await fs.writeFile(CHANNELS_PATH, tmpData, 'utf-8');
|
|
50
|
+
return recovered;
|
|
51
|
+
}
|
|
52
|
+
catch (tmpErr) {
|
|
53
|
+
console.warn('[loadChannels] .tmp 恢复也失败:', tmpErr?.message?.slice(0, 80));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
29
56
|
return [];
|
|
30
57
|
}
|
|
31
58
|
}
|
|
32
|
-
|
|
33
|
-
// 写盘前剥掉任何遗留的 didDocument 字段, 防止历史脏数据撑大文件
|
|
59
|
+
async function rawSaveChannels(channels) {
|
|
34
60
|
const sanitized = channels.map(ch => {
|
|
35
61
|
const { didDocument: _omit, ...rest } = ch;
|
|
36
62
|
return rest;
|
|
37
63
|
});
|
|
38
64
|
const jsonStr = JSON.stringify(sanitized, null, 2);
|
|
39
|
-
|
|
40
|
-
if (jsonStr === lastChannelsJson) {
|
|
65
|
+
if (jsonStr === lastChannelsJson)
|
|
41
66
|
return;
|
|
42
|
-
}
|
|
43
67
|
lastChannelsJson = jsonStr;
|
|
44
68
|
console.log('[saveChannels] 保存频道数据, 数量:', sanitized.length);
|
|
45
|
-
|
|
46
|
-
|
|
69
|
+
// 2026-07-24: 原子写入 — 先写 .tmp 再 rename, 防止崩溃导致 channels.json 损坏
|
|
70
|
+
const tmpPath = CHANNELS_PATH + '.tmp';
|
|
71
|
+
await fs.writeFile(tmpPath, jsonStr, 'utf-8');
|
|
72
|
+
await fs.rename(tmpPath, CHANNELS_PATH);
|
|
47
73
|
lastChannelsWriteAt = Date.now();
|
|
48
74
|
}
|
|
75
|
+
export async function loadChannels() {
|
|
76
|
+
return rawLoadChannels();
|
|
77
|
+
}
|
|
78
|
+
export async function saveChannels(channels) {
|
|
79
|
+
return rawSaveChannels(channels);
|
|
80
|
+
}
|
|
49
81
|
/**
|
|
50
82
|
* loadSession 加 L0 window fallback 链 (2026-07-07 P0-B):
|
|
51
83
|
* 1) full session.json (主路径)
|
package/dist/web/server.js
CHANGED
|
@@ -3728,6 +3728,77 @@ ${goalDesc}
|
|
|
3728
3728
|
res.status(500).json({ error: err.message });
|
|
3729
3729
|
}
|
|
3730
3730
|
});
|
|
3731
|
+
/**
|
|
3732
|
+
* 存储加密私钥: 客户端用 DID 派生 AES-GCM 密钥加密私钥后上传.
|
|
3733
|
+
* body: { encryptedPrivateKey (base64), encryptedPrivateKeyIv (base64), autoPayEnabled? }
|
|
3734
|
+
*/
|
|
3735
|
+
app.post('/channels/:channelId/encrypted-key', async (req, res) => {
|
|
3736
|
+
try {
|
|
3737
|
+
const { channelId } = req.params;
|
|
3738
|
+
const { encryptedPrivateKey, encryptedPrivateKeyIv, autoPayEnabled } = req.body || {};
|
|
3739
|
+
if (!encryptedPrivateKey || !encryptedPrivateKeyIv) {
|
|
3740
|
+
return res.status(400).json({ error: '缺少必填字段: encryptedPrivateKey, encryptedPrivateKeyIv' });
|
|
3741
|
+
}
|
|
3742
|
+
const channels = await loadChannels();
|
|
3743
|
+
const channel = channels.find(c => c.id === channelId);
|
|
3744
|
+
if (!channel)
|
|
3745
|
+
return res.status(404).json({ error: 'Channel not found' });
|
|
3746
|
+
channel.encryptedPrivateKey = encryptedPrivateKey;
|
|
3747
|
+
channel.encryptedPrivateKeyIv = encryptedPrivateKeyIv;
|
|
3748
|
+
if (typeof autoPayEnabled === 'boolean') {
|
|
3749
|
+
channel.autoPayEnabled = autoPayEnabled;
|
|
3750
|
+
}
|
|
3751
|
+
channel.updatedAt = new Date().toISOString();
|
|
3752
|
+
await saveChannels(channels);
|
|
3753
|
+
console.log(`[Wallet] channel ${channelId} 已存储加密私钥 (autoPay=${channel.autoPayEnabled})`);
|
|
3754
|
+
res.json(channel);
|
|
3755
|
+
}
|
|
3756
|
+
catch (err) {
|
|
3757
|
+
res.status(500).json({ error: err.message });
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3760
|
+
/** 清除加密私钥 (用户选择不再自动支付) */
|
|
3761
|
+
app.delete('/channels/:channelId/encrypted-key', async (req, res) => {
|
|
3762
|
+
try {
|
|
3763
|
+
const { channelId } = req.params;
|
|
3764
|
+
const channels = await loadChannels();
|
|
3765
|
+
const channel = channels.find(c => c.id === channelId);
|
|
3766
|
+
if (!channel)
|
|
3767
|
+
return res.status(404).json({ error: 'Channel not found' });
|
|
3768
|
+
channel.encryptedPrivateKey = undefined;
|
|
3769
|
+
channel.encryptedPrivateKeyIv = undefined;
|
|
3770
|
+
channel.autoPayEnabled = false;
|
|
3771
|
+
channel.updatedAt = new Date().toISOString();
|
|
3772
|
+
await saveChannels(channels);
|
|
3773
|
+
console.log(`[Wallet] channel ${channelId} 已清除加密私钥`);
|
|
3774
|
+
res.json(channel);
|
|
3775
|
+
}
|
|
3776
|
+
catch (err) {
|
|
3777
|
+
res.status(500).json({ error: err.message });
|
|
3778
|
+
}
|
|
3779
|
+
});
|
|
3780
|
+
/** 切换 autoPay 开关 */
|
|
3781
|
+
app.patch('/channels/:channelId/auto-pay', async (req, res) => {
|
|
3782
|
+
try {
|
|
3783
|
+
const { channelId } = req.params;
|
|
3784
|
+
const { autoPayEnabled } = req.body || {};
|
|
3785
|
+
if (typeof autoPayEnabled !== 'boolean') {
|
|
3786
|
+
return res.status(400).json({ error: 'autoPayEnabled 必须是 boolean' });
|
|
3787
|
+
}
|
|
3788
|
+
const channels = await loadChannels();
|
|
3789
|
+
const channel = channels.find(c => c.id === channelId);
|
|
3790
|
+
if (!channel)
|
|
3791
|
+
return res.status(404).json({ error: 'Channel not found' });
|
|
3792
|
+
channel.autoPayEnabled = autoPayEnabled;
|
|
3793
|
+
channel.updatedAt = new Date().toISOString();
|
|
3794
|
+
await saveChannels(channels);
|
|
3795
|
+
console.log(`[Wallet] channel ${channelId} autoPay → ${autoPayEnabled}`);
|
|
3796
|
+
res.json(channel);
|
|
3797
|
+
}
|
|
3798
|
+
catch (err) {
|
|
3799
|
+
res.status(500).json({ error: err.message });
|
|
3800
|
+
}
|
|
3801
|
+
});
|
|
3731
3802
|
// 2026-07-07 P1-C: 列出 channel 的项目事件日志 (L2)
|
|
3732
3803
|
// 用于客户端时间线折叠块 + LLM prompt 注入
|
|
3733
3804
|
app.get('/api/events/:channelId', async (req, res) => {
|
package/dist/web/style.css
CHANGED
|
@@ -2123,6 +2123,31 @@ body {
|
|
|
2123
2123
|
border-color: var(--accent);
|
|
2124
2124
|
}
|
|
2125
2125
|
|
|
2126
|
+
.wallet-row .wallet-badges {
|
|
2127
|
+
display: flex;
|
|
2128
|
+
gap: 4px;
|
|
2129
|
+
margin-top: 2px;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
.wallet-row .wallet-badge {
|
|
2133
|
+
font-size: 9px;
|
|
2134
|
+
font-weight: 700;
|
|
2135
|
+
padding: 1px 5px;
|
|
2136
|
+
border-radius: 3px;
|
|
2137
|
+
letter-spacing: 0.3px;
|
|
2138
|
+
text-transform: uppercase;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
.wallet-row .badge-autopay {
|
|
2142
|
+
background: #1a6d1a;
|
|
2143
|
+
color: #8f8;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
.wallet-row .badge-stored {
|
|
2147
|
+
background: #1a4d6d;
|
|
2148
|
+
color: #8cf;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2126
2151
|
.task-item {
|
|
2127
2152
|
background: var(--bg-secondary);
|
|
2128
2153
|
border: 1px solid var(--border);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bolloon/bolloon-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
|
|
6
6
|
"main": "dist/cli-entry.js",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"src/constraint-runtime"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@bolloon/bolloon-agent": "^0.3.
|
|
51
|
+
"@bolloon/bolloon-agent": "^0.3.13",
|
|
52
52
|
"@bolloon/constraint-runtime": "0.1.0",
|
|
53
53
|
"@capacitor/core": "^8.4.1",
|
|
54
54
|
"@capacitor/ios": "^8.4.1",
|
|
@@ -65,6 +65,24 @@
|
|
|
65
65
|
"@multiformats/multiaddr": "^13.0.3",
|
|
66
66
|
"@noble/hashes": "^1.3.0",
|
|
67
67
|
"@rayhanadev/iroh": "^0.1.1",
|
|
68
|
+
"@x402/aptos": "^2.19.0",
|
|
69
|
+
"@x402/avm": "^2.19.0",
|
|
70
|
+
"@x402/axios": "^2.19.0",
|
|
71
|
+
"@x402/core": "^2.19.0",
|
|
72
|
+
"@x402/evm": "^2.19.0",
|
|
73
|
+
"@x402/express": "^2.19.0",
|
|
74
|
+
"@x402/extensions": "^2.19.0",
|
|
75
|
+
"@x402/fastify": "^2.19.0",
|
|
76
|
+
"@x402/fetch": "^2.19.0",
|
|
77
|
+
"@x402/hedera": "^2.19.0",
|
|
78
|
+
"@x402/hono": "^2.19.0",
|
|
79
|
+
"@x402/keeta": "^2.19.0",
|
|
80
|
+
"@x402/mcp": "^2.19.0",
|
|
81
|
+
"@x402/next": "^2.19.0",
|
|
82
|
+
"@x402/paywall": "^2.19.0",
|
|
83
|
+
"@x402/stellar": "^2.19.0",
|
|
84
|
+
"@x402/svm": "^2.19.0",
|
|
85
|
+
"@x402/tvm": "^2.19.0",
|
|
68
86
|
"b4a": "^1.8.1",
|
|
69
87
|
"dotenv": "^17.4.2",
|
|
70
88
|
"esbuild": "^0.24.0",
|
|
@@ -155,6 +173,7 @@
|
|
|
155
173
|
"asarUnpack": [
|
|
156
174
|
"node_modules/@diap/**/*",
|
|
157
175
|
"node_modules/@rayhanadev/**/*",
|
|
176
|
+
"node_modules/@x402/**/*",
|
|
158
177
|
"node_modules/libp2p/**/*",
|
|
159
178
|
"node_modules/@libp2p/**/*"
|
|
160
179
|
]
|