@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/src/index.ts CHANGED
@@ -22,7 +22,8 @@ import { createSubAgentManager } from './agents/subagent-manager.js';
22
22
  import { getGlobalSharedContext } from './social/global-shared-context.js';
23
23
  import { BollharnessIntegration, createBollharnessIntegration } from './bollharness-integration/index.js';
24
24
  import * as readline from 'readline';
25
- import { checkAndUpdate } from './utils/auto-update.js';
25
+ // 启动时自动检查更新已禁用 (改用 --update-check / --update-now 显式触发)
26
+
26
27
 
27
28
  const RESET = '\x1b[0m';
28
29
  const BOLD = '\x1b[1m';
@@ -1516,13 +1517,10 @@ async function main() {
1516
1517
  process.exit(0);
1517
1518
  }
1518
1519
 
1519
- // 启动时检查 npm 包更新
1520
- const updateResult = await checkAndUpdate();
1521
- if (updateResult.hasUpdate && updateResult.updated) {
1522
- console.log(`\n${YELLOW}更新完成,请重新启动 bolloon${RESET}\n`);
1523
- // 更新完成后退出,让用户重新启动
1524
- process.exit(0);
1525
- }
1520
+ // 自动更新已禁用: 启动时不再自动检查/安装更新.
1521
+ // 想手动检查: bolloon --update-check
1522
+ // 想手动更新: bolloon --update-now [package]
1523
+ // 想完全屏蔽 (CI / sandbox): BOLLOON_SKIP_UPDATE=true
1526
1524
 
1527
1525
  const mode = args.web ? 'web' : 'cli';
1528
1526
  const isNonInteractive = !!(args.tool || args.prompt);
@@ -1,5 +1,6 @@
1
1
  import { Endpoint, Connection } from '@rayhanadev/iroh';
2
2
  import * as crypto from 'crypto';
3
+ import { loadOrCreateIrohSecret } from '../agents/iroh-secret.js';
3
4
 
4
5
  export interface IrohMessage {
5
6
  type: string;
@@ -91,6 +92,19 @@ export class IrohTransport {
91
92
  };
92
93
  }
93
94
 
95
+ // 若调用方未传 secretKey,从 ~/.bolloon/iroh-secret-default.json 落盘/读取
96
+ // 保证 iroh nodeId 在同一台机器上跨重启保持稳定
97
+ if (!secretKey) {
98
+ try {
99
+ const sec = loadOrCreateIrohSecret('default');
100
+ // iroh binding 的 secretKey 字段是 hex 字符串 (32 字节 Ed25519 种子)
101
+ secretKey = Buffer.from(sec.secretKey).toString('hex');
102
+ console.log(`[IrohTransport] ${sec.reused ? '复用' : '新建'} iroh-secret-default.json (createdAt=${sec.createdAt})`);
103
+ } catch (e) {
104
+ console.warn('[IrohTransport] iroh-secret 加载失败, 将使用临时身份:', e);
105
+ }
106
+ }
107
+
94
108
  const options: any = { alpns: [IROH_ALPN] };
