@seasonkoh/webaz 0.1.25 → 0.1.26

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.
Files changed (40) hide show
  1. package/README.md +3 -1
  2. package/dist/layer1-agent/L1-1-mcp-server/server.js +129 -150
  3. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +9 -0
  4. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +1 -1
  5. package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
  6. package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
  7. package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
  8. package/dist/pwa/admin-bearer-auth.js +21 -0
  9. package/dist/pwa/email-delivery.js +127 -0
  10. package/dist/pwa/public/app.js +940 -245
  11. package/dist/pwa/public/i18n.js +269 -40
  12. package/dist/pwa/public/openapi.json +4 -4
  13. package/dist/pwa/public/whitepaper/en/index.html +153 -0
  14. package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
  15. package/dist/pwa/routes/admin-atomic.js +10 -4
  16. package/dist/pwa/routes/admin-moderation.js +25 -1
  17. package/dist/pwa/routes/admin-ops.js +13 -2
  18. package/dist/pwa/routes/admin-users-query.js +12 -1
  19. package/dist/pwa/routes/admin-wallet-ops.js +26 -3
  20. package/dist/pwa/routes/auction.js +4 -2
  21. package/dist/pwa/routes/auth-read.js +10 -1
  22. package/dist/pwa/routes/auth-register.js +82 -12
  23. package/dist/pwa/routes/contribution-identity.js +17 -0
  24. package/dist/pwa/routes/growth.js +1 -1
  25. package/dist/pwa/routes/orders-action.js +19 -13
  26. package/dist/pwa/routes/profile-credentials.js +7 -4
  27. package/dist/pwa/routes/profile-placement.js +7 -8
  28. package/dist/pwa/routes/promoter.js +3 -17
  29. package/dist/pwa/routes/ratings.js +64 -4
  30. package/dist/pwa/routes/recover-key.js +58 -19
  31. package/dist/pwa/routes/referral.js +4 -24
  32. package/dist/pwa/routes/share-redirects.js +4 -3
  33. package/dist/pwa/routes/shop-referral.js +6 -5
  34. package/dist/pwa/routes/shops.js +5 -2
  35. package/dist/pwa/routes/task-proposals.js +76 -0
  36. package/dist/pwa/routes/trial.js +4 -2
  37. package/dist/pwa/routes/users-public.js +2 -12
  38. package/dist/pwa/routes/wallet-read.js +1 -1
  39. package/dist/pwa/server.js +67 -9
  40. package/package.json +31 -3
@@ -43,6 +43,15 @@ const TELEMETRY_ENABLED = (process.env.WEBAZ_TELEMETRY ?? 'off').toLowerCase() =
43
43
  // P0 不迁移任何工具(NETWORK_TOOLS 为空)→ 一切仍走本地 = 零行为变化;P1/P2 逐个把工具名加入集合切到网络。
44
44
  const WEBAZ_API_URL = (process.env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
45
45
  const WEBAZ_API_KEY = process.env.WEBAZ_API_KEY ?? '';
46
+ // F6 (dogfood R2): keyed MCP handlers resolve api_key as explicit args.api_key > env WEBAZ_API_KEY >
47
+ // '' (→ the existing typed API_KEY_REQUIRED guards). Explicit ALWAYS wins; env never overrides an explicit
48
+ // key. Keyless actions (list/discover/detail/suggest/browse/get_campaign…) gate their public branches
49
+ // separately and never call this, so a configured env key does NOT change the public read boundary.
50
+ // The key is never printed/returned/logged.
51
+ export function resolveMcpApiKey(args, envKey = WEBAZ_API_KEY) {
52
+ const explicit = typeof args?.api_key === 'string' ? args.api_key.trim() : '';
53
+ return explicit || envKey;
54
+ }
46
55
  const WEBAZ_MODE_ENV = (process.env.WEBAZ_MODE ?? '').toLowerCase();
47
56
  // 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 key → network_readonly(装完即见真网络)。
48
57
  // network_readonly(L1 onboarding,2026-06-08):无 key 默认。公共读匿名打 webaz.xyz(真 catalog/协议),
@@ -403,11 +412,11 @@ Skipping is allowed but agent then carries price/stock-race risk itself.`,
403
412
  inputSchema: {
404
413
  type: 'object',
405
414
  properties: {
406
- api_key: { type: 'string', description: "Buyer's api_key" },
415
+ api_key: { type: 'string', description: "Buyer's api_key (or omit and set the WEBAZ_API_KEY env var)" },
407
416
  product_id: { type: 'string', description: 'Product ID (from webaz_search)' },
408
417
  quantity: { type: 'number', description: 'Quantity, default 1' },
409
418
  },
410
- required: ['api_key', 'product_id'],
419
+ required: ['product_id'],
411
420
  },
412
421
  },
413
422
  {
@@ -423,7 +432,7 @@ Actions: create (title/description/price) | mine | update (product_id + changed
423
432
  inputSchema: {
424
433
  type: 'object',
425
434
  properties: {
426
- api_key: { type: 'string', description: "Seller's api_key" },
435
+ api_key: { type: 'string', description: "Seller's api_key (or omit and set the WEBAZ_API_KEY env var)" },
427
436
  action: {
428
437
  type: 'string',
429
438
  enum: ['create', 'mine', 'update', 'delist', 'relist', 'trash', 'delete'],
@@ -479,7 +488,7 @@ Actions: create (title/description/price) | mine | update (product_id + changed
479
488
  description: '[S4] Product-origin claims (challengeable). E.g. {"made_in":"Kyoto JP","material":"100% cotton GOTS-cert","certs":[{"name":"GOTS","sha256":"<64-hex>"}]}. Total JSON ≤4KB; any cert sha256 must be 64-hex. Any buyer can challenge.',
480
489
  },
481
490
  },
482
- required: ['api_key'],
491
+ required: [],
483
492
  },
484
493
  },
485
494
  {
@@ -501,7 +510,7 @@ Options:
501
510
  inputSchema: {
502
511
  type: 'object',
503
512
  properties: {
504
- api_key: { type: 'string', description: "Buyer's api_key" },
513
+ api_key: { type: 'string', description: "Buyer's api_key (or omit and set the WEBAZ_API_KEY env var)" },
505
514
  product_id: { type: 'string', description: 'Product ID to buy (from webaz_search)' },
506
515
  quantity: { type: 'number', description: 'Quantity, default 1' },
507
516
  shipping_address: { type: 'string', description: 'Shipping address' },
@@ -526,7 +535,7 @@ Options:
526
535
  description: '[B5] Per-order donation pct (0 / 0.5 / 1 / 2 / 5). Computed separately + into charity_fund, posted on order complete.',
527
536
  },
528
537
  },
529
- required: ['api_key', 'product_id', 'shipping_address'],
538
+ required: ['product_id', 'shipping_address'],
530
539
  },
531
540
  },
532
541
  {
@@ -534,15 +543,15 @@ Options:
534
543
  // was ~927 chars, now ~430 chars
535
544
  description: `STATUS TRANSITIONS on an order — NOT for editing order content (price/qty/address immutable after creation). Each role can only perform their own actions.
536
545
 
537
- - **Seller**: accept (24h after payment) | ship (needs tracking, within handling time)
538
- - **Logistics**: pickup (48h after ship) | transit | deliver (needs proof description)
546
+ - **Seller**: accept (24h after payment) | ship (needs tracking/notes, within handling time) | pickup/transit/deliver ONLY when order.logistics_id is empty (Phase-1 self-fulfill; seller carries logistics responsibility) | decline (actively refuse a PAID order instead of silent timeout; requires decline_reason_code — objective codes [stock_consumed_concurrent/stale_price_snapshot/force_majeure] go to a PROVISIONAL fault you must then contest within the window, NOT auto-cleared; subjective codes [price_regret/cherry_pick/other] settle immediately as seller-fault + buyer refund) | contest_decline (open human arbitration on an objective-claimed provisional fault, within the contest window, to be cleared to no-fault; pass evidence_description — window expiry finalizes as fault)
547
+ - **Logistics**: pickup (48h after ship) | transit | deliver (needs proof description) when assigned or claiming an unassigned shipped order
539
548
  - **Buyer**: confirm (→ fund settlement) | dispute (needs reason; freezes funds → arbitration)
540
549
 
541
550
  Missing deadline → protocol auto-marks party in default.`,
542
551
  inputSchema: {
543
552
  type: 'object',
544
553
  properties: {
545
- api_key: { type: 'string', description: "Operator's api_key" },
554
+ api_key: { type: 'string', description: "Operator's api_key (or omit and set the WEBAZ_API_KEY env var)" },
546
555
  order_id: { type: 'string', description: 'Order ID' },
547
556
  action: {
548
557
  type: 'string',
@@ -560,7 +569,7 @@ Missing deadline → protocol auto-marks party in default.`,
560
569
  description: 'Evidence description (recommended for ship/pickup/deliver; required for dispute)',
561
570
  },
