@bolloon/bolloon-agent 0.6.3 → 0.6.5
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/README.md +24 -17
- package/dist/agents/intent-store.js +193 -0
- package/dist/agents/opportunity-inbox.js +147 -0
- package/dist/agents/opportunity-match.js +301 -0
- package/dist/agents/opportunity-web-search.js +183 -0
- package/dist/agents/world-profile.js +49 -0
- package/dist/agents/world-watcher.js +104 -0
- package/dist/cli/commands/tasks.js +191 -1
- package/dist/cli/intent-command.js +338 -0
- package/dist/cli/mention-data.js +3 -0
- package/dist/cli/protocol-envelope.js +2 -0
- package/dist/cli-entry.js +24 -0
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +231 -1
- package/dist/ios/index.html +10 -0
- package/dist/ios/mobile.css +80 -0
- package/dist/ios/mobile.html +10 -0
- package/dist/ios/mobile.js +54 -0
- package/dist/ios/routes-world.js +159 -0
- package/dist/ios/server.js +7 -0
- package/dist/ios/style.css +39 -0
- package/dist/ios/sw.js +1 -1
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +231 -1
- package/dist/web/index.html +40 -2
- package/dist/web/mobile.css +80 -0
- package/dist/web/mobile.html +10 -0
- package/dist/web/mobile.js +54 -0
- package/dist/web/routes-world.js +159 -0
- package/dist/web/server.js +7 -0
- package/dist/web/style.css +39 -0
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { setIntent, listIntents, getIntent, removeIntent } from '../agents/intent-store.js';
|
|
2
|
+
import { scanOpportunities, recordFeedback, readObservations } from '../agents/opportunity-match.js';
|
|
3
|
+
import { readProfile, setProfile } from '../agents/world-profile.js';
|
|
4
|
+
import { worldWatcherStatus } from '../agents/world-watcher.js';
|
|
5
|
+
import { deliverOpportunity, listInboxOpportunities, INBOX_PROTOCOL } from '../agents/opportunity-inbox.js';
|
|
6
|
+
import { searchOpportunitiesForTopic, listSearchOpportunities } from '../agents/opportunity-web-search.js';
|
|
7
|
+
function json(res, status, body) {
|
|
8
|
+
res.status(status).json(body);
|
|
9
|
+
}
|
|
10
|
+
function bodyOf(req) {
|
|
11
|
+
return req.body && typeof req.body === 'object' ? req.body : {};
|
|
12
|
+
}
|
|
13
|
+
/** 限制输入形状: 只收明确字段, 不整个吞前端对象 */
|
|
14
|
+
function intentInputFromBody(body) {
|
|
15
|
+
const b = bodyOf(body);
|
|
16
|
+
const out = {};
|
|
17
|
+
if (typeof b.text === 'string' && b.text.trim())
|
|
18
|
+
out.text = b.text.trim();
|
|
19
|
+
if (Array.isArray(b.tags))
|
|
20
|
+
out.tags = b.tags.filter((t) => typeof t === 'string').map(String).slice(0, 24);
|
|
21
|
+
if (b.priority !== undefined)
|
|
22
|
+
out.priority = Number(b.priority);
|
|
23
|
+
if (typeof b.budget === 'string')
|
|
24
|
+
out.budget = b.budget.trim();
|
|
25
|
+
if (b.deadline !== undefined) {
|
|
26
|
+
const d = typeof b.deadline === 'number' ? b.deadline : Date.parse(String(b.deadline));
|
|
27
|
+
if (!Number.isNaN(d))
|
|
28
|
+
out.deadline = d;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
export function registerWorldRoutes(app) {
|
|
33
|
+
// ── intents ──────────────────────────────────────────────────────────────
|
|
34
|
+
app.get('/api/intents', async (_req, res) => {
|
|
35
|
+
const r = await listIntents();
|
|
36
|
+
if (!r.ok)
|
|
37
|
+
return json(res, 500, { error: r.error });
|
|
38
|
+
const order = { active: 0, paused: 1, done: 2 };
|
|
39
|
+
const rows = [...r.intents].sort((a, b) => order[a.status] - order[b.status] || b.createdAt - a.createdAt);
|
|
40
|
+
json(res, 200, { ok: true, count: rows.length, intents: rows });
|
|
41
|
+
});
|
|
42
|
+
app.post('/api/intents', async (req, res) => {
|
|
43
|
+
const input = intentInputFromBody(req.body);
|
|
44
|
+
if (!input.text)
|
|
45
|
+
return json(res, 400, { ok: false, error: 'text 必填 (声明你现在在做什么)' });
|
|
46
|
+
const r = await setIntent(input);
|
|
47
|
+
if (!r.ok)
|
|
48
|
+
return json(res, 400, { ok: false, error: r.error });
|
|
49
|
+
json(res, r.created ? 201 : 200, { ok: true, created: r.created, intent: r.intent });
|
|
50
|
+
});
|
|
51
|
+
app.get('/api/intents/:id', async (req, res) => {
|
|
52
|
+
const r = await getIntent(req.params.id);
|
|
53
|
+
if (!r.ok)
|
|
54
|
+
return json(res, 500, { error: r.error });
|
|
55
|
+
if (!r.intent)
|
|
56
|
+
return json(res, 404, { ok: false, error: `没有意图 ${req.params.id}` });
|
|
57
|
+
json(res, 200, { ok: true, intent: r.intent });
|
|
58
|
+
});
|
|
59
|
+
app.delete('/api/intents/:id', async (req, res) => {
|
|
60
|
+
const done = req.query.done === '1' || req.query.done === 'true';
|
|
61
|
+
const r = await removeIntent(req.params.id, done);
|
|
62
|
+
if (!r.ok)
|
|
63
|
+
return json(res, 404, { ok: false, error: r.error });
|
|
64
|
+
json(res, 200, { ok: true, removed: r.removed, archived: done });
|
|
65
|
+
});
|
|
66
|
+
// ── opportunities ────────────────────────────────────────────────────────
|
|
67
|
+
app.get('/api/opportunities', async (req, res) => {
|
|
68
|
+
const minScoreRaw = typeof req.query['min-score'] === 'string' ? req.query['min-score'] : '0.4';
|
|
69
|
+
const minScore = Number(minScoreRaw);
|
|
70
|
+
const limitRaw = typeof req.query.limit === 'string' ? req.query.limit : '50';
|
|
71
|
+
const limit = Math.max(1, Math.min(200, parseInt(limitRaw, 10) || 50));
|
|
72
|
+
const r = await scanOpportunities({ minScore: Number.isFinite(minScore) ? minScore : 0.4, limit });
|
|
73
|
+
if (!r.ok)
|
|
74
|
+
return json(res, 500, { ok: false, error: r.error });
|
|
75
|
+
json(res, 200, { ok: true, count: r.results.length, minScore, opportunities: r.results });
|
|
76
|
+
});
|
|
77
|
+
app.post('/api/opportunities/:id/ignore', async (req, res) => {
|
|
78
|
+
const sourceId = String(req.params.id).replace(/^opp_/, 'ann-');
|
|
79
|
+
const r = await recordFeedback('ignore', sourceId);
|
|
80
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, opportunityId: req.params.id, ignored: r.ok, error: r.error });
|
|
81
|
+
});
|
|
82
|
+
app.post('/api/opportunities/:id/accept', async (req, res) => {
|
|
83
|
+
const sourceId = String(req.params.id).replace(/^opp_/, 'ann-');
|
|
84
|
+
const r = await recordFeedback('accept', sourceId);
|
|
85
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, opportunityId: req.params.id, accepted: r.ok, error: r.error });
|
|
86
|
+
});
|
|
87
|
+
// ── 用户画像 (leo 三条分发规划③: 初始化收集 → 常驻意图推送) ─────────────────
|
|
88
|
+
app.get('/api/world/profile', async (_req, res) => {
|
|
89
|
+
const profile = await readProfile();
|
|
90
|
+
json(res, 200, { ok: true, profile });
|
|
91
|
+
});
|
|
92
|
+
app.put('/api/world/profile', async (req, res) => {
|
|
93
|
+
const b = bodyOf(req);
|
|
94
|
+
const r = await setProfile({
|
|
95
|
+
name: typeof b.name === 'string' ? b.name : undefined,
|
|
96
|
+
about: typeof b.about === 'string' ? b.about : undefined,
|
|
97
|
+
tags: Array.isArray(b.tags) ? b.tags.filter((t) => typeof t === 'string') : undefined,
|
|
98
|
+
});
|
|
99
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, profile: r.profile, error: r.error });
|
|
100
|
+
});
|
|
101
|
+
// ── 世界观察器状态 (自动触发机制可见性) ─────────────────────────────────────
|
|
102
|
+
app.get('/api/world/watch', async (_req, res) => {
|
|
103
|
+
const status = worldWatcherStatus();
|
|
104
|
+
const observations = await readObservations(20);
|
|
105
|
+
json(res, 200, { ok: true, watcher: status, recentObservations: observations });
|
|
106
|
+
});
|
|
107
|
+
// ── 机会信箱 (2026-10-05, leo: 别人主动投来的机会 — 真实可信来源) ───────────
|
|
108
|
+
// 外部 agent 用 DIAP Ed25519 身份签名投递机会声明; 验签通过才入库并进世界流。
|
|
109
|
+
// 协议: POST { protocol, title, summary, refs[], provider:{did}, issuedAt } + header X-Signature
|
|
110
|
+
// signature = ed25519Sign(provider 私钥, canonicalize(上述 payload)) — 与 x402 同源 canonicalize
|
|
111
|
+
app.get('/api/world/inbox', async (_req, res) => {
|
|
112
|
+
const list = await listInboxOpportunities();
|
|
113
|
+
json(res, 200, { ok: true, count: list.length, opportunities: list });
|
|
114
|
+
});
|
|
115
|
+
app.post('/api/world/inbox', async (req, res) => {
|
|
116
|
+
const b = bodyOf(req);
|
|
117
|
+
const sig = typeof req.headers['x-signature'] === 'string' ? req.headers['x-signature'] : '';
|
|
118
|
+
const payload = {
|
|
119
|
+
protocol: typeof b.protocol === 'string' ? b.protocol : '',
|
|
120
|
+
title: typeof b.title === 'string' ? b.title : '',
|
|
121
|
+
summary: typeof b.summary === 'string' ? b.summary : '',
|
|
122
|
+
refs: Array.isArray(b.refs) ? b.refs.filter((r) => typeof r === 'string') : [],
|
|
123
|
+
provider: {
|
|
124
|
+
did: typeof b.provider?.did === 'string' ? b.provider.did : '',
|
|
125
|
+
name: typeof b.provider?.name === 'string' ? b.provider.name : undefined,
|
|
126
|
+
},
|
|
127
|
+
issuedAt: typeof b.issuedAt === 'string' ? b.issuedAt : new Date().toISOString(),
|
|
128
|
+
};
|
|
129
|
+
const r = await deliverOpportunity(payload, sig);
|
|
130
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, opportunity: r.opportunity, error: r.error });
|
|
131
|
+
});
|
|
132
|
+
// 协议说明 (外部 agent 对接用 — 怎么构造 payload / 怎么签名)
|
|
133
|
+
app.get('/api/world/inbox/protocol', async (_req, res) => {
|
|
134
|
+
json(res, 200, {
|
|
135
|
+
ok: true,
|
|
136
|
+
protocol: INBOX_PROTOCOL,
|
|
137
|
+
description: '外部 agent 主动投递机会到信箱: POST /api/world/inbox, header X-Signature = Ed25519(provider 私钥, canonicalize(payload))',
|
|
138
|
+
payloadFields: ['protocol', 'title', 'summary', 'refs[]', 'provider.did', 'provider.name?', 'issuedAt'],
|
|
139
|
+
signature: 'ed25519Sign(providerPrivateKey, canonicalize({protocol,title,summary,refs,provider,issuedAt}))',
|
|
140
|
+
verification: '本机解析 provider.did → Ed25519 公钥 → 验签; 通过才入库 (verified) 并进世界流, 否则拒绝',
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
// ── AI 主动搜索 (2026-10-05, leo: 系统要主动上网搜机会, 非只被动响应) ─────────
|
|
144
|
+
// POST /api/world/search [{topic}] → 按 topic 搜机会落 world/search/ 并进世界流
|
|
145
|
+
app.post('/api/world/search', async (req, res) => {
|
|
146
|
+
const b = bodyOf(req);
|
|
147
|
+
const topic = typeof b.topic === 'string' && b.topic.trim() ? b.topic.trim()
|
|
148
|
+
: (typeof b.text === 'string' && b.text.trim() ? b.text.trim() : '');
|
|
149
|
+
if (!topic)
|
|
150
|
+
return json(res, 400, { ok: false, error: '缺 topic (要搜什么机会)' });
|
|
151
|
+
const r = await searchOpportunitiesForTopic(topic, { limit: 6 });
|
|
152
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, topic, added: r.added, total: r.total, opportunities: r.results, error: r.error });
|
|
153
|
+
});
|
|
154
|
+
// GET /api/world/search → 已搜到的机会 (最近在前)
|
|
155
|
+
app.get('/api/world/search', async (_req, res) => {
|
|
156
|
+
const list = await listSearchOpportunities();
|
|
157
|
+
json(res, 200, { ok: true, count: list.length, opportunities: list });
|
|
158
|
+
});
|
|
159
|
+
}
|
package/dist/web/server.js
CHANGED
|
@@ -11,6 +11,8 @@ import { validateMessageInput, validateChannelInput, healthCheck, } from './inpu
|
|
|
11
11
|
import { segmentChatReply } from '../agents/chat-segmenter.js';
|
|
12
12
|
import { registerJudgmentsRoutes } from './routes-judgments.js';
|
|
13
13
|
import { registerLlmConfigRoutes } from './routes-llm-config.js';
|
|
14
|
+
import { startWorldWatcher } from '../agents/world-watcher.js';
|
|
15
|
+
import { registerWorldRoutes } from './routes-world.js';
|
|
14
16
|
import { registerExternalEngineRoutes } from './routes-external-engines.js';
|
|
15
17
|
import { registerTaskRoutes } from './routes-tasks.js';
|
|
16
18
|
// 2026-09-19: 联系方式 / 人工批准 / 手机—桌面配对 路由
|
|
@@ -6956,6 +6958,11 @@ fetchState();
|
|
|
6956
6958
|
registerOnchainTradeRoutes(app);
|
|
6957
6959
|
// 2026-07-06: LLM/Video/Audio 配置路由抽到 ./routes-llm-config.ts
|
|
6958
6960
|
registerLlmConfigRoutes(app);
|
|
6961
|
+
// 2026-10-05 (leo 四端 Intent Network P1): World API — 意图声明 + 机会匹配 (Web/Mobile 共用)
|
|
6962
|
+
registerWorldRoutes(app);
|
|
6963
|
+
// 2026-10-05 (leo 三条分发规划①): 自动触发机制 —— 世界观察器常驻,
|
|
6964
|
+
// 定时扫描 + 观察日志自动积累, 不依赖用户打开界面 (AI 主动看世界)。
|
|
6965
|
+
startWorldWatcher();
|
|
6959
6966
|
// 2026-07-22: 外部编码智能体 (codex/claude-code/opencode/openclaw/hermes/实验 API)
|
|
6960
6967
|
// 发现 + 配置为供应商 + 委派
|
|
6961
6968
|
registerExternalEngineRoutes(app);
|
package/dist/web/style.css
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
box-sizing: border-box;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/* 2026-10-05 (leo): hidden 属性必须真隐藏 —— 之前 .messages{display:flex} 覆盖了
|
|
8
|
+
UA 的 [hidden]{display:none}, 导致世界栏下面一直显示聊天记录 (污染)。 */
|
|
9
|
+
[hidden] {
|
|
10
|
+
display: none !important;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
:root {
|
|
8
14
|
--bg: #1a1a18;
|
|
9
15
|
--bg-sidebar: #222220;
|
|
@@ -1858,6 +1864,39 @@ body {
|
|
|
1858
1864
|
font-weight: normal;
|
|
1859
1865
|
color: var(--text-muted);
|
|
1860
1866
|
}
|
|
1867
|
+
|
|
1868
|
+
/* 2026-10-05 (leo): 世界/聊天切换放进原导航栏 (header) 中间 — 与钱包/配置同栏 */
|
|
1869
|
+
.header-tabs {
|
|
1870
|
+
display: flex;
|
|
1871
|
+
gap: 2px;
|
|
1872
|
+
margin-left: 18px;
|
|
1873
|
+
padding: 3px;
|
|
1874
|
+
background: var(--bg-hover);
|
|
1875
|
+
border-radius: 8px;
|
|
1876
|
+
flex-shrink: 0;
|
|
1877
|
+
}
|
|
1878
|
+
.header-tab {
|
|
1879
|
+
padding: 5px 14px;
|
|
1880
|
+
background: transparent;
|
|
1881
|
+
border: none;
|
|
1882
|
+
border-radius: 6px;
|
|
1883
|
+
color: var(--text-secondary);
|
|
1884
|
+
font-size: 12px;
|
|
1885
|
+
font-weight: 600;
|
|
1886
|
+
cursor: pointer;
|
|
1887
|
+
transition: var(--transition);
|
|
1888
|
+
display: flex;
|
|
1889
|
+
align-items: center;
|
|
1890
|
+
gap: 4px;
|
|
1891
|
+
}
|
|
1892
|
+
.header-tab:hover {
|
|
1893
|
+
color: var(--text-primary);
|
|
1894
|
+
}
|
|
1895
|
+
.header-tab.active {
|
|
1896
|
+
background: var(--bg-main);
|
|
1897
|
+
color: var(--accent-hover);
|
|
1898
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
1899
|
+
}
|
|
1861
1900
|
.judgment-status-bar {
|
|
1862
1901
|
display: flex;
|
|
1863
1902
|
gap: 6px;
|
package/dist/web/sw.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 现在: 代码类资源 (html/js/css/json) 一律 **network-first** (拿不到网才回退缓存),
|
|
7
7
|
* 缓存名带版本号, activate 时清掉所有旧缓存; 图标等静态资源仍 cache-first。
|
|
8
8
|
*/
|
|
9
|
-
const CACHE = 'bolloon-mobile-v0.6.
|
|
9
|
+
const CACHE = 'bolloon-mobile-v0.6.5';
|
|
10
10
|
const SHELL = [
|
|
11
11
|
'./mobile.html',
|
|
12
12
|
'./mobile.css',
|