@bolloon/bolloon-agent 0.6.3 → 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/intent-store.js +193 -0
- package/dist/agents/opportunity-match.js +260 -0
- 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 +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 +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/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
|
@@ -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 {
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
function json(res, status, body) {
|
|
6
|
+
res.status(status).json(body);
|
|
7
|
+
}
|
|
8
|
+
function bodyOf(req) {
|
|
9
|
+
return req.body && typeof req.body === 'object' ? req.body : {};
|
|
10
|
+
}
|
|
11
|
+
/** 限制输入形状: 只收明确字段, 不整个吞前端对象 */
|
|
12
|
+
function intentInputFromBody(body) {
|
|
13
|
+
const b = bodyOf(body);
|
|
14
|
+
const out = {};
|
|
15
|
+
if (typeof b.text === 'string' && b.text.trim())
|
|
16
|
+
out.text = b.text.trim();
|
|
17
|
+
if (Array.isArray(b.tags))
|
|
18
|
+
out.tags = b.tags.filter((t) => typeof t === 'string').map(String).slice(0, 24);
|
|
19
|
+
if (b.priority !== undefined)
|
|
20
|
+
out.priority = Number(b.priority);
|
|
21
|
+
if (typeof b.budget === 'string')
|
|
22
|
+
out.budget = b.budget.trim();
|
|
23
|
+
if (b.deadline !== undefined) {
|
|
24
|
+
const d = typeof b.deadline === 'number' ? b.deadline : Date.parse(String(b.deadline));
|
|
25
|
+
if (!Number.isNaN(d))
|
|
26
|
+
out.deadline = d;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
export function registerWorldRoutes(app) {
|
|
31
|
+
// ── intents ──────────────────────────────────────────────────────────────
|
|
32
|
+
app.get('/api/intents', async (_req, res) => {
|
|
33
|
+
const r = await listIntents();
|
|
34
|
+
if (!r.ok)
|
|
35
|
+
return json(res, 500, { error: r.error });
|
|
36
|
+
const order = { active: 0, paused: 1, done: 2 };
|
|
37
|
+
const rows = [...r.intents].sort((a, b) => order[a.status] - order[b.status] || b.createdAt - a.createdAt);
|
|
38
|
+
json(res, 200, { ok: true, count: rows.length, intents: rows });
|
|
39
|
+
});
|
|
40
|
+
app.post('/api/intents', async (req, res) => {
|
|
41
|
+
const input = intentInputFromBody(req.body);
|
|
42
|
+
if (!input.text)
|
|
43
|
+
return json(res, 400, { ok: false, error: 'text 必填 (声明你现在在做什么)' });
|
|
44
|
+
const r = await setIntent(input);
|
|
45
|
+
if (!r.ok)
|
|
46
|
+
return json(res, 400, { ok: false, error: r.error });
|
|
47
|
+
json(res, r.created ? 201 : 200, { ok: true, created: r.created, intent: r.intent });
|
|
48
|
+
});
|
|
49
|
+
app.get('/api/intents/:id', async (req, res) => {
|
|
50
|
+
const r = await getIntent(req.params.id);
|
|
51
|
+
if (!r.ok)
|
|
52
|
+
return json(res, 500, { error: r.error });
|
|
53
|
+
if (!r.intent)
|
|
54
|
+
return json(res, 404, { ok: false, error: `没有意图 ${req.params.id}` });
|
|
55
|
+
json(res, 200, { ok: true, intent: r.intent });
|
|
56
|
+
});
|
|
57
|
+
app.delete('/api/intents/:id', async (req, res) => {
|
|
58
|
+
const done = req.query.done === '1' || req.query.done === 'true';
|
|
59
|
+
const r = await removeIntent(req.params.id, done);
|
|
60
|
+
if (!r.ok)
|
|
61
|
+
return json(res, 404, { ok: false, error: r.error });
|
|
62
|
+
json(res, 200, { ok: true, removed: r.removed, archived: done });
|
|
63
|
+
});
|
|
64
|
+
// ── opportunities ────────────────────────────────────────────────────────
|
|
65
|
+
app.get('/api/opportunities', async (req, res) => {
|
|
66
|
+
const minScoreRaw = typeof req.query['min-score'] === 'string' ? req.query['min-score'] : '0.4';
|
|
67
|
+
const minScore = Number(minScoreRaw);
|
|
68
|
+
const limitRaw = typeof req.query.limit === 'string' ? req.query.limit : '50';
|
|
69
|
+
const limit = Math.max(1, Math.min(200, parseInt(limitRaw, 10) || 50));
|
|
70
|
+
const r = await scanOpportunities({ minScore: Number.isFinite(minScore) ? minScore : 0.4, limit });
|
|
71
|
+
if (!r.ok)
|
|
72
|
+
return json(res, 500, { ok: false, error: r.error });
|
|
73
|
+
json(res, 200, { ok: true, count: r.results.length, minScore, opportunities: r.results });
|
|
74
|
+
});
|
|
75
|
+
app.post('/api/opportunities/:id/ignore', async (req, res) => {
|
|
76
|
+
const sourceId = String(req.params.id).replace(/^opp_/, 'ann-');
|
|
77
|
+
const r = await recordFeedback('ignore', sourceId);
|
|
78
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, opportunityId: req.params.id, ignored: r.ok, error: r.error });
|
|
79
|
+
});
|
|
80
|
+
app.post('/api/opportunities/:id/accept', async (req, res) => {
|
|
81
|
+
const sourceId = String(req.params.id).replace(/^opp_/, 'ann-');
|
|
82
|
+
const r = await recordFeedback('accept', sourceId);
|
|
83
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, opportunityId: req.params.id, accepted: r.ok, error: r.error });
|
|
84
|
+
});
|
|
85
|
+
// ── 用户画像 (leo 三条分发规划③: 初始化收集 → 常驻意图推送) ─────────────────
|
|
86
|
+
app.get('/api/world/profile', async (_req, res) => {
|
|
87
|
+
const profile = await readProfile();
|
|
88
|
+
json(res, 200, { ok: true, profile });
|
|
89
|
+
});
|
|
90
|
+
app.put('/api/world/profile', async (req, res) => {
|
|
91
|
+
const b = bodyOf(req);
|
|
92
|
+
const r = await setProfile({
|
|
93
|
+
name: typeof b.name === 'string' ? b.name : undefined,
|
|
94
|
+
about: typeof b.about === 'string' ? b.about : undefined,
|
|
95
|
+
tags: Array.isArray(b.tags) ? b.tags.filter((t) => typeof t === 'string') : undefined,
|
|
96
|
+
});
|
|
97
|
+
json(res, r.ok ? 200 : 400, { ok: r.ok, profile: r.profile, error: r.error });
|
|
98
|
+
});
|
|
99
|
+
// ── 世界观察器状态 (自动触发机制可见性) ─────────────────────────────────────
|
|
100
|
+
app.get('/api/world/watch', async (_req, res) => {
|
|
101
|
+
const status = worldWatcherStatus();
|
|
102
|
+
const observations = await readObservations(20);
|
|
103
|
+
json(res, 200, { ok: true, watcher: status, recentObservations: observations });
|
|
104
|
+
});
|
|
105
|
+
}
|
package/dist/ios/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/ios/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.4';
|
|
10
10
|
const SHELL = [
|
|
11
11
|
'./mobile.html',
|
|
12
12
|
'./mobile.css',
|
|
@@ -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": "0dfa01104a71ed0351be8402a6b85efaf909df19",
|
|
7
|
+
"builtAt": "2026-10-05T10:49:06.717Z",
|
|
8
8
|
"layer": "web",
|
|
9
9
|
"note": "web 资源层构建戳 (手机端 OTA 的身份第 ③ 级回退; 装机后以 OTA state.json 为准)"
|
|
10
10
|
}
|