@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.
@@ -127,6 +127,13 @@
127
127
  </svg>
128
128
  <span id="task-badge" class="task-badge" style="display:none;">0</span>
129
129
  </button>
130
+ <button id="judgments-btn" class="header-action" title="我的判断">
131
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
132
+ <path d="M12 2L4 6v6c0 5 3.5 9.5 8 10 4.5-.5 8-5 8-10V6l-8-4z"></path>
133
+ <path d="M9 12l2 2 4-4"></path>
134
+ </svg>
135
+ <span id="judgments-badge" class="task-badge" style="display:none;">0</span>
136
+ </button>
130
137
  </div>
131
138
  </header>
132
139
 
@@ -190,6 +197,73 @@
190
197
  </div>
191
198
  </div>
192
199
 
200
+ <!-- Judgments Modal (v1 核心) -->
201
+ <div id="judgments-modal" class="modal">
202
+ <div class="modal-content modal-wide">
203
+ <div class="modal-header">
204
+ <h2>我的判断</h2>
205
+ <button id="judgments-modal-close" class="modal-close">&times;</button>
206
+ </div>
207
+ <div class="modal-body">
208
+ <div class="form-group">
209
+ <label>判断 (decision)</label>
210
+ <textarea id="judgment-decision" rows="2" placeholder="例: 不在时让 AI 替我做决定"></textarea>
211
+ </div>
212
+ <div class="form-group">
213
+ <label>理由 (reason, 可选)</label>
214
+ <input type="text" id="judgment-reason" placeholder="例: 信任 Bolloon 的判断存储">
215
+ </div>
216
+ <div class="form-group" style="display:flex;gap:8px;align-items:center;">
217
+ <label style="margin:0;">领域 (domain)</label>
218
+ <select id="judgment-domain">
219
+ <option value="general">general</option>
220
+ <option value="code">code</option>
221
+ <option value="architecture">architecture</option>
222
+ <option value="security">security</option>
223
+ <option value="testing">testing</option>
224
+ </select>
225
+ <label style="margin:0 0 0 16px;">风险 (stakes)</label>
226
+ <select id="judgment-stakes">
227
+ <option value="medium">medium</option>
228
+ <option value="low">low</option>
229
+ <option value="high">high</option>
230
+ <option value="critical">critical</option>
231
+ </select>
232
+ </div>
233
+ <div class="btn-group" style="gap:8px;">
234
+ <button id="judgment-submit-btn" class="btn-primary">记录</button>
235
+ <button id="judgment-import-btn" class="btn-secondary" title="从 .json / .yaml / .md / .txt / .html 文件批量导入">导入文件</button>
236
+ <input type="file" id="judgment-import-file" accept=".json,.yaml,.yml,.md,.txt,.html,.htm" style="display:none">
237
+ </div>
238
+ <div id="judgment-error" class="form-info" style="display:none;color:#b91c1c;"></div>
239
+ <!-- v3 重做: tab 切换 — 默认显示当前 channel 的 judgment 上下文, 切换后才是全部 -->
240
+ <div id="judgments-tabs" style="display:flex;border-bottom:1px solid #e5e7eb;margin-top:20px;">
241
+ <button class="judgment-tab active" data-tab="channel"
242
+ style="flex:1;padding:10px 12px;background:none;border:none;border-bottom:2px solid #2563eb;color:#2563eb;font-size:13px;font-weight:600;cursor:pointer;">
243
+ 本 channel <span id="judgments-tab-channel-name" style="font-weight:normal;color:#6b7280;"></span>
244
+ </button>
245
+ <button class="judgment-tab" data-tab="global"
246
+ style="flex:1;padding:10px 12px;background:none;border:none;border-bottom:2px solid transparent;color:#6b7280;font-size:13px;font-weight:600;cursor:pointer;">
247
+ 全局
248
+ </button>
249
+ </div>
250
+ <h3 style="margin-top:16px;font-size:14px;font-weight:600;display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
251
+ <span id="judgments-list-title">本 channel 的判断力</span>
252
+ <span style="display:flex;align-items:center;gap:6px;margin-left:auto;font-size:12px;font-weight:normal;">
253
+ <label style="display:flex;align-items:center;gap:4px;cursor:pointer;color:#6b7280;">
254
+ <input type="checkbox" id="judgment-select-all" style="cursor:pointer;"> 全选
255
+ </label>
256
+ <span id="judgment-selected-count" style="color:#6b7280;">已选 0</span>
257
+ <button id="judgment-bulk-delete-btn" class="btn-secondary btn-sm" disabled style="background:none;border:1px solid #fca5a5;color:#b91c1c;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;opacity:0.5;">批量删除</button>
258
+ </span>
259
+ </h3>
260
+ <div id="judgments-list" class="task-list" style="max-height:420px;overflow-y:auto;">
261
+ <div class="task-empty">加载中...</div>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>
266
+
193
267
  <!-- Agent Add / Wallet / Auto-tool Modal -->
194
268
  <div id="agent-add-modal" class="modal">
195
269
  <div class="modal-content">
