@seasonkoh/webaz 0.1.29 → 0.1.31
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/bond-refund-blockers.js +50 -0
- package/dist/bond-slash.js +100 -0
- package/dist/bond-terms.js +31 -0
- package/dist/direct-pay-bond-rail-clearance.js +10 -5
- package/dist/direct-pay-cancel-refund.js +160 -0
- package/dist/direct-pay-create.js +78 -9
- package/dist/direct-pay-disclosures.js +17 -11
- package/dist/direct-pay-fee-ar.js +2 -2
- package/dist/direct-pay-fee-prepay-request.js +80 -0
- package/dist/direct-pay-returns.js +104 -0
- package/dist/direct-pay-stock.js +9 -0
- package/dist/direct-receive-deferral.js +30 -1
- package/dist/direct-receive-deposits.js +122 -17
- package/dist/free-shipping.js +37 -0
- package/dist/fx-rates.js +7 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +246 -0
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +73 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +150 -33
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
- package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
- package/dist/platform-receive-accounts.js +94 -0
- package/dist/pwa/arbitration-read-admin.js +37 -0
- package/dist/pwa/arbitrator-lifecycle.js +100 -0
- package/dist/pwa/contract-fingerprint.js +15 -0
- package/dist/pwa/direct-pay-order-redaction.js +20 -2
- package/dist/pwa/human-presence.js +2 -6
- package/dist/pwa/public/app-account.js +9 -9
- package/dist/pwa/public/app-admin-disputes.js +55 -0
- package/dist/pwa/public/app-agent-appeal.js +90 -0
- package/dist/pwa/public/app-agent-approvals.js +93 -0
- package/dist/pwa/public/app-agent-pair.js +127 -0
- package/dist/pwa/public/app-arbitrator-admin.js +87 -0
- package/dist/pwa/public/app-arbitrator-entry.js +9 -0
- package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
- package/dist/pwa/public/app-bond-refund-ui.js +66 -0
- package/dist/pwa/public/app-bond-slash-ui.js +74 -0
- package/dist/pwa/public/app-bond-terms-ui.js +23 -0
- package/dist/pwa/public/app-bond-ui.js +108 -0
- package/dist/pwa/public/app-chat-poll.js +6 -6
- package/dist/pwa/public/app-contribution-hub.js +23 -0
- package/dist/pwa/public/app-direct-pay-accounts.js +1 -1
- package/dist/pwa/public/app-direct-pay-buyer.js +1 -1
- package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
- package/dist/pwa/public/app-direct-pay-copy.js +12 -0
- package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
- package/dist/pwa/public/app-direct-pay-fee-ops.js +1 -1
- package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
- package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
- package/dist/pwa/public/app-direct-pay-memo.js +14 -0
- package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
- package/dist/pwa/public/app-direct-pay-pay.js +15 -0
- package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
- package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
- package/dist/pwa/public/app-direct-pay-returns.js +56 -0
- package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
- package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
- package/dist/pwa/public/app-direct-pay.js +36 -37
- package/dist/pwa/public/app-dispute-close-ui.js +38 -0
- package/dist/pwa/public/app-free-shipping-ui.js +29 -0
- package/dist/pwa/public/app-gmv-rail-split.js +12 -0
- package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
- package/dist/pwa/public/app-mutual-cancel.js +54 -0
- package/dist/pwa/public/app-notif-templates-orders.js +43 -0
- package/dist/pwa/public/app-notif-templates.js +22 -0
- package/dist/pwa/public/app-order-accept-ui.js +158 -0
- package/dist/pwa/public/app-order-errors.js +50 -0
- package/dist/pwa/public/app-order-labels.js +19 -0
- package/dist/pwa/public/app-order-rail-filter.js +26 -0
- package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
- package/dist/pwa/public/app-poll-governor.js +22 -0
- package/dist/pwa/public/app-profile.js +4 -4
- package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
- package/dist/pwa/public/app-sale-regions-ui.js +38 -0
- package/dist/pwa/public/app-seller.js +1 -1
- package/dist/pwa/public/app-trade-tax-ui.js +33 -0
- package/dist/pwa/public/app.js +133 -160
- package/dist/pwa/public/i18n.js +675 -5
- package/dist/pwa/public/index.html +41 -0
- package/dist/pwa/public/openapi.json +719 -11
- package/dist/pwa/public/style.css +3 -0
- package/dist/pwa/routes/admin-direct-receive-deposits.js +215 -3
- package/dist/pwa/routes/admin-protocol-params.js +16 -0
- package/dist/pwa/routes/admin-reports.js +31 -5
- package/dist/pwa/routes/agent-governance.js +1 -1
- package/dist/pwa/routes/agent-grants.js +281 -32
- package/dist/pwa/routes/analytics.js +2 -0
- package/dist/pwa/routes/arbitrator.js +67 -14
- package/dist/pwa/routes/bond-seller.js +162 -0
- package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
- package/dist/pwa/routes/direct-pay-disclosure-acks.js +9 -4
- package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
- package/dist/pwa/routes/direct-pay-returns.js +74 -0
- package/dist/pwa/routes/direct-pay-timeouts.js +186 -9
- package/dist/pwa/routes/disputes-read.js +20 -6
- package/dist/pwa/routes/disputes-write.js +91 -33
- package/dist/pwa/routes/external-anchors.js +4 -2
- package/dist/pwa/routes/fee-prepay-requests.js +66 -0
- package/dist/pwa/routes/governance-onboarding.js +46 -8
- package/dist/pwa/routes/logistics.js +4 -4
- package/dist/pwa/routes/me-data.js +2 -2
- package/dist/pwa/routes/mutual-cancel.js +62 -0
- package/dist/pwa/routes/orders-action.js +182 -9
- package/dist/pwa/routes/orders-create.js +18 -7
- package/dist/pwa/routes/orders-read.js +76 -14
- package/dist/pwa/routes/platform-receive-accounts.js +111 -0
- package/dist/pwa/routes/products-create.js +45 -16
- package/dist/pwa/routes/products-update.js +15 -1
- package/dist/pwa/routes/profile-identity.js +4 -2
- package/dist/pwa/routes/returns.js +39 -8
- package/dist/pwa/routes/seller-directpay-report.js +110 -0
- package/dist/pwa/routes/seller-quota.js +5 -1
- package/dist/pwa/routes/shipping-templates.js +219 -0
- package/dist/pwa/routes/snf.js +4 -1
- package/dist/pwa/routes/webauthn.js +3 -3
- package/dist/pwa/server.js +63 -40
- package/dist/runtime/agent-grant-scopes.js +69 -1
- package/dist/runtime/webaz-schema-helpers.js +57 -3
- package/dist/sale-regions.js +116 -0
- package/dist/shipping-templates.js +119 -0
- package/dist/trade-tax.js +99 -0
- package/dist/trade-terms.js +76 -0
- package/dist/version.js +1 -1
- package/package.json +61 -2
|
@@ -31,7 +31,8 @@ import { join as pathJoin } from 'node:path';
|
|
|
31
31
|
import { existsSync as fsExists, mkdirSync, writeFileSync, readFileSync, unlinkSync, chmodSync } from 'node:fs';
|
|
32
32
|
import { execFileSync } from 'node:child_process';
|
|
33
33
|
import { transition, getOrderStatus, initSystemUser, } from '../../layer0-foundation/L0-2-state-machine/engine.js';
|
|
34
|
-
import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes,
|
|
34
|
+
import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes, isActiveWhitelistArbitrator, // 仲裁能力唯一源=active 白名单;legacy user.role==='arbitrator' 旁路已移除
|
|
35
|
+
} from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
|
|
35
36
|
import { initNotificationSchema, notifyTransition, getNotifications, getUnreadCount, markRead, } from '../../layer2-business/L2-6-notifications/notification-engine.js';
|
|
36
37
|
import { initSkillSchema, publishSkill, listSkills, getMySkills, subscribeSkill, unsubscribeSkill, getMySubscriptions, formatSkillForAgent, shouldAutoAccept, SKILL_TYPE_META, } from '../../layer4-economics/L4-4-skill-market/skill-engine.js';
|
|
37
38
|
import { initReputationSchema, recordOrderReputation, getReputation, getSearchBoost, getStakeDiscount, } from '../../layer4-economics/L4-3-reputation/reputation-engine.js';
|
|
@@ -307,25 +308,68 @@ export async function handlePair(args) {
|
|
|
307
308
|
};
|
|
308
309
|
}
|
|
309
310
|
if (action === 'verify') {
|
|
310
|
-
//
|
|
311
|
-
// it re-checks active/expiry/revoked/subject-suspension
|
|
312
|
-
// bearer from the secret store and attach it; the raw token is never printed.
|
|
313
|
-
// business tool and no risk scope is wired to grants here.
|
|
311
|
+
// Ask the server for the FULL grant (all authorized scopes/bundle/expiry/status), not just one capability.
|
|
312
|
+
// The SERVER is authoritative: it re-checks active/expiry/revoked/subject-suspension + audits on EVERY call.
|
|
313
|
+
// We resolve the bearer from the secret store and attach it; the raw token is never printed.
|
|
314
314
|
const cred = resolveGrantCredential();
|
|
315
315
|
if (!cred)
|
|
316
316
|
return { status: 'not_paired', error_code: 'NO_GRANT_CREDENTIAL', hint: 'No stored grant. Run webaz_pair action="start", have the human approve, then action="complete".' };
|
|
317
|
-
const resp = await apiCall('/api/agent-grants/
|
|
317
|
+
const resp = await apiCall('/api/agent-grants/verify', { method: 'GET', apiKey: cred.token });
|
|
318
318
|
if (resp.error) {
|
|
319
319
|
return { status: 'grant_invalid', grant_id: cred.grant_id, error: resp.error, error_code: resp.error_code, hint: 'Grant is no longer valid (revoked / expired / suspended). Re-pair with webaz_pair action="start".' };
|
|
320
320
|
}
|
|
321
321
|
return {
|
|
322
322
|
status: 'active',
|
|
323
|
-
grant: resp.grant, // SERVER-AUTHORITATIVE principal
|
|
323
|
+
grant: resp.grant, // SERVER-AUTHORITATIVE principal: grant_id/human_id/agent_label + ALL scopes[]/permission_bundle/expiry/status
|
|
324
324
|
local_cache: { capabilities: cred.capabilities, expires_at: cred.expires_at }, // advisory only, may be stale — NOT authoritative
|
|
325
|
-
note: 'Grant verified LIVE by the server (per-call: active/expiry/revoked/subject-suspension
|
|
325
|
+
note: 'Grant verified LIVE by the server (per-call: active/expiry/revoked/subject-suspension + audited). `grant.scopes` is the authoritative full scope list; `local_cache` is advisory. Safe scopes only.',
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
if (action === 'request') {
|
|
329
|
+
// The agent asks the human to EXPAND this grant with more SAFE scope / a permission bundle. Uses the
|
|
330
|
+
// stored grant bearer; the server binds the request to (human, grant). Approval is Passkey-gated on the
|
|
331
|
+
// human's side. Only SAFE scopes — risk/never-delegable are structurally rejected.
|
|
332
|
+
const cred = resolveGrantCredential();
|
|
333
|
+
if (!cred)
|
|
334
|
+
return { status: 'not_paired', error_code: 'NO_GRANT_CREDENTIAL', hint: 'No stored grant. Pair first: webaz_pair action="start" → human approves → action="complete".' };
|
|
335
|
+
const bundle = typeof args.bundle === 'string' ? args.bundle : undefined;
|
|
336
|
+
const scopes = Array.isArray(args.scopes) ? args.scopes.map(String).filter(Boolean) : undefined;
|
|
337
|
+
if (!bundle && (!scopes || !scopes.length))
|
|
338
|
+
return { error: 'provide a bundle (e.g. "catalog_agent") OR a scopes[] array of SAFE scopes', error_code: 'NO_SCOPES' };
|
|
339
|
+
const resp = await apiCall('/api/agent-grants/permission-requests', {
|
|
340
|
+
method: 'POST', apiKey: cred.token,
|
|
341
|
+
body: { bundle, scopes, reason: typeof args.reason === 'string' ? args.reason : undefined, duration: typeof args.duration === 'string' ? args.duration : undefined },
|
|
342
|
+
});
|
|
343
|
+
if (resp.error)
|
|
344
|
+
return resp;
|
|
345
|
+
return {
|
|
346
|
+
status: 'requested',
|
|
347
|
+
approval_id: resp.approval_id,
|
|
348
|
+
approval_url: `${WEBAZ_API_URL}${String(resp.approval_url || '')}`,
|
|
349
|
+
requested_scopes: resp.requested_scopes,
|
|
350
|
+
permission_bundle: resp.permission_bundle,
|
|
351
|
+
risk_level: resp.risk_level,
|
|
352
|
+
suggested_duration: resp.suggested_duration,
|
|
353
|
+
next: 'Ask the human to open approval_url (logged in at webaz.xyz) and approve with their Passkey. Then simply RETRY your original call — on approval your grant carries the new scope. Use action="requests" to poll status.',
|
|
354
|
+
note: 'Only SAFE (read/draft) scopes can be requested; risk actions are never delegated to a persistent grant.',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (action === 'requests') {
|
|
358
|
+
// Poll THIS grant's own permission requests + their status (pending/approved/rejected) without hitting the
|
|
359
|
+
// target surface. Grant-authed, grant-scoped (you see only your own requests). Server audits every call.
|
|
360
|
+
const cred = resolveGrantCredential();
|
|
361
|
+
if (!cred)
|
|
362
|
+
return { status: 'not_paired', error_code: 'NO_GRANT_CREDENTIAL', hint: 'No stored grant. Pair first: webaz_pair action="start" → human approves → action="complete".' };
|
|
363
|
+
const resp = await apiCall('/api/agent-grants/my-permission-requests', { method: 'GET', apiKey: cred.token });
|
|
364
|
+
if (resp.error)
|
|
365
|
+
return resp;
|
|
366
|
+
return {
|
|
367
|
+
status: 'ok',
|
|
368
|
+
requests: resp.requests,
|
|
369
|
+
note: 'Your grant\'s own permission requests + status. On "approved", just retry your original call — the grant now carries the scope. "pending" → the human has not approved yet.',
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return { error: `unknown action "${action}" — use "start" | "complete" | "verify" | "request" | "requests"`, error_code: 'BAD_ACTION' };
|
|
329
373
|
}
|
|
330
374
|
// 启动 banner(stderr)+ status 声明用 —— 让用户/agent 一眼知道现在是真网络还是沙盒
|
|
331
375
|
function modeBanner() {
|
|
@@ -398,8 +442,12 @@ db.exec(`
|
|
|
398
442
|
error_msg TEXT
|
|
399
443
|
)
|
|
400
444
|
`);
|
|
401
|
-
|
|
402
|
-
|
|
445
|
+
// 索引命名与 runtime bridge(apply-webaz-runtime-schema)统一 —— 两组合根曾各自命名(idx_mcp_tool_calls_* vs idx_mcp_tc_*)
|
|
446
|
+
// → 同库跑过两根就出现同列重复索引,且 pg 产物逐列/索引 parity 因根不同而漂移。老名幂等清除。
|
|
447
|
+
db.exec(`DROP INDEX IF EXISTS idx_mcp_tool_calls_ts`);
|
|
448
|
+
db.exec(`DROP INDEX IF EXISTS idx_mcp_tool_calls_tool`);
|
|
449
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tc_ts ON mcp_tool_calls(ts)`);
|
|
450
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tc_tool ON mcp_tool_calls(tool_name, ts)`);
|
|
403
451
|
// ─── 4 层身份模型 helpers(与 PWA server 同源逻辑)───────────────
|
|
404
452
|
const PERMA_ALPHABET_MCP = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
405
453
|
function mcpGeneratePermanentCode() {
|
|
@@ -486,14 +534,21 @@ No auth required, no parameters needed.
|
|
|
486
534
|
2. action="complete" → retrieves the credential ONCE (PKCE) and stores it in your OS secret store (macOS Keychain → ~/.webaz/credentials 0600 fallback). Returns only a credential_handle + status — the raw token is never shown.
|
|
487
535
|
3. action="verify" → uses the stored grant against a safe read endpoint to confirm it is still valid. The server re-checks active/expiry/revoked/suspension/scope and audits the call every time. Returns the grant principal/status; never the raw token.
|
|
488
536
|
|
|
489
|
-
|
|
537
|
+
Need MORE scope later? If a call returns error_code="PERMISSION_REQUIRED", do NOT re-pair:
|
|
538
|
+
4. action="request" (bundle="catalog_agent" OR scopes=[...]) → asks the human to expand THIS grant. Returns an approval_id + approval_url; the human approves with a Passkey, then you simply RETRY your original call.
|
|
539
|
+
5. action="requests" → lists your grant's own requests + status (pending/approved/rejected) so you can poll instead of hitting the target surface.
|
|
540
|
+
|
|
541
|
+
Scopes: SAFE only (read_public, profile_read, search, list_product_draft, product_publish_request, draft_order + seller_*_read / seller_product_draft / seller_pricing_suggestion; bundle "catalog_agent"). Risk scopes (place_order/wallet/refund/...) and never-delegable actions (withdraw/key-change/vote/...) are hard-rejected.
|
|
490
542
|
|
|
491
543
|
NOTE: this consumes the grant only on safe read paths; no business tool and no risk scope is wired to grants. No account is created (registration stays human-only at webaz.xyz).`,
|
|
492
544
|
inputSchema: {
|
|
493
545
|
type: 'object',
|
|
494
546
|
properties: {
|
|
495
|
-
action: { type: 'string', enum: ['start', 'complete', 'verify'], description: 'start a pairing, complete it after the human approves,
|
|
496
|
-
capabilities: { type: 'array', items: { type: 'string' }, description: '
|
|
547
|
+
action: { type: 'string', enum: ['start', 'complete', 'verify', 'request', 'requests'], description: 'start a pairing, complete it after the human approves, verify the stored grant, request MORE scope (bundle/scopes), or list your requests (default: start)' },
|
|
548
|
+
capabilities: { type: 'array', items: { type: 'string' }, description: 'action="start": requested SAFE scopes (default: read_public, search). Non-safe scopes are rejected.' },
|
|
549
|
+
bundle: { type: 'string', description: 'action="request": a permission bundle key (e.g. "catalog_agent") — a named all-safe scope set the human approves as one thing.' },
|
|
550
|
+
scopes: { type: 'array', items: { type: 'string' }, description: 'action="request": individual SAFE scopes to request (alternative to bundle).' },
|
|
551
|
+
duration: { type: 'string', enum: ['once', '1h', '24h', '7d', '30d'], description: 'action="request": how long the expanded grant should last (safe scopes may be long-term; the server caps by risk).' },
|
|
497
552
|
agent_label: { type: 'string', description: 'Human-friendly name for this agent (shown in the consent screen)' },
|
|
498
553
|
reason: { type: 'string', description: 'Free-text reason shown to the human (you cannot relabel the scopes)' },
|
|
499
554
|
},
|
|
@@ -598,7 +653,7 @@ Skipping is allowed but agent then carries price/stock-race risk itself.`,
|
|
|
598
653
|
// was ~1336 chars, now ~650 chars
|
|
599
654
|
description: `⚠️ **"list" = PUBLISH** (verb), NOT "list out". Seller-only catalog publish + manage.
|
|
600
655
|
|
|
601
|
-
USE THIS when seller wants to publish / update / delist / relist / trash / delete own product, OR view own listings (action=mine). NOT for browsing marketplace — use webaz_search (anyone, no auth).
|
|
656
|
+
USE THIS when seller wants to publish / update / delist / relist / trash / delete own product, OR view own listings (action=mine). NOT for browsing marketplace — use webaz_search (anyone, no auth). A paired delegation grant (webaz_pair — Catalog Agent) can **read** (action=mine, seller_products_read) and **create DRAFTS** (action=create, seller_product_draft → the product lands in your warehouse as status=warehouse, NOT public); **publishing (going active), updating, delisting and deleting stay human/api_key only** — a grant can never publish. A grant that lacks the scope gets a structured PERMISSION_REQUIRED (request it via webaz_pair action="request", then retry). On create: system auto-suggests stake ~15% of price (buyer protection).
|
|
602
657
|
|
|
603
658
|
Fill agent_summary fields completely (brand/return/handling/warranty) — helps buyer agents compare. For "exclusive-price vs external-link" listing → PWA Web only (link claim needs crowd-verify).
|
|
604
659
|
|
|
@@ -615,7 +670,7 @@ Actions: create (title/description/price) | mine | update (product_id + changed
|
|
|
615
670
|
product_id: { type: 'string', description: 'Product ID (required for update/delist/relist/trash/delete)' },
|
|
616
671
|
title: { type: 'string', description: 'Product name (required for create; optional for update)' },
|
|
617
672
|
description: { type: 'string', description: 'Product description (required for create; optional for update)' },
|
|
618
|
-
price: { type: 'number', description: '
|
|
673
|
+
price: { type: 'number', description: 'Listing amount = the protocol/base amount (waz_usdc_rate 1.0). No currency is set at listing time. On the direct_p2p rail the actual payable DISPLAY follows the seller\'s declared receiving-account currency/instruction — USDC only when that account is USDC/USD, otherwise the seller\'s currency ("以收款说明为准"); WebAZ does not custody, route, validate, or restrict the payment method/currency. USDC is only the base/reference amount when applicable. Legacy escrow uses WAZ. Required for create; optional for update.' },
|
|
619
674
|
stock: { type: 'number', description: 'Stock, default 1' },
|
|
620
675
|
category: { type: 'string', description: 'Category (optional)' },
|
|
621
676
|
specs: {
|
|
@@ -668,9 +723,14 @@ Actions: create (title/description/price) | mine | update (product_id + changed
|
|
|
668
723
|
{
|
|
669
724
|
name: 'webaz_place_order',
|
|
670
725
|
// was ~1117 chars, now ~580 chars
|
|
671
|
-
description: `Buyer places order. Buyer api_key required.
|
|
726
|
+
description: `Buyer places order. Buyer api_key required.
|
|
672
727
|
|
|
673
|
-
|
|
728
|
+
Payment rail (chosen here, at purchase — default: escrow):
|
|
729
|
+
- **escrow** (default, legacy): funds auto-enter protocol escrow; the amount is the WAZ/protocol-unit price. The deadlines/auto-judge below apply.
|
|
730
|
+
- **direct_p2p** (live, non-custodial direct pay): buyer pays the seller directly OFF-platform via the seller's chosen receiving account. The order amount is the protocol/base amount; the actual payable DISPLAY follows that account's declared currency/instruction (USDC only when the account is USDC/USD — otherwise the seller's currency, "以收款说明为准"). WebAZ does NOT custody, route, validate, or restrict the method/currency. Pass \`direct_receive_account_id\` (seller's receiving account). Eligibility — product/store verification, per-tx caps, valid account — is enforced by the SERVER; you do not decide it. Ineligible → the route's own gate error (e.g. DIRECT_PAY_PRODUCT_NOT_VERIFIED → use escrow).
|
|
731
|
+
- onchain_full_stake / psp: PLANNED, not enabled → passing them returns PAYMENT_RAIL_DISABLED.
|
|
732
|
+
|
|
733
|
+
Order deadlines (escrow rail; absolute ISO timestamps in response):
|
|
674
734
|
- accept T+48h | ship T+120h (72h after accept)
|
|
675
735
|
- pickup T+168h (48h after ship) | delivery T+336h (7d after pickup)
|
|
676
736
|
- confirm T+408h (72h after delivery)
|
|
@@ -687,6 +747,8 @@ Options:
|
|
|
687
747
|
api_key: { type: 'string', description: "Buyer's api_key (or omit and set the WEBAZ_API_KEY env var)" },
|
|
688
748
|
product_id: { type: 'string', description: 'Product ID to buy (from webaz_search)' },
|
|
689
749
|
quantity: { type: 'number', description: 'Quantity, default 1' },
|
|
750
|
+
payment_rail: { type: 'string', enum: ['escrow', 'direct_p2p'], description: 'Payment rail (default: escrow). "direct_p2p" = live non-custodial direct pay, off-platform; the payable display follows the seller\'s receiving-account currency/instruction (USDC only as the base/reference amount, WebAZ doesn\'t custody/route/validate/restrict currency) — requires direct_receive_account_id; the server runs the direct-pay eligibility gates (you do not). Other rails (onchain_full_stake / psp) are planned but disabled → PAYMENT_RAIL_DISABLED.' },
|
|
751
|
+
direct_receive_account_id: { type: 'string', description: 'For payment_rail="direct_p2p": the seller\'s direct-receive account id to pay. Ignored on escrow.' },
|
|
690
752
|
shipping_address: { type: 'string', description: 'Shipping address' },
|
|
691
753
|
notes: { type: 'string', description: 'Note to seller (optional)' },
|
|
692
754
|
session_token: {
|
|
@@ -810,9 +872,9 @@ Actions:
|
|
|
810
872
|
- list_open pending disputes (arbitrators only)
|
|
811
873
|
- respond respondent rebuttal (before 48h deadline)
|
|
812
874
|
- add_evidence any participant supplements
|
|
813
|
-
- arbitrate ruling
|
|
875
|
+
- arbitrate ruling (assigned arbitrator only). ESCROW: fund disposition. DIRECT PAY (payment_rail=direct_p2p, non-custodial): reputation-only ruling — NO refund/release/arbitration-fee, NO amounts.
|
|
814
876
|
|
|
815
|
-
Ruling options: refund_buyer | release_seller | partial_refund (needs refund_amount; optional liable_party → 3rd-party fault, seller settled full + deducted from liable) | liability_split (needs liability_parties[]={user_id,amount}).
|
|
877
|
+
Ruling options — ESCROW: refund_buyer | release_seller | partial_refund (needs refund_amount; optional liable_party → 3rd-party fault, seller settled full + deducted from liable) | liability_split (needs liability_parties[]={user_id,amount}). DIRECT PAY: refund_buyer=买家胜诉 | release_seller=卖家胜诉 | partial_refund=部分责任 — reputation verdict only, do NOT pass refund_amount/liability_parties (protocol holds no funds). Check \`view\`→payment_rail/ruling_options first.
|
|
816
878
|
|
|
817
879
|
Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbitrator silent 120h → refund_buyer. Executed rulings are instant + irreversible.
|
|
818
880
|
|
|
@@ -1528,7 +1590,7 @@ Enums: **category** phone/computer/appliance/furniture/clothing/book/toy/sports/
|
|
|
1528
1590
|
description: { type: 'string', description: 'Description (≤1000 chars, optional)' },
|
|
1529
1591
|
category: { type: 'string', enum: ['phone', 'computer', 'appliance', 'furniture', 'clothing', 'book', 'toy', 'sports', 'other'], description: 'Category (required for publish)' },
|
|
1530
1592
|
condition_grade: { type: 'string', enum: ['brand_new', 'like_new', 'lightly_used', 'well_used', 'heavily_used'], description: 'Condition grade (required for publish)' },
|
|
1531
|
-
price: { type: 'number', description: '
|
|
1593
|
+
price: { type: 'number', description: 'Listing amount 0-100000 = protocol/base amount (waz_usdc_rate 1.0). On direct_p2p the payable display follows the seller\'s receiving-account currency/instruction (USDC only as the base/reference; WebAZ doesn\'t custody/route/validate/restrict currency); WAZ on legacy escrow. Rail chosen at purchase. Required for publish.' },
|
|
1532
1594
|
negotiable: { type: 'boolean', description: 'Negotiable flag (optional)' },
|
|
1533
1595
|
images: { type: 'array', items: { type: 'string' }, description: 'Images dataURL/URL array, ≥1 ≤9 (required for publish)' },
|
|
1534
1596
|
region: { type: 'string', description: 'Region (≤40 chars, optional)' },
|
|
@@ -2386,12 +2448,43 @@ async function handleVerifyPrice(args) {
|
|
|
2386
2448
|
next: `调用 webaz_place_order 时传入 session_token="${token}" 确保以此价格成交`,
|
|
2387
2449
|
};
|
|
2388
2450
|
}
|
|
2389
|
-
async function handleListProduct(args) {
|
|
2451
|
+
export async function handleListProduct(args) {
|
|
2390
2452
|
// Wave 3 audit P0: 加 action 分发 — agent 卖家能完整管理目录(不止 create)
|
|
2391
2453
|
const action = args.action || 'create';
|
|
2392
2454
|
const apiKey = resolveMcpApiKey(args);
|
|
2393
|
-
if (!apiKey)
|
|
2394
|
-
|
|
2455
|
+
if (!apiKey) {
|
|
2456
|
+
// No human api_key — fall back to a delegation grant (Catalog Agent). A grant can READ the catalog
|
|
2457
|
+
// (action="mine", seller_products_read) and create DRAFTS (action="create"|"draft", seller_product_draft
|
|
2458
|
+
// → status=warehouse, NOT public). Publishing (active) + all other writes stay human/api_key only.
|
|
2459
|
+
// Missing scope → structured PERMISSION_REQUIRED (agent can request it, then retry).
|
|
2460
|
+
if (!isNetworkMode())
|
|
2461
|
+
return { error: 'a delegation grant requires NETWORK mode (grants live on webaz.xyz)', error_code: 'GRANT_REQUIRES_NETWORK' };
|
|
2462
|
+
const cred = resolveGrantCredential();
|
|
2463
|
+
if (!cred)
|
|
2464
|
+
return { error: 'api_key required — or pair a delegation grant: webaz_pair action="start", have the human approve the catalog_agent bundle, then retry.', error_code: 'AUTH_REQUIRED' };
|
|
2465
|
+
if (action === 'mine') {
|
|
2466
|
+
const r = await apiCall('/api/agent/seller/products', { method: 'GET', apiKey: cred.token });
|
|
2467
|
+
if (r.error_code === 'PERMISSION_REQUIRED')
|
|
2468
|
+
return { ...r, retry_after_approval: true, hint: 'Your grant lacks seller_products_read. Run webaz_pair action="request" bundle="catalog_agent", have the human approve, then retry.' };
|
|
2469
|
+
if (r.error)
|
|
2470
|
+
return r;
|
|
2471
|
+
return { found: r.count, products: r.products, seller_id: r.seller_id, via: 'delegation_grant', note: 'Read via your delegation grant (safe scope seller_products_read). Creating a DRAFT: action="create" (goes to your warehouse for you to review + publish).' };
|
|
2472
|
+
}
|
|
2473
|
+
if (action === 'create' || action === 'draft') {
|
|
2474
|
+
const createFields = ['title', 'description', 'price', 'stock', 'category', 'specs', 'brand', 'model', 'source_url', 'source_price', 'external_title', 'weight_kg', 'ship_regions', 'handling_hours', 'estimated_days', 'fragile', 'return_days', 'return_condition', 'warranty_days', 'commission_rate', 'product_type', 'aliases', 'image_hashes', 'additional_links'];
|
|
2475
|
+
const body = {};
|
|
2476
|
+
for (const k of createFields)
|
|
2477
|
+
if (args[k] !== undefined)
|
|
2478
|
+
body[k] = args[k];
|
|
2479
|
+
const r = await apiCall('/api/agent/seller/products', { method: 'POST', apiKey: cred.token, body });
|
|
2480
|
+
if (r.error_code === 'PERMISSION_REQUIRED')
|
|
2481
|
+
return { ...r, retry_after_approval: true, hint: 'Your grant lacks seller_product_draft. Run webaz_pair action="request" bundle="catalog_agent", have the human approve, then retry.' };
|
|
2482
|
+
if (r.error)
|
|
2483
|
+
return r;
|
|
2484
|
+
return { ...r, via: 'delegation_grant', note: 'Created as a DRAFT (status=warehouse — NOT public/sellable). Publishing stays with the human: they review it in 我的商品 → 仓库 and publish (a notification was sent). A grant can never publish.' };
|
|
2485
|
+
}
|
|
2486
|
+
return { error_code: 'GRANT_WRITE_NOT_ENABLED', note: `action="${action}" is not available via a delegation grant — a grant can READ (action="mine") and create DRAFTS (action="create" → warehouse). Publishing, updating, delisting, and deleting stay human/api_key only. Provide a seller api_key for those.` };
|
|
2487
|
+
}
|
|
2395
2488
|
// RFC-003 P2b: NETWORK 模式 — 卖家目录管理全部转发生产端点(单一真相源)
|
|
2396
2489
|
if (toolBackend('webaz_list_product') === 'network') {
|
|
2397
2490
|
const pid = args.product_id;
|
|
@@ -2554,9 +2647,20 @@ async function handleListProduct(args) {
|
|
|
2554
2647
|
...(extraResult ? { extra_fields_applied: !('error' in extraResult), extra_result: extraResult } : {}),
|
|
2555
2648
|
};
|
|
2556
2649
|
}
|
|
2557
|
-
async function handlePlaceOrder(args) {
|
|
2650
|
+
export async function handlePlaceOrder(args) {
|
|
2651
|
+
// Payment rail (PWA-aligned). Only escrow (legacy) + direct_p2p (live, non-custodial) are enabled; the
|
|
2652
|
+
// other declared rails (onchain_full_stake / psp) are PLANNED placeholders — refuse them here, never
|
|
2653
|
+
// silently downgrade. The MCP does NOT judge direct-pay eligibility: it forwards payment_rail +
|
|
2654
|
+
// direct_receive_account_id to the SAME /api/orders route, which runs all the PWA gates.
|
|
2655
|
+
const rail = args.payment_rail;
|
|
2656
|
+
if (rail != null && rail !== 'escrow' && rail !== 'direct_p2p') {
|
|
2657
|
+
return { error: `payment rail "${String(rail)}" is planned but not enabled — only "direct_p2p" (live, non-custodial direct pay) and "escrow" (legacy) are available.`, error_code: 'PAYMENT_RAIL_DISABLED' };
|
|
2658
|
+
}
|
|
2659
|
+
if (rail === 'direct_p2p' && !isNetworkMode()) {
|
|
2660
|
+
return { error: 'direct_p2p (direct pay) requires NETWORK mode — it settles against a real seller receiving account on webaz.xyz.', error_code: 'DIRECT_PAY_REQUIRES_NETWORK' };
|
|
2661
|
+
}
|
|
2558
2662
|
// RFC-003 P2: network 模式转发到生产 POST /api/orders(前置,绕过本地 db)。
|
|
2559
|
-
// 生产端做完整鉴权/库存/session/spend-cap
|
|
2663
|
+
// 生产端做完整鉴权/库存/session/spend-cap/结算 + direct-pay gate(createDirectPayResponse)。
|
|
2560
2664
|
if (toolBackend('webaz_place_order') === 'network') {
|
|
2561
2665
|
const body = { product_id: args.product_id, quantity: Number(args.quantity ?? 1) };
|
|
2562
2666
|
if (args.session_token != null)
|
|
@@ -2567,6 +2671,10 @@ async function handlePlaceOrder(args) {
|
|
|
2567
2671
|
body.shipping_address = args.shipping_address;
|
|
2568
2672
|
if (args.donation_pct != null)
|
|
2569
2673
|
body.donation_pct = args.donation_pct;
|
|
2674
|
+
if (args.payment_rail != null)
|
|
2675
|
+
body.payment_rail = args.payment_rail; // escrow | direct_p2p (route forks + gates)
|
|
2676
|
+
if (args.direct_receive_account_id != null)
|
|
2677
|
+
body.direct_receive_account_id = args.direct_receive_account_id; // seller receiving acct (direct_p2p)
|
|
2570
2678
|
return apiCall('/api/orders', { method: 'POST', apiKey: resolveMcpApiKey(args), body });
|
|
2571
2679
|
}
|
|
2572
2680
|
const auth = requireAuth(db, resolveMcpApiKey(args));
|
|
@@ -2785,7 +2893,7 @@ async function handleUpdateOrder(args) {
|
|
|
2785
2893
|
const isParticipant = order.buyer_id === user.id ||
|
|
2786
2894
|
order.seller_id === user.id ||
|
|
2787
2895
|
order.logistics_id === user.id;
|
|
2788
|
-
if (!isParticipant && user.
|
|
2896
|
+
if (!isParticipant && !isActiveWhitelistArbitrator(db, user.id)) { // 白名单(非 legacy role):role-only/已吊销不可越权;下游 transition 仍按 actor.role 守边
|
|
2789
2897
|
return { error: '你不是这笔订单的参与方,无法操作' };
|
|
2790
2898
|
}
|
|
2791
2899
|
// action → 状态映射
|
|
@@ -3088,10 +3196,13 @@ async function handleDispute(args) {
|
|
|
3088
3196
|
WHERE e.order_id = ? AND u.role = ?
|
|
3089
3197
|
ORDER BY e.created_at ASC
|
|
3090
3198
|
`).all(orderId, uploaderRole);
|
|
3199
|
+
const dpNonCustodial = dispute.payment_rail === 'direct_p2p'; // 直付=非托管:仅信誉裁决,无退款/放款/仲裁费/金额
|
|
3091
3200
|
return {
|
|
3092
3201
|
dispute_id: dispute.id,
|
|
3093
3202
|
order_id: dispute.order_id,
|
|
3094
3203
|
status: dispute.status,
|
|
3204
|
+
payment_rail: dispute.payment_rail || 'escrow',
|
|
3205
|
+
non_custodial: dpNonCustodial,
|
|
3095
3206
|
initiator: `${dispute.initiator_name}(${dispute.initiator_role})`,
|
|
3096
3207
|
defendant: `${dispute.defendant_name}(${dispute.defendant_role})`,
|
|
3097
3208
|
reason: dispute.reason,
|
|
@@ -3101,21 +3212,27 @@ async function handleDispute(args) {
|
|
|
3101
3212
|
defendant_notes: dispute.defendant_notes ?? '(被诉方尚未提交回应)',
|
|
3102
3213
|
defendant_evidence: JSON.parse(dispute.defendant_evidence_ids || '[]'),
|
|
3103
3214
|
ruling: dispute.ruling_type
|
|
3104
|
-
?
|
|
3215
|
+
? (dpNonCustodial
|
|
3216
|
+
? { type: dispute.ruling_type, reason: dispute.verdict_reason, note: '非托管信誉裁决:无退款/放款金额' }
|
|
3217
|
+
: { type: dispute.ruling_type, refund_amount: dispute.refund_amount, reason: dispute.verdict_reason })
|
|
3105
3218
|
: null,
|
|
3106
|
-
//
|
|
3107
|
-
ruling_options: dispute.ruling_type ? undefined : [
|
|
3219
|
+
// 未裁定时暴露可选 ruling 列表。direct_p2p:仅信誉裁决(胜负/责任,无金额);escrow:含金额/责任分配。
|
|
3220
|
+
ruling_options: dispute.ruling_type ? undefined : (dpNonCustodial ? [
|
|
3221
|
+
{ type: 'refund_buyer', desc: '判买家胜诉(信誉裁决;非托管:无退款/放款/仲裁费)' },
|
|
3222
|
+
{ type: 'release_seller', desc: '判卖家胜诉(信誉裁决;无退款/放款)' },
|
|
3223
|
+
{ type: 'partial_refund', desc: '判部分责任(信誉裁决;无金额)' },
|
|
3224
|
+
] : [
|
|
3108
3225
|
{ type: 'refund_buyer', desc: '全额退款给买家(卖家败诉)' },
|
|
3109
3226
|
{ type: 'release_seller', desc: '放款给卖家(买家败诉)' },
|
|
3110
3227
|
{ type: 'partial_refund', desc: '部分退款(需传 refund_amount)' },
|
|
3111
3228
|
{ type: 'liability_split', desc: '按责任分配(需传 liability_parties 数组)' },
|
|
3112
|
-
],
|
|
3229
|
+
]),
|
|
3113
3230
|
resolved_at: dispute.resolved_at,
|
|
3114
3231
|
};
|
|
3115
3232
|
}
|
|
3116
3233
|
// ── 仲裁员查看所有待处理争议 ───────────────────────────────
|
|
3117
3234
|
if (action === 'list_open') {
|
|
3118
|
-
if (user.
|
|
3235
|
+
if (!isActiveWhitelistArbitrator(db, user.id)) { // 白名单(非 legacy role):whitelist-only 真人可用,role-only/已吊销 403
|
|
3119
3236
|
return { error: '只有仲裁员可以查看所有待处理争议' };
|
|
3120
3237
|
}
|
|
3121
3238
|
const disputes = await getOpenDisputes(db);
|
|
@@ -3128,7 +3245,7 @@ async function handleDispute(args) {
|
|
|
3128
3245
|
initiator: `${d.initiator_name}(${d.initiator_role})`,
|
|
3129
3246
|
defendant: `${d.defendant_name}(${d.defendant_role})`,
|
|
3130
3247
|
reason: d.reason,
|
|
3131
|
-
amount: `${d.total_amount} WAZ`,
|
|
3248
|
+
amount: d.payment_rail === 'direct_p2p' ? `${d.total_amount} USDC(非托管·信誉裁决)` : `${d.total_amount} WAZ`,
|
|
3132
3249
|
respond_deadline: d.respond_deadline,
|
|
3133
3250
|
arbitrate_deadline: d.arbitrate_deadline,
|
|
3134
3251
|
created_at: d.created_at,
|