@seasonkoh/webaz 0.1.23 → 0.1.25
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 +2 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
- package/dist/layer0-foundation/L0-1-database/db.js +65 -0
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
- package/dist/layer1-agent/L1-1-mcp-server/server.js +198 -83
- package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
- package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
- package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
- package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
- package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +173 -0
- package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
- package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
- package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +10 -2
- package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
- package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
- package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
- package/dist/pwa/acp-feed.js +13 -1
- package/dist/pwa/contract-fingerprint.js +2 -0
- package/dist/pwa/endpoint-actions.js +5 -1
- package/dist/pwa/goal-index.js +8 -8
- package/dist/pwa/human-presence.js +62 -0
- package/dist/pwa/public/app.js +575 -68
- package/dist/pwa/public/i18n.js +29 -20
- package/dist/pwa/public/index.html +1 -0
- package/dist/pwa/public/openapi.json +2 -2
- package/dist/pwa/rate-limit.js +22 -0
- package/dist/pwa/routes/account-deletion.js +15 -13
- package/dist/pwa/routes/addresses.js +10 -9
- package/dist/pwa/routes/admin-admins.js +13 -14
- package/dist/pwa/routes/admin-analytics.js +109 -69
- package/dist/pwa/routes/admin-catalog.js +13 -11
- package/dist/pwa/routes/admin-editor-picks.js +15 -10
- package/dist/pwa/routes/admin-events.js +5 -3
- package/dist/pwa/routes/admin-health.js +2 -1
- package/dist/pwa/routes/admin-moderation.js +26 -29
- package/dist/pwa/routes/admin-ops.js +22 -21
- package/dist/pwa/routes/admin-protocol-params.js +16 -19
- package/dist/pwa/routes/admin-reports.js +23 -21
- package/dist/pwa/routes/admin-tokenomics.js +26 -25
- package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
- package/dist/pwa/routes/admin-users-query.js +54 -53
- package/dist/pwa/routes/admin-verifier-flow.js +82 -41
- package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
- package/dist/pwa/routes/admin-wallet-ops.js +7 -5
- package/dist/pwa/routes/agent-buy.js +46 -22
- package/dist/pwa/routes/agent-governance.js +52 -56
- package/dist/pwa/routes/ai.js +7 -5
- package/dist/pwa/routes/analytics.js +43 -41
- package/dist/pwa/routes/anchors.js +19 -20
- package/dist/pwa/routes/announcements.js +13 -13
- package/dist/pwa/routes/arbitrator.js +97 -31
- package/dist/pwa/routes/auction.js +153 -114
- package/dist/pwa/routes/auth-login.js +6 -4
- package/dist/pwa/routes/auth-read.js +11 -9
- package/dist/pwa/routes/auth-register.js +35 -20
- package/dist/pwa/routes/auth-sessions.js +12 -11
- package/dist/pwa/routes/blocklist.js +16 -15
- package/dist/pwa/routes/build-feedback.js +10 -9
- package/dist/pwa/routes/build-reputation.js +6 -2
- package/dist/pwa/routes/build-tasks.js +45 -13
- package/dist/pwa/routes/buyer-feeds.js +27 -25
- package/dist/pwa/routes/cart.js +16 -15
- package/dist/pwa/routes/charity.js +212 -150
- package/dist/pwa/routes/chat.js +42 -43
- package/dist/pwa/routes/checkin-tasks.js +10 -9
- package/dist/pwa/routes/checkout-helpers.js +12 -10
- package/dist/pwa/routes/claim-initiators.js +34 -14
- package/dist/pwa/routes/claim-verify.js +86 -53
- package/dist/pwa/routes/claim-voting.js +43 -18
- package/dist/pwa/routes/contribution-identity.js +147 -0
- package/dist/pwa/routes/contribution-score.js +19 -0
- package/dist/pwa/routes/coupons.js +19 -16
- package/dist/pwa/routes/dashboards.js +18 -16
- package/dist/pwa/routes/dispute-cases.js +25 -24
- package/dist/pwa/routes/disputes-read.js +45 -51
- package/dist/pwa/routes/disputes-write.js +124 -61
- package/dist/pwa/routes/evidence.js +9 -9
- package/dist/pwa/routes/external-anchors.js +13 -12
- package/dist/pwa/routes/feedback.js +29 -33
- package/dist/pwa/routes/flash-sales.js +18 -16
- package/dist/pwa/routes/follows.js +25 -24
- package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
- package/dist/pwa/routes/governance-onboarding.js +70 -59
- package/dist/pwa/routes/group-buys.js +22 -22
- package/dist/pwa/routes/growth.js +33 -30
- package/dist/pwa/routes/import-product.js +12 -10
- package/dist/pwa/routes/kyc.js +9 -8
- package/dist/pwa/routes/leaderboard.js +20 -18
- package/dist/pwa/routes/listings.js +23 -22
- package/dist/pwa/routes/logistics.js +10 -8
- package/dist/pwa/routes/manifests.js +27 -27
- package/dist/pwa/routes/me-data.js +23 -21
- package/dist/pwa/routes/notifications.js +7 -6
- package/dist/pwa/routes/offers.js +30 -12
- package/dist/pwa/routes/orders-action.js +33 -17
- package/dist/pwa/routes/orders-create.js +75 -20
- package/dist/pwa/routes/orders-read.js +21 -20
- package/dist/pwa/routes/p2p-products.js +30 -18
- package/dist/pwa/routes/payments-governance.js +61 -56
- package/dist/pwa/routes/peers.js +9 -8
- package/dist/pwa/routes/pin-receipts.js +13 -13
- package/dist/pwa/routes/products-aliases.js +12 -10
- package/dist/pwa/routes/products-claims.js +36 -17
- package/dist/pwa/routes/products-create.js +53 -38
- package/dist/pwa/routes/products-crud.js +17 -16
- package/dist/pwa/routes/products-links.js +49 -26
- package/dist/pwa/routes/products-list.js +6 -4
- package/dist/pwa/routes/products-meta.js +40 -39
- package/dist/pwa/routes/products-update.js +19 -5
- package/dist/pwa/routes/profile-credentials.js +14 -16
- package/dist/pwa/routes/profile-identity.js +14 -13
- package/dist/pwa/routes/profile-location.js +7 -6
- package/dist/pwa/routes/profile-placement.js +19 -17
- package/dist/pwa/routes/profile-prefs.js +11 -11
- package/dist/pwa/routes/promoter.js +55 -49
- package/dist/pwa/routes/public-build-tasks.js +19 -0
- package/dist/pwa/routes/public-utils.js +108 -46
- package/dist/pwa/routes/push.js +16 -15
- package/dist/pwa/routes/ratings.js +30 -30
- package/dist/pwa/routes/recover-key.js +13 -12
- package/dist/pwa/routes/referral.js +37 -32
- package/dist/pwa/routes/reputation.js +3 -2
- package/dist/pwa/routes/returns.js +76 -73
- package/dist/pwa/routes/reviews.js +41 -18
- package/dist/pwa/routes/rewards-apply.js +16 -15
- package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
- package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
- package/dist/pwa/routes/rfqs.js +163 -85
- package/dist/pwa/routes/search.js +16 -14
- package/dist/pwa/routes/secondhand.js +25 -22
- package/dist/pwa/routes/seller-quota.js +24 -26
- package/dist/pwa/routes/share-redirects.js +59 -55
- package/dist/pwa/routes/shareables-interactions.js +34 -35
- package/dist/pwa/routes/shareables.js +55 -51
- package/dist/pwa/routes/shop-referral.js +57 -0
- package/dist/pwa/routes/shops.js +20 -18
- package/dist/pwa/routes/signaling.js +10 -9
- package/dist/pwa/routes/skill-market.js +16 -16
- package/dist/pwa/routes/skills.js +15 -14
- package/dist/pwa/routes/snf.js +14 -13
- package/dist/pwa/routes/tags.js +10 -9
- package/dist/pwa/routes/task-proposals.js +45 -0
- package/dist/pwa/routes/trial.js +69 -51
- package/dist/pwa/routes/trusted-kpi.js +20 -18
- package/dist/pwa/routes/url-claim.js +67 -28
- package/dist/pwa/routes/users-public.js +62 -60
- package/dist/pwa/routes/variants.js +12 -13
- package/dist/pwa/routes/verifier-user.js +61 -21
- package/dist/pwa/routes/verify-tasks.js +49 -25
- package/dist/pwa/routes/waitlist.js +16 -15
- package/dist/pwa/routes/wallet-read.js +74 -36
- package/dist/pwa/routes/wallet-write.js +12 -9
- package/dist/pwa/routes/webauthn.js +25 -26
- package/dist/pwa/routes/webhooks.js +26 -26
- package/dist/pwa/routes/welcome.js +45 -50
- package/dist/pwa/routes/wishlist-qa.js +29 -32
- package/dist/pwa/server.js +237 -81
- package/dist/version.js +1 -1
- package/package.json +47 -2
|
@@ -22,6 +22,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
22
22
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, // #B.1 a — MCP 三大原语之 Prompts
|
|
23
23
|
GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
24
24
|
import { initDatabase, generateId } from '../../layer0-foundation/L0-1-database/schema.js';
|
|
25
|
+
import { setSeamDb } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam(本进程注入)
|
|
25
26
|
import { transition, getOrderStatus, initSystemUser, } from '../../layer0-foundation/L0-2-state-machine/engine.js';
|
|
26
27
|
import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes, } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
|
|
27
28
|
import { initNotificationSchema, notifyTransition, getNotifications, getUnreadCount, markRead, } from '../../layer2-business/L2-6-notifications/notification-engine.js';
|
|
@@ -43,10 +44,16 @@ const TELEMETRY_ENABLED = (process.env.WEBAZ_TELEMETRY ?? 'off').toLowerCase() =
|
|
|
43
44
|
const WEBAZ_API_URL = (process.env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
|
|
44
45
|
const WEBAZ_API_KEY = process.env.WEBAZ_API_KEY ?? '';
|
|
45
46
|
const WEBAZ_MODE_ENV = (process.env.WEBAZ_MODE ?? '').toLowerCase();
|
|
46
|
-
// 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 →
|
|
47
|
+
// 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 key → network_readonly(装完即见真网络)。
|
|
48
|
+
// network_readonly(L1 onboarding,2026-06-08):无 key 默认。公共读匿名打 webaz.xyz(真 catalog/协议),
|
|
49
|
+
// 需身份的写/读返回"设 WEBAZ_API_KEY(到 #welcome 申请邀请)"。离线本地 playground 改为【显式】 WEBAZ_MODE=sandbox。
|
|
50
|
+
// —— 治"装完=空沙盒劝退"的死首体验;route/guard 与 network 同路(见 isNetworkMode),只是无 Bearer + 文案不同。
|
|
47
51
|
const MODE = WEBAZ_MODE_ENV === 'network' ? 'network'
|
|
48
52
|
: WEBAZ_MODE_ENV === 'sandbox' ? 'sandbox'
|
|
49
|
-
:
|
|
53
|
+
: WEBAZ_MODE_ENV === 'network_readonly' ? 'network_readonly'
|
|
54
|
+
: (WEBAZ_API_KEY ? 'network' : 'network_readonly');
|
|
55
|
+
// network 或 network_readonly 都"走真网络"(后者无 Bearer)。sandbox 才是本地。
|
|
56
|
+
const isNetworkMode = () => MODE === 'network' || MODE === 'network_readonly';
|
|
50
57
|
// 已迁移到 NETWORK 的工具名。P1/P2 逐个加入;未在集合里的工具仍走 sandbox(本地)。
|
|
51
58
|
// P1(读工具): 纯公开读,无写无 Passkey,作"MCP 连得上生产网络"的首验证。
|
|
52
59
|
const NETWORK_TOOLS = new Set([
|
|
@@ -103,9 +110,10 @@ function pushRecentCall(c) {
|
|
|
103
110
|
if (recentCalls.length > 8)
|
|
104
111
|
recentCalls.shift(); // 只留最近 8 条
|
|
105
112
|
}
|
|
106
|
-
//
|
|
113
|
+
// 单个工具实际后端:network 或 network_readonly 下、且该工具已迁移,才走网络;否则 sandbox。
|
|
114
|
+
// readonly 无 Bearer:公共读拿真数据,需身份的端点服务端返 401(诚实)→ 不会静默落本地。
|
|
107
115
|
function toolBackend(tool) {
|
|
108
|
-
return (
|
|
116
|
+
return (isNetworkMode() && NETWORK_TOOLS.has(tool)) ? 'network' : 'sandbox';
|
|
109
117
|
}
|
|
110
118
|
// 未在 NETWORK_TOOLS 名单、但 NETWORK 模式下仍可本地运行的"自省/引导"工具(非数据操作)。
|
|
111
119
|
// info = 本地自省(并拉 live 网络状态);register = 引导真人去 webaz.xyz。其余未迁工具一律硬失败。
|
|
@@ -166,14 +174,18 @@ async function apiCall(path, opts = {}) {
|
|
|
166
174
|
// 启动 banner(stderr)+ status 声明用 —— 让用户/agent 一眼知道现在是真网络还是沙盒
|
|
167
175
|
function modeBanner() {
|
|
168
176
|
if (MODE === 'network') {
|
|
169
|
-
return `🟢 NETWORK mode — webaz.xyz (${WEBAZ_API_URL}). Migrated tools: ${NETWORK_TOOLS.size}/${TOOLS.length}
|
|
170
|
-
+ (NETWORK_TOOLS.size === 0 ? ' ⚠️ network client not active yet (P0 scaffold) — all tools still SANDBOX/local.' : '');
|
|
177
|
+
return `🟢 NETWORK mode — webaz.xyz (${WEBAZ_API_URL}), authenticated. Migrated tools: ${NETWORK_TOOLS.size}/${TOOLS.length}`;
|
|
171
178
|
}
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
if (MODE === 'network_readonly') {
|
|
180
|
+
return `🟢 NETWORK (read-only) — no api_key: public reads (search / leaderboard / price history / browse) hit the LIVE webaz.xyz network. `
|
|
181
|
+
+ `To transact (register/order/list/etc.), set WEBAZ_API_KEY — request an invite at ${WEBAZ_API_URL}/#welcome.`;
|
|
182
|
+
}
|
|
183
|
+
return `🟡 SANDBOX mode — local-only (~/.webaz/webaz.db), NOT the live network. Data is private to this machine. `
|
|
184
|
+
+ `(Explicit dev/demo mode; unset WEBAZ_MODE to use the live network read-only by default.)`;
|
|
174
185
|
}
|
|
175
186
|
// ─── 初始化 ──────────────────────────────────────────────────
|
|
176
187
|
const db = initDatabase();
|
|
188
|
+
setSeamDb(db); // RFC-016 Phase 1:注入异步 DB seam(本进程)—— 共享引擎迁 seam 后 MCP 进程也能用,否则 dbOne/dbAll 抛"未初始化"
|
|
177
189
|
initSystemUser(db);
|
|
178
190
|
initDisputeSchema(db);
|
|
179
191
|
initNotificationSchema(db);
|
|
@@ -313,7 +325,7 @@ No auth required, no parameters needed.
|
|
|
313
325
|
|
|
314
326
|
Roles: buyer (browse/order/confirm) | seller (list/accept/ship) | logistics (pickup/transit/deliver) | reviewer (reviews) | arbitrator (disputes/rulings).
|
|
315
327
|
|
|
316
|
-
⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`?ref=<
|
|
328
|
+
⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`/i/<permanent_code>\` (or \`?ref=<permanent_code>\`) URL clicked in browser (PWA flow). region defaults 'global'; valid: singapore/china/usa/malaysia/indonesia/thailand/vietnam/taiwan/hk/global.`,
|
|
317
329
|
inputSchema: {
|
|
318
330
|
type: 'object',
|
|
319
331
|
properties: {
|
|
@@ -854,7 +866,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
|
|
|
854
866
|
name: 'webaz_referral',
|
|
855
867
|
description: `View your referral status: 3-tier commission team + earnings + invite links + points-matching tier progress + L1 share permission gate + **rewards_status (RFC-002 §3.5 opt-in state + pending escrow)**.
|
|
856
868
|
|
|
857
|
-
⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree
|
|
869
|
+
⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree. Commission destination differs by state: **never_activated / auto_downgraded** → held in pending_commission_escrow, recoverable by (re-)activating within the window via PWA #me; **deactivated** (active opt-out) → future L1/L2/L3 commission goes to commission_reserve / protocol reserve, NOT escrow and NOT recoverable (re-applying only affects future commission).
|
|
858
870
|
|
|
859
871
|
⚠️ **Consent required**: generating referral links / promoting on a human user's behalf needs the user's explicit authorization. Agent **MUST get explicit consent** before generating referral links / promoting. Do NOT auto-recruit.`,
|
|
860
872
|
inputSchema: {
|
|
@@ -869,7 +881,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
|
|
|
869
881
|
name: 'webaz_share_link',
|
|
870
882
|
description: `Generate product share link with your referral attached. Open in any social platform (TikTok/WeChat/Telegram). Clicker registers/buys → counts toward your 3-tier commission (if verified buyer) + points-matching.
|
|
871
883
|
|
|
872
|
-
⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer action. Caller must have \`rewards_opted_in=1\` (
|
|
884
|
+
⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer (rewards) action, not a contribution gate. Caller must have \`rewards_opted_in=1\` (rewards / share-commission opt-in). Opted-out users get \`{error: 'rewards_opt_in_required', missing_requirements, next_steps}\` — direct user to PWA #me to apply.
|
|
873
885
|
|
|
874
886
|
⚠️ **Consent required**: this builds a referral chain on the user's behalf. Agent acting for a human user **MUST get explicit consent**. Do NOT auto-generate. See webaz_info.commission_model.`,
|
|
875
887
|
inputSchema: {
|
|
@@ -1416,28 +1428,44 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
|
|
|
1416
1428
|
},
|
|
1417
1429
|
{
|
|
1418
1430
|
name: 'webaz_contribute',
|
|
1419
|
-
description: `Coordinate building WebAZ itself (RFC-006) — a
|
|
1431
|
+
description: `Coordinate building WebAZ itself (RFC-006 / RFC-017) — a public task board so contributors don't collide. Check BEFORE starting work on an area. 协调"谁在做什么"防撞车.
|
|
1432
|
+
|
|
1433
|
+
Discovery + suggesting need NO api_key (anyone / any agent can browse and propose). Claiming + submitting need an api_key (a real, accountable identity).
|
|
1420
1434
|
|
|
1421
1435
|
Actions:
|
|
1422
|
-
- list_open (default): open tasks (opt. area
|
|
1423
|
-
-
|
|
1424
|
-
-
|
|
1425
|
-
-
|
|
1426
|
-
-
|
|
1436
|
+
- list_open (default): open public tasks (opt. filters: area / risk_level / auto_claimable / required_capabilities / agent_capabilities / max_duration_minutes / estimated_context_size / estimated_agent_budget — estimated_agent_budget is a resource/effort estimate, NOT a payment). Each task carries its execution boundary + the trusted canonical contribution target. NO api_key needed.
|
|
1437
|
+
- detail: one task's full execution boundary (allowed/forbidden paths, prohibited actions, acceptance criteria, verification commands, deliverables, definition_of_done) + the canonical repo to PR to + a copy-ready agent_handoff. NO api_key needed.
|
|
1438
|
+
- suggest: propose a NEW task (title + summary/reason; opt. area/expected_outcome/source_ref/github_login). It enters the maintainer inbox — it is a suggestion, NOT a contribution fact / reward / participation, and never auto-becomes a task. NO api_key needed.
|
|
1439
|
+
- claim: take an open task (api_key); provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted. Returns a handoff — point a coding agent at it; the human needn't know git but stays accountable (Passkey).
|
|
1440
|
+
- submit: mark in_review with pr_ref + verification_summary (api_key). The PR's base repo MUST be the canonical WebAZ repo, and a verification_summary (what you ran/verified) is REQUIRED — both server-enforced. A human maintainer reviews next; done ≠ merge.
|
|
1441
|
+
- status: tasks you hold (api_key).
|
|
1442
|
+
- profile: your build dashboard — KPI/tier/restrictions+appeal, private self-view (api_key).
|
|
1427
1443
|
|
|
1428
|
-
Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. build_reputation is a SEPARATE pool, never gates verifier/arbitrator.
|
|
1444
|
+
Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. Contribution value is uncommitted (RFC-017 I-12). build_reputation is a SEPARATE pool, never gates verifier/arbitrator. NETWORK only (contribution is a real-network act; sandbox has nothing to coordinate with).`,
|
|
1429
1445
|
inputSchema: {
|
|
1430
1446
|
type: 'object',
|
|
1431
1447
|
properties: {
|
|
1432
|
-
action: { type: 'string', enum: ['list_open', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | claim | submit | status | profile' },
|
|
1433
|
-
api_key: { type: 'string', description:
|
|
1434
|
-
task_id: { type: 'string', description: 'claim / submit: the task id' },
|
|
1435
|
-
area: { type: 'string', description: 'list_open:
|
|
1448
|
+
action: { type: 'string', enum: ['list_open', 'detail', 'suggest', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | detail | suggest | claim | submit | status | profile' },
|
|
1449
|
+
api_key: { type: 'string', description: 'claim/submit/status/profile: your api_key (accountable identity). NOT needed for list_open/detail/suggest.' },
|
|
1450
|
+
task_id: { type: 'string', description: 'detail / claim / submit: the task id' },
|
|
1451
|
+
area: { type: 'string', description: 'list_open: area filter / suggest: suggested area (e.g. search / docs / mcp)' },
|
|
1452
|
+
risk_level: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'list_open: optional risk filter' },
|
|
1453
|
+
auto_claimable: { type: 'boolean', description: 'list_open: optional filter — only auto-claimable (true) or manual-claim (false) tasks' },
|
|
1454
|
+
required_capabilities: { type: 'string', description: 'list_open: optional filter — comma-separated; matches tasks that REQUIRE ALL of the listed capabilities (superset/AND match on the task requirement). For "tasks my agent can do", use agent_capabilities instead.' },
|
|
1455
|
+
agent_capabilities: { type: 'string', description: 'list_open: optional filter — capabilities your agent HAS (comma-separated); matches tasks whose required_capabilities are a SUBSET of these, i.e. tasks your agent can actually do' },
|
|
1456
|
+
max_duration_minutes: { type: 'number', description: 'list_open: optional filter — only tasks whose estimated max duration fits within this many minutes (your idle time)' },
|
|
1457
|
+
estimated_context_size: { type: 'string', enum: ['small', 'medium', 'large'], description: 'list_open: optional filter — task estimated context size' },
|
|
1458
|
+
estimated_agent_budget: { type: 'string', enum: ['minimal', 'small', 'moderate', 'large', 'xlarge'], description: 'list_open: optional filter — task estimated agent budget (resource/effort estimate, not a payment)' },
|
|
1436
1459
|
provenance: { type: 'string', enum: ['human', 'ai_assisted', 'ai_authored'], description: 'claim: self-declared authorship (default human)' },
|
|
1437
|
-
pr_ref: { type: 'string', description: 'submit: your PR link or number' },
|
|
1460
|
+
pr_ref: { type: 'string', description: 'submit: your PR link or number (must target the canonical repo)' },
|
|
1461
|
+
verification_summary: { type: 'string', description: 'submit (REQUIRED): summarize what you ran/verified — the task verification_commands you ran and their results' },
|
|
1438
1462
|
note: { type: 'string', description: 'submit: optional note' },
|
|
1463
|
+
title: { type: 'string', description: 'suggest: task title (≥3 chars)' },
|
|
1464
|
+
summary: { type: 'string', description: 'suggest: why it is worth doing / what it solves (the reason)' },
|
|
1465
|
+
expected_outcome: { type: 'string', description: 'suggest: optional — what should be true when done' },
|
|
1466
|
+
source_ref: { type: 'string', description: 'suggest: optional reference link (reference only; does NOT set the target repo)' },
|
|
1467
|
+
proposer_github_login: { type: 'string', description: 'suggest: optional — your GitHub login' },
|
|
1439
1468
|
},
|
|
1440
|
-
required: ['api_key'],
|
|
1441
1469
|
},
|
|
1442
1470
|
},
|
|
1443
1471
|
];
|
|
@@ -1479,19 +1507,100 @@ async function handleFeedback(args) {
|
|
|
1479
1507
|
},
|
|
1480
1508
|
});
|
|
1481
1509
|
}
|
|
1482
|
-
// RFC-006
|
|
1483
|
-
|
|
1510
|
+
// RFC-006 断点1(b)交接:从【可信】canonical 目标(API 响应里,绝不硬编码/不取自 task metadata)构造"怎么真正
|
|
1511
|
+
// 动手"。人的编码 agent 做 git/PR;Passkey 真人担责。sandbox 运行 / 本地草稿不算正式参与。
|
|
1512
|
+
function buildContributeHandoff(cct, taskId) {
|
|
1513
|
+
const c = (cct ?? {});
|
|
1514
|
+
const repoUrl = c.canonical_github_url || 'https://github.com/seasonsagents-art/webaz';
|
|
1515
|
+
const baseRepo = c.expected_pr_base_repo || c.canonical_repository_full_name || 'seasonsagents-art/webaz';
|
|
1516
|
+
const baseBranch = c.base_branch || 'main';
|
|
1517
|
+
return {
|
|
1518
|
+
canonical_repo: baseRepo,
|
|
1519
|
+
repo: repoUrl,
|
|
1520
|
+
base_branch: baseBranch,
|
|
1521
|
+
start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
|
|
1522
|
+
do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
|
|
1523
|
+
submit_pr: `Open a PR whose BASE repo is ${baseRepo} (${repoUrl}), base branch ${baseBranch}. If any target repo differs from this canonical repo, STOP and ask the human — never contribute to a non-canonical repository.`,
|
|
1524
|
+
pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, mark the PR per the meta-rule. Humans merge — no auto-merge.',
|
|
1525
|
+
then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${taskId} pr_ref=#<N> verification_summary="<the verification_commands you ran + their results>". Both pr_ref and verification_summary are required.`,
|
|
1526
|
+
not_participation: 'A sandbox run or a local-only draft is NOT participation and is NOT a contribution; only a merged PR (or recognized issue/task/RFC) on the canonical repo enters the contribution record.',
|
|
1527
|
+
human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
// RFC-006/RFC-017: webaz_contribute — 协调"谁在做什么"。NETWORK only. Discovery + suggest 无需 key(打公开
|
|
1531
|
+
// 端口 #329/#331);claim/submit/status/profile 需 key(真实可问责身份,走受 #330 守卫的 member 端口)。
|
|
1532
|
+
export async function handleContribute(args) {
|
|
1484
1533
|
const action = args.action || 'list_open';
|
|
1485
1534
|
const apiKey = args.api_key;
|
|
1486
|
-
if (!apiKey)
|
|
1487
|
-
return { error: 'api_key required' };
|
|
1488
1535
|
if (toolBackend('webaz_contribute') !== 'network') {
|
|
1489
1536
|
return {
|
|
1490
1537
|
_mode: 'sandbox',
|
|
1491
|
-
error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK
|
|
1538
|
+
error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK 模式(或不设 key 默认 network_readonly 也可浏览/建议)。 / Coordination needs the live network; sandbox has nothing to coordinate with.',
|
|
1492
1539
|
error_code: 'CONTRIBUTE_NEEDS_NETWORK',
|
|
1493
1540
|
};
|
|
1494
1541
|
}
|
|
1542
|
+
// ── keyless discovery + suggest (public surface; same trusted canonical target as the PWA) ──
|
|
1543
|
+
if (action === 'list_open') {
|
|
1544
|
+
// public endpoint already restricts to audience=public + status=open; only pass the optional filters.
|
|
1545
|
+
const qs = new URLSearchParams();
|
|
1546
|
+
if (args.area)
|
|
1547
|
+
qs.set('area', String(args.area));
|
|
1548
|
+
if (args.risk_level)
|
|
1549
|
+
qs.set('risk_level', String(args.risk_level));
|
|
1550
|
+
if (args.auto_claimable !== undefined)
|
|
1551
|
+
qs.set('auto_claimable', String(Boolean(args.auto_claimable)));
|
|
1552
|
+
if (args.required_capabilities)
|
|
1553
|
+
qs.set('required_capabilities', String(args.required_capabilities));
|
|
1554
|
+
if (args.agent_capabilities !== undefined)
|
|
1555
|
+
qs.set('agent_capabilities', String(args.agent_capabilities)); // forward even '' so the route fail-closes (typed 400), never silently returns the full list
|
|
1556
|
+
if (args.max_duration_minutes !== undefined)
|
|
1557
|
+
qs.set('max_duration_minutes', String(args.max_duration_minutes));
|
|
1558
|
+
if (args.estimated_context_size)
|
|
1559
|
+
qs.set('estimated_context_size', String(args.estimated_context_size));
|
|
1560
|
+
if (args.estimated_agent_budget)
|
|
1561
|
+
qs.set('estimated_agent_budget', String(args.estimated_agent_budget));
|
|
1562
|
+
const q = qs.toString();
|
|
1563
|
+
const r = await apiCall('/api/public/build-tasks' + (q ? '?' + q : ''));
|
|
1564
|
+
if (!r.error)
|
|
1565
|
+
r._next = 'Pick a task, then: webaz_contribute action=detail task_id=<id> for its full execution boundary + the canonical repo to PR to; then action=claim task_id=<id> api_key=<key> to take it (claiming needs an account).';
|
|
1566
|
+
return r;
|
|
1567
|
+
}
|
|
1568
|
+
if (action === 'detail') {
|
|
1569
|
+
const tid = args.task_id;
|
|
1570
|
+
if (!tid)
|
|
1571
|
+
return { error: 'task_id required for action=detail' };
|
|
1572
|
+
const r = await apiCall('/api/public/build-tasks/' + encodeURIComponent(tid));
|
|
1573
|
+
if (!r.error && r.task)
|
|
1574
|
+
r.agent_handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
|
|
1575
|
+
return r;
|
|
1576
|
+
}
|
|
1577
|
+
if (action === 'suggest') {
|
|
1578
|
+
const title = (args.title ?? '').trim();
|
|
1579
|
+
const summary = (args.summary ?? args.note ?? '').trim();
|
|
1580
|
+
if (title.length < 3)
|
|
1581
|
+
return { error: 'title required (≥3 chars) for action=suggest' };
|
|
1582
|
+
if (summary.length < 1)
|
|
1583
|
+
return { error: 'summary (the reason) required for action=suggest' };
|
|
1584
|
+
const r = await apiCall('/api/public/task-proposals', {
|
|
1585
|
+
method: 'POST',
|
|
1586
|
+
body: {
|
|
1587
|
+
title, summary,
|
|
1588
|
+
suggested_area: args.area ?? args.suggested_area,
|
|
1589
|
+
expected_outcome: args.expected_outcome,
|
|
1590
|
+
source_ref: args.source_ref,
|
|
1591
|
+
proposer_github_login: args.proposer_github_login,
|
|
1592
|
+
},
|
|
1593
|
+
});
|
|
1594
|
+
// typed errors (RATE_LIMITED / DUPLICATE_PROPOSAL / validation) are already mapped by apiCall; the
|
|
1595
|
+
// success response already carries the route-level `proposal_notice` (suggestion ≠ contribution/reward).
|
|
1596
|
+
return r;
|
|
1597
|
+
}
|
|
1598
|
+
// ── participation: a real, accountable identity is required ──
|
|
1599
|
+
if (!apiKey)
|
|
1600
|
+
return {
|
|
1601
|
+
error: `api_key required for action=${action} — set WEBAZ_API_KEY (request an invite at ${WEBAZ_API_URL}/#welcome). Discovery (list_open / detail) and suggest work WITHOUT a key.`,
|
|
1602
|
+
error_code: 'API_KEY_REQUIRED',
|
|
1603
|
+
};
|
|
1495
1604
|
if (action === 'status')
|
|
1496
1605
|
return apiCall('/api/build-tasks?mine=1', { apiKey });
|
|
1497
1606
|
if (action === 'profile')
|
|
@@ -1503,38 +1612,32 @@ async function handleContribute(args) {
|
|
|
1503
1612
|
const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/claim', {
|
|
1504
1613
|
method: 'POST', apiKey, body: { provenance: args.provenance },
|
|
1505
1614
|
});
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
if (!r.error) {
|
|
1509
|
-
r.handoff = {
|
|
1510
|
-
repo: 'https://github.com/seasonsagents-art/webaz',
|
|
1511
|
-
start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
|
|
1512
|
-
do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo; work on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
|
|
1513
|
-
pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, add 🤖🤖🤖 to the PR title + a meta-rule trace. Humans merge — no auto-merge.',
|
|
1514
|
-
then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${tid} pr_ref=#<N>.`,
|
|
1515
|
-
human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1615
|
+
if (!r.error)
|
|
1616
|
+
r.handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
|
|
1518
1617
|
return r;
|
|
1519
1618
|
}
|
|
1520
1619
|
if (action === 'submit') {
|
|
1521
1620
|
const tid = args.task_id;
|
|
1522
1621
|
if (!tid)
|
|
1523
1622
|
return { error: 'task_id required for action=submit' };
|
|
1524
|
-
|
|
1525
|
-
|
|
1623
|
+
const vs = (args.verification_summary ?? '').trim();
|
|
1624
|
+
if (vs.length < 1)
|
|
1625
|
+
return { error: 'verification_summary required for action=submit — summarize what you ran/verified (the task verification_commands and their results)', error_code: 'VERIFICATION_SUMMARY_REQUIRED' };
|
|
1626
|
+
const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/submit', {
|
|
1627
|
+
method: 'POST', apiKey, body: { pr_ref: args.pr_ref, note: args.note, verification_summary: vs },
|
|
1526
1628
|
});
|
|
1629
|
+
if (!r.error)
|
|
1630
|
+
r._next = 'A human maintainer reviews next — acceptance (done) is manual and done ≠ merge. Track it with webaz_contribute action=status.';
|
|
1631
|
+
return r;
|
|
1527
1632
|
}
|
|
1528
|
-
|
|
1529
|
-
const q = args.area ? '?status=open&area=' + encodeURIComponent(String(args.area)) : '?status=open';
|
|
1530
|
-
return apiCall('/api/build-tasks' + q, { apiKey });
|
|
1633
|
+
return { error: 'unknown action: ' + action };
|
|
1531
1634
|
}
|
|
1532
1635
|
async function handleInfo() {
|
|
1533
1636
|
const summary = getManifestSummary();
|
|
1534
1637
|
// RFC-003 Batch 0:NETWORK 模式下,best-effort 拉 webaz.xyz 的 live 协议状态,
|
|
1535
1638
|
// 让带 key 的 agent 拿到【真网络】数字,而非只看本机本地 live_stats(下方仍保留并标注为本地)。
|
|
1536
1639
|
let network_live = null;
|
|
1537
|
-
if (
|
|
1640
|
+
if (isNetworkMode()) {
|
|
1538
1641
|
try {
|
|
1539
1642
|
const ps = await apiCall('/api/protocol-status');
|
|
1540
1643
|
network_live = { source: `${WEBAZ_API_URL}/api/protocol-status (live, fetched this call)`, ...ps };
|
|
@@ -1587,11 +1690,13 @@ async function handleInfo() {
|
|
|
1587
1690
|
// 佣金按【功能】中性描述(commission_model),不做"自证不是X"的辩护——正常机制无需自证。
|
|
1588
1691
|
network_state: {
|
|
1589
1692
|
// RFC-003 P3:显式声明当前客户端模式,让 agent 一眼分清"真网络 vs 本机沙盒"
|
|
1590
|
-
mode: MODE, // 'network' | 'sandbox'
|
|
1693
|
+
mode: MODE, // 'network' | 'network_readonly' | 'sandbox'
|
|
1591
1694
|
mode_banner: modeBanner(),
|
|
1592
1695
|
mode_meaning: MODE === 'network'
|
|
1593
1696
|
? '🟢 NETWORK:核心交易工具(下单/上架/履约/比价等)走 webaz.xyz 共享生产网络。真网络规模见下方 network_live(本次实时拉取);live_stats 仍是本机本地缓存,仅供参考。'
|
|
1594
|
-
:
|
|
1697
|
+
: MODE === 'network_readonly'
|
|
1698
|
+
? '🟢 NETWORK(只读):无 api_key。公共读(搜索/榜单/价格史/浏览)打 webaz.xyz 真网络(见 network_live)。要交易(注册/下单/上架等)请设 WEBAZ_API_KEY —— 到 ' + WEBAZ_API_URL + '/#welcome 申请邀请。'
|
|
1699
|
+
: '🟡 SANDBOX:所有工具都在本机本地 SQLite 运行,与 webaz.xyz 全网隔离(显式 dev/demo 模式)。任何计数 / 账号 / 订单仅本机有效。不设 WEBAZ_MODE 则默认走真网络只读。',
|
|
1595
1700
|
phase: 'pre_launch',
|
|
1596
1701
|
real_users_on_canonical: 0,
|
|
1597
1702
|
canonical_endpoint: 'https://webaz.xyz',
|
|
@@ -1629,10 +1734,10 @@ async function handleInfo() {
|
|
|
1629
1734
|
split: '7:2:1 — L1 70% / L2 20% / L3 10% of an order\'s commission_pool',
|
|
1630
1735
|
jurisdiction_tiers: 'Tiers are graded by the order region\'s max_levels — NOT a uniform 3 tiers everywhere. e.g. global region max_levels=1 → L1 only; singapore (etc.) max_levels=3 → up to L3. A region may also be 0 (no commission tiers; pool → community fund).',
|
|
1631
1736
|
attribution: 'EXPLICIT per-order — commission goes to the promoter attributed at purchase time, not derived from the buyer\'s sponsor chain.',
|
|
1632
|
-
how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link ?ref
|
|
1737
|
+
how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link /i/<permanent_code> (?ref=<permanent_code>) URL clicked in a browser (builds product_share_attribution).',
|
|
1633
1738
|
redirect_rules: 'chain_gap (no L / invalid sponsor) → charity_fund; level beyond the region cap → global_fund.',
|
|
1634
1739
|
l1_gate: 'the promoter must be a verified buyer (≥1 completed order) to receive commission, otherwise that share redirects.',
|
|
1635
|
-
opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded
|
|
1740
|
+
opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded. Commission destination is state-dependent: never_activated / auto_downgraded → held in pending_commission_escrow (30d grace), recoverable by (re-)activating within the window, else swept to commission_reserve; deactivated (active opt-out) → future commission goes directly to commission_reserve, NOT escrow and NOT recoverable. Never to charity_fund. See docs/rfcs/RFC-002-rewards-opt-in.md.',
|
|
1636
1741
|
},
|
|
1637
1742
|
// QA 轮 3 FAIL:roles 漏 reviewer。register 工具支持 5 个角色,info 必须列全。
|
|
1638
1743
|
roles: {
|
|
@@ -1674,10 +1779,10 @@ async function handleInfo() {
|
|
|
1674
1779
|
function handleRegister(args) {
|
|
1675
1780
|
// ─── RFC-003 P3:NETWORK 模式不自助建号 ────────────────────────
|
|
1676
1781
|
// 自助注册会绕过邀请码 / captcha / 责任制;且 CHARTER §4 I-5 要求账号必须由已绑 Passkey
|
|
1677
|
-
// 的真人创建("每个 agent 背后有可问责的真人")。NETWORK
|
|
1678
|
-
if (
|
|
1782
|
+
// 的真人创建("每个 agent 背后有可问责的真人")。NETWORK / 无 key 只读模式下都引导真人去 webaz.xyz 拿 key。
|
|
1783
|
+
if (isNetworkMode()) {
|
|
1679
1784
|
return {
|
|
1680
|
-
_mode:
|
|
1785
|
+
_mode: MODE,
|
|
1681
1786
|
registration: 'must_be_done_by_human_at_webaz_xyz',
|
|
1682
1787
|
message: '🟢 NETWORK 模式下不支持 agent 自助注册。开放协议的信任来自"每个 agent 背后有可问责的真人",所以注册这一步刻意留给真人在 webaz.xyz 完成。请按三步加入共享网络:',
|
|
1683
1788
|
steps: [
|
|
@@ -1945,8 +2050,8 @@ async function handleSearch(args) {
|
|
|
1945
2050
|
: '没有找到匹配的商品';
|
|
1946
2051
|
return { found: 0, message: hint, products: [], matched_by: 'strict_no_match' };
|
|
1947
2052
|
}
|
|
1948
|
-
const enriched = products.map((p) => {
|
|
1949
|
-
const boost = getSearchBoost(db, p.seller_id);
|
|
2053
|
+
const enriched = await Promise.all(products.map(async (p) => {
|
|
2054
|
+
const boost = await getSearchBoost(db, p.seller_id);
|
|
1950
2055
|
const rep_level = p.rep_level || 'new';
|
|
1951
2056
|
const rep_points = Number(p.rep_points) || 0;
|
|
1952
2057
|
const completion = Number(p.completion_count) || 0;
|
|
@@ -1971,7 +2076,7 @@ async function handleSearch(args) {
|
|
|
1971
2076
|
const firstSaleBoost = firstSold && (Date.now() - new Date(firstSold.replace(' ', 'T') + 'Z').getTime()) < 14 * 86400_000 ? 5 : 0;
|
|
1972
2077
|
const score = completion * 0.5 + rep_points * 0.1 + sharer * 2.0 + freshness + firstSaleBoost - dispute * 5.0;
|
|
1973
2078
|
return { ...p, _boost: boost, _rep_level: rep_level, _rep_points: rep_points, _score: score, _freshness: freshness, _first_sale_boost: firstSaleBoost };
|
|
1974
|
-
});
|
|
2079
|
+
}));
|
|
1975
2080
|
const sorted = sortMode === 'trending'
|
|
1976
2081
|
? enriched.sort((a, b) => b._score - a._score || b._boost - a._boost).slice(0, limit)
|
|
1977
2082
|
: enriched.slice(0, limit);
|
|
@@ -2200,7 +2305,7 @@ async function handleListProduct(args) {
|
|
|
2200
2305
|
// stake 在 place_order 那一刻按"该订单总额 × stake_rate"现锁,订单结算时退该笔,违约时扣该笔。
|
|
2201
2306
|
// 这样每个 active 订单都有独立 stake 担保,多 stock 商品也不会被空头薅。
|
|
2202
2307
|
// product.stake_amount 字段保留为"indicative rate × price"(前端展示用),不强制 lock。
|
|
2203
|
-
const stakeDiscount = getStakeDiscount(db, user.id);
|
|
2308
|
+
const stakeDiscount = await getStakeDiscount(db, user.id);
|
|
2204
2309
|
const stakeRate = Math.max(0.05, 0.15 - stakeDiscount); // 最低 5%,声誉越高折扣越大
|
|
2205
2310
|
const stakeAmount = Math.round(price * stakeRate * 100) / 100; // indicative only; actual lock per-order
|
|
2206
2311
|
const id = generateId('prd');
|
|
@@ -2682,8 +2787,8 @@ async function handleNotifications(args) {
|
|
|
2682
2787
|
return auth;
|
|
2683
2788
|
const { user } = auth;
|
|
2684
2789
|
const onlyUnread = args.unread === true;
|
|
2685
|
-
const notifs = getNotifications(db, user.id, onlyUnread, 30);
|
|
2686
|
-
const unread = getUnreadCount(db, user.id);
|
|
2790
|
+
const notifs = await getNotifications(db, user.id, onlyUnread, 30);
|
|
2791
|
+
const unread = await getUnreadCount(db, user.id);
|
|
2687
2792
|
if (args.mark_read) {
|
|
2688
2793
|
markRead(db, user.id);
|
|
2689
2794
|
}
|
|
@@ -2770,9 +2875,9 @@ async function handleDispute(args) {
|
|
|
2770
2875
|
// ── 查看争议详情 ────────────────────────────────────────────
|
|
2771
2876
|
if (action === 'view') {
|
|
2772
2877
|
let dispute = args.dispute_id
|
|
2773
|
-
? getDisputeDetails(db, args.dispute_id)
|
|
2878
|
+
? await getDisputeDetails(db, args.dispute_id)
|
|
2774
2879
|
: args.order_id
|
|
2775
|
-
? getOrderDispute(db, args.order_id)
|
|
2880
|
+
? await getOrderDispute(db, args.order_id)
|
|
2776
2881
|
: null;
|
|
2777
2882
|
if (!dispute)
|
|
2778
2883
|
return { error: '找不到争议记录,请提供 dispute_id 或 order_id' };
|
|
@@ -2812,7 +2917,7 @@ async function handleDispute(args) {
|
|
|
2812
2917
|
if (user.role !== 'arbitrator') {
|
|
2813
2918
|
return { error: '只有仲裁员可以查看所有待处理争议' };
|
|
2814
2919
|
}
|
|
2815
|
-
const disputes = getOpenDisputes(db);
|
|
2920
|
+
const disputes = await getOpenDisputes(db);
|
|
2816
2921
|
return {
|
|
2817
2922
|
open_count: disputes.length,
|
|
2818
2923
|
disputes: disputes.map(d => ({
|
|
@@ -2836,7 +2941,7 @@ async function handleDispute(args) {
|
|
|
2836
2941
|
// 如有证据描述,先创建证据记录
|
|
2837
2942
|
const evidenceIds = [];
|
|
2838
2943
|
if (args.evidence_description) {
|
|
2839
|
-
const dispute = getDisputeDetails(db, args.dispute_id);
|
|
2944
|
+
const dispute = await getDisputeDetails(db, args.dispute_id);
|
|
2840
2945
|
if (dispute) {
|
|
2841
2946
|
const eid = generateId('evt');
|
|
2842
2947
|
db.prepare(`
|
|
@@ -2994,7 +3099,7 @@ async function handleSkill(args) {
|
|
|
2994
3099
|
if (!('error' in a))
|
|
2995
3100
|
userId = a.user.id;
|
|
2996
3101
|
}
|
|
2997
|
-
const skills = listSkills(db, {
|
|
3102
|
+
const skills = await listSkills(db, {
|
|
2998
3103
|
skillType: args.skill_type,
|
|
2999
3104
|
query: args.query,
|
|
3000
3105
|
subscriberId: userId,
|
|
@@ -3052,7 +3157,7 @@ async function handleSkill(args) {
|
|
|
3052
3157
|
}
|
|
3053
3158
|
// ── 我发布的 Skill ────────────────────────────────────────
|
|
3054
3159
|
if (action === 'my_skills') {
|
|
3055
|
-
const skills = getMySkills(db, user.id);
|
|
3160
|
+
const skills = await getMySkills(db, user.id);
|
|
3056
3161
|
return {
|
|
3057
3162
|
total: skills.length,
|
|
3058
3163
|
skills: skills.map(formatSkillForAgent),
|
|
@@ -3061,7 +3166,7 @@ async function handleSkill(args) {
|
|
|
3061
3166
|
}
|
|
3062
3167
|
// ── 我订阅的 Skill ────────────────────────────────────────
|
|
3063
3168
|
if (action === 'my_subs') {
|
|
3064
|
-
const skills = getMySubscriptions(db, user.id);
|
|
3169
|
+
const skills = await getMySubscriptions(db, user.id);
|
|
3065
3170
|
return {
|
|
3066
3171
|
total: skills.length,
|
|
3067
3172
|
subscriptions: skills.map(formatSkillForAgent),
|
|
@@ -3361,10 +3466,14 @@ async function handleReferral(args) {
|
|
|
3361
3466
|
const tiers = db.prepare("SELECT tier, pv_threshold, score_per_hit FROM binary_tier_config WHERE active=1 ORDER BY tier ASC").all();
|
|
3362
3467
|
const pair = Math.min(Number(me?.total_left_pv ?? 0), Number(me?.total_right_pv ?? 0));
|
|
3363
3468
|
const nextTier = tiers.find(t => t.pv_threshold > pair);
|
|
3469
|
+
// invite / share links use permanent_code ONLY — never usr_xxx. (sandbox users have one from register.)
|
|
3470
|
+
const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
|
|
3364
3471
|
return {
|
|
3365
3472
|
user_id: userId,
|
|
3366
3473
|
name: user.name,
|
|
3367
|
-
|
|
3474
|
+
invite_code: permaCode,
|
|
3475
|
+
invite_unavailable_reason: permaCode ? null : 'permanent_code_missing — re-register or contact support',
|
|
3476
|
+
base_referral_link: permaCode ? `/i/${permaCode}` : null, // 仅推土机
|
|
3368
3477
|
region: user.region ?? 'global',
|
|
3369
3478
|
permissions: {
|
|
3370
3479
|
can_earn_l1_commission: canL1,
|
|
@@ -3379,10 +3488,10 @@ async function handleReferral(args) {
|
|
|
3379
3488
|
grand_total: byLevel[1].total + byLevel[2].total + byLevel[3].total,
|
|
3380
3489
|
},
|
|
3381
3490
|
binary: {
|
|
3382
|
-
left_invite_link:
|
|
3383
|
-
right_invite_link:
|
|
3384
|
-
platform_left: `/?placement=${
|
|
3385
|
-
platform_right: `/?placement=${
|
|
3491
|
+
left_invite_link: permaCode ? `/i/${permaCode}-L` : null,
|
|
3492
|
+
right_invite_link: permaCode ? `/i/${permaCode}-R` : null,
|
|
3493
|
+
platform_left: permaCode ? `/?placement=${permaCode}&side=left` : null, // 仅 PV 条线
|
|
3494
|
+
platform_right: permaCode ? `/?placement=${permaCode}&side=right` : null,
|
|
3386
3495
|
total_left_pv: Number(me?.total_left_pv ?? 0),
|
|
3387
3496
|
total_right_pv: Number(me?.total_right_pv ?? 0),
|
|
3388
3497
|
pair_volume: pair,
|
|
@@ -3402,7 +3511,7 @@ async function handleReferral(args) {
|
|
|
3402
3511
|
}
|
|
3403
3512
|
else if (lastAction === 'deactivate') {
|
|
3404
3513
|
state = 'deactivated';
|
|
3405
|
-
note = 'You actively deactivated rewards. Future commissions
|
|
3514
|
+
note = 'You actively deactivated rewards. Future L1/L2/L3 commissions go to commission_reserve / protocol reserve, not charity_fund and not pending escrow. Re-applying only affects future commissions.';
|
|
3406
3515
|
}
|
|
3407
3516
|
else if (lastAction === 'auto_downgrade') {
|
|
3408
3517
|
state = 'auto_downgraded';
|
|
@@ -3420,7 +3529,7 @@ async function handleReferral(args) {
|
|
|
3420
3529
|
note,
|
|
3421
3530
|
pending_escrow: { count: pending.n, total_amount: pending.total },
|
|
3422
3531
|
expired_to_charity: { count: expired.n, total_amount: expired.total },
|
|
3423
|
-
spec: 'RFC-002 §3.5 — rewards opt-in
|
|
3532
|
+
spec: 'RFC-002 §3.5 — rewards / share-commission opt-in (RFC-002)',
|
|
3424
3533
|
};
|
|
3425
3534
|
})(),
|
|
3426
3535
|
tip: canL1
|
|
@@ -3468,10 +3577,10 @@ async function handleShareLink(args) {
|
|
|
3468
3577
|
missing.push('application_not_submitted');
|
|
3469
3578
|
return {
|
|
3470
3579
|
error: 'rewards_opt_in_required',
|
|
3471
|
-
message: 'Share-link generation is a valuation-layer action — requires
|
|
3580
|
+
message: 'Share-link generation is a valuation-layer (rewards / share-link) action, NOT a contribution gate — requires rewards / share-commission opt-in (RFC-002 §3.5)',
|
|
3472
3581
|
missing_requirements: missing,
|
|
3473
3582
|
next_steps: [
|
|
3474
|
-
'Open PWA #me → tap "
|
|
3583
|
+
'Open PWA #me → tap "申请分享分润 / Enable share-commission opt-in"',
|
|
3475
3584
|
'Read the 8-second disclosure (cannot skip)',
|
|
3476
3585
|
'Submit application — pre-checks run server-side',
|
|
3477
3586
|
],
|
|
@@ -3510,7 +3619,11 @@ async function handleShareLink(args) {
|
|
|
3510
3619
|
const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
|
|
3511
3620
|
const canL1 = override === 1 || (override === 0 && completed > 0);
|
|
3512
3621
|
const rate = Number(product.commission_rate ?? 0);
|
|
3513
|
-
|
|
3622
|
+
// share ref uses permanent_code ONLY — never usr_xxx
|
|
3623
|
+
const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
|
|
3624
|
+
if (!permaCode)
|
|
3625
|
+
return { error: 'permanent_code_missing — cannot build a share link; re-register or contact support', error_code: 'PERMANENT_CODE_MISSING' };
|
|
3626
|
+
const link = `/?ref=${permaCode}&side=${side}#order-product/${productId}`;
|
|
3514
3627
|
return {
|
|
3515
3628
|
product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
|
|
3516
3629
|
share_link: link,
|
|
@@ -3919,9 +4032,9 @@ async function readEndpoint(tool, subpath) {
|
|
|
3919
4032
|
}
|
|
3920
4033
|
}
|
|
3921
4034
|
async function pwaApi(method, path, apiKey, body) {
|
|
3922
|
-
// RFC-003:NETWORK
|
|
3923
|
-
// (其余未迁工具在 dispatch 被 Batch 0 守卫拦下);SANDBOX
|
|
3924
|
-
if (
|
|
4035
|
+
// RFC-003:NETWORK / network_readonly → 走 webaz.xyz(Bearer 可空)。仅 NETWORK_TOOLS 里的工具会到这里
|
|
4036
|
+
// (其余未迁工具在 dispatch 被 Batch 0 守卫拦下);SANDBOX 才转发本地 PWA(localhost)。
|
|
4037
|
+
if (isNetworkMode()) {
|
|
3925
4038
|
return apiCall(path.startsWith('/api') ? path : '/api' + path, { method, apiKey, body });
|
|
3926
4039
|
}
|
|
3927
4040
|
const opts = {
|
|
@@ -4694,7 +4807,7 @@ export async function startMCPServer() {
|
|
|
4694
4807
|
if (request.params.uri !== MANIFEST_URI) {
|
|
4695
4808
|
throw new Error(`未知资源:${request.params.uri}`);
|
|
4696
4809
|
}
|
|
4697
|
-
const manifest = generateManifest(db);
|
|
4810
|
+
const manifest = await generateManifest(db);
|
|
4698
4811
|
return {
|
|
4699
4812
|
contents: [
|
|
4700
4813
|
{
|
|
@@ -4848,7 +4961,7 @@ export async function startMCPServer() {
|
|
|
4848
4961
|
// ─── RFC-003 Batch 0 安全网:NETWORK 模式下未迁移的工具【硬失败】,不静默落本地沙盒 ───
|
|
4849
4962
|
// 例外:info / register(NETWORK_SELF_AWARE)有专门 network-aware 处理,照常放行。
|
|
4850
4963
|
let handled = false;
|
|
4851
|
-
if (
|
|
4964
|
+
if (isNetworkMode() && !NETWORK_TOOLS.has(name) && !NETWORK_SELF_AWARE.has(name)) {
|
|
4852
4965
|
result = networkMigrationPending(name);
|
|
4853
4966
|
handled = true;
|
|
4854
4967
|
}
|
|
@@ -4991,7 +5104,9 @@ export async function startMCPServer() {
|
|
|
4991
5104
|
}
|
|
4992
5105
|
// RFC-003 P0: 给每个工具结果盖模式戳(诚实可见,防把 sandbox 当 live 网络)
|
|
4993
5106
|
// P3: handler 可自行预设 _mode(如 register 在 network 模式返回引导,不是 sandbox 结果)→ 不覆盖。
|
|
4994
|
-
|
|
5107
|
+
// self-aware 工具(info/register)按全局 MODE 盖戳,不按 toolBackend(它们不在 NETWORK_TOOLS 但本就网络感知),
|
|
5108
|
+
// 否则 network_readonly/network 下 info 会被误盖 sandbox 戳,与其自身 network_state 矛盾。
|
|
5109
|
+
const backend = NETWORK_SELF_AWARE.has(name) ? (isNetworkMode() ? 'network' : 'sandbox') : toolBackend(name);
|
|
4995
5110
|
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
4996
5111
|
const r = result;
|
|
4997
5112
|
if (!('_mode' in r))
|