@bolloon/bolloon-agent 0.6.4 → 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/dist/agents/opportunity-inbox.js +147 -0
- package/dist/agents/opportunity-match.js +48 -7
- package/dist/agents/opportunity-web-search.js +183 -0
- package/dist/agents/world-watcher.js +30 -1
- package/dist/cli/intent-command.js +1 -1
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +80 -82
- package/dist/ios/routes-world.js +54 -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 +80 -82
- package/dist/web/index.html +27 -46
- package/dist/web/routes-world.js +54 -0
- package/dist/web/style.css +39 -0
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
package/dist/ios/client.js
CHANGED
|
@@ -3111,6 +3111,8 @@ ${data.error || "channel not found"}`, "error");
|
|
|
3111
3111
|
}, 1e4);
|
|
3112
3112
|
eventSource.onerror = () => {
|
|
3113
3113
|
console.warn("[SSE] \u9519\u8BEF, \u6D4F\u89C8\u5668\u81EA\u52A8\u91CD\u8FDE\u4E2D:", targetChannelId, "readyState=", eventSource.readyState);
|
|
3114
|
+
hideLoopStatusBar2();
|
|
3115
|
+
setSendMode("idle");
|
|
3114
3116
|
if (eventSource.readyState === EventSource.CLOSED) {
|
|
3115
3117
|
clearInterval(heartbeatTimer);
|
|
3116
3118
|
eventSources.delete(targetChannelId);
|
|
@@ -3263,10 +3265,16 @@ ${data.error || "channel not found"}`, "error");
|
|
|
3263
3265
|
})
|
|
3264
3266
|
});
|
|
3265
3267
|
if (!res.ok) {
|
|
3266
|
-
|
|
3268
|
+
hideLoopStatusBar2();
|
|
3269
|
+
const errBody = await res.json().catch(() => ({}));
|
|
3270
|
+
const reason = errBody?.error || errBody?.message || `HTTP ${res.status}`;
|
|
3271
|
+
addMessage2(`\u26A0\uFE0F \u53D1\u9001\u5931\u8D25: ${reason}`, "ai");
|
|
3267
3272
|
setSendMode("idle");
|
|
3273
|
+
if (typeof showSimpleToast === "function") showSimpleToast("\u26A0\uFE0F " + String(reason).slice(0, 200));
|
|
3274
|
+
return;
|
|
3268
3275
|
}
|
|
3269
3276
|
} catch (err) {
|
|
3277
|
+
hideLoopStatusBar2();
|
|
3270
3278
|
addMessage2("\u8FDE\u63A5\u9519\u8BEF", "ai");
|
|
3271
3279
|
console.error("Send error", err);
|
|
3272
3280
|
setSendMode("idle");
|
|
@@ -4994,27 +5002,16 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4994
5002
|
}
|
|
4995
5003
|
var worldView = document.getElementById("world-view");
|
|
4996
5004
|
var worldBtn = document.getElementById("world-btn");
|
|
4997
|
-
var worldViewClose = document.getElementById("world-view-close");
|
|
4998
5005
|
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
5006
|
var worldIntentsList = document.getElementById("world-intents-list");
|
|
5005
5007
|
var worldOpportunities = document.getElementById("world-opportunities");
|
|
5006
5008
|
var worldStats = document.getElementById("world-stats");
|
|
5007
5009
|
var worldBadge = document.getElementById("world-badge");
|
|
5008
|
-
var worldProfileInput = document.getElementById("world-profile-input");
|
|
5009
|
-
var worldProfileSet = document.getElementById("world-profile-set");
|
|
5010
5010
|
var worldLoaded = false;
|
|
5011
5011
|
var worldPollTimer = null;
|
|
5012
5012
|
var WORLD_POLL_MS = 3e4;
|
|
5013
5013
|
function worldStatus(msg) {
|
|
5014
|
-
if (
|
|
5015
|
-
worldError.textContent = msg || "";
|
|
5016
|
-
worldError.style.display = msg ? "" : "none";
|
|
5017
|
-
}
|
|
5014
|
+
if (msg) console.info("[world]", msg);
|
|
5018
5015
|
}
|
|
5019
5016
|
async function fetchWorldIntents() {
|
|
5020
5017
|
try {
|
|
@@ -5035,7 +5032,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
5035
5032
|
function renderWorldIntents(intents) {
|
|
5036
5033
|
if (!worldIntentsList) return;
|
|
5037
5034
|
if (!intents.length) {
|
|
5038
|
-
worldIntentsList.innerHTML = '<div class="form-hint" style="font-size:11px;">(\u8FD8\u6CA1\u6709\u610F\u56FE \u2014 \
|
|
5035
|
+
worldIntentsList.innerHTML = '<div class="form-hint" style="font-size:11px;">(\u8FD8\u6CA1\u6709\u610F\u56FE \u2014 \u5728\u5E95\u90E8\u8F93\u5165\u6846\u544A\u8BC9\u4E16\u754C\u4F60\u5728\u505A\u4EC0\u4E48)</div>';
|
|
5039
5036
|
return;
|
|
5040
5037
|
}
|
|
5041
5038
|
const active = intents.filter((i) => i.status === "active");
|
|
@@ -5082,12 +5079,30 @@ ${data.error || "channel not found"}`, "error");
|
|
|
5082
5079
|
}
|
|
5083
5080
|
const cards = opps.map((o) => {
|
|
5084
5081
|
const pct = Math.round(o.score * 100);
|
|
5085
|
-
|
|
5082
|
+
const inboxLine = o.inbox ? `<div style="font-size:11px;color:#8a8a80;margin-top:6px;border-top:1px dashed var(--border,#3a3a36);padding-top:6px;">
|
|
5083
|
+
\u2709 \u5916\u90E8\u6295\u9012 \xB7 \u6765\u81EA <span style="color:#c4d640;">${String(o.inbox.providerName || o.inbox.providerDid || "").slice(0, 40)}</span>
|
|
5084
|
+
\xB7 <span style="color:#6aaa6a;">\u2713 \u9A8C\u7B7E\u901A\u8FC7</span>
|
|
5085
|
+
${(o.inbox.refs || []).length ? "\xB7 " + o.inbox.refs.map((r) => `<a href="${String(r).replace(/</g, "<")}" target="_blank" rel="noopener" style="color:#7a9ad4;">[\u5F15\u7528]</a>`).join(" ") : ""}
|
|
5086
|
+
</div>` : "";
|
|
5087
|
+
const searchLine = o.search ? `<div style="font-size:11px;color:#8a8a80;margin-top:6px;border-top:1px dashed var(--border,#3a3a36);padding-top:6px;">
|
|
5088
|
+
\u{1F50E} AI \u4E3B\u52A8\u641C\u7D22 \xB7 <a href="${String(o.search.url || "").replace(/</g, "<")}" target="_blank" rel="noopener" style="color:#7a9ad4;">\u6253\u5F00\u539F\u6587 \u2197</a>
|
|
5089
|
+
\xB7 <span style="color:#6aaa6a;">\u6765\u6E90 ${o.search.source}</span>
|
|
5090
|
+
</div>` : "";
|
|
5091
|
+
const detailLine = `<div class="world-opp-detail" style="display:none;margin-top:8px;padding-top:8px;border-top:1px solid var(--border,#3a3a36);font-size:12px;color:#b8b8b0;line-height:1.6;">
|
|
5092
|
+
<div style="font-weight:600;color:#f0f0ea;margin-bottom:4px;">\u5B8C\u6574\u5185\u5BB9</div>
|
|
5093
|
+
<div>${String(o.summary || "(\u65E0\u66F4\u591A\u5185\u5BB9)").replace(/</g, "<")}</div>
|
|
5094
|
+
${o.search?.url ? `<div style="margin-top:6px;">\u{1F517} <a href="${String(o.search.url).replace(/</g, "<")}" target="_blank" rel="noopener" style="color:#7a9ad4;">${String(o.search.url).slice(0, 80)}</a></div>` : ""}
|
|
5095
|
+
<div style="margin-top:6px;color:#606058;">\u5339\u914D ${pct}% \xB7 \u6765\u6E90 ${o.source} \xB7 id ${o.id}</div>
|
|
5096
|
+
</div>`;
|
|
5097
|
+
return `<div class="world-opp-card" data-opp-card="${o.id}" style="border:1px solid var(--border,#3a3a36);border-radius:12px;padding:12px 14px;margin-bottom:10px;background:var(--bg-card,#222220);cursor:pointer;transition:border-color 0.2s;">
|
|
5086
5098
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
5087
5099
|
<b style="font-size:14px;">${String(o.title || "").slice(0, 60).replace(/</g, "<")}</b>
|
|
5088
5100
|
<span style="color:#c4d640;font-weight:700;font-size:14px;">${pct}%</span>
|
|
5089
5101
|
</div>
|
|
5090
5102
|
<div style="font-size:12px;color:#909088;margin-top:4px;">${String(o.summary || "").slice(0, 90).replace(/</g, "<")}</div>
|
|
5103
|
+
${inboxLine}
|
|
5104
|
+
${searchLine}
|
|
5105
|
+
${detailLine}
|
|
5091
5106
|
<div style="margin-top:8px;display:flex;gap:8px;">
|
|
5092
5107
|
<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
5108
|
<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>
|
|
@@ -5096,26 +5111,39 @@ ${data.error || "channel not found"}`, "error");
|
|
|
5096
5111
|
}).join("");
|
|
5097
5112
|
worldOpportunities.innerHTML = cards;
|
|
5098
5113
|
worldOpportunities.querySelectorAll("[data-opp-action]").forEach((btn) => {
|
|
5099
|
-
btn.addEventListener("click", async () => {
|
|
5114
|
+
btn.addEventListener("click", async (ev) => {
|
|
5115
|
+
ev.stopPropagation();
|
|
5100
5116
|
const id = btn.dataset.oppId;
|
|
5101
5117
|
if (btn.dataset.oppAction === "ignore") {
|
|
5102
5118
|
try {
|
|
5103
5119
|
await fetch("/api/opportunities/" + id + "/ignore", { method: "POST" });
|
|
5104
5120
|
} catch (e) {
|
|
5105
5121
|
}
|
|
5106
|
-
btn.closest(".world-opp-card")
|
|
5107
|
-
|
|
5122
|
+
const card = btn.closest(".world-opp-card");
|
|
5123
|
+
if (card) {
|
|
5124
|
+
card.style.opacity = "0.3";
|
|
5125
|
+
card.style.filter = "grayscale(1)";
|
|
5126
|
+
}
|
|
5108
5127
|
} else {
|
|
5109
5128
|
try {
|
|
5110
5129
|
await fetch("/api/opportunities/" + id + "/accept", { method: "POST" });
|
|
5111
5130
|
} catch (e) {
|
|
5112
5131
|
}
|
|
5113
|
-
|
|
5114
|
-
worldStatus("\u5DF2\u8BB0\u4E0B: " + srcId);
|
|
5115
|
-
btn.closest(".world-opp-card").style.borderColor = "#c4d640";
|
|
5132
|
+
toggleOppDetail(btn.closest(".world-opp-card"));
|
|
5116
5133
|
}
|
|
5117
5134
|
});
|
|
5118
5135
|
});
|
|
5136
|
+
worldOpportunities.querySelectorAll("[data-opp-card]").forEach((card) => {
|
|
5137
|
+
card.addEventListener("click", () => toggleOppDetail(card));
|
|
5138
|
+
});
|
|
5139
|
+
}
|
|
5140
|
+
function toggleOppDetail(card) {
|
|
5141
|
+
if (!card) return;
|
|
5142
|
+
const detail = card.querySelector(".world-opp-detail");
|
|
5143
|
+
if (!detail) return;
|
|
5144
|
+
const shown = detail.style.display !== "none";
|
|
5145
|
+
detail.style.display = shown ? "none" : "block";
|
|
5146
|
+
card.style.borderColor = shown ? "var(--border,#3a3a36)" : "#c4d640";
|
|
5119
5147
|
}
|
|
5120
5148
|
async function loadWorld() {
|
|
5121
5149
|
worldLoaded = true;
|
|
@@ -5136,88 +5164,58 @@ ${data.error || "channel not found"}`, "error");
|
|
|
5136
5164
|
worldPollTimer = null;
|
|
5137
5165
|
}
|
|
5138
5166
|
}
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5167
|
+
var tabWorld = document.getElementById("tab-world");
|
|
5168
|
+
var tabChat = document.getElementById("tab-chat");
|
|
5169
|
+
var loopStatusBar2 = document.getElementById("loop-status-bar");
|
|
5170
|
+
function switchPanel(panel) {
|
|
5171
|
+
if (!worldView || !messagesEl) return;
|
|
5172
|
+
const isWorld = panel === "world";
|
|
5173
|
+
worldView.style.display = isWorld ? "flex" : "none";
|
|
5174
|
+
if (messagesEl) messagesEl.hidden = isWorld;
|
|
5175
|
+
if (loopStatusBar2) loopStatusBar2.hidden = isWorld;
|
|
5176
|
+
if (inputArea) inputArea.hidden = isWorld;
|
|
5177
|
+
if (tabWorld) tabWorld.classList.toggle("active", isWorld);
|
|
5178
|
+
if (tabChat) tabChat.classList.toggle("active", !isWorld);
|
|
5179
|
+
if (isWorld) {
|
|
5142
5180
|
if (!worldLoaded) loadWorld();
|
|
5143
5181
|
else {
|
|
5144
5182
|
fetchWorldIntents();
|
|
5145
5183
|
refreshWorldOpportunities();
|
|
5146
5184
|
}
|
|
5147
5185
|
startWorldPoll();
|
|
5186
|
+
} else {
|
|
5187
|
+
stopWorldPoll();
|
|
5148
5188
|
}
|
|
5149
5189
|
}
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
}
|
|
5154
|
-
if (worldBtn) worldBtn.addEventListener("click", showWorldView);
|
|
5155
|
-
if (worldViewClose) worldViewClose.addEventListener("click", hideWorldView);
|
|
5190
|
+
if (tabWorld) tabWorld.addEventListener("click", () => switchPanel("world"));
|
|
5191
|
+
if (tabChat) tabChat.addEventListener("click", () => switchPanel("chat"));
|
|
5192
|
+
if (worldBtn) worldBtn.addEventListener("click", () => switchPanel("world"));
|
|
5156
5193
|
if (worldRefreshBtn) worldRefreshBtn.addEventListener("click", () => {
|
|
5157
5194
|
fetchWorldIntents();
|
|
5158
5195
|
refreshWorldOpportunities();
|
|
5159
5196
|
});
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
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;
|
|
5197
|
+
var worldInput = document.getElementById("world-input");
|
|
5198
|
+
var worldInputSend = document.getElementById("world-input-send");
|
|
5199
|
+
async function tellWorld() {
|
|
5200
|
+
const text = (worldInput && worldInput.value || "").trim();
|
|
5201
|
+
if (!text) return;
|
|
5202
|
+
worldInput.value = "";
|
|
5193
5203
|
try {
|
|
5194
|
-
const
|
|
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) });
|
|
5204
|
+
const res = await fetch("/api/intents", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text, priority: 3 }) });
|
|
5197
5205
|
if (!res.ok) {
|
|
5198
|
-
|
|
5199
|
-
worldStatus("\u58F0\u660E\u5931\u8D25: " + (err.error || "HTTP " + res.status));
|
|
5206
|
+
console.info("[world] \u58F0\u660E\u5931\u8D25 HTTP", res.status);
|
|
5200
5207
|
return;
|
|
5201
5208
|
}
|
|
5202
|
-
if (worldIntentInput) worldIntentInput.value = "";
|
|
5203
|
-
if (worldIntentBudget) worldIntentBudget.value = "";
|
|
5204
5209
|
await fetchWorldIntents();
|
|
5205
5210
|
await refreshWorldOpportunities();
|
|
5206
|
-
if (worldPollTimer) {
|
|
5207
|
-
clearInterval(worldPollTimer);
|
|
5208
|
-
startWorldPoll();
|
|
5209
|
-
}
|
|
5210
5211
|
} catch (e) {
|
|
5211
|
-
|
|
5212
|
-
} finally {
|
|
5213
|
-
if (worldIntentSet) worldIntentSet.disabled = false;
|
|
5212
|
+
console.info("[world] \u58F0\u660E\u5931\u8D25", e);
|
|
5214
5213
|
}
|
|
5215
|
-
});
|
|
5216
|
-
if (worldIntentInput) {
|
|
5217
|
-
worldIntentInput.addEventListener("keydown", (e) => {
|
|
5218
|
-
if (e.key === "Enter" && worldIntentSet) worldIntentSet.click();
|
|
5219
|
-
});
|
|
5220
5214
|
}
|
|
5215
|
+
if (worldInputSend) worldInputSend.addEventListener("click", tellWorld);
|
|
5216
|
+
if (worldInput) worldInput.addEventListener("keydown", (e) => {
|
|
5217
|
+
if (e.key === "Enter") tellWorld();
|
|
5218
|
+
});
|
|
5221
5219
|
(async function autoWorldBadge() {
|
|
5222
5220
|
try {
|
|
5223
5221
|
await refreshWorldOpportunities();
|
package/dist/ios/routes-world.js
CHANGED
|
@@ -2,6 +2,8 @@ import { setIntent, listIntents, getIntent, removeIntent } from '../agents/inten
|
|
|
2
2
|
import { scanOpportunities, recordFeedback, readObservations } from '../agents/opportunity-match.js';
|
|
3
3
|
import { readProfile, setProfile } from '../agents/world-profile.js';
|
|
4
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';
|
|
5
7
|
function json(res, status, body) {
|
|
6
8
|
res.status(status).json(body);
|
|
7
9
|
}
|
|
@@ -102,4 +104,56 @@ export function registerWorldRoutes(app) {
|
|
|
102
104
|
const observations = await readObservations(20);
|
|
103
105
|
json(res, 200, { ok: true, watcher: status, recentObservations: observations });
|
|
104
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
|
+
});
|
|
105
159
|
}
|
package/dist/ios/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/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.5';
|
|
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.5",
|
|
4
4
|
"channel": "stable",
|
|
5
5
|
"build": "stable",
|
|
6
|
-
"commit": "
|
|
7
|
-
"builtAt": "2026-10-
|
|
6
|
+
"commit": "0d876c1b48ea86d5a390bd21b62468c8d2487fe0",
|
|
7
|
+
"builtAt": "2026-10-06T07:04:38.761Z",
|
|
8
8
|
"layer": "web",
|
|
9
9
|
"note": "web 资源层构建戳 (手机端 OTA 的身份第 ③ 级回退; 装机后以 OTA state.json 为准)"
|
|
10
10
|
}
|