@seasonkoh/webaz 0.1.22 → 0.1.24
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/layer0-foundation/L0-2-state-machine/engine.js +11 -16
- package/dist/layer1-agent/L1-1-mcp-server/server.js +697 -250
- package/dist/pwa/economic-participation.js +1 -1
- package/dist/pwa/integration-contract.js +4 -0
- package/dist/pwa/public/index.html +2 -1
- package/dist/pwa/routes/public-utils.js +17 -0
- package/dist/pwa/routes/referral.js +81 -0
- package/package.json +1 -1
|
@@ -43,10 +43,16 @@ const TELEMETRY_ENABLED = (process.env.WEBAZ_TELEMETRY ?? 'off').toLowerCase() =
|
|
|
43
43
|
const WEBAZ_API_URL = (process.env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
|
|
44
44
|
const WEBAZ_API_KEY = process.env.WEBAZ_API_KEY ?? '';
|
|
45
45
|
const WEBAZ_MODE_ENV = (process.env.WEBAZ_MODE ?? '').toLowerCase();
|
|
46
|
-
// 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 →
|
|
46
|
+
// 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 key → network_readonly(装完即见真网络)。
|
|
47
|
+
// network_readonly(L1 onboarding,2026-06-08):无 key 默认。公共读匿名打 webaz.xyz(真 catalog/协议),
|
|
48
|
+
// 需身份的写/读返回"设 WEBAZ_API_KEY(到 #welcome 申请邀请)"。离线本地 playground 改为【显式】 WEBAZ_MODE=sandbox。
|
|
49
|
+
// —— 治"装完=空沙盒劝退"的死首体验;route/guard 与 network 同路(见 isNetworkMode),只是无 Bearer + 文案不同。
|
|
47
50
|
const MODE = WEBAZ_MODE_ENV === 'network' ? 'network'
|
|
48
51
|
: WEBAZ_MODE_ENV === 'sandbox' ? 'sandbox'
|
|
49
|
-
:
|
|
52
|
+
: WEBAZ_MODE_ENV === 'network_readonly' ? 'network_readonly'
|
|
53
|
+
: (WEBAZ_API_KEY ? 'network' : 'network_readonly');
|
|
54
|
+
// network 或 network_readonly 都"走真网络"(后者无 Bearer)。sandbox 才是本地。
|
|
55
|
+
const isNetworkMode = () => MODE === 'network' || MODE === 'network_readonly';
|
|
50
56
|
// 已迁移到 NETWORK 的工具名。P1/P2 逐个加入;未在集合里的工具仍走 sandbox(本地)。
|
|
51
57
|
// P1(读工具): 纯公开读,无写无 Passkey,作"MCP 连得上生产网络"的首验证。
|
|
52
58
|
const NETWORK_TOOLS = new Set([
|
|
@@ -60,6 +66,42 @@ const NETWORK_TOOLS = new Set([
|
|
|
60
66
|
'webaz_get_status',
|
|
61
67
|
'webaz_feedback',
|
|
62
68
|
'webaz_contribute',
|
|
69
|
+
// Batch 1(只读 + 低危自身写):走 webaz.xyz Bearer api_key。
|
|
70
|
+
'webaz_notifications',
|
|
71
|
+
'webaz_nearby',
|
|
72
|
+
'webaz_profile',
|
|
73
|
+
'webaz_shareables',
|
|
74
|
+
'webaz_mykey',
|
|
75
|
+
// Batch 2(低危写,无钱无 escrow):走 webaz.xyz Bearer api_key。
|
|
76
|
+
// 注:share_link 暂不迁(无对应服务端端点,需新建,留待后续)。
|
|
77
|
+
'webaz_follows',
|
|
78
|
+
'webaz_like',
|
|
79
|
+
'webaz_blocklist',
|
|
80
|
+
'webaz_default_address',
|
|
81
|
+
'webaz_chat',
|
|
82
|
+
'webaz_rfq',
|
|
83
|
+
'webaz_referral',
|
|
84
|
+
// Batch 3(商务):secondhand/skill_market/auction 纯 pwaApi(mode-aware 自动走网络);
|
|
85
|
+
// skill 直连本地引擎,加了显式 apiCall network 分支。
|
|
86
|
+
'webaz_secondhand',
|
|
87
|
+
'webaz_skill',
|
|
88
|
+
'webaz_skill_market',
|
|
89
|
+
'webaz_auction',
|
|
90
|
+
// Batch 4(资金/质押,守恒由服务端 RFC-014 保证;wallet 只读,写=Passkey 仅 PWA):
|
|
91
|
+
'webaz_wallet',
|
|
92
|
+
'webaz_trial',
|
|
93
|
+
'webaz_charity',
|
|
94
|
+
'webaz_bid',
|
|
95
|
+
'webaz_auto_bid',
|
|
96
|
+
// Batch 5(铁律/敏感):claim_verify 纯 pwaApi(真人门由服务端 require_human_presence 强制);
|
|
97
|
+
// dispute view/list_open/respond/add_evidence 走网络,arbitrate 仅返回 Passkey 指引;
|
|
98
|
+
// rotate_key/revoke_key 仅返回 Passkey 指引(不本地校验)。
|
|
99
|
+
'webaz_dispute',
|
|
100
|
+
'webaz_claim_verify',
|
|
101
|
+
'webaz_rotate_key',
|
|
102
|
+
'webaz_revoke_key',
|
|
103
|
+
// #1122:share_link 现有服务端端点 /api/share-link,可走网络。
|
|
104
|
+
'webaz_share_link',
|
|
63
105
|
]);
|
|
64
106
|
const recentCalls = [];
|
|
65
107
|
function pushRecentCall(c) {
|
|
@@ -67,9 +109,28 @@ function pushRecentCall(c) {
|
|
|
67
109
|
if (recentCalls.length > 8)
|
|
68
110
|
recentCalls.shift(); // 只留最近 8 条
|
|
69
111
|
}
|
|
70
|
-
//
|
|
112
|
+
// 单个工具实际后端:network 或 network_readonly 下、且该工具已迁移,才走网络;否则 sandbox。
|
|
113
|
+
// readonly 无 Bearer:公共读拿真数据,需身份的端点服务端返 401(诚实)→ 不会静默落本地。
|
|
71
114
|
function toolBackend(tool) {
|
|
72
|
-
return (
|
|
115
|
+
return (isNetworkMode() && NETWORK_TOOLS.has(tool)) ? 'network' : 'sandbox';
|
|
116
|
+
}
|
|
117
|
+
// 未在 NETWORK_TOOLS 名单、但 NETWORK 模式下仍可本地运行的"自省/引导"工具(非数据操作)。
|
|
118
|
+
// info = 本地自省(并拉 live 网络状态);register = 引导真人去 webaz.xyz。其余未迁工具一律硬失败。
|
|
119
|
+
const NETWORK_SELF_AWARE = new Set(['webaz_info', 'webaz_register']);
|
|
120
|
+
// RFC-003 Batch 0 安全网:NETWORK 模式下调用【未迁移】工具时的诚实拒绝(而非静默落本地沙盒)。
|
|
121
|
+
// 否则带 key 的用户调未迁工具会被悄悄喂本地结果——写操作=幻影操作(根本没到 webaz.xyz)。
|
|
122
|
+
function networkMigrationPending(tool) {
|
|
123
|
+
const short = tool.replace(/^webaz_/, '');
|
|
124
|
+
return {
|
|
125
|
+
_mode: 'network',
|
|
126
|
+
not_on_network_yet: true,
|
|
127
|
+
error: `${tool} 尚未接入 webaz.xyz 共享网络(迁移进行中)。NETWORK 模式下拒绝把它落到本机沙盒——本地结果不会到达 webaz.xyz,写操作会变成"幻影操作"。 / ${tool} is not on the live network yet (migration in progress); refusing to run it against your local sandbox while in NETWORK mode — a local result would NOT reach webaz.xyz.`,
|
|
128
|
+
what_to_do: [
|
|
129
|
+
`现在就用网页完成此动作:${WEBAZ_API_URL}(PWA) / Use the web app for this action now.`,
|
|
130
|
+
`只想本地试玩/测试?设环境变量 WEBAZ_MODE=sandbox 显式进沙盒。 / Set WEBAZ_MODE=sandbox to use the local sandbox explicitly.`,
|
|
131
|
+
],
|
|
132
|
+
migration: `RFC-003 渐进迁移:webaz_${short} 将在后续批次获得网络支持。 / incremental migration; network support for this tool lands in an upcoming batch.`,
|
|
133
|
+
};
|
|
73
134
|
}
|
|
74
135
|
// 统一 API helper(P1/P2 迁移工具时使用)。Bearer api_key + 15s 超时 + 错误映射。
|
|
75
136
|
async function apiCall(path, opts = {}) {
|
|
@@ -112,11 +173,14 @@ async function apiCall(path, opts = {}) {
|
|
|
112
173
|
// 启动 banner(stderr)+ status 声明用 —— 让用户/agent 一眼知道现在是真网络还是沙盒
|
|
113
174
|
function modeBanner() {
|
|
114
175
|
if (MODE === 'network') {
|
|
115
|
-
return `🟢 NETWORK mode — webaz.xyz (${WEBAZ_API_URL}). Migrated tools: ${NETWORK_TOOLS.size}/${TOOLS.length}
|
|
116
|
-
|
|
176
|
+
return `🟢 NETWORK mode — webaz.xyz (${WEBAZ_API_URL}), authenticated. Migrated tools: ${NETWORK_TOOLS.size}/${TOOLS.length}`;
|
|
177
|
+
}
|
|
178
|
+
if (MODE === 'network_readonly') {
|
|
179
|
+
return `🟢 NETWORK (read-only) — no api_key: public reads (search / leaderboard / price history / browse) hit the LIVE webaz.xyz network. `
|
|
180
|
+
+ `To transact (register/order/list/etc.), set WEBAZ_API_KEY — request an invite at ${WEBAZ_API_URL}/#welcome.`;
|
|
117
181
|
}
|
|
118
|
-
return `🟡 SANDBOX mode — local-only (~/.webaz/webaz.db), NOT the live network. Data is private to this machine
|
|
119
|
-
+ (
|
|
182
|
+
return `🟡 SANDBOX mode — local-only (~/.webaz/webaz.db), NOT the live network. Data is private to this machine. `
|
|
183
|
+
+ `(Explicit dev/demo mode; unset WEBAZ_MODE to use the live network read-only by default.)`;
|
|
120
184
|
}
|
|
121
185
|
// ─── 初始化 ──────────────────────────────────────────────────
|
|
122
186
|
const db = initDatabase();
|
|
@@ -1475,8 +1539,24 @@ async function handleContribute(args) {
|
|
|
1475
1539
|
const q = args.area ? '?status=open&area=' + encodeURIComponent(String(args.area)) : '?status=open';
|
|
1476
1540
|
return apiCall('/api/build-tasks' + q, { apiKey });
|
|
1477
1541
|
}
|
|
1478
|
-
function handleInfo() {
|
|
1542
|
+
async function handleInfo() {
|
|
1479
1543
|
const summary = getManifestSummary();
|
|
1544
|
+
// RFC-003 Batch 0:NETWORK 模式下,best-effort 拉 webaz.xyz 的 live 协议状态,
|
|
1545
|
+
// 让带 key 的 agent 拿到【真网络】数字,而非只看本机本地 live_stats(下方仍保留并标注为本地)。
|
|
1546
|
+
let network_live = null;
|
|
1547
|
+
if (isNetworkMode()) {
|
|
1548
|
+
try {
|
|
1549
|
+
const ps = await apiCall('/api/protocol-status');
|
|
1550
|
+
network_live = { source: `${WEBAZ_API_URL}/api/protocol-status (live, fetched this call)`, ...ps };
|
|
1551
|
+
}
|
|
1552
|
+
catch (e) {
|
|
1553
|
+
network_live = {
|
|
1554
|
+
source: `${WEBAZ_API_URL}/api/protocol-status`,
|
|
1555
|
+
error: `couldn't reach live network this call: ${e.message}`,
|
|
1556
|
+
note: 'live_stats below is LOCAL-only (this MCP server\'s SQLite), not protocol-wide.',
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1480
1560
|
// QA 轮 3 抓到:live_stats 不是 hardcoded、不是 remote — 就是本地 SQLite count。这里加 source 字段澄清。
|
|
1481
1561
|
const stats = (() => {
|
|
1482
1562
|
try {
|
|
@@ -1517,11 +1597,13 @@ function handleInfo() {
|
|
|
1517
1597
|
// 佣金按【功能】中性描述(commission_model),不做"自证不是X"的辩护——正常机制无需自证。
|
|
1518
1598
|
network_state: {
|
|
1519
1599
|
// RFC-003 P3:显式声明当前客户端模式,让 agent 一眼分清"真网络 vs 本机沙盒"
|
|
1520
|
-
mode: MODE, // 'network' | 'sandbox'
|
|
1600
|
+
mode: MODE, // 'network' | 'network_readonly' | 'sandbox'
|
|
1521
1601
|
mode_banner: modeBanner(),
|
|
1522
1602
|
mode_meaning: MODE === 'network'
|
|
1523
|
-
? '🟢 NETWORK:核心交易工具(下单/上架/履约/比价等)走 webaz.xyz
|
|
1524
|
-
:
|
|
1603
|
+
? '🟢 NETWORK:核心交易工具(下单/上架/履约/比价等)走 webaz.xyz 共享生产网络。真网络规模见下方 network_live(本次实时拉取);live_stats 仍是本机本地缓存,仅供参考。'
|
|
1604
|
+
: MODE === 'network_readonly'
|
|
1605
|
+
? '🟢 NETWORK(只读):无 api_key。公共读(搜索/榜单/价格史/浏览)打 webaz.xyz 真网络(见 network_live)。要交易(注册/下单/上架等)请设 WEBAZ_API_KEY —— 到 ' + WEBAZ_API_URL + '/#welcome 申请邀请。'
|
|
1606
|
+
: '🟡 SANDBOX:所有工具都在本机本地 SQLite 运行,与 webaz.xyz 全网隔离(显式 dev/demo 模式)。任何计数 / 账号 / 订单仅本机有效。不设 WEBAZ_MODE 则默认走真网络只读。',
|
|
1525
1607
|
phase: 'pre_launch',
|
|
1526
1608
|
real_users_on_canonical: 0,
|
|
1527
1609
|
canonical_endpoint: 'https://webaz.xyz',
|
|
@@ -1541,6 +1623,7 @@ function handleInfo() {
|
|
|
1541
1623
|
],
|
|
1542
1624
|
honesty: 'Pre-launch: WAZ is a simulated test currency, no real money settles yet. Don\'t treat balances as real value. / 尚未上线:WAZ 是模拟测试币,暂无真实资金结算,余额勿当真实价值。',
|
|
1543
1625
|
try_it: 'Browse without an account at https://webaz.xyz/#discover ; the protocol state is public at https://webaz.xyz/.well-known/webaz-protocol.json',
|
|
1626
|
+
get_access: 'Pre-launch is invite-gated — request an invite at https://webaz.xyz/#welcome (browsing/reading needs no invite). / 上线前邀请制:到 #welcome 申请邀请,浏览/查看无需邀请。',
|
|
1544
1627
|
},
|
|
1545
1628
|
// 连接两个场景:用协议(本工具) ↔ 改协议(开发协作)。想改 WebAZ 本身的 agent 从这里进。
|
|
1546
1629
|
for_contributors: {
|
|
@@ -1549,6 +1632,8 @@ function handleInfo() {
|
|
|
1549
1632
|
start_here: 'AGENTS.md (project map + before-you-code + PR flow) → CONTRIBUTING.md (full guide)',
|
|
1550
1633
|
ai_accountability: 'AI-authored PRs: add 🤖🤖🤖 to the PR title; the agent must be triggered by a Passkey-bound human (webazer) who is accountable. / AI 提 PR:标题加 🤖🤖🤖,且须由已绑 Passkey 的真人(webazer)触发并担责。',
|
|
1551
1634
|
},
|
|
1635
|
+
// NETWORK 模式:真网络 live 状态(best-effort 拉自 webaz.xyz);SANDBOX 模式为 null。
|
|
1636
|
+
network_live,
|
|
1552
1637
|
live_stats: stats,
|
|
1553
1638
|
economics,
|
|
1554
1639
|
// 佣金机制 —— 纯功能性描述(怎么运作),不做"自证清白"式辩护。
|
|
@@ -1601,10 +1686,10 @@ function handleInfo() {
|
|
|
1601
1686
|
function handleRegister(args) {
|
|
1602
1687
|
// ─── RFC-003 P3:NETWORK 模式不自助建号 ────────────────────────
|
|
1603
1688
|
// 自助注册会绕过邀请码 / captcha / 责任制;且 CHARTER §4 I-5 要求账号必须由已绑 Passkey
|
|
1604
|
-
// 的真人创建("每个 agent 背后有可问责的真人")。NETWORK
|
|
1605
|
-
if (
|
|
1689
|
+
// 的真人创建("每个 agent 背后有可问责的真人")。NETWORK / 无 key 只读模式下都引导真人去 webaz.xyz 拿 key。
|
|
1690
|
+
if (isNetworkMode()) {
|
|
1606
1691
|
return {
|
|
1607
|
-
_mode:
|
|
1692
|
+
_mode: MODE,
|
|
1608
1693
|
registration: 'must_be_done_by_human_at_webaz_xyz',
|
|
1609
1694
|
message: '🟢 NETWORK 模式下不支持 agent 自助注册。开放协议的信任来自"每个 agent 背后有可问责的真人",所以注册这一步刻意留给真人在 webaz.xyz 完成。请按三步加入共享网络:',
|
|
1610
1695
|
steps: [
|
|
@@ -2532,6 +2617,19 @@ async function handleWallet(args) {
|
|
|
2532
2617
|
const apiKey = args.api_key;
|
|
2533
2618
|
if (!apiKey)
|
|
2534
2619
|
return { error: 'api_key required' };
|
|
2620
|
+
// RFC-003 Batch 4:NETWORK 模式 → webaz.xyz 真网络【只读】(Bearer api_key)。
|
|
2621
|
+
// 写动作(withdraw/topup/whitelist/connect)是 Passkey+OTP 多步流,MCP 不暴露,仅 PWA。
|
|
2622
|
+
if (toolBackend('webaz_wallet') === 'network') {
|
|
2623
|
+
if (action === 'view')
|
|
2624
|
+
return await apiCall('/api/wallet', { apiKey });
|
|
2625
|
+
if (action === 'deposits')
|
|
2626
|
+
return await apiCall('/api/wallet/deposits', { apiKey });
|
|
2627
|
+
if (action === 'withdrawals')
|
|
2628
|
+
return await apiCall('/api/wallet/withdrawals', { apiKey });
|
|
2629
|
+
if (action === 'income')
|
|
2630
|
+
return await apiCall('/api/wallet/income', { apiKey });
|
|
2631
|
+
return { error: `unknown action: ${action}. Valid: view | deposits | withdrawals | income. 提现/充值/白名单需 Passkey+OTP,仅 PWA Web 端。` };
|
|
2632
|
+
}
|
|
2535
2633
|
if (action === 'deposits')
|
|
2536
2634
|
return await pwaApi('GET', '/wallet/deposits', apiKey);
|
|
2537
2635
|
if (action === 'withdrawals')
|
|
@@ -2581,7 +2679,16 @@ async function handleWallet(args) {
|
|
|
2581
2679
|
};
|
|
2582
2680
|
}
|
|
2583
2681
|
// ─── 通知处理 ─────────────────────────────────────────────────
|
|
2584
|
-
function handleNotifications(args) {
|
|
2682
|
+
async function handleNotifications(args) {
|
|
2683
|
+
// RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络通知端点(Bearer api_key);SANDBOX 走本地。
|
|
2684
|
+
if (toolBackend('webaz_notifications') === 'network') {
|
|
2685
|
+
const apiKey = String(args.api_key || '');
|
|
2686
|
+
if (!apiKey)
|
|
2687
|
+
return { error: 'api_key required' };
|
|
2688
|
+
if (args.mark_read)
|
|
2689
|
+
await apiCall('/api/notifications/read', { method: 'POST', apiKey });
|
|
2690
|
+
return await apiCall('/api/notifications' + (args.unread === true ? '?unread=1' : ''), { apiKey });
|
|
2691
|
+
}
|
|
2585
2692
|
const auth = requireAuth(db, args.api_key);
|
|
2586
2693
|
if ('error' in auth)
|
|
2587
2694
|
return auth;
|
|
@@ -2609,11 +2716,69 @@ async function handleDispute(args) {
|
|
|
2609
2716
|
const apiKey = args.api_key;
|
|
2610
2717
|
if (!apiKey)
|
|
2611
2718
|
return { error: 'api_key required' };
|
|
2719
|
+
const action = args.action;
|
|
2720
|
+
// ── 仲裁裁定(Iron-Rule)──────────────────────────────────────
|
|
2721
|
+
// 两种模式都只登记意图 + 返回 PWA+Passkey 指引;不执行、不碰 db。放在 auth 之前。
|
|
2722
|
+
if (action === 'arbitrate') {
|
|
2723
|
+
if (!args.dispute_id)
|
|
2724
|
+
return { error: '请提供 dispute_id' };
|
|
2725
|
+
if (!args.ruling)
|
|
2726
|
+
return { error: '请提供 ruling(refund_buyer / release_seller / partial_refund / liability_split)' };
|
|
2727
|
+
if (!args.ruling_reason)
|
|
2728
|
+
return { error: '请提供 ruling_reason(裁定理由将永久记录)' };
|
|
2729
|
+
if (args.ruling === 'partial_refund' && !args.refund_amount && !args.liable_party) {
|
|
2730
|
+
return { error: 'partial_refund 需要提供 refund_amount,或 liable_party(第三方责任方)' };
|
|
2731
|
+
}
|
|
2732
|
+
if (args.ruling === 'liability_split' && (!Array.isArray(args.liability_parties) || args.liability_parties.length === 0)) {
|
|
2733
|
+
return { error: 'liability_split 需要提供 liability_parties 数组,每项 { user_id, amount }' };
|
|
2734
|
+
}
|
|
2735
|
+
return {
|
|
2736
|
+
success: false,
|
|
2737
|
+
requires_human_action: true,
|
|
2738
|
+
iron_rule: 'Arbitration ruling is irreversible, affects multiple parties, and locks fund distribution permanently. Agent cannot execute unilaterally. Same Iron-Rule gating as webaz_revoke_key / claim_verify vote.',
|
|
2739
|
+
action: 'arbitrate_dispute',
|
|
2740
|
+
dispute_id: args.dispute_id,
|
|
2741
|
+
proposed_ruling: {
|
|
2742
|
+
ruling: args.ruling,
|
|
2743
|
+
reason: args.ruling_reason,
|
|
2744
|
+
...(args.refund_amount !== undefined ? { refund_amount: args.refund_amount } : {}),
|
|
2745
|
+
...(args.liable_party ? { liable_party: args.liable_party } : {}),
|
|
2746
|
+
...(args.liability_parties ? { liability_parties: args.liability_parties } : {}),
|
|
2747
|
+
},
|
|
2748
|
+
next_step: {
|
|
2749
|
+
via: 'PWA + Passkey (arbitrator role)',
|
|
2750
|
+
url: `https://webaz.xyz/arbitrate?dispute=${encodeURIComponent(args.dispute_id)}`,
|
|
2751
|
+
instructions: '1) Open URL in browser 2) Sign in as arbitrator 3) Review evidence 4) Confirm with Passkey 5) Submit final ruling. Action is irreversible after confirmation.',
|
|
2752
|
+
},
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
// RFC-003 Batch 5:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
|
|
2756
|
+
if (toolBackend('webaz_dispute') === 'network') {
|
|
2757
|
+
if (action === 'view') {
|
|
2758
|
+
if (!args.dispute_id)
|
|
2759
|
+
return { error: '网络模式请提供 dispute_id(order_id 查询仅 PWA 支持)。 / provide dispute_id on the live network.' };
|
|
2760
|
+
return await apiCall('/api/disputes/' + encodeURIComponent(String(args.dispute_id)), { apiKey });
|
|
2761
|
+
}
|
|
2762
|
+
if (action === 'list_open')
|
|
2763
|
+
return await apiCall('/api/disputes', { apiKey });
|
|
2764
|
+
if (action === 'respond') {
|
|
2765
|
+
if (!args.dispute_id)
|
|
2766
|
+
return { error: '请提供 dispute_id' };
|
|
2767
|
+
return await apiCall('/api/disputes/' + encodeURIComponent(String(args.dispute_id)) + '/respond', { method: 'POST', apiKey, body: { notes: args.notes ?? '', evidence_description: args.evidence_description ?? '' } });
|
|
2768
|
+
}
|
|
2769
|
+
if (action === 'add_evidence') {
|
|
2770
|
+
if (!args.dispute_id)
|
|
2771
|
+
return { error: '请提供 dispute_id' };
|
|
2772
|
+
if (!args.evidence_description)
|
|
2773
|
+
return { error: '请提供 evidence_description(证据描述)' };
|
|
2774
|
+
return await apiCall('/api/disputes/' + encodeURIComponent(String(args.dispute_id)) + '/add-evidence', { method: 'POST', apiKey, body: { description: args.evidence_description, evidence_type: 'text' } });
|
|
2775
|
+
}
|
|
2776
|
+
return { error: `未知 action:${action}。Valid: view | list_open | respond | add_evidence | arbitrate` };
|
|
2777
|
+
}
|
|
2612
2778
|
const auth = requireAuth(db, apiKey);
|
|
2613
2779
|
if ('error' in auth)
|
|
2614
2780
|
return auth;
|
|
2615
2781
|
const { user } = auth;
|
|
2616
|
-
const action = args.action;
|
|
2617
2782
|
// ── 查看争议详情 ────────────────────────────────────────────
|
|
2618
2783
|
if (action === 'view') {
|
|
2619
2784
|
let dispute = args.dispute_id
|
|
@@ -2707,43 +2872,6 @@ async function handleDispute(args) {
|
|
|
2707
2872
|
evidence_type: 'text',
|
|
2708
2873
|
});
|
|
2709
2874
|
}
|
|
2710
|
-
// ── 仲裁员裁定(Iron-Rule:MCP 仅登记意图,真正裁定需 PWA + Passkey 二次确认)─
|
|
2711
|
-
// QA 轮 7 P1 修复:旧版直接调 PWA HTTP /api/disputes/:id/arbitrate,
|
|
2712
|
-
// 1) 违反 Iron-Rule 模型(应该跟 revoke_key/rotate_key 同模式 — agent 不能单方面执行不可逆操作)
|
|
2713
|
-
// 2) 本地 dev 没起 PWA 就挂
|
|
2714
|
-
if (action === 'arbitrate') {
|
|
2715
|
-
if (!args.dispute_id)
|
|
2716
|
-
return { error: '请提供 dispute_id' };
|
|
2717
|
-
if (!args.ruling)
|
|
2718
|
-
return { error: '请提供 ruling(refund_buyer / release_seller / partial_refund / liability_split)' };
|
|
2719
|
-
if (!args.ruling_reason)
|
|
2720
|
-
return { error: '请提供 ruling_reason(裁定理由将永久记录)' };
|
|
2721
|
-
if (args.ruling === 'partial_refund' && !args.refund_amount && !args.liable_party) {
|
|
2722
|
-
return { error: 'partial_refund 需要提供 refund_amount,或 liable_party(第三方责任方)' };
|
|
2723
|
-
}
|
|
2724
|
-
if (args.ruling === 'liability_split' && (!Array.isArray(args.liability_parties) || args.liability_parties.length === 0)) {
|
|
2725
|
-
return { error: 'liability_split 需要提供 liability_parties 数组,每项 { user_id, amount }' };
|
|
2726
|
-
}
|
|
2727
|
-
return {
|
|
2728
|
-
success: false,
|
|
2729
|
-
requires_human_action: true,
|
|
2730
|
-
iron_rule: 'Arbitration ruling is irreversible, affects multiple parties, and locks fund distribution permanently. Agent cannot execute unilaterally. Same Iron-Rule gating as webaz_revoke_key / claim_verify vote.',
|
|
2731
|
-
action: 'arbitrate_dispute',
|
|
2732
|
-
dispute_id: args.dispute_id,
|
|
2733
|
-
proposed_ruling: {
|
|
2734
|
-
ruling: args.ruling,
|
|
2735
|
-
reason: args.ruling_reason,
|
|
2736
|
-
...(args.refund_amount !== undefined ? { refund_amount: args.refund_amount } : {}),
|
|
2737
|
-
...(args.liable_party ? { liable_party: args.liable_party } : {}),
|
|
2738
|
-
...(args.liability_parties ? { liability_parties: args.liability_parties } : {}),
|
|
2739
|
-
},
|
|
2740
|
-
next_step: {
|
|
2741
|
-
via: 'PWA + Passkey (arbitrator role)',
|
|
2742
|
-
url: `https://webaz.xyz/arbitrate?dispute=${encodeURIComponent(args.dispute_id)}`,
|
|
2743
|
-
instructions: '1) Open URL in browser 2) Sign in as arbitrator 3) Review evidence 4) Confirm with Passkey 5) Submit final ruling. Action is irreversible after confirmation.',
|
|
2744
|
-
},
|
|
2745
|
-
};
|
|
2746
|
-
}
|
|
2747
2875
|
return { error: `未知 action:${action}。Valid: view | list_open | respond | add_evidence | arbitrate` };
|
|
2748
2876
|
}
|
|
2749
2877
|
// ─── 索赔验证(claim-verification)处理 — Wave 6 新增 ────────────
|
|
@@ -2832,8 +2960,44 @@ async function handleClaimVerify(args) {
|
|
|
2832
2960
|
return { error: `未知 action:${action}。Valid: create | view | mine | submit_seller_evidence | available | vote | eligibility | verifier_status | apply | withdraw_application | appeal` };
|
|
2833
2961
|
}
|
|
2834
2962
|
// ─── Skill 市场处理 ────────────────────────────────────────────
|
|
2835
|
-
function handleSkill(args) {
|
|
2963
|
+
async function handleSkill(args) {
|
|
2836
2964
|
const action = args.action;
|
|
2965
|
+
// RFC-003 Batch 3:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地引擎。
|
|
2966
|
+
if (toolBackend('webaz_skill') === 'network') {
|
|
2967
|
+
const apiKey = String(args.api_key || '');
|
|
2968
|
+
if (action === 'list') {
|
|
2969
|
+
const qs = new URLSearchParams();
|
|
2970
|
+
if (args.skill_type)
|
|
2971
|
+
qs.set('skill_type', String(args.skill_type));
|
|
2972
|
+
if (args.query)
|
|
2973
|
+
qs.set('q', String(args.query));
|
|
2974
|
+
const q = qs.toString();
|
|
2975
|
+
return await apiCall('/api/skills' + (q ? '?' + q : ''), { apiKey });
|
|
2976
|
+
}
|
|
2977
|
+
if (!apiKey)
|
|
2978
|
+
return { error: 'api_key required' };
|
|
2979
|
+
if (action === 'publish') {
|
|
2980
|
+
return await apiCall('/api/skills', { method: 'POST', apiKey, body: {
|
|
2981
|
+
name: args.name, description: args.description, category: args.category,
|
|
2982
|
+
skill_type: args.skill_type, config: args.config,
|
|
2983
|
+
} });
|
|
2984
|
+
}
|
|
2985
|
+
if (action === 'subscribe') {
|
|
2986
|
+
if (!args.skill_id)
|
|
2987
|
+
return { error: '请提供 skill_id' };
|
|
2988
|
+
return await apiCall('/api/skills/' + encodeURIComponent(String(args.skill_id)) + '/subscribe', { method: 'POST', apiKey, body: { config: args.config } });
|
|
2989
|
+
}
|
|
2990
|
+
if (action === 'unsubscribe') {
|
|
2991
|
+
if (!args.skill_id)
|
|
2992
|
+
return { error: '请提供 skill_id' };
|
|
2993
|
+
return await apiCall('/api/skills/' + encodeURIComponent(String(args.skill_id)) + '/subscribe', { method: 'DELETE', apiKey });
|
|
2994
|
+
}
|
|
2995
|
+
if (action === 'my_skills')
|
|
2996
|
+
return await apiCall('/api/skills/mine', { apiKey });
|
|
2997
|
+
if (action === 'my_subs')
|
|
2998
|
+
return await apiCall('/api/skills/subscriptions', { apiKey });
|
|
2999
|
+
return { error: `未知 action:${action}。可选:list, publish, subscribe, unsubscribe, my_skills, my_subs` };
|
|
3000
|
+
}
|
|
2837
3001
|
// ── 浏览 Skill 市场 ────────────────────────────────────────
|
|
2838
3002
|
if (action === 'list') {
|
|
2839
3003
|
let userId;
|
|
@@ -2929,6 +3093,21 @@ function redactKey(key) {
|
|
|
2929
3093
|
return `${key.slice(0, 8)}***${key.slice(-4)}`;
|
|
2930
3094
|
}
|
|
2931
3095
|
function handleMyKey(args) {
|
|
3096
|
+
// RFC-003 Batch 1:NETWORK 模式下,账号找回是 Passkey 门控(Iron-Rule)——handle+permanent_code
|
|
3097
|
+
// 查询不作为网络端点暴露(防枚举)。诚实引导到 PWA 的 Passkey 找回流,不在本地假装查到。
|
|
3098
|
+
if (toolBackend('webaz_mykey') === 'network') {
|
|
3099
|
+
return {
|
|
3100
|
+
_mode: 'network',
|
|
3101
|
+
found: null,
|
|
3102
|
+
message: 'On the live network, account recovery is Passkey-gated for security (Iron-Rule). handle + permanent_code lookup is not exposed as a network endpoint (anti-enumeration).',
|
|
3103
|
+
recover: {
|
|
3104
|
+
via: 'PWA + Passkey',
|
|
3105
|
+
start_url: `${WEBAZ_API_URL}/recover`,
|
|
3106
|
+
note: 'Open in a browser and verify with your Passkey to recover or rotate your api_key.',
|
|
3107
|
+
},
|
|
3108
|
+
rotate_hint: 'Already have your api_key but want to replace it? Use webaz_rotate_key.',
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
2932
3111
|
const handle = args.handle?.trim();
|
|
2933
3112
|
const permaCode = args.permanent_code?.trim()?.toUpperCase();
|
|
2934
3113
|
if (!handle || !permaCode) {
|
|
@@ -2982,6 +3161,33 @@ function handleMyKey(args) {
|
|
|
2982
3161
|
async function handleProfile(args) {
|
|
2983
3162
|
const action = args.action;
|
|
2984
3163
|
const apiKey = String(args.api_key || '');
|
|
3164
|
+
// RFC-003 Batch 1:NETWORK 模式 → 全部 action 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
|
|
3165
|
+
if (toolBackend('webaz_profile') === 'network') {
|
|
3166
|
+
if (action === 'view_user') {
|
|
3167
|
+
if (!args.user_id)
|
|
3168
|
+
return { error: 'user_id required' };
|
|
3169
|
+
return await apiCall('/api/users/' + encodeURIComponent(String(args.user_id)), { apiKey });
|
|
3170
|
+
}
|
|
3171
|
+
if (action === 'feed') {
|
|
3172
|
+
if (!args.user_id || !args.feed)
|
|
3173
|
+
return { error: 'user_id + feed required' };
|
|
3174
|
+
const FEED_PATH = {
|
|
3175
|
+
secondhand: 'secondhand', auctions: 'auctions', reviews: 'reviews', products: 'products',
|
|
3176
|
+
shares: 'shareables', reputation: 'reputation', pv: 'pv-summary', liked: 'liked-shareables',
|
|
3177
|
+
};
|
|
3178
|
+
const seg = FEED_PATH[String(args.feed)];
|
|
3179
|
+
if (!seg)
|
|
3180
|
+
return { error: `unknown feed: ${args.feed}. options: ${Object.keys(FEED_PATH).join(', ')}` };
|
|
3181
|
+
return await apiCall('/api/users/' + encodeURIComponent(String(args.user_id)) + '/' + seg, { apiKey });
|
|
3182
|
+
}
|
|
3183
|
+
if (action === 'view')
|
|
3184
|
+
return await apiCall('/api/me', { apiKey });
|
|
3185
|
+
if (action === 'add_role')
|
|
3186
|
+
return await apiCall('/api/profile/add-role', { method: 'POST', apiKey, body: { role: args.role } });
|
|
3187
|
+
if (action === 'switch_role')
|
|
3188
|
+
return await apiCall('/api/profile/switch-role', { method: 'POST', apiKey, body: { role: args.role } });
|
|
3189
|
+
return { error: `Unknown action: ${action}. Options: view, add_role, switch_role, view_user, feed` };
|
|
3190
|
+
}
|
|
2985
3191
|
// 看他人公开主页 / 内容流
|
|
2986
3192
|
if (action === 'view_user') {
|
|
2987
3193
|
if (!args.user_id)
|
|
@@ -3049,6 +3255,26 @@ async function handleProfile(args) {
|
|
|
3049
3255
|
// 这里给 agent 两个"声明意图"工具:MCP 验 api_key 合法 → 返回 PWA URL 让用户 Passkey 二次确认。
|
|
3050
3256
|
// 真正改 DB 的动作放 PWA endpoint,跟 claim_verify / arbitrate 同模型。
|
|
3051
3257
|
function handleRevokeKey(args) {
|
|
3258
|
+
// RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 撤销指引。
|
|
3259
|
+
if (toolBackend('webaz_revoke_key') === 'network') {
|
|
3260
|
+
const apiKey = String(args.api_key || '');
|
|
3261
|
+
const reason = (args.reason || 'unspecified').trim().slice(0, 100);
|
|
3262
|
+
return {
|
|
3263
|
+
_mode: 'network',
|
|
3264
|
+
success: false,
|
|
3265
|
+
requires_human_action: true,
|
|
3266
|
+
iron_rule: 'API key revocation is a destructive, irreversible operation. Agent cannot execute unilaterally. Same gating as claim_verify and arbitrate.',
|
|
3267
|
+
action: 'revoke_api_key',
|
|
3268
|
+
api_key_hint: redactKey(apiKey),
|
|
3269
|
+
reason_logged: reason,
|
|
3270
|
+
next_step: {
|
|
3271
|
+
via: 'PWA + Passkey',
|
|
3272
|
+
url: 'https://webaz.xyz/revoke',
|
|
3273
|
+
instructions: '1) Open URL in browser 2) Sign in 3) Confirm with Passkey 4) Click "Revoke". After confirm the old api_key returns 401 on all tools.',
|
|
3274
|
+
warning: 'After revoke you cannot call any auth\'d tool until you have a new api_key. Use webaz_rotate_key instead for atomic invalidate + re-issue.',
|
|
3275
|
+
},
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3052
3278
|
const auth = requireAuth(db, args.api_key);
|
|
3053
3279
|
if ('error' in auth)
|
|
3054
3280
|
return auth;
|
|
@@ -3071,6 +3297,25 @@ function handleRevokeKey(args) {
|
|
|
3071
3297
|
};
|
|
3072
3298
|
}
|
|
3073
3299
|
function handleRotateKey(args) {
|
|
3300
|
+
// RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 轮换指引。
|
|
3301
|
+
if (toolBackend('webaz_rotate_key') === 'network') {
|
|
3302
|
+
const apiKey = String(args.api_key || '');
|
|
3303
|
+
const reason = (args.reason || 'rotation').trim().slice(0, 100);
|
|
3304
|
+
return {
|
|
3305
|
+
_mode: 'network',
|
|
3306
|
+
success: false,
|
|
3307
|
+
requires_human_action: true,
|
|
3308
|
+
iron_rule: 'API key rotation requires Passkey verification (Iron-Rule). MCP registers intent only.',
|
|
3309
|
+
action: 'rotate_api_key',
|
|
3310
|
+
old_api_key_hint: redactKey(apiKey),
|
|
3311
|
+
reason_logged: reason,
|
|
3312
|
+
next_step: {
|
|
3313
|
+
via: 'PWA + Passkey',
|
|
3314
|
+
url: 'https://webaz.xyz/rotate',
|
|
3315
|
+
instructions: '1) Open URL 2) Sign in 3) Confirm with Passkey 4) PWA returns new api_key — copy immediately, shown once. Old key invalidated atomically with new key issuance.',
|
|
3316
|
+
},
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3074
3319
|
const auth = requireAuth(db, args.api_key);
|
|
3075
3320
|
if ('error' in auth)
|
|
3076
3321
|
return auth;
|
|
@@ -3092,7 +3337,14 @@ function handleRotateKey(args) {
|
|
|
3092
3337
|
};
|
|
3093
3338
|
}
|
|
3094
3339
|
// ─── 推广 / 双轨 (Tokenomics) ───────────────────────────────────
|
|
3095
|
-
function handleReferral(args) {
|
|
3340
|
+
async function handleReferral(args) {
|
|
3341
|
+
// RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络聚合(Bearer api_key);SANDBOX 走本地。
|
|
3342
|
+
if (toolBackend('webaz_referral') === 'network') {
|
|
3343
|
+
const apiKey = String(args.api_key || '');
|
|
3344
|
+
if (!apiKey)
|
|
3345
|
+
return { error: 'api_key required' };
|
|
3346
|
+
return await apiCall('/api/referral/me', { apiKey });
|
|
3347
|
+
}
|
|
3096
3348
|
const auth = requireAuth(db, args.api_key);
|
|
3097
3349
|
if ('error' in auth)
|
|
3098
3350
|
return auth;
|
|
@@ -3188,7 +3440,19 @@ function handleReferral(args) {
|
|
|
3188
3440
|
: 'Complete 1 purchase first, then your share link will earn 3-tier commission. Until then, your share builds points-matching only.',
|
|
3189
3441
|
};
|
|
3190
3442
|
}
|
|
3191
|
-
function handleShareLink(args) {
|
|
3443
|
+
async function handleShareLink(args) {
|
|
3444
|
+
// RFC-003 #1122:NETWORK 模式 → 调 webaz.xyz 的 /api/share-link(服务端同款计算);SANDBOX 走本地。
|
|
3445
|
+
if (toolBackend('webaz_share_link') === 'network') {
|
|
3446
|
+
const apiKey = String(args.api_key || '');
|
|
3447
|
+
if (!apiKey)
|
|
3448
|
+
return { error: 'api_key required' };
|
|
3449
|
+
if (!args.product_id)
|
|
3450
|
+
return { error: 'product_id required' };
|
|
3451
|
+
const qs = new URLSearchParams({ product_id: String(args.product_id) });
|
|
3452
|
+
if (args.side)
|
|
3453
|
+
qs.set('side', String(args.side));
|
|
3454
|
+
return await apiCall('/api/share-link?' + qs.toString(), { apiKey });
|
|
3455
|
+
}
|
|
3192
3456
|
const auth = requireAuth(db, args.api_key);
|
|
3193
3457
|
if ('error' in auth)
|
|
3194
3458
|
return auth;
|
|
@@ -3272,7 +3536,24 @@ function handleShareLink(args) {
|
|
|
3272
3536
|
};
|
|
3273
3537
|
}
|
|
3274
3538
|
// ─── 黑名单 / 关注 / 雷达 / 默认地址 / shareables ─────────
|
|
3275
|
-
function handleBlocklist(args) {
|
|
3539
|
+
async function handleBlocklist(args) {
|
|
3540
|
+
// RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
|
|
3541
|
+
if (toolBackend('webaz_blocklist') === 'network') {
|
|
3542
|
+
const apiKey = String(args.api_key || '');
|
|
3543
|
+
if (!apiKey)
|
|
3544
|
+
return { error: 'api_key required' };
|
|
3545
|
+
const act = String(args.action || '');
|
|
3546
|
+
if (act === 'list')
|
|
3547
|
+
return await apiCall('/api/blocklist', { apiKey });
|
|
3548
|
+
const uid = args.user_id ? encodeURIComponent(String(args.user_id)) : '';
|
|
3549
|
+
if (!uid)
|
|
3550
|
+
return { error: 'user_id required for block/unblock' };
|
|
3551
|
+
if (act === 'block')
|
|
3552
|
+
return await apiCall('/api/blocklist/' + uid, { method: 'POST', apiKey, body: { reason: args.reason } });
|
|
3553
|
+
if (act === 'unblock')
|
|
3554
|
+
return await apiCall('/api/blocklist/' + uid, { method: 'DELETE', apiKey });
|
|
3555
|
+
return { error: `unknown action: ${act}` };
|
|
3556
|
+
}
|
|
3276
3557
|
const auth = requireAuth(db, args.api_key);
|
|
3277
3558
|
if ('error' in auth)
|
|
3278
3559
|
return auth;
|
|
@@ -3309,7 +3590,26 @@ function handleBlocklist(args) {
|
|
|
3309
3590
|
}
|
|
3310
3591
|
return { error: `unknown action: ${action}` };
|
|
3311
3592
|
}
|
|
3312
|
-
function handleFollows(args) {
|
|
3593
|
+
async function handleFollows(args) {
|
|
3594
|
+
// RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
|
|
3595
|
+
if (toolBackend('webaz_follows') === 'network') {
|
|
3596
|
+
const apiKey = String(args.api_key || '');
|
|
3597
|
+
if (!apiKey)
|
|
3598
|
+
return { error: 'api_key required' };
|
|
3599
|
+
const act = String(args.action || '');
|
|
3600
|
+
if (act === 'list')
|
|
3601
|
+
return await apiCall('/api/follows/me', { apiKey });
|
|
3602
|
+
const uid = args.user_id ? encodeURIComponent(String(args.user_id)) : '';
|
|
3603
|
+
if (!uid)
|
|
3604
|
+
return { error: 'user_id required' };
|
|
3605
|
+
if (act === 'follow')
|
|
3606
|
+
return await apiCall('/api/follows/' + uid, { method: 'POST', apiKey });
|
|
3607
|
+
if (act === 'unfollow')
|
|
3608
|
+
return await apiCall('/api/follows/' + uid, { method: 'DELETE', apiKey });
|
|
3609
|
+
if (act === 'status')
|
|
3610
|
+
return await apiCall('/api/follows/' + uid + '/status', { apiKey });
|
|
3611
|
+
return { error: `unknown action: ${act}` };
|
|
3612
|
+
}
|
|
3313
3613
|
const auth = requireAuth(db, args.api_key);
|
|
3314
3614
|
if ('error' in auth)
|
|
3315
3615
|
return auth;
|
|
@@ -3355,12 +3655,32 @@ function handleFollows(args) {
|
|
|
3355
3655
|
}
|
|
3356
3656
|
return { error: `unknown action: ${action}` };
|
|
3357
3657
|
}
|
|
3358
|
-
function handleNearby(args) {
|
|
3658
|
+
async function handleNearby(args) {
|
|
3659
|
+
const action = String(args.action || '');
|
|
3660
|
+
// RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
|
|
3661
|
+
if (toolBackend('webaz_nearby') === 'network') {
|
|
3662
|
+
const apiKey = String(args.api_key || '');
|
|
3663
|
+
if (!apiKey)
|
|
3664
|
+
return { error: 'api_key required' };
|
|
3665
|
+
if (action === 'set_location')
|
|
3666
|
+
return await apiCall('/api/profile/set-location', { method: 'POST', apiKey, body: { lat: args.lat, lng: args.lng } });
|
|
3667
|
+
if (action === 'clear_location')
|
|
3668
|
+
return await apiCall('/api/profile/clear-location', { method: 'POST', apiKey });
|
|
3669
|
+
if (action === 'query') {
|
|
3670
|
+
const qs = new URLSearchParams();
|
|
3671
|
+
if (args.scope)
|
|
3672
|
+
qs.set('scope', String(args.scope));
|
|
3673
|
+
if (args.window)
|
|
3674
|
+
qs.set('window', String(args.window));
|
|
3675
|
+
const q = qs.toString();
|
|
3676
|
+
return await apiCall('/api/nearby' + (q ? '?' + q : ''), { apiKey });
|
|
3677
|
+
}
|
|
3678
|
+
return { error: `unknown action: ${action}` };
|
|
3679
|
+
}
|
|
3359
3680
|
const auth = requireAuth(db, args.api_key);
|
|
3360
3681
|
if ('error' in auth)
|
|
3361
3682
|
return auth;
|
|
3362
3683
|
const { user } = auth;
|
|
3363
|
-
const action = String(args.action || '');
|
|
3364
3684
|
if (action === 'set_location') {
|
|
3365
3685
|
const lat = Number(args.lat), lng = Number(args.lng);
|
|
3366
3686
|
if (!Number.isFinite(lat) || !Number.isFinite(lng))
|
|
@@ -3405,7 +3725,28 @@ function handleNearby(args) {
|
|
|
3405
3725
|
}
|
|
3406
3726
|
return { error: `unknown action: ${action}` };
|
|
3407
3727
|
}
|
|
3408
|
-
function handleDefaultAddress(args) {
|
|
3728
|
+
async function handleDefaultAddress(args) {
|
|
3729
|
+
// RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
|
|
3730
|
+
if (toolBackend('webaz_default_address') === 'network') {
|
|
3731
|
+
const apiKey = String(args.api_key || '');
|
|
3732
|
+
if (!apiKey)
|
|
3733
|
+
return { error: 'api_key required' };
|
|
3734
|
+
const act = String(args.action || '');
|
|
3735
|
+
if (act === 'read') {
|
|
3736
|
+
const me = await apiCall('/api/me', { apiKey });
|
|
3737
|
+
if (me.error)
|
|
3738
|
+
return me;
|
|
3739
|
+
return { address_text: me.default_address_text ?? null, address_region: me.default_address_region ?? null };
|
|
3740
|
+
}
|
|
3741
|
+
if (act === 'set') {
|
|
3742
|
+
const text = (args.text || '').trim().slice(0, 200);
|
|
3743
|
+
const region = (args.region || '').trim().slice(0, 40);
|
|
3744
|
+
if (!text)
|
|
3745
|
+
return { error: 'missing_text', error_code: 'TEXT_REQUIRED', message: 'action=set 需要 "text" 字段(自由格式地址,≤200);可选 "region"。' };
|
|
3746
|
+
return await apiCall('/api/profile/default-address', { method: 'POST', apiKey, body: { text, region: region || null } });
|
|
3747
|
+
}
|
|
3748
|
+
return { error: `unknown action: ${act}` };
|
|
3749
|
+
}
|
|
3409
3750
|
const auth = requireAuth(db, args.api_key);
|
|
3410
3751
|
if ('error' in auth)
|
|
3411
3752
|
return auth;
|
|
@@ -3443,12 +3784,42 @@ function handleDefaultAddress(args) {
|
|
|
3443
3784
|
}
|
|
3444
3785
|
return { error: `unknown action: ${action}` };
|
|
3445
3786
|
}
|
|
3446
|
-
function handleShareables(args) {
|
|
3787
|
+
async function handleShareables(args) {
|
|
3788
|
+
const action = String(args.action || '');
|
|
3789
|
+
// RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
|
|
3790
|
+
if (toolBackend('webaz_shareables') === 'network') {
|
|
3791
|
+
const apiKey = String(args.api_key || '');
|
|
3792
|
+
if (!apiKey)
|
|
3793
|
+
return { error: 'api_key required' };
|
|
3794
|
+
if (action === 'list_mine')
|
|
3795
|
+
return await apiCall('/api/shareables/me', { apiKey });
|
|
3796
|
+
if (action === 'by_product') {
|
|
3797
|
+
if (!args.related_product_id)
|
|
3798
|
+
return { error: 'related_product_id required' };
|
|
3799
|
+
return await apiCall('/api/shareables/by-product/' + encodeURIComponent(String(args.related_product_id)), { apiKey });
|
|
3800
|
+
}
|
|
3801
|
+
if (action === 'by_anchor') {
|
|
3802
|
+
if (!args.related_anchor)
|
|
3803
|
+
return { error: 'related_anchor required' };
|
|
3804
|
+
return await apiCall('/api/shareables/by-anchor/' + encodeURIComponent(String(args.related_anchor)), { apiKey });
|
|
3805
|
+
}
|
|
3806
|
+
if (action === 'add') {
|
|
3807
|
+
return await apiCall('/api/shareables', { method: 'POST', apiKey, body: {
|
|
3808
|
+
external_url: args.external_url, related_product_id: args.related_product_id,
|
|
3809
|
+
related_anchor: args.related_anchor, title: args.title, description: args.description,
|
|
3810
|
+
} });
|
|
3811
|
+
}
|
|
3812
|
+
if (action === 'delete') {
|
|
3813
|
+
if (!args.shareable_id)
|
|
3814
|
+
return { error: 'shareable_id required' };
|
|
3815
|
+
return await apiCall('/api/shareables/' + encodeURIComponent(String(args.shareable_id)), { method: 'DELETE', apiKey });
|
|
3816
|
+
}
|
|
3817
|
+
return { error: `unknown action: ${action}` };
|
|
3818
|
+
}
|
|
3447
3819
|
const auth = requireAuth(db, args.api_key);
|
|
3448
3820
|
if ('error' in auth)
|
|
3449
3821
|
return auth;
|
|
3450
3822
|
const { user } = auth;
|
|
3451
|
-
const action = String(args.action || '');
|
|
3452
3823
|
if (action === 'list_mine') {
|
|
3453
3824
|
const rows = db.prepare(`
|
|
3454
3825
|
SELECT s.*, p.title as product_title FROM shareables s
|
|
@@ -3560,6 +3931,11 @@ async function readEndpoint(tool, subpath) {
|
|
|
3560
3931
|
}
|
|
3561
3932
|
}
|
|
3562
3933
|
async function pwaApi(method, path, apiKey, body) {
|
|
3934
|
+
// RFC-003:NETWORK / network_readonly → 走 webaz.xyz(Bearer 可空)。仅 NETWORK_TOOLS 里的工具会到这里
|
|
3935
|
+
// (其余未迁工具在 dispatch 被 Batch 0 守卫拦下);SANDBOX 才转发本地 PWA(localhost)。
|
|
3936
|
+
if (isNetworkMode()) {
|
|
3937
|
+
return apiCall(path.startsWith('/api') ? path : '/api' + path, { method, apiKey, body });
|
|
3938
|
+
}
|
|
3563
3939
|
const opts = {
|
|
3564
3940
|
method,
|
|
3565
3941
|
headers: {
|
|
@@ -3602,9 +3978,12 @@ async function handleSecondhand(args) {
|
|
|
3602
3978
|
if (!isPublic) {
|
|
3603
3979
|
if (!apiKey)
|
|
3604
3980
|
return { error: 'api_key required' };
|
|
3605
|
-
|
|
3606
|
-
if ('
|
|
3607
|
-
|
|
3981
|
+
// NETWORK 模式由 webaz.xyz 端点鉴权;只有 SANDBOX 才查本地库(否则真网络用户的 key 不在本地会误拒)。
|
|
3982
|
+
if (toolBackend('webaz_secondhand') !== 'network') {
|
|
3983
|
+
const auth = requireAuth(db, apiKey);
|
|
3984
|
+
if ('error' in auth)
|
|
3985
|
+
return auth;
|
|
3986
|
+
}
|
|
3608
3987
|
}
|
|
3609
3988
|
const iid = () => encodeURIComponent(String(args.item_id || ''));
|
|
3610
3989
|
switch (action) {
|
|
@@ -3670,9 +4049,11 @@ async function handleTrial(args) {
|
|
|
3670
4049
|
if (!isPublic) {
|
|
3671
4050
|
if (!apiKey)
|
|
3672
4051
|
return { error: 'api_key required' };
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
4052
|
+
if (toolBackend('webaz_trial') !== 'network') {
|
|
4053
|
+
const auth = requireAuth(db, apiKey);
|
|
4054
|
+
if ('error' in auth)
|
|
4055
|
+
return auth;
|
|
4056
|
+
}
|
|
3676
4057
|
}
|
|
3677
4058
|
const pid = () => encodeURIComponent(String(args.product_id || ''));
|
|
3678
4059
|
switch (action) {
|
|
@@ -3721,9 +4102,11 @@ async function handleSkillMarket(args) {
|
|
|
3721
4102
|
if (!isPublic) {
|
|
3722
4103
|
if (!apiKey)
|
|
3723
4104
|
return { error: 'api_key required' };
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
4105
|
+
if (toolBackend('webaz_skill_market') !== 'network') {
|
|
4106
|
+
const auth = requireAuth(db, apiKey);
|
|
4107
|
+
if ('error' in auth)
|
|
4108
|
+
return auth;
|
|
4109
|
+
}
|
|
3727
4110
|
}
|
|
3728
4111
|
const sid = () => encodeURIComponent(String(args.skill_id || ''));
|
|
3729
4112
|
switch (action) {
|
|
@@ -3790,9 +4173,11 @@ async function handleRfq(args) {
|
|
|
3790
4173
|
const action = String(args.action || '');
|
|
3791
4174
|
if (!apiKey)
|
|
3792
4175
|
return { error: 'api_key required' };
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
4176
|
+
if (toolBackend('webaz_rfq') !== 'network') {
|
|
4177
|
+
const auth = requireAuth(db, apiKey);
|
|
4178
|
+
if ('error' in auth)
|
|
4179
|
+
return auth;
|
|
4180
|
+
}
|
|
3796
4181
|
switch (action) {
|
|
3797
4182
|
case 'create': return await pwaApi('POST', '/rfqs', apiKey, {
|
|
3798
4183
|
title: args.title, qty: args.qty, max_price: args.max_price,
|
|
@@ -3837,6 +4222,36 @@ async function handleBid(args) {
|
|
|
3837
4222
|
const action = String(args.action || '');
|
|
3838
4223
|
if (!apiKey)
|
|
3839
4224
|
return { error: 'api_key required' };
|
|
4225
|
+
// RFC-003 Batch 4:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);质押由服务端结算。
|
|
4226
|
+
if (toolBackend('webaz_bid') === 'network') {
|
|
4227
|
+
if (action === 'submit') {
|
|
4228
|
+
if (!args.rfq_id || !args.price)
|
|
4229
|
+
return { error: 'rfq_id + price required' };
|
|
4230
|
+
return await apiCall('/api/rfqs/' + encodeURIComponent(String(args.rfq_id)) + '/bids', { method: 'POST', apiKey, body: {
|
|
4231
|
+
price: args.price, qty_offered: args.qty_offered, eta_hours: args.eta_hours,
|
|
4232
|
+
fulfillment_type: args.fulfillment_type ?? 'standard', note: args.note, offer_id: args.offer_id,
|
|
4233
|
+
} });
|
|
4234
|
+
}
|
|
4235
|
+
if (action === 'patch') {
|
|
4236
|
+
if (!args.bid_id)
|
|
4237
|
+
return { error: 'bid_id required' };
|
|
4238
|
+
const body = {};
|
|
4239
|
+
for (const k of ['price', 'qty_offered', 'eta_hours', 'fulfillment_type', 'note'])
|
|
4240
|
+
if (args[k] !== undefined)
|
|
4241
|
+
body[k] = args[k];
|
|
4242
|
+
return await apiCall('/api/bids/' + encodeURIComponent(String(args.bid_id)), { method: 'PATCH', apiKey, body });
|
|
4243
|
+
}
|
|
4244
|
+
if (action === 'cancel') {
|
|
4245
|
+
if (!args.bid_id)
|
|
4246
|
+
return { error: 'bid_id required' };
|
|
4247
|
+
return await apiCall('/api/bids/' + encodeURIComponent(String(args.bid_id)), { method: 'DELETE', apiKey });
|
|
4248
|
+
}
|
|
4249
|
+
if (action === 'list_mine') {
|
|
4250
|
+
return { _mode: 'network', not_available_on_network: true,
|
|
4251
|
+
error: 'webaz_bid list_mine 暂无网络端点(webaz.xyz 未提供 my-bids GET)。请用 webaz_rfq action=detail 查看具体 RFQ 的出价,或到 PWA 查看。 / no my-bids GET on the network yet; use webaz_rfq detail or the PWA.' };
|
|
4252
|
+
}
|
|
4253
|
+
return { error: `unknown action: ${action}` };
|
|
4254
|
+
}
|
|
3840
4255
|
const auth = requireAuth(db, apiKey);
|
|
3841
4256
|
if ('error' in auth)
|
|
3842
4257
|
return auth;
|
|
@@ -3881,9 +4296,11 @@ async function handleChat(args) {
|
|
|
3881
4296
|
const action = String(args.action || '');
|
|
3882
4297
|
if (!apiKey)
|
|
3883
4298
|
return { error: 'api_key required' };
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
4299
|
+
if (toolBackend('webaz_chat') !== 'network') {
|
|
4300
|
+
const auth = requireAuth(db, apiKey);
|
|
4301
|
+
if ('error' in auth)
|
|
4302
|
+
return auth;
|
|
4303
|
+
}
|
|
3887
4304
|
switch (action) {
|
|
3888
4305
|
case 'start': {
|
|
3889
4306
|
if (!args.kind || !args.context_id)
|
|
@@ -3921,6 +4338,52 @@ async function handleAutoBidSkill(args) {
|
|
|
3921
4338
|
const action = String(args.action || '');
|
|
3922
4339
|
if (!apiKey)
|
|
3923
4340
|
return { error: 'api_key required' };
|
|
4341
|
+
const buildAutoBidConfig = () => ({
|
|
4342
|
+
enabled: args.enabled !== false,
|
|
4343
|
+
categories: Array.isArray(args.categories) ? args.categories : ['standard'],
|
|
4344
|
+
regions: Array.isArray(args.regions) ? args.regions : [],
|
|
4345
|
+
max_eta_h: Number(args.max_eta_h ?? 24),
|
|
4346
|
+
fulfillment_type: String(args.fulfillment_type ?? 'standard'),
|
|
4347
|
+
bid_strategy: String(args.bid_strategy ?? 'cheapest_undercut'),
|
|
4348
|
+
undercut_pct: Math.max(0, Math.min(0.5, Number(args.undercut_pct ?? 0.05))),
|
|
4349
|
+
max_price_cap: args.max_price_cap ?? null,
|
|
4350
|
+
daily_cap: Number(args.daily_cap ?? 20),
|
|
4351
|
+
cooldown_min: Number(args.cooldown_min ?? 60),
|
|
4352
|
+
});
|
|
4353
|
+
// RFC-003 Batch 4:NETWORK 模式 → 先从 webaz.xyz 取既有 auto_bid skill,再 PATCH/POST/disable。
|
|
4354
|
+
if (toolBackend('webaz_auto_bid') === 'network') {
|
|
4355
|
+
const mine = await apiCall('/api/skills/mine', { apiKey });
|
|
4356
|
+
if (mine.error)
|
|
4357
|
+
return mine;
|
|
4358
|
+
const list = (Array.isArray(mine) ? mine : (Array.isArray(mine.skills) ? mine.skills : []));
|
|
4359
|
+
const existingNet = list.find(s => s.skill_type === 'auto_bid');
|
|
4360
|
+
if (action === 'get') {
|
|
4361
|
+
if (!existingNet)
|
|
4362
|
+
return { exists: false };
|
|
4363
|
+
let cfg = existingNet.config;
|
|
4364
|
+
if (typeof cfg === 'string') {
|
|
4365
|
+
try {
|
|
4366
|
+
cfg = JSON.parse(cfg || '{}');
|
|
4367
|
+
}
|
|
4368
|
+
catch {
|
|
4369
|
+
cfg = {};
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
return { exists: true, id: existingNet.id, active: existingNet.active, config: cfg };
|
|
4373
|
+
}
|
|
4374
|
+
if (action === 'set') {
|
|
4375
|
+
const config = buildAutoBidConfig();
|
|
4376
|
+
if (existingNet)
|
|
4377
|
+
return await apiCall('/api/skills/' + encodeURIComponent(String(existingNet.id)), { method: 'PATCH', apiKey, body: { config, active: config.enabled ? 1 : 0 } });
|
|
4378
|
+
return await apiCall('/api/skills', { method: 'POST', apiKey, body: { name: '我的自动报价 (MCP)', description: 'auto_bid via MCP', category: 'rfq', skill_type: 'auto_bid', config } });
|
|
4379
|
+
}
|
|
4380
|
+
if (action === 'disable') {
|
|
4381
|
+
if (!existingNet)
|
|
4382
|
+
return { error: '尚未创建 auto_bid Skill' };
|
|
4383
|
+
return await apiCall('/api/skills/' + encodeURIComponent(String(existingNet.id)) + '/disable', { method: 'POST', apiKey });
|
|
4384
|
+
}
|
|
4385
|
+
return { error: `unknown action: ${action}` };
|
|
4386
|
+
}
|
|
3924
4387
|
const auth = requireAuth(db, apiKey);
|
|
3925
4388
|
if ('error' in auth)
|
|
3926
4389
|
return auth;
|
|
@@ -3972,6 +4435,7 @@ async function handlePriceHistory(args) {
|
|
|
3972
4435
|
}
|
|
3973
4436
|
async function handleCharity(args) {
|
|
3974
4437
|
const action = String(args.action || '');
|
|
4438
|
+
// RFC-003 Batch 4:公开读走 readEndpoint(network → webaz.xyz / sandbox → 本地);写走 pwaApi(mode-aware)。
|
|
3975
4439
|
if (action === 'list') {
|
|
3976
4440
|
const qs = new URLSearchParams();
|
|
3977
4441
|
if (args.category)
|
|
@@ -3980,58 +4444,27 @@ async function handleCharity(args) {
|
|
|
3980
4444
|
qs.set('target_kind', String(args.target_kind));
|
|
3981
4445
|
if (args.limit)
|
|
3982
4446
|
qs.set('limit', String(args.limit));
|
|
3983
|
-
|
|
3984
|
-
const r = await fetch(PWA_API_BASE + '/wishes' + (qs.toString() ? '?' + qs : ''));
|
|
3985
|
-
return await r.json();
|
|
3986
|
-
}
|
|
3987
|
-
catch (e) {
|
|
3988
|
-
return { error: String(e.message) };
|
|
3989
|
-
}
|
|
4447
|
+
return readEndpoint('webaz_charity', '/wishes' + (qs.toString() ? '?' + qs : ''));
|
|
3990
4448
|
}
|
|
3991
4449
|
if (action === 'detail') {
|
|
3992
4450
|
if (!args.wish_id)
|
|
3993
4451
|
return { error: 'wish_id required' };
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
if (action === 'stories') {
|
|
4003
|
-
try {
|
|
4004
|
-
const r = await fetch(PWA_API_BASE + '/charity/stories');
|
|
4005
|
-
return await r.json();
|
|
4006
|
-
}
|
|
4007
|
-
catch (e) {
|
|
4008
|
-
return { error: String(e.message) };
|
|
4009
|
-
}
|
|
4010
|
-
}
|
|
4011
|
-
if (action === 'leaderboard') {
|
|
4012
|
-
try {
|
|
4013
|
-
const r = await fetch(PWA_API_BASE + '/charity/leaderboard');
|
|
4014
|
-
return await r.json();
|
|
4015
|
-
}
|
|
4016
|
-
catch (e) {
|
|
4017
|
-
return { error: String(e.message) };
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
|
-
if (action === 'fund') {
|
|
4021
|
-
try {
|
|
4022
|
-
const r = await fetch(PWA_API_BASE + '/charity/fund');
|
|
4023
|
-
return await r.json();
|
|
4024
|
-
}
|
|
4025
|
-
catch (e) {
|
|
4026
|
-
return { error: String(e.message) };
|
|
4027
|
-
}
|
|
4028
|
-
}
|
|
4452
|
+
return readEndpoint('webaz_charity', '/wishes/' + encodeURIComponent(String(args.wish_id)));
|
|
4453
|
+
}
|
|
4454
|
+
if (action === 'stories')
|
|
4455
|
+
return readEndpoint('webaz_charity', '/charity/stories');
|
|
4456
|
+
if (action === 'leaderboard')
|
|
4457
|
+
return readEndpoint('webaz_charity', '/charity/leaderboard');
|
|
4458
|
+
if (action === 'fund')
|
|
4459
|
+
return readEndpoint('webaz_charity', '/charity/fund');
|
|
4029
4460
|
const apiKey = String(args.api_key || '');
|
|
4030
4461
|
if (!apiKey)
|
|
4031
4462
|
return { error: 'api_key required for this action' };
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4463
|
+
if (toolBackend('webaz_charity') !== 'network') {
|
|
4464
|
+
const auth = requireAuth(db, apiKey);
|
|
4465
|
+
if ('error' in auth)
|
|
4466
|
+
return auth;
|
|
4467
|
+
}
|
|
4035
4468
|
if (action === 'create') {
|
|
4036
4469
|
return await pwaApi('POST', '/wishes', apiKey, {
|
|
4037
4470
|
title: args.title, content: args.content, category: args.category,
|
|
@@ -4119,9 +4552,11 @@ async function handleLike(args) {
|
|
|
4119
4552
|
const sid = String(args.shareable_id || '');
|
|
4120
4553
|
if (!apiKey || !sid)
|
|
4121
4554
|
return { error: 'api_key + shareable_id required' };
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4555
|
+
if (toolBackend('webaz_like') !== 'network') {
|
|
4556
|
+
const auth = requireAuth(db, apiKey);
|
|
4557
|
+
if ('error' in auth)
|
|
4558
|
+
return auth;
|
|
4559
|
+
}
|
|
4125
4560
|
if (action === 'toggle')
|
|
4126
4561
|
return await pwaApi('POST', '/shareables/' + encodeURIComponent(sid) + '/like', apiKey);
|
|
4127
4562
|
if (action === 'status')
|
|
@@ -4142,9 +4577,11 @@ async function handleAuction(args) {
|
|
|
4142
4577
|
const action = String(args.action || '');
|
|
4143
4578
|
if (!apiKey)
|
|
4144
4579
|
return { error: 'api_key required' };
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4580
|
+
if (toolBackend('webaz_auction') !== 'network') {
|
|
4581
|
+
const auth = requireAuth(db, apiKey);
|
|
4582
|
+
if ('error' in auth)
|
|
4583
|
+
return auth;
|
|
4584
|
+
}
|
|
4148
4585
|
switch (action) {
|
|
4149
4586
|
case 'create': return await pwaApi('POST', '/auctions', apiKey, {
|
|
4150
4587
|
title: args.title, qty: args.qty, category: args.category,
|
|
@@ -4420,126 +4857,134 @@ export async function startMCPServer() {
|
|
|
4420
4857
|
const t0 = Date.now();
|
|
4421
4858
|
let result;
|
|
4422
4859
|
try {
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
break;
|
|
4430
|
-
case 'webaz_search':
|
|
4431
|
-
result = await handleSearch(args);
|
|
4432
|
-
break;
|
|
4433
|
-
case 'webaz_verify_price':
|
|
4434
|
-
result = await handleVerifyPrice(args);
|
|
4435
|
-
break;
|
|
4436
|
-
case 'webaz_list_product':
|
|
4437
|
-
result = await handleListProduct(args);
|
|
4438
|
-
break;
|
|
4439
|
-
case 'webaz_place_order':
|
|
4440
|
-
result = await handlePlaceOrder(args);
|
|
4441
|
-
break;
|
|
4442
|
-
case 'webaz_update_order':
|
|
4443
|
-
result = await handleUpdateOrder(args);
|
|
4444
|
-
break;
|
|
4445
|
-
case 'webaz_get_status':
|
|
4446
|
-
result = await handleGetStatus(args);
|
|
4447
|
-
break;
|
|
4448
|
-
case 'webaz_feedback':
|
|
4449
|
-
result = await handleFeedback(args);
|
|
4450
|
-
break;
|
|
4451
|
-
case 'webaz_contribute':
|
|
4452
|
-
result = await handleContribute(args);
|
|
4453
|
-
break;
|
|
4454
|
-
case 'webaz_wallet':
|
|
4455
|
-
result = await handleWallet(args);
|
|
4456
|
-
break;
|
|
4457
|
-
case 'webaz_dispute':
|
|
4458
|
-
result = await handleDispute(args);
|
|
4459
|
-
break;
|
|
4460
|
-
case 'webaz_claim_verify':
|
|
4461
|
-
result = await handleClaimVerify(args);
|
|
4462
|
-
break;
|
|
4463
|
-
case 'webaz_notifications':
|
|
4464
|
-
result = handleNotifications(args);
|
|
4465
|
-
break;
|
|
4466
|
-
case 'webaz_skill':
|
|
4467
|
-
result = handleSkill(args);
|
|
4468
|
-
break;
|
|
4469
|
-
case 'webaz_skill_market':
|
|
4470
|
-
result = await handleSkillMarket(args);
|
|
4471
|
-
break;
|
|
4472
|
-
case 'webaz_secondhand':
|
|
4473
|
-
result = await handleSecondhand(args);
|
|
4474
|
-
break;
|
|
4475
|
-
case 'webaz_trial':
|
|
4476
|
-
result = await handleTrial(args);
|
|
4477
|
-
break;
|
|
4478
|
-
case 'webaz_mykey':
|
|
4479
|
-
result = handleMyKey(args);
|
|
4480
|
-
break;
|
|
4481
|
-
case 'webaz_profile':
|
|
4482
|
-
result = await handleProfile(args);
|
|
4483
|
-
break;
|
|
4484
|
-
case 'webaz_revoke_key':
|
|
4485
|
-
result = handleRevokeKey(args);
|
|
4486
|
-
break;
|
|
4487
|
-
case 'webaz_rotate_key':
|
|
4488
|
-
result = handleRotateKey(args);
|
|
4489
|
-
break;
|
|
4490
|
-
case 'webaz_referral':
|
|
4491
|
-
result = handleReferral(args);
|
|
4492
|
-
break;
|
|
4493
|
-
case 'webaz_share_link':
|
|
4494
|
-
result = handleShareLink(args);
|
|
4495
|
-
break;
|
|
4496
|
-
case 'webaz_blocklist':
|
|
4497
|
-
result = handleBlocklist(args);
|
|
4498
|
-
break;
|
|
4499
|
-
case 'webaz_follows':
|
|
4500
|
-
result = handleFollows(args);
|
|
4501
|
-
break;
|
|
4502
|
-
case 'webaz_nearby':
|
|
4503
|
-
result = handleNearby(args);
|
|
4504
|
-
break;
|
|
4505
|
-
case 'webaz_default_address':
|
|
4506
|
-
result = handleDefaultAddress(args);
|
|
4507
|
-
break;
|
|
4508
|
-
case 'webaz_shareables':
|
|
4509
|
-
result = handleShareables(args);
|
|
4510
|
-
break;
|
|
4511
|
-
case 'webaz_rfq':
|
|
4512
|
-
result = await handleRfq(args);
|
|
4513
|
-
break;
|
|
4514
|
-
case 'webaz_bid':
|
|
4515
|
-
result = await handleBid(args);
|
|
4516
|
-
break;
|
|
4517
|
-
case 'webaz_chat':
|
|
4518
|
-
result = await handleChat(args);
|
|
4519
|
-
break;
|
|
4520
|
-
case 'webaz_auto_bid':
|
|
4521
|
-
result = await handleAutoBidSkill(args);
|
|
4522
|
-
break;
|
|
4523
|
-
case 'webaz_auction':
|
|
4524
|
-
result = await handleAuction(args);
|
|
4525
|
-
break;
|
|
4526
|
-
case 'webaz_like':
|
|
4527
|
-
result = await handleLike(args);
|
|
4528
|
-
break;
|
|
4529
|
-
case 'webaz_p2p_product':
|
|
4530
|
-
result = await handleP2pProduct(args);
|
|
4531
|
-
break;
|
|
4532
|
-
case 'webaz_charity':
|
|
4533
|
-
result = await handleCharity(args);
|
|
4534
|
-
break;
|
|
4535
|
-
case 'webaz_price_history':
|
|
4536
|
-
result = await handlePriceHistory(args);
|
|
4537
|
-
break;
|
|
4538
|
-
case 'webaz_leaderboard':
|
|
4539
|
-
result = await handleLeaderboard(args);
|
|
4540
|
-
break;
|
|
4541
|
-
default: result = { error: `未知工具:${name}` };
|
|
4860
|
+
// ─── RFC-003 Batch 0 安全网:NETWORK 模式下未迁移的工具【硬失败】,不静默落本地沙盒 ───
|
|
4861
|
+
// 例外:info / register(NETWORK_SELF_AWARE)有专门 network-aware 处理,照常放行。
|
|
4862
|
+
let handled = false;
|
|
4863
|
+
if (isNetworkMode() && !NETWORK_TOOLS.has(name) && !NETWORK_SELF_AWARE.has(name)) {
|
|
4864
|
+
result = networkMigrationPending(name);
|
|
4865
|
+
handled = true;
|
|
4542
4866
|
}
|
|
4867
|
+
if (!handled)
|
|
4868
|
+
switch (name) {
|
|
4869
|
+
case 'webaz_info':
|
|
4870
|
+
result = await handleInfo();
|
|
4871
|
+
break;
|
|
4872
|
+
case 'webaz_register':
|
|
4873
|
+
result = handleRegister(args);
|
|
4874
|
+
break;
|
|
4875
|
+
case 'webaz_search':
|
|
4876
|
+
result = await handleSearch(args);
|
|
4877
|
+
break;
|
|
4878
|
+
case 'webaz_verify_price':
|
|
4879
|
+
result = await handleVerifyPrice(args);
|
|
4880
|
+
break;
|
|
4881
|
+
case 'webaz_list_product':
|
|
4882
|
+
result = await handleListProduct(args);
|
|
4883
|
+
break;
|
|
4884
|
+
case 'webaz_place_order':
|
|
4885
|
+
result = await handlePlaceOrder(args);
|
|
4886
|
+
break;
|
|
4887
|
+
case 'webaz_update_order':
|
|
4888
|
+
result = await handleUpdateOrder(args);
|
|
4889
|
+
break;
|
|
4890
|
+
case 'webaz_get_status':
|
|
4891
|
+
result = await handleGetStatus(args);
|
|
4892
|
+
break;
|
|
4893
|
+
case 'webaz_feedback':
|
|
4894
|
+
result = await handleFeedback(args);
|
|
4895
|
+
break;
|
|
4896
|
+
case 'webaz_contribute':
|
|
4897
|
+
result = await handleContribute(args);
|
|
4898
|
+
break;
|
|
4899
|
+
case 'webaz_wallet':
|
|
4900
|
+
result = await handleWallet(args);
|
|
4901
|
+
break;
|
|
4902
|
+
case 'webaz_dispute':
|
|
4903
|
+
result = await handleDispute(args);
|
|
4904
|
+
break;
|
|
4905
|
+
case 'webaz_claim_verify':
|
|
4906
|
+
result = await handleClaimVerify(args);
|
|
4907
|
+
break;
|
|
4908
|
+
case 'webaz_notifications':
|
|
4909
|
+
result = await handleNotifications(args);
|
|
4910
|
+
break;
|
|
4911
|
+
case 'webaz_skill':
|
|
4912
|
+
result = await handleSkill(args);
|
|
4913
|
+
break;
|
|
4914
|
+
case 'webaz_skill_market':
|
|
4915
|
+
result = await handleSkillMarket(args);
|
|
4916
|
+
break;
|
|
4917
|
+
case 'webaz_secondhand':
|
|
4918
|
+
result = await handleSecondhand(args);
|
|
4919
|
+
break;
|
|
4920
|
+
case 'webaz_trial':
|
|
4921
|
+
result = await handleTrial(args);
|
|
4922
|
+
break;
|
|
4923
|
+
case 'webaz_mykey':
|
|
4924
|
+
result = handleMyKey(args);
|
|
4925
|
+
break;
|
|
4926
|
+
case 'webaz_profile':
|
|
4927
|
+
result = await handleProfile(args);
|
|
4928
|
+
break;
|
|
4929
|
+
case 'webaz_revoke_key':
|
|
4930
|
+
result = handleRevokeKey(args);
|
|
4931
|
+
break;
|
|
4932
|
+
case 'webaz_rotate_key':
|
|
4933
|
+
result = handleRotateKey(args);
|
|
4934
|
+
break;
|
|
4935
|
+
case 'webaz_referral':
|
|
4936
|
+
result = await handleReferral(args);
|
|
4937
|
+
break;
|
|
4938
|
+
case 'webaz_share_link':
|
|
4939
|
+
result = await handleShareLink(args);
|
|
4940
|
+
break;
|
|
4941
|
+
case 'webaz_blocklist':
|
|
4942
|
+
result = await handleBlocklist(args);
|
|
4943
|
+
break;
|
|
4944
|
+
case 'webaz_follows':
|
|
4945
|
+
result = await handleFollows(args);
|
|
4946
|
+
break;
|
|
4947
|
+
case 'webaz_nearby':
|
|
4948
|
+
result = await handleNearby(args);
|
|
4949
|
+
break;
|
|
4950
|
+
case 'webaz_default_address':
|
|
4951
|
+
result = await handleDefaultAddress(args);
|
|
4952
|
+
break;
|
|
4953
|
+
case 'webaz_shareables':
|
|
4954
|
+
result = await handleShareables(args);
|
|
4955
|
+
break;
|
|
4956
|
+
case 'webaz_rfq':
|
|
4957
|
+
result = await handleRfq(args);
|
|
4958
|
+
break;
|
|
4959
|
+
case 'webaz_bid':
|
|
4960
|
+
result = await handleBid(args);
|
|
4961
|
+
break;
|
|
4962
|
+
case 'webaz_chat':
|
|
4963
|
+
result = await handleChat(args);
|
|
4964
|
+
break;
|
|
4965
|
+
case 'webaz_auto_bid':
|
|
4966
|
+
result = await handleAutoBidSkill(args);
|
|
4967
|
+
break;
|
|
4968
|
+
case 'webaz_auction':
|
|
4969
|
+
result = await handleAuction(args);
|
|
4970
|
+
break;
|
|
4971
|
+
case 'webaz_like':
|
|
4972
|
+
result = await handleLike(args);
|
|
4973
|
+
break;
|
|
4974
|
+
case 'webaz_p2p_product':
|
|
4975
|
+
result = await handleP2pProduct(args);
|
|
4976
|
+
break;
|
|
4977
|
+
case 'webaz_charity':
|
|
4978
|
+
result = await handleCharity(args);
|
|
4979
|
+
break;
|
|
4980
|
+
case 'webaz_price_history':
|
|
4981
|
+
result = await handlePriceHistory(args);
|
|
4982
|
+
break;
|
|
4983
|
+
case 'webaz_leaderboard':
|
|
4984
|
+
result = await handleLeaderboard(args);
|
|
4985
|
+
break;
|
|
4986
|
+
default: result = { error: `未知工具:${name}` };
|
|
4987
|
+
}
|
|
4543
4988
|
}
|
|
4544
4989
|
catch (err) {
|
|
4545
4990
|
result = { error: `执行出错:${err.message}` };
|
|
@@ -4558,7 +5003,9 @@ export async function startMCPServer() {
|
|
|
4558
5003
|
}
|
|
4559
5004
|
// RFC-003 P0: 给每个工具结果盖模式戳(诚实可见,防把 sandbox 当 live 网络)
|
|
4560
5005
|
// P3: handler 可自行预设 _mode(如 register 在 network 模式返回引导,不是 sandbox 结果)→ 不覆盖。
|
|
4561
|
-
|
|
5006
|
+
// self-aware 工具(info/register)按全局 MODE 盖戳,不按 toolBackend(它们不在 NETWORK_TOOLS 但本就网络感知),
|
|
5007
|
+
// 否则 network_readonly/network 下 info 会被误盖 sandbox 戳,与其自身 network_state 矛盾。
|
|
5008
|
+
const backend = NETWORK_SELF_AWARE.has(name) ? (isNetworkMode() ? 'network' : 'sandbox') : toolBackend(name);
|
|
4562
5009
|
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
4563
5010
|
const r = result;
|
|
4564
5011
|
if (!('_mode' in r))
|