562
571
  },
563
- required: ['api_key', 'order_id', 'action'],
572
+ required: ['order_id', 'action'],
564
573
  },
565
574
  },
566
575
  {
@@ -570,10 +579,10 @@ Missing deadline → protocol auto-marks party in default.`,
570
579
  inputSchema: {
571
580
  type: 'object',
572
581
  properties: {
573
- api_key: { type: 'string', description: "Querier's api_key" },
582
+ api_key: { type: 'string', description: "Querier's api_key (or omit and set the WEBAZ_API_KEY env var)" },
574
583
  order_id: { type: 'string', description: 'Order ID' },
575
584
  },
576
- required: ['api_key', 'order_id'],
585
+ required: ['order_id'],
577
586
  },
578
587
  },
579
588
  {
@@ -591,14 +600,14 @@ Actions:
591
600
  inputSchema: {
592
601
  type: 'object',
593
602
  properties: {
594
- api_key: { type: 'string', description: 'Your api_key' },
603
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
595
604
  action: {
596
605
  type: 'string',
597
606
  enum: ['view', 'deposits', 'withdrawals', 'income'],
598
607
  description: 'Action type (default: view)',
599
608
  },
600
609
  },
601
- required: ['api_key'],
610
+ required: [],
602
611
  },
603
612
  },
604
613
  {
@@ -610,11 +619,11 @@ Actions:
610
619
  inputSchema: {
611
620
  type: 'object',
612
621
  properties: {
613
- api_key: { type: 'string', description: 'Your api_key' },
622
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
614
623
  unread: { type: 'boolean', description: 'Return only unread (default false)' },
615
624
  mark_read: { type: 'boolean', description: 'Auto-mark read after call (default false)' },
616
625
  },
617
- required: ['api_key'],
626
+ required: [],
618
627
  },
619
628
  },
620
629
  {
@@ -637,7 +646,7 @@ Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbi
637
646
  inputSchema: {
638
647
  type: 'object',
639
648
  properties: {
640
- api_key: { type: 'string', description: "Operator's api_key" },
649
+ api_key: { type: 'string', description: "Operator's api_key (or omit and set the WEBAZ_API_KEY env var)" },
641
650
  action: {
642
651
  type: 'string',
643
652
  enum: ['view', 'list_open', 'respond', 'add_evidence', 'arbitrate'],
@@ -668,7 +677,7 @@ Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbi
668
677
  },
669
678
  ruling_reason: { type: 'string', description: 'Ruling reason (required for arbitrate; permanently recorded on-chain)' },
670
679
  },
671
- required: ['api_key', 'action'],
680
+ required: ['action'],
672
681
  },
673
682
  },
674
683
  {
@@ -695,7 +704,7 @@ Actions:
695
704
  inputSchema: {
696
705
  type: 'object',
697
706
  properties: {
698
- api_key: { type: 'string', description: 'Your api_key' },
707
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
699
708
  action: {
700
709
  type: 'string',
701
710
  enum: ['create', 'view', 'mine', 'submit_seller_evidence', 'available', 'vote', 'eligibility', 'verifier_status', 'apply', 'withdraw_application', 'appeal'],
@@ -718,7 +727,7 @@ Actions:
718
727
  // appeal
719
728
  reason: { type: 'string', description: 'Appeal reason (required for appeal, ≤500 chars)' },
720
729
  },
721
- required: ['api_key', 'action'],
730
+ required: ['action'],
722
731
  },
723
732
  },
724
733
  {
@@ -743,7 +752,7 @@ Actions: list (no auth) | publish (seller) | subscribe / unsubscribe (buyer) | m
743
752
  inputSchema: {
744
753
  type: 'object',
745
754
  properties: {
746
- api_key: { type: 'string', description: 'Your api_key (omit for list)' },
755
+ api_key: { type: 'string', description: 'Your api_key (omit for list) (or set the WEBAZ_API_KEY env var)' },
747
756
  action: {
748
757
  type: 'string',
749
758
  enum: ['list', 'publish', 'subscribe', 'unsubscribe', 'my_skills', 'my_subs'],
@@ -807,7 +816,7 @@ Public-profile actions:
807
816
  inputSchema: {
808
817
  type: 'object',
809
818
  properties: {
810
- api_key: { type: 'string', description: 'Your api_key (required for view/add_role/switch_role/view_user; optional for public feed)' },
819
+ api_key: { type: 'string', description: 'Your api_key (required for view/add_role/switch_role/view_user; optional for public feed) (or set the WEBAZ_API_KEY env var)' },
811
820
  action: {
812
821
  type: 'string',
813
822
  enum: ['view', 'add_role', 'switch_role', 'view_user', 'feed'],
@@ -841,10 +850,10 @@ Use revoke when: PERMANENT decommission of agent/device, OR want access death NO
841
850
  inputSchema: {
842
851
  type: 'object',
843
852
  properties: {
844
- api_key: { type: 'string', description: 'Your current api_key (the one to revoke)' },
853
+ api_key: { type: 'string', description: 'Your current api_key (the one to revoke) (or set the WEBAZ_API_KEY env var)' },
845
854
  reason: { type: 'string', description: 'Optional: leaked / lost_device / rotation / unspecified' },
846
855
  },
847
- required: ['api_key'],
856
+ required: [],
848
857
  },
849
858
  },
850
859
  {
@@ -856,10 +865,10 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
856
865
  inputSchema: {
857
866
  type: 'object',
858
867
  properties: {
859
- api_key: { type: 'string', description: 'Your current api_key (will be invalidated after PWA confirm)' },
868
+ api_key: { type: 'string', description: 'Your current api_key (will be invalidated after PWA confirm) (or set the WEBAZ_API_KEY env var)' },
860
869
  reason: { type: 'string', description: 'Optional: rotation / leaked / scheduled' },
861
870
  },
862
- required: ['api_key'],
871
+ required: [],
863
872
  },
864
873
  },
865
874
  {
@@ -872,9 +881,9 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
872
881
  inputSchema: {
873
882
  type: 'object',
874
883
  properties: {
875
- api_key: { type: 'string', description: 'Your api_key' },
884
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
876
885
  },
877
- required: ['api_key'],
886
+ required: [],
878
887
  },
879
888
  },
880
889
  {
@@ -887,15 +896,15 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
887
896
  inputSchema: {
888
897
  type: 'object',
889
898
  properties: {
890
- api_key: { type: 'string', description: 'Your api_key' },
899
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
891
900
  product_id: { type: 'string', description: 'Product to promote (from webaz_search)' },
892
901
  side: {
893
902
  type: 'string',
894
- enum: ['left', 'right', 'auto'],
895
- description: 'Which side of your binary tree the new user lands. auto = weaker leg (default)',
903
+ enum: ['auto'],
904
+ description: 'Deprecated / no-op placement is always automatic (system-decided). Left/right选择已下线。',
896
905
  },
897
906
  },
898
- required: ['api_key', 'product_id'],
907
+ required: ['product_id'],
899
908
  },
900
909
  },
901
910
  {
@@ -909,12 +918,12 @@ Actions: list | block | unblock.`,
909
918
  inputSchema: {
910
919
  type: 'object',
911
920
  properties: {
912
- api_key: { type: 'string', description: 'Your api_key' },
921
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
913
922
  action: { type: 'string', enum: ['list', 'block', 'unblock'], description: 'list: my blocked users | block: add | unblock: remove' },
914
923
  user_id: { type: 'string', description: 'Target user id (required for block/unblock)' },
915
924
  reason: { type: 'string', description: 'Optional reason for block (e.g. "fake product", "abuse")' },
916
925
  },
917
- required: ['api_key', 'action'],
926
+ required: ['action'],
918
927
  },
919
928
  },
