@seasonkoh/webaz 0.1.13 → 0.1.14

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: `此操作需要上传证据。提示:${rule.evidenceHint ?? '请上传相关证明文件'}`
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. 执行转移(数据库事务,保证原子性)
@@ -351,7 +354,7 @@ function getActiveDeadline(order, db) {
351
354
  // QA 轮 7 P1:旧表 picked_up 状态没 deadline → agent 不知道下一步多久前要做完
352
355
  // 修:picked_up 状态视为"已揽收,等运输/投递",下一个 deadline 是 delivery_deadline
353
356
  // QA 轮 7 P1(另一条):disputed 状态下没读 dispute_cases 的 arbitrate_deadline → agent 不知道仲裁还有多久
354
- const deadlineMap = {
357
+ const deadlineMapMarket = {
355
358
  created: 'pay_deadline',
356
359
  paid: 'accept_deadline',
357
360
  accepted: 'ship_deadline',
@@ -360,6 +363,18 @@ function getActiveDeadline(order, db) {
360
363
  in_transit: 'delivery_deadline',
361
364
  delivered: 'confirm_deadline',
362
365
  };
366
+ // 2026-05-31 修:self-fulfill(logistics_id 空)无三方揽收环节,shipped/picked_up/in_transit
367
+ // 全部直接走 delivery_deadline,跟 CURRENT_RESPONSIBLE_SELF_FULFILL 对齐(都归 seller)。
368
+ // 之前 deadlineMap 单表导致 self-fulfill shipped 显示 pickup_deadline + responsible=seller,
369
+ // agent 看到矛盾不知道下一步(pickup 是物流的事,seller 又没物流可揽收)。
370
+ const deadlineMapSelfFulfill = {
371
+ ...deadlineMapMarket,
372
+ shipped: 'delivery_deadline',
373
+ picked_up: 'delivery_deadline',
374
+ in_transit: 'delivery_deadline',
375
+ };
376
+ const isSelfFulfill = !order.logistics_id;
377
+ const deadlineMap = isSelfFulfill ? deadlineMapSelfFulfill : deadlineMapMarket;
363
378
  // disputed 状态:从 `disputes` 表(active dispute, not the `dispute_cases` archive)查 deadline
364
379
  // 优先返 arbitrate_deadline(仲裁总截止);如果还在 respond 窗口(被诉方未回应)返 respond_deadline
365
380
  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
- USE THIS when the user:
218
- - gives KEYWORDS / filters (typical product discovery), OR
219
- - pastes ANY external product URL/share-text from Taobao / Tmall / JD / PDD / 1688 / Douyin / Xiaohongshu
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:
@@ -1789,7 +1796,12 @@ async function handleSearch(args) {
1789
1796
  params.push(Math.min(limit * 3, 500)); // 取更多候选,便于 trending 排序
1790
1797
  const products = db.prepare(sql).all(...params);
1791
1798
  if (products.length === 0) {
1792
- return { found: 0, message: '没有找到匹配的商品', products: [] };
1799
+ // query 模式 0 命中 = strict 精准匹配未命中。**不做 fuzzy fallback**(协议精准承诺)。
1800
+ // 引导用户去 PWA 发现页做模糊搜索;agent 不应自己 LIKE 降级,那会污染精准 trust。
1801
+ const hint = query
1802
+ ? `精准匹配 0 命中(query='${String(query).slice(0, 40)}')。webaz_search 是协议精准接口,不做模糊降级。要做模糊搜索请引导用户访问 https://webaz.xyz/#discover ,在搜索框输入关键词浏览 — 这是用户主动操作,不是 agent 代办。`
1803
+ : '没有找到匹配的商品';
1804
+ return { found: 0, message: hint, products: [], matched_by: 'strict_no_match' };
1793
1805
  }
1794
1806
  const enriched = products.map((p) => {
1795
1807
  const boost = getSearchBoost(db, p.seller_id);
@@ -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 = 网络不可达,保留 key 等下次
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`).then(r => r.json()).then(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`).then(r => r.json()).then(p => {
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`).then(r => r.json()).then(p => {
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
  }
@@ -1,5 +1,5 @@
1
1
  // Service Worker — 网络优先,离线降级缓存;API 请求不缓存
2
- const CACHE = 'webaz-v480'
2
+ const CACHE = 'webaz-v481'
3
3
 
4
4
  self.addEventListener('install', e => {
5
5
  self.skipWaiting()
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seasonkoh/webaz",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Agent-native decentralized commerce protocol. Humans and AI agents trade on the same protocol via MCP tools.",
5
5
  "main": "dist/mcp.js",
6
6
  "bin": {