@bolloon/bolloon-agent 0.6.4 → 0.6.6
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/chain/chain-settlement.js +0 -1
- package/dist/agents/chat-segmenter.js +4 -16
- package/dist/agents/contacts/human-directory.js +155 -0
- package/dist/agents/contacts/tools.js +79 -0
- package/dist/agents/experience-review.js +0 -3
- package/dist/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/agents/goal-resume.js +0 -4
- package/dist/agents/intent-classifier.js +1 -1
- package/dist/agents/iteration-budget.js +12 -0
- package/dist/agents/knowledge-organizer.js +0 -2
- package/dist/agents/lesson-to-skill.js +0 -1
- package/dist/agents/loop-review.js +2 -2
- package/dist/agents/memory-store.js +359 -0
- package/dist/agents/opportunity-inbox.js +147 -0
- package/dist/agents/opportunity-match.js +48 -7
- package/dist/agents/opportunity-web-search.js +222 -0
- package/dist/agents/p2p-chat-tools.js +1 -2
- package/dist/agents/parse-tool-call.js +9 -22
- package/dist/agents/pi-harness.js +1 -2
- package/dist/agents/pi-sdk-tools.js +180 -5
- package/dist/agents/pi-sdk.js +143 -9
- package/dist/agents/process-runner.js +28 -9
- package/dist/agents/react-loop.js +9 -17
- package/dist/agents/runner-resolver.js +4 -3
- package/dist/agents/skill-loader.js +22 -15
- package/dist/agents/skills-manager.js +1 -25
- package/dist/agents/workflow-pivot-loop.js +152 -61
- package/dist/agents/world-watcher.js +30 -1
- package/dist/bootstrap/context-collector.js +2 -4
- package/dist/bootstrap/context-hierarchy.js +13 -1
- package/dist/cli/ink-app.js +44 -17
- package/dist/cli/intent-command.js +22 -12
- package/dist/cli/loading-tui.js +8 -2
- package/dist/cli/log-gate.js +40 -8
- package/dist/cli/mention-data.js +1 -0
- package/dist/cli/reply-hygiene.js +22 -0
- package/dist/cli/trace-line.js +61 -2
- package/dist/electron-build/agents/agent-gateway.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
- package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/contacts/human-directory.js +204 -0
- package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
- package/dist/electron-build/agents/contacts/policy.js.map +1 -1
- package/dist/electron-build/agents/contacts/tools.js +112 -0
- package/dist/electron-build/agents/contacts/tools.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +0 -3
- package/dist/electron-build/agents/experience-review.js.map +1 -1
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
- package/dist/electron-build/agents/goal-resume.js +0 -4
- package/dist/electron-build/agents/goal-resume.js.map +1 -1
- package/dist/electron-build/agents/intent-classifier.js +1 -1
- package/dist/electron-build/agents/lesson-to-skill.js +0 -1
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
- package/dist/electron-build/agents/loop-review.js +2 -2
- package/dist/electron-build/agents/loop-review.js.map +1 -1
- package/dist/electron-build/agents/memory-recall.js.map +1 -1
- package/dist/electron-build/agents/memory-store.js +404 -0
- package/dist/electron-build/agents/memory-store.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +9 -22
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-harness.js +1 -2
- package/dist/electron-build/agents/pi-harness.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +143 -9
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +28 -9
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/runner-resolver.js +4 -3
- package/dist/electron-build/agents/runner-resolver.js.map +1 -1
- package/dist/electron-build/agents/skill-loader.js +22 -16
- package/dist/electron-build/agents/skill-loader.js.map +1 -1
- package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
- package/dist/electron-build/agents/skills-manager.js +0 -24
- package/dist/electron-build/agents/skills-manager.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-engine.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
- package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
- package/dist/electron-build/bootstrap/context-collector.js +2 -4
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
- package/dist/electron-build/electron-preload.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/kernel/channel-actor.js +31 -2
- package/dist/electron-build/kernel/channel-actor.js.map +1 -1
- package/dist/electron-build/kernel/model-runtime.js +8 -1
- package/dist/electron-build/kernel/model-runtime.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +20 -0
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/embedding-adapter.js +75 -0
- package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +96 -35
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/network/agent-network.js.map +1 -1
- package/dist/electron-build/orbitdb/memory-sync.js +76 -0
- package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
- package/dist/electron-build/security/tool-gate.js +2 -0
- package/dist/electron-build/security/tool-gate.js.map +1 -1
- package/dist/electron-build/setup/setup-store.js +34 -6
- package/dist/electron-build/setup/setup-store.js.map +1 -1
- package/dist/electron-build/social/heartbeat.js +18 -5
- package/dist/electron-build/social/heartbeat.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/index.js +250 -12
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +150 -113
- package/dist/ios/icons/icon-1400x1400.png +0 -0
- package/dist/ios/icons/icon-1400x1400.webp +0 -0
- package/dist/ios/index.html +106 -107
- package/dist/ios/mobile-agent.js +346 -45
- package/dist/ios/mobile-core.js +7303 -4590
- package/dist/ios/mobile-harness.js +1004 -0
- package/dist/ios/mobile-orbitdb.js +194 -0
- package/dist/ios/mobile-persona.js +80 -0
- package/dist/ios/mobile-privacy.js +2 -3
- package/dist/ios/mobile.css +286 -21
- package/dist/ios/mobile.html +106 -107
- package/dist/ios/mobile.js +300 -132
- package/dist/ios/routes-world.js +58 -1
- package/dist/ios/server.js +3 -1
- package/dist/ios/style.css +49 -2
- package/dist/ios/sw.js +1 -1
- package/dist/ios/ui/message-renderer.js +4 -18
- package/dist/ios/ui/step-timeline.js +14 -1
- package/dist/kernel/channel-actor.js +30 -2
- package/dist/kernel/gate-scan.js +1 -1
- package/dist/kernel/index.js +33 -0
- package/dist/kernel/model-runtime.js +8 -1
- package/dist/kernel/plan.js +3 -3
- package/dist/kernel/roster.js +3 -3
- package/dist/llm/config-store.js +20 -0
- package/dist/llm/embedding-adapter.js +69 -0
- package/dist/llm/pi-ai.js +94 -34
- package/dist/orbitdb/memory-sync.js +70 -0
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
- package/dist/security/tool-gate.js +2 -0
- package/dist/setup/setup-store.js +34 -6
- package/dist/social/heartbeat.js +18 -5
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +150 -113
- package/dist/web/icons/icon-1400x1400.png +0 -0
- package/dist/web/icons/icon-1400x1400.webp +0 -0
- package/dist/web/index.html +52 -59
- package/dist/web/mobile-agent.js +351 -45
- package/dist/web/mobile-contacts.js +0 -4
- package/dist/web/mobile-core.js +7430 -4590
- package/dist/web/mobile-harness.js +1004 -0
- package/dist/web/mobile-orbitdb.js +194 -0
- package/dist/web/mobile-persona.js +80 -0
- package/dist/web/mobile-privacy.js +2 -3
- package/dist/web/mobile.css +286 -21
- package/dist/web/mobile.html +121 -107
- package/dist/web/mobile.js +409 -136
- package/dist/web/routes-contacts.js +94 -1
- package/dist/web/routes-world.js +58 -1
- package/dist/web/server.js +4 -2
- package/dist/web/style.css +49 -2
- package/dist/web/sw.js +1 -1
- package/dist/web/ui/message-renderer.js +4 -18
- package/dist/web/ui/step-timeline.js +14 -1
- package/package.json +3 -2
- package/scripts/build-web.ts +16 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opportunity-web-search.ts — AI 主动上网搜机会 (2026-10-05, leo: 系统要主动搜索展示)
|
|
3
|
+
*
|
|
4
|
+
* 设计: 世界观察器 (world-watcher) 每轮 tick 时, 若有 active intent / 用户画像,
|
|
5
|
+
* 自动用 DuckDuckGo 搜索「<意图> + 合作/机会/招募/开放」类关键词, 把真搜索结果
|
|
6
|
+
* 结构化落盘 `~/.bolloon/world/search/<id>.json`, 并进世界流 (reason=search, 带来源 URL)。
|
|
7
|
+
*
|
|
8
|
+
* 真实可信: 每条 = 真 URL + 真摘要 (来自搜索返回), 可点开核实, 不是本机编造。
|
|
9
|
+
* 无 TAVILY_API_KEY 时走 DuckDuckGo HTML 端点 (免费无需 key, 与本仓 fetch_url 同源 curl 模式)。
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs/promises';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as crypto from 'node:crypto';
|
|
14
|
+
import { execFile } from 'node:child_process';
|
|
15
|
+
import { promisify } from 'node:util';
|
|
16
|
+
import { worldDir } from './intent-store.js';
|
|
17
|
+
const pExecFile = promisify(execFile);
|
|
18
|
+
const searchDir = () => path.join(worldDir(), 'search');
|
|
19
|
+
function safeId(s) {
|
|
20
|
+
return s.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
21
|
+
}
|
|
22
|
+
/** DuckDuckGo HTML 搜索 → 结构化结果 (标题/URL/摘要) */
|
|
23
|
+
async function searchDuckDuckGo(query, limit = 8) {
|
|
24
|
+
try {
|
|
25
|
+
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36';
|
|
26
|
+
const { stdout } = await pExecFile('curl', [
|
|
27
|
+
'-sL', '--max-time', '20', '-A', UA,
|
|
28
|
+
'-H', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
|
|
29
|
+
`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`,
|
|
30
|
+
], { maxBuffer: 2 * 1024 * 1024, timeout: 25_000 });
|
|
31
|
+
if (!stdout)
|
|
32
|
+
return [];
|
|
33
|
+
// 解析 result__a (标题+URL) + result__snippet (摘要)
|
|
34
|
+
const out = [];
|
|
35
|
+
const itemRe = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/g;
|
|
36
|
+
let m;
|
|
37
|
+
let guard = 0;
|
|
38
|
+
while ((m = itemRe.exec(stdout)) !== null && guard++ < 30) {
|
|
39
|
+
const rawHref = m[1];
|
|
40
|
+
// DDG 的 href 是重定向包装 (//duckduckgo.com/l/?uddg=<encoded>)
|
|
41
|
+
let url = rawHref;
|
|
42
|
+
const uddg = rawHref.match(/uddg=([^&]+)/);
|
|
43
|
+
if (uddg) {
|
|
44
|
+
try {
|
|
45
|
+
url = decodeURIComponent(uddg[1]);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
url = rawHref;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!/^https?:\/\//i.test(url))
|
|
52
|
+
continue;
|
|
53
|
+
const title = m[2].replace(/<[^>]+>/g, '').trim();
|
|
54
|
+
const summary = m[3].replace(/<[^>]+>/g, '').replace(/&[a-z]+;/g, ' ').replace(/\s+/g, ' ').trim();
|
|
55
|
+
if (title)
|
|
56
|
+
out.push({ title, url, summary: summary.slice(0, 200) });
|
|
57
|
+
if (out.length >= limit)
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Tavily 搜索 (配置了 TAVILY_API_KEY 时用, 更完整) */
|
|
67
|
+
async function searchTavily(query, limit = 8) {
|
|
68
|
+
const key = process.env.TAVILY_API_KEY || '';
|
|
69
|
+
if (!key)
|
|
70
|
+
return [];
|
|
71
|
+
try {
|
|
72
|
+
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36';
|
|
73
|
+
const { stdout } = await pExecFile('curl', [
|
|
74
|
+
'-s', '--max-time', '20', '-A', UA,
|
|
75
|
+
'-X', 'POST', 'https://api.tavily.com/search',
|
|
76
|
+
'-H', 'Content-Type: application/json',
|
|
77
|
+
'-d', JSON.stringify({ api_key: key, query, max_results: limit, include_answer: false }),
|
|
78
|
+
], { maxBuffer: 2 * 1024 * 1024, timeout: 25_000 });
|
|
79
|
+
const d = JSON.parse(stdout);
|
|
80
|
+
return (d.results || []).slice(0, limit).map((x) => ({
|
|
81
|
+
title: String(x.title || '').trim(),
|
|
82
|
+
url: String(x.url || '').trim(),
|
|
83
|
+
summary: String(x.content || '').replace(/\s+/g, ' ').slice(0, 200),
|
|
84
|
+
})).filter((x) => x.title && /^https?:/.test(x.url));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** 搜索查询构造: 意图/画像 + 机会类词 (真搜索, 关键词可配) */
|
|
91
|
+
const OPPORTUNITY_HINTS = ['合作', '机会', '招募', 'open call', 'partnership', 'looking for', 'call for'];
|
|
92
|
+
function buildQueries(topic) {
|
|
93
|
+
const base = String(topic || '').trim().slice(0, 60);
|
|
94
|
+
if (!base)
|
|
95
|
+
return [];
|
|
96
|
+
return OPPORTUNITY_HINTS.map((h) => `${base} ${h}`);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 主动搜索一轮: 按 topic (意图/画像文本) 搜机会, 结构化落盘。
|
|
100
|
+
* 返回本轮新增条数 (已有同 URL 的不重复落盘 — 幂等)。
|
|
101
|
+
*/
|
|
102
|
+
export async function searchOpportunitiesForTopic(topic, opts = {}) {
|
|
103
|
+
const limit = opts.limit ?? 6;
|
|
104
|
+
const queries = buildQueries(topic);
|
|
105
|
+
if (!queries.length)
|
|
106
|
+
return { ok: false, added: 0, total: 0, results: [], error: '空 topic' };
|
|
107
|
+
// 合并引擎结果 (Tavily 优先, 无 key 走 DDG)
|
|
108
|
+
const engine = process.env.TAVILY_API_KEY ? searchTavily : searchDuckDuckGo;
|
|
109
|
+
const raw = [];
|
|
110
|
+
for (const q of queries.slice(0, 3)) {
|
|
111
|
+
const hits = await engine(q, limit);
|
|
112
|
+
for (const h of hits)
|
|
113
|
+
raw.push({ ...h, query: q });
|
|
114
|
+
}
|
|
115
|
+
// 已有 URL 集合 (幂等)
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
try {
|
|
118
|
+
const files = await fs.readdir(searchDir());
|
|
119
|
+
for (const f of files) {
|
|
120
|
+
if (!f.endsWith('.json'))
|
|
121
|
+
continue;
|
|
122
|
+
try {
|
|
123
|
+
const p = JSON.parse(await fs.readFile(path.join(searchDir(), f), 'utf-8'));
|
|
124
|
+
if (p && p.url)
|
|
125
|
+
seen.add(String(p.url));
|
|
126
|
+
}
|
|
127
|
+
catch { /* 坏文件跳过 */ }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch { /* 目录不存在 = 首次 */ }
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
const results = [];
|
|
133
|
+
let added = 0;
|
|
134
|
+
try {
|
|
135
|
+
await fs.mkdir(searchDir(), { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
catch { /* 落盘失败不阻断 */ }
|
|
138
|
+
for (const r of raw) {
|
|
139
|
+
if (seen.has(r.url))
|
|
140
|
+
continue; // 已有, 不重复
|
|
141
|
+
const id = `search_${safeId(crypto.createHash('sha256').update(r.url).digest('hex').slice(0, 14))}`;
|
|
142
|
+
const opp = {
|
|
143
|
+
id,
|
|
144
|
+
query: r.query,
|
|
145
|
+
title: r.title.slice(0, 80),
|
|
146
|
+
url: r.url,
|
|
147
|
+
summary: r.summary,
|
|
148
|
+
source: process.env.TAVILY_API_KEY ? 'tavily' : 'duckduckgo',
|
|
149
|
+
searchedAt: now,
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
await fs.writeFile(path.join(searchDir(), `${id}.json`), JSON.stringify(opp, null, 2) + '\n', { mode: 0o600 });
|
|
153
|
+
seen.add(r.url);
|
|
154
|
+
results.push(opp);
|
|
155
|
+
added += 1;
|
|
156
|
+
}
|
|
157
|
+
catch { /* 单条失败跳过 */ }
|
|
158
|
+
if (added >= limit)
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
// 2026-10-05 降级 (leo: 输入后要足够多机会): 引擎被限流/返回空时,
|
|
162
|
+
// 从已有搜索库按关键词相关度返回匹配旧结果 — 保证输入后总有内容 (不假装, 都是真 URL)。
|
|
163
|
+
if (added === 0) {
|
|
164
|
+
try {
|
|
165
|
+
const existing = await listSearchOpportunities();
|
|
166
|
+
const topicTags = buildTags(topic);
|
|
167
|
+
const related = existing
|
|
168
|
+
.map((o) => {
|
|
169
|
+
const tags = buildTags(`${o.title} ${o.summary} ${o.query || ''}`);
|
|
170
|
+
const hit = topicTags.filter((t) => tags.includes(t)).length;
|
|
171
|
+
return { o, hit };
|
|
172
|
+
})
|
|
173
|
+
.filter((x) => x.hit > 0)
|
|
174
|
+
.sort((a, b) => b.hit - a.hit)
|
|
175
|
+
.slice(0, limit);
|
|
176
|
+
for (const { o } of related) {
|
|
177
|
+
results.push({ ...o, query: `${topic} (相关)` });
|
|
178
|
+
}
|
|
179
|
+
if (results.length > 0) {
|
|
180
|
+
return { ok: true, added: 0, total: results.length, results, error: '引擎限流, 已返回相关旧结果' };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch { /* 降级失败不影响 */ }
|
|
184
|
+
}
|
|
185
|
+
return { ok: true, added, total: results.length, results };
|
|
186
|
+
}
|
|
187
|
+
/** 提取关键词标签 (分词: ASCII 词 + CJK 二元片) — 用于相关度匹配 (导出供测试) */
|
|
188
|
+
export function buildTags(text) {
|
|
189
|
+
const src = String(text || '').toLowerCase();
|
|
190
|
+
const words = src.match(/[a-z][a-z0-9_-]{2,}/g) || [];
|
|
191
|
+
const cjk = src.match(/[\u4e00-\u9fff]+/g) || [];
|
|
192
|
+
const cjkBigrams = cjk.flatMap((s) => {
|
|
193
|
+
if (s.length <= 2)
|
|
194
|
+
return [s];
|
|
195
|
+
const out = [];
|
|
196
|
+
for (let i = 0; i + 1 < s.length; i += 1)
|
|
197
|
+
out.push(s.slice(i, i + 2));
|
|
198
|
+
return out;
|
|
199
|
+
});
|
|
200
|
+
return Array.from(new Set([...words, ...cjkBigrams]));
|
|
201
|
+
}
|
|
202
|
+
/** 列出已搜索到的机会 (最近在前) */
|
|
203
|
+
export async function listSearchOpportunities() {
|
|
204
|
+
try {
|
|
205
|
+
const files = await fs.readdir(searchDir());
|
|
206
|
+
const out = [];
|
|
207
|
+
for (const f of files) {
|
|
208
|
+
if (!f.endsWith('.json'))
|
|
209
|
+
continue;
|
|
210
|
+
try {
|
|
211
|
+
const p = JSON.parse(await fs.readFile(path.join(searchDir(), f), 'utf-8'));
|
|
212
|
+
if (p && p.id && p.url)
|
|
213
|
+
out.push(p);
|
|
214
|
+
}
|
|
215
|
+
catch { /* 坏文件跳过 */ }
|
|
216
|
+
}
|
|
217
|
+
return out.sort((a, b) => b.searchedAt - a.searchedAt);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -23,7 +23,7 @@ import * as path from 'path';
|
|
|
23
23
|
import * as os from 'os';
|
|
24
24
|
import { EventEmitter } from 'events';
|
|
25
25
|
import { irohTransport as defaultIrohTransport } from '../network/iroh-transport.js';
|
|
26
|
-
import {
|
|
26
|
+
import { getValueProfile, loadAllJudgments } from '../pi-ecosystem-judgment/human-value-store.js';
|
|
27
27
|
function homeDir() {
|
|
28
28
|
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
29
29
|
}
|
|
@@ -189,7 +189,6 @@ export async function getInbox(peerDID) {
|
|
|
189
189
|
async function buildValueHint(text) {
|
|
190
190
|
try {
|
|
191
191
|
const allJudgments = await loadAllJudgments();
|
|
192
|
-
const relevant = await getRelevantValues(text, undefined);
|
|
193
192
|
const profile = await getValueProfile('me');
|
|
194
193
|
const profileHint = [
|
|
195
194
|
`quality_focus=${profile.quality_focus.toFixed(2)}`,
|
|
@@ -522,21 +522,19 @@ export function parseToolCall(content, ctx) {
|
|
|
522
522
|
return null;
|
|
523
523
|
}
|
|
524
524
|
/**
|
|
525
|
-
* isFinalResponse —
|
|
526
|
-
* 2026-
|
|
527
|
-
*
|
|
525
|
+
* isFinalResponse — 判断 LLM 是否已完结。
|
|
526
|
+
* 2026-10-12 (leo 拍板: 全项目去掉 <final gen> 截止符): 旧实现只有在回复里找到
|
|
527
|
+
* `<final gen>` 标记才返回 true —— 模型中间轮次 echo 该文本就误判提前截止。
|
|
528
|
+
* 现在: 剥掉 think 块后**没有可执行工具调用** = 这一轮就是最终答复。
|
|
529
|
+
* (工具调用优先: 含可解析 tool_call 不算 final, 先执行工具。)
|
|
528
530
|
*/
|
|
529
531
|
export function isFinalResponse(content, ctx) {
|
|
530
|
-
const stripped = content.replace(/<think[\s\S]*?<\/think
|
|
531
|
-
|
|
532
|
-
return false;
|
|
533
|
-
// 用默认 ctx (无需 tool 名解析, 只看能否 parse 出来) - 但 isFinalResponse 不需要 tools 列表上下文.
|
|
534
|
-
// 如果没有传 ctx, 使用 empty set (只解析不 resolve).
|
|
532
|
+
const stripped = content.replace(/<think[\s\S]*?<\/think>/g, '');
|
|
533
|
+
// 与 react-loop 的判定链一致: 有可解析的工具调用 → 优先执行工具, 不算 final
|
|
535
534
|
if (ctx && parseToolCall(stripped, ctx))
|
|
536
535
|
return false;
|
|
537
536
|
if (!ctx) {
|
|
538
537
|
// 试探性 parse (不 resolve name) — 如果至少能提取 name+args 模式, 就不是 final.
|
|
539
|
-
// 这里粗略检测: stripped 中含 json "name": 或 <invoke name= 或 <function_calls> 任一
|
|
540
538
|
if (/\{[\s\S]*?"name"\s*:/.test(stripped))
|
|
541
539
|
return false;
|
|
542
540
|
if (/<invoke\s+name=/.test(stripped))
|
|
@@ -546,23 +544,12 @@ export function isFinalResponse(content, ctx) {
|
|
|
546
544
|
}
|
|
547
545
|
return true;
|
|
548
546
|
}
|
|
549
|
-
/** extractFinalAnswer: 去除 <final gen>
|
|
547
|
+
/** extractFinalAnswer: 去除 tool call 噪声. (2026-10-12: <final gen> 标记移除, 不再剥除) */
|
|
550
548
|
export function extractFinalAnswer(content) {
|
|
551
|
-
const marker = '<final gen>';
|
|
552
|
-
const markerIndex = content.indexOf(marker);
|
|
553
|
-
if (markerIndex !== -1) {
|
|
554
|
-
const after = content.substring(markerIndex + marker.length).trim();
|
|
555
|
-
if (after) {
|
|
556
|
-
content = after;
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
content = content.substring(0, markerIndex).trim();
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
549
|
let cleaned = content
|
|
563
550
|
.replace(/调用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
564
551
|
.replace(/使用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
565
|
-
.replace(/tool[_
|
|
552
|
+
.replace(/tool[_\\w]*[::]\s*\w+\s*\([^)]*\)/gi, '')
|
|
566
553
|
.trim();
|
|
567
554
|
return cleaned;
|
|
568
555
|
}
|
|
@@ -29,7 +29,6 @@ export class PiAgentHarness {
|
|
|
29
29
|
deps;
|
|
30
30
|
failClosed;
|
|
31
31
|
modelCalls = 0;
|
|
32
|
-
degradedOnce = new Set();
|
|
33
32
|
constructor(deps) {
|
|
34
33
|
this.deps = deps;
|
|
35
34
|
this.failClosed = deps.failClosed !== false;
|
|
@@ -256,7 +255,7 @@ export class PiAgentHarness {
|
|
|
256
255
|
this.emit({ event: 'review', kind: 'error', failureKind: 'goal_review', reason: `审查器失效: ${err?.message}`, runId: state.runId, goalId: state.goalId });
|
|
257
256
|
return {
|
|
258
257
|
kind: 'continue-review',
|
|
259
|
-
hint: `[目标审查降级] 审查器执行失败 (${String(err?.message || err).slice(0, 120)}), 按"未确认完成"处理: 请再次核对用户需求是否真的全部满足,
|
|
258
|
+
hint: `[目标审查降级] 审查器执行失败 (${String(err?.message || err).slice(0, 120)}), 按"未确认完成"处理: 请再次核对用户需求是否真的全部满足, 并列出证据。确认完成后再给出最终回答。`,
|
|
260
259
|
};
|
|
261
260
|
}
|
|
262
261
|
}
|
|
@@ -13,6 +13,7 @@ import { checkWritePath } from './shell-guard.js';
|
|
|
13
13
|
import { p2pDocumentTools } from './p2p-document-tools.js';
|
|
14
14
|
import { runSelfImproveLoop } from './pi-sdk-session-factory.js';
|
|
15
15
|
import { getMinimax } from '../constraints/index.js';
|
|
16
|
+
import { llmConfigStore } from '../llm/config-store.js';
|
|
16
17
|
import { delegateToEngine, buildDelegateCommand } from '../external-engines/delegate.js';
|
|
17
18
|
import { spawnBackground } from './process-runner.js';
|
|
18
19
|
import { DELEGATE_ENV_MARK } from './background-notices.js';
|
|
@@ -858,11 +859,9 @@ export function registerBuiltinTools(ctx) {
|
|
|
858
859
|
// 与 shell_exec 的区别: 直接接受完整 shell 命令字符串, 更适合模型自主写命令.
|
|
859
860
|
// 2026-10-01 (优化 #2 v1): **工具发现** —— 125 个工具不可能都记住, 给一个"按关键词找工具"的入口。
|
|
860
861
|
// 空关键词只回**分类索引**(名称清单), 有关键词才回"名称+用途摘要" ⇒ 不刷屏。
|
|
861
|
-
// 2026-10-01 (用户: 「skills 教训学习, bolloon
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
// 她那边的做法: 启动自动装载 + 按**描述**在提示里露面 + 需要时用 `skill_view` 读全文 ✓。
|
|
865
|
-
// bolloon 有 1300 个(她百来号) ⇒ 全量注入会撑爆提示 ✗ ⇒ 改成**两个发现工具**(和 `list_tools` 同一思路 ✓)。
|
|
862
|
+
// 2026-10-01 (用户: 「skills 教训学习, bolloon 有吗」): 技能发现 = 两个工具而非全量注入 —
|
|
863
|
+
// 技能库上千个, 全量注入会撑爆提示 (历史: 19 个 skill 曾把 systemPrompt 顶到 22K 导致模型不输出)。
|
|
864
|
+
// 做法同 `list_tools`: 按关键词找 (`list_skills`) → 读全文 (`read_skill`)。
|
|
866
865
|
ctx.tools.set('list_skills', {
|
|
867
866
|
name: 'list_skills',
|
|
868
867
|
description: '用于**找技能**: 给关键词(中文/英文都行)⇒ 返回已装载技能的名字+说明(≤20 条); 不给关键词 ⇒ 先给总数与前若干条. 找到后用 read_skill 读全文再照做.',
|
|
@@ -4445,6 +4444,182 @@ export function registerWalletTools(ctx) {
|
|
|
4445
4444
|
}
|
|
4446
4445
|
}
|
|
4447
4446
|
});
|
|
4447
|
+
// 2026-10-14 (leo: 「需要向量记忆和去中心化记忆」, P0): 结构化记忆工具
|
|
4448
|
+
// —— MemoryEntry 本地 JSONL 持久化; embedding 向量 (P1) / OrbitDB 复制 (P3) 后续接
|
|
4449
|
+
const memoryAgentId = () => ctx.identity?.did || String(ctx.cwd || '');
|
|
4450
|
+
/**
|
|
4451
|
+
* P1: 记忆 embedding 配置。读 llm-config 的 `embedding` 段:
|
|
4452
|
+
* { embedding: { provider, model, baseUrl?, apiKey?, groupId? } }
|
|
4453
|
+
* 未配置 / provider 不可用 → { provider: 'none' } (记忆层回退关键词, 行为不变)。
|
|
4454
|
+
* 诚实边界: 本仓现有 provider (glm 中转 / minimax 订阅 key) 实测无可用 embedding,
|
|
4455
|
+
* 故默认 none; 将来任一供应商 key 就绪, 在此配置段填上即可, 记忆层零改动。
|
|
4456
|
+
*/
|
|
4457
|
+
const getMemoryEmbeddingConfig = () => {
|
|
4458
|
+
try {
|
|
4459
|
+
const cfg = llmConfigStore?.config;
|
|
4460
|
+
const emb = cfg?.embedding;
|
|
4461
|
+
if (emb && typeof emb === 'object' && emb.provider && emb.provider !== 'none') {
|
|
4462
|
+
return {
|
|
4463
|
+
provider: String(emb.provider),
|
|
4464
|
+
model: emb.model ? String(emb.model) : undefined,
|
|
4465
|
+
baseUrl: emb.baseUrl ? String(emb.baseUrl) : undefined,
|
|
4466
|
+
apiKey: emb.apiKey ? String(emb.apiKey) : undefined,
|
|
4467
|
+
groupId: emb.groupId ? String(emb.groupId) : undefined,
|
|
4468
|
+
};
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
catch { /* 配置读取失败 → none */ }
|
|
4472
|
+
return { provider: 'none' };
|
|
4473
|
+
};
|
|
4474
|
+
ctx.tools.set('memory_save', {
|
|
4475
|
+
name: 'memory_save',
|
|
4476
|
+
description: '保存一条**结构化长期记忆** (事实/偏好/洞察/事件/关系/技能反馈)。用于: 用户明确说"记住 X"、反复出现的偏好、值得跨会话保留的事实。自动去重 (同内容不重复), 自动打分重要性。',
|
|
4477
|
+
parameters: {
|
|
4478
|
+
content: '记忆内容 (必填, ≥2 字符)',
|
|
4479
|
+
kind: '类型: fact | insight | preference | event | relationship | skill-feedback (默认 fact)',
|
|
4480
|
+
importance: '手动重要性 0-1 (可选, 缺省自动启发式打分)',
|
|
4481
|
+
refs: '关联证据 JSON 数组 (可选, 如 goalId/runId)',
|
|
4482
|
+
},
|
|
4483
|
+
execute: async (args) => {
|
|
4484
|
+
try {
|
|
4485
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4486
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4487
|
+
// P1: 有 embedding adapter 时 save 顺便向量化 (存 vec, 供语义召回)
|
|
4488
|
+
let adapter;
|
|
4489
|
+
try {
|
|
4490
|
+
const { createEmbeddingAdapter } = await import('../llm/embedding-adapter.js');
|
|
4491
|
+
const a = createEmbeddingAdapter(getMemoryEmbeddingConfig());
|
|
4492
|
+
if (a.provider !== 'none')
|
|
4493
|
+
adapter = a;
|
|
4494
|
+
}
|
|
4495
|
+
catch { /* 无向量能力 → 只存文本 */ }
|
|
4496
|
+
const entry = await store.save({
|
|
4497
|
+
content: String(args.content || '').trim(),
|
|
4498
|
+
kind: args.kind || 'fact',
|
|
4499
|
+
importance: args.importance !== undefined ? Number(args.importance) : undefined,
|
|
4500
|
+
refs: Array.isArray(args.refs) ? args.refs.map(String) : undefined,
|
|
4501
|
+
source: 'tool',
|
|
4502
|
+
}, adapter);
|
|
4503
|
+
// P3: 条目级 OrbitDB 同步 (失败静默 — 本地记忆仍权威)
|
|
4504
|
+
try {
|
|
4505
|
+
const { syncMemoryEntryToOrbit } = await import('../orbitdb/memory-sync.js');
|
|
4506
|
+
await syncMemoryEntryToOrbit(entry);
|
|
4507
|
+
}
|
|
4508
|
+
catch { /* 同步失败不阻塞保存 */ }
|
|
4509
|
+
return { success: true, output: `✅ 已保存记忆 #${entry.id.slice(0, 8)} (${entry.kind}, 重要性 ${entry.importance.toFixed(2)})` };
|
|
4510
|
+
}
|
|
4511
|
+
catch (e) {
|
|
4512
|
+
return { success: false, error: `memory_save 失败: ${String(e.message || e)}` };
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
});
|
|
4516
|
+
ctx.tools.set('memory_recall', {
|
|
4517
|
+
name: 'memory_recall',
|
|
4518
|
+
description: '按查询关键词**召回长期记忆** (事实/偏好/洞察)。每轮对话已自动做记忆召回, 这里用于显式深挖: 用户说"你还记得我之前说的 X"、或当前任务需要历史上下文时调用。',
|
|
4519
|
+
parameters: {
|
|
4520
|
+
query: '查询内容 (必填, 用关键词)',
|
|
4521
|
+
limit: '返回条数 (默认 3)',
|
|
4522
|
+
},
|
|
4523
|
+
execute: async (args) => {
|
|
4524
|
+
try {
|
|
4525
|
+
const { getMemoryStore, formatRecallBlock } = await import('./memory-store.js');
|
|
4526
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4527
|
+
await store.load();
|
|
4528
|
+
// P3: 召回前先拉远端条目合并 (LWW, updatedAt 大者胜; 网络不可用静默)
|
|
4529
|
+
try {
|
|
4530
|
+
const { fetchRemoteEntries } = await import('../orbitdb/memory-sync.js');
|
|
4531
|
+
const remote = await fetchRemoteEntries(memoryAgentId());
|
|
4532
|
+
if (remote.length > 0)
|
|
4533
|
+
await store.mergeRemote(remote);
|
|
4534
|
+
}
|
|
4535
|
+
catch { /* 远端拉取失败 → 只用本地 */ }
|
|
4536
|
+
const query = String(args.query || '');
|
|
4537
|
+
// P1: 有 embedding adapter 时走向量召回; 无 → 关键词回退 (P0 行为不变)
|
|
4538
|
+
let queryVec;
|
|
4539
|
+
try {
|
|
4540
|
+
const { createEmbeddingAdapter } = await import('../llm/embedding-adapter.js');
|
|
4541
|
+
const adapter = createEmbeddingAdapter(getMemoryEmbeddingConfig());
|
|
4542
|
+
if (adapter.provider !== 'none' && query.trim()) {
|
|
4543
|
+
queryVec = await adapter.embed(query);
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
catch { /* vector 不可用 → 关键词回退 */ }
|
|
4547
|
+
const hits = await store.recall({
|
|
4548
|
+
query,
|
|
4549
|
+
limit: args.limit ? Number(args.limit) : 3,
|
|
4550
|
+
queryVec,
|
|
4551
|
+
});
|
|
4552
|
+
if (!hits.length)
|
|
4553
|
+
return { success: true, output: '📭 没有找到相关记忆 (可能是新话题或记忆未保存)' };
|
|
4554
|
+
return { success: true, output: formatRecallBlock(hits) };
|
|
4555
|
+
}
|
|
4556
|
+
catch (e) {
|
|
4557
|
+
return { success: false, error: `memory_recall 失败: ${String(e.message || e)}` };
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
});
|
|
4561
|
+
// P2: 价值分层工具 — 固化 / 遗忘 / 统计 (2026-10-11, decentralized-memory-design §2.7)
|
|
4562
|
+
ctx.tools.set('memory_crystallize', {
|
|
4563
|
+
name: 'memory_crystallize',
|
|
4564
|
+
description: '固化一条记忆 (crystallize): 标记为「重要, 不淡忘」+ importance 加成。用于: 用户明确说"这个要记住"、反复确认的重要约定、多次命中的核心事实。返回固化后的条目。',
|
|
4565
|
+
parameters: {
|
|
4566
|
+
entryId: '记忆 ID (从 memory_recall 的返回里拿, 形如 #xxxxxxxx)',
|
|
4567
|
+
},
|
|
4568
|
+
execute: async (args) => {
|
|
4569
|
+
try {
|
|
4570
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4571
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4572
|
+
await store.load();
|
|
4573
|
+
const id = String(args.entryId || '').replace(/^#/, '');
|
|
4574
|
+
const e = await store.crystallize(id);
|
|
4575
|
+
if (!e)
|
|
4576
|
+
return { success: false, error: `未找到记忆 #${id}` };
|
|
4577
|
+
return { success: true, output: `✅ 已固化记忆 #${e.id.slice(0, 8)} (${e.kind}, importance ${e.importance.toFixed(2)}, 不再淡忘)` };
|
|
4578
|
+
}
|
|
4579
|
+
catch (e) {
|
|
4580
|
+
return { success: false, error: `memory_crystallize 失败: ${String(e.message || e)}` };
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
});
|
|
4584
|
+
ctx.tools.set('memory_forget', {
|
|
4585
|
+
name: 'memory_forget',
|
|
4586
|
+
description: '删除一条记忆 (entryId 从 memory_recall 返回拿)。用于: 用户说"忘掉这个/删掉这条记忆"、记忆过时/错误。删除不可恢复。',
|
|
4587
|
+
parameters: {
|
|
4588
|
+
entryId: '记忆 ID (形如 #xxxxxxxx)',
|
|
4589
|
+
},
|
|
4590
|
+
execute: async (args) => {
|
|
4591
|
+
try {
|
|
4592
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4593
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4594
|
+
await store.load();
|
|
4595
|
+
const id = String(args.entryId || '').replace(/^#/, '');
|
|
4596
|
+
const ok = await store.remove(id);
|
|
4597
|
+
if (!ok)
|
|
4598
|
+
return { success: false, error: `未找到记忆 #${id}` };
|
|
4599
|
+
return { success: true, output: `🗑️ 已删除记忆 #${id.slice(0, 8)}` };
|
|
4600
|
+
}
|
|
4601
|
+
catch (e) {
|
|
4602
|
+
return { success: false, error: `memory_forget 失败: ${String(e.message || e)}` };
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
});
|
|
4606
|
+
ctx.tools.set('memory_stats', {
|
|
4607
|
+
name: 'memory_stats',
|
|
4608
|
+
description: '查看记忆库统计: 总条数 / 已固化 / 已淡化 (decay)。用于: 用户问"我存了多少记忆"、诊断记忆衰减是否生效。',
|
|
4609
|
+
parameters: {},
|
|
4610
|
+
execute: async () => {
|
|
4611
|
+
try {
|
|
4612
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4613
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4614
|
+
await store.load();
|
|
4615
|
+
const s = store.stats();
|
|
4616
|
+
return { success: true, output: `📊 记忆统计: 共 ${s.total} 条 · 已固化 ${s.crystallized} · 已淡化 ${s.decayed}` };
|
|
4617
|
+
}
|
|
4618
|
+
catch (e) {
|
|
4619
|
+
return { success: false, error: `memory_stats 失败: ${String(e.message || e)}` };
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
});
|
|
4448
4623
|
// 2026-07-28: 注册 LSP 工具 (代码智能)
|
|
4449
4624
|
// registerBuiltinTools 不是 async, 用同步 import 兜底
|
|
4450
4625
|
try {
|