@bolloon/bolloon-agent 0.6.2 → 0.6.4
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/gateway-group.js +172 -4
- package/dist/agents/group-autopilot.js +29 -1
- package/dist/agents/intent-store.js +193 -0
- package/dist/agents/opportunity-match.js +260 -0
- package/dist/agents/pi-sdk-tools.js +22 -2
- package/dist/agents/pi-sdk.js +8 -1
- package/dist/agents/world-profile.js +49 -0
- package/dist/agents/world-watcher.js +75 -0
- package/dist/cli/commands/tasks.js +191 -1
- package/dist/cli/intent-command.js +338 -0
- package/dist/cli/mention-data.js +4 -1
- package/dist/cli/model-selector.js +84 -104
- package/dist/cli/protocol-envelope.js +2 -0
- package/dist/cli/setup-wizard.js +2 -10
- package/dist/cli/tui-select.js +99 -10
- package/dist/cli-entry.js +25 -9
- package/dist/electron-build/agents/gateway-group.js +177 -3
- package/dist/electron-build/agents/gateway-group.js.map +1 -1
- package/dist/electron-build/agents/group-autopilot.js +30 -2
- package/dist/electron-build/agents/group-autopilot.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +21 -1
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +8 -1
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +2 -2
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/model-selection.js +16 -2
- package/dist/electron-build/llm/model-selection.js.map +1 -1
- package/dist/electron-build/llm/pi-ai.js +2 -2
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/index.js +121 -3
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +232 -0
- 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 +105 -0
- package/dist/ios/server.js +7 -0
- package/dist/ios/sw.js +1 -1
- package/dist/llm/config-store.js +2 -2
- package/dist/llm/model-selection.js +16 -2
- package/dist/llm/pi-ai.js +2 -2
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +232 -0
- package/dist/web/index.html +57 -0
- 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 +105 -0
- package/dist/web/server.js +7 -0
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ export function parseGroupSub(input) {
|
|
|
49
49
|
// 与 `/channel <序号>` 同一个习惯 ✓ (列表里就带序号)
|
|
50
50
|
if (/^\d+$/.test(head))
|
|
51
51
|
return { sub: 'use', arg: head };
|
|
52
|
-
const known = ['list', 'new', 'join', 'leave', 'use', 'send', 'log', 'members', 'help'];
|
|
52
|
+
const known = ['list', 'new', 'join', 'leave', 'use', 'send', 'log', 'members', 'invite', 'kick', 'link', 'privacy', 'status', 'help'];
|
|
53
53
|
return { sub: known.includes(sub) ? sub : 'help', arg: known.includes(sub) ? arg : rest };
|
|
54
54
|
}
|
|
55
55
|
export const CLI_KNOWN_COMMAND_HEADS = new Set([
|
|
@@ -398,7 +398,7 @@ async function bootstrapIdentity() {
|
|
|
398
398
|
writeOut(` ${reused ? GRAY + '复用 ' : ''}${GRAY}DID:${RESET} ${did}`);
|
|
399
399
|
writeOut(` ${GRAY}名称:${RESET} ${name}`);
|
|
400
400
|
s.step(1, 5, reused ? '复用 DIAP 身份' : '生成 DIAP 身份', 'ok');
|
|
401
|
-
return { keypair: kp, did, name };
|
|
401
|
+
return { keypair: kp, did, name, created: !reused };
|
|
402
402
|
}
|
|
403
403
|
function publishDID(name, kp) {
|
|
404
404
|
// 2026-06-17: 去掉 IPNS 重试机制 — 老逻辑 60s × 10 次 = 10 分钟阻塞,
|
|
@@ -3878,6 +3878,11 @@ async function processInputInner(input, comm) {
|
|
|
3878
3878
|
dim(' /group send <文本> 往当前群发一条');
|
|
3879
3879
|
dim(' /group log [N] 看当前群最近 N 条 (默认 10)');
|
|
3880
3880
|
dim(' /group members 列当前群成员');
|
|
3881
|
+
dim(' /group invite <orbitdbId> 邀请进白名单 (门控群; 重建群地址)');
|
|
3882
|
+
dim(' /group kick <orbitdbId> 踢出白名单 (门控群; 重建群地址)');
|
|
3883
|
+
dim(' /group link 分享当前群链接');
|
|
3884
|
+
dim(' /group privacy on|off 隐私群标记 (默认建群即隐私)');
|
|
3885
|
+
dim(' /group status 群状态: 成员/消息/白名单/门控/隐私');
|
|
3881
3886
|
return;
|
|
3882
3887
|
}
|
|
3883
3888
|
if (sub === 'list') {
|
|
@@ -3983,6 +3988,116 @@ async function processInputInner(input, comm) {
|
|
|
3983
3988
|
dim(ms.length ? ms.join('\n') : '(暂无成员记录)');
|
|
3984
3989
|
return;
|
|
3985
3990
|
}
|
|
3991
|
+
// ── 2026-10-05 (leo): 群运营能力 —— 邀请/踢出/链接/隐私/状态
|
|
3992
|
+
if (sub === 'invite') {
|
|
3993
|
+
const gid = await ensureCurrentGroup(gg);
|
|
3994
|
+
if (!gid)
|
|
3995
|
+
return;
|
|
3996
|
+
const g = await gg.groupInfo(gid);
|
|
3997
|
+
if (!g) {
|
|
3998
|
+
dim('群不存在');
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
if (!g.gated) {
|
|
4002
|
+
appendLine(`${C_DIM}「${g.name}」是开放群 (write:[*]) —— 邀请 = 把链接发给对方自行加入:${RESET}\n${C_ACCENT}${g.link}${RESET}`);
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
if (!arg) {
|
|
4006
|
+
dim('用法: /group invite <orbitdbId> (门控群: 把对方加进白名单, 会重建群地址)');
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
const ref = {
|
|
4010
|
+
did: arg.startsWith('did:') ? arg : `did:key:z${arg.slice(0, 40)}`,
|
|
4011
|
+
publicKeyHex: '',
|
|
4012
|
+
orbitdbId: arg.startsWith('did:') ? (await gg.groupMembers(gid).catch(() => [])).find((m) => m === arg) || arg : arg,
|
|
4013
|
+
};
|
|
4014
|
+
const r = await gg.inviteMember(gid, ref);
|
|
4015
|
+
if (!r.ok) {
|
|
4016
|
+
appendLine(`${C_WARN}邀请失败: ${r.error}${RESET}`);
|
|
4017
|
+
return;
|
|
4018
|
+
}
|
|
4019
|
+
appendLine(renderMessageBox({
|
|
4020
|
+
title: `✅ 已邀请进白名单: ${r.group?.name}`,
|
|
4021
|
+
body: `新地址: ${r.group?.link}\n\n⚠ 重建代价 (IPFS 白名单不能就地改):\n${(r.costs || []).slice(0, 3).map((c) => `· ${c}`).join('\n')}\n\n把新链接发给对方, 用 /group join <链接> 加入 (老链接作废)`,
|
|
4022
|
+
color: C_ACCENT, maxLines: 12,
|
|
4023
|
+
}));
|
|
4024
|
+
currentGroupId = r.group?.id || currentGroupId;
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
4027
|
+
if (sub === 'kick') {
|
|
4028
|
+
const gid = await ensureCurrentGroup(gg);
|
|
4029
|
+
if (!gid)
|
|
4030
|
+
return;
|
|
4031
|
+
const g = await gg.groupInfo(gid);
|
|
4032
|
+
if (!g) {
|
|
4033
|
+
dim('群不存在');
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
4036
|
+
if (!g.gated) {
|
|
4037
|
+
dim('「' + g.name + '」是开放群 (write:[*]), 没有踢人语义 (人人可写)');
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
4040
|
+
if (!arg) {
|
|
4041
|
+
dim('用法: /group kick <orbitdbId> (从白名单移除; 重建群地址)');
|
|
4042
|
+
return;
|
|
4043
|
+
}
|
|
4044
|
+
const r = await gg.kickMember(gid, arg);
|
|
4045
|
+
if (!r.ok) {
|
|
4046
|
+
appendLine(`${C_WARN}踢出失败: ${r.error}${RESET}`);
|
|
4047
|
+
return;
|
|
4048
|
+
}
|
|
4049
|
+
appendLine(renderMessageBox({
|
|
4050
|
+
title: `✅ 已移出白名单: ${r.group?.name}`,
|
|
4051
|
+
body: `新地址: ${r.group?.link}\n\n⚠ 重建代价:\n${(r.costs || []).slice(0, 3).map((c) => `· ${c}`).join('\n')}\n\n被踢者老链接作废; 其他成员要用新链接重新加入`,
|
|
4052
|
+
color: C_ACCENT, maxLines: 12,
|
|
4053
|
+
}));
|
|
4054
|
+
currentGroupId = r.group?.id || currentGroupId;
|
|
4055
|
+
return;
|
|
4056
|
+
}
|
|
4057
|
+
if (sub === 'link') {
|
|
4058
|
+
const gid = await ensureCurrentGroup(gg);
|
|
4059
|
+
if (!gid)
|
|
4060
|
+
return;
|
|
4061
|
+
const g = await gg.groupInfo(gid);
|
|
4062
|
+
if (!g) {
|
|
4063
|
+
dim('群不存在');
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
appendLine(renderMessageBox({ title: `群链接: ${g.name}`, body: `${g.link}\n\n复制发给同伴, 用 /group join <链接> 加入`, color: C_ACCENT, maxLines: 8 }));
|
|
4067
|
+
return;
|
|
4068
|
+
}
|
|
4069
|
+
if (sub === 'privacy') {
|
|
4070
|
+
const gid = await ensureCurrentGroup(gg);
|
|
4071
|
+
if (!gid)
|
|
4072
|
+
return;
|
|
4073
|
+
const on = arg === 'on' || arg === 'true' || arg === '1';
|
|
4074
|
+
const off = arg === 'off' || arg === 'false' || arg === '0';
|
|
4075
|
+
if (!on && !off) {
|
|
4076
|
+
dim('用法: /group privacy on|off (隐私群标记, 只影响本机展示/默认 ACL 说明)');
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
const r = await gg.setGroupPrivacy(gid, on);
|
|
4080
|
+
appendLine(r.ok ? `${C_ACCENT}✓ 隐私群 ${on ? '开启' : '关闭'}: ${r.group?.name}${RESET}${C_DIM} (白名单/ACL 由建群时决定, 本标记只做展示)${RESET}` : `${C_WARN}设置失败: ${r.error}${RESET}`);
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
if (sub === 'status') {
|
|
4084
|
+
const gid = await ensureCurrentGroup(gg);
|
|
4085
|
+
if (!gid)
|
|
4086
|
+
return;
|
|
4087
|
+
const s = await gg.groupStatus(gid);
|
|
4088
|
+
if (!s.ok || !s.status) {
|
|
4089
|
+
appendLine(`${C_WARN}${s.error}${RESET}`);
|
|
4090
|
+
return;
|
|
4091
|
+
}
|
|
4092
|
+
const st = s.status;
|
|
4093
|
+
const wl = st.writeList.slice(0, 12).join('\n ');
|
|
4094
|
+
appendLine(renderMessageBox({
|
|
4095
|
+
title: `群状态: ${st.group.name}`,
|
|
4096
|
+
body: `隐私: ${st.privacy ? '🔒 是' : '公开'} · 门控: ${st.gated ? '是' : '否'} · 成员: ${st.memberCount} · 消息: ${st.messageCount}\n最后同步: ${st.lastSyncAt || '-'}\n群主: ${st.ownerDid ? String(st.ownerDid).slice(0, 32) + '…' : '-'}\n\n写白名单 (${st.writeList.length}):\n ${wl || '(无)'}`,
|
|
4097
|
+
color: C_ACCENT, maxLines: 20,
|
|
4098
|
+
}));
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
3986
4101
|
dim('用法: /group help');
|
|
3987
4102
|
}
|
|
3988
4103
|
catch (e) {
|
|
@@ -6135,7 +6250,7 @@ async function main() {
|
|
|
6135
6250
|
s.warn('未设置任何 LLM API Key,功能受限');
|
|
6136
6251
|
}
|
|
6137
6252
|
}
|
|
6138
|
-
const { keypair, did, name } = await bootstrapIdentity();
|
|
6253
|
+
const { keypair, did, name, created } = await bootstrapIdentity();
|
|
6139
6254
|
agentIdentity = { did, name, publicKey: Buffer.from(keypair.publicKey).toString('hex') };
|
|
6140
6255
|
// 2026-08-07: CLI 模式后台自动安装/启动本地 Kubo (web 模式 server.ts 已有, CLI 缺 → IPNS 发布后无法解析的根因)
|
|
6141
6256
|
// fire-and-forget, 不阻塞启动; Kubo 就绪后才发布 DID (避免 registerAgent 在 Kubo 未启动时 30s 超时)
|
|
@@ -6161,6 +6276,9 @@ async function main() {
|
|
|
6161
6276
|
catch (e) {
|
|
6162
6277
|
bootNotice('warn', `Kubo 自动安装失败 (非致命): ${String(e?.message || e).slice(0, 120)}`);
|
|
6163
6278
|
}
|
|
6279
|
+
// DID 已存在时不重复发布;只有首次创建或显式要求刷新才发布。
|
|
6280
|
+
if (!created && process.env.BOLLOON_REFRESH_DID !== '1')
|
|
6281
|
+
return;
|
|
6164
6282
|
publishDID(name, keypair).then(({ cid, ipnsName }) => {
|
|
6165
6283
|
if (cid)
|
|
6166
6284
|
agentIdentity.cid = cid;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "bolloon-mobile-web/1",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"channel": "stable",
|
|
5
5
|
"build": "stable",
|
|
6
|
-
"commit": "
|
|
7
|
-
"builtAt": "2026-10-
|
|
6
|
+
"commit": "d0d0f60ab5efd7e473c1aac60cd63aa97fc4e331",
|
|
7
|
+
"builtAt": "2026-10-05T10:43:20.796Z",
|
|
8
8
|
"layer": "web",
|
|
9
9
|
"note": "web 资源层构建戳 (手机端 OTA 的身份第 ③ 级回退; 装机后以 OTA state.json 为准)"
|
|
10
10
|
}
|
package/dist/ios/client.js
CHANGED
|
@@ -4992,6 +4992,238 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4992
4992
|
if (e.target === judgmentsModal) hideJudgmentsModal();
|
|
4993
4993
|
});
|
|
4994
4994
|
}
|
|
4995
|
+
var worldView = document.getElementById("world-view");
|
|
4996
|
+
var worldBtn = document.getElementById("world-btn");
|
|
4997
|
+
var worldViewClose = document.getElementById("world-view-close");
|
|
4998
|
+
var worldRefreshBtn = document.getElementById("world-refresh-btn");
|
|
4999
|
+
var worldIntentInput = document.getElementById("world-intent-input");
|
|
5000
|
+
var worldIntentPriority = document.getElementById("world-intent-priority");
|
|
5001
|
+
var worldIntentBudget = document.getElementById("world-intent-budget");
|
|
5002
|
+
var worldIntentSet = document.getElementById("world-intent-set");
|
|
5003
|
+
var worldError = document.getElementById("world-intent-error");
|
|
5004
|
+
var worldIntentsList = document.getElementById("world-intents-list");
|
|
5005
|
+
var worldOpportunities = document.getElementById("world-opportunities");
|
|
5006
|
+
var worldStats = document.getElementById("world-stats");
|
|
5007
|
+
var worldBadge = document.getElementById("world-badge");
|
|
5008
|
+
var worldProfileInput = document.getElementById("world-profile-input");
|
|
5009
|
+
var worldProfileSet = document.getElementById("world-profile-set");
|
|
5010
|
+
var worldLoaded = false;
|
|
5011
|
+
var worldPollTimer = null;
|
|
5012
|
+
var WORLD_POLL_MS = 3e4;
|
|
5013
|
+
function worldStatus(msg) {
|
|
5014
|
+
if (worldError) {
|
|
5015
|
+
worldError.textContent = msg || "";
|
|
5016
|
+
worldError.style.display = msg ? "" : "none";
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
async function fetchWorldIntents() {
|
|
5020
|
+
try {
|
|
5021
|
+
const res = await fetch("/api/intents");
|
|
5022
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
5023
|
+
const data = await res.json();
|
|
5024
|
+
renderWorldIntents(data.intents || []);
|
|
5025
|
+
if (worldStats) {
|
|
5026
|
+
const active = (data.intents || []).filter((i) => i.status === "active").length;
|
|
5027
|
+
worldStats.textContent = `\u610F\u56FE ${active} \u4E2A\u6D3B\u8DC3 \xB7 \u4E16\u754C\u6301\u7EED\u89C2\u5BDF\u4E2D`;
|
|
5028
|
+
}
|
|
5029
|
+
return data.intents || [];
|
|
5030
|
+
} catch (e) {
|
|
5031
|
+
worldStatus("\u62C9\u53D6\u610F\u56FE\u5931\u8D25: " + (e && e.message ? e.message : e));
|
|
5032
|
+
return [];
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
function renderWorldIntents(intents) {
|
|
5036
|
+
if (!worldIntentsList) return;
|
|
5037
|
+
if (!intents.length) {
|
|
5038
|
+
worldIntentsList.innerHTML = '<div class="form-hint" style="font-size:11px;">(\u8FD8\u6CA1\u6709\u610F\u56FE \u2014 \u53F3\u8FB9\u58F0\u660E\u4E00\u4E2A)</div>';
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
const active = intents.filter((i) => i.status === "active");
|
|
5042
|
+
const rows = active.map((i) => {
|
|
5043
|
+
const stars = "\u2605".repeat(i.priority) + "\u2606".repeat(5 - i.priority);
|
|
5044
|
+
return `<div class="world-intent-row" style="display:flex;justify-content:space-between;align-items:center;padding:6px 8px;border:1px solid var(--border,#3a3a36);border-radius:8px;margin-bottom:6px;font-size:11px;">
|
|
5045
|
+
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"><b>${i.text.replace(/</g, "<")}</b> <span style="color:#909088;">${stars}</span></span>
|
|
5046
|
+
<button class="btn-secondary btn-sm" data-intent-rm="${i.id}" style="font-size:10px;padding:1px 6px;">\u2715</button>
|
|
5047
|
+
</div>`;
|
|
5048
|
+
}).join("");
|
|
5049
|
+
worldIntentsList.innerHTML = rows;
|
|
5050
|
+
worldIntentsList.querySelectorAll("[data-intent-rm]").forEach((btn) => {
|
|
5051
|
+
btn.addEventListener("click", async () => {
|
|
5052
|
+
try {
|
|
5053
|
+
await fetch("/api/intents/" + btn.dataset.intentRm, { method: "DELETE" });
|
|
5054
|
+
} catch (e) {
|
|
5055
|
+
}
|
|
5056
|
+
fetchWorldIntents();
|
|
5057
|
+
});
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
5060
|
+
async function refreshWorldOpportunities() {
|
|
5061
|
+
try {
|
|
5062
|
+
const res = await fetch("/api/opportunities?min-score=0.4");
|
|
5063
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
5064
|
+
const data = await res.json();
|
|
5065
|
+
renderWorldOpportunities(data.opportunities || []);
|
|
5066
|
+
if (worldBadge) {
|
|
5067
|
+
const n = (data.opportunities || []).length;
|
|
5068
|
+
worldBadge.textContent = String(n);
|
|
5069
|
+
worldBadge.style.display = n > 0 ? "" : "none";
|
|
5070
|
+
}
|
|
5071
|
+
return data.opportunities || [];
|
|
5072
|
+
} catch (e) {
|
|
5073
|
+
worldStatus("\u626B\u63CF\u673A\u4F1A\u5931\u8D25: " + (e && e.message ? e.message : e));
|
|
5074
|
+
return [];
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
function renderWorldOpportunities(opps) {
|
|
5078
|
+
if (!worldOpportunities) return;
|
|
5079
|
+
if (!opps.length) {
|
|
5080
|
+
worldOpportunities.innerHTML = '<div class="form-hint">\u4E16\u754C\u6B63\u5728\u6301\u7EED\u9001\u6765 \u2014 \u7A0D\u7B49\u4E00\u4E0B\u3002</div>';
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
const cards = opps.map((o) => {
|
|
5084
|
+
const pct = Math.round(o.score * 100);
|
|
5085
|
+
return `<div class="world-opp-card" style="border:1px solid var(--border,#3a3a36);border-radius:12px;padding:12px 14px;margin-bottom:10px;background:var(--bg-card,#222220);">
|
|
5086
|
+
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
5087
|
+
<b style="font-size:14px;">${String(o.title || "").slice(0, 60).replace(/</g, "<")}</b>
|
|
5088
|
+
<span style="color:#c4d640;font-weight:700;font-size:14px;">${pct}%</span>
|
|
5089
|
+
</div>
|
|
5090
|
+
<div style="font-size:12px;color:#909088;margin-top:4px;">${String(o.summary || "").slice(0, 90).replace(/</g, "<")}</div>
|
|
5091
|
+
<div style="margin-top:8px;display:flex;gap:8px;">
|
|
5092
|
+
<button class="btn-primary btn-sm" data-opp-action="accept" data-opp-id="${o.id}" style="font-size:11px;padding:3px 12px;">\u770B\u770B</button>
|
|
5093
|
+
<button class="btn-secondary btn-sm" data-opp-action="ignore" data-opp-id="${o.id}" style="font-size:11px;padding:3px 12px;">\u5FFD\u7565</button>
|
|
5094
|
+
</div>
|
|
5095
|
+
</div>`;
|
|
5096
|
+
}).join("");
|
|
5097
|
+
worldOpportunities.innerHTML = cards;
|
|
5098
|
+
worldOpportunities.querySelectorAll("[data-opp-action]").forEach((btn) => {
|
|
5099
|
+
btn.addEventListener("click", async () => {
|
|
5100
|
+
const id = btn.dataset.oppId;
|
|
5101
|
+
if (btn.dataset.oppAction === "ignore") {
|
|
5102
|
+
try {
|
|
5103
|
+
await fetch("/api/opportunities/" + id + "/ignore", { method: "POST" });
|
|
5104
|
+
} catch (e) {
|
|
5105
|
+
}
|
|
5106
|
+
btn.closest(".world-opp-card").style.opacity = "0.3";
|
|
5107
|
+
btn.closest(".world-opp-card").style.filter = "grayscale(1)";
|
|
5108
|
+
} else {
|
|
5109
|
+
try {
|
|
5110
|
+
await fetch("/api/opportunities/" + id + "/accept", { method: "POST" });
|
|
5111
|
+
} catch (e) {
|
|
5112
|
+
}
|
|
5113
|
+
const srcId = id.replace(/^opp_/, "ann-");
|
|
5114
|
+
worldStatus("\u5DF2\u8BB0\u4E0B: " + srcId);
|
|
5115
|
+
btn.closest(".world-opp-card").style.borderColor = "#c4d640";
|
|
5116
|
+
}
|
|
5117
|
+
});
|
|
5118
|
+
});
|
|
5119
|
+
}
|
|
5120
|
+
async function loadWorld() {
|
|
5121
|
+
worldLoaded = true;
|
|
5122
|
+
worldStatus("");
|
|
5123
|
+
await fetchWorldIntents();
|
|
5124
|
+
await refreshWorldOpportunities();
|
|
5125
|
+
}
|
|
5126
|
+
function startWorldPoll() {
|
|
5127
|
+
if (worldPollTimer) clearInterval(worldPollTimer);
|
|
5128
|
+
worldPollTimer = setInterval(() => {
|
|
5129
|
+
refreshWorldOpportunities();
|
|
5130
|
+
fetchWorldIntents();
|
|
5131
|
+
}, WORLD_POLL_MS);
|
|
5132
|
+
}
|
|
5133
|
+
function stopWorldPoll() {
|
|
5134
|
+
if (worldPollTimer) {
|
|
5135
|
+
clearInterval(worldPollTimer);
|
|
5136
|
+
worldPollTimer = null;
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5139
|
+
function showWorldView() {
|
|
5140
|
+
if (worldView) {
|
|
5141
|
+
worldView.hidden = false;
|
|
5142
|
+
if (!worldLoaded) loadWorld();
|
|
5143
|
+
else {
|
|
5144
|
+
fetchWorldIntents();
|
|
5145
|
+
refreshWorldOpportunities();
|
|
5146
|
+
}
|
|
5147
|
+
startWorldPoll();
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
function hideWorldView() {
|
|
5151
|
+
if (worldView) worldView.hidden = true;
|
|
5152
|
+
stopWorldPoll();
|
|
5153
|
+
}
|
|
5154
|
+
if (worldBtn) worldBtn.addEventListener("click", showWorldView);
|
|
5155
|
+
if (worldViewClose) worldViewClose.addEventListener("click", hideWorldView);
|
|
5156
|
+
if (worldRefreshBtn) worldRefreshBtn.addEventListener("click", () => {
|
|
5157
|
+
fetchWorldIntents();
|
|
5158
|
+
refreshWorldOpportunities();
|
|
5159
|
+
});
|
|
5160
|
+
if (worldProfileSet) worldProfileSet.addEventListener("click", async () => {
|
|
5161
|
+
const text = (worldProfileInput && worldProfileInput.value || "").trim();
|
|
5162
|
+
if (!text) {
|
|
5163
|
+
worldStatus("\u5148\u5199\u4E00\u53E5\u4F60\u662F\u8C01 / \u5728\u505A\u4EC0\u4E48");
|
|
5164
|
+
return;
|
|
5165
|
+
}
|
|
5166
|
+
if (worldProfileSet) worldProfileSet.disabled = true;
|
|
5167
|
+
try {
|
|
5168
|
+
const res = await fetch("/api/world/profile", {
|
|
5169
|
+
method: "PUT",
|
|
5170
|
+
headers: { "Content-Type": "application/json" },
|
|
5171
|
+
body: JSON.stringify({ name: text, about: text, tags: (text.match(/[\u4e00-\u9fff]{2,}|[a-z][a-z0-9_-]{1,}/gi) || []).slice(0, 6) })
|
|
5172
|
+
});
|
|
5173
|
+
if (!res.ok) {
|
|
5174
|
+
const err = await res.json().catch(() => ({}));
|
|
5175
|
+
worldStatus("\u753B\u50CF\u4FDD\u5B58\u5931\u8D25: " + (err.error || "HTTP " + res.status));
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5178
|
+
worldStatus("\u753B\u50CF\u5DF2\u4FDD\u5B58 \u2014 \u4E16\u754C\u5F00\u59CB\u6309\u5B83\u63A8\u9001");
|
|
5179
|
+
await refreshWorldOpportunities();
|
|
5180
|
+
} catch (e) {
|
|
5181
|
+
worldStatus("\u753B\u50CF\u4FDD\u5B58\u5931\u8D25: " + (e && e.message ? e.message : e));
|
|
5182
|
+
} finally {
|
|
5183
|
+
if (worldProfileSet) worldProfileSet.disabled = false;
|
|
5184
|
+
}
|
|
5185
|
+
});
|
|
5186
|
+
if (worldIntentSet) worldIntentSet.addEventListener("click", async () => {
|
|
5187
|
+
const text = (worldIntentInput && worldIntentInput.value || "").trim();
|
|
5188
|
+
if (!text) {
|
|
5189
|
+
worldStatus("\u5148\u5199\u4E00\u53E5\u4F60\u73B0\u5728\u5728\u505A\u4EC0\u4E48");
|
|
5190
|
+
return;
|
|
5191
|
+
}
|
|
5192
|
+
if (worldIntentSet) worldIntentSet.disabled = true;
|
|
5193
|
+
try {
|
|
5194
|
+
const body = { text, priority: Number(worldIntentPriority ? worldIntentPriority.value : 3) };
|
|
5195
|
+
if (worldIntentBudget && worldIntentBudget.value.trim()) body.budget = worldIntentBudget.value.trim();
|
|
5196
|
+
const res = await fetch("/api/intents", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
5197
|
+
if (!res.ok) {
|
|
5198
|
+
const err = await res.json().catch(() => ({}));
|
|
5199
|
+
worldStatus("\u58F0\u660E\u5931\u8D25: " + (err.error || "HTTP " + res.status));
|
|
5200
|
+
return;
|
|
5201
|
+
}
|
|
5202
|
+
if (worldIntentInput) worldIntentInput.value = "";
|
|
5203
|
+
if (worldIntentBudget) worldIntentBudget.value = "";
|
|
5204
|
+
await fetchWorldIntents();
|
|
5205
|
+
await refreshWorldOpportunities();
|
|
5206
|
+
if (worldPollTimer) {
|
|
5207
|
+
clearInterval(worldPollTimer);
|
|
5208
|
+
startWorldPoll();
|
|
5209
|
+
}
|
|
5210
|
+
} catch (e) {
|
|
5211
|
+
worldStatus("\u58F0\u660E\u5931\u8D25: " + (e && e.message ? e.message : e));
|
|
5212
|
+
} finally {
|
|
5213
|
+
if (worldIntentSet) worldIntentSet.disabled = false;
|
|
5214
|
+
}
|
|
5215
|
+
});
|
|
5216
|
+
if (worldIntentInput) {
|
|
5217
|
+
worldIntentInput.addEventListener("keydown", (e) => {
|
|
5218
|
+
if (e.key === "Enter" && worldIntentSet) worldIntentSet.click();
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
(async function autoWorldBadge() {
|
|
5222
|
+
try {
|
|
5223
|
+
await refreshWorldOpportunities();
|
|
5224
|
+
} catch (e) {
|
|
5225
|
+
}
|
|
5226
|
+
})();
|
|
4995
5227
|
var judgmentImportBtn = document.getElementById("judgment-import-btn");
|
|
4996
5228
|
var judgmentImportFile = document.getElementById("judgment-import-file");
|
|
4997
5229
|
var judgmentCleanupBtn = document.getElementById("judgment-cleanup-btn");
|
package/dist/ios/index.html
CHANGED
|
@@ -44,6 +44,16 @@
|
|
|
44
44
|
|
|
45
45
|
<!-- 主页面: 双频滑动卡片 -->
|
|
46
46
|
<main class="page-container" id="page-main" data-tab="main" hidden>
|
|
47
|
+
<!-- 2026-10-05 (leo Intent Network): 世界机会流 — 自动流入, 不是按钮式被动响应 -->
|
|
48
|
+
<div class="world-feed" id="world-feed" hidden>
|
|
49
|
+
<div class="world-feed-header">
|
|
50
|
+
<span>✦ 世界送来</span>
|
|
51
|
+
<span class="world-feed-sub">自动匹配 · 持续校准</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="world-feed-cards" id="world-feed-cards">
|
|
54
|
+
<div class="world-feed-loading">世界正在观察…</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
47
57
|
<!-- 水平滑动卡片轨道 -->
|
|
48
58
|
<div class="card-carousel" id="card-carousel">
|
|
49
59
|
<div class="card-track" id="card-track">
|
package/dist/ios/mobile.css
CHANGED
|
@@ -359,6 +359,86 @@ body {
|
|
|
359
359
|
}
|
|
360
360
|
.agent-trace-line:first-child { color: var(--text); }
|
|
361
361
|
|
|
362
|
+
/* ============ 世界机会流 (2026-10-05, leo Intent Network) ============ */
|
|
363
|
+
/* 主动流入: 打开首页即拉, 持续刷新; 不是按钮式被动响应 */
|
|
364
|
+
.world-feed {
|
|
365
|
+
padding: 10px 14px 4px;
|
|
366
|
+
border-bottom: 1px solid var(--border);
|
|
367
|
+
background: var(--bg);
|
|
368
|
+
overflow-y: auto;
|
|
369
|
+
max-height: 42vh;
|
|
370
|
+
}
|
|
371
|
+
.world-feed-header {
|
|
372
|
+
display: flex;
|
|
373
|
+
justify-content: space-between;
|
|
374
|
+
align-items: baseline;
|
|
375
|
+
font-size: 13px;
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
color: var(--text);
|
|
378
|
+
margin-bottom: 8px;
|
|
379
|
+
}
|
|
380
|
+
.world-feed-sub {
|
|
381
|
+
font-size: 11px;
|
|
382
|
+
color: var(--text-muted);
|
|
383
|
+
font-weight: 400;
|
|
384
|
+
}
|
|
385
|
+
.world-feed-cards { display: flex; flex-direction: column; gap: 8px; }
|
|
386
|
+
.world-feed-loading { font-size: 12px; color: var(--text-muted); padding: 8px 0; }
|
|
387
|
+
.world-feed-card {
|
|
388
|
+
background: var(--bg-card);
|
|
389
|
+
border: 1px solid var(--border);
|
|
390
|
+
border-left: 3px solid transparent;
|
|
391
|
+
border-radius: 10px;
|
|
392
|
+
padding: 10px 12px;
|
|
393
|
+
}
|
|
394
|
+
.world-feed-card-top {
|
|
395
|
+
display: flex;
|
|
396
|
+
justify-content: space-between;
|
|
397
|
+
align-items: center;
|
|
398
|
+
gap: 8px;
|
|
399
|
+
}
|
|
400
|
+
.world-feed-title {
|
|
401
|
+
font-size: 13px;
|
|
402
|
+
font-weight: 600;
|
|
403
|
+
color: var(--text);
|
|
404
|
+
overflow: hidden;
|
|
405
|
+
text-overflow: ellipsis;
|
|
406
|
+
white-space: nowrap;
|
|
407
|
+
}
|
|
408
|
+
.world-feed-pct {
|
|
409
|
+
color: var(--accent);
|
|
410
|
+
font-weight: 700;
|
|
411
|
+
font-size: 13px;
|
|
412
|
+
white-space: nowrap;
|
|
413
|
+
}
|
|
414
|
+
.world-feed-pct small { color: var(--text-muted); font-weight: 400; font-size: 10px; }
|
|
415
|
+
.world-feed-summary {
|
|
416
|
+
font-size: 12px;
|
|
417
|
+
color: var(--text-secondary);
|
|
418
|
+
margin-top: 4px;
|
|
419
|
+
line-height: 1.5;
|
|
420
|
+
}
|
|
421
|
+
.world-feed-actions {
|
|
422
|
+
display: flex;
|
|
423
|
+
gap: 8px;
|
|
424
|
+
margin-top: 8px;
|
|
425
|
+
}
|
|
426
|
+
.world-feed-btn {
|
|
427
|
+
flex: 1;
|
|
428
|
+
padding: 6px 0;
|
|
429
|
+
border: 1px solid var(--accent);
|
|
430
|
+
background: var(--accent);
|
|
431
|
+
color: #1a1a18;
|
|
432
|
+
border-radius: 8px;
|
|
433
|
+
font-size: 12px;
|
|
434
|
+
font-weight: 600;
|
|
435
|
+
}
|
|
436
|
+
.world-feed-btn.ghost {
|
|
437
|
+
background: transparent;
|
|
438
|
+
color: var(--text-secondary);
|
|
439
|
+
border-color: var(--border);
|
|
440
|
+
}
|
|
441
|
+
|
|
362
442
|
/* ============ 双频滑动卡片 (主页面) ============ */
|
|
363
443
|
|
|
364
444
|
.card-carousel {
|
package/dist/ios/mobile.html
CHANGED
|
@@ -44,6 +44,16 @@
|
|
|
44
44
|
|
|
45
45
|
<!-- 主页面: 双频滑动卡片 -->
|
|
46
46
|
<main class="page-container" id="page-main" data-tab="main" hidden>
|
|
47
|
+
<!-- 2026-10-05 (leo Intent Network): 世界机会流 — 自动流入, 不是按钮式被动响应 -->
|
|
48
|
+
<div class="world-feed" id="world-feed" hidden>
|
|
49
|
+
<div class="world-feed-header">
|
|
50
|
+
<span>✦ 世界送来</span>
|
|
51
|
+
<span class="world-feed-sub">自动匹配 · 持续校准</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="world-feed-cards" id="world-feed-cards">
|
|
54
|
+
<div class="world-feed-loading">世界正在观察…</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
47
57
|
<!-- 水平滑动卡片轨道 -->
|
|
48
58
|
<div class="card-carousel" id="card-carousel">
|
|
49
59
|
<div class="card-track" id="card-track">
|
package/dist/ios/mobile.js
CHANGED
|
@@ -506,6 +506,59 @@
|
|
|
506
506
|
let currentCardIndex = 0;
|
|
507
507
|
let allAgentCards = [];
|
|
508
508
|
|
|
509
|
+
// === 世界机会流 (2026-10-05, leo Intent Network) ===
|
|
510
|
+
// 设计: 自动流入 (打开首页即拉), 不是按钮式被动响应。ignore/accept 走 feedback 校准环。
|
|
511
|
+
let worldFeedShown = false;
|
|
512
|
+
let worldFeedTimer = null;
|
|
513
|
+
async function loadWorldFeed() {
|
|
514
|
+
const feed = $('#world-feed');
|
|
515
|
+
const cards = $('#world-feed-cards');
|
|
516
|
+
if (!feed || !cards) return;
|
|
517
|
+
try {
|
|
518
|
+
const res = await fetch('/api/opportunities?min-score=0.2&limit=10');
|
|
519
|
+
if (!res.ok) return;
|
|
520
|
+
const data = await res.json();
|
|
521
|
+
const opps = (data.opportunities || []).slice(0, 6);
|
|
522
|
+
if (!opps.length) { feed.hidden = true; return; }
|
|
523
|
+
feed.hidden = false;
|
|
524
|
+
worldFeedShown = true;
|
|
525
|
+
cards.innerHTML = opps.map((o) => {
|
|
526
|
+
const pct = Math.round(o.score * 100);
|
|
527
|
+
const tag = o.reason === 'match' ? '与你匹配' : '世界变化';
|
|
528
|
+
return `<div class="world-feed-card">
|
|
529
|
+
<div class="world-feed-card-top">
|
|
530
|
+
<span class="world-feed-title">${escapeHtml(String(o.title || '').slice(0, 34))}</span>
|
|
531
|
+
<span class="world-feed-pct">${pct}%<small> ${tag}</small></span>
|
|
532
|
+
</div>
|
|
533
|
+
<div class="world-feed-summary">${escapeHtml(String(o.summary || '').slice(0, 60))}</div>
|
|
534
|
+
<div class="world-feed-actions">
|
|
535
|
+
<button class="world-feed-btn" data-wf-action="accept" data-wf-id="${o.id}">看看</button>
|
|
536
|
+
<button class="world-feed-btn ghost" data-wf-action="ignore" data-wf-id="${o.id}">忽略</button>
|
|
537
|
+
</div>
|
|
538
|
+
</div>`;
|
|
539
|
+
}).join('');
|
|
540
|
+
cards.querySelectorAll('[data-wf-action]').forEach((btn) => {
|
|
541
|
+
btn.addEventListener('click', async () => {
|
|
542
|
+
const id = btn.dataset.wfId;
|
|
543
|
+
const action = btn.dataset.wfAction;
|
|
544
|
+
try { await fetch('/api/opportunities/' + id + '/' + action, { method: 'POST' }); } catch (e) {}
|
|
545
|
+
const card = btn.closest('.world-feed-card');
|
|
546
|
+
if (card) {
|
|
547
|
+
card.style.opacity = '0.3';
|
|
548
|
+
card.style.filter = action === 'ignore' ? 'grayscale(1)' : 'none';
|
|
549
|
+
card.style.borderLeftColor = action === 'accept' ? '#c4d640' : 'transparent';
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
} catch (e) {
|
|
554
|
+
feed.hidden = true; // 拉不到就不显示世界区 (不阻塞首页)
|
|
555
|
+
}
|
|
556
|
+
// 持续流入: 世界区可见时每 45s 自动刷新 (AI 持续观察, 不是人刷新)
|
|
557
|
+
if (feed && !feed.hidden && worldFeedShown && !worldFeedTimer) {
|
|
558
|
+
worldFeedTimer = setInterval(() => { loadWorldFeed(); }, 45000);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
509
562
|
// === 卡片封面 (docs/fig 导出 → src/web/covers, 每个 agent 唯一不重复) ===
|
|
510
563
|
let _coverList = null;
|
|
511
564
|
async function loadCovers() {
|
|
@@ -533,6 +586,7 @@
|
|
|
533
586
|
async function loadAgentCovers() {
|
|
534
587
|
const track = $('#card-track');
|
|
535
588
|
if (!track) return;
|
|
589
|
+
loadWorldFeed(); // 2026-10-05: 世界机会流自动流入 (不阻塞智能体卡片, fire-and-forget)
|
|
536
590
|
await loadCovers();
|
|
537
591
|
track.innerHTML = '<div class="card-loading">加载智能体卡片...</div>';
|
|
538
592
|
try {
|