@bolloon/bolloon-agent 0.5.2 → 0.5.4
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 +4 -4
- package/dist/agents/chain/transfer-classify.js +99 -0
- package/dist/agents/chain/transfer-index.js +354 -0
- package/dist/agents/identity/address-binding.js +613 -0
- package/dist/agents/network-pulse.js +706 -27
- package/dist/agents/pi-sdk-tools.js +67 -15
- package/dist/agents/pi-sdk.js +7 -1
- package/dist/agents/session-store.js +144 -3
- package/dist/agents/wallet-tools.js +63 -0
- package/dist/agents/x402/direct-payment.js +689 -0
- package/dist/agents/x402/order-identity.js +408 -0
- package/dist/agents/x402/paid-info-store.js +17 -0
- package/dist/agents/x402/seller-signing.js +614 -0
- package/dist/agents/x402/seller-summary.js +398 -0
- package/dist/cli/identity-command.js +327 -2
- package/dist/cli/ink-app.js +79 -27
- package/dist/cli/input-history.js +119 -0
- package/dist/cli/loading-tui.js +66 -6
- package/dist/cli/log-gate.js +39 -1
- package/dist/cli/protocol-envelope.js +4 -0
- package/dist/cli/session-summary.js +129 -0
- package/dist/cli/session-tree.js +141 -0
- package/dist/cli/status-segments.js +70 -0
- package/dist/cli/stores.js +16 -0
- package/dist/cli/update-commands.js +41 -25
- package/dist/cli/wallet-store.js +159 -0
- package/dist/cli/wallet-table.js +46 -0
- package/dist/cli/x402-buyer-command.js +463 -0
- package/dist/cli/x402-seller-command.js +289 -0
- package/dist/cli-entry.js +43 -5
- package/dist/electron-build/agents/chain/transfer-classify.js +106 -0
- package/dist/electron-build/agents/chain/transfer-classify.js.map +1 -0
- package/dist/electron-build/agents/chain/transfer-index.js +400 -0
- package/dist/electron-build/agents/chain/transfer-index.js.map +1 -0
- package/dist/electron-build/agents/identity/address-binding.js +672 -0
- package/dist/electron-build/agents/identity/address-binding.js.map +1 -0
- package/dist/electron-build/agents/network-pulse.js +714 -28
- package/dist/electron-build/agents/network-pulse.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +68 -15
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +7 -1
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/session-store.js +144 -3
- package/dist/electron-build/agents/session-store.js.map +1 -1
- package/dist/electron-build/agents/wallet-tools.js +102 -0
- package/dist/electron-build/agents/wallet-tools.js.map +1 -0
- package/dist/electron-build/agents/x402/direct-payment.js +744 -0
- package/dist/electron-build/agents/x402/direct-payment.js.map +1 -0
- package/dist/electron-build/agents/x402/order-identity.js +458 -0
- package/dist/electron-build/agents/x402/order-identity.js.map +1 -0
- package/dist/electron-build/agents/x402/paid-info-protocol.js.map +1 -1
- package/dist/electron-build/agents/x402/paid-info-store.js +18 -0
- package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -1
- package/dist/electron-build/agents/x402/seller-signing.js +682 -0
- package/dist/electron-build/agents/x402/seller-signing.js.map +1 -0
- package/dist/electron-build/cli/wallet-store.js +200 -0
- package/dist/electron-build/cli/wallet-store.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +25 -0
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/utils/auto-update.js +2 -2
- package/dist/electron-build/utils/auto-update.js.map +1 -1
- package/dist/electron-build/utils/runtime-bootstrap.js +1 -1
- package/dist/electron-build/utils/runtime-bootstrap.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js +3 -3
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/version-identity.js +1 -1
- package/dist/electron-build/utils/version-identity.js.map +1 -1
- package/dist/electron-build/web/routes-x402-info.js +28 -1
- package/dist/electron-build/web/routes-x402-info.js.map +1 -1
- package/dist/index.js +829 -104
- package/dist/llm/pi-ai.js +23 -0
- package/dist/utils/auto-update.js +2 -2
- package/dist/utils/runtime-bootstrap.js +1 -1
- package/dist/utils/update-health.js +2 -2
- package/dist/utils/update-manager.js +3 -3
- package/dist/utils/version-identity.js +1 -1
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/routes-x402-info.js +28 -1
- package/dist/web/sw.js +1 -1
- package/package.json +2 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
|
+
import { loadWalletTool } from './wallet-tools.js';
|
|
2
3
|
import * as fsSync from 'fs';
|
|
3
4
|
import * as path from 'path';
|
|
4
5
|
import { currentPackageRoot } from '../utils/version-info.js';
|
|
@@ -2980,13 +2981,16 @@ export function registerBuiltinTools(ctx) {
|
|
|
2980
2981
|
const { KeyManager } = await import('@diap/sdk');
|
|
2981
2982
|
const kp = KeyManager.fromPrivateKey(Buffer.from(identity.privateKey, 'hex'));
|
|
2982
2983
|
const displayName = args.name ? String(args.name) : agentId || 'bolloon-agent';
|
|
2983
|
-
// 1. 确保本地 Kubo (自动安装 + 启动)
|
|
2984
|
+
// 1. 确保本地 Kubo (自动安装 + 启动) —— **已经在跑就别调 SDK 那次注定失败的 init**
|
|
2985
|
+
// (`sdk` 后面发布 IPNS 还要用, 所以模块导入留在函数作用域, 只把这一次调用关进短路里)
|
|
2984
2986
|
const sdk = await import('@diap/sdk');
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2987
|
+
if (!(await kuboAlreadyRunning())) {
|
|
2988
|
+
const checkKuboSetup = sdk.checkKuboSetup;
|
|
2989
|
+
if (typeof checkKuboSetup === 'function') {
|
|
2990
|
+
const setup = await checkKuboSetup(true, true);
|
|
2991
|
+
if (!setup?.ready || !setup?.daemonRunning) {
|
|
2992
|
+
return { success: false, error: '本地 Kubo 不可用 (自动安装失败), 无法发布到 IPFS' };
|
|
2993
|
+
}
|
|
2990
2994
|
}
|
|
2991
2995
|
}
|
|
2992
2996
|
// 2. 注册 agent → 上传 DID 文档 → CID
|
|
@@ -3443,7 +3447,26 @@ export function registerBuiltinTools(ctx) {
|
|
|
3443
3447
|
}
|
|
3444
3448
|
// ─── IPFS/IPNS 通用 helper (2026-08-04) ─────────────────────────────────────
|
|
3445
3449
|
// 复用 publish_did 的 checkKuboSetup 自动安装/启动本地 Kubo (darwin-arm64 v0.28.0)
|
|
3450
|
+
/**
|
|
3451
|
+
* Kubo 本地节点已经在跑吗 (只读探测: POST /api/v0/version —— Kubo 只接受 POST).
|
|
3452
|
+
*
|
|
3453
|
+
* 2026-09-30 新增 (leo 报「开启过程的日志没去掉」): SDK 的 `checkKuboSetup(true, true)` 即使守护进程
|
|
3454
|
+
* 已经在跑, 仍会跑一遍 `ipfs init` ⇒ 抛 "daemon is running" ⇒ **打一行 [warn](消息里含"失败"二字,
|
|
3455
|
+
* 于是被启动日志门按"错误不许被吞"放行到屏上)** 外加子进程输出, 两行都落在启动面板之前.
|
|
3456
|
+
* 启动路径上所有调用点都先用这个探测短路: 已经在跑就没有任何可装配的.
|
|
3457
|
+
*/
|
|
3458
|
+
export async function kuboAlreadyRunning(timeoutMs = 1500) {
|
|
3459
|
+
try {
|
|
3460
|
+
await kuboApi('/api/v0/version', undefined, timeoutMs);
|
|
3461
|
+
return true;
|
|
3462
|
+
}
|
|
3463
|
+
catch {
|
|
3464
|
+
return false;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3446
3467
|
export async function ensureKuboReady() {
|
|
3468
|
+
if (await kuboAlreadyRunning())
|
|
3469
|
+
return; // 已在跑 ⇒ 没有可装配的, 也别触发那次注定失败的 init
|
|
3447
3470
|
const sdk = await import('@diap/sdk');
|
|
3448
3471
|
const checkKuboSetup = sdk.checkKuboSetup;
|
|
3449
3472
|
if (typeof checkKuboSetup === 'function') {
|
|
@@ -3483,9 +3506,24 @@ export function registerWalletTools(ctx) {
|
|
|
3483
3506
|
parameters: {},
|
|
3484
3507
|
execute: async () => {
|
|
3485
3508
|
try {
|
|
3486
|
-
const { createWallet } = await
|
|
3509
|
+
const { createWallet } = await loadWalletTool('createWallet');
|
|
3487
3510
|
const r = await createWallet();
|
|
3488
|
-
|
|
3511
|
+
// 2026-09-30: **落台账** (以前只生成不存 ⇒ /wallet 永远「钱包 (0)」)。
|
|
3512
|
+
// 落 ~/.bolloon/wallets/<名字>.json (0600); 输出只给地址 + 路径, 私钥/助记词不回显。
|
|
3513
|
+
let savedPath = '';
|
|
3514
|
+
try {
|
|
3515
|
+
const { addWallet } = await import('../cli/wallet-store.js');
|
|
3516
|
+
const { file } = await addWallet({
|
|
3517
|
+
name: `agent-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`,
|
|
3518
|
+
address: r.address, network: '—', source: 'created',
|
|
3519
|
+
createdAt: r.createdAt || new Date().toISOString(), privateKey: r.privateKey, mnemonic: r.mnemonic,
|
|
3520
|
+
});
|
|
3521
|
+
savedPath = file;
|
|
3522
|
+
}
|
|
3523
|
+
catch (e) {
|
|
3524
|
+
return { success: true, output: `⚠️ 钱包已生成但**没落台账**: ${String(e?.message || e).slice(0, 120)}\n address: ${r.address}\n (私钥只在本次输出里, 请立刻自己存好)` };
|
|
3525
|
+
}
|
|
3526
|
+
return { success: true, output: `✅ 钱包创建成功 (已落台账)\n address: ${r.address}\n 台账: ${savedPath} (0600 — 私钥/助记词已存, 不回显)\n createdAt: ${r.createdAt}` };
|
|
3489
3527
|
}
|
|
3490
3528
|
catch (e) {
|
|
3491
3529
|
return { success: false, error: `创建失败: ${String(e.message || e)}` };
|
|
@@ -3498,9 +3536,23 @@ export function registerWalletTools(ctx) {
|
|
|
3498
3536
|
parameters: { mnemonic: '可选, 12/15/18/21/24 词助记词', privateKey: '可选, 0x 开头的私钥' },
|
|
3499
3537
|
execute: async (args) => {
|
|
3500
3538
|
try {
|
|
3501
|
-
const { importWallet } = await
|
|
3539
|
+
const { importWallet } = await loadWalletTool('importWallet');
|
|
3502
3540
|
const r = await importWallet({ mnemonic: args.mnemonic, privateKey: args.privateKey });
|
|
3503
|
-
|
|
3541
|
+
// 2026-09-30: 同 wallet_create —— 导入也落台账 (0600), 输出不回显私钥
|
|
3542
|
+
let impPath = '';
|
|
3543
|
+
try {
|
|
3544
|
+
const { addWallet } = await import('../cli/wallet-store.js');
|
|
3545
|
+
const { file } = await addWallet({
|
|
3546
|
+
name: `imported-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '')}`,
|
|
3547
|
+
address: r.address, network: '—', source: 'imported',
|
|
3548
|
+
createdAt: new Date().toISOString(), privateKey: args.privateKey, mnemonic: args.mnemonic,
|
|
3549
|
+
});
|
|
3550
|
+
impPath = file;
|
|
3551
|
+
}
|
|
3552
|
+
catch (e) {
|
|
3553
|
+
return { success: true, output: `⚠️ 钱包已导入但**没落台账**: ${String(e?.message || e).slice(0, 120)}\n address: ${r.address}\n (私钥请自己存好, 不会再有第二遍)` };
|
|
3554
|
+
}
|
|
3555
|
+
return { success: true, output: `✅ 钱包导入成功 (已落台账)\n address: ${r.address}\n 台账: ${impPath} (0600 — 私钥已存, 不回显)\n source: ${r.source}` };
|
|
3504
3556
|
}
|
|
3505
3557
|
catch (e) {
|
|
3506
3558
|
return { success: false, error: `导入失败: ${String(e.message || e)}` };
|
|
@@ -3513,7 +3565,7 @@ export function registerWalletTools(ctx) {
|
|
|
3513
3565
|
parameters: { address: '0x 开头的 EVM 地址 (必填)', rpcUrl: '可选 RPC URL (默认 eth.llamarpc.com)' },
|
|
3514
3566
|
execute: async (args) => {
|
|
3515
3567
|
try {
|
|
3516
|
-
const { getBalance } = await
|
|
3568
|
+
const { getBalance } = await loadWalletTool('getBalance');
|
|
3517
3569
|
const r = await getBalance({ address: String(args.address), rpcUrl: args.rpcUrl });
|
|
3518
3570
|
return { success: true, output: `💰 ${r.address}\n ${r.balanceEth} ${r.symbol} (${r.balance} wei)` };
|
|
3519
3571
|
}
|
|
@@ -3528,7 +3580,7 @@ export function registerWalletTools(ctx) {
|
|
|
3528
3580
|
parameters: { message: '要签名的消息 (必填)', privateKey: '0x 开头的私钥 (必填)' },
|
|
3529
3581
|
execute: async (args) => {
|
|
3530
3582
|
try {
|
|
3531
|
-
const { signMessage } = await
|
|
3583
|
+
const { signMessage } = await loadWalletTool('signMessage');
|
|
3532
3584
|
const r = await signMessage({ message: String(args.message), privateKey: String(args.privateKey) });
|
|
3533
3585
|
return { success: true, output: `✅ 签名完成:\n address: ${r.address}\n message: ${r.message}\n signature: ${r.signature}` };
|
|
3534
3586
|
}
|
|
@@ -3543,7 +3595,7 @@ export function registerWalletTools(ctx) {
|
|
|
3543
3595
|
parameters: { privateKey: '私钥 (必填)', to: '接收地址 (必填)', value: '发送 wei 数量 (必填)', data: '可选 0x 开头的 calldata', rpcUrl: '可选 RPC URL' },
|
|
3544
3596
|
execute: async (args) => {
|
|
3545
3597
|
try {
|
|
3546
|
-
const { sendTransaction } = await
|
|
3598
|
+
const { sendTransaction } = await loadWalletTool('sendTransaction');
|
|
3547
3599
|
const r = await sendTransaction({
|
|
3548
3600
|
privateKey: String(args.privateKey),
|
|
3549
3601
|
to: String(args.to),
|
|
@@ -3564,7 +3616,7 @@ export function registerWalletTools(ctx) {
|
|
|
3564
3616
|
parameters: { privateKey: '私钥 (必填)', tokenAddress: 'ERC20 合约地址 (必填)', to: '接收地址 (必填)', amount: 'token 数量', decimals: '可选 token decimals', rpcUrl: '可选 RPC URL' },
|
|
3565
3617
|
execute: async (args) => {
|
|
3566
3618
|
try {
|
|
3567
|
-
const { transferToken } = await
|
|
3619
|
+
const { transferToken } = await loadWalletTool('transferToken');
|
|
3568
3620
|
const r = await transferToken({
|
|
3569
3621
|
privateKey: String(args.privateKey),
|
|
3570
3622
|
tokenAddress: String(args.tokenAddress),
|
|
@@ -3586,7 +3638,7 @@ export function registerWalletTools(ctx) {
|
|
|
3586
3638
|
parameters: { from: '付款方地址 (必填)', to: '收款方地址 (必填)', amount: '金额 (必填)', interval: '周期 (e.g. daily/weekly/monthly)', token: '可选, 默认 ETH' },
|
|
3587
3639
|
execute: async (args) => {
|
|
3588
3640
|
try {
|
|
3589
|
-
const mod = await
|
|
3641
|
+
const mod = await loadWalletTool('autoPay');
|
|
3590
3642
|
const fn = mod.autoPay || mod.setAutoPay || mod.default;
|
|
3591
3643
|
if (!fn)
|
|
3592
3644
|
return { success: false, error: 'autoPay 接口未找到' };
|
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -466,7 +466,13 @@ export class PiAgentSession {
|
|
|
466
466
|
}
|
|
467
467
|
}
|
|
468
468
|
catch (err) {
|
|
469
|
-
|
|
469
|
+
// 2026-09-30: "会话文件不存在" 不是失败 —— 新 channel / 还没落过盘的会话就是这种情况
|
|
470
|
+
// (真机噪声: `hydrateMessageHistory 失败 (non-fatal): session not found: ch_bolloon:default`)。
|
|
471
|
+
// 只有**非** not-found 的错误才值得 warn。
|
|
472
|
+
const __msg = String(err?.message || err);
|
|
473
|
+
if (!/session not found|ENOENT/i.test(__msg)) {
|
|
474
|
+
console.warn(`[PiAgent] hydrateMessageHistory 失败 (non-fatal): ${__msg.slice(0, 100)}`);
|
|
475
|
+
}
|
|
470
476
|
}
|
|
471
477
|
}
|
|
472
478
|
/**
|
|
@@ -102,6 +102,59 @@ export class SessionStore {
|
|
|
102
102
|
* fs.writeFile 抛 EINVAL. 修法: filename 层 escape `:` → `__`, key 保持不变
|
|
103
103
|
* (load/save/listKeys/deleteKey 全部透明).
|
|
104
104
|
*/
|
|
105
|
+
/**
|
|
106
|
+
* **写**的时候用这个 —— 与 resolveExisting 的区别: 不存在**不抛错**, 返回规范路径 (等于新建)。
|
|
107
|
+
*
|
|
108
|
+
* 2026-09-30 回归事故: 我把 resolveExisting (读语义, 不存在就抛 not found) 批量替换进了
|
|
109
|
+
* saveMessages ⇒ 退出存档对"还没落盘的新会话"直接抛 `session not found`
|
|
110
|
+
* (leo 贴的 `⚠ session 存档失败: SessionStore: session not found: 20260930_175348_870c70`)。
|
|
111
|
+
* 写路径必须容忍"还没有文件" —— 那正是它要创建的东西。
|
|
112
|
+
*/
|
|
113
|
+
async resolveWritePath(key) {
|
|
114
|
+
const canonical = this.pathFor(key);
|
|
115
|
+
try {
|
|
116
|
+
await fs.access(canonical);
|
|
117
|
+
return canonical;
|
|
118
|
+
}
|
|
119
|
+
catch { /* 试老名 */ }
|
|
120
|
+
const legacy = path.join(this.cacheDir, `${key}.json`);
|
|
121
|
+
try {
|
|
122
|
+
await fs.access(legacy);
|
|
123
|
+
return legacy;
|
|
124
|
+
}
|
|
125
|
+
catch { /* 新会话 */ }
|
|
126
|
+
return canonical;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 读的时候用这个 —— 兼容**老文件名**。
|
|
130
|
+
*
|
|
131
|
+
* 2026-09-30 发现: 早期版本直接把 `channelId:sessionId` 当文件名写盘 (`real-123:default.json`),
|
|
132
|
+
* 后来 Windows 兼容改成 filenameEscape (`:` → `__`), 但**老文件没迁移** ⇒ 用 pathFor 去读
|
|
133
|
+
* 老会话一律 ENOENT (`/sessions` 里那些 `real-*:default` 全是 Preview `—`, 也续不上)。
|
|
134
|
+
* 这里: 先试新名, 不存在再试老名; 老名存在时**顺手迁移**成新名 (迁移失败就继续读老名)。
|
|
135
|
+
*/
|
|
136
|
+
async resolveExisting(key) {
|
|
137
|
+
const canonical = this.pathFor(key);
|
|
138
|
+
try {
|
|
139
|
+
await fs.access(canonical);
|
|
140
|
+
return canonical;
|
|
141
|
+
}
|
|
142
|
+
catch { /* 试老名 */ }
|
|
143
|
+
const legacy = path.join(this.cacheDir, `${key}.json`);
|
|
144
|
+
try {
|
|
145
|
+
await fs.access(legacy);
|
|
146
|
+
try {
|
|
147
|
+
await fs.rename(legacy, canonical);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return legacy;
|
|
151
|
+
} // 迁移失败也能读
|
|
152
|
+
return canonical;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
throw new Error(`SessionStore: session not found: ${key}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
105
158
|
pathFor(key) {
|
|
106
159
|
if (!key || key.includes('/') || key.includes('..')) {
|
|
107
160
|
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
@@ -135,7 +188,7 @@ export class SessionStore {
|
|
|
135
188
|
totalCount: messages.length,
|
|
136
189
|
},
|
|
137
190
|
};
|
|
138
|
-
const filePath = this.
|
|
191
|
+
const filePath = await this.resolveWritePath(key); // 写路径: 新会话就建 (不抛 not found)
|
|
139
192
|
const tmpPath = `${filePath}.tmp`;
|
|
140
193
|
await fs.writeFile(tmpPath, JSON.stringify(payload, null, 2), 'utf-8');
|
|
141
194
|
await fs.rename(tmpPath, filePath);
|
|
@@ -177,7 +230,7 @@ export class SessionStore {
|
|
|
177
230
|
totalCount: messages.length,
|
|
178
231
|
},
|
|
179
232
|
};
|
|
180
|
-
const filePath = this.pathFor(key);
|
|
233
|
+
const filePath = this.pathFor(key); // 同步保存一律写规范文件名
|
|
181
234
|
const tmpPath = `${filePath}.tmp`;
|
|
182
235
|
fsSyncWrite(tmpPath, JSON.stringify(payload, null, 2));
|
|
183
236
|
fsSyncRename(tmpPath, filePath);
|
|
@@ -192,7 +245,11 @@ export class SessionStore {
|
|
|
192
245
|
if (!key || key.includes('/') || key.includes('..')) {
|
|
193
246
|
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
194
247
|
}
|
|
195
|
-
|
|
248
|
+
// 2026-09-30 回归修复: 读路径**不许**在"会话不存在"时抛错 —— 仓里的契约是
|
|
249
|
+
// `loadMessages('cli:nonexistent') === null` (见 src/test/session-store.test.ts)。
|
|
250
|
+
// 用容错解析 (不存在 ⇒ 返回规范路径), 随后的 readFile 命中 ENOENT ⇒ return null。
|
|
251
|
+
// 老文件名(`real-123:default.json` 那批)由 resolveWritePath 兼容: 存在就返回老路径。
|
|
252
|
+
const filePath = await this.resolveWritePath(key);
|
|
196
253
|
let raw;
|
|
197
254
|
try {
|
|
198
255
|
raw = await fs.readFile(filePath, 'utf-8');
|
|
@@ -222,6 +279,90 @@ export class SessionStore {
|
|
|
222
279
|
return [];
|
|
223
280
|
}
|
|
224
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* 列最近改动的会话 (按 mtime 倒序), 带条数与首条用户消息预览。
|
|
284
|
+
*
|
|
285
|
+
* 2026-09-30 (leo: 「/session 没有反应, 没展示最新记录, 无法选择继续」): 只为"选一个继续"服务 ——
|
|
286
|
+
* **只解析最新的 limit 个文件**(缓存目录可能上千个, 全量解析会卡住输入), 解析失败的那条如实记 0。
|
|
287
|
+
*/
|
|
288
|
+
async listRecent(limit = 10) {
|
|
289
|
+
const out = [];
|
|
290
|
+
let files = [];
|
|
291
|
+
try {
|
|
292
|
+
files = await fs.readdir(this.cacheDir);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
const cands = [];
|
|
298
|
+
for (const f of files) {
|
|
299
|
+
if (!f.endsWith('.json') || f.endsWith('.tmp'))
|
|
300
|
+
continue;
|
|
301
|
+
try {
|
|
302
|
+
const st = await fs.stat(path.join(this.cacheDir, f));
|
|
303
|
+
cands.push({ key: SessionStore.filenameUnescape(f.slice(0, -'.json'.length)), mtimeMs: st.mtimeMs });
|
|
304
|
+
}
|
|
305
|
+
catch { /* 单条 stat 失败跳过 */ }
|
|
306
|
+
}
|
|
307
|
+
cands.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
308
|
+
for (const c of cands.slice(0, Math.max(1, limit))) {
|
|
309
|
+
let messages = 0;
|
|
310
|
+
let preview = '';
|
|
311
|
+
let title = c.key; // 会话文件里没有 title 字段 ⇒ 默认就是 key (web 侧才有会话名)
|
|
312
|
+
try {
|
|
313
|
+
// 兼容老文件名 (`real-123:default.json` 未转义那批) —— 读不到规范名就试原名
|
|
314
|
+
let text = '';
|
|
315
|
+
try {
|
|
316
|
+
text = await fs.readFile(this.pathFor(c.key), 'utf-8');
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
text = await fs.readFile(path.join(this.cacheDir, `${c.key}.json`), 'utf-8');
|
|
320
|
+
}
|
|
321
|
+
const raw = JSON.parse(text);
|
|
322
|
+
const arr = Array.isArray(raw) ? raw : (raw?.messages || []);
|
|
323
|
+
messages = Array.isArray(arr) ? arr.length : 0;
|
|
324
|
+
const md = (!Array.isArray(raw) && raw?.metadata) || {};
|
|
325
|
+
if (typeof md.title === 'string' && md.title.trim())
|
|
326
|
+
title = md.title.trim();
|
|
327
|
+
const mdPreview = typeof md.preview === 'string' ? md.preview.trim() : '';
|
|
328
|
+
// 预览要用**真实用户话**, 跳过自动注入的上下文块 (否则全是 <!-- current-turn … -->)
|
|
329
|
+
const isInjected = (x) => !!x && (/^<!--/.test(x.trim()) || /^#\s*你的项目上下文/.test(x.trim()) || /^\[cron\]/.test(x.trim()));
|
|
330
|
+
const first = (Array.isArray(arr) ? arr : []).find((m) => m && m.role === 'user' && m.content && !isInjected(String(m.content)));
|
|
331
|
+
preview = mdPreview || String(first?.content || '').replace(/\s+/g, ' ').trim().slice(0, 120);
|
|
332
|
+
}
|
|
333
|
+
catch { /* 损坏文件: 仍列出, 只是 0 条无预览 */ }
|
|
334
|
+
out.push({ key: c.key, title, mtimeMs: c.mtimeMs, messages, preview });
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 合并写回会话文件的 metadata (原子: 先写 .tmp 再 rename)。
|
|
340
|
+
* 2026-09-30 (leo: 「Title/Preview 要有内容, 使用 AI 来总结进去」): AI 总结结果落在
|
|
341
|
+
* `metadata.{title,preview,summarizedAt}`, **只算一次**, 后续 /sessions 直接读。
|
|
342
|
+
* 原子写是硬要求 —— 会话文件是用户的会话历史, 半截文件比没有标题糟得多。
|
|
343
|
+
*/
|
|
344
|
+
async updateMetadata(key, patch) {
|
|
345
|
+
if (!key || key.includes('/') || key.includes('..'))
|
|
346
|
+
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
347
|
+
const file = await this.resolveWritePath(key);
|
|
348
|
+
let raw = null;
|
|
349
|
+
try {
|
|
350
|
+
raw = JSON.parse(await fs.readFile(file, 'utf-8'));
|
|
351
|
+
}
|
|
352
|
+
catch (e) {
|
|
353
|
+
if (e?.code !== 'ENOENT')
|
|
354
|
+
throw new Error(`会话文件读不了 (${file}): ${String(e?.message || e).slice(0, 120)}`);
|
|
355
|
+
// 还没有文件 ⇒ 建成只有 metadata 的会话 (元数据更新对新会话应当有效, 不是错误)
|
|
356
|
+
}
|
|
357
|
+
const next = raw === null
|
|
358
|
+
? { key, messages: [], metadata: { ...patch } }
|
|
359
|
+
: Array.isArray(raw)
|
|
360
|
+
? { key, messages: raw, metadata: { ...patch } }
|
|
361
|
+
: { ...raw, metadata: { ...(raw?.metadata || {}), ...patch } };
|
|
362
|
+
const tmp = `${file}.tmp`;
|
|
363
|
+
await fs.writeFile(tmp, JSON.stringify(next, null, 2), 'utf-8');
|
|
364
|
+
await fs.rename(tmp, file);
|
|
365
|
+
}
|
|
225
366
|
/** 删一条 session — 失败抛错. */
|
|
226
367
|
async deleteKey(key) {
|
|
227
368
|
if (!key || key.includes('/') || key.includes('..')) {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wallet-tools.ts — 钱包工具的**双布局加载器** (2026-09-30)
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有这个文件:
|
|
5
|
+
* 钱包真身是 `constraint-runtime` 里的 `tools/WalletTools/*`。构建 (`npm run build:main` =
|
|
6
|
+
* `tsc && node scripts/copy-constraint-runtime.mjs`) 把 `src/constraint-runtime/dist/*`
|
|
7
|
+
* 复制成 `dist/constraint-runtime/*` —— **少一层 `dist/`**。于是同一句相对导入:
|
|
8
|
+
* `../constraint-runtime/dist/tools/WalletTools/createWallet.js`
|
|
9
|
+
* 在源码态 (src/agents) 能解析 ✓, 构建后 (dist/agents) 解析到 `dist/constraint-runtime/dist/…` ✗
|
|
10
|
+
* ⇒ `wallet_create` 报 `Cannot find module '…/dist/constraint-runtime/src/tools/…'` (leo 2026-09-30 贴的屏),
|
|
11
|
+
* 而 `/wallet` 又因为"从来没写过文件"显示「钱包 (0)」—— 两个毛病叠在一起, 看着像"钱包功能不存在"。
|
|
12
|
+
*
|
|
13
|
+
* 规则: **按存在的路径挑**, 挑不到就抛错并**列出试过的路径** (不许含糊成"未知错误")。
|
|
14
|
+
* 注意: 这里用**变量**给 `import()` ⇒ 不做静态解析 (跨布局本来就不能静态解析)。
|
|
15
|
+
*/
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import { pathToFileURL } from 'url';
|
|
18
|
+
// 2026-09-30: 不用 import.meta —— 本文件会被 electron 那条 CJS 链间接引到,
|
|
19
|
+
// `tsc -p tsconfig.electron.json` 会报 TS1343 (module: CommonJS 下不允许 import.meta),
|
|
20
|
+
// 而 `npm publish` 的 prepublishOnly (build:all → build:electron) 会因此挂住。
|
|
21
|
+
// 仓里现成的替代: cjsModuleDir() (CJS/ESM 都能用) + currentPackageRoot() + firstExisting()。
|
|
22
|
+
import { cjsModuleDir, firstExisting } from '../utils/module-context.js';
|
|
23
|
+
import { currentPackageRoot } from '../utils/version-info.js';
|
|
24
|
+
/** 候选路径 (先近后远; 覆盖 dev / dist 两种布局) */
|
|
25
|
+
export function walletToolCandidates(name) {
|
|
26
|
+
const root = (() => { try {
|
|
27
|
+
return currentPackageRoot();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return process.cwd();
|
|
31
|
+
} })();
|
|
32
|
+
const here = cjsModuleDir() || path.join(root, 'dist', 'agents'); // src/agents 或 dist/agents
|
|
33
|
+
return [
|
|
34
|
+
// ① 构建后布局: dist/constraint-runtime/tools/… (从 dist/agents 出发)
|
|
35
|
+
path.join(here, '..', 'constraint-runtime', 'tools', 'WalletTools', `${name}.js`),
|
|
36
|
+
// ② 源码态布局: src/constraint-runtime/dist/tools/… (从 src/agents 出发)
|
|
37
|
+
path.join(here, '..', 'constraint-runtime', 'dist', 'tools', 'WalletTools', `${name}.js`),
|
|
38
|
+
// ③ 兜底: 从仓根分别按两种布局找
|
|
39
|
+
path.join(root, 'dist', 'constraint-runtime', 'tools', 'WalletTools', `${name}.js`),
|
|
40
|
+
path.join(root, 'src', 'constraint-runtime', 'dist', 'tools', 'WalletTools', `${name}.js`),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
/** 加载一个钱包工具模块 (挑第一个真实存在的; 都不在就带着"试过哪些"报错) */
|
|
44
|
+
export async function loadWalletTool(name) {
|
|
45
|
+
const tried = walletToolCandidates(name);
|
|
46
|
+
const hit = firstExisting(tried);
|
|
47
|
+
if (hit)
|
|
48
|
+
return (await import(pathToFileURL(hit).href));
|
|
49
|
+
throw new Error(`找不到钱包工具 ${name} (试过 ${tried.length} 个路径):\n ${tried.join('\n ')}`);
|
|
50
|
+
}
|
|
51
|
+
/** 给诊断用: 当前各自解析到哪 (打印用的字符串, 不加载) */
|
|
52
|
+
export function walletToolLayout() {
|
|
53
|
+
const names = ['createWallet', 'getBalance', 'signMessage'];
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const n of names) {
|
|
56
|
+
for (const c of walletToolCandidates(n)) {
|
|
57
|
+
if (out.some(o => o.pattern === c))
|
|
58
|
+
continue;
|
|
59
|
+
out.push({ pattern: c, exists: firstExisting([c]) !== null });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|