95
109
  if (secretKey) {
96
110
  options.secretKey = secretKey;
@@ -363,12 +363,22 @@ export async function checkAndUpdate(): Promise<{
363
363
  updated: boolean;
364
364
  message: string;
365
365
  }> {
366
- // 检查是否有 --no-update 标志
366
+ // opt-in: 默认跳过更新检查. 想允许必须显式提供以下任一标志:
367
+ // --update-check, --update-now, --allow-update
368
+ // BOLLOON_AUTO_UPDATE=1
369
+ const allowFlag = process.argv.includes('--update-check')
370
+ || process.argv.includes('--update-now')
371
+ || process.argv.includes('--allow-update');
372
+ const allowEnv = process.env.BOLLOON_AUTO_UPDATE === '1';
373
+ if (!allowFlag && !allowEnv) {
374
+ return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查 (默认关闭, 用 --update-check 显式触发)' };
375
+ }
376
+
377
+ // 显式屏蔽仍然优先
367
378
  if (process.argv.includes('--no-update') || process.argv.includes('--skip-update')) {
368
379
  return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查' };
369
380
  }
370
381
 
371
- // 检查环境变量
372
382
  if (process.env.BOLLOON_SKIP_UPDATE === 'true') {
373
383
  return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查(环境变量)' };
374
384
  }
@@ -0,0 +1,148 @@
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
+
19
+ import express from 'express';
20
+ import {
21
+ buildAgentDelegateRequest, buildAgentResponse, buildManifestPayload, buildManifestRequest,
22
+ parseFrame, setLocalManifest, addLocalAgent, getLocalManifest, getRemoteManifests,
23
+ cacheRemoteManifest, pickAgent, type AgentManifestEntry, type AgentManifest,
24
+ } from '../agents/agent-manifest-protocol.js';
25
+
26
+ export interface DelegateTransport {
27
+ /** 发送 frame 到指定节点公钥, 等待回包. null 表示不实现 (同步返回占位) */
28
+ sendToNode(publicKey: string, frame: string, timeoutMs?: number): Promise<string | null>;
29
+ /** 注册 onMessage: 对方 manifest_request 来了, 我方要回 manifest_payload */
30
+ onIncomingFrame(handler: (fromPublicKey: string, frame: string) => Promise<string | null>): void;
31
+ }
32
+
33
+ export function createAgentDelegateApp(transport: DelegateTransport): express.Express {
34
+ const app = express();
35
+ app.use(express.json({ limit: '2mb' }));
36
+
37
+ // ---- 本地 manifest ----
38
+ app.get('/api/agent/local-manifest', (_req, res) => {
39
+ res.json(getLocalManifest());
40
+ });
41
+
42
+ app.post('/api/agent/register', (req, res) => {
43
+ const body = req.body as { agents: AgentManifestEntry[]; ownerName?: string; ownerPublicKey?: string };
44
+ if (!body.agents || !Array.isArray(body.agents)) {
45
+ return res.status(400).json({ error: 'agents array required' });
46
+ }
47
+ setLocalManifest({
48
+ ownerName: body.ownerName || getLocalManifest().ownerName || 'unknown',
49
+ ownerPublicKey: body.ownerPublicKey || getLocalManifest().ownerPublicKey || '',
50
+ agents: body.agents,
51
+ });
52
+ res.json({ ok: true, manifest: getLocalManifest() });
53
+ });
54
+
55
+ // ---- 远端 manifest 列表 ----
56
+ app.get('/api/agent/remote-manifests', (_req, res) => {
57
+ res.json({ count: getRemoteManifests().length, manifests: getRemoteManifests() });
58
+ });
59
+
60
+ // ---- 按 capability 选 agent ----
61
+ app.post('/api/agent/pick', (req, res) => {
62
+ const { capability, ownerPublicKey } = req.body as { capability: string; ownerPublicKey?: string };
63
+ if (!capability) return res.status(400).json({ error: 'capability required' });
64
+ const picked = pickAgent(capability, ownerPublicKey);
65
+ if (!picked) return res.status(404).json({ error: 'no matching agent', capability });
66
+ res.json({ ok: true, agent: picked.agent, owner: { name: picked.owner.ownerName, publicKey: picked.owner.ownerPublicKey } });
67
+ });
68
+
69
+ // ---- DOC 驱动委派 ----
70
+ app.post('/api/agent/delegate', async (req, res) => {
71
+ try {
72
+ const { toPublicKey, capability, docPath, docContent, instruction, fromAgentId } = req.body as {
73
+ toPublicKey: string;
74
+ capability: string;
75
+ docPath?: string;
76
+ docContent?: string;
77
+ instruction: string;
78
+ fromAgentId?: string;
79
+ };
80
+ if (!toPublicKey || !capability || !instruction) {
81
+ return res.status(400).json({ error: 'toPublicKey, capability, instruction required' });
82
+ }
83
+
84
+ // 1) 优先从已缓存的远端 manifest 里选 agent
85
+ let targetAgent: AgentManifestEntry | null = null;
86
+ const remote = getRemoteManifests().find((m) => m.ownerPublicKey === toPublicKey || m.ownerPublicKey.startsWith(toPublicKey.substring(0, 16)));
87
+ if (remote) {
88
+ targetAgent = remote.agents.find((a) => a.capabilities.includes(capability) && a.status === 'active') || null;
89
+ }
90
+
91
+ // 2) 构造 frame, 通过 transport 发送
92
+ const frame = buildAgentDelegateRequest({
93
+ capability,
94
+ docPath,
95
+ docContent,
96
+ instruction,
97
+ fromAgentId: fromAgentId || 'local-user',
98
+ });
99
+
100
+ const replyFrame = await transport.sendToNode(toPublicKey, frame, 30000);
101
+ if (!replyFrame) {
102
+ return res.status(504).json({ error: 'no response from peer (timeout or transport not wired)' });
103
+ }
104
+ const f = parseFrame(replyFrame);
105
+ if (!f || f.type !== 'agent_response') {
106
+ return res.status(502).json({ error: 'bad response', frame: replyFrame });
107
+ }
108
+ res.json({
109
+ ok: true,
110
+ targetAgent: targetAgent || { id: f.payload.delegatedTo, capabilities: [capability], status: 'active', name: f.payload.delegatedTo },
111
+ response: f.payload,
112
+ });
113
+ } catch (e: any) {
114
+ res.status(500).json({ error: e.message });
115
+ }
116
+ });
117
+
118
+ // ---- 接收对方 manifest_request / agent_delegate 处理 ----
119
+ // 业务端需要把对端来的入站 frame 转给本 handler
120
+ // 简化: 把 handler 挂到 transport.onIncomingFrame
121
+ transport.onIncomingFrame(async (fromPublicKey, frame) => {
122
+ const f = parseFrame(frame);
123
+ if (!f) return null;
124
+ if (f.type === 'manifest_request') {
125
+ return buildManifestPayload(getLocalManifest());
126
+ }
127
+ if (f.type === 'manifest_payload') {
128
+ cacheRemoteManifest(f.payload as AgentManifest);
129
+ return null; // 不需要回包
130
+ }
131
+ if (f.type === 'agent_delegate') {
132
+ // 路由到本地匹配 agent
133
+ const req = f.payload as any;
134
+ const local = getLocalManifest();
135
+ const target = local.agents.find((a) => a.capabilities.includes(req.capability) && a.status === 'active') || local.agents[0];
136
+ if (!target) return buildAgentResponse({ ok: false, delegatedTo: 'none', summary: 'no local agent available' });
137
+ return buildAgentResponse({
138
+ ok: true,
139
+ delegatedTo: target.id,
140
+ resultCid: `mock-${Date.now()}`,
141
+ summary: `[${target.name}] 已处理任务: ${req.instruction?.substring(0, 30)}`,
142
+ });
143
+ }
144
+ return null;
145
+ });
146
+
147
+ return app;
148
+ }