@bolloon/bolloon-agent 0.6.5 → 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-web-search.js +39 -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/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 +2 -2
- package/dist/ios/client.js +74 -35
- 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 +4 -1
- package/dist/ios/server.js +3 -1
- package/dist/ios/style.css +13 -5
- 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 +74 -35
- package/dist/web/icons/icon-1400x1400.png +0 -0
- package/dist/web/icons/icon-1400x1400.webp +0 -0
- package/dist/web/index.html +37 -25
- 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 +4 -1
- package/dist/web/server.js +4 -2
- package/dist/web/style.css +13 -5
- 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
|
@@ -18,7 +18,6 @@ import { loadChainConfig, DEFAULT_CONFIRMATIONS, } from './chain-config.js';
|
|
|
18
18
|
/** ERC20 Transfer 事件 topic0 */
|
|
19
19
|
export const ERC20_TRANSFER_TOPIC0 = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
|
|
20
20
|
const ERC20_IFACE = new Interface(['event Transfer(address indexed from, address indexed to, uint256 value)']);
|
|
21
|
-
const ZERO32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
22
21
|
function base(partial) {
|
|
23
22
|
return {
|
|
24
23
|
chainSettled: false,
|
|
@@ -44,25 +44,13 @@ export function segmentChatReply(reply, ctx) {
|
|
|
44
44
|
if (remaining.trim()) {
|
|
45
45
|
remaining = filterFillerText(remaining);
|
|
46
46
|
}
|
|
47
|
-
// === 6.
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
let beforeFinalText = remaining;
|
|
52
|
-
if (finalIdx >= 0) {
|
|
53
|
-
beforeFinalText = remaining.substring(0, finalIdx);
|
|
54
|
-
}
|
|
55
|
-
const textContent = beforeFinalText.trim();
|
|
47
|
+
// === 6. 剩余内容全部作为 text 段 (2026-10-12: 移除 <final gen> 后不再切 final segment) ===
|
|
48
|
+
// 旧实现找 `<final gen>` 标记把内容切成 before/after 两段; 标记已全项目移除,
|
|
49
|
+
// 剩余内容就是给用户的完整答复, 全部进 text。
|
|
50
|
+
const textContent = remaining.trim();
|
|
56
51
|
if (textContent) {
|
|
57
52
|
segments.push({ type: 'text', content: textContent });
|
|
58
53
|
}
|
|
59
|
-
// === 7. 切 <final gen>...</final gen> (永远最后 push, 渲染时在最后) ===
|
|
60
|
-
if (finalIdx >= 0) {
|
|
61
|
-
const afterFinal = remaining.substring(finalIdx + '<final gen>'.length).trim();
|
|
62
|
-
if (afterFinal) {
|
|
63
|
-
segments.push({ type: 'final', content: afterFinal });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
54
|
return segments;
|
|
67
55
|
}
|
|
68
56
|
/**
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* human-directory.ts — 手机号 → DID 人类账号目录 (2026-10-11, phone-account-lookup-design §3)
|
|
3
|
+
*
|
|
4
|
+
* 落点 A (群内/已知盐目录, 推荐起步):
|
|
5
|
+
* - 发布: 用户 opt-in → 本地算 HMAC(salt || phone, pepper) 盲化哈希 → 存目录条目
|
|
6
|
+
* - 查询: 输入对方手机号 → 用同一算法算哈希 → 遍历本地目录比对 → 命中拿 DID
|
|
7
|
+
* - 隐私: 目录永不存明文手机号 · 哈希带 per-user salt (不可离线枚举) ·
|
|
8
|
+
* pepper 本地持有不进目录 · 未发布 = 查不到 (与不存在同义)
|
|
9
|
+
*
|
|
10
|
+
* 诚实边界 (写死):
|
|
11
|
+
* - 落点 A 只能搜「已与你交换过 salt / 已加入同一群」的节点 (三角不可能: 隐私/可搜/去中心化只取二)
|
|
12
|
+
* - 本模块只做「本地目录 + 哈希运算」; 跨节点查询靠既有 P2P (群目录复制 / direct 消息)
|
|
13
|
+
* - pepper 是本机私密 (~/.bolloon/contacts/directory-pepper.json, 0600), 不进目录条目
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'fs/promises';
|
|
16
|
+
import * as os from 'os';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import { createHmac, randomBytes } from 'node:crypto';
|
|
19
|
+
const home = () => process.env.HOME || os.homedir() || '/tmp';
|
|
20
|
+
export function directoryPath(homeDir) {
|
|
21
|
+
return path.join(homeDir || home(), '.bolloon', 'contacts', 'human-directory.json');
|
|
22
|
+
}
|
|
23
|
+
/** pepper 路径 (0600 私密) */
|
|
24
|
+
export function pepperPath(homeDir) {
|
|
25
|
+
return path.join(homeDir || home(), '.bolloon', 'contacts', 'directory-pepper.json');
|
|
26
|
+
}
|
|
27
|
+
/** 原子写: tmp + rename (写坏不残留半截文件; 0600 私密权限) */
|
|
28
|
+
async function atomicWriteJson(file, value) {
|
|
29
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
30
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now().toString(36)}`;
|
|
31
|
+
await fs.writeFile(tmp, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
32
|
+
await fs.rename(tmp, file);
|
|
33
|
+
}
|
|
34
|
+
/** 读取/生成 pepper (首次生成随机 32 字节; 幂等复用) */
|
|
35
|
+
export async function loadPepper(homeDir) {
|
|
36
|
+
const p = pepperPath(homeDir);
|
|
37
|
+
try {
|
|
38
|
+
const raw = await fs.readFile(p, 'utf8');
|
|
39
|
+
const j = JSON.parse(raw);
|
|
40
|
+
if (j?.pepper)
|
|
41
|
+
return j.pepper;
|
|
42
|
+
}
|
|
43
|
+
catch { /* 不存在 → 生成 */ }
|
|
44
|
+
const pepper = randomBytes(32).toString('hex');
|
|
45
|
+
await atomicWriteJson(p, { pepper });
|
|
46
|
+
return pepper;
|
|
47
|
+
}
|
|
48
|
+
/** 手机号归一化: 只留数字与 + (同一规则只此一处, 供校验与哈希共用) */
|
|
49
|
+
export function normalizePhone(value) {
|
|
50
|
+
return String(value || '').replace(/[^\d+]/g, '');
|
|
51
|
+
}
|
|
52
|
+
/** 盲化哈希: HMAC-SHA256(salt || phone, pepper) → hex (同一输入永远同一输出) */
|
|
53
|
+
export function blindHash(phone, salt, pepper) {
|
|
54
|
+
const normalized = normalizePhone(phone);
|
|
55
|
+
return createHmac('sha256', pepper).update(`${salt}||${normalized}`).digest('hex');
|
|
56
|
+
}
|
|
57
|
+
/** 生成 per-user salt */
|
|
58
|
+
export function genSalt() {
|
|
59
|
+
return randomBytes(16).toString('hex');
|
|
60
|
+
}
|
|
61
|
+
/** 校验手机号形状 (宽松: 8-15 位数字, 可带 + 前缀) */
|
|
62
|
+
export function isPlausiblePhone(value) {
|
|
63
|
+
return /^\+?\d{8,15}$/.test(normalizePhone(value));
|
|
64
|
+
}
|
|
65
|
+
/** 目录存储: 读/写原子 (与 contacts store 同模式) */
|
|
66
|
+
export async function readDirectory(homeDir) {
|
|
67
|
+
try {
|
|
68
|
+
const raw = await fs.readFile(directoryPath(homeDir), 'utf8');
|
|
69
|
+
const j = JSON.parse(raw);
|
|
70
|
+
return Array.isArray(j) ? j : [];
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (err?.code === 'ENOENT')
|
|
74
|
+
return [];
|
|
75
|
+
throw new Error(`手机号目录损坏 (${directoryPath(homeDir)}): ${err?.message || err}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function writeDirectory(entries, homeDir) {
|
|
79
|
+
await atomicWriteJson(directoryPath(homeDir), entries);
|
|
80
|
+
}
|
|
81
|
+
/** 从本机 identity/user.json 读取 64 hex publicKey (目录发布自动带上, 供对方直达加好友) */
|
|
82
|
+
export async function loadOwnPublicKeyHex(homeDir) {
|
|
83
|
+
try {
|
|
84
|
+
const p = path.join(homeDir || home(), '.bolloon', 'identity', 'user.json');
|
|
85
|
+
const j = JSON.parse(await fs.readFile(p, 'utf8'));
|
|
86
|
+
const pk = String(j?.publicKeyHex || '');
|
|
87
|
+
return /^[0-9a-f]{64}$/i.test(pk) ? pk.toLowerCase() : undefined;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** 发布一条目录条目 (opt-in; 同 phoneHash 已存在 → 更新为最新, 撤销后重发 = 恢复) */
|
|
94
|
+
export async function publishEntry(phone, did, opts = {}) {
|
|
95
|
+
if (!isPlausiblePhone(phone))
|
|
96
|
+
throw new Error('手机号格式不正确 (8-15 位数字, 可带 + 前缀)');
|
|
97
|
+
if (!did || !did.startsWith('did:'))
|
|
98
|
+
throw new Error('DID 格式不正确 (须以 did: 开头)');
|
|
99
|
+
const pepper = await loadPepper(opts.homeDir);
|
|
100
|
+
const salt = genSalt(); // 每次发布新 salt (旧哈希不可回推, 重发布 = 新哈希)
|
|
101
|
+
const publicKeyHex = opts.publicKeyHex
|
|
102
|
+
? opts.publicKeyHex.toLowerCase()
|
|
103
|
+
: await loadOwnPublicKeyHex(opts.homeDir); // 缺省自动带本机 P2P 公钥 (若有)
|
|
104
|
+
const entry = {
|
|
105
|
+
phoneHash: blindHash(phone, salt, pepper),
|
|
106
|
+
salt,
|
|
107
|
+
did,
|
|
108
|
+
publicKeyHex,
|
|
109
|
+
displayName: opts.displayName,
|
|
110
|
+
publishedAt: new Date().toISOString(),
|
|
111
|
+
revokedAt: null,
|
|
112
|
+
ownerSig: '', // P2 落点 B 才要求签名
|
|
113
|
+
};
|
|
114
|
+
const entries = await readDirectory(opts.homeDir);
|
|
115
|
+
const idx = entries.findIndex((e) => e.did === did); // 同一 DID 只留一条 (更新)
|
|
116
|
+
if (idx >= 0)
|
|
117
|
+
entries[idx] = entry;
|
|
118
|
+
else
|
|
119
|
+
entries.push(entry);
|
|
120
|
+
await writeDirectory(entries, opts.homeDir);
|
|
121
|
+
return entry;
|
|
122
|
+
}
|
|
123
|
+
/** 撤销 (软删): 标记 revokedAt, 条目保留 (审计/可恢复) */
|
|
124
|
+
export async function retractEntry(did, homeDir) {
|
|
125
|
+
const entries = await readDirectory(homeDir);
|
|
126
|
+
const e = entries.find((x) => x.did === did);
|
|
127
|
+
if (!e)
|
|
128
|
+
return false;
|
|
129
|
+
e.revokedAt = new Date().toISOString();
|
|
130
|
+
await writeDirectory(entries, homeDir);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
/** 查询: 用同一算法算哈希, 遍历比对 (未发布/已撤销 → not found) */
|
|
134
|
+
export async function lookupPhone(phone, homeDir) {
|
|
135
|
+
const pepper = await loadPepper(homeDir);
|
|
136
|
+
const entries = await readDirectory(homeDir);
|
|
137
|
+
// 对方的 salt 未知 → 逐个试本目录里的条目 salt (落点 A: 群小, N 可接受)
|
|
138
|
+
for (const e of entries) {
|
|
139
|
+
if (blindHash(phone, e.salt, pepper) === e.phoneHash) {
|
|
140
|
+
if (e.revokedAt)
|
|
141
|
+
return { found: false, revoked: true }; // 撤销 = 与不存在同义
|
|
142
|
+
return { found: true, did: e.did, publicKeyHex: e.publicKeyHex, displayName: e.displayName };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { found: false };
|
|
146
|
+
}
|
|
147
|
+
/** 列出已发布条目 (本机审计用; 只回脱敏: 哈希前 8 位 + DID + 时间) */
|
|
148
|
+
export function summarizeEntries(entries) {
|
|
149
|
+
return entries.map((e) => ({
|
|
150
|
+
hash: e.phoneHash.slice(0, 8) + '…',
|
|
151
|
+
did: e.did,
|
|
152
|
+
publishedAt: e.publishedAt,
|
|
153
|
+
revoked: !!e.revokedAt,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
@@ -150,5 +150,84 @@ export function registerContactTools(ctx, chain) {
|
|
|
150
150
|
},
|
|
151
151
|
});
|
|
152
152
|
registered.push(...CONTACT_TOOL_NAMES);
|
|
153
|
+
// ── 手机号目录 (2026-10-11, phone-account-lookup-design 落点 A) ──────────
|
|
154
|
+
// 工具面: 发布自己的手机号指针 / 查对方 / 撤销。隐私: 只操作哈希, 明文手机号只在本机。
|
|
155
|
+
ctx.tools.set('directory.publish', {
|
|
156
|
+
name: 'directory.publish',
|
|
157
|
+
description: '把自己的手机号 → DID 指针发布到本机目录 (opt-in, 用户显式要求才做)。之后对方凭手机号可查到你的 DID 加好友。**先问用户确认**再调用 (发布手机号是隐私动作)。',
|
|
158
|
+
parameters: { phone: '手机号 (11 位, 可带 +86 前缀)', displayName: '公开昵称 (可选, 不是真名)' },
|
|
159
|
+
execute: async (args) => {
|
|
160
|
+
const { publishEntry, isPlausiblePhone } = await import('./human-directory.js');
|
|
161
|
+
const phone = text(args.phone);
|
|
162
|
+
if (!isPlausiblePhone(phone))
|
|
163
|
+
return { success: false, error: '手机号格式不正确 (8-15 位数字)' };
|
|
164
|
+
const did = globalThis.__bolloonUserDid || 'did:bolln:local';
|
|
165
|
+
const e = await publishEntry(phone, did, { displayName: text(args.displayName) || undefined });
|
|
166
|
+
return { success: true, phoneHash: e.phoneHash.slice(0, 8) + '…', did: e.did, note: '已发布到本机目录 (对方凭手机号可查到你的 DID)' };
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
ctx.tools.set('directory.lookup', {
|
|
170
|
+
name: 'directory.lookup',
|
|
171
|
+
description: '按手机号查对方 DID (在本机目录内找)。用于: 用户说"用手机号找 XX 加好友"、想加某人为好友但只有手机号。响应只回 DID/昵称, 不回手机号。',
|
|
172
|
+
parameters: { phone: '对方手机号 (11 位)' },
|
|
173
|
+
execute: async (args) => {
|
|
174
|
+
const { lookupPhone, isPlausiblePhone } = await import('./human-directory.js');
|
|
175
|
+
const phone = text(args.phone);
|
|
176
|
+
if (!isPlausiblePhone(phone))
|
|
177
|
+
return { success: false, error: '手机号格式不正确' };
|
|
178
|
+
const r = await lookupPhone(phone);
|
|
179
|
+
if (!r.found)
|
|
180
|
+
return { success: true, found: false, note: '未找到 (对方未注册目录或已撤销)' };
|
|
181
|
+
return { success: true, found: true, did: r.did, publicKeyHex: r.publicKeyHex || null, displayName: r.displayName || null, note: '已找到, 可用 directory.add_friend 发起好友请求' };
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
ctx.tools.set('directory.add_friend', {
|
|
185
|
+
name: 'directory.add_friend',
|
|
186
|
+
description: '按手机号发起好友申请: 目录 lookup → P2P friend request。用于用户明确说“用手机号加好友”。这是对外通信动作,调用前应确认目标手机号和用户意图; 对方仍需 consent 接受,不会自动成为好友。',
|
|
187
|
+
parameters: { phone: '对方手机号 (8-15 位)', name: '好友备注 (可选)', message: '申请附言 (可选)' },
|
|
188
|
+
execute: async (args) => {
|
|
189
|
+
const { lookupPhone, isPlausiblePhone } = await import('./human-directory.js');
|
|
190
|
+
const phone = text(args.phone);
|
|
191
|
+
if (!isPlausiblePhone(phone))
|
|
192
|
+
return { success: false, error: '手机号格式不正确' };
|
|
193
|
+
const r = await lookupPhone(phone);
|
|
194
|
+
if (!r.found)
|
|
195
|
+
return { success: true, found: false, note: '未找到 (对方未注册目录或已撤销)' };
|
|
196
|
+
if (!r.publicKeyHex)
|
|
197
|
+
return { success: false, found: true, did: r.did, error: '对方已公开目录但未发布 P2P 公钥, 暂时无法直接发好友请求' };
|
|
198
|
+
try {
|
|
199
|
+
const port = process.env.PORT || '54188';
|
|
200
|
+
const resp = await fetch(`http://127.0.0.1:${port}/api/friend-request`, {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: { 'Content-Type': 'application/json' },
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
targetPublicKey: r.publicKeyHex,
|
|
205
|
+
name: text(args.name) || r.displayName || undefined,
|
|
206
|
+
message: text(args.message) || '来自手机号目录的好友申请',
|
|
207
|
+
note: 'via-phone-directory',
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
const data = await resp.json();
|
|
211
|
+
return resp.ok
|
|
212
|
+
? { success: true, found: true, did: r.did, requestId: data.requestId, output: `已向 ${r.displayName || r.did} 发起好友申请; 对方同意后才会建立好友关系` }
|
|
213
|
+
: { success: false, found: true, did: r.did, error: data.error || '好友申请发送失败', code: data.code };
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
return { success: false, error: `好友申请链路不可用: ${String(err?.message || err)}` };
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
ctx.tools.set('directory.retract', {
|
|
221
|
+
name: 'directory.retract',
|
|
222
|
+
description: '从本机目录撤销自己的手机号 → DID 指针 (软删)。用于: 用户说"不要让别人查到我手机号了"。',
|
|
223
|
+
parameters: {},
|
|
224
|
+
execute: async () => {
|
|
225
|
+
const { retractEntry } = await import('./human-directory.js');
|
|
226
|
+
const did = globalThis.__bolloonUserDid || 'did:bolln:local';
|
|
227
|
+
const ok = await retractEntry(did);
|
|
228
|
+
return { success: ok, note: ok ? '已从目录撤销 (手机号不再可查)' : '目录里没有你的条目' };
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
registered.push('directory.publish', 'directory.lookup', 'directory.add_friend', 'directory.retract');
|
|
153
232
|
return registered;
|
|
154
233
|
}
|
|
@@ -299,7 +299,7 @@ function impactFor(kind, scope) {
|
|
|
299
299
|
};
|
|
300
300
|
}
|
|
301
301
|
const EMPTY_IMPACT = (scope) => impactFor('clarification', scope);
|
|
302
|
-
function describeKind(kind,
|
|
302
|
+
function describeKind(kind, dir) {
|
|
303
303
|
switch (kind) {
|
|
304
304
|
case 'abort':
|
|
305
305
|
return `撤销/终止: 在下一个 Run 边界终止该目标 (优先级最高, 压过同批其余变更)`;
|
|
@@ -334,7 +334,6 @@ function describeKind(kind, r, dir) {
|
|
|
334
334
|
export function classifyChange(r, goal) {
|
|
335
335
|
if (TERMINAL_STATUSES.includes(r.status))
|
|
336
336
|
return { ...r, appliesToFutureRunsOnly: true };
|
|
337
|
-
const t = lower(r.instruction);
|
|
338
337
|
const criteria = Array.isArray(goal?.successCriteria) ? goal.successCriteria.map(String) : [];
|
|
339
338
|
let intents = detectChangeIntents(r.instruction);
|
|
340
339
|
if (intents.includes('success_criteria_change') && isCriteriaRestatement(r.instruction, criteria)) {
|
|
@@ -362,7 +361,7 @@ export function classifyChange(r, goal) {
|
|
|
362
361
|
status: 'triaged',
|
|
363
362
|
requiresReplan: false,
|
|
364
363
|
impact: impactFor('abort', scope),
|
|
365
|
-
interpreted: `${describeKind('abort',
|
|
364
|
+
interpreted: `${describeKind('abort', null)}; 原文: ${head(r.instruction)}` +
|
|
366
365
|
(superseded.length ? `; 同批的 ${superseded.join('/')} 一律作废 (superseded), 需单独重新提交` : ''),
|
|
367
366
|
};
|
|
368
367
|
}
|
|
@@ -386,7 +385,7 @@ export function classifyChange(r, goal) {
|
|
|
386
385
|
status: 'triaged',
|
|
387
386
|
requiresReplan: REPLAN_KINDS.has(kind),
|
|
388
387
|
impact,
|
|
389
|
-
interpreted: `${KIND_LABELS[kind]}: ${describeKind(kind,
|
|
388
|
+
interpreted: `${KIND_LABELS[kind]}: ${describeKind(kind, dir)}; 影响面: ${impactLabel(impact)}; 原文: ${head(r.instruction)}`,
|
|
390
389
|
};
|
|
391
390
|
}
|
|
392
391
|
function impactLabel(i) {
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
import * as fs from 'fs/promises';
|
|
29
29
|
import * as os from 'os';
|
|
30
30
|
import * as path from 'path';
|
|
31
|
-
import * as crypto from 'crypto';
|
|
32
31
|
import { sessionStore } from './session-store.js';
|
|
33
32
|
import * as taskState from './task-state.js';
|
|
34
33
|
import { onGoalParked, onGoalResumed } from '../bootstrap/lifecycle-hooks.js';
|
|
@@ -88,9 +87,6 @@ async function loadAllSnapshots() {
|
|
|
88
87
|
}
|
|
89
88
|
return out;
|
|
90
89
|
}
|
|
91
|
-
function newGoalId() {
|
|
92
|
-
return `goal-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
93
|
-
}
|
|
94
90
|
// ============================================================
|
|
95
91
|
// Park 现有 task (复用 task-state.ts 'paused' 状态)
|
|
96
92
|
// ============================================================
|
|
@@ -60,7 +60,7 @@ export function intentHint(intent) {
|
|
|
60
60
|
2. 用 \`edit_file\` 精确定位改 (old_text 全文匹配) 或 \`write_file\` 整体覆写
|
|
61
61
|
3. 改完跑 \`npx tsc --noEmit\` 和 \`npx vitest run --reporter=dot --bail=1\` 验证
|
|
62
62
|
4. 用 \`git_commit\` 提交到当前分支 (分支名 agent/<task-id>)
|
|
63
|
-
|
|
63
|
+
不要提前收尾: 任务真正完成 (commit + 测试通过) 之前持续调用工具推进.
|
|
64
64
|
|
|
65
65
|
## 信息不足时主动查询
|
|
66
66
|
**重要**: 不要假设! 如果任何步骤信息不足 (路径不明确/命令参数不知道/分支策略不清楚/保护规则不明白), 用工具主动查:
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 迭代预算 (2026-10-01 落实第二轮: 学的是"**可退还**的迭代预算"那套)。
|
|
3
|
+
*
|
|
4
|
+
* 意图: 惩罚**零碎**调用, 奖励**批处理** —— 一次调用干多件事(程序化工具调用)不该吃预算。
|
|
5
|
+
* 用户的抱怨正是"一件小事花太多次工具调用" ⇒ 把预算变成**激励**而不是单纯的刹车。
|
|
6
|
+
* 设计对齐 (只搬约束, 不搬实现):
|
|
7
|
+
* · `consume()` 扣一次; **`refund()` 还一次** —— 批处理工具(见 REFUNDABLE_TOOLS)调用后归还;
|
|
8
|
+
* · 每个 agent 一份(父/子各自的 cap 不同);
|
|
9
|
+
* · 阈值**警告**(默认 80%) ⇒ 提前提示, 而不是到顶了才硬停;
|
|
10
|
+
* · 非法配置 **fail-open**(坏值当关闭, 绝不因配置坏掉就把 agent 卡死)。
|
|
11
|
+
* 纪律: 这是**记账 + 激励**, 不做硬刹车 —— 循环退出条件仍由调用方决定(它读 effective 值)。
|
|
12
|
+
*/
|
|
1
13
|
/**
|
|
2
14
|
* 这些工具"一次调用可顶多次操作" ⇒ 用它换来的迭代要**退还**。
|
|
3
15
|
* 加新工具时问自己: 它能不能在一次调用里做完 N 件事? 能 ⇒ 加进来。
|
|
@@ -419,7 +419,6 @@ export async function maintainGoals(home, llm) {
|
|
|
419
419
|
const queue = (await readJson(path.join(goalsDir, 'queue.json'))) || {};
|
|
420
420
|
// queue.json 结构未知, 兼容 object/array
|
|
421
421
|
const queueItems = Array.isArray(queue) ? queue : Object.values(queue).filter((v) => v && typeof v === 'object');
|
|
422
|
-
const events = await readDir(goalsDir);
|
|
423
422
|
const now = new Date().toISOString().slice(0, 10);
|
|
424
423
|
const currentDir = path.join(home, '.bolloon', 'context-os', '03-Current');
|
|
425
424
|
const currentFiles = (await listMd(currentDir)).filter((f) => f.file !== 'README.md');
|
|
@@ -453,7 +452,6 @@ export async function maintainGoals(home, llm) {
|
|
|
453
452
|
/** 自动整理心跳 · 知识层: 串行跑全部 9 个整理器, 汇总 sections */
|
|
454
453
|
export async function runKnowledgeOrganize(opts = {}) {
|
|
455
454
|
const home = opts.home || os.homedir();
|
|
456
|
-
const cwd = opts.cwd || process.cwd();
|
|
457
455
|
const llm = opts.llm;
|
|
458
456
|
const sections = [];
|
|
459
457
|
sections.push(await archiveContextOs(home));
|
|
@@ -198,7 +198,6 @@ export function routeLessonToSkill(lesson, skills, home = os.homedir()) {
|
|
|
198
198
|
// 命中了但写不进去(文件读不到/空) ⇒ 退到沉淀技能, 保证"都进去" ✓
|
|
199
199
|
}
|
|
200
200
|
const sink = ensureSinkSkill(home);
|
|
201
|
-
const body = fs.readFileSync(sink.file, 'utf-8');
|
|
202
201
|
const withCandidates = d.suggestions.length
|
|
203
202
|
? { ...lesson, body: `${lesson.body} (候选技能: ${d.suggestions.map((s) => `${s.name}(${s.score})`).join(', ')})` }
|
|
204
203
|
: lesson;
|
|
@@ -61,9 +61,9 @@ export function buildReviewHint(state, maxReviews = DEFAULT_MAX_REVIEWS) {
|
|
|
61
61
|
`\n用户需求: ${intent.slice(0, 300)}` +
|
|
62
62
|
actionLines +
|
|
63
63
|
`\n已完成工具: ${tools}` +
|
|
64
|
-
`\n[完成契约]
|
|
64
|
+
`\n[完成契约] 宣布完成前必须展示具体证据 (命令输出 / 文件内容 / 测试结果), 只写结论不算完成.` +
|
|
65
65
|
`\n请对照需求逐条自查: 用户需求是否每一项都真正完成了? 如果还有未完成的子目标或自然衔接的后续步骤 → 继续调用工具推进 (已完成动作不要重复, 直接基于已有结果做下一步); ` +
|
|
66
|
-
`如果用户需求已全部满足 →
|
|
66
|
+
`如果用户需求已全部满足 → 直接给出完整最终回答结束.` +
|
|
67
67
|
`\n[重要] 不要因为做了一部分就提前结束 — 结束前逐条对照「用户需求」确认每一项都完成.`);
|
|
68
68
|
}
|
|
69
69
|
/** 布尔门: 是否该继续 review (测试/消融快速断言) */
|