@bolloon/bolloon-agent 0.3.46 → 0.3.47
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 +5 -3
- package/dist/bootstrap/context-os.js +19 -10
- package/dist/cli/loading-tui.js +18 -10
- package/dist/index.js +95 -23
- package/package.json +1 -1
|
@@ -1776,7 +1776,8 @@ export function registerBuiltinTools(ctx) {
|
|
|
1776
1776
|
execute: async () => {
|
|
1777
1777
|
try {
|
|
1778
1778
|
const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
|
|
1779
|
-
|
|
1779
|
+
// 2026-08-09: 按 agentId 分区读取 (每个智能体独立 Context OS)
|
|
1780
|
+
const listings = await readContextAssets(undefined, undefined, undefined, ctx.agentId || '');
|
|
1780
1781
|
const total = listings.reduce((s, l) => s + l.fileCount, 0);
|
|
1781
1782
|
if (total === 0)
|
|
1782
1783
|
return { success: true, output: '📂 Context OS 资产层已就绪 (12+3 层), 当前暂无资产. 有价值的内容用 write_context_asset 写入对应层.' };
|
|
@@ -1816,7 +1817,7 @@ export function registerBuiltinTools(ctx) {
|
|
|
1816
1817
|
tags = t.map(String);
|
|
1817
1818
|
}
|
|
1818
1819
|
catch { /* tags 解析失败 */ }
|
|
1819
|
-
const r = await writeContextAsset({ layer, title, content, tags, domain: args.domain ? String(args.domain) : undefined });
|
|
1820
|
+
const r = await writeContextAsset({ layer, title, content, tags, domain: args.domain ? String(args.domain) : undefined }, undefined, ctx.agentId || '');
|
|
1820
1821
|
if (!r.ok)
|
|
1821
1822
|
return { success: false, error: r.error };
|
|
1822
1823
|
if (r.skipped)
|
|
@@ -1840,7 +1841,8 @@ export function registerBuiltinTools(ctx) {
|
|
|
1840
1841
|
const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
|
|
1841
1842
|
const layer = args.layer ? String(args.layer) : undefined;
|
|
1842
1843
|
const kw = args.keyword ? String(args.keyword) : undefined;
|
|
1843
|
-
|
|
1844
|
+
// 2026-08-09: 按 agentId 分区读取 (每个智能体独立 Context OS)
|
|
1845
|
+
const listings = await readContextAssets(layer, kw, undefined, ctx.agentId || '');
|
|
1844
1846
|
if (listings.every((l) => l.fileCount === 0)) {
|
|
1845
1847
|
return { success: true, output: layer ? `📂 资产层 ${layer} 暂无资产` : '📂 资产层暂无资产' };
|
|
1846
1848
|
}
|
|
@@ -48,7 +48,16 @@ const LAYER_KEYS = new Set(CONTEXT_OS_LAYERS.map((l) => l.key));
|
|
|
48
48
|
export function getContextOsRoot(home = os.homedir()) {
|
|
49
49
|
return path.join(home, '.bolloon', 'context-os');
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* 2026-08-09: 层目录 — 支持按 agentId 分区 (每个智能体独立 Context OS).
|
|
53
|
+
* agentId 有值 → ~/.bolloon/context-os/<sanitizeAgentId>/<layer>
|
|
54
|
+
* agentId 空 → ~/.bolloon/context-os/<layer> (旧全局路径, 兼容)
|
|
55
|
+
*/
|
|
56
|
+
export function getLayerDir(layer, home = os.homedir(), agentId) {
|
|
57
|
+
const safeId = String(agentId || '').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
58
|
+
if (safeId) {
|
|
59
|
+
return path.join(getContextOsRoot(home), safeId, layer);
|
|
60
|
+
}
|
|
52
61
|
return path.join(getContextOsRoot(home), layer);
|
|
53
62
|
}
|
|
54
63
|
/** 校验 layer 合法; 非法返回 null */
|
|
@@ -86,11 +95,11 @@ ${l.usage}
|
|
|
86
95
|
→ 阶段2 固化 (本层唯一位置) → 阶段3 索引化 (高频引用) → 阶段4 归档/删除.
|
|
87
96
|
`;
|
|
88
97
|
}
|
|
89
|
-
export async function ensureContextOsDirs(home) {
|
|
98
|
+
export async function ensureContextOsDirs(home, agentId) {
|
|
90
99
|
const root = getContextOsRoot(home);
|
|
91
100
|
await fs.mkdir(root, { recursive: true });
|
|
92
101
|
for (const l of CONTEXT_OS_LAYERS) {
|
|
93
|
-
const dir = getLayerDir(l.key, home);
|
|
102
|
+
const dir = getLayerDir(l.key, home, agentId);
|
|
94
103
|
await fs.mkdir(dir, { recursive: true });
|
|
95
104
|
const readmePath = path.join(dir, 'README.md');
|
|
96
105
|
try {
|
|
@@ -106,7 +115,7 @@ export async function ensureContextOsDirs(home) {
|
|
|
106
115
|
* 文件名: <ts>-<slug>.md; frontmatter v2 (stage0 = 临时价值点, 待验证).
|
|
107
116
|
* 幂等: 同层同 slug 已存在 → 跳过 (不重复造文件, Context OS §6 Step3).
|
|
108
117
|
*/
|
|
109
|
-
export async function writeContextAsset(input, home) {
|
|
118
|
+
export async function writeContextAsset(input, home, agentId) {
|
|
110
119
|
const layer = resolveLayer(input.layer);
|
|
111
120
|
if (!layer) {
|
|
112
121
|
return { ok: false, error: `layer 非法: '${input.layer}'. 合法: ${CONTEXT_OS_LAYERS.map((l) => l.key).join(' / ')}` };
|
|
@@ -117,15 +126,15 @@ export async function writeContextAsset(input, home) {
|
|
|
117
126
|
const content = String(input.content || '').trim();
|
|
118
127
|
if (!content)
|
|
119
128
|
return { ok: false, error: 'content 必填' };
|
|
120
|
-
await ensureContextOsDirs(home);
|
|
129
|
+
await ensureContextOsDirs(home, agentId);
|
|
121
130
|
const now = new Date().toISOString();
|
|
122
131
|
const ts = Date.now();
|
|
123
132
|
const slug = slugify(title);
|
|
124
133
|
const fileName = `${ts}-${slug}.md`;
|
|
125
|
-
const filePath = path.join(getLayerDir(layer.key, home), fileName);
|
|
134
|
+
const filePath = path.join(getLayerDir(layer.key, home, agentId), fileName);
|
|
126
135
|
// 幂等: 同 slug 已存在 → 跳过
|
|
127
136
|
try {
|
|
128
|
-
const files = await fs.readdir(getLayerDir(layer.key, home));
|
|
137
|
+
const files = await fs.readdir(getLayerDir(layer.key, home, agentId));
|
|
129
138
|
if (files.some((f) => f.endsWith(`-${slug}.md`))) {
|
|
130
139
|
return { ok: true, skipped: true, error: `同标题资产已存在 (${slug}.md), 未重复写入` };
|
|
131
140
|
}
|
|
@@ -157,7 +166,7 @@ export async function writeContextAsset(input, home) {
|
|
|
157
166
|
}
|
|
158
167
|
}
|
|
159
168
|
/** 列出层资产; layer 为空 → 全层汇总 */
|
|
160
|
-
export async function readContextAssets(layer, keyword, home) {
|
|
169
|
+
export async function readContextAssets(layer, keyword, home, agentId) {
|
|
161
170
|
const root = getContextOsRoot(home);
|
|
162
171
|
const kw = String(keyword || '').trim().toLowerCase();
|
|
163
172
|
const wanted = layer ? [resolveLayer(layer)].filter(Boolean).map((l) => l.key) : CONTEXT_OS_LAYERS.map((l) => l.key);
|
|
@@ -165,11 +174,11 @@ export async function readContextAssets(layer, keyword, home) {
|
|
|
165
174
|
for (const key of wanted) {
|
|
166
175
|
const l = resolveLayer(key);
|
|
167
176
|
try {
|
|
168
|
-
const files = (await fs.readdir(getLayerDir(key, home))).filter((f) => f.endsWith('.md') && f !== 'README.md');
|
|
177
|
+
const files = (await fs.readdir(getLayerDir(key, home, agentId))).filter((f) => f.endsWith('.md') && f !== 'README.md');
|
|
169
178
|
const entries = [];
|
|
170
179
|
for (const f of files) {
|
|
171
180
|
try {
|
|
172
|
-
const raw = await fs.readFile(path.join(getLayerDir(key, home), f), 'utf-8');
|
|
181
|
+
const raw = await fs.readFile(path.join(getLayerDir(key, home, agentId), f), 'utf-8');
|
|
173
182
|
const titleM = raw.match(/^title:\s*(.+)$/m);
|
|
174
183
|
const createdM = raw.match(/^created:\s*(.+)$/m);
|
|
175
184
|
const title = titleM ? titleM[1].trim() : f.replace(/\.md$/, '');
|
package/dist/cli/loading-tui.js
CHANGED
|
@@ -45,14 +45,20 @@ function getPackageVersion() {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
const BOLLOON_VERSION = getPackageVersion();
|
|
48
|
-
// ── 品牌图标:
|
|
48
|
+
// ── 品牌图标: 笑脸机器人 (2026-08-09, bolloon 色系填充) ──────
|
|
49
|
+
// 头: 主色边框 + 亮绿填充 (C_ACCENT_BG); 眼睛 ◉ / 嘴 ◡ 用亮色填充;
|
|
50
|
+
// 末行 BOLLOON 主色艺术字 (仅 printBanner 用, brandArtLines 会裁掉避免双 logo).
|
|
51
|
+
const ROBOT_HEAD = [
|
|
52
|
+
`${C_ACCENT} ╭───────╮${RESET}`,
|
|
53
|
+
`${C_ACCENT} ╭─╯${C_ACCENT_BG} ${C_WHITE}◉${C_ACCENT_BG} ${C_WHITE}◉${C_ACCENT_BG} ${RESET}${C_ACCENT}╰─╮${RESET}`,
|
|
54
|
+
`${C_ACCENT} │${C_ACCENT_BG} ${C_WHITE}◡${C_ACCENT_BG} ${RESET}${C_ACCENT}│${RESET}`,
|
|
55
|
+
`${C_ACCENT} ╰─╮${C_ACCENT_BG} ${RESET}${C_ACCENT}╭─╯${RESET}`,
|
|
56
|
+
`${C_ACCENT} ╰───┬───╯${RESET}`,
|
|
57
|
+
`${C_ACCENT} │${RESET}`,
|
|
58
|
+
];
|
|
49
59
|
export const BOLLOON_ICON = [
|
|
50
|
-
|
|
51
|
-
`${C_ACCENT}
|
|
52
|
-
`${C_ACCENT} ════◆════${RESET}`,
|
|
53
|
-
`${C_ACCENT} ╲ ╱${RESET}`,
|
|
54
|
-
`${C_ACCENT} ╲${RESET}`,
|
|
55
|
-
`${C_ACCENT} ✦ ╲${RESET}`,
|
|
60
|
+
...ROBOT_HEAD,
|
|
61
|
+
`${C_ACCENT}${BOLD} BOLLOON${RESET}`,
|
|
56
62
|
].join('\n');
|
|
57
63
|
// ── 艺术字: BOLLOON (box 字体) + Bolloon Agent 副标题 ──
|
|
58
64
|
export const BOLLOON_BANNER = [
|
|
@@ -64,9 +70,10 @@ export const BOLLOON_BANNER = [
|
|
|
64
70
|
`${C_TEXT}${BOLD}╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝${RESET}`,
|
|
65
71
|
`${C_DIM}Bolloon Agent v${BOLLOON_VERSION}${RESET}`,
|
|
66
72
|
].join('\n');
|
|
67
|
-
/** 艺术字全部行 (
|
|
73
|
+
/** 艺术字全部行 (机器人头在左, BOLLOON 艺术字在右), 供框内渲染 */
|
|
68
74
|
export function brandArtLines() {
|
|
69
|
-
|
|
75
|
+
// 2026-08-09: icon 只取机器人头 (裁掉末行 BOLLOON 文字), 避免和右侧 banner 双 logo
|
|
76
|
+
const icon = ROBOT_HEAD;
|
|
70
77
|
const banner = BOLLOON_BANNER.split('\n');
|
|
71
78
|
const gap = 2;
|
|
72
79
|
const iconW = Math.max(1, ...icon.map(l => dispWidth(l)));
|
|
@@ -81,8 +88,9 @@ export function brandArtLines() {
|
|
|
81
88
|
return rows;
|
|
82
89
|
}
|
|
83
90
|
export function printBanner(version) {
|
|
91
|
+
// 2026-08-09: 新品牌 logo = 笑脸机器人 (BOLLOON_ICON 自带 BOLLOON 文字),
|
|
92
|
+
// 不再叠加旧 box 字体 banner (避免双 logo)
|
|
84
93
|
console.log(BOLLOON_ICON);
|
|
85
|
-
console.log(BOLLOON_BANNER);
|
|
86
94
|
if (version)
|
|
87
95
|
console.log(`${C_DIM} Bolloon Agent v${version}${RESET}`);
|
|
88
96
|
console.log(`${C_DIM} P2P AI Agent · 文档智能体${RESET}`);
|
package/dist/index.js
CHANGED
|
@@ -271,12 +271,44 @@ async function bootstrapIroh(keypair, name) {
|
|
|
271
271
|
// Agent 懒加载
|
|
272
272
|
// ---------------------------------------------------------------------------
|
|
273
273
|
let agent = null;
|
|
274
|
+
/** 2026-08-09: agent 当前绑定的 channel id (null = 默认 harness 身份) — 切换时据此重建 */
|
|
275
|
+
let agentBoundChannelId = null;
|
|
274
276
|
let harness = null;
|
|
275
277
|
let hybridMessenger = null;
|
|
276
278
|
let agentIdentity = null;
|
|
277
279
|
async function getAgent() {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
// 2026-08-09: agent 身份绑定当前 active channel — 切换 / 新建 channel 后重建.
|
|
281
|
+
// 旧实现: agent 全局单例 + peerId:'harness' 固定, 切 channel 身份不变 (bug).
|
|
282
|
+
// 新实现: channel 有 agentId/did/publicKey/persona 时按 channel 建 session,
|
|
283
|
+
// agentIdentity 同步更新, loadSessionKey 回灌该 channel 的历史.
|
|
284
|
+
const targetChannelId = cliActiveChannelId || null;
|
|
285
|
+
if (agent && agentBoundChannelId === targetChannelId)
|
|
286
|
+
return agent;
|
|
287
|
+
// 读取当前 active channel 的持久身份
|
|
288
|
+
let chIdentity = null;
|
|
289
|
+
if (targetChannelId) {
|
|
290
|
+
try {
|
|
291
|
+
const { getIdentityStore } = await import('./agents/agent-identity-store.js');
|
|
292
|
+
const store = getIdentityStore();
|
|
293
|
+
await store.load();
|
|
294
|
+
const ch = store.rawChannels.find((c) => c.id === targetChannelId);
|
|
295
|
+
if (ch)
|
|
296
|
+
chIdentity = ch;
|
|
297
|
+
}
|
|
298
|
+
catch { /* 读不到就退默认 */ }
|
|
299
|
+
}
|
|
300
|
+
let identityDoc;
|
|
301
|
+
if (chIdentity?.did && chIdentity.publicKey) {
|
|
302
|
+
// channel 已有持久 DID → 用 channel 身份
|
|
303
|
+
identityDoc = {
|
|
304
|
+
did: chIdentity.did,
|
|
305
|
+
name: chIdentity.persona?.name || chIdentity.name || 'agent',
|
|
306
|
+
publicKey: chIdentity.publicKey,
|
|
307
|
+
createdAt: Date.now(),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
else if (agentIdentity) {
|
|
311
|
+
identityDoc = {
|
|
280
312
|
did: agentIdentity.did,
|
|
281
313
|
name: agentIdentity.name,
|
|
282
314
|
publicKey: agentIdentity.publicKey,
|
|
@@ -285,15 +317,39 @@ async function getAgent() {
|
|
|
285
317
|
p2pChannel: agentIdentity.p2pChannel,
|
|
286
318
|
cid: agentIdentity.cid,
|
|
287
319
|
ipnsName: agentIdentity.ipnsName
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
identityDoc = undefined;
|
|
324
|
+
}
|
|
325
|
+
const loadSessionKey = targetChannelId
|
|
326
|
+
? `${targetChannelId}:${chIdentity?.currentSessionId || 'default'}`
|
|
327
|
+
: undefined;
|
|
328
|
+
agent = await createAgentSession({
|
|
329
|
+
cwd: process.cwd(),
|
|
330
|
+
peerId: targetChannelId ?? 'harness',
|
|
331
|
+
identityDoc,
|
|
332
|
+
// 2026-08-09: 透传 channel.agentId → persona docs 按 agent 加载 (身份真正变化)
|
|
333
|
+
agentId: chIdentity?.agentId || (targetChannelId ? undefined : agentIdentity?.name),
|
|
334
|
+
loadSessionKey,
|
|
335
|
+
});
|
|
336
|
+
agentBoundChannelId = targetChannelId;
|
|
337
|
+
// 同步 agentIdentity (状态栏 / 身份引用)
|
|
338
|
+
if (chIdentity) {
|
|
339
|
+
agentIdentity = {
|
|
340
|
+
did: chIdentity.did || agentIdentity?.did || '',
|
|
341
|
+
name: chIdentity.persona?.name || chIdentity.name || 'agent',
|
|
342
|
+
publicKey: chIdentity.publicKey || agentIdentity?.publicKey || '',
|
|
343
|
+
peerId: targetChannelId ?? undefined,
|
|
344
|
+
};
|
|
294
345
|
}
|
|
295
346
|
return agent;
|
|
296
347
|
}
|
|
348
|
+
/** 强制重建 agent (切 channel / 新建 agent 后调用) */
|
|
349
|
+
function invalidateAgent() {
|
|
350
|
+
agent = null;
|
|
351
|
+
agentBoundChannelId = null;
|
|
352
|
+
}
|
|
297
353
|
// ---------------------------------------------------------------------------
|
|
298
354
|
// Dispatch
|
|
299
355
|
// ---------------------------------------------------------------------------
|
|
@@ -575,6 +631,13 @@ async function processInput(input, comm) {
|
|
|
575
631
|
await store.setActive(r.channel.id);
|
|
576
632
|
cliAgentName = r.identity.name;
|
|
577
633
|
cliActiveChannelId = r.channel.id;
|
|
634
|
+
// 2026-08-09: 切 channel 必须重建 agent session — 否则身份/记忆停留在旧 channel (bug 修复)
|
|
635
|
+
invalidateAgent();
|
|
636
|
+
// 立即重建 (提前建好, 避免下次输入才卡顿; 失败不阻塞切换)
|
|
637
|
+
try {
|
|
638
|
+
await getAgent();
|
|
639
|
+
}
|
|
640
|
+
catch { /* 非致命, 下次输入时再试 */ }
|
|
578
641
|
inkSetStatus(getStatus()); // 触发状态栏立即重绘 (无需等 1s 定时器)
|
|
579
642
|
const extra = prev && prev.name !== r.identity.name ? ` (从 ${prev.name} 切换)` : '';
|
|
580
643
|
appendLine(`${C_ACCENT}→ 当前智能体: ${r.identity.name}${RESET}${extra}`);
|
|
@@ -599,38 +662,47 @@ async function processInput(input, comm) {
|
|
|
599
662
|
const { getIdentityStore } = await import('./agents/agent-identity-store.js');
|
|
600
663
|
const store = getIdentityStore();
|
|
601
664
|
await store.load();
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
let channels = [];
|
|
607
|
-
try {
|
|
608
|
-
const parsed = JSON.parse(await readFile(channelsPath, 'utf-8'));
|
|
609
|
-
channels = Array.isArray(parsed) ? parsed : parsed?.channels || [];
|
|
610
|
-
}
|
|
611
|
-
catch { /* 首次无文件 */ }
|
|
612
|
-
const dupName = channels.find((c) => c.name === name.trim());
|
|
665
|
+
// 2026-08-09: 复用 server-storage updateChannels 原子写 (互斥锁) — 旧实现裸 readFile→push→writeFile
|
|
666
|
+
// 与 Web server 并发写 channels.json 互相覆盖 → 创建的 agent 重启后丢失 (bug 修复)
|
|
667
|
+
const { updateChannels } = await import('./web/server-storage.js');
|
|
668
|
+
const dupName = store.rawChannels.find((c) => c.name === name.trim());
|
|
613
669
|
if (dupName) {
|
|
614
670
|
appendLine(`${C_ERROR}同名智能体已存在: '${dupName.name}' (id=${dupName.id})${RESET}`);
|
|
615
671
|
return;
|
|
616
672
|
}
|
|
617
673
|
const id = `ch_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
674
|
+
const agentId = `agent-${name.trim().toLowerCase().replace(/\s+/g, '-')}`;
|
|
618
675
|
const ch = {
|
|
619
676
|
id,
|
|
620
677
|
name: name.trim(),
|
|
621
|
-
agentId
|
|
678
|
+
agentId,
|
|
622
679
|
createdAt: new Date().toISOString(),
|
|
623
680
|
updatedAt: new Date().toISOString(),
|
|
624
681
|
currentSessionId: 'default',
|
|
625
682
|
};
|
|
626
683
|
if (personaHint)
|
|
627
684
|
ch.persona = { name: name.trim(), description: personaHint };
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
685
|
+
// 2026-08-09: 立即生成该 agent 的持久 DID 身份 (agent-keys/<agentId>.json) —
|
|
686
|
+
// 与 server fixOneChannelDID 对齐, 保证 CLI 新建的 agent 身份稳定且归属用户 DID
|
|
687
|
+
try {
|
|
688
|
+
const { loadOrCreateAgentIdentity } = await import('./agents/agent-identity.js');
|
|
689
|
+
const idt = loadOrCreateAgentIdentity(agentId);
|
|
690
|
+
ch.did = idt.did;
|
|
691
|
+
ch.publicKey = idt.publicKey;
|
|
692
|
+
}
|
|
693
|
+
catch { /* DID 生成失败不阻塞创建 */ }
|
|
694
|
+
const channels = await updateChannels((chs) => [...chs, ch]);
|
|
695
|
+
// 刷新 store 缓存 (updateChannels 走了 server-storage, store 内存还是旧的)
|
|
696
|
+
await store.load();
|
|
631
697
|
await store.setActive(id);
|
|
632
698
|
cliAgentName = name.trim();
|
|
633
699
|
cliActiveChannelId = id;
|
|
700
|
+
// 2026-08-09: 新建 agent 后立即重建 session — 否则新 agent 身份不加载 (bug 修复)
|
|
701
|
+
invalidateAgent();
|
|
702
|
+
try {
|
|
703
|
+
await getAgent();
|
|
704
|
+
}
|
|
705
|
+
catch { /* 非致命 */ }
|
|
634
706
|
inkSetStatus(getStatus());
|
|
635
707
|
appendLine(`${C_OK}✓ 已创建智能体 channel: ${name.trim()}${RESET} (${C_DIM}${id}${RESET})${personaHint ? `\n ${C_DIM}persona: ${personaHint}${RESET}` : ''}`);
|
|
636
708
|
}
|