@bolloon/bolloon-agent 0.1.23 → 0.1.25
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/agent-manifest-protocol.js +81 -0
- package/dist/agents/iroh-secret.js +32 -0
- package/dist/index.js +5 -8
- package/dist/network/iroh-transport.js +14 -0
- package/dist/pi-ecosystem-judgment/human-value-store.js +40 -0
- package/dist/utils/auto-update.js +11 -2
- package/dist/web/agent-delegate-server.js +123 -0
- package/dist/web/client.js +570 -10
- package/dist/web/index.html +74 -0
- package/dist/web/iroh-delegate-transport.js +125 -0
- package/dist/web/server.js +385 -7
- package/dist/web/style.css +7 -0
- package/package.json +1 -1
- package/src/agents/agent-manifest-protocol.ts +117 -0
- package/src/agents/iroh-secret.ts +32 -0
- package/src/index.ts +6 -8
- package/src/network/iroh-transport.ts +14 -0
- package/src/utils/auto-update.ts +12 -2
- package/src/web/agent-delegate-server.ts +148 -0
- package/src/web/client.js +570 -10
- package/src/web/index.html +74 -0
- package/src/web/iroh-delegate-transport.ts +139 -0
- package/src/web/server.ts +410 -7
- package/src/web/style.css +7 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-manifest-protocol - 节点握手后立刻互换"我有哪些 agent + capabilities"
|
|
3
|
+
*
|
|
4
|
+
* 核心目的:实现 "建联一次,访问对方所有智能体"。
|
|
5
|
+
* - 节点连上 (Hyperswarm 主题 / iroh) 后立刻发 'manifest_request'
|
|
6
|
+
* - 对端回 'manifest_payload',写入本地 agentRegistry
|
|
7
|
+
* - 之后任何指令可以 pickAgent(capability, ownerDid) → 直接委派
|
|
8
|
+
*
|
|
9
|
+
* 协议消息 (Hyperswarm 字符串帧):
|
|
10
|
+
* manifest_request: { }
|
|
11
|
+
* manifest_payload: { ownerName, ownerPublicKey, agents:[{id,name,capabilities,status}], publishedAt }
|
|
12
|
+
*
|
|
13
|
+
* 本文件不绑定 transport — 只提供 build/parse/dispatch 帮助函数。
|
|
14
|
+
* 调用方在自己 transport 上挂 onMessage('manifest_request', ...) 和 onMessage('manifest_payload', ...)。
|
|
15
|
+
*/
|
|
16
|
+
// ============== 帧构造 ==============
|
|
17
|
+
export function buildManifestRequest() {
|
|
18
|
+
return JSON.stringify({ type: 'manifest_request', payload: {}, ts: Date.now(), fromDid: '' });
|
|
19
|
+
}
|
|
20
|
+
export function buildManifestPayload(manifest) {
|
|
21
|
+
return JSON.stringify({ type: 'manifest_payload', payload: manifest, ts: Date.now(), fromDid: '' });
|
|
22
|
+
}
|
|
23
|
+
export function buildAgentDelegateRequest(opts) {
|
|
24
|
+
return JSON.stringify({ type: 'agent_delegate', payload: opts, ts: Date.now(), fromDid: '' });
|
|
25
|
+
}
|
|
26
|
+
export function buildAgentResponse(opts) {
|
|
27
|
+
return JSON.stringify({ type: 'agent_response', payload: opts, ts: Date.now(), fromDid: '' });
|
|
28
|
+
}
|
|
29
|
+
// ============== 帧解析 ==============
|
|
30
|
+
export function parseFrame(text) {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(text);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ============== 本地 manifest registry ==============
|
|
39
|
+
const localManifest = {
|
|
40
|
+
ownerName: '',
|
|
41
|
+
ownerPublicKey: '',
|
|
42
|
+
agents: [],
|
|
43
|
+
publishedAt: 0,
|
|
44
|
+
};
|
|
45
|
+
export function setLocalManifest(m) {
|
|
46
|
+
Object.assign(localManifest, m, { publishedAt: Date.now() });
|
|
47
|
+
return localManifest;
|
|
48
|
+
}
|
|
49
|
+
export function getLocalManifest() {
|
|
50
|
+
return localManifest;
|
|
51
|
+
}
|
|
52
|
+
export function addLocalAgent(agent) {
|
|
53
|
+
const idx = localManifest.agents.findIndex((a) => a.id === agent.id);
|
|
54
|
+
if (idx >= 0)
|
|
55
|
+
localManifest.agents[idx] = agent;
|
|
56
|
+
else
|
|
57
|
+
localManifest.agents.push(agent);
|
|
58
|
+
localManifest.publishedAt = Date.now();
|
|
59
|
+
return localManifest;
|
|
60
|
+
}
|
|
61
|
+
// ============== 远端 manifest 缓存 ==============
|
|
62
|
+
const remoteManifests = new Map(); // key = ownerPublicKey
|
|
63
|
+
export function cacheRemoteManifest(m) {
|
|
64
|
+
if (m.ownerPublicKey)
|
|
65
|
+
remoteManifests.set(m.ownerPublicKey, m);
|
|
66
|
+
return m;
|
|
67
|
+
}
|
|
68
|
+
export function getRemoteManifests() {
|
|
69
|
+
return Array.from(remoteManifests.values());
|
|
70
|
+
}
|
|
71
|
+
export function pickAgent(capability, ownerPublicKey) {
|
|
72
|
+
const owners = ownerPublicKey
|
|
73
|
+
? [remoteManifests.get(ownerPublicKey)].filter(Boolean)
|
|
74
|
+
: getRemoteManifests();
|
|
75
|
+
for (const owner of owners) {
|
|
76
|
+
const a = owner.agents.find((x) => x.capabilities.includes(capability) && x.status === 'active');
|
|
77
|
+
if (a)
|
|
78
|
+
return { agent: a, owner };
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iroh-secret - 把 iroh secretKey 落 ~/.bolloon/iroh-secret.json
|
|
3
|
+
*
|
|
4
|
+
* iroh 默认每次启动生成新节点 ID;落盘后可实现"跨重启稳定 nodeId"。
|
|
5
|
+
* 对应 Issue: "建联一次访问所有智能体"需要稳定标识, 不然对方缓存的 nodeId 失效
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import * as crypto from 'crypto';
|
|
11
|
+
const SECRET_DIR = path.join(os.homedir(), '.bolloon');
|
|
12
|
+
export function loadOrCreateIrohSecret(role = 'default') {
|
|
13
|
+
if (!fs.existsSync(SECRET_DIR))
|
|
14
|
+
fs.mkdirSync(SECRET_DIR, { recursive: true });
|
|
15
|
+
const fp = path.join(SECRET_DIR, `iroh-secret-${role}.json`);
|
|
16
|
+
if (fs.existsSync(fp)) {
|
|
17
|
+
const j = JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
18
|
+
return { secretKey: Buffer.from(j.secretKey, 'hex'), createdAt: j.createdAt, reused: true };
|
|
19
|
+
}
|
|
20
|
+
const sk = crypto.randomBytes(32);
|
|
21
|
+
const createdAt = new Date().toISOString();
|
|
22
|
+
fs.writeFileSync(fp, JSON.stringify({ secretKey: sk.toString('hex'), createdAt }, null, 2), { mode: 0o600 });
|
|
23
|
+
// chmod in case the file already existed and umask produced looser perms
|
|
24
|
+
try {
|
|
25
|
+
fs.chmodSync(fp, 0o600);
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
return { secretKey: sk, createdAt, reused: false };
|
|
29
|
+
}
|
|
30
|
+
export function irohSecretFilePath(role = 'default') {
|
|
31
|
+
return path.join(SECRET_DIR, `iroh-secret-${role}.json`);
|
|
32
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createSubAgentManager } from './agents/subagent-manager.js';
|
|
|
11
11
|
import { getGlobalSharedContext } from './social/global-shared-context.js';
|
|
12
12
|
import { createBollharnessIntegration } from './bollharness-integration/index.js';
|
|
13
13
|
import * as readline from 'readline';
|
|
14
|
-
|
|
14
|
+
// 启动时自动检查更新已禁用 (改用 --update-check / --update-now 显式触发)
|
|
15
15
|
const RESET = '\x1b[0m';
|
|
16
16
|
const BOLD = '\x1b[1m';
|
|
17
17
|
const DIM = '\x1b[2m';
|
|
@@ -1324,13 +1324,10 @@ async function main() {
|
|
|
1324
1324
|
printHelp();
|
|
1325
1325
|
process.exit(0);
|
|
1326
1326
|
}
|
|
1327
|
-
//
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
// 更新完成后退出,让用户重新启动
|
|
1332
|
-
process.exit(0);
|
|
1333
|
-
}
|
|
1327
|
+
// 自动更新已禁用: 启动时不再自动检查/安装更新.
|
|
1328
|
+
// 想手动检查: bolloon --update-check
|
|
1329
|
+
// 想手动更新: bolloon --update-now [package]
|
|
1330
|
+
// 想完全屏蔽 (CI / sandbox): BOLLOON_SKIP_UPDATE=true
|
|
1334
1331
|
const mode = args.web ? 'web' : 'cli';
|
|
1335
1332
|
const isNonInteractive = !!(args.tool || args.prompt);
|
|
1336
1333
|
const isTuiMode = mode === 'cli' && !isNonInteractive && args.tui;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Endpoint } from '@rayhanadev/iroh';
|
|
2
2
|
import * as crypto from 'crypto';
|
|
3
|
+
import { loadOrCreateIrohSecret } from '../agents/iroh-secret.js';
|
|
3
4
|
const IROH_ALPN = 'bolloon/iroh/1';
|
|
4
5
|
export class IrohTransport {
|
|
5
6
|
endpoint = null;
|
|
@@ -24,6 +25,19 @@ export class IrohTransport {
|
|
|
24
25
|
addr: this.ownNodeId // iroh 没有 listenAddresses,用 nodeId 作为 addr
|
|
25
26
|
};
|
|
26
27
|
}
|
|
28
|
+
// 若调用方未传 secretKey,从 ~/.bolloon/iroh-secret-default.json 落盘/读取
|
|
29
|
+
// 保证 iroh nodeId 在同一台机器上跨重启保持稳定
|
|
30
|
+
if (!secretKey) {
|
|
31
|
+
try {
|
|
32
|
+
const sec = loadOrCreateIrohSecret('default');
|
|
33
|
+
// iroh binding 的 secretKey 字段是 hex 字符串 (32 字节 Ed25519 种子)
|
|
34
|
+
secretKey = Buffer.from(sec.secretKey).toString('hex');
|
|
35
|
+
console.log(`[IrohTransport] ${sec.reused ? '复用' : '新建'} iroh-secret-default.json (createdAt=${sec.createdAt})`);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
console.warn('[IrohTransport] iroh-secret 加载失败, 将使用临时身份:', e);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
27
41
|
const options = { alpns: [IROH_ALPN] };
|
|
28
42
|
if (secretKey) {
|
|
29
43
|
options.secretKey = secretKey;
|
|
@@ -130,6 +130,44 @@ export async function learnFromCorrection(original, corrected, reason) {
|
|
|
130
130
|
// ============================================================
|
|
131
131
|
// 读取操作
|
|
132
132
|
// ============================================================
|
|
133
|
+
/**
|
|
134
|
+
* 修改一个判断 (手动编辑). 允许改: decision, reasons, context, values_derived, decision_type, confidence.
|
|
135
|
+
* 不能改 id, timestamp (id 不暴露, 靠 id 查).
|
|
136
|
+
*/
|
|
137
|
+
export async function updateJudgment(id, patch) {
|
|
138
|
+
const judgments = await loadAllJudgments();
|
|
139
|
+
const idx = judgments.findIndex((j) => j.id === id);
|
|
140
|
+
if (idx < 0)
|
|
141
|
+
return null;
|
|
142
|
+
const cur = judgments[idx];
|
|
143
|
+
const next = {
|
|
144
|
+
...cur,
|
|
145
|
+
...(patch.decision !== undefined ? { decision: patch.decision } : {}),
|
|
146
|
+
...(patch.decision_type !== undefined ? { decision_type: patch.decision_type } : {}),
|
|
147
|
+
...(patch.reasons !== undefined ? { reasons: patch.reasons } : {}),
|
|
148
|
+
...(patch.values_derived !== undefined ? { values_derived: patch.values_derived } : {}),
|
|
149
|
+
...(patch.context !== undefined ? { context: { ...cur.context, ...patch.context } } : {}),
|
|
150
|
+
...(patch.outcome !== undefined ? { outcome: { ...cur.outcome, ...patch.outcome } } : {}),
|
|
151
|
+
};
|
|
152
|
+
judgments[idx] = next;
|
|
153
|
+
await saveJudgments(judgments);
|
|
154
|
+
// 价值画像缓存失效 (内容变了画像也得重算)
|
|
155
|
+
valueProfileCache.clear();
|
|
156
|
+
return next;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 删除一个判断.
|
|
160
|
+
*/
|
|
161
|
+
export async function deleteJudgment(id) {
|
|
162
|
+
const judgments = await loadAllJudgments();
|
|
163
|
+
const idx = judgments.findIndex((j) => j.id === id);
|
|
164
|
+
if (idx < 0)
|
|
165
|
+
return false;
|
|
166
|
+
judgments.splice(idx, 1);
|
|
167
|
+
await saveJudgments(judgments);
|
|
168
|
+
valueProfileCache.clear();
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
133
171
|
/**
|
|
134
172
|
* 加载所有人类判断
|
|
135
173
|
*/
|
|
@@ -238,6 +276,8 @@ export async function getPriorityRules() {
|
|
|
238
276
|
async function saveJudgments(judgments) {
|
|
239
277
|
await fs.mkdir(VALUE_STORE_DIR, { recursive: true });
|
|
240
278
|
await fs.writeFile(JUDGMENTS_FILE, JSON.stringify(judgments, null, 2), 'utf-8');
|
|
279
|
+
// 让 loadAllJudgments 下次重新读盘, 避免缓存与磁盘脱节
|
|
280
|
+
judgmentCache = judgments;
|
|
241
281
|
}
|
|
242
282
|
function buildValueProfile(agentId, judgments) {
|
|
243
283
|
const profile = {
|
|
@@ -312,11 +312,20 @@ async function updatePackagesWithVersion(packagesWithVersion) {
|
|
|
312
312
|
* 检查并自动更新(启动时调用)
|
|
313
313
|
*/
|
|
314
314
|
export async function checkAndUpdate() {
|
|
315
|
-
//
|
|
315
|
+
// opt-in: 默认跳过更新检查. 想允许必须显式提供以下任一标志:
|
|
316
|
+
// --update-check, --update-now, --allow-update
|
|
317
|
+
// BOLLOON_AUTO_UPDATE=1
|
|
318
|
+
const allowFlag = process.argv.includes('--update-check')
|
|
319
|
+
|| process.argv.includes('--update-now')
|
|
320
|
+
|| process.argv.includes('--allow-update');
|
|
321
|
+
const allowEnv = process.env.BOLLOON_AUTO_UPDATE === '1';
|
|
322
|
+
if (!allowFlag && !allowEnv) {
|
|
323
|
+
return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查 (默认关闭, 用 --update-check 显式触发)' };
|
|
324
|
+
}
|
|
325
|
+
// 显式屏蔽仍然优先
|
|
316
326
|
if (process.argv.includes('--no-update') || process.argv.includes('--skip-update')) {
|
|
317
327
|
return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查' };
|
|
318
328
|
}
|
|
319
|
-
// 检查环境变量
|
|
320
329
|
if (process.env.BOLLOON_SKIP_UPDATE === 'true') {
|
|
321
330
|
return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查(环境变量)' };
|
|
322
331
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-delegate-server - 把 manifest + agent_delegate 协议挂到 Web API
|
|
3
|
+
*
|
|
4
|
+
* 启动: PORT=54189 npx tsx src/web/agent-delegate-server.ts
|
|
5
|
+
*
|
|
6
|
+
* 提供:
|
|
7
|
+
* GET /api/agent/local-manifest — 本节点智能体清单
|
|
8
|
+
* POST /api/agent/register — 注册/更新本节点智能体
|
|
9
|
+
* GET /api/agent/remote-manifests — 缓存的远端 manifest 列表
|
|
10
|
+
* POST /api/agent/pick — 按 capability + 可选 ownerPublicKey 选 agent
|
|
11
|
+
* POST /api/agent/delegate — DOC 驱动委派 (转发到对端 agent)
|
|
12
|
+
*
|
|
13
|
+
* 该模块不直接绑 Hyperswarm — 通过注入的 transport 抽象:
|
|
14
|
+
* - sendToNode(nodeId, frame) -> Promise<responseFrame>
|
|
15
|
+
*
|
|
16
|
+
* 主进程接入时把 Hyperswarm 拨号逻辑包成 transport 注入。
|
|
17
|
+
*/
|
|
18
|
+
import express from 'express';
|
|
19
|
+
import { buildAgentDelegateRequest, buildAgentResponse, buildManifestPayload, parseFrame, setLocalManifest, getLocalManifest, getRemoteManifests, cacheRemoteManifest, pickAgent, } from '../agents/agent-manifest-protocol.js';
|
|
20
|
+
export function createAgentDelegateApp(transport) {
|
|
21
|
+
const app = express();
|
|
22
|
+
app.use(express.json({ limit: '2mb' }));
|
|
23
|
+
// ---- 本地 manifest ----
|
|
24
|
+
app.get('/api/agent/local-manifest', (_req, res) => {
|
|
25
|
+
res.json(getLocalManifest());
|
|
26
|
+
});
|
|
27
|
+
app.post('/api/agent/register', (req, res) => {
|
|
28
|
+
const body = req.body;
|
|
29
|
+
if (!body.agents || !Array.isArray(body.agents)) {
|
|
30
|
+
return res.status(400).json({ error: 'agents array required' });
|
|
31
|
+
}
|
|
32
|
+
setLocalManifest({
|
|
33
|
+
ownerName: body.ownerName || getLocalManifest().ownerName || 'unknown',
|
|
34
|
+
ownerPublicKey: body.ownerPublicKey || getLocalManifest().ownerPublicKey || '',
|
|
35
|
+
agents: body.agents,
|
|
36
|
+
});
|
|
37
|
+
res.json({ ok: true, manifest: getLocalManifest() });
|
|
38
|
+
});
|
|
39
|
+
// ---- 远端 manifest 列表 ----
|
|
40
|
+
app.get('/api/agent/remote-manifests', (_req, res) => {
|
|
41
|
+
res.json({ count: getRemoteManifests().length, manifests: getRemoteManifests() });
|
|
42
|
+
});
|
|
43
|
+
// ---- 按 capability 选 agent ----
|
|
44
|
+
app.post('/api/agent/pick', (req, res) => {
|
|
45
|
+
const { capability, ownerPublicKey } = req.body;
|
|
46
|
+
if (!capability)
|
|
47
|
+
return res.status(400).json({ error: 'capability required' });
|
|
48
|
+
const picked = pickAgent(capability, ownerPublicKey);
|
|
49
|
+
if (!picked)
|
|
50
|
+
return res.status(404).json({ error: 'no matching agent', capability });
|
|
51
|
+
res.json({ ok: true, agent: picked.agent, owner: { name: picked.owner.ownerName, publicKey: picked.owner.ownerPublicKey } });
|
|
52
|
+
});
|
|
53
|
+
// ---- DOC 驱动委派 ----
|
|
54
|
+
app.post('/api/agent/delegate', async (req, res) => {
|
|
55
|
+
try {
|
|
56
|
+
const { toPublicKey, capability, docPath, docContent, instruction, fromAgentId } = req.body;
|
|
57
|
+
if (!toPublicKey || !capability || !instruction) {
|
|
58
|
+
return res.status(400).json({ error: 'toPublicKey, capability, instruction required' });
|
|
59
|
+
}
|
|
60
|
+
// 1) 优先从已缓存的远端 manifest 里选 agent
|
|
61
|
+
let targetAgent = null;
|
|
62
|
+
const remote = getRemoteManifests().find((m) => m.ownerPublicKey === toPublicKey || m.ownerPublicKey.startsWith(toPublicKey.substring(0, 16)));
|
|
63
|
+
if (remote) {
|
|
64
|
+
targetAgent = remote.agents.find((a) => a.capabilities.includes(capability) && a.status === 'active') || null;
|
|
65
|
+
}
|
|
66
|
+
// 2) 构造 frame, 通过 transport 发送
|
|
67
|
+
const frame = buildAgentDelegateRequest({
|
|
68
|
+
capability,
|
|
69
|
+
docPath,
|
|
70
|
+
docContent,
|
|
71
|
+
instruction,
|
|
72
|
+
fromAgentId: fromAgentId || 'local-user',
|
|
73
|
+
});
|
|
74
|
+
const replyFrame = await transport.sendToNode(toPublicKey, frame, 30000);
|
|
75
|
+
if (!replyFrame) {
|
|
76
|
+
return res.status(504).json({ error: 'no response from peer (timeout or transport not wired)' });
|
|
77
|
+
}
|
|
78
|
+
const f = parseFrame(replyFrame);
|
|
79
|
+
if (!f || f.type !== 'agent_response') {
|
|
80
|
+
return res.status(502).json({ error: 'bad response', frame: replyFrame });
|
|
81
|
+
}
|
|
82
|
+
res.json({
|
|
83
|
+
ok: true,
|
|
84
|
+
targetAgent: targetAgent || { id: f.payload.delegatedTo, capabilities: [capability], status: 'active', name: f.payload.delegatedTo },
|
|
85
|
+
response: f.payload,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
res.status(500).json({ error: e.message });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// ---- 接收对方 manifest_request / agent_delegate 处理 ----
|
|
93
|
+
// 业务端需要把对端来的入站 frame 转给本 handler
|
|
94
|
+
// 简化: 把 handler 挂到 transport.onIncomingFrame
|
|
95
|
+
transport.onIncomingFrame(async (fromPublicKey, frame) => {
|
|
96
|
+
const f = parseFrame(frame);
|
|
97
|
+
if (!f)
|
|
98
|
+
return null;
|
|
99
|
+
if (f.type === 'manifest_request') {
|
|
100
|
+
return buildManifestPayload(getLocalManifest());
|
|
101
|
+
}
|
|
102
|
+
if (f.type === 'manifest_payload') {
|
|
103
|
+
cacheRemoteManifest(f.payload);
|
|
104
|
+
return null; // 不需要回包
|
|
105
|
+
}
|
|
106
|
+
if (f.type === 'agent_delegate') {
|
|
107
|
+
// 路由到本地匹配 agent
|
|
108
|
+
const req = f.payload;
|
|
109
|
+
const local = getLocalManifest();
|
|
110
|
+
const target = local.agents.find((a) => a.capabilities.includes(req.capability) && a.status === 'active') || local.agents[0];
|
|
111
|
+
if (!target)
|
|
112
|
+
return buildAgentResponse({ ok: false, delegatedTo: 'none', summary: 'no local agent available' });
|
|
113
|
+
return buildAgentResponse({
|
|
114
|
+
ok: true,
|
|
115
|
+
delegatedTo: target.id,
|
|
116
|
+
resultCid: `mock-${Date.now()}`,
|
|
117
|
+
summary: `[${target.name}] 已处理任务: ${req.instruction?.substring(0, 30)}`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
});
|
|
122
|
+
return app;
|
|
123
|
+
}
|