@bolloon/bolloon-agent 0.3.27 → 0.3.28
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/decision-store.js +247 -0
- package/dist/agents/pi-sdk-tools.js +344 -0
- package/dist/bootstrap/context-os.js +213 -0
- package/dist/bootstrap/lifecycle-hooks.js +8 -2
- package/dist/bootstrap/memory-compressor.js +126 -1
- package/dist/bootstrap/persona-loader.js +88 -5
- package/dist/pi-ecosystem-mcp/index.js +153 -37
- package/dist/security/tool-gate.js +8 -0
- package/dist/web/server.js +56 -2
- package/package.json +1 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* decision-store.ts — 决策协议 (Context OS §7, 2026-08-03)
|
|
3
|
+
*
|
|
4
|
+
* Context OS 原则: 重要决策不允许只写结论, 至少要留下可回滚的推理链.
|
|
5
|
+
* 9 要素:
|
|
6
|
+
* 1. 问题到底是什么 → problem
|
|
7
|
+
* 2. 有哪些选项, 包括"不做" → options[].label (includeDoNothing)
|
|
8
|
+
* 3. 每个选项的时间/金钱/精力/机会成本 → options[].costs
|
|
9
|
+
* 4. 每个选项的短期/长期收益和战略价值 → options[].benefits
|
|
10
|
+
* 5. 风险、概率、影响、是否可恢复 → options[].risks
|
|
11
|
+
* 6. 当前的信息缺口 → infoGaps
|
|
12
|
+
* 7. 推荐方案 → recommendation
|
|
13
|
+
* 8. 为什么是现在 → timing
|
|
14
|
+
* 9. 失败时的触发条件和回滚动作 → rollback
|
|
15
|
+
*
|
|
16
|
+
* 防止三类错误: 把一时情绪当判断 / 因一条新信息推翻整个项目 / 把不能回滚的重投入当"勇敢".
|
|
17
|
+
* 原则: 新信息只修改它真正证伪的那个假设; 其他部分默认不动.
|
|
18
|
+
*
|
|
19
|
+
* 设计 (减法, 同 plan-store):
|
|
20
|
+
* - 不建数据库. 落盘 = ~/.bolloon/decisions/<decisionId>.json
|
|
21
|
+
* - 工具入口在 pi-sdk-tools.ts 注册, 复用现有 LLM 调度.
|
|
22
|
+
* - 任何 IO 失败静默返回 error 对象, 不阻塞主对话.
|
|
23
|
+
* - 决策确认 (decideDecision) 后自动 reflect 到 judgeness:
|
|
24
|
+
* HumanJudgment (storeHumanJudgment, source=trajectory) + JudgenessDescription
|
|
25
|
+
* (reflectAfterJudgment, openState=locked → Context OS 阶段0 临时价值点).
|
|
26
|
+
*/
|
|
27
|
+
import * as fs from 'fs/promises';
|
|
28
|
+
import * as os from 'os';
|
|
29
|
+
import * as path from 'path';
|
|
30
|
+
import * as crypto from 'crypto';
|
|
31
|
+
// ============================================================
|
|
32
|
+
// 落盘路径
|
|
33
|
+
// ============================================================
|
|
34
|
+
export function getDecisionsDir(home = os.homedir()) {
|
|
35
|
+
return path.join(home, '.bolloon', 'decisions');
|
|
36
|
+
}
|
|
37
|
+
export function getDecisionPath(decisionId, home = os.homedir()) {
|
|
38
|
+
return path.join(getDecisionsDir(home), `${decisionId.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
|
|
39
|
+
}
|
|
40
|
+
export function sanitizeDecisionId(id) {
|
|
41
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
42
|
+
}
|
|
43
|
+
export async function createDecision(input, home) {
|
|
44
|
+
const problem = String(input.problem || '').trim();
|
|
45
|
+
if (!problem)
|
|
46
|
+
return { ok: false, error: 'problem 必填 (问题到底是什么)' };
|
|
47
|
+
const options = Array.isArray(input.options)
|
|
48
|
+
? input.options
|
|
49
|
+
.map((o) => ({
|
|
50
|
+
label: String(o.label || '').trim(),
|
|
51
|
+
includeDoNothing: !!o.includeDoNothing,
|
|
52
|
+
costs: String(o.costs || ''),
|
|
53
|
+
benefits: String(o.benefits || ''),
|
|
54
|
+
risks: String(o.risks || ''),
|
|
55
|
+
}))
|
|
56
|
+
.filter((o) => o.label)
|
|
57
|
+
: [];
|
|
58
|
+
if (options.length === 0)
|
|
59
|
+
return { ok: false, error: 'options 至少 1 个选项 (含"不做")' };
|
|
60
|
+
const now = new Date().toISOString();
|
|
61
|
+
const decisionId = `dec_${Date.now()}_${crypto.randomBytes(3).toString('hex')}`;
|
|
62
|
+
const decision = {
|
|
63
|
+
decisionId,
|
|
64
|
+
problem,
|
|
65
|
+
options,
|
|
66
|
+
infoGaps: String(input.infoGaps || ''),
|
|
67
|
+
recommendation: String(input.recommendation || ''),
|
|
68
|
+
timing: String(input.timing || ''),
|
|
69
|
+
rollback: String(input.rollback || ''),
|
|
70
|
+
context: {
|
|
71
|
+
domain: String(input.domain || '通用'),
|
|
72
|
+
stakes: input.stakes || 'medium',
|
|
73
|
+
by: input.by || 'agent',
|
|
74
|
+
originChannel: String(input.originChannel || ''),
|
|
75
|
+
},
|
|
76
|
+
status: 'draft',
|
|
77
|
+
createdAt: now,
|
|
78
|
+
updatedAt: now,
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
const dir = getDecisionsDir(home);
|
|
82
|
+
await fs.mkdir(dir, { recursive: true });
|
|
83
|
+
await fs.writeFile(getDecisionPath(decisionId, home), JSON.stringify(decision, null, 2), 'utf-8');
|
|
84
|
+
return { ok: true, decision };
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
return { ok: false, error: `写入失败: ${e?.message || String(e)}` };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function loadDecision(decisionId, home) {
|
|
91
|
+
try {
|
|
92
|
+
const raw = await fs.readFile(getDecisionPath(decisionId, home), 'utf-8');
|
|
93
|
+
return JSON.parse(raw);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function saveDecision(decision, home) {
|
|
100
|
+
decision.updatedAt = new Date().toISOString();
|
|
101
|
+
await fs.writeFile(getDecisionPath(decision.decisionId, home), JSON.stringify(decision, null, 2), 'utf-8');
|
|
102
|
+
}
|
|
103
|
+
export async function updateDecisionStatus(decisionId, input, opts = {}) {
|
|
104
|
+
const decision = await loadDecision(decisionId, opts.home);
|
|
105
|
+
if (!decision)
|
|
106
|
+
return { ok: false, error: `decision '${decisionId}' 不存在` };
|
|
107
|
+
const now = new Date().toISOString();
|
|
108
|
+
if (input.decide) {
|
|
109
|
+
if (input.recommendation && input.recommendation.trim()) {
|
|
110
|
+
decision.recommendation = String(input.recommendation).trim();
|
|
111
|
+
}
|
|
112
|
+
if (!decision.recommendation)
|
|
113
|
+
return { ok: false, error: 'recommendation 必填 (推荐方案)' };
|
|
114
|
+
decision.status = 'decided';
|
|
115
|
+
decision.decidedAt = now;
|
|
116
|
+
// 决策确认 → 自动 reflect 到 judgeness (Context OS 阶段0 入账)
|
|
117
|
+
const refl = await reflectDecisionToJudgeness(decision, opts.byAgentId);
|
|
118
|
+
if (!refl.ok) {
|
|
119
|
+
return { ok: false, error: `决策确认失败 (judgeness 入库): ${refl.error}` };
|
|
120
|
+
}
|
|
121
|
+
decision.reflection = refl.reflection;
|
|
122
|
+
}
|
|
123
|
+
else if (input.implemented) {
|
|
124
|
+
if (decision.status !== 'decided' && decision.status !== 'implemented') {
|
|
125
|
+
return { ok: false, error: '只能把 decided 的决策标记为 implemented' };
|
|
126
|
+
}
|
|
127
|
+
decision.status = 'implemented';
|
|
128
|
+
}
|
|
129
|
+
else if (input.rollback || input.abandon) {
|
|
130
|
+
decision.status = input.rollback ? 'rolled-back' : 'abandoned';
|
|
131
|
+
// 回滚/放弃 → 教训入库 (reject 语义, 防重复踩坑)
|
|
132
|
+
const reason = String(input.reason || '');
|
|
133
|
+
if (reason && input.rollback) {
|
|
134
|
+
await reflectRollbackLesson(decision, reason, opts.byAgentId).catch(() => { });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
return { ok: false, error: '必须指定 decide / implemented / rollback / abandon 之一' };
|
|
139
|
+
}
|
|
140
|
+
await saveDecision(decision, opts.home);
|
|
141
|
+
return { ok: true, decision };
|
|
142
|
+
}
|
|
143
|
+
export async function listDecisions(status, home) {
|
|
144
|
+
try {
|
|
145
|
+
const dir = getDecisionsDir(home);
|
|
146
|
+
const files = await fs.readdir(dir);
|
|
147
|
+
const out = [];
|
|
148
|
+
for (const f of files) {
|
|
149
|
+
if (!f.endsWith('.json'))
|
|
150
|
+
continue;
|
|
151
|
+
try {
|
|
152
|
+
const raw = await fs.readFile(path.join(dir, f), 'utf-8');
|
|
153
|
+
const d = JSON.parse(raw);
|
|
154
|
+
if (status && d.status !== status)
|
|
155
|
+
continue;
|
|
156
|
+
out.push(d);
|
|
157
|
+
}
|
|
158
|
+
catch { /* 单文件损坏跳过 */ }
|
|
159
|
+
}
|
|
160
|
+
out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** 决策摘要 → 上下文注入 (9 要素完整可追溯, 上下文只给精简版) */
|
|
168
|
+
export function decisionToContext(d) {
|
|
169
|
+
const lines = [`📌 ${d.problem} [${d.status}] (id=${d.decisionId})`];
|
|
170
|
+
for (const o of d.options) {
|
|
171
|
+
const tag = o.includeDoNothing ? ' [不做]' : '';
|
|
172
|
+
lines.push(` - 选项${tag}: ${o.label}`);
|
|
173
|
+
}
|
|
174
|
+
if (d.recommendation)
|
|
175
|
+
lines.push(` 推荐: ${d.recommendation}`);
|
|
176
|
+
if (d.infoGaps)
|
|
177
|
+
lines.push(` 信息缺口: ${d.infoGaps}`);
|
|
178
|
+
if (d.rollback)
|
|
179
|
+
lines.push(` 回滚条件: ${d.rollback}`);
|
|
180
|
+
if (d.reflection)
|
|
181
|
+
lines.push(` ✅ 已入库 judgeness: hv=${d.reflection.hvId}${d.reflection.jdId ? ` jd=${d.reflection.jdId}` : ''}`);
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
}
|
|
184
|
+
/** 决策确认后入库: HumanJudgment (source=trajectory) + reflectAfterJudgment (openState=locked) */
|
|
185
|
+
export async function reflectDecisionToJudgeness(decision, byAgentId) {
|
|
186
|
+
try {
|
|
187
|
+
const { storeHumanJudgment } = await import('../pi-ecosystem-judgment/human-value-store.js');
|
|
188
|
+
const { reflectAfterJudgment } = await import('../judgeness/reflect.js');
|
|
189
|
+
const decisionText = `${decision.recommendation} (问题: ${decision.problem})`;
|
|
190
|
+
const judgment = await storeHumanJudgment({
|
|
191
|
+
decision: decisionText.slice(0, 300),
|
|
192
|
+
decision_type: 'approve',
|
|
193
|
+
reasons: [
|
|
194
|
+
`选项: ${decision.options.map((o) => o.label).join(' / ')}`,
|
|
195
|
+
`风险: ${decision.options.map((o) => o.risks || '无').join('; ')}`,
|
|
196
|
+
`回滚: ${decision.rollback || '无'}`,
|
|
197
|
+
].filter(Boolean),
|
|
198
|
+
values_derived: [],
|
|
199
|
+
context: {
|
|
200
|
+
domain: decision.context.domain,
|
|
201
|
+
complexity: decision.context.stakes === 'critical' ? 'profound' : decision.context.stakes === 'high' ? 'complex' : decision.context.stakes === 'medium' ? 'moderate' : 'simple',
|
|
202
|
+
stakes: decision.context.stakes,
|
|
203
|
+
time_pressure: 'medium',
|
|
204
|
+
},
|
|
205
|
+
outcome: { approved: true },
|
|
206
|
+
metadata: { source: 'trajectory', confidence: 0.8, revisable: true },
|
|
207
|
+
status: 'active',
|
|
208
|
+
appliesTo: [],
|
|
209
|
+
});
|
|
210
|
+
const jd = await reflectAfterJudgment(judgment, 'agent', byAgentId).catch(() => null);
|
|
211
|
+
return {
|
|
212
|
+
ok: true,
|
|
213
|
+
reflection: { hvId: judgment.id, jdId: jd?.descriptionId, at: new Date().toISOString() },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
return { ok: false, error: e?.message || String(e) };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** 回滚教训入库: HumanJudgment (reject 语义) — Context OS "失败时的触发条件和回滚动作" */
|
|
221
|
+
async function reflectRollbackLesson(decision, reason, byAgentId) {
|
|
222
|
+
try {
|
|
223
|
+
const { storeHumanJudgment } = await import('../pi-ecosystem-judgment/human-value-store.js');
|
|
224
|
+
const { reflectAfterJudgment } = await import('../judgeness/reflect.js');
|
|
225
|
+
const judgment = await storeHumanJudgment({
|
|
226
|
+
decision: `回滚决策: ${decision.problem} — 失败原因: ${reason.slice(0, 200)}`,
|
|
227
|
+
decision_type: 'reject',
|
|
228
|
+
reasons: [decision.rollback || '', reason].filter(Boolean),
|
|
229
|
+
values_derived: [],
|
|
230
|
+
context: {
|
|
231
|
+
domain: decision.context.domain,
|
|
232
|
+
complexity: 'moderate',
|
|
233
|
+
stakes: decision.context.stakes,
|
|
234
|
+
time_pressure: 'medium',
|
|
235
|
+
},
|
|
236
|
+
outcome: { approved: false, feedback: reason.slice(0, 200) },
|
|
237
|
+
metadata: { source: 'trajectory', confidence: 0.7, revisable: false },
|
|
238
|
+
status: 'active',
|
|
239
|
+
appliesTo: [],
|
|
240
|
+
});
|
|
241
|
+
const jd = await reflectAfterJudgment(judgment, 'agent', byAgentId).catch(() => null);
|
|
242
|
+
return { ok: true, reflection: { hvId: judgment.id, jdId: jd?.descriptionId, at: new Date().toISOString() } };
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
return { ok: false, error: e?.message || String(e) };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -1508,6 +1508,350 @@ export function registerBuiltinTools(ctx) {
|
|
|
1508
1508
|
}
|
|
1509
1509
|
}
|
|
1510
1510
|
});
|
|
1511
|
+
// ============================================================
|
|
1512
|
+
// 决策协议工具 (2026-08-03, Context OS §7) — 可回滚的推理链
|
|
1513
|
+
// create_decision / decide_decision / rollback_decision / list_decisions
|
|
1514
|
+
// 实现: decision-store.ts (~/.bolloon/decisions/<id>.json)
|
|
1515
|
+
// 9 要素: 问题/选项(含不做)/成本/收益/风险/信息缺口/推荐/时机/回滚
|
|
1516
|
+
// 决策确认 (decide_decision) 自动 reflect 到 judgeness (HumanJudgment + JudgenessDescription)
|
|
1517
|
+
// ============================================================
|
|
1518
|
+
ctx.tools.set('create_decision', {
|
|
1519
|
+
name: 'create_decision',
|
|
1520
|
+
description: '重大决策前先写推理链 (Context OS 9 要素): problem 问题是什么, options 选项数组 (含"不做"), info_gaps 信息缺口, recommendation 推荐方案, timing 为什么是现在, rollback 失败时回滚条件. 之后用 decide_decision 确认.',
|
|
1521
|
+
parameters: {
|
|
1522
|
+
problem: '问题到底是什么 (必填)',
|
|
1523
|
+
options: '选项数组 JSON (必填, e.g. [{"label":"方案A","costs":"成本","benefits":"收益","risks":"风险"},{"label":"什么都不做","includeDoNothing":true}])',
|
|
1524
|
+
info_gaps: '当前信息缺口 (可选)',
|
|
1525
|
+
recommendation: '推荐方案 (可选, 确认时必填)',
|
|
1526
|
+
timing: '为什么是现在 (可选)',
|
|
1527
|
+
rollback: '失败时的回滚条件 (可选)',
|
|
1528
|
+
stakes: '风险等级: low / medium / high / critical (可选)',
|
|
1529
|
+
domain: '领域 (可选)',
|
|
1530
|
+
},
|
|
1531
|
+
execute: async (args) => {
|
|
1532
|
+
try {
|
|
1533
|
+
const { createDecision, decisionToContext } = await import('./decision-store.js');
|
|
1534
|
+
const problem = String(args.problem || '').trim();
|
|
1535
|
+
if (!problem)
|
|
1536
|
+
return { success: false, error: 'problem 必填' };
|
|
1537
|
+
let options = [];
|
|
1538
|
+
try {
|
|
1539
|
+
const s = JSON.parse(String(args.options || '[]'));
|
|
1540
|
+
if (Array.isArray(s))
|
|
1541
|
+
options = s;
|
|
1542
|
+
}
|
|
1543
|
+
catch { /* options 解析失败 */ }
|
|
1544
|
+
const rawStakes = String(args.stakes || 'medium');
|
|
1545
|
+
const stakes = rawStakes === 'low' || rawStakes === 'high' || rawStakes === 'critical' ? rawStakes : 'medium';
|
|
1546
|
+
const r = await createDecision({
|
|
1547
|
+
problem,
|
|
1548
|
+
options,
|
|
1549
|
+
infoGaps: args.info_gaps ? String(args.info_gaps) : undefined,
|
|
1550
|
+
recommendation: args.recommendation ? String(args.recommendation) : undefined,
|
|
1551
|
+
timing: args.timing ? String(args.timing) : undefined,
|
|
1552
|
+
rollback: args.rollback ? String(args.rollback) : undefined,
|
|
1553
|
+
stakes,
|
|
1554
|
+
domain: args.domain ? String(args.domain) : undefined,
|
|
1555
|
+
by: 'agent',
|
|
1556
|
+
originChannel: ctx.channelId || '',
|
|
1557
|
+
});
|
|
1558
|
+
if (!r.ok || !r.decision)
|
|
1559
|
+
return { success: false, error: r.error };
|
|
1560
|
+
return { success: true, output: `✅ 决策推理链已创建 ${r.decision.decisionId}\n\n${decisionToContext(r.decision)}` };
|
|
1561
|
+
}
|
|
1562
|
+
catch (e) {
|
|
1563
|
+
return { success: false, error: `create_decision 失败: ${String(e).slice(0, 200)}` };
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
ctx.tools.set('decide_decision', {
|
|
1568
|
+
name: 'decide_decision',
|
|
1569
|
+
description: '确认一个决策 (必须已有 recommendation). 确认后自动把该决策入库 judgeness (HumanJudgment + 5 维描述, 阶段0 临时价值点). 决策确认后状态 → decided.',
|
|
1570
|
+
parameters: {
|
|
1571
|
+
decision_id: '决策 ID (必填, create_decision 返回)',
|
|
1572
|
+
recommendation: '最终推荐方案 (必填, 若创建时未填)',
|
|
1573
|
+
},
|
|
1574
|
+
execute: async (args) => {
|
|
1575
|
+
try {
|
|
1576
|
+
const { updateDecisionStatus } = await import('./decision-store.js');
|
|
1577
|
+
const decisionId = String(args.decision_id || '').trim();
|
|
1578
|
+
if (!decisionId)
|
|
1579
|
+
return { success: false, error: 'decision_id 必填' };
|
|
1580
|
+
const r = await updateDecisionStatus(decisionId, { decide: true, recommendation: args.recommendation ? String(args.recommendation) : undefined }, { byAgentId: ctx.agentId || '' });
|
|
1581
|
+
if (!r.ok || !r.decision)
|
|
1582
|
+
return { success: false, error: r.error };
|
|
1583
|
+
const refl = r.decision.reflection ? ` (已入库 judgeness: hv=${r.decision.reflection.hvId})` : '';
|
|
1584
|
+
return { success: true, output: `✅ 决策已确认: ${r.decision.problem} → ${r.decision.recommendation}${refl}` };
|
|
1585
|
+
}
|
|
1586
|
+
catch (e) {
|
|
1587
|
+
return { success: false, error: `decide_decision 失败: ${String(e).slice(0, 200)}` };
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
ctx.tools.set('rollback_decision', {
|
|
1592
|
+
name: 'rollback_decision',
|
|
1593
|
+
description: '决策失败触发回滚条件时调用: 标记 rolled-back + 记录教训 (reject 语义入库 judgeness, 防止重复踩坑).',
|
|
1594
|
+
parameters: {
|
|
1595
|
+
decision_id: '决策 ID (必填)',
|
|
1596
|
+
reason: '失败/回滚原因 (必填, 将作为教训入库)',
|
|
1597
|
+
},
|
|
1598
|
+
execute: async (args) => {
|
|
1599
|
+
try {
|
|
1600
|
+
const { updateDecisionStatus } = await import('./decision-store.js');
|
|
1601
|
+
const decisionId = String(args.decision_id || '').trim();
|
|
1602
|
+
if (!decisionId)
|
|
1603
|
+
return { success: false, error: 'decision_id 必填' };
|
|
1604
|
+
const reason = String(args.reason || '').trim();
|
|
1605
|
+
if (!reason)
|
|
1606
|
+
return { success: false, error: 'reason 必填 (回滚原因)' };
|
|
1607
|
+
const r = await updateDecisionStatus(decisionId, { rollback: true, reason }, { byAgentId: ctx.agentId || '' });
|
|
1608
|
+
if (!r.ok || !r.decision)
|
|
1609
|
+
return { success: false, error: r.error };
|
|
1610
|
+
return { success: true, output: `↩️ 决策已回滚: ${r.decision.problem}\n教训已入库 judgeness (reject 语义): ${reason.slice(0, 120)}` };
|
|
1611
|
+
}
|
|
1612
|
+
catch (e) {
|
|
1613
|
+
return { success: false, error: `rollback_decision 失败: ${String(e).slice(0, 200)}` };
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
ctx.tools.set('list_decisions', {
|
|
1618
|
+
name: 'list_decisions',
|
|
1619
|
+
description: '列出全部决策 (按创建时间倒序). 可选 status 过滤: draft / decided / implemented / abandoned / rolled-back. 用于恢复决策上下文.',
|
|
1620
|
+
parameters: {
|
|
1621
|
+
status: '可选过滤: draft / decided / implemented / abandoned / rolled-back',
|
|
1622
|
+
},
|
|
1623
|
+
execute: async (args) => {
|
|
1624
|
+
try {
|
|
1625
|
+
const { listDecisions, decisionToContext } = await import('./decision-store.js');
|
|
1626
|
+
const status = ['draft', 'decided', 'implemented', 'abandoned', 'rolled-back'].includes(args.status) ? args.status : undefined;
|
|
1627
|
+
const decisions = await listDecisions(status);
|
|
1628
|
+
if (decisions.length === 0)
|
|
1629
|
+
return { success: true, output: '暂无决策记录.' };
|
|
1630
|
+
const text = decisions.slice(0, 8).map(d => decisionToContext(d)).join('\n\n');
|
|
1631
|
+
return { success: true, output: `🧭 ${decisions.length} 条决策 (9 要素推理链可追溯):\n\n${text}` };
|
|
1632
|
+
}
|
|
1633
|
+
catch (e) {
|
|
1634
|
+
return { success: false, error: `list_decisions 失败: ${String(e).slice(0, 200)}` };
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
// ============================================================
|
|
1639
|
+
// Context OS 资产层工具 (2026-08-03, P5) — 12+3 层文件夹体系
|
|
1640
|
+
// list_context_layers / write_context_asset / read_context_assets
|
|
1641
|
+
// 实现: src/bootstrap/context-os.ts (~/.bolloon/context-os/)
|
|
1642
|
+
// 价值判断: 写入前回答"未来哪个具体场景会用到它?" — 回答不出进 tmp/
|
|
1643
|
+
// ============================================================
|
|
1644
|
+
ctx.tools.set('list_context_layers', {
|
|
1645
|
+
name: 'list_context_layers',
|
|
1646
|
+
description: '列出 Context OS 资产层 (12+3 层: 01-Me 我是谁 / 02-Network 我认识谁 / 03-Current 我在做什么 / 04-Projects 项目 / 05-Prompts 提示词 / 06-Protocols 协议 / 07-Knowledge 知识 / 08-Insights 洞察 / 09-Tools 工具 / 10-Skills 技能 / 11-Write 写作 / 12-Analysis 决策复盘 / output / research / tmp) + 每层资产数. 任务前先看目录, 再按任务路由读取对应层.',
|
|
1647
|
+
parameters: {},
|
|
1648
|
+
execute: async () => {
|
|
1649
|
+
try {
|
|
1650
|
+
const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
|
|
1651
|
+
const listings = await readContextAssets();
|
|
1652
|
+
const total = listings.reduce((s, l) => s + l.fileCount, 0);
|
|
1653
|
+
if (total === 0)
|
|
1654
|
+
return { success: true, output: '📂 Context OS 资产层已就绪 (12+3 层), 当前暂无资产. 有价值的内容用 write_context_asset 写入对应层.' };
|
|
1655
|
+
return { success: true, output: `📂 Context OS 资产层共 ${total} 篇资产:\n\n${formatLayerListing(listings)}` };
|
|
1656
|
+
}
|
|
1657
|
+
catch (e) {
|
|
1658
|
+
return { success: false, error: `list_context_layers 失败: ${String(e).slice(0, 200)}` };
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
ctx.tools.set('write_context_asset', {
|
|
1663
|
+
name: 'write_context_asset',
|
|
1664
|
+
description: '把已验证的价值写入 Context OS 资产层 (唯一落点, 不制造重复文件). 写入前先自检: 未来哪个具体场景会用到它? 回答不出 → 写 tmp/ 或放弃. layer 可选: 01-Me 原则边界 / 02-Network 人脉 / 03-Current 当前状态 / 04-Projects 项目 / 05-Prompts 已验证提示词 / 06-Protocols 规则 / 07-Knowledge 跨项目知识 / 08-Insights 已验证洞察/教训 / 09-Tools 工具经验 / 10-Skills 可验证能力 / 11-Write 成熟表达 / 12-Analysis 决策复盘 / output 对外交付 / research 中间成果 / tmp 一次性草稿.',
|
|
1665
|
+
parameters: {
|
|
1666
|
+
layer: '层 key (必填, 见 description 列表)',
|
|
1667
|
+
title: '资产标题 (必填, 一句话)',
|
|
1668
|
+
content: '资产正文 markdown (必填)',
|
|
1669
|
+
tags: '可选 tags 数组 JSON',
|
|
1670
|
+
domain: '可选领域',
|
|
1671
|
+
},
|
|
1672
|
+
execute: async (args) => {
|
|
1673
|
+
try {
|
|
1674
|
+
const { writeContextAsset } = await import('../bootstrap/context-os.js');
|
|
1675
|
+
const layer = String(args.layer || '').trim();
|
|
1676
|
+
const title = String(args.title || '').trim();
|
|
1677
|
+
const content = String(args.content || '').trim();
|
|
1678
|
+
if (!layer)
|
|
1679
|
+
return { success: false, error: 'layer 必填 (如 07-Knowledge)' };
|
|
1680
|
+
if (!title)
|
|
1681
|
+
return { success: false, error: 'title 必填' };
|
|
1682
|
+
if (!content)
|
|
1683
|
+
return { success: false, error: 'content 必填' };
|
|
1684
|
+
let tags = [];
|
|
1685
|
+
try {
|
|
1686
|
+
const t = JSON.parse(String(args.tags || '[]'));
|
|
1687
|
+
if (Array.isArray(t))
|
|
1688
|
+
tags = t.map(String);
|
|
1689
|
+
}
|
|
1690
|
+
catch { /* tags 解析失败 */ }
|
|
1691
|
+
const r = await writeContextAsset({ layer, title, content, tags, domain: args.domain ? String(args.domain) : undefined });
|
|
1692
|
+
if (!r.ok)
|
|
1693
|
+
return { success: false, error: r.error };
|
|
1694
|
+
if (r.skipped)
|
|
1695
|
+
return { success: true, output: `⏭️ ${r.error}` };
|
|
1696
|
+
return { success: true, output: `📥 已写入资产层 ${r.asset.layer}: ${r.asset.title} (stage0 临时价值点, 待验证后固化)\n路径: ${r.asset.path}` };
|
|
1697
|
+
}
|
|
1698
|
+
catch (e) {
|
|
1699
|
+
return { success: false, error: `write_context_asset 失败: ${String(e).slice(0, 200)}` };
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
ctx.tools.set('read_context_assets', {
|
|
1704
|
+
name: 'read_context_assets',
|
|
1705
|
+
description: '读取 Context OS 资产层内容. layer 可选 (空 = 全层汇总); keyword 可选 (标题/内容过滤). 做项目前先读 04-Projects 对应项目, 重大决策前读 08-Insights + 12-Analysis, 学技术读 07-Knowledge + 09-Tools.',
|
|
1706
|
+
parameters: {
|
|
1707
|
+
layer: '可选层 key (如 07-Knowledge), 空 = 全部',
|
|
1708
|
+
keyword: '可选关键词过滤',
|
|
1709
|
+
},
|
|
1710
|
+
execute: async (args) => {
|
|
1711
|
+
try {
|
|
1712
|
+
const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
|
|
1713
|
+
const layer = args.layer ? String(args.layer) : undefined;
|
|
1714
|
+
const kw = args.keyword ? String(args.keyword) : undefined;
|
|
1715
|
+
const listings = await readContextAssets(layer, kw);
|
|
1716
|
+
if (listings.every((l) => l.fileCount === 0)) {
|
|
1717
|
+
return { success: true, output: layer ? `📂 资产层 ${layer} 暂无资产` : '📂 资产层暂无资产' };
|
|
1718
|
+
}
|
|
1719
|
+
// 单层且有 keyword → 输出完整正文
|
|
1720
|
+
if (layer && kw) {
|
|
1721
|
+
const found = listings[0]?.files || [];
|
|
1722
|
+
const { readAssetBody } = await import('../bootstrap/context-os.js');
|
|
1723
|
+
const bodies = [];
|
|
1724
|
+
for (const f of found.slice(0, 5)) {
|
|
1725
|
+
try {
|
|
1726
|
+
const r = await readAssetBody(layer, f.file);
|
|
1727
|
+
if (r.ok && r.body)
|
|
1728
|
+
bodies.push(`--- ${f.title} ---\n${r.body.slice(0, 2000)}\n--- 结束 ---`);
|
|
1729
|
+
}
|
|
1730
|
+
catch { /* 跳过 */ }
|
|
1731
|
+
}
|
|
1732
|
+
return { success: true, output: bodies.length > 0 ? bodies.join('\n\n') : '未找到匹配资产' };
|
|
1733
|
+
}
|
|
1734
|
+
return { success: true, output: formatLayerListing(listings) };
|
|
1735
|
+
}
|
|
1736
|
+
catch (e) {
|
|
1737
|
+
return { success: false, error: `read_context_assets 失败: ${String(e).slice(0, 200)}` };
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
// ============================================================
|
|
1742
|
+
// MCP 工具 (2026-08-03) — 外部 MCP server 接入 agent 工具系统
|
|
1743
|
+
// 配置: ~/.mcp.json (mcpServers), 启动时 initializeMcpAdapter 自动握手发现工具
|
|
1744
|
+
// mcp_list_tools: 列出已发现的 MCP 工具
|
|
1745
|
+
// mcp_tool: 调用任意 MCP 工具 (真实 stdio JSON-RPC)
|
|
1746
|
+
// ============================================================
|
|
1747
|
+
ctx.tools.set('mcp_list_tools', {
|
|
1748
|
+
name: 'mcp_list_tools',
|
|
1749
|
+
description: '列出通过 MCP 协议连接的可用外部工具 (来自 ~/.mcp.json 配置的 MCP servers). 调用 MCP 工具前先列一次, 拿准确工具名和参数.',
|
|
1750
|
+
parameters: {},
|
|
1751
|
+
execute: async () => {
|
|
1752
|
+
try {
|
|
1753
|
+
const mcp = await import('../pi-ecosystem-mcp/index.js');
|
|
1754
|
+
await mcp.initializeMcpAdapter().catch(() => { });
|
|
1755
|
+
const tools = mcp.listTools();
|
|
1756
|
+
if (tools.length === 0) {
|
|
1757
|
+
return { success: true, output: '未发现 MCP 工具. 配置 ~/.mcp.json (mcpServers: {name: {command, args}}), 重启后自动连接.' };
|
|
1758
|
+
}
|
|
1759
|
+
const lines = tools.map((t) => ` - ${t.name} (${t.serverName}): ${t.description?.slice(0, 80) || '无描述'}`);
|
|
1760
|
+
return { success: true, output: `🔌 ${tools.length} 个 MCP 工具可用:\n${lines.join('\n')}\n\n调用用 mcp_tool (tool=工具名, arguments=参数 JSON)` };
|
|
1761
|
+
}
|
|
1762
|
+
catch (e) {
|
|
1763
|
+
return { success: false, error: `mcp_list_tools 失败: ${String(e).slice(0, 200)}` };
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
ctx.tools.set('mcp_tool', {
|
|
1768
|
+
name: 'mcp_tool',
|
|
1769
|
+
description: '调用外部 MCP 工具 (真实 stdio JSON-RPC 通信). tool = 工具名 (先用 mcp_list_tools 查看), arguments = 参数 JSON 对象.',
|
|
1770
|
+
parameters: {
|
|
1771
|
+
tool: 'MCP 工具名 (必填)',
|
|
1772
|
+
arguments: '参数 JSON 对象 (必填, e.g. {"text":"hello"})',
|
|
1773
|
+
},
|
|
1774
|
+
execute: async (args) => {
|
|
1775
|
+
try {
|
|
1776
|
+
const mcp = await import('../pi-ecosystem-mcp/index.js');
|
|
1777
|
+
const tool = String(args.tool || '').trim();
|
|
1778
|
+
if (!tool)
|
|
1779
|
+
return { success: false, error: 'tool 必填' };
|
|
1780
|
+
let argumentsObj = {};
|
|
1781
|
+
try {
|
|
1782
|
+
const a = JSON.parse(String(args.arguments || '{}'));
|
|
1783
|
+
if (a && typeof a === 'object')
|
|
1784
|
+
argumentsObj = a;
|
|
1785
|
+
}
|
|
1786
|
+
catch {
|
|
1787
|
+
return { success: false, error: 'arguments 必须是 JSON 对象' };
|
|
1788
|
+
}
|
|
1789
|
+
const r = await mcp.executeTool(tool, argumentsObj);
|
|
1790
|
+
if (!r.success)
|
|
1791
|
+
return { success: false, error: r.error || 'MCP 调用失败' };
|
|
1792
|
+
const text = Array.isArray(r.content)
|
|
1793
|
+
? r.content.map((c) => c?.text ?? '').filter(Boolean).join('\n')
|
|
1794
|
+
: JSON.stringify(r.content);
|
|
1795
|
+
return { success: true, output: `🔧 MCP ${tool}:\n${text.slice(0, 4000)}` };
|
|
1796
|
+
}
|
|
1797
|
+
catch (e) {
|
|
1798
|
+
return { success: false, error: `mcp_tool 失败: ${String(e).slice(0, 200)}` };
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
// ============================================================
|
|
1803
|
+
// publish_did (2026-08-03) — 把当前 agent 的 DID 发布到 IPFS + IPNS
|
|
1804
|
+
// 全自动: 自动安装/启动本地 Kubo → 上传 DID 文档 → 发布 IPNS name
|
|
1805
|
+
// 实现: @diap/sdk (AgentAuthManager + publishAfterUpload)
|
|
1806
|
+
// ============================================================
|
|
1807
|
+
ctx.tools.set('publish_did', {
|
|
1808
|
+
name: 'publish_did',
|
|
1809
|
+
description: '把当前 agent 的 DID 身份发布到本地 IPFS + IPNS (自动安装启动 Kubo). 返回 DID + CID (IPFS 内容地址) + IPNS name (稳定可解析标识). 跨节点发现和身份解析依赖它.',
|
|
1810
|
+
parameters: {
|
|
1811
|
+
name: '可选: 发布显示名 (默认 agentId)',
|
|
1812
|
+
},
|
|
1813
|
+
execute: async (args) => {
|
|
1814
|
+
try {
|
|
1815
|
+
const agentId = String(ctx.agentId || '').trim();
|
|
1816
|
+
const { loadOrCreateAgentIdentity } = await import('./agent-identity.js');
|
|
1817
|
+
const identity = loadOrCreateAgentIdentity(agentId || 'default-agent');
|
|
1818
|
+
const { KeyManager } = await import('@diap/sdk');
|
|
1819
|
+
const kp = KeyManager.fromPrivateKey(Buffer.from(identity.privateKey, 'hex'));
|
|
1820
|
+
const displayName = args.name ? String(args.name) : agentId || 'bolloon-agent';
|
|
1821
|
+
// 1. 确保本地 Kubo (自动安装 + 启动)
|
|
1822
|
+
const sdk = await import('@diap/sdk');
|
|
1823
|
+
const checkKuboSetup = sdk.checkKuboSetup;
|
|
1824
|
+
if (typeof checkKuboSetup === 'function') {
|
|
1825
|
+
const setup = await checkKuboSetup(true, true);
|
|
1826
|
+
if (!setup?.ready || !setup?.daemonRunning) {
|
|
1827
|
+
return { success: false, error: '本地 Kubo 不可用 (自动安装失败), 无法发布到 IPFS' };
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
// 2. 注册 agent → 上传 DID 文档 → CID
|
|
1831
|
+
const { AgentAuthManager } = await import('@diap/sdk');
|
|
1832
|
+
const auth = await AgentAuthManager.newWithRemoteIpfs('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
|
|
1833
|
+
const result = await auth.registerAgent({ name: displayName, services: [] }, kp, '');
|
|
1834
|
+
const cid = result.cid || result.didDocCid;
|
|
1835
|
+
if (!cid)
|
|
1836
|
+
return { success: false, error: 'DID 上传成功但未拿到 CID' };
|
|
1837
|
+
// 3. 发布 IPNS name (稳定标识)
|
|
1838
|
+
let ipnsName = '';
|
|
1839
|
+
try {
|
|
1840
|
+
const ipfs = await sdk.IpfsClient.newWithRemoteNode('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
|
|
1841
|
+
const pub = await ipfs.publishAfterUpload?.(cid, kp);
|
|
1842
|
+
ipnsName = pub?.name || pub?.ipnsName || '';
|
|
1843
|
+
}
|
|
1844
|
+
catch { /* IPNS 失败不致命, CID 仍可用 */ }
|
|
1845
|
+
return {
|
|
1846
|
+
success: true,
|
|
1847
|
+
output: `✅ DID 已发布到 IPFS:\n DID: ${identity.did}\n CID: ${cid}\n IPNS: ${ipnsName || '(发布失败, CID 仍可用)'}\n 读回验证: curl -X POST "http://127.0.0.1:5001/api/v0/cat?arg=${cid}"`,
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
catch (e) {
|
|
1851
|
+
return { success: false, error: `publish_did 失败: ${String(e).slice(0, 200)}` };
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
});
|
|
1511
1855
|
}
|
|
1512
1856
|
/**
|
|
1513
1857
|
* 注册 Wallet + Polymarket + Safe 工具 (基于 constraint-runtime/src/tools/).
|