920
929
  {
@@ -923,11 +932,11 @@ Actions: list | block | unblock.`,
923
932
  inputSchema: {
924
933
  type: 'object',
925
934
  properties: {
926
- api_key: { type: 'string', description: 'Your api_key' },
935
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
927
936
  action: { type: 'string', enum: ['list', 'follow', 'unfollow', 'status'], description: 'list: my follows + followers | follow/unfollow: change relation | status: check if I follow a user' },
928
937
  user_id: { type: 'string', description: 'Target user (required for follow/unfollow/status)' },
929
938
  },
930
- required: ['api_key', 'action'],
939
+ required: ['action'],
931
940
  },
932
941
  },
933
942
  {
@@ -941,12 +950,12 @@ USE THIS for "what's popular near me / 我附近 / 同城" — geo-aggregated, n
941
950
  inputSchema: {
942
951
  type: 'object',
943
952
  properties: {
944
- api_key: { type: 'string', description: 'Your api_key' },
953
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
945
954
  action: { type: 'string', enum: ['query', 'set_location', 'clear_location'], description: 'query: get aggregated nearby activity | set_location: set your geo cell | clear_location: remove' },
946
955
  lat: { type: 'number', description: 'Latitude -90..90 (for set_location, auto-truncated to 0.1°)' },
947
956
  lng: { type: 'number', description: 'Longitude -180..180 (for set_location, auto-truncated to 0.1°)' },
948
957
  },
949
- required: ['api_key', 'action'],
958
+ required: ['action'],
950
959
  },
951
960
  },
952
961
  {
@@ -958,12 +967,12 @@ USE THIS for "what's popular near me / 我附近 / 同城" — geo-aggregated, n
958
967
  inputSchema: {
959
968
  type: 'object',
960
969
  properties: {
961
- api_key: { type: 'string', description: 'Your api_key' },
970
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
962
971
  action: { type: 'string', enum: ['read', 'set'], description: 'read: get current default | set: update' },
963
972
  text: { type: 'string', description: 'Full address as free-text string (e.g. "John Doe / 1 Test St / Singapore SG / +65 12345678"). Required for set. ≤ 200 chars.' },
964
973
  region: { type: 'string', description: 'Region tag for shipping match (e.g. "global", "china", "SG"). Optional for set. ≤ 40 chars.' },
965
974
  },
966
- required: ['api_key', 'action'],
975
+ required: ['action'],
967
976
  },
968
977
  },
969
978
  {
@@ -979,7 +988,7 @@ Actions: list_mine | add (external_url + product/anchor) | delete | by_product |
979
988
  inputSchema: {
980
989
  type: 'object',
981
990
  properties: {
982
- api_key: { type: 'string', description: 'Your api_key' },
991
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
983
992
  action: { type: 'string', enum: ['list_mine', 'add', 'delete', 'by_product', 'by_anchor'], description: 'list_mine | add (need external_url + product/anchor) | delete | by_product | by_anchor' },
984
993
  external_url: { type: 'string', description: 'For action=add' },
985
994
  title: { type: 'string', description: 'For action=add (optional)' },
@@ -988,7 +997,7 @@ Actions: list_mine | add (external_url + product/anchor) | delete | by_product |
988
997
  related_anchor: { type: 'string', description: 'For action=add or by_anchor' },
989
998
  shareable_id: { type: 'string', description: 'For action=delete' },
990
999
  },
991
- required: ['api_key', 'action'],
1000
+ required: ['action'],
992
1001
  },
993
1002
  },
994
1003
  // ── P3 RFQ / bid / chat / auto_bid(MCP 通过 HTTP 调 PWA,复用所有校验+状态机)────
@@ -1017,7 +1026,7 @@ Shipping address falls back to webaz_default_address if omitted.`,
1017
1026
  inputSchema: {
1018
1027
  type: 'object',
1019
1028
  properties: {
1020
- api_key: { type: 'string', description: 'Your api_key' },
1029
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1021
1030
  action: { type: 'string', enum: ['create', 'mine', 'browse', 'detail', 'award', 'cancel'] },
1022
1031
  // create
1023
1032
  title: { type: 'string' },
@@ -1036,7 +1045,7 @@ Shipping address falls back to webaz_default_address if omitted.`,
1036
1045
  rfq_id: { type: 'string' },
1037
1046
  bid_id: { type: 'string', description: 'Optional for award — if omitted, auto-pick current lowest bid' },
1038
1047
  },
1039
- required: ['api_key', 'action'],
1048
+ required: ['action'],
1040
1049
  },
1041
1050
  },
1042
1051
  {
@@ -1050,7 +1059,7 @@ Actions: submit (rfq_id + price + qty_offered + fulfillment_type; optional eta/n
1050
1059
  inputSchema: {
1051
1060
  type: 'object',
1052
1061
  properties: {
1053
- api_key: { type: 'string', description: 'Seller api_key' },
1062
+ api_key: { type: 'string', description: 'Seller api_key (or set the WEBAZ_API_KEY env var)' },
1054
1063
  action: { type: 'string', enum: ['submit', 'patch', 'cancel', 'list_mine'] },
1055
1064
  rfq_id: { type: 'string' },
1056
1065
  bid_id: { type: 'string' },
@@ -1061,7 +1070,7 @@ Actions: submit (rfq_id + price + qty_offered + fulfillment_type; optional eta/n
1061
1070
  note: { type: 'string' },
1062
1071
  offer_id: { type: 'string', description: 'Optional; reference existing offer' },
1063
1072
  },
1064
- required: ['api_key', 'action'],
1073
+ required: ['action'],
1065
1074
  },
1066
1075
  },
1067
1076
  {
@@ -1081,7 +1090,7 @@ webaz_blocklist hides from search but does NOT auto-silence existing convs (busi
1081
1090
  inputSchema: {
1082
1091
  type: 'object',
1083
1092
  properties: {
1084
- api_key: { type: 'string' },
1093
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1085
1094
  action: { type: 'string', enum: ['start', 'list', 'read', 'send', 'mark_read', 'block'] },
1086
1095
  kind: { type: 'string', enum: ['order', 'rfq', 'listing_qa'] },
1087
1096
  context_id: { type: 'string' },
@@ -1089,7 +1098,7 @@ webaz_blocklist hides from search but does NOT auto-silence existing convs (busi
1089
1098
  conversation_id: { type: 'string' },
1090
1099
  body: { type: 'string', description: 'Message body for send action (≤2000 chars)' },
1091
1100
  },
1092
- required: ['api_key', 'action'],
1101
+ required: ['action'],
1093
1102
  },
1094
1103
  },
1095
1104
  {
@@ -1129,7 +1138,7 @@ Actions (15):
1129
1138
  inputSchema: {
1130
1139
  type: 'object',
1131
1140
  properties: {
1132
- api_key: { type: 'string' },
1141
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1133
1142
  action: { type: 'string', enum: ['list', 'detail', 'create', 'claim', 'proof', 'confirm', 'disclose', 'cancel', 'me', 'stories', 'leaderboard', 'repay', 'repay_respond', 'donate', 'fund'] },
1134
1143
  wish_id: { type: 'string' },
1135
1144
  fulfillment_id: { type: 'string' },
@@ -1165,7 +1174,7 @@ Actions: create (seller, needs title/price/stock + content_hash sha256 + content
1165
1174
  inputSchema: {
1166
1175
  type: 'object',
1167
1176
  properties: {
1168
- api_key: { type: 'string' },
1177
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1169
1178
  action: { type: 'string', enum: ['create', 'list', 'detail', 'patch'] },
1170
1179
  product_id: { type: 'string' },
1171
1180
  title: { type: 'string' }, price: { type: 'number' }, stock: { type: 'number' },
@@ -1190,11 +1199,11 @@ Actions: toggle (same endpoint; 2nd call auto-unlikes) | status (my like status
1190
1199
  inputSchema: {
1191
1200
  type: 'object',
1192
1201
  properties: {
1193
- api_key: { type: 'string' },
1202
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1194
1203
  action: { type: 'string', enum: ['toggle', 'status'] },
1195
1204
  shareable_id: { type: 'string' },
1196
1205
  },
1197
- required: ['api_key', 'action', 'shareable_id'],
1206
+ required: ['action', 'shareable_id'],
1198
1207
  },
1199
1208
  },
1200
1209
  {
@@ -1239,7 +1248,7 @@ Actions:
1239
1248
  inputSchema: {
1240
1249
  type: 'object',
1241
1250
  properties: {
1242
- api_key: { type: 'string' },
1251
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1243
1252
  action: { type: 'string', enum: ['create', 'browse', 'mine', 'detail', 'bid', 'cancel'] },
1244
1253
  title: { type: 'string' },
1245
1254
  qty: { type: 'number' },
@@ -1253,7 +1262,7 @@ Actions:
1253
1262
  auction_id: { type: 'string' },
1254
1263
  price: { type: 'number' },
1255
1264
  },
1256
- required: ['api_key', 'action'],
1265
+ required: ['action'],
1257
1266
  },
1258
1267
  },
1259
1268
  {
@@ -1267,7 +1276,7 @@ Actions: get | set (categories[] / regions[] / max_eta_h / bid_strategy) | disab
1267
1276
  inputSchema: {
1268
1277
  type: 'object',
1269
1278
  properties: {
1270
- api_key: { type: 'string' },
1279
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1271
1280
  action: { type: 'string', enum: ['get', 'set', 'disable'] },
1272
1281
  categories: { type: 'array', items: { type: 'string' } },
1273
1282
  regions: { type: 'array', items: { type: 'string' } },
@@ -1280,7 +1289,7 @@ Actions: get | set (categories[] / regions[] / max_eta_h / bid_strategy) | disab
1280
1289
  cooldown_min: { type: 'number' },
1281
1290
  enabled: { type: 'boolean' },
1282
1291
  },
1283
- required: ['api_key', 'action'],
1292
+ required: ['action'],
1284
1293
  },
1285
1294
  },
1286
1295
  {
@@ -1300,7 +1309,7 @@ Actions: list (no auth, filters: kind/billing/query) | detail (public, no conten
1300
1309
  inputSchema: {
1301
1310
  type: 'object',
1302
1311
  properties: {
1303
- api_key: { type: 'string', description: 'Your api_key (omit for list/detail)' },
1312
+ api_key: { type: 'string', description: 'Your api_key (omit for list/detail) (or set the WEBAZ_API_KEY env var)' },
1304
1313
  action: {
1305
1314
  type: 'string',
1306
1315
  enum: ['list', 'detail', 'publish', 'update', 'delist', 'resubmit', 'purchase', 'read', 'my_skills', 'library'],
@@ -1337,7 +1346,7 @@ Enums: **category** phone/computer/appliance/furniture/clothing/book/toy/sports/
1337
1346
  inputSchema: {
1338
1347
  type: 'object',
1339
1348
  properties: {
1340
- api_key: { type: 'string', description: 'Your api_key (omit for browse/detail)' },
1349
+ api_key: { type: 'string', description: 'Your api_key (omit for browse/detail) (or set the WEBAZ_API_KEY env var)' },
1341
1350
  action: { type: 'string', enum: ['browse', 'detail', 'publish', 'update', 'mine', 'buy'], description: 'Action to execute' },
1342
1351
  item_id: { type: 'string', description: 'Item ID (required for detail/update/buy)' },
1343
1352
  // publish / update
@@ -1386,7 +1395,7 @@ Seller actions:
1386
1395
  inputSchema: {
1387
1396
  type: 'object',
1388
1397
  properties: {
1389
- api_key: { type: 'string', description: 'Your api_key (omit for get_campaign)' },
1398
+ api_key: { type: 'string', description: 'Your api_key (omit for get_campaign) (or set the WEBAZ_API_KEY env var)' },
1390
1399
  action: { type: 'string', enum: ['get_campaign', 'apply', 'link_note', 'my_claims', 'create_campaign', 'cancel_campaign', 'my_campaigns', 'campaign_claims'], description: 'Action to execute' },
1391
1400
  product_id: { type: 'string', description: 'Product ID (required for get_campaign/apply/create_campaign/cancel_campaign)' },
1392
1401
  claim_id: { type: 'string', description: 'Claim ID (required for link_note)' },
@@ -1415,7 +1424,7 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1415
1424
  type: 'object',
1416
1425
  properties: {
1417
1426
  action: { type: 'string', enum: ['submit', 'my', 'get'], description: 'submit (default) | my | get' },
1418
- api_key: { type: 'string', description: "User's api_key (real person required)" },
1427
+ api_key: { type: 'string', description: "User's api_key (real person required; or set the WEBAZ_API_KEY env var)" },
1419
1428
  type: { type: 'string', enum: ['ux_issue', 'bug', 'proposal'], description: 'submit: kind of feedback' },
1420
1429
  area: { type: 'string', description: 'submit: which feature, e.g. search / order / dispute' },
1421
1430
  severity: { type: 'string', enum: ['low', 'annoying', 'blocking'], description: 'submit: for ux_issue/bug' },
@@ -1423,7 +1432,7 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1423
1432
  text: { type: 'string', description: 'submit: the feedback / idea (≥5 chars)' },
1424
1433
  feedback_id: { type: 'string', description: 'get: the feedback id' },
1425
1434
  },
1426
- required: ['api_key'],
1435
+ required: [],
1427
1436
  },
1428
1437
  },
1429
1438
  {
@@ -1446,7 +1455,7 @@ Coordinates + records only — NO merge/reward; acceptance (done) = human mainta
1446
1455
  type: 'object',
1447
1456
  properties: {
1448
1457
  action: { type: 'string', enum: ['list_open', 'detail', 'suggest', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | detail | suggest | claim | submit | status | profile' },
1449
- api_key: { type: 'string', description: 'claim/submit/status/profile: your api_key (accountable identity). NOT needed for list_open/detail/suggest.' },
1458
+ api_key: { type: 'string', description: 'claim/submit/status/profile: your api_key (accountable identity). NOT needed for list_open/detail/suggest. (or set the WEBAZ_API_KEY env var)' },
1450
1459
  task_id: { type: 'string', description: 'detail / claim / submit: the task id' },
1451
1460
  area: { type: 'string', description: 'list_open: area filter / suggest: suggested area (e.g. search / docs / mcp)' },
1452
1461
  risk_level: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'list_open: optional risk filter' },
@@ -1473,7 +1482,7 @@ Coordinates + records only — NO merge/reward; acceptance (done) = human mainta
1473
1482
  // RFC-004: webaz_feedback — agent-native "use → build" 反馈(双模;仅 NETWORK 能送达)
1474
1483
  async function handleFeedback(args) {
1475
1484
  const action = args.action || 'submit';
1476
- const apiKey = args.api_key;
1485
+ const apiKey = resolveMcpApiKey(args);
1477
1486
  if (!apiKey)
1478
1487
  return { error: 'api_key required' };
1479
1488
  if (toolBackend('webaz_feedback') !== 'network') {
@@ -1531,7 +1540,7 @@ function buildContributeHandoff(cct, taskId) {
1531
1540
  // 端口 #329/#331);claim/submit/status/profile 需 key(真实可问责身份,走受 #330 守卫的 member 端口)。
1532
1541
  export async function handleContribute(args) {
1533
1542
  const action = args.action || 'list_open';
1534
- const apiKey = args.api_key;
1543
+ const apiKey = resolveMcpApiKey(args);
1535
1544
  if (toolBackend('webaz_contribute') !== 'network') {
1536
1545
  return {
1537
1546
  _mode: 'sandbox',
@@ -2138,11 +2147,11 @@ async function handleVerifyPrice(args) {
2138
2147
  if (toolBackend('webaz_verify_price') === 'network') {
2139
2148
  return apiCall('/api/verify-price', {
2140
2149
  method: 'POST',
2141
- apiKey: args.api_key,
2150
+ apiKey: resolveMcpApiKey(args),
2142
2151
  body: { product_id: args.product_id, quantity: Number(args.quantity ?? 1) },
2143
2152
  });
2144
2153
  }
2145
- const auth = requireAuth(db, args.api_key);
2154
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2146
2155
  if ('error' in auth)
2147
2156
  return auth;
2148
2157
  const { user } = auth;
@@ -2188,7 +2197,7 @@ async function handleVerifyPrice(args) {
2188
2197
  async function handleListProduct(args) {
2189
2198
  // Wave 3 audit P0: 加 action 分发 — agent 卖家能完整管理目录(不止 create)
2190
2199
  const action = args.action || 'create';
2191
- const apiKey = args.api_key;
2200
+ const apiKey = resolveMcpApiKey(args);
2192
2201
  if (!apiKey)
2193
2202
  return { error: 'api_key required' };
2194
2203
  // RFC-003 P2b: NETWORK 模式 — 卖家目录管理全部转发生产端点(单一真相源)
@@ -2366,9 +2375,9 @@ async function handlePlaceOrder(args) {
2366
2375
  body.shipping_address = args.shipping_address;
2367
2376
  if (args.donation_pct != null)
2368
2377
  body.donation_pct = args.donation_pct;
2369
- return apiCall('/api/orders', { method: 'POST', apiKey: args.api_key, body });
2378
+ return apiCall('/api/orders', { method: 'POST', apiKey: resolveMcpApiKey(args), body });
2370
2379
  }
2371
- const auth = requireAuth(db, args.api_key);
2380
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2372
2381
  if ('error' in auth)
2373
2382
  return auth;
2374
2383
  const { user } = auth;
@@ -2535,7 +2544,7 @@ async function handleUpdateOrder(args) {
2535
2544
  return { error: 'order_id and action required' };
2536
2545
  return apiCall(`/api/orders/${encodeURIComponent(orderId)}/action`, {
2537
2546
  method: 'POST',
2538
- apiKey: args.api_key,
2547
+ apiKey: resolveMcpApiKey(args),
2539
2548
  body: {
2540
2549
  action,
2541
2550
  notes: args.notes ?? '',
@@ -2545,7 +2554,7 @@ async function handleUpdateOrder(args) {
2545
2554
  },
2546
2555
  });
2547
2556
  }
2548
- const auth = requireAuth(db, args.api_key);
2557
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2549
2558
  if ('error' in auth)
2550
2559
  return auth;
2551
2560
  const { user } = auth;
@@ -2559,7 +2568,7 @@ async function handleUpdateOrder(args) {
2559
2568
  // agent-native 协议要求"哪个接口进结果一致"。MCP confirm 不再自己结算,
2560
2569
  // 走 PWA /api/orders/:id/action 的 settleOrder + settleCommission(authoritative)。
2561
2570
  if (action === 'confirm') {
2562
- const apiKey = args.api_key;
2571
+ const apiKey = resolveMcpApiKey(args);
2563
2572
  const result = await pwaApi('POST', `/orders/${encodeURIComponent(orderId)}/action`, apiKey, {
2564
2573
  action: 'confirm',
2565
2574
  notes,
@@ -2668,9 +2677,9 @@ async function handleGetStatus(args) {
2668
2677
  const orderId = args.order_id;
2669
2678
  if (!orderId)
2670
2679
  return { error: 'order_id required' };
2671
- return apiCall(`/api/orders/${encodeURIComponent(orderId)}`, { apiKey: args.api_key });
2680
+ return apiCall(`/api/orders/${encodeURIComponent(orderId)}`, { apiKey: resolveMcpApiKey(args) });
2672
2681
  }
2673
- const auth = requireAuth(db, args.api_key);
2682
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2674
2683
  if ('error' in auth)
2675
2684
  return auth;
2676
2685
  const statusInfo = getOrderStatus(db, args.order_id);
@@ -2707,7 +2716,7 @@ async function handleGetStatus(args) {
2707
2716
  async function handleWallet(args) {
2708
2717
  // Wave 3 audit P0: 加 action 分发 — agent 能查充值/提现/收入历史(写操作仍 UI-only 走 2FA)
2709
2718
  const action = args.action || 'view';
2710
- const apiKey = args.api_key;
2719
+ const apiKey = resolveMcpApiKey(args);
2711
2720
  if (!apiKey)
2712
2721
  return { error: 'api_key required' };
2713
2722
  // RFC-003 Batch 4:NETWORK 模式 → webaz.xyz 真网络【只读】(Bearer api_key)。
@@ -2775,14 +2784,14 @@ async function handleWallet(args) {
2775
2784
  async function handleNotifications(args) {
2776
2785
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络通知端点(Bearer api_key);SANDBOX 走本地。
2777
2786
  if (toolBackend('webaz_notifications') === 'network') {
2778
- const apiKey = String(args.api_key || '');
2787
+ const apiKey = resolveMcpApiKey(args);
2779
2788
  if (!apiKey)
2780
2789
  return { error: 'api_key required' };
2781
2790
  if (args.mark_read)
2782
2791
  await apiCall('/api/notifications/read', { method: 'POST', apiKey });
2783
2792
  return await apiCall('/api/notifications' + (args.unread === true ? '?unread=1' : ''), { apiKey });
2784
2793
  }
2785
- const auth = requireAuth(db, args.api_key);
2794
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2786
2795
  if ('error' in auth)
2787
2796
  return auth;
2788
2797
  const { user } = auth;
@@ -2806,7 +2815,7 @@ async function handleNotifications(args) {
2806
2815
  }
2807
2816
  // ─── 争议处理 ─────────────────────────────────────────────────
2808
2817
  async function handleDispute(args) {
2809
- const apiKey = args.api_key;
2818
+ const apiKey = resolveMcpApiKey(args);
2810
2819
  if (!apiKey)
2811
2820
  return { error: 'api_key required' };
2812
2821
  const action = args.action;
@@ -2969,7 +2978,7 @@ async function handleDispute(args) {
2969
2978
  }
2970
2979
  // ─── 索赔验证(claim-verification)处理 — Wave 6 新增 ────────────
2971
2980
  async function handleClaimVerify(args) {
2972
- const apiKey = args.api_key;
2981
+ const apiKey = resolveMcpApiKey(args);
2973
2982
  if (!apiKey)
2974
2983
  return { error: 'api_key required' };
2975
2984
  const action = String(args.action || '');
@@ -3057,7 +3066,7 @@ async function handleSkill(args) {
3057
3066
  const action = args.action;
3058
3067
  // RFC-003 Batch 3:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地引擎。
3059
3068
  if (toolBackend('webaz_skill') === 'network') {
3060
- const apiKey = String(args.api_key || '');
3069
+ const apiKey = resolveMcpApiKey(args);
3061
3070
  if (action === 'list') {
3062
3071
  const qs = new URLSearchParams();
3063
3072
  if (args.skill_type)
@@ -3094,8 +3103,8 @@ async function handleSkill(args) {
3094
3103
  // ── 浏览 Skill 市场 ────────────────────────────────────────
3095
3104
  if (action === 'list') {
3096
3105
  let userId;
3097
- if (args.api_key) {
3098
- const a = requireAuth(db, args.api_key);
3106
+ if (resolveMcpApiKey(args)) {
3107
+ const a = requireAuth(db, resolveMcpApiKey(args));
3099
3108
  if (!('error' in a))
3100
3109
  userId = a.user.id;
3101
3110
  }
@@ -3112,7 +3121,7 @@ async function handleSkill(args) {
3112
3121
  };
3113
3122
  }
3114
3123
  // 以下操作需要身份验证
3115
- const auth = requireAuth(db, args.api_key);
3124
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3116
3125
  if ('error' in auth)
3117
3126
  return auth;
3118
3127
  const { user } = auth;
@@ -3253,7 +3262,7 @@ function handleMyKey(args) {
3253
3262
  }
3254
3263
  async function handleProfile(args) {
3255
3264
  const action = args.action;
3256
- const apiKey = String(args.api_key || '');
3265
+ const apiKey = resolveMcpApiKey(args);
3257
3266
  // RFC-003 Batch 1:NETWORK 模式 → 全部 action 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3258
3267
  if (toolBackend('webaz_profile') === 'network') {
3259
3268
  if (action === 'view_user') {
@@ -3350,7 +3359,7 @@ async function handleProfile(args) {
3350
3359
  function handleRevokeKey(args) {
3351
3360
  // RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 撤销指引。
3352
3361
  if (toolBackend('webaz_revoke_key') === 'network') {
3353
- const apiKey = String(args.api_key || '');
3362
+ const apiKey = resolveMcpApiKey(args);
3354
3363
  const reason = (args.reason || 'unspecified').trim().slice(0, 100);
3355
3364
  return {
3356
3365
  _mode: 'network',
@@ -3368,7 +3377,7 @@ function handleRevokeKey(args) {
3368
3377
  },
3369
3378
  };
3370
3379
  }
3371
- const auth = requireAuth(db, args.api_key);
3380
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3372
3381
  if ('error' in auth)
3373
3382
  return auth;
3374
3383
  const { user } = auth;
@@ -3392,7 +3401,7 @@ function handleRevokeKey(args) {
3392
3401
  function handleRotateKey(args) {
3393
3402
  // RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 轮换指引。
3394
3403
  if (toolBackend('webaz_rotate_key') === 'network') {
3395
- const apiKey = String(args.api_key || '');
3404
+ const apiKey = resolveMcpApiKey(args);
3396
3405
  const reason = (args.reason || 'rotation').trim().slice(0, 100);
3397
3406
  return {
3398
3407
  _mode: 'network',
@@ -3409,7 +3418,7 @@ function handleRotateKey(args) {
3409
3418
  },
3410
3419
  };
3411
3420
  }
3412
- const auth = requireAuth(db, args.api_key);
3421
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3413
3422
  if ('error' in auth)
3414
3423
  return auth;
3415
3424
  const { user } = auth;
@@ -3433,12 +3442,12 @@ function handleRotateKey(args) {
3433
3442
  async function handleReferral(args) {
3434
3443
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络聚合(Bearer api_key);SANDBOX 走本地。
3435
3444
  if (toolBackend('webaz_referral') === 'network') {
3436
- const apiKey = String(args.api_key || '');
3445
+ const apiKey = resolveMcpApiKey(args);
3437
3446
  if (!apiKey)
3438
3447
  return { error: 'api_key required' };
3439
3448
  return await apiCall('/api/referral/me', { apiKey });
3440
3449
  }
3441
- const auth = requireAuth(db, args.api_key);
3450
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3442
3451
  if ('error' in auth)
3443
3452
  return auth;
3444
3453
  const { user } = auth;
@@ -3488,10 +3497,8 @@ async function handleReferral(args) {
3488
3497
  grand_total: byLevel[1].total + byLevel[2].total + byLevel[3].total,
3489
3498
  },
3490
3499
  binary: {
3491
- left_invite_link: permaCode ? `/i/${permaCode}-L` : null,
3492
- right_invite_link: permaCode ? `/i/${permaCode}-R` : null,
3493
- platform_left: permaCode ? `/?placement=${permaCode}&side=left` : null, // 仅 PV 条线
3494
- platform_right: permaCode ? `/?placement=${permaCode}&side=right` : null,
3500
+ // pre-public 去左右码:只暴露唯一的推荐码;放置侧别由系统自动决定(无 left/right 选择)
3501
+ referral_link: permaCode ? `/i/${permaCode}` : null,
3495
3502
  total_left_pv: Number(me?.total_left_pv ?? 0),
3496
3503
  total_right_pv: Number(me?.total_right_pv ?? 0),
3497
3504
  pair_volume: pair,
@@ -3540,23 +3547,21 @@ async function handleReferral(args) {
3540
3547
  async function handleShareLink(args) {
3541
3548
  // RFC-003 #1122:NETWORK 模式 → 调 webaz.xyz 的 /api/share-link(服务端同款计算);SANDBOX 走本地。
3542
3549
  if (toolBackend('webaz_share_link') === 'network') {
3543
- const apiKey = String(args.api_key || '');
3550
+ const apiKey = resolveMcpApiKey(args);
3544
3551
  if (!apiKey)
3545
3552
  return { error: 'api_key required' };
3546
3553
  if (!args.product_id)
3547
3554
  return { error: 'product_id required' };
3555
+ // pre-public 去左右码:不再向 /api/share-link 转发 side(放置永远自动)
3548
3556
  const qs = new URLSearchParams({ product_id: String(args.product_id) });
3549
- if (args.side)
3550
- qs.set('side', String(args.side));
3551
3557
  return await apiCall('/api/share-link?' + qs.toString(), { apiKey });
3552
3558
  }
3553
- const auth = requireAuth(db, args.api_key);
3559
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3554
3560
  if ('error' in auth)
3555
3561
  return auth;
3556
3562
  const { user } = auth;
3557
3563
  const userId = user.id;
3558
3564
  const productId = args.product_id;
3559
- const sideArg = args.side || 'auto';
3560
3565
  // RFC-002 §3.5 valuation-layer gate — share_link generation requires opt-in
3561
3566
  const optIn = db.prepare("SELECT rewards_opted_in FROM users WHERE id = ?").get(userId)?.rewards_opted_in ?? 0;
3562
3567
  if (optIn !== 1) {
@@ -3589,32 +3594,7 @@ async function handleShareLink(args) {
3589
3594
  const product = db.prepare("SELECT id, title, price, commission_rate FROM products WHERE id = ? AND status='active'").get(productId);
3590
3595
  if (!product)
3591
3596
  return { error: '商品不存在或已下架' };
3592
- let side = 'right';
3593
- if (sideArg === 'left' || sideArg === 'right') {
3594
- side = sideArg;
3595
- }
3596
- else {
3597
- // auto = 与 PWA pickPreferredSide 对齐:尊重 placement_pref(team_count | pv_count)
3598
- // 老版只看 total_left_pv vs total_right_pv,team_count 用户被错算
3599
- const u = db.prepare("SELECT placement_pref, total_left_pv, total_right_pv, left_count, right_count FROM users WHERE id = ?")
3600
- .get(userId);
3601
- const pref = u?.placement_pref || 'team_count';
3602
- if (pref === 'pv_count') {
3603
- const since = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' ');
3604
- const w = db.prepare(`SELECT COALESCE(SUM(consumed_left_pv),0) AS l, COALESCE(SUM(consumed_right_pv),0) AS r
3605
- FROM binary_score_records WHERE user_id = ? AND created_at >= ?`)
3606
- .get(userId, since);
3607
- const leftPv = Number(u?.total_left_pv ?? 0) + Number(w.l);
3608
- const rightPv = Number(u?.total_right_pv ?? 0) + Number(w.r);
3609
- side = leftPv <= rightPv ? 'left' : 'right';
3610
- }
3611
- else {
3612
- // team_count: 整棵子树人数(增量维护的 left_count/right_count,与 PWA pickPreferredSide 对齐)。
3613
- // 2026-06-04 修:旧版沿单条脊链数(countLeg)名实不符 → 选边失真。分享链接的 side 会被注册时
3614
- // 当 placement_side 显式采用,必须与 PWA joinPowerLeg 用同一指标,否则两路径不一致。
3615
- side = (Number(u?.left_count ?? 0) <= Number(u?.right_count ?? 0)) ? 'left' : 'right';
3616
- }
3617
- }
3597
+ // pre-public 去左右码:分享链接不再携带 side(放置侧别由注册时系统自动决定)
3618
3598
  const completed = db.prepare("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(userId).n;
3619
3599
  const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
3620
3600
  const canL1 = override === 1 || (override === 0 && completed > 0);
@@ -3623,13 +3603,12 @@ async function handleShareLink(args) {
3623
3603
  const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
3624
3604
  if (!permaCode)
3625
3605
  return { error: 'permanent_code_missing — cannot build a share link; re-register or contact support', error_code: 'PERMANENT_CODE_MISSING' };
3626
- const link = `/?ref=${permaCode}&side=${side}#order-product/${productId}`;
3606
+ const link = `/?ref=${permaCode}#order-product/${productId}`;
3627
3607
  return {
3628
3608
  product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
3629
3609
  share_link: link,
3630
3610
  full_url_hint: 'Prepend webaz.xyz (production) or http://localhost:3000 (local) to get the absolute URL',
3631
- side,
3632
- binary_explanation: `New user via this link → placed in your ${side === 'left' ? '🔵 left' : '🟢 right'} subtree (tail anchor)`,
3611
+ placement_note: 'New user via this link → placement is recorded automatically by the system (no left/right choice).',
3633
3612
  commission_eligibility: canL1
3634
3613
  ? `You will earn 3-tier commission: L1=${(rate * 0.70 * 100).toFixed(1)}% L2=${(rate * 0.20 * 100).toFixed(1)}% L3=${(rate * 0.10 * 100).toFixed(1)}% of sale price`
3635
3614
  : 'You are NOT verified yet (need 1 completed purchase). 3-tier commission will be skipped, but points-matching still builds.',
@@ -3640,7 +3619,7 @@ async function handleShareLink(args) {
3640
3619
  async function handleBlocklist(args) {
3641
3620
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3642
3621
  if (toolBackend('webaz_blocklist') === 'network') {
3643
- const apiKey = String(args.api_key || '');
3622
+ const apiKey = resolveMcpApiKey(args);
3644
3623
  if (!apiKey)
3645
3624
  return { error: 'api_key required' };
3646
3625
  const act = String(args.action || '');
@@ -3655,7 +3634,7 @@ async function handleBlocklist(args) {
3655
3634
  return await apiCall('/api/blocklist/' + uid, { method: 'DELETE', apiKey });
3656
3635
  return { error: `unknown action: ${act}` };
3657
3636
  }
3658
- const auth = requireAuth(db, args.api_key);
3637
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3659
3638
  if ('error' in auth)
3660
3639
  return auth;
3661
3640
  const { user } = auth;
@@ -3694,7 +3673,7 @@ async function handleBlocklist(args) {
3694
3673
  async function handleFollows(args) {
3695
3674
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3696
3675
  if (toolBackend('webaz_follows') === 'network') {
3697
- const apiKey = String(args.api_key || '');
3676
+ const apiKey = resolveMcpApiKey(args);
3698
3677
  if (!apiKey)
3699
3678
  return { error: 'api_key required' };
3700
3679
  const act = String(args.action || '');
@@ -3711,7 +3690,7 @@ async function handleFollows(args) {
3711
3690
  return await apiCall('/api/follows/' + uid + '/status', { apiKey });
3712
3691
  return { error: `unknown action: ${act}` };
3713
3692
  }
3714
- const auth = requireAuth(db, args.api_key);
3693
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3715
3694
  if ('error' in auth)
3716
3695
  return auth;
3717
3696
  const { user } = auth;
@@ -3760,7 +3739,7 @@ async function handleNearby(args) {
3760
3739
  const action = String(args.action || '');
3761
3740
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3762
3741
  if (toolBackend('webaz_nearby') === 'network') {
3763
- const apiKey = String(args.api_key || '');
3742
+ const apiKey = resolveMcpApiKey(args);
3764
3743
  if (!apiKey)
3765
3744
  return { error: 'api_key required' };
3766
3745
  if (action === 'set_location')
@@ -3778,7 +3757,7 @@ async function handleNearby(args) {
3778
3757
  }
3779
3758
  return { error: `unknown action: ${action}` };
3780
3759
  }
3781
- const auth = requireAuth(db, args.api_key);
3760
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3782
3761
  if ('error' in auth)
3783
3762
  return auth;
3784
3763
  const { user } = auth;
@@ -3829,7 +3808,7 @@ async function handleNearby(args) {
3829
3808
  async function handleDefaultAddress(args) {
3830
3809
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3831
3810
  if (toolBackend('webaz_default_address') === 'network') {
3832
- const apiKey = String(args.api_key || '');
3811
+ const apiKey = resolveMcpApiKey(args);
3833
3812
  if (!apiKey)
3834
3813
  return { error: 'api_key required' };
3835
3814
  const act = String(args.action || '');
@@ -3848,7 +3827,7 @@ async function handleDefaultAddress(args) {
3848
3827
  }
3849
3828
  return { error: `unknown action: ${act}` };
3850
3829
  }
3851
- const auth = requireAuth(db, args.api_key);
3830
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3852
3831
  if ('error' in auth)
3853
3832
  return auth;
3854
3833
  const { user } = auth;
@@ -3889,7 +3868,7 @@ async function handleShareables(args) {
3889
3868
  const action = String(args.action || '');
3890
3869
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3891
3870
  if (toolBackend('webaz_shareables') === 'network') {
3892
- const apiKey = String(args.api_key || '');
3871
+ const apiKey = resolveMcpApiKey(args);
3893
3872
  if (!apiKey)
3894
3873
  return { error: 'api_key required' };
3895
3874
  if (action === 'list_mine')
@@ -3917,7 +3896,7 @@ async function handleShareables(args) {
3917
3896
  }
3918
3897
  return { error: `unknown action: ${action}` };
3919
3898
  }
3920
- const auth = requireAuth(db, args.api_key);
3899
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3921
3900
  if ('error' in auth)
3922
3901
  return auth;
3923
3902
  const { user } = auth;
@@ -4073,7 +4052,7 @@ async function pwaApi(method, path, apiKey, body) {
4073
4052
  }
4074
4053
  }
4075
4054
  async function handleSecondhand(args) {
4076
- const apiKey = String(args.api_key || '');
4055
+ const apiKey = resolveMcpApiKey(args);
4077
4056
  const action = String(args.action || '');
4078
4057
  const isPublic = action === 'browse' || action === 'detail';
4079
4058
  if (!isPublic) {
@@ -4144,7 +4123,7 @@ async function handleSecondhand(args) {
4144
4123
  }
4145
4124
  }
4146
4125
  async function handleTrial(args) {
4147
- const apiKey = String(args.api_key || '');
4126
+ const apiKey = resolveMcpApiKey(args);
4148
4127
  const action = String(args.action || '');
4149
4128
  const isPublic = action === 'get_campaign';
4150
4129
  if (!isPublic) {
@@ -4197,7 +4176,7 @@ async function handleTrial(args) {
4197
4176
  }
4198
4177
  }
4199
4178
  async function handleSkillMarket(args) {
4200
- const apiKey = String(args.api_key || '');
4179
+ const apiKey = resolveMcpApiKey(args);
4201
4180
  const action = String(args.action || '');
4202
4181
  const isPublic = action === 'list' || action === 'detail';
4203
4182
  if (!isPublic) {
@@ -4270,7 +4249,7 @@ async function handleSkillMarket(args) {
4270
4249
  }
4271
4250
  }
4272
4251
  async function handleRfq(args) {
4273
- const apiKey = String(args.api_key || '');
4252
+ const apiKey = resolveMcpApiKey(args);
4274
4253
  const action = String(args.action || '');
4275
4254
  if (!apiKey)
4276
4255
  return { error: 'api_key required' };
@@ -4319,7 +4298,7 @@ async function handleRfq(args) {
4319
4298
  }
4320
4299
  }
4321
4300
  async function handleBid(args) {
4322
- const apiKey = String(args.api_key || '');
4301
+ const apiKey = resolveMcpApiKey(args);
4323
4302
  const action = String(args.action || '');
4324
4303
  if (!apiKey)
4325
4304
  return { error: 'api_key required' };
@@ -4393,7 +4372,7 @@ async function handleBid(args) {
4393
4372
  }
4394
4373
  }
4395
4374
  async function handleChat(args) {
4396
- const apiKey = String(args.api_key || '');
4375
+ const apiKey = resolveMcpApiKey(args);
4397
4376
  const action = String(args.action || '');
4398
4377
  if (!apiKey)
4399
4378
  return { error: 'api_key required' };
@@ -4435,7 +4414,7 @@ async function handleChat(args) {
4435
4414
  }
4436
4415
  }
4437
4416
  async function handleAutoBidSkill(args) {
4438
- const apiKey = String(args.api_key || '');
4417
+ const apiKey = resolveMcpApiKey(args);
4439
4418
  const action = String(args.action || '');
4440
4419
  if (!apiKey)
4441
4420
  return { error: 'api_key required' };
@@ -4558,7 +4537,7 @@ async function handleCharity(args) {
4558
4537
  return readEndpoint('webaz_charity', '/charity/leaderboard');
4559
4538
  if (action === 'fund')
4560
4539
  return readEndpoint('webaz_charity', '/charity/fund');
4561
- const apiKey = String(args.api_key || '');
4540
+ const apiKey = resolveMcpApiKey(args);
4562
4541
  if (!apiKey)
4563
4542
  return { error: 'api_key required for this action' };
4564
4543
  if (toolBackend('webaz_charity') !== 'network') {
@@ -4621,7 +4600,7 @@ async function handleP2pProduct(args) {
4621
4600
  return { error: String(e.message) };
4622
4601
  }
4623
4602
  }
4624
- const apiKey = String(args.api_key || '');
4603
+ const apiKey = resolveMcpApiKey(args);
4625
4604
  if (!apiKey)
4626
4605
  return { error: 'api_key required for create/patch' };
4627
4606
  const auth = requireAuth(db, apiKey);
@@ -4648,7 +4627,7 @@ async function handleP2pProduct(args) {
4648
4627
  return { error: `unknown action: ${action}` };
4649
4628
  }
4650
4629
  async function handleLike(args) {
4651
- const apiKey = String(args.api_key || '');
4630
+ const apiKey = resolveMcpApiKey(args);
4652
4631
  const action = String(args.action || '');
4653
4632
  const sid = String(args.shareable_id || '');
4654
4633
  if (!apiKey || !sid)
@@ -4674,7 +4653,7 @@ async function handleLeaderboard(args) {
4674
4653
  return readEndpoint('webaz_leaderboard', '/leaderboard?kind=' + kind + '&limit=' + limit);
4675
4654
  }
4676
4655
  async function handleAuction(args) {
4677
- const apiKey = String(args.api_key || '');
4656
+ const apiKey = resolveMcpApiKey(args);
4678
4657
  const action = String(args.action || '');
4679
4658
  if (!apiKey)
4680
4659
  return { error: 'api_key required' };
@@ -5132,7 +5111,7 @@ function addHours(date, hours) {
5132
5111
  function recordToolCall(tool, args, result, latencyMs) {
5133
5112
  let userId = null;
5134
5113
  try {
5135
- const apiKey = args.api_key;
5114
+ const apiKey = resolveMcpApiKey(args);
5136
5115
  if (apiKey) {
5137
5116
  const row = db.prepare('SELECT id FROM users WHERE api_key = ?').get(apiKey);
5138
5117
  if (row)