@@ -0,0 +1,125 @@
1
+ /**
2
+ * iroh-delegate-transport — 把 irohTransport 适配成 DelegateTransport 抽象
3
+ *
4
+ * 目的: 让 agent-delegate-server 可以挂到 iroh transport 上, 而不是只在 Hyperswarm 测试里跑.
5
+ *
6
+ * 注意命名坑:
7
+ * - DelegateTransport.sendToNode 用的是 "publicKey" (Hyperswarm 测试用 Hyperswarm 公钥)
8
+ * - iroh 路径里我们没有 "publicKey", 只有 nodeId (string)
9
+ * - 这里我们把 iroh nodeId 直接当作 publicKey 字段传入. 调用方 (/api/agent/delegate) 的
10
+ * toPublicKey 参数实际上对应的就是 iroh 目标 nodeId.
11
+ * - DIAP 真实 did:key 与 iroh nodeId 的映射关系另由 agent-manifest 协议承担
12
+ * (manifest_payload.ownerPublicKey 字段), 后续在 manifest cache 里维护.
13
+ */
14
+ import { irohTransport } from '../network/iroh-transport.js';
15
+ import { parseFrame } from '../agents/agent-manifest-protocol.js';
16
+ /**
17
+ * 构造一个基于 irohTransport 的 DelegateTransport 实现.
18
+ *
19
+ * sendToNode: 用 irohTransport.sendMessage 发送 frame, 然后用 requestResponse 等待
20
+ * 对端 via onIncomingFrame 注册的 handler 写回.
21
+ * 由于 iroh 1-shot 消息没有 req/resp 关联, 这里用 'agent_request' 类型
22
+ * 包一层, 在 onMessage('agent_request') 内部复用现有 handler, 拿到 reply
23
+ * 再用 'agent_response' 单播回原 node.
24
+ *
25
+ * onIncomingFrame: 把对方发的 manifest_request / manifest_payload / agent_delegate
26
+ * 类型消息路由到 agent-delegate-server 注册的 handler.
27
+ */
28
+ export function createIrohDelegateTransport(opts = {}) {
29
+ const timeoutMs = opts.timeoutMs ?? 30000;
30
+ const verbose = opts.verbose ?? false;
31
+ let incomingHandler = null;
32
+ // 单播回包映射: requestId -> resolver
33
+ const pendingReplies = new Map();
34
+ // 启动时挂一次 onMessage 监听 (重复挂也只会换 handler, 不重复触发)
35
+ irohTransport.onMessage('agent_request', async (msg) => {
36
+ if (!incomingHandler)
37
+ return;
38
+ const f = parseFrame(new TextDecoder().decode(msg.payload));
39
+ if (!f)
40
+ return;
41
+ const fromKey = msg.from;
42
+ const reply = await incomingHandler(fromKey, new TextDecoder().decode(msg.payload));
43
+ if (reply) {
44
+ try {
45
+ // 把 reply 也用 agent_response 类型发回去, requestId 透传
46
+ const replyFrame = JSON.parse(reply);
47
+ const reqId = f._reqId || replyFrame._reqId;
48
+ const tagged = reqId ? JSON.stringify({ ...replyFrame, _reqId: reqId }) : reply;
49
+ await irohTransport.sendMessage(fromKey, 'agent_response', new TextEncoder().encode(tagged));
50
+ if (verbose)
51
+ console.log(`[iroh-delegate] 已回包给 ${fromKey.substring(0, 12)}... (${replyFrame.type})`);
52
+ }
53
+ catch (e) {
54
+ if (verbose)
55
+ console.warn('[iroh-delegate] 回包失败:', e);
56
+ }
57
+ }
58
+ });
59
+ irohTransport.onMessage('agent_response', (msg) => {
60
+ const f = parseFrame(new TextDecoder().decode(msg.payload));
61
+ if (!f)
62
+ return;
63
+ const reqId = f._reqId;
64
+ if (!reqId)
65
+ return;
66
+ const pending = pendingReplies.get(reqId);
67
+ if (pending) {
68
+ clearTimeout(pending.timer);
69
+ pendingReplies.delete(reqId);
70
+ pending.resolve(JSON.stringify(f));
71
+ }
72
+ });
73
+ return {
74
+ sendToNode: async (publicKey, frame, timeoutOverrideMs) => {
75
+ const t = timeoutOverrideMs ?? timeoutMs;
76
+ const reqId = `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
77
+ const tagged = JSON.stringify({ ...JSON.parse(frame), _reqId: reqId });
78
+ const payload = new TextEncoder().encode(tagged);
79
+ return new Promise(async (resolve) => {
80
+ const timer = setTimeout(() => {
81
+ pendingReplies.delete(reqId);
82
+ resolve(null);
83
+ }, t);
84
+ pendingReplies.set(reqId, { resolve, timer });
85
+ try {
86
+ const ok = await irohTransport.sendMessage(publicKey, 'agent_request', payload);
87
+ if (!ok) {
88
+ clearTimeout(timer);
89
+ pendingReplies.delete(reqId);
90
+ resolve(null);
91
+ }
92
+ }
93
+ catch (e) {
94
+ if (verbose)
95
+ console.warn('[iroh-delegate] 发送失败:', e);
96
+ clearTimeout(timer);
97
+ pendingReplies.delete(reqId);
98
+ resolve(null);
99
+ }
100
+ });
101
+ },
102
+ onIncomingFrame: (handler) => {
103
+ incomingHandler = handler;
104
+ },
105
+ };
106
+ }
107
+ /**
108
+ * 把本地节点 manifest 注册到 agent-manifest 协议, 并写入本地缓存.
109
+ * 在 iroh 初始化完成时调用一次.
110
+ */
111
+ export function registerLocalAgents(ownerName, ownerPublicKey, agents) {
112
+ // 动态导入避免循环引用
113
+ import('../agents/agent-manifest-protocol.js').then((mod) => {
114
+ mod.setLocalManifest({
115
+ ownerName,
116
+ ownerPublicKey,
117
+ agents: agents.map((a) => ({
118
+ id: a.id,
119
+ name: a.name,
120
+ capabilities: a.capabilities,
121
+ status: a.status || 'active',
122
+ })),
123
+ });
124
+ });
125
+ }