@bolloon/bolloon-agent 0.4.33 → 0.5.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/contacts/chain.js +27 -7
- package/dist/agents/execution-supervisor.js +580 -40
- package/dist/agents/external-events.js +35 -6
- package/dist/agents/gateway-group.js +100 -12
- package/dist/agents/goal-criteria.js +10 -3
- package/dist/agents/goal-flywheel/block-executor.js +346 -0
- package/dist/agents/goal-flywheel/continuation-decision.js +592 -0
- package/dist/agents/goal-flywheel/goal-change.js +754 -0
- package/dist/agents/goal-flywheel/index.js +26 -0
- package/dist/agents/goal-flywheel/memory-layers.js +227 -0
- package/dist/agents/goal-flywheel/run-closure.js +809 -0
- package/dist/agents/goal-flywheel/skill-candidate.js +628 -0
- package/dist/agents/goal-flywheel/types.js +329 -0
- package/dist/agents/goal-flywheel/wiring/change.js +288 -0
- package/dist/agents/goal-flywheel/wiring/closure.js +697 -0
- package/dist/agents/goal-flywheel/wiring/continuation.js +432 -0
- package/dist/agents/goal-flywheel/wiring/contract.js +270 -0
- package/dist/agents/goal-flywheel/wiring/index.js +26 -0
- package/dist/agents/goal-flywheel/wiring/monitor.js +254 -0
- package/dist/agents/goal-flywheel/wiring/seams.js +592 -0
- package/dist/agents/goal-flywheel/work-contract.js +478 -0
- package/dist/agents/goal-flywheel/work-monitor.js +488 -0
- package/dist/agents/goal-flywheel-wiring.js +1870 -0
- package/dist/agents/goal-state-reducer.js +406 -0
- package/dist/agents/goal-store.js +35 -1
- package/dist/agents/mobile-flywheel-view.js +144 -0
- package/dist/agents/mobile-task-actions-verify.js +74 -0
- package/dist/agents/mobile-task-actions.js +178 -0
- package/dist/agents/mobile-task-views.js +199 -0
- package/dist/agents/model-policy.js +651 -0
- package/dist/agents/network-pulse.js +304 -18
- package/dist/agents/pi-sdk-tools.js +84 -33
- package/dist/agents/pi-sdk.js +157 -42
- package/dist/agents/run-store.js +71 -5
- package/dist/agents/skill-readiness.js +52 -10
- package/dist/agents/subagent-manager.js +209 -3
- package/dist/agents/task/task-runner.js +17 -4
- package/dist/agents/task-group.js +8 -51
- package/dist/agents/task-public-text.js +89 -0
- package/dist/agents/workflow-pivot-loop.js +19 -12
- package/dist/cli/clipboard.js +65 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/tasks.js +350 -2
- package/dist/cli/identity-command.js +177 -0
- package/dist/cli/ink-app.js +145 -17
- package/dist/cli/log-gate.js +386 -0
- package/dist/cli/mention-data.js +2 -0
- package/dist/cli/model-selector.js +538 -0
- package/dist/cli/reply-hygiene.js +54 -0
- package/dist/cli/setup-wizard.js +757 -69
- package/dist/cli/startup-notice.js +160 -0
- package/dist/cli/stores.js +22 -1
- package/dist/cli/theme.js +49 -0
- package/dist/cli/tui-select.js +970 -0
- package/dist/cli/update-commands.js +150 -24
- package/dist/cli-entry.js +137 -20
- package/dist/electron-build/agents/contacts/chain.js +26 -6
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/execution-supervisor.js +578 -38
- package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
- package/dist/electron-build/agents/external-events.js +34 -5
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-criteria.js +10 -3
- package/dist/electron-build/agents/goal-criteria.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/continuation-decision.js +598 -0
- package/dist/electron-build/agents/goal-flywheel/continuation-decision.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +773 -0
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/memory-layers.js +269 -0
- package/dist/electron-build/agents/goal-flywheel/memory-layers.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/run-closure.js +818 -0
- package/dist/electron-build/agents/goal-flywheel/run-closure.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/skill-candidate.js +642 -0
- package/dist/electron-build/agents/goal-flywheel/skill-candidate.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/types.js +333 -0
- package/dist/electron-build/agents/goal-flywheel/types.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/change.js +294 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/change.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/closure.js +742 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/closure.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js +444 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/contract.js +279 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/contract.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/index.js +111 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/index.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js +264 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js +612 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/work-contract.js +488 -0
- package/dist/electron-build/agents/goal-flywheel/work-contract.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/work-monitor.js +501 -0
- package/dist/electron-build/agents/goal-flywheel/work-monitor.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel-wiring.js +1962 -0
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -0
- package/dist/electron-build/agents/goal-state-reducer.js +414 -0
- package/dist/electron-build/agents/goal-state-reducer.js.map +1 -0
- package/dist/electron-build/agents/goal-store.js +35 -1
- package/dist/electron-build/agents/goal-store.js.map +1 -1
- package/dist/electron-build/agents/model-policy.js +706 -0
- package/dist/electron-build/agents/model-policy.js.map +1 -0
- package/dist/electron-build/agents/network-pulse.js +307 -19
- package/dist/electron-build/agents/network-pulse.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +84 -33
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +156 -41
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/run-store.js +75 -6
- package/dist/electron-build/agents/run-store.js.map +1 -1
- package/dist/electron-build/agents/skill-readiness.js +52 -9
- package/dist/electron-build/agents/skill-readiness.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js +16 -3
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +19 -12
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +146 -6
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/connection-probe.js +764 -0
- package/dist/electron-build/llm/connection-probe.js.map +1 -0
- package/dist/electron-build/llm/custom-provider-store.js +272 -0
- package/dist/electron-build/llm/custom-provider-store.js.map +1 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js +23 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js.map +1 -0
- package/dist/electron-build/llm/model-catalog.js +778 -0
- package/dist/electron-build/llm/model-catalog.js.map +1 -0
- package/dist/electron-build/llm/model-discovery.js +1202 -0
- package/dist/electron-build/llm/model-discovery.js.map +1 -0
- package/dist/electron-build/llm/model-selection.js +1315 -0
- package/dist/electron-build/llm/model-selection.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +70 -11
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/provider-catalog.js +969 -0
- package/dist/electron-build/llm/provider-catalog.js.map +1 -0
- package/dist/electron-build/llm/provider-registry.js +950 -0
- package/dist/electron-build/llm/provider-registry.js.map +1 -0
- package/dist/electron-build/llm/tool-name.js +266 -0
- package/dist/electron-build/llm/tool-name.js.map +1 -0
- package/dist/electron-build/orbitdb/cid-database.js +83 -17
- package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
- package/dist/electron-build/orbitdb/ipfs-node.js +31 -2
- package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
- package/dist/electron-build/utils/dual-source-facts.js +236 -0
- package/dist/electron-build/utils/dual-source-facts.js.map +1 -0
- package/dist/electron-build/utils/dual-source.js +347 -0
- package/dist/electron-build/utils/dual-source.js.map +1 -0
- package/dist/electron-build/utils/update-manager.js +328 -104
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/update-state.js +9 -11
- package/dist/electron-build/utils/update-state.js.map +1 -1
- package/dist/electron-build/utils/version-identity.js +193 -0
- package/dist/electron-build/utils/version-identity.js.map +1 -0
- package/dist/electron-build/utils/version-info.js +57 -31
- package/dist/electron-build/utils/version-info.js.map +1 -1
- package/dist/index.js +385 -108
- package/dist/ios/bolloon-web.json +10 -0
- package/dist/ios/chain-explorer.js +617 -0
- package/dist/ios/explorer.html +163 -0
- package/dist/ios/index.html +96 -0
- package/dist/ios/mobile-agent.js +2 -2
- package/dist/ios/mobile-contacts.js +362 -0
- package/dist/ios/mobile-core.js +3102 -310
- package/dist/ios/mobile-privacy.js +185 -0
- package/dist/ios/mobile-tasks.js +339 -0
- package/dist/ios/mobile-update.js +1442 -0
- package/dist/ios/mobile.css +19 -0
- package/dist/ios/mobile.html +96 -0
- package/dist/ios/mobile.js +1013 -41
- package/dist/ios/routes-chain-index.js +108 -0
- package/dist/ios/routes-contacts.js +420 -0
- package/dist/ios/routes-llm-config.js +344 -52
- package/dist/ios/routes-mobile-tasks.js +353 -0
- package/dist/ios/routes-onchain-trade.js +99 -0
- package/dist/ios/routes-x402-info.js +1 -0
- package/dist/ios/server.js +921 -0
- package/dist/ios/sw.js +1 -1
- package/dist/ios/task-frame-server.js +211 -0
- package/dist/llm/config-store.js +146 -6
- package/dist/llm/connection-probe.js +744 -0
- package/dist/llm/custom-provider-store.js +224 -0
- package/dist/llm/data/provider-catalog-baked.js +19 -0
- package/dist/llm/model-catalog.js +717 -0
- package/dist/llm/model-discovery.js +1137 -0
- package/dist/llm/model-selection.js +1241 -0
- package/dist/llm/pi-ai.js +70 -11
- package/dist/llm/provider-catalog.js +898 -0
- package/dist/llm/provider-registry.js +917 -0
- package/dist/llm/tool-name.js +220 -0
- package/dist/orbitdb/cid-database.js +81 -16
- package/dist/orbitdb/ipfs-node.js +31 -2
- package/dist/setup/onboard.js +34 -8
- package/dist/utils/dual-source-facts.js +223 -0
- package/dist/utils/dual-source.js +297 -0
- package/dist/utils/update-cli.js +16 -21
- package/dist/utils/update-health.js +35 -5
- package/dist/utils/update-manager.js +325 -96
- package/dist/utils/update-state.js +13 -10
- package/dist/utils/version-identity.js +176 -0
- package/dist/utils/version-info.js +27 -22
- package/dist/web/bolloon-web.json +10 -0
- package/dist/web/mobile-contacts.js +44 -11
- package/dist/web/mobile-core.js +2901 -580
- package/dist/web/mobile-tasks.js +339 -0
- package/dist/web/mobile-update.js +1442 -0
- package/dist/web/mobile.css +19 -0
- package/dist/web/mobile.html +64 -0
- package/dist/web/mobile.js +838 -35
- package/dist/web/routes-llm-config.js +344 -52
- package/dist/web/routes-mobile-tasks.js +353 -0
- package/dist/web/server.js +163 -4
- package/dist/web/sw.js +1 -1
- package/package.json +3 -1
- package/scripts/build-web.ts +36 -0
package/dist/ios/sw.js
CHANGED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-frame-server.ts — bolloon-task/1 任务帧接收端 (P3, 2026-09-21)
|
|
3
|
+
*
|
|
4
|
+
* 把 `task-transport` 发来的帧 (request / accept / reject / result) 接到**本机收件箱与台账**:
|
|
5
|
+
* · 每个信封都**必须过验签** (`verifyTaskEnvelope` + 帧里带来的公钥), 验不过 → 如实拒, 不落盘;
|
|
6
|
+
* · `task_request` 只落**待处理收件箱** (`~/.bolloon/tasks/inbox/`): 不执行、不付款、不自动接单;
|
|
7
|
+
* · 状态推进一律走 `task-contract.checkTaskMove` (非法迁移拒绝, 不静默修正);
|
|
8
|
+
* · 与 `agent-delegate-server` 同一套 express + JSON 形状 (薄; 不含业务判断)。
|
|
9
|
+
*
|
|
10
|
+
* 起法 (E2E / 长跑节点):
|
|
11
|
+
* npx tsx scripts/task-frame-node.ts --port 54901 --home /tmp/nodeA
|
|
12
|
+
*
|
|
13
|
+
* 安全口径 (如实): 收帧端点**不设身份认证** —— 它就是一个"信箱": 收到的东西一律先验签, 再只能落
|
|
14
|
+
* 待处理收件箱 (没有任何执行/付款副作用)。要不要接单是**本机用户**用 `bolloon task accept` 决定的事。
|
|
15
|
+
*/
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import { validateTaskRequest, verifyTaskEnvelope, checkTaskMove, } from '../agents/task-contract.js';
|
|
18
|
+
import { verifierFor } from '../agents/local-signer.js';
|
|
19
|
+
import { saveIncomingRequest, readInboxItem, patchInboxItem, readLocalTask, patchLocalTask, saveLocalTask, saveResult, saveBody, listInbox, } from '../agents/task-inbox.js';
|
|
20
|
+
import { parseTaskFrame } from '../agents/task-transport.js';
|
|
21
|
+
/** 校验一帧的信封签名 (帧里带的是公钥 hex; 这是公开材料, 不涉及任何私钥) */
|
|
22
|
+
async function verifyFrame(frame) {
|
|
23
|
+
const v = verifierFor(frame.signer?.publicKeyHex || '');
|
|
24
|
+
if (!v)
|
|
25
|
+
return false;
|
|
26
|
+
return await verifyTaskEnvelope(frame.envelope, v);
|
|
27
|
+
}
|
|
28
|
+
/** 按 14 态状态机逐步推进 (非法就停, 返回停在哪一步) */
|
|
29
|
+
function advance(state, steps) {
|
|
30
|
+
let cur = state;
|
|
31
|
+
const applied = [];
|
|
32
|
+
for (const to of steps) {
|
|
33
|
+
if (cur === to)
|
|
34
|
+
continue;
|
|
35
|
+
const mv = checkTaskMove(cur, to);
|
|
36
|
+
if (!mv.ok)
|
|
37
|
+
return { state: cur, applied, rejected: mv.reason };
|
|
38
|
+
cur = to;
|
|
39
|
+
applied.push(to);
|
|
40
|
+
}
|
|
41
|
+
return { state: cur, applied };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 处理一帧 —— 唯一入口 (HTTP / iroh / 测试都走这里, 语义只有一套)。
|
|
45
|
+
*/
|
|
46
|
+
export async function handleTaskFrame(frame, opts = {}) {
|
|
47
|
+
const home = opts.home;
|
|
48
|
+
const env = frame.envelope;
|
|
49
|
+
const verified = await verifyFrame(frame);
|
|
50
|
+
switch (frame.frame) {
|
|
51
|
+
case 'task_request': {
|
|
52
|
+
if (!verified) {
|
|
53
|
+
return { ok: false, error: 'SIGNATURE_INVALID', detail: '任务请求信封验签不过 (用帧里 signer.publicKeyHex 验的), 不落盘' };
|
|
54
|
+
}
|
|
55
|
+
const req = env;
|
|
56
|
+
const v = validateTaskRequest(req, { now: Date.now() });
|
|
57
|
+
if (!v.ok) {
|
|
58
|
+
// 契约层说不行 → 如实拒 (版本/字段/预算/deadline/签名逐条在 issues 里)
|
|
59
|
+
return { ok: false, error: 'INVALID_REQUEST', detail: v.issues.join('; ').slice(0, 400) };
|
|
60
|
+
}
|
|
61
|
+
const saved = saveIncomingRequest(req, {
|
|
62
|
+
home,
|
|
63
|
+
requestVerified: true,
|
|
64
|
+
buyerPublicKeyHex: frame.signer.publicKeyHex,
|
|
65
|
+
replyTo: frame.replyTo || null,
|
|
66
|
+
});
|
|
67
|
+
if (!saved.ok)
|
|
68
|
+
return { ok: false, error: 'INBOX_WRITE_FAILED', detail: saved.error };
|
|
69
|
+
const known = !opts.capabilities?.length || opts.capabilities.includes(req.capability);
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
duplicate: saved.dup,
|
|
73
|
+
note: saved.dup
|
|
74
|
+
? `同一 requestId 已在收件箱 (幂等: 不重复接受/不重复执行/不重复收费) — ${saved.reason}`
|
|
75
|
+
: `已落待处理收件箱 (不执行、不付款、不自动接单)${known ? '' : ` · 本机没有声明能力 '${req.capability}' (接单时会被拒)`}`,
|
|
76
|
+
facts: { requestId: req.requestId, taskId: req.taskId, state: saved.item?.state ?? 'pending', receivedAt: saved.item?.receivedAt },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case 'task_accept': {
|
|
80
|
+
if (!verified)
|
|
81
|
+
return { ok: false, error: 'SIGNATURE_INVALID', detail: '接受回执验签不过' };
|
|
82
|
+
const acc = env;
|
|
83
|
+
const cur = readLocalTask(acc.requestId, home);
|
|
84
|
+
if (!cur) {
|
|
85
|
+
// 没有本地台账: 只存回执本身, 并如实标注"没有对应台账"
|
|
86
|
+
const rec = {
|
|
87
|
+
protocol: 'bolloon-task/1',
|
|
88
|
+
requestId: acc.requestId,
|
|
89
|
+
taskId: acc.taskId,
|
|
90
|
+
capability: '',
|
|
91
|
+
instruction: '',
|
|
92
|
+
buyerDid: '',
|
|
93
|
+
providerDid: acc.providerDid,
|
|
94
|
+
target: frame.signer.did,
|
|
95
|
+
transportKind: 'inbound',
|
|
96
|
+
state: 'accepted',
|
|
97
|
+
sentAt: frame.sentAt,
|
|
98
|
+
updatedAt: Date.now(),
|
|
99
|
+
requestSignature: '',
|
|
100
|
+
accept: acc,
|
|
101
|
+
notes: ['本机没有对应的本地任务台账 (只存了对端回执; 不伪造本地发送记录)'],
|
|
102
|
+
};
|
|
103
|
+
saveLocalTask(rec, home);
|
|
104
|
+
return { ok: true, note: '已存对端接受回执 (本机没有对应的本地台账)', facts: { requestId: acc.requestId, state: 'accepted' } };
|
|
105
|
+
}
|
|
106
|
+
const mv = advance(cur.state, ['accepted']);
|
|
107
|
+
if (mv.rejected) {
|
|
108
|
+
return { ok: false, error: 'TASK_TRANSITION_REJECTED', detail: `对端回执 accepted 与本机状态 ${cur.state} 不合: ${mv.rejected}` };
|
|
109
|
+
}
|
|
110
|
+
patchLocalTask(acc.requestId, { state: mv.state, accept: acc, providerPublicKeyHex: frame.signer.publicKeyHex, notes: [...cur.notes, `收到接受回执 ${new Date(frame.sentAt).toISOString()}`] }, home);
|
|
111
|
+
const inb = readInboxItem(acc.requestId, home);
|
|
112
|
+
if (inb)
|
|
113
|
+
patchInboxItem(acc.requestId, { state: 'accepted', accept: acc, providerPublicKeyHex: frame.signer.publicKeyHex }, home);
|
|
114
|
+
return { ok: true, note: `接受回执已入库 (state=${mv.state})`, facts: { requestId: acc.requestId, state: mv.state } };
|
|
115
|
+
}
|
|
116
|
+
case 'task_reject': {
|
|
117
|
+
if (!verified)
|
|
118
|
+
return { ok: false, error: 'SIGNATURE_INVALID', detail: '拒绝回执验签不过' };
|
|
119
|
+
const rej = env;
|
|
120
|
+
const cur = readLocalTask(rej.requestId, home);
|
|
121
|
+
if (!cur) {
|
|
122
|
+
return { ok: true, note: '收到拒绝对端回执, 本机没有对应台账 (不伪造)', facts: { requestId: rej.requestId, state: 'rejected', reason: rej.reason } };
|
|
123
|
+
}
|
|
124
|
+
const mv = advance(cur.state, ['rejected']);
|
|
125
|
+
if (mv.rejected)
|
|
126
|
+
return { ok: false, error: 'TASK_TRANSITION_REJECTED', detail: `对端回执 rejected 与本机状态 ${cur.state} 不合: ${mv.rejected}` };
|
|
127
|
+
patchLocalTask(rej.requestId, { state: mv.state, reject: rej, notes: [...cur.notes, `被拒: ${String(rej.reason).slice(0, 200)}`] }, home);
|
|
128
|
+
const inb = readInboxItem(rej.requestId, home);
|
|
129
|
+
if (inb)
|
|
130
|
+
patchInboxItem(rej.requestId, { state: 'rejected', reject: rej }, home);
|
|
131
|
+
return { ok: true, note: `拒绝回执已入库 (state=${mv.state})`, facts: { requestId: rej.requestId, state: mv.state, reason: rej.reason } };
|
|
132
|
+
}
|
|
133
|
+
case 'task_result': {
|
|
134
|
+
if (!verified)
|
|
135
|
+
return { ok: false, error: 'SIGNATURE_INVALID', detail: '结果信封验签不过 (不落地未验签的结果)' };
|
|
136
|
+
const res = env;
|
|
137
|
+
// ① 存结果信封 (真实内容哈希/CID + 签名)
|
|
138
|
+
const w = saveResult(res, home);
|
|
139
|
+
if (!w.ok)
|
|
140
|
+
return { ok: false, error: 'RESULT_WRITE_FAILED', detail: w.error };
|
|
141
|
+
// ② 正文 (若随帧带来): 落**私有一层**, 并用签名里的 contentHash 复核
|
|
142
|
+
let bodyFacts = { bodyPresent: false };
|
|
143
|
+
if (typeof frame.body === 'string' && frame.body.length) {
|
|
144
|
+
const saved = saveBody(res.taskId, frame.body, home);
|
|
145
|
+
const matches = !!saved.contentHash && !!res.contentHash && saved.contentHash === res.contentHash;
|
|
146
|
+
bodyFacts = {
|
|
147
|
+
bodyPresent: true,
|
|
148
|
+
bodyBytes: saved.bytes,
|
|
149
|
+
bodyHashMatches: matches,
|
|
150
|
+
bodyNote: matches ? '正文哈希与签名里的 contentHash 一致' : '正文哈希与签名里的 contentHash **不一致** (如实记录)',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// ③ 状态推进: submitted/accepted → running → delivered (逐条过 checkTaskMove)
|
|
154
|
+
const cur = readLocalTask(res.requestId, home);
|
|
155
|
+
if (cur) {
|
|
156
|
+
const mv = advance(cur.state, ['accepted', 'running', 'delivered']);
|
|
157
|
+
if (mv.rejected)
|
|
158
|
+
return { ok: false, error: 'TASK_TRANSITION_REJECTED', detail: `结果回执与本机状态 ${cur.state} 不合: ${mv.rejected}` };
|
|
159
|
+
patchLocalTask(res.requestId, {
|
|
160
|
+
state: mv.state, result: res, resultVerified: verified,
|
|
161
|
+
providerPublicKeyHex: frame.signer.publicKeyHex,
|
|
162
|
+
notes: [...cur.notes, `收到结果 ${new Date(frame.sentAt).toISOString()} (contentHash=${(res.contentHash || '').slice(0, 12)}…)`],
|
|
163
|
+
}, home);
|
|
164
|
+
const inb = readInboxItem(res.requestId, home);
|
|
165
|
+
if (inb)
|
|
166
|
+
patchInboxItem(res.requestId, { state: 'delivered', result: res, resultVerified: verified, providerPublicKeyHex: frame.signer.publicKeyHex }, home);
|
|
167
|
+
return { ok: true, note: `结果已入库 (state=${mv.state}, 签名验过=${verified})`, facts: { requestId: res.requestId, taskId: res.taskId, state: mv.state, protocolVerified: verified, ...bodyFacts } };
|
|
168
|
+
}
|
|
169
|
+
const inb = readInboxItem(res.requestId, home);
|
|
170
|
+
if (inb) {
|
|
171
|
+
patchInboxItem(res.requestId, { state: 'delivered', result: res, resultVerified: verified, providerPublicKeyHex: frame.signer.publicKeyHex }, home);
|
|
172
|
+
return { ok: true, note: `结果已入库到收件箱条目 (state=delivered)`, facts: { requestId: res.requestId, taskId: res.taskId, protocolVerified: verified, ...bodyFacts } };
|
|
173
|
+
}
|
|
174
|
+
return { ok: true, note: '结果信封已存, 但本机没有对应的请求/台账 (不伪造状态)', facts: { taskId: res.taskId, protocolVerified: verified, ...bodyFacts } };
|
|
175
|
+
}
|
|
176
|
+
default:
|
|
177
|
+
return { ok: false, error: 'UNKNOWN_FRAME', detail: `未知帧类型: ${String(frame.frame)}` };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** express app: POST /api/task/frame + GET /api/task/health */
|
|
181
|
+
export function createTaskFrameApp(opts = {}) {
|
|
182
|
+
const app = express();
|
|
183
|
+
app.use(express.json({ limit: '2mb' }));
|
|
184
|
+
app.get('/api/task/health', (_req, res) => {
|
|
185
|
+
res.json({
|
|
186
|
+
ok: true,
|
|
187
|
+
schema: 'bolloon-task-frame/1',
|
|
188
|
+
selfDid: opts.selfDid || null,
|
|
189
|
+
capabilities: opts.capabilities || [],
|
|
190
|
+
inboxPending: listInbox(opts.home).filter((i) => i.state === 'pending').length,
|
|
191
|
+
note: '任务帧端点 (只落待处理收件箱; 不执行、不付款)',
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
app.post('/api/task/frame', async (req, res) => {
|
|
195
|
+
const raw = req.body?.frame;
|
|
196
|
+
if (!raw)
|
|
197
|
+
return res.status(400).json({ ok: false, error: 'BAD_FRAME', detail: '缺 frame' });
|
|
198
|
+
const parsed = parseTaskFrame(typeof raw === 'string' ? raw : JSON.stringify(raw));
|
|
199
|
+
if (!parsed.ok)
|
|
200
|
+
return res.status(400).json({ ok: false, error: 'BAD_FRAME', detail: parsed.error });
|
|
201
|
+
try {
|
|
202
|
+
const reply = await handleTaskFrame(parsed.frame, opts);
|
|
203
|
+
// 帧级失败用 200 + ok:false 表达 (与 CLI 信封同一条纪律: 失败也结构化, 不靠 HTTP 状态猜)
|
|
204
|
+
return res.status(200).json({ ok: reply.ok, ...(reply.duplicate !== undefined ? { duplicate: reply.duplicate } : {}), ...(reply.note ? { note: reply.note } : {}), ...(reply.error ? { error: reply.error } : {}), ...(reply.detail ? { detail: reply.detail } : {}), ...(reply.facts ? { facts: reply.facts } : {}) });
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return res.status(500).json({ ok: false, error: 'HANDLER_THREW', detail: String(e?.message || e).slice(0, 300) });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return app;
|
|
211
|
+
}
|
package/dist/llm/config-store.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as fs from 'fs/promises';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import * as fsSync from 'fs';
|
|
9
|
+
import { absorbLegacyProviderEntries, ensureProviderRegistryMetadataSource, getProviderRegistryEntry, normalizeCustomProviders, setCustomProviderSnapshot } from './provider-registry.js';
|
|
9
10
|
/** 2026-09-16 (M1): 不再在模块加载时固定 HOME —— 独立宿主/测试注入/长驻进程都走同一个解析 */
|
|
10
11
|
function configDir() {
|
|
11
12
|
const env = process.env.BOLLOON_HOME?.trim();
|
|
@@ -256,6 +257,11 @@ class LLMConfigStore {
|
|
|
256
257
|
return next;
|
|
257
258
|
}
|
|
258
259
|
async initialize() {
|
|
260
|
+
// 2026-09-26 (P3): 任何一次配置读都顺手把注册表的元数据填充点接上。
|
|
261
|
+
// 为什么不放模块体: 本模块与注册表之间是循环 import, 模块体的执行时机取决于"谁是入口",
|
|
262
|
+
// 实测会撞上对方模块 `metadataSources` 的 TDZ (整个进程起不来)。配置路径是所有读模型
|
|
263
|
+
// 元数据的前置步骤, 放这里既能自动接上, 又不依赖 import 顺序 (详见 provider-registry)。
|
|
264
|
+
ensureProviderRegistryMetadataSource();
|
|
259
265
|
// 目录变了 (换 HOME / 独立宿主 / 测试注入): 内存缓存作废, 重新读盘
|
|
260
266
|
const dir = configDir();
|
|
261
267
|
const sig = this.fileSignature();
|
|
@@ -290,19 +296,46 @@ class LLMConfigStore {
|
|
|
290
296
|
loadedConfig.providers[provider] = { ...DEFAULT_PROVIDER_CONFIGS[provider] };
|
|
291
297
|
}
|
|
292
298
|
}
|
|
299
|
+
// 2026-09-26 (P3): 自定义供应商在**内存里**规范化 (数组形 → map / 别名 / 从 providers 吸收),
|
|
300
|
+
// **不写盘** —— "读一下就把用户文件改了"是另一种意外。写回只发生在显式 add/update/remove。
|
|
301
|
+
const normCustom = normalizeCustomProviders(loadedConfig.customProviders);
|
|
302
|
+
const absorbed = absorbLegacyProviderEntries(loadedConfig.providers, normCustom.providers);
|
|
303
|
+
loadedConfig.customProviders = absorbed.providers;
|
|
304
|
+
if (normCustom.rejected.length) {
|
|
305
|
+
console.warn(`[config-store] customProviders 有 ${normCustom.rejected.length} 条被拒: `
|
|
306
|
+
+ normCustom.rejected.map((r) => `${r.key}(${r.reason})`).join('; '));
|
|
307
|
+
}
|
|
308
|
+
// 注册表的同步读口 (元数据填充点/长期任务门) 靠这份快照 —— 配置一读完就刷新, 不留陈旧结论
|
|
309
|
+
setCustomProviderSnapshot(absorbed.providers);
|
|
293
310
|
// 确保有 activeProvider
|
|
294
311
|
const activeProvider = loadedConfig.activeProvider;
|
|
295
|
-
if (!activeProvider || !
|
|
312
|
+
if (!activeProvider || !this.isSelectableProvider(activeProvider, loadedConfig)) {
|
|
296
313
|
loadedConfig.activeProvider = 'ollama';
|
|
297
314
|
}
|
|
298
315
|
this.config = loadedConfig;
|
|
299
316
|
}
|
|
300
317
|
catch {
|
|
301
318
|
this.config = getDefaultConfig();
|
|
319
|
+
setCustomProviderSnapshot({});
|
|
302
320
|
await this.save();
|
|
303
321
|
}
|
|
304
322
|
this.initialized = true;
|
|
305
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* 这个 provider id 现在**能当 activeProvider** 吗。
|
|
326
|
+
*
|
|
327
|
+
* 2026-09-26 (P3) 之前: 只认内置表 → 用户把自定义端点当默认模型时会被**静默改成 ollama**
|
|
328
|
+
* (旧配置直接失效)。现在: 内置, 或已注册的自定义供应商, 或配置里**真有这一格且写了地址/模型**
|
|
329
|
+
* (旧的"手写 provider 条目"写法) 都算数; 三者都不是才退到 ollama。
|
|
330
|
+
*/
|
|
331
|
+
isSelectableProvider(id, cfg) {
|
|
332
|
+
if (DEFAULT_PROVIDER_CONFIGS[id])
|
|
333
|
+
return true;
|
|
334
|
+
if (cfg.customProviders && cfg.customProviders[id])
|
|
335
|
+
return true;
|
|
336
|
+
const own = cfg.providers[id];
|
|
337
|
+
return !!own && (String(own.baseUrl || '').trim().length > 0 || String(own.model || '').trim().length > 0);
|
|
338
|
+
}
|
|
306
339
|
async save() {
|
|
307
340
|
if (!this.config)
|
|
308
341
|
return;
|
|
@@ -341,31 +374,87 @@ class LLMConfigStore {
|
|
|
341
374
|
}
|
|
342
375
|
async setActiveProvider(provider) {
|
|
343
376
|
await this.initialize();
|
|
344
|
-
|
|
377
|
+
const hit = this.providerRowFor(String(provider));
|
|
378
|
+
if (!hit) {
|
|
345
379
|
throw new Error(`Unknown provider: ${provider}`);
|
|
346
380
|
}
|
|
347
|
-
const providerConfig =
|
|
381
|
+
const providerConfig = hit.row;
|
|
348
382
|
if (providerConfig.requiresApiKey && !providerConfig.apiKey) {
|
|
349
383
|
throw new Error(`${provider} requires an API key but none is configured`);
|
|
350
384
|
}
|
|
351
385
|
await this.withWriteLock(async () => {
|
|
386
|
+
if (!hit.existing)
|
|
387
|
+
this.config.providers[String(provider)] = hit.row;
|
|
352
388
|
this.config.activeProvider = provider;
|
|
353
389
|
await this.save();
|
|
354
390
|
});
|
|
355
391
|
}
|
|
356
392
|
async updateProvider(provider, updates) {
|
|
357
393
|
await this.initialize();
|
|
358
|
-
|
|
394
|
+
const hit = this.providerRowFor(String(provider));
|
|
395
|
+
if (!hit) {
|
|
359
396
|
throw new Error(`Unknown provider: ${provider}`);
|
|
360
397
|
}
|
|
361
398
|
await this.withWriteLock(async () => {
|
|
362
|
-
this.config.providers[provider] = {
|
|
363
|
-
...
|
|
399
|
+
this.config.providers[String(provider)] = {
|
|
400
|
+
...hit.row,
|
|
364
401
|
...updates
|
|
365
402
|
};
|
|
366
403
|
await this.save();
|
|
367
404
|
});
|
|
368
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* 取(必要时**算出**)一个 provider 的配置行。`existing=false` 表示这一行还不存在, 由调用方
|
|
408
|
+
* 在自己的写锁里落进 map(本方法**不改任何状态** —— 校验失败时就该什么都不发生)。
|
|
409
|
+
*
|
|
410
|
+
* 2026-09-26 (P6): 统一入口现在**接受自定义供应商 id** (从注册表校验, 不再只看内置表),
|
|
411
|
+
* 于是切换时会把 `model/baseUrl/apiKey` 写进 `providers.<自定义 id>` 这一格 —— 而这一格此前
|
|
412
|
+
* 根本不存在, 旧实现直接 `Unknown provider` 抛错。
|
|
413
|
+
*
|
|
414
|
+
* 规矩 (不编默认值):
|
|
415
|
+
* · 内置供应商 / 配置里本来就有这一格 → 原样返回 (老行为一字不变);
|
|
416
|
+
* · 已注册的**自定义供应商** → 用它的**声明**起手 (地址/模型), 凭据那一格留空等写入 ——
|
|
417
|
+
* 不拿内置默认去顶;
|
|
418
|
+
* · 其余 (既不是内置, 也不在 `customProviders` 里) → `null` (调用方照旧抛 Unknown provider)。
|
|
419
|
+
*/
|
|
420
|
+
providerRowFor(id) {
|
|
421
|
+
const rows = this.config?.providers;
|
|
422
|
+
if (!rows)
|
|
423
|
+
return null;
|
|
424
|
+
if (rows[id])
|
|
425
|
+
return { row: rows[id], existing: true };
|
|
426
|
+
const spec = this.config?.customProviders?.[id];
|
|
427
|
+
if (!spec) {
|
|
428
|
+
// 2026-09-27 (目录驱动): **目录里的家**也走这条 —— 新家只要在目录里就能切进来 (不改代码)。
|
|
429
|
+
// 起手行只放目录**声明过的**东西: 基址 = 目录里的 api (目录没给就是空串, 让校验层如实报
|
|
430
|
+
// "需自定义 baseUrl"); 模型留空 (公开目录没有"默认模型"这回事, 不许抓一个凑数);
|
|
431
|
+
// 凭据那一格留空, 等写入或等环境变量 (目录只给环境变量**名**)。
|
|
432
|
+
const catEntry = getProviderRegistryEntry(id);
|
|
433
|
+
if (!catEntry || catEntry.kind !== 'catalog')
|
|
434
|
+
return null;
|
|
435
|
+
return {
|
|
436
|
+
existing: false,
|
|
437
|
+
row: {
|
|
438
|
+
enabled: true,
|
|
439
|
+
apiKey: '',
|
|
440
|
+
baseUrl: catEntry.defaultBaseUrl,
|
|
441
|
+
model: '',
|
|
442
|
+
requiresApiKey: catEntry.requiresApiKey !== false,
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const entry = getProviderRegistryEntry(id);
|
|
447
|
+
return {
|
|
448
|
+
existing: false,
|
|
449
|
+
row: {
|
|
450
|
+
enabled: true,
|
|
451
|
+
apiKey: '',
|
|
452
|
+
baseUrl: spec.baseUrl,
|
|
453
|
+
model: String(spec.model || spec.models?.[0] || ''),
|
|
454
|
+
requiresApiKey: entry ? entry.requiresApiKey : true,
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
}
|
|
369
458
|
async testProvider(provider) {
|
|
370
459
|
await this.initialize();
|
|
371
460
|
const config = this.config?.providers[provider];
|
|
@@ -494,6 +583,57 @@ class LLMConfigStore {
|
|
|
494
583
|
getAllProviderInfo() {
|
|
495
584
|
return PROVIDER_INFO;
|
|
496
585
|
}
|
|
586
|
+
/**
|
|
587
|
+
* 2026-09-26: 丢弃内存缓存, 下一次 initialize() 强制重新读盘。
|
|
588
|
+
* 用途: 统一入口在做"读出→改写→写回"时, 必须在拿到跨进程锁之后重读,
|
|
589
|
+
* 否则两个进程各自拿旧内存值写回, 后写的会把先写的那份改动覆盖掉。
|
|
590
|
+
* 回滚路径同样需要它 (把文件还原后, 内存里的那一份也得跟着回来)。
|
|
591
|
+
*/
|
|
592
|
+
invalidate() {
|
|
593
|
+
this.initialized = false;
|
|
594
|
+
this.config = null;
|
|
595
|
+
this.loadedDir = null;
|
|
596
|
+
this.loadedSig = null;
|
|
597
|
+
}
|
|
598
|
+
/** 配置文件绝对路径 (统一入口/锁/迁移都从这里取, 不再各自拼) */
|
|
599
|
+
configFilePath() {
|
|
600
|
+
return CONFIG_PATH;
|
|
601
|
+
}
|
|
602
|
+
/** 旧文件名 (迁移输入) */
|
|
603
|
+
legacyConfigFilePath() {
|
|
604
|
+
return LEGACY_CONFIG_PATH;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* 现在在册的自定义供应商 (规范化后的内存视图; 读盘由 `initialize()` 保证已经发生过)。
|
|
608
|
+
* 凭据原样在里面 —— 调用方展示前必须走脱敏 (`custom-provider-store.redactCustomProviders`)。
|
|
609
|
+
*/
|
|
610
|
+
async getCustomProviders() {
|
|
611
|
+
await this.initialize();
|
|
612
|
+
return { ...(this.config?.customProviders || {}) };
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* 覆盖 `customProviders` 一格 (唯一写口)。
|
|
616
|
+
*
|
|
617
|
+
* 只动这一格 —— 内置 `providers` / `activeProvider` 一个字节都不碰; 于是"加个自定义供应商"
|
|
618
|
+
* 不会顺手把用户在别处刚改的模型选择覆盖掉。写前调用方负责 `invalidate()` + `initialize()`
|
|
619
|
+
* (拿到跨进程锁之后重读), 否则两个进程各自拿旧内存值写回。
|
|
620
|
+
*/
|
|
621
|
+
async setCustomProviders(map) {
|
|
622
|
+
await this.initialize();
|
|
623
|
+
const norm = normalizeCustomProviders(map && typeof map === 'object' ? map : {});
|
|
624
|
+
if (norm.rejected.length) {
|
|
625
|
+
throw new Error(`customProviders 规范化失败: ${norm.rejected.map((r) => `${r.key}(${r.reason})`).join('; ')}`);
|
|
626
|
+
}
|
|
627
|
+
await this.withWriteLock(async () => {
|
|
628
|
+
this.config.customProviders = norm.providers;
|
|
629
|
+
setCustomProviderSnapshot(norm.providers);
|
|
630
|
+
await this.save();
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/** 配置目录 (锁文件、session 绑定都落在这里) */
|
|
634
|
+
configDirPath() {
|
|
635
|
+
return configDir();
|
|
636
|
+
}
|
|
497
637
|
}
|
|
498
638
|
export const llmConfigStore = new LLMConfigStore();
|
|
499
639
|
export function getPiSDKConfig() {
|