@bolloon/bolloon-agent 0.6.0 → 0.6.1
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/execution-entry.js +10 -0
- package/dist/agents/goal-flywheel/wiring/seams.js +5 -1
- package/dist/agents/pi-sdk-tools.js +18 -8
- package/dist/agents/pi-sdk.js +472 -1263
- package/dist/agents/runner-resolver.js +2 -1
- package/dist/agents/tool-capability/index.js +51 -0
- package/dist/agents/workflow-pivot-loop.js +31 -10
- package/dist/agents/write-verify.js +60 -0
- package/dist/bollharness-integration/integration.js +8 -0
- package/dist/bollharness-integration/skill-adapter.js +32 -3
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js +5 -1
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +21 -11
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +473 -1262
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/runner-resolver.js +2 -1
- package/dist/electron-build/agents/runner-resolver.js.map +1 -1
- package/dist/electron-build/agents/tool-capability/index.js +90 -0
- package/dist/electron-build/agents/tool-capability/index.js.map +1 -0
- package/dist/electron-build/agents/workflow-pivot-loop.js +31 -10
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/agents/write-verify.js +99 -0
- package/dist/electron-build/agents/write-verify.js.map +1 -0
- package/dist/electron-build/bollharness-integration/integration.js +8 -0
- package/dist/electron-build/bollharness-integration/integration.js.map +1 -1
- package/dist/electron-build/bollharness-integration/skill-adapter.js +32 -3
- package/dist/electron-build/bollharness-integration/skill-adapter.js.map +1 -1
- package/dist/electron-build/kernel/channel-actor.js +25 -0
- package/dist/electron-build/kernel/channel-actor.js.map +1 -1
- package/dist/electron-build/kernel/control.js +102 -0
- package/dist/electron-build/kernel/control.js.map +1 -0
- package/dist/electron-build/kernel/model-runtime.js +508 -0
- package/dist/electron-build/kernel/model-runtime.js.map +1 -0
- package/dist/electron-build/kernel/run-lifecycle.js +146 -0
- package/dist/electron-build/kernel/run-lifecycle.js.map +1 -0
- package/dist/electron-build/kernel/transport.js +119 -0
- package/dist/electron-build/kernel/transport.js.map +1 -0
- package/dist/electron-build/llm/native-adapter.js +122 -0
- package/dist/electron-build/llm/native-adapter.js.map +1 -0
- package/dist/index.js +19 -0
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +13 -7
- package/dist/ios/mobile-core.js +7762 -6289
- package/dist/ios/mobile.js +64 -0
- package/dist/ios/routes-mobile-tasks.js +2 -1
- package/dist/ios/routes-tasks.js +16 -1
- package/dist/ios/routes-x402-info.js +28 -1
- package/dist/ios/server-storage.js +26 -5
- package/dist/ios/server.js +840 -862
- package/dist/ios/sw.js +1 -1
- package/dist/ios/ui/message-renderer.js +473 -29
- package/dist/ios/ui/step-timeline.js +13 -12
- package/dist/kernel/channel-actor.js +24 -0
- package/dist/kernel/gate-scan.js +318 -4
- package/dist/kernel/model-runtime.js +22 -0
- package/dist/kernel/plan-channel-actor.js +12 -5
- package/dist/kernel/plan-communication.js +163 -0
- package/dist/kernel/plan-constraint.js +2 -15
- package/dist/kernel/plan-deletion.js +1 -1
- package/dist/kernel/plan-harness.js +78 -0
- package/dist/kernel/plan.js +1 -5
- package/dist/kernel/roster.js +13 -11
- package/dist/kernel/run-lifecycle.js +138 -0
- package/dist/kernel/transport.js +111 -0
- package/dist/llm/native-adapter.js +117 -0
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +9 -5
- package/dist/web/routes-tasks.js +12 -1
- package/dist/web/server.js +802 -866
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
package/dist/ios/mobile.js
CHANGED
|
@@ -225,6 +225,68 @@
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// ── Efficode 条目 (内置参考实现: 仓内 src/efficode/**, 不靠"从电脑端同步") ──────
|
|
229
|
+
// 纪律: 这段**不用 innerHTML 拼任何数据** (纯 createElement + textContent) ·
|
|
230
|
+
// 双语走 data-zh/data-en (切语言时外面那段 applyLang 会一起改文字节点) ·
|
|
231
|
+
// role=button + tabindex + aria-label + Enter/Space 可达 · 无外部依赖。
|
|
232
|
+
const EFFICODE_SPEC_PATH = 'docs/wiki/efficode.md';
|
|
233
|
+
const EFFICODE_IMPL_PATH = 'src/efficode/** · src/agents/agent-lang.ts';
|
|
234
|
+
const EFFICODE_GATE = 'scripts/verify-efficode.ts (55/0)';
|
|
235
|
+
function effiLangNow() {
|
|
236
|
+
try { return localStorage.getItem('bolloon_lang') === 'en' ? 'en' : 'zh'; } catch (e) { return 'zh'; }
|
|
237
|
+
}
|
|
238
|
+
function buildEfficodeEntry() {
|
|
239
|
+
const el = document.createElement('div');
|
|
240
|
+
el.className = 'conv-item';
|
|
241
|
+
el.id = 'item-efficode';
|
|
242
|
+
el.setAttribute('role', 'button');
|
|
243
|
+
el.setAttribute('tabindex', '0');
|
|
244
|
+
el.setAttribute('aria-label', 'Efficode 规范页 (中文): ' + EFFICODE_SPEC_PATH);
|
|
245
|
+
const avatar = document.createElement('div');
|
|
246
|
+
avatar.className = 'conv-avatar';
|
|
247
|
+
avatar.textContent = 'E';
|
|
248
|
+
const body = document.createElement('div');
|
|
249
|
+
body.className = 'conv-body';
|
|
250
|
+
const name = document.createElement('div');
|
|
251
|
+
name.className = 'conv-name';
|
|
252
|
+
name.textContent = '/efficode';
|
|
253
|
+
const desc = document.createElement('div');
|
|
254
|
+
desc.className = 'conv-preview';
|
|
255
|
+
desc.setAttribute('data-zh', '智能体间的符号化交流语言 · 参考实现 (签名/声波未实现) — 点开看规范页 docs/wiki/efficode.md');
|
|
256
|
+
desc.setAttribute('data-en', 'Symbolic agent-to-agent language · reference impl (signing/audio not implemented) — tap for docs/wiki/efficode.md');
|
|
257
|
+
desc.textContent = effiLangNow() === 'en' ? desc.getAttribute('data-en') : desc.getAttribute('data-zh');
|
|
258
|
+
body.appendChild(name);
|
|
259
|
+
body.appendChild(desc);
|
|
260
|
+
el.appendChild(avatar);
|
|
261
|
+
el.appendChild(body);
|
|
262
|
+
const open = () => alert(
|
|
263
|
+
'/efficode\n\n' +
|
|
264
|
+
(effiLangNow() === 'en'
|
|
265
|
+
? 'Symbolic binary language between agents (Efficient + Code). Reference implementation in this repo; negotiation falls back to natural language unless both sides declare efficode. Signing / ECC session keys / sound-wave mode are NOT implemented.\n\n'
|
|
266
|
+
: '智能体之间的符号化二进制交流语言 (Efficient + Code)。本仓有参考实现; 只有双方都声明 efficode 才用, 否则明确回落自然语言。签名验证 / ECC 临时会话密钥 / 声波模式尚未实现。\n\n') +
|
|
267
|
+
'规范页: ' + EFFICODE_SPEC_PATH + '\n' +
|
|
268
|
+
'实现: ' + EFFICODE_IMPL_PATH + '\n' +
|
|
269
|
+
'验收门: ' + EFFICODE_GATE
|
|
270
|
+
);
|
|
271
|
+
el.addEventListener('click', open);
|
|
272
|
+
el.addEventListener('keydown', (ev) => {
|
|
273
|
+
if (ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar') { ev.preventDefault(); open(); }
|
|
274
|
+
});
|
|
275
|
+
return el;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** 把内置 Efficode 条目追加到 Skills 页 (空态 / 有列表都显示 —— 它是仓内参考实现) */
|
|
279
|
+
function appendEfficodeBlock(body) {
|
|
280
|
+
if (!body || body.querySelector('#item-efficode')) return;
|
|
281
|
+
const cap = document.createElement('div');
|
|
282
|
+
cap.style.cssText = 'font-size:11px;color:var(--text-muted);margin:14px 0 6px';
|
|
283
|
+
cap.setAttribute('data-zh', '内置 (随客户端发布, 不来自电脑端同步)');
|
|
284
|
+
cap.setAttribute('data-en', 'Built in (ships with the client, not synced from desktop)');
|
|
285
|
+
cap.textContent = effiLangNow() === 'en' ? cap.getAttribute('data-en') : cap.getAttribute('data-zh');
|
|
286
|
+
body.appendChild(cap);
|
|
287
|
+
body.appendChild(buildEfficodeEntry());
|
|
288
|
+
}
|
|
289
|
+
|
|
228
290
|
async function openSkillsPage() {
|
|
229
291
|
if ($('#skills-page')) return;
|
|
230
292
|
const page = mkOverlayPage('skills-page', 'Skills', '<div style="font-size:12px;color:var(--text-muted)">读取中…</div>');
|
|
@@ -241,6 +303,7 @@
|
|
|
241
303
|
body.innerHTML = `<div style="padding:18px 8px;text-align:center;color:var(--text-muted);line-height:1.8">
|
|
242
304
|
还没有技能列表<br><span style="font-size:12px">电脑端 ~/.bolloon/skills/ 下的 skills,会在「从电脑端同步」后出现在这里</span>
|
|
243
305
|
${err ? `<div style="font-size:11px;margin-top:8px">(读取失败:${escapeHtml(err)})</div>` : ''}</div>`;
|
|
306
|
+
appendEfficodeBlock(body);
|
|
244
307
|
return;
|
|
245
308
|
}
|
|
246
309
|
body.innerHTML = skills.map((s, i) => `<div class="conv-item" data-sk="${i}">
|
|
@@ -252,6 +315,7 @@
|
|
|
252
315
|
const s = skills[Number(el.dataset.sk)] || {};
|
|
253
316
|
alert(`/${s.name || ''}\n\n${s.description || '(无说明)'}`);
|
|
254
317
|
}));
|
|
318
|
+
appendEfficodeBlock(body);
|
|
255
319
|
};
|
|
256
320
|
await render();
|
|
257
321
|
const btn = page.querySelector('#skills-sync');
|
|
@@ -167,7 +167,8 @@ export function registerMobileTaskRoutes(app, opts = {}) {
|
|
|
167
167
|
if (kind === 'group_create' || kind === 'group_join' || kind === 'group_leave') {
|
|
168
168
|
const { createGroup, joinGroup, leaveGroup } = await import('../agents/gateway-group.js');
|
|
169
169
|
if (kind === 'group_create') {
|
|
170
|
-
|
|
170
|
+
// 手机端建群与 Web 端同一语义 (微信式群聊) ⇒ 显式声明开放写入
|
|
171
|
+
const r = await createGroup(String(request.groupRef || '').trim(), { acl: 'open' });
|
|
171
172
|
if (!r.ok || !r.group)
|
|
172
173
|
return fail(res, 409, 'GROUP_CREATE_FAILED', `建群失败: ${r.error || '未知原因'}`);
|
|
173
174
|
return res.json({ ok: true, kind, group: { id: r.group.id, name: r.group.name }, text: `已建群「${r.group.name}」` });
|
package/dist/ios/routes-tasks.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 从 src/web/server.ts 抽出 (~160 行).
|
|
5
5
|
* 包含 /api/tasks/* (GET, POST, PATCH, DELETE, execute, execute-next)
|
|
6
6
|
*/
|
|
7
|
+
import { deliverThroughActor } from '../kernel/channel-actor.js';
|
|
7
8
|
import { loadTaskQueue, saveTaskQueue } from './server-storage.js';
|
|
8
9
|
import { documentReader } from '../documents/reader.js';
|
|
9
10
|
import { applyCancelRequest, shouldFinalizeAsCancelled } from './task-cancel.js';
|
|
@@ -309,7 +310,21 @@ async function executeTask(task, channelId, getAgentForChannel, broadcast) {
|
|
|
309
310
|
case 'chat':
|
|
310
311
|
if (task.description) {
|
|
311
312
|
broadcast({ type: 'status', content: `执行任务: ${task.title}` }, channelId);
|
|
312
|
-
|
|
313
|
+
// 闭包里 TS 不保留收窄 ⇒ 先收成局部 const
|
|
314
|
+
const desc = task.description;
|
|
315
|
+
const agentForRun = agent;
|
|
316
|
+
// 2026-10-02 (K8): 走 Pi 的**唯一执行入口** `runExecution` (K5 步骤⑦ 已建), 不再由通道自己直呼 prompt
|
|
317
|
+
if (typeof agentForRun.runExecution !== 'function') {
|
|
318
|
+
// K8 纪律: 拿不到唯一执行入口 ⇒ **响亮失败**, 不静默回落直呼 prompt (否则收口变成纸面的)
|
|
319
|
+
throw new Error('K8: 该 session 未提供 runExecution (唯一执行入口) ⇒ 拒绝直呼 prompt');
|
|
320
|
+
}
|
|
321
|
+
// 注意: 这里**显式传 channelId** —— ExecutionRequest 要求它, 且语义上本就该绑定 task 的通道。
|
|
322
|
+
// 与旧写法 (prompt(desc), 隐式走 actor 绑定) 相比这是一处**行为差量**: 绑定从隐式变显式。
|
|
323
|
+
// 按纪律记过"待真跑核验", **2026-10-02 已核验通过** (`src/test/k8-routes-tasks-execution.test.ts`,
|
|
324
|
+
// 真 express + 真 HTTP + 真任务队列): ① agent 只给 runExecution (无 prompt) 也能跑通 ⇒ 活路径唯一;
|
|
325
|
+
// ② **两半 id 一致** (解析 agent 用的 id == 绑 run 用的 id) 是自检不变量; ③ 无门时响亮失败且 prompt 调用 0 次。
|
|
326
|
+
// 真变异: 绑成 `channelId + '-mut'` ⇒ 判红; 回落直呼 `prompt` ⇒ 判红; 还原 ⇒ 回绿无残差。
|
|
327
|
+
result = await deliverThroughActor(agentForRun, () => agentForRun.runExecution({ input: desc, channelId }));
|
|
313
328
|
}
|
|
314
329
|
break;
|
|
315
330
|
case 'read':
|
|
@@ -20,6 +20,7 @@ import * as os from 'os';
|
|
|
20
20
|
import { listInfo, getStoredInfo, publishInfo, removeInfo, buildPaymentRequired, checkAndSettlePayment, buyInfo, } from '../agents/x402/paid-info-store.js';
|
|
21
21
|
import { buildSignedEnvelope, verifyEnvelope, summarizeVerify } from '../agents/x402/paid-info-protocol.js';
|
|
22
22
|
import { makeDidResolver } from '../agents/x402/paid-info-tools.js';
|
|
23
|
+
import { handleDirectPayment, directHealth, directPaymentPath, directPaymentEnabled } from '../agents/x402/direct-payment.js';
|
|
23
24
|
/** 本机 DIAP 身份 (签发信封用) — 缺失时明确报错, 不静默造一个 */
|
|
24
25
|
async function loadProviderKeypair() {
|
|
25
26
|
try {
|
|
@@ -122,7 +123,7 @@ export function registerX402InfoRoutes(app) {
|
|
|
122
123
|
content: stored.content,
|
|
123
124
|
keypair: kp,
|
|
124
125
|
payment: {
|
|
125
|
-
mode: pay.mode === 'facilitator' ? 'facilitator' : 'local-dev',
|
|
126
|
+
mode: pay.mode === 'facilitator' ? 'facilitator' : pay.mode === 'direct' ? 'direct' : 'local-dev',
|
|
126
127
|
receipt: String(pay.receipt || ''),
|
|
127
128
|
txHash: pay.txHash,
|
|
128
129
|
network: pay.network || stored.item.price.network,
|
|
@@ -139,6 +140,32 @@ export function registerX402InfoRoutes(app) {
|
|
|
139
140
|
res.status(500).json({ error: e?.message });
|
|
140
141
|
}
|
|
141
142
|
});
|
|
143
|
+
// ---- 去中心化直付 (mode = 'direct'): 买方自己发 USDC, 把 txHash 交回来 ----
|
|
144
|
+
// POST /api/x402/info/:id/payment body: { "txHash": "0x…" }
|
|
145
|
+
// 服务器**只读链**: receipt.status=1 + USDC Transfer → payTo + value >= accepts.amount
|
|
146
|
+
// + 两条不同 RPC 交叉一致 + 确认数够 → 落待办 + 给取件 token (202); 未过 → 402 (accepts 逐字)
|
|
147
|
+
app.post('/api/x402/info/:id/payment', async (req, res) => {
|
|
148
|
+
try {
|
|
149
|
+
const id = String(req.params.id);
|
|
150
|
+
const stored = await getStoredInfo(id);
|
|
151
|
+
if (!stored)
|
|
152
|
+
return res.status(404).json({ error: '信息不存在' });
|
|
153
|
+
const baseUrl = `${req.protocol || 'http'}://${req.headers?.host || `127.0.0.1:${process.env.PORT || 54188}`}`;
|
|
154
|
+
const out = await handleDirectPayment({
|
|
155
|
+
item: stored.item,
|
|
156
|
+
bodyText: typeof req.body === 'string' ? req.body : JSON.stringify(req.body ?? {}),
|
|
157
|
+
resourceUrl: `${baseUrl}${directPaymentPath(stored.item.id)}`,
|
|
158
|
+
});
|
|
159
|
+
return res.status(out.status).set(out.headers).send(out.body);
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
res.status(500).json({ error: e?.message });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
// ---- 直付模式自述 (health 用: 开关与口径必须能从机器读到) ----
|
|
166
|
+
app.get('/api/x402/direct', async (_req, res) => {
|
|
167
|
+
res.json({ enabled: directPaymentEnabled(), paymentPath: directPaymentPath(':id'), ...directHealth() });
|
|
168
|
+
});
|
|
142
169
|
// ---- 买方辅助: 代付 + 验真 ----
|
|
143
170
|
app.post('/api/x402/info/buy', async (req, res) => {
|
|
144
171
|
try {
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* Channel / Session / Task / SessionMessage / SessionSummary type 来自 ./server-types.ts.
|
|
12
12
|
*/
|
|
13
13
|
import * as fs from 'fs/promises';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { channelsPaths, assertTestWriteTarget } from '../agents/agent-identity-store.js';
|
|
14
16
|
import { CHANNELS_PATH, SESSION_CACHE_PATH, TASK_QUEUE_PATH, THEME_PATH, } from './server-types.js';
|
|
15
17
|
import { saveWindow as saveSessionWindow, loadWindow as loadSessionWindow } from '../bootstrap/session-window.js';
|
|
16
18
|
import { parentsSatisfied } from './task-deps.js';
|
|
@@ -42,7 +44,7 @@ export async function updateChannels(fn) {
|
|
|
42
44
|
}
|
|
43
45
|
async function rawLoadChannels() {
|
|
44
46
|
try {
|
|
45
|
-
const data = await fs.readFile(
|
|
47
|
+
const data = await fs.readFile(channelsPathNow(), 'utf-8');
|
|
46
48
|
return JSON.parse(data);
|
|
47
49
|
}
|
|
48
50
|
catch (readErr) {
|
|
@@ -50,11 +52,11 @@ async function rawLoadChannels() {
|
|
|
50
52
|
if (readErr?.code !== 'ENOENT') {
|
|
51
53
|
console.warn('[loadChannels] channels.json 解析失败, 尝试从 .tmp 恢复:', readErr?.message?.slice(0, 80));
|
|
52
54
|
try {
|
|
53
|
-
const tmpData = await fs.readFile(
|
|
55
|
+
const tmpData = await fs.readFile(channelsPathNow() + '.tmp', 'utf-8');
|
|
54
56
|
const recovered = JSON.parse(tmpData);
|
|
55
57
|
console.log(`[loadChannels] 从 .tmp 恢复成功: ${recovered.length} 个 channel`);
|
|
56
58
|
// 立即把恢复的内容写回主文件
|
|
57
|
-
await fs.writeFile(
|
|
59
|
+
await fs.writeFile(channelsPathNow(), tmpData, 'utf-8');
|
|
58
60
|
return recovered;
|
|
59
61
|
}
|
|
60
62
|
catch (tmpErr) {
|
|
@@ -64,6 +66,22 @@ async function rawLoadChannels() {
|
|
|
64
66
|
return [];
|
|
65
67
|
}
|
|
66
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* channels.json 的路径**每次调用时**解析 (2026-10-01, 数据事故修复)。
|
|
71
|
+
*
|
|
72
|
+
* 事故: `server-types.CHANNELS_PATH` 是 import 时定死的常量 (按那一刻的 HOME 解析),
|
|
73
|
+
* 于是测试里后设的 process.env.HOME **完全不起作用** ⇒ 测试夹具把用户的真实
|
|
74
|
+
* ~/.bolloon/sessions/channels.json 整表覆盖 (4 个智能体只剩 1 条)。
|
|
75
|
+
* 现在改成按**当前** HOME 解析 (与 CLI 侧 agent-identity-store.channelsPaths 同一口径)。
|
|
76
|
+
*/
|
|
77
|
+
function channelsPathNow() {
|
|
78
|
+
try {
|
|
79
|
+
return channelsPaths()[0];
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return CHANNELS_PATH;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
67
85
|
async function rawSaveChannels(channels) {
|
|
68
86
|
const sanitized = channels.map(ch => {
|
|
69
87
|
const { didDocument: _omit, ...rest } = ch;
|
|
@@ -75,9 +93,12 @@ async function rawSaveChannels(channels) {
|
|
|
75
93
|
lastChannelsJson = jsonStr;
|
|
76
94
|
console.log('[saveChannels] 保存频道数据, 数量:', sanitized.length);
|
|
77
95
|
// 2026-07-24: 原子写入 — 先写 .tmp 再 rename, 防止崩溃导致 channels.json 损坏
|
|
78
|
-
const
|
|
96
|
+
const target = channelsPathNow();
|
|
97
|
+
// 测试进程只允许写临时目录 —— 真实数据目录一律拒绝 (第二道闸)
|
|
98
|
+
assertTestWriteTarget(path.dirname(path.dirname(target)), 'channels.json');
|
|
99
|
+
const tmpPath = target + '.tmp';
|
|
79
100
|
await fs.writeFile(tmpPath, jsonStr, 'utf-8');
|
|
80
|
-
await fs.rename(tmpPath,
|
|
101
|
+
await fs.rename(tmpPath, target);
|
|
81
102
|
lastChannelsWriteAt = Date.now();
|
|
82
103
|
}
|
|
83
104
|
export async function loadChannels() {
|