@seasonkoh/webaz 0.1.13 → 0.1.15
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.
|
@@ -49,7 +49,10 @@ export function transition(db, orderId, toStatus, actorId, evidenceIds = [], not
|
|
|
49
49
|
if (rule.requiresEvidence && evidenceIds.length === 0) {
|
|
50
50
|
return {
|
|
51
51
|
success: false,
|
|
52
|
-
error:
|
|
52
|
+
error: `此操作需要证据。可任选一种方式提供:\n` +
|
|
53
|
+
`(A) 传 evidence_description(文字描述,如 "物流单号: SF1234567 / 顺丰" / "已揽收 GPS:31.2,121.5") — agent 最方便\n` +
|
|
54
|
+
`(B) 上传文件作为证据 — 提示:${rule.evidenceHint ?? '相关证明文件'}\n` +
|
|
55
|
+
`两种都会写入 evidence 表,效力等同。`
|
|
53
56
|
};
|
|
54
57
|
}
|
|
55
58
|
// 5. 执行转移(数据库事务,保证原子性)
|
|
@@ -148,12 +151,29 @@ export function getOrderStatus(db, orderId) {
|
|
|
148
151
|
const responsibleTable = isSelfFulfill ? CURRENT_RESPONSIBLE_SELF_FULFILL : CURRENT_RESPONSIBLE;
|
|
149
152
|
const currentResponsible = responsibleTable[order.status] ?? null;
|
|
150
153
|
const activeDeadline = getActiveDeadline(order, db);
|
|
154
|
+
// 2026-05-31 透传 Phase 1 兜底语义给 agent — 当 fulfillment_mode='shipping' 但 logistics_id NULL,
|
|
155
|
+
// 实际是"协议 Phase 1 没物流市场,seller 自履行兜底"。agent 看到 responsible=seller 容易误解为
|
|
156
|
+
// "卖家本来就要自履行",所以加显式 phase + context 让 agent 明白这是 Phase 1 临时兜底。
|
|
157
|
+
// 真正面交(fulfillment_mode='in_person')才是 self-fulfill 本意。
|
|
158
|
+
const mode = order.fulfillment_mode;
|
|
159
|
+
const isInPerson = mode === 'in_person';
|
|
160
|
+
const isPhase1Fallback = isSelfFulfill && !isInPerson && (mode === 'shipping' || !mode);
|
|
161
|
+
const fulfillmentPhase = isPhase1Fallback
|
|
162
|
+
? 'phase_1_no_logistics_market'
|
|
163
|
+
: (isInPerson ? 'in_person' : (order.logistics_id ? 'phase_2_logistics_assigned' : 'unknown'));
|
|
164
|
+
const responsibleContext = isPhase1Fallback
|
|
165
|
+
? `Phase 1 fallback: 协议物流市场未启用,logistics_id 未绑定。seller 自履行兜底(自己揽收/运输/送达),超时按 fault_seller 处置。Phase 2 logistics 市场上线后会自动派单给 logistics 角色,届时 shipped/picked_up/in_transit → logistics 负责。`
|
|
166
|
+
: (isInPerson
|
|
167
|
+
? '面交模式(fulfillment_mode=in_person):seller 与 buyer 当面交接,无第三方物流'
|
|
168
|
+
: (order.logistics_id ? '已绑定物流方,按 logistics 责任流转(market-fulfill)' : null));
|
|
151
169
|
return {
|
|
152
170
|
order,
|
|
153
171
|
history,
|
|
154
172
|
currentResponsible,
|
|
155
173
|
activeDeadline,
|
|
156
|
-
isOverdue: activeDeadline ? new Date() > new Date(activeDeadline.deadline) : false
|
|
174
|
+
isOverdue: activeDeadline ? new Date() > new Date(activeDeadline.deadline) : false,
|
|
175
|
+
fulfillmentPhase,
|
|
176
|
+
responsibleContext,
|
|
157
177
|
};
|
|
158
178
|
}
|
|
159
179
|
// ─── 超时判责资金处置 ────────────────────────────────────────
|
|
@@ -351,7 +371,7 @@ function getActiveDeadline(order, db) {
|
|
|
351
371
|
// QA 轮 7 P1:旧表 picked_up 状态没 deadline → agent 不知道下一步多久前要做完
|
|
352
372
|
// 修:picked_up 状态视为"已揽收,等运输/投递",下一个 deadline 是 delivery_deadline
|
|
353
373
|
// QA 轮 7 P1(另一条):disputed 状态下没读 dispute_cases 的 arbitrate_deadline → agent 不知道仲裁还有多久
|
|
354
|
-
const
|
|
374
|
+
const deadlineMapMarket = {
|
|
355
375
|
created: 'pay_deadline',
|
|
356
376
|
paid: 'accept_deadline',
|
|
357
377
|
accepted: 'ship_deadline',
|
|
@@ -360,6 +380,18 @@ function getActiveDeadline(order, db) {
|
|
|
360
380
|
in_transit: 'delivery_deadline',
|
|
361
381
|
delivered: 'confirm_deadline',
|
|
362
382
|
};
|
|
383
|
+
// 2026-05-31 修:self-fulfill(logistics_id 空)无三方揽收环节,shipped/picked_up/in_transit
|
|
384
|
+
// 全部直接走 delivery_deadline,跟 CURRENT_RESPONSIBLE_SELF_FULFILL 对齐(都归 seller)。
|
|
385
|
+
// 之前 deadlineMap 单表导致 self-fulfill shipped 显示 pickup_deadline + responsible=seller,
|
|
386
|
+
// agent 看到矛盾不知道下一步(pickup 是物流的事,seller 又没物流可揽收)。
|
|
387
|
+
const deadlineMapSelfFulfill = {
|
|
388
|
+
...deadlineMapMarket,
|
|
389
|
+
shipped: 'delivery_deadline',
|
|
390
|
+
picked_up: 'delivery_deadline',
|
|
391
|
+
in_transit: 'delivery_deadline',
|
|
392
|
+
};
|
|
393
|
+
const isSelfFulfill = !order.logistics_id;
|
|
394
|
+
const deadlineMap = isSelfFulfill ? deadlineMapSelfFulfill : deadlineMapMarket;
|
|
363
395
|
// disputed 状态:从 `disputes` 表(active dispute, not the `dispute_cases` archive)查 deadline
|
|
364
396
|
// 优先返 arbitrate_deadline(仲裁总截止);如果还在 respond 窗口(被诉方未回应)返 respond_deadline
|
|
365
397
|
if (order.status === 'disputed' && db) {
|
|
@@ -214,9 +214,15 @@ Roles:
|
|
|
214
214
|
name: 'webaz_search',
|
|
215
215
|
description: `Search WebAZ marketplace + cross-platform anchor lookup. No auth required.
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
⚠️ **STRICT MATCH ONLY** — query 永远是精准匹配(等于 title / 等于 external_title / 包含 ≥6 字符 alias)。
|
|
218
|
+
不做 fuzzy LIKE 模糊降级。短查询(如 "钱包" "iphone")很大概率 0 命中,这是设计如此,不是 bug。
|
|
219
|
+
0 命中时,**不要**自己拼短词 retry,也**不要**调别的工具假装搜索;应**引导用户访问 https://webaz.xyz/#discover**
|
|
220
|
+
在搜索框输入关键词浏览(那里有 fuzzy LIKE)。模糊搜索是用户主动行为,不是 agent 代办。
|
|
221
|
+
|
|
222
|
+
USE THIS when:
|
|
223
|
+
- 用户给出 **完整商品标题 / SKU / 精准描述**(典型 strict match,如 "AirPods Pro 2 USB-C 港行"), OR
|
|
224
|
+
- 用户给出 **筛选条件**(category / max_price / min_return_days / max_handling_hours / sort), OR
|
|
225
|
+
- 用户粘贴 ANY external product URL/share-text from Taobao / Tmall / JD / PDD / 1688 / Douyin / Xiaohongshu
|
|
220
226
|
→ WebAZ exact-matches against its anchor registry (canonical product fingerprints across
|
|
221
227
|
platforms). matched_by='none' means truly not indexed — do NOT silently fall back to keyword,
|
|
222
228
|
and do NOT guess similar products.
|
|
@@ -225,7 +231,8 @@ USE THIS when the user:
|
|
|
225
231
|
NOT a separate browser-fetch capability. Returns structured specs + logistics + after-sales +
|
|
226
232
|
agent_summary (one-line decision hint).
|
|
227
233
|
|
|
228
|
-
【Keyword search】Pass query / category / max_price / min_return_days / max_handling_hours.
|
|
234
|
+
【Keyword search — STRICT】Pass query / category / max_price / min_return_days / max_handling_hours.
|
|
235
|
+
query 命中 = (1) 完全等于 product.title OR (2) 完全等于任一 external_title OR (3) 用户文本包含任一卖家声明 alias_value (≥6 字符)。模糊关键词请引导用户去 PWA #discover。
|
|
229
236
|
|
|
230
237
|
【External-link paste search】When user pastes share text from Taobao/Tmall/JD/Pinduoduo/1688/Douyin/Xiaohongshu
|
|
231
238
|
(e.g. '【淘宝】「Product Name」 https://e.tb.cn/xxx ...'), do:
|
|
@@ -1572,6 +1579,23 @@ function handleInfo() {
|
|
|
1572
1579
|
reviewer: '1. webaz_register(role=reviewer) → 2. webaz_claim_verify(action=apply) 申请测评免单 → 3. 收货后写真实评价',
|
|
1573
1580
|
arbitrator: '1. webaz_register(role=arbitrator) → 2. webaz_dispute(action=list_open) 看待裁案件 → 3. webaz_dispute(action=arbitrate) — ⚠️ 需 PWA + Passkey(Iron-Rule)',
|
|
1574
1581
|
},
|
|
1582
|
+
// 搜索/发现场景决策地图 — agent 第一次 onboard 拿到 user-scenario → tool + PWA-page 总表,
|
|
1583
|
+
// 避免每次试错。每行:scenario(用户怎么说)→ tool(我用哪个 MCP 工具)+ pwa_page(没接口时让用户去哪)
|
|
1584
|
+
search_routing: [
|
|
1585
|
+
{ scenario: '用户给出商品完整标题 / SKU / 精准描述', tool: 'webaz_search query=...', pwa_page: '#buy', note: 'STRICT 精准匹配,0 命中不降级 fuzzy' },
|
|
1586
|
+
{ scenario: '用户粘贴外站链接(淘宝/抖音/小红书/JD/PDD/1688/Tmall)', tool: 'webaz_search external_link=... 或 paste_text=...', pwa_page: '#buy', note: '匹配 anchor registry 精准产品指纹' },
|
|
1587
|
+
{ scenario: '用户要"模糊浏览"/ 关键词探索 / 不知精准 SKU', tool: '⚠️ 无 MCP 接口 — 引导用户去 PWA 自己输关键词', pwa_page: '#discover', note: 'fuzzy 是用户主动行为,不是 agent 代办,见 [protocol invariant]' },
|
|
1588
|
+
{ scenario: '用户问"我附近 / 同城 / 11km 范围 有人买什么"', tool: 'webaz_nearby action=query', pwa_page: '#nearby', note: 'k-anonymity ≥3 隐私保护' },
|
|
1589
|
+
{ scenario: '用户要二手 / 闲置 / 个人 pre-owned 商品', tool: 'webaz_secondhand action=browse', pwa_page: '#secondhand', note: '独立空间,不跟 catalog 商品混' },
|
|
1590
|
+
{ scenario: '用户要竞拍 / 捡漏稀有品 / English forward auction', tool: 'webaz_auction action=browse', pwa_page: '#auctions', note: '时间窗口事件,有 anti-snipe 延时' },
|
|
1591
|
+
{ scenario: '用户买不到,想发起求购让卖家来报价', tool: 'webaz_rfq action=create', pwa_page: '#rfqs', note: '反向匹配 — buyer 出需求 + 押金 1%' },
|
|
1592
|
+
{ scenario: '用户想申请试用 / 0 元拿样 / 测评免单', tool: 'webaz_trial action=get_campaign / apply', pwa_page: '#trials', note: '需 ≥1 笔订单 + ≥3 天账号' },
|
|
1593
|
+
{ scenario: '用户想发布或回应慈善许愿 / 捐赠社区基金', tool: 'webaz_charity action=list / create / donate', pwa_page: '#charity', note: '跟 place_order donation_pct 是不同动作' },
|
|
1594
|
+
{ scenario: '用户买知识技能 / Prompt / 模板 / 清单', tool: 'webaz_skill_market action=list / purchase / read', pwa_page: '#skill-market', note: '内容型,与 webaz_skill 行为插件市场不同' },
|
|
1595
|
+
{ scenario: '用户问某具体人 / @handle / usr_xxx 的主页或内容流', tool: 'webaz_profile action=view_user', pwa_page: '#u/:id', note: '不是关键词搜,只看指定人' },
|
|
1596
|
+
{ scenario: '用户想看某商家全部商品(已知 seller_id)', tool: 'webaz_search seller_id=...', pwa_page: '#shop/:id', note: 'strict 精准过滤,sort 可选 trending/newest' },
|
|
1597
|
+
{ scenario: '用户问什么"最热门 / 排行 / trending"', tool: 'webaz_leaderboard kind=...', pwa_page: '(无独立页,各域内置)', note: '8 类:products / value_products / creators / sellers / buyers / verifiers / arbitrators / agents' },
|
|
1598
|
+
],
|
|
1575
1599
|
available_tools: TOOLS.map((t) => ({ name: t.name, description: t.description.split('\n')[0] })),
|
|
1576
1600
|
full_manifest: `读取 MCP Resource "${MANIFEST_URI}" 获取完整协议规范(状态机/经济模型/争议系统/Skill 市场/声誉系统)`,
|
|
1577
1601
|
};
|
|
@@ -1789,7 +1813,12 @@ async function handleSearch(args) {
|
|
|
1789
1813
|
params.push(Math.min(limit * 3, 500)); // 取更多候选,便于 trending 排序
|
|
1790
1814
|
const products = db.prepare(sql).all(...params);
|
|
1791
1815
|
if (products.length === 0) {
|
|
1792
|
-
|
|
1816
|
+
// query 模式 0 命中 = strict 精准匹配未命中。**不做 fuzzy fallback**(协议精准承诺)。
|
|
1817
|
+
// 引导用户去 PWA 发现页做模糊搜索;agent 不应自己 LIKE 降级,那会污染精准 trust。
|
|
1818
|
+
const hint = query
|
|
1819
|
+
? `精准匹配 0 命中(query='${String(query).slice(0, 40)}')。webaz_search 是协议精准接口,不做模糊降级。要做模糊搜索请引导用户访问 https://webaz.xyz/#discover ,在搜索框输入关键词浏览 — 这是用户主动操作,不是 agent 代办。`
|
|
1820
|
+
: '没有找到匹配的商品';
|
|
1821
|
+
return { found: 0, message: hint, products: [], matched_by: 'strict_no_match' };
|
|
1793
1822
|
}
|
|
1794
1823
|
const enriched = products.map((p) => {
|
|
1795
1824
|
const boost = getSearchBoost(db, p.seller_id);
|
|
@@ -2319,13 +2348,17 @@ function handleGetStatus(args) {
|
|
|
2319
2348
|
const statusInfo = getOrderStatus(db, args.order_id);
|
|
2320
2349
|
if (!statusInfo)
|
|
2321
2350
|
return { error: `订单不存在:${args.order_id}` };
|
|
2322
|
-
const { order, history, currentResponsible, activeDeadline, isOverdue } = statusInfo;
|
|
2351
|
+
const { order, history, currentResponsible, activeDeadline, isOverdue, fulfillmentPhase, responsibleContext } = statusInfo;
|
|
2323
2352
|
return {
|
|
2324
2353
|
order_id: order.id,
|
|
2325
2354
|
current_status: order.status,
|
|
2326
2355
|
current_responsible: currentResponsible
|
|
2327
2356
|
? `${currentResponsible}(当前应由此角色操作)`
|
|
2328
2357
|
: '无(等待系统处理)',
|
|
2358
|
+
// Phase 1 兜底语义透传 — agent 看到 responsible=seller 时若不理解为什么(如 shipped 后还要 seller 揽收),
|
|
2359
|
+
// 应读此字段。Phase 2 logistics 市场上线后,phase_1_no_logistics_market 会消失。
|
|
2360
|
+
fulfillment_phase: fulfillmentPhase,
|
|
2361
|
+
responsible_context: responsibleContext,
|
|
2329
2362
|
deadline: activeDeadline
|
|
2330
2363
|
? {
|
|
2331
2364
|
field: activeDeadline.field,
|
package/dist/pwa/public/app.js
CHANGED
|
@@ -420,6 +420,10 @@ async function apiWithStatus(method, path, body) {
|
|
|
420
420
|
const opts = {
|
|
421
421
|
method,
|
|
422
422
|
headers: { 'Content-Type': 'application/json', ...(state.apiKey ? { Authorization: `Bearer ${state.apiKey}` } : {}) },
|
|
423
|
+
// 2026-05-31 修:boot 阶段 fetch 必须有 timeout,否则服务挂时 render 永远 await,
|
|
424
|
+
// skeleton "正在恢复登录..." 永不替换 = PWA 刷新卡死。10s 后强制 abort → catch → status=0 →
|
|
425
|
+
// 命中已有的"无法连接+重试"兜底 UI(render 530-548)。
|
|
426
|
+
signal: AbortSignal.timeout(10000),
|
|
423
427
|
}
|
|
424
428
|
if (body) opts.body = JSON.stringify(body)
|
|
425
429
|
try {
|
|
@@ -427,7 +431,7 @@ async function apiWithStatus(method, path, body) {
|
|
|
427
431
|
const json = await res.json().catch(() => ({}))
|
|
428
432
|
return { status: res.status, body: json }
|
|
429
433
|
} catch {
|
|
430
|
-
return { status: 0, body: { error: 'network' } } // 0 =
|
|
434
|
+
return { status: 0, body: { error: 'network' } } // 0 = 网络不可达或超时,保留 key 等下次
|
|
431
435
|
}
|
|
432
436
|
}
|
|
433
437
|
|
|
@@ -10600,22 +10604,25 @@ async function initShareCtx() {
|
|
|
10600
10604
|
}
|
|
10601
10605
|
|
|
10602
10606
|
// Enrich:并发 fetch sponsor 公开卡 + target 预览(阻塞返回,确保 banner 渲染时数据齐)
|
|
10607
|
+
// 2026-05-31 修:每个 fetch 加 AbortSignal.timeout(5000),防 boot 阶段任一 fetch hang
|
|
10608
|
+
// 把 render 卡死在"正在恢复登录..."
|
|
10609
|
+
const sig = () => ({ signal: AbortSignal.timeout(5000) })
|
|
10603
10610
|
const fetches = []
|
|
10604
10611
|
if (ctx.sponsor_id && !ctx.sponsor_name) {
|
|
10605
|
-
fetches.push(fetch(`/api/users/${ctx.sponsor_id}/public-card
|
|
10612
|
+
fetches.push(fetch(`/api/users/${ctx.sponsor_id}/public-card`, sig()).then(r => r.json()).then(card => {
|
|
10606
10613
|
if (card?.name) writeShareCtx({ sponsor_name: card.name, sponsor_bio: card.bio, sponsor_joined_days_ago: card.joined_days_ago })
|
|
10607
10614
|
}).catch(() => {}))
|
|
10608
10615
|
}
|
|
10609
10616
|
if (ctx.target_hash && !ctx.target_preview) {
|
|
10610
10617
|
const m = ctx.target_hash.match(/^#order-product\/(.+)$/)
|
|
10611
10618
|
if (m) {
|
|
10612
|
-
fetches.push(fetch(`/api/products/${m[1]}/preview
|
|
10619
|
+
fetches.push(fetch(`/api/products/${m[1]}/preview`, sig()).then(r => r.json()).then(p => {
|
|
10613
10620
|
if (p?.id) writeShareCtx({ target_kind: 'product', target_preview: p })
|
|
10614
10621
|
}).catch(() => {}))
|
|
10615
10622
|
} else if (ctx.target_hash.startsWith('#u/')) {
|
|
10616
10623
|
const uid = ctx.target_hash.slice(3)
|
|
10617
10624
|
if (/^usr_[A-Za-z0-9_]+$/.test(uid)) {
|
|
10618
|
-
fetches.push(fetch(`/api/users/${uid}/public-card
|
|
10625
|
+
fetches.push(fetch(`/api/users/${uid}/public-card`, sig()).then(r => r.json()).then(p => {
|
|
10619
10626
|
if (p?.id) writeShareCtx({ target_kind: 'user', target_preview: p })
|
|
10620
10627
|
}).catch(() => {}))
|
|
10621
10628
|
}
|
package/dist/pwa/public/sw.js
CHANGED
package/dist/pwa/server.js
CHANGED
|
@@ -126,6 +126,8 @@ import { registerExternalAnchorsRoutes } from './routes/external-anchors.js';
|
|
|
126
126
|
import { registerAnchorsRoutes } from './routes/anchors.js';
|
|
127
127
|
// 仲裁员申请 (#1013 Phase 44) — 4 user + 3 admin endpoints
|
|
128
128
|
import { registerArbitratorRoutes } from './routes/arbitrator.js';
|
|
129
|
+
// 卖家配额 + 数据中心 (#1013 Phase 45) — 4 user + 3 admin
|
|
130
|
+
import { registerSellerQuotaRoutes } from './routes/seller-quota.js';
|
|
129
131
|
// 验证员用户侧 (#1013 Phase 46) — 5 endpoints
|
|
130
132
|
import { registerVerifierUserRoutes } from './routes/verifier-user.js';
|
|
131
133
|
// 公开用户主页 (#1013 Phase 47) — 6 endpoints
|
|
@@ -7257,6 +7259,13 @@ registerOrdersCreateRoutes(app, {
|
|
|
7257
7259
|
signPassport: (message) => privateKeyToAccount(derivePrivKey('platform-hot-wallet')).signMessage({ message }),
|
|
7258
7260
|
issuerAddress: () => privateKeyToAddress(derivePrivKey('platform-hot-wallet')),
|
|
7259
7261
|
});
|
|
7262
|
+
// #1013 Phase 45: 卖家配额 + 数据中心 7 endpoints — 2026-05-31 修补:之前 import 了但忘了 register,
|
|
7263
|
+
// 导致 /api/seller/quota-status + /api/seller/insights 落入 SPA fallback 返回 HTML,前端 JSON.parse 死循环
|
|
7264
|
+
registerSellerQuotaRoutes(app, {
|
|
7265
|
+
db, generateId, auth,
|
|
7266
|
+
requireUsersAdmin: (req, res) => requireAdminPermission(req, res, 'users'),
|
|
7267
|
+
safeRoles, checkSellerCanList, adminCanOperateOn, logAdminAction, QUOTA_TIERS,
|
|
7268
|
+
});
|
|
7260
7269
|
// #1013 Phase 86: 5 disputes 读端点已迁出
|
|
7261
7270
|
registerDisputesReadRoutes(app, {
|
|
7262
7271
|
db, auth, errorRes,
|
package/package.json
CHANGED