@tbox.cn/app-module-member 0.1.0 → 0.8.6

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 (65) hide show
  1. package/AGENTS.md +16 -0
  2. package/README.md +99 -11
  3. package/dist/chunk-KEBILLQ5.js +771 -0
  4. package/dist/chunk-T4SYPQA5.js +1565 -0
  5. package/dist/client/index.d.ts +11 -2
  6. package/dist/client/index.js +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.js +14 -4
  9. package/dist/login-mascot-5D4X6YZ7.jpg +0 -0
  10. package/dist/member-QFCIYILN.css +1734 -0
  11. package/dist/server/index.d.ts +928 -41
  12. package/dist/server/index.js +13 -3
  13. package/package.json +25 -16
  14. package/skills/member/SKILL.md +141 -0
  15. package/skills/member/references/enrollment.md +34 -0
  16. package/skills/member/references/points-and-card.md +24 -0
  17. package/src/client/assets/login-mascot.jpg +0 -0
  18. package/src/client/assets.d.ts +5 -0
  19. package/src/client/cards/index.ts +10 -0
  20. package/src/client/cards/member-card-summary/index.tsx +375 -0
  21. package/src/client/cards/member-enrollment-cta/index.tsx +61 -0
  22. package/src/client/cards/member-enrollment-form/index.tsx +181 -0
  23. package/src/client/cards/member-enrollment-status/index.tsx +65 -0
  24. package/src/client/cards/member-info/index.tsx +60 -0
  25. package/src/client/cards/member-login-cta/index.tsx +81 -0
  26. package/src/client/cards/member-point-usage-list/index.tsx +53 -0
  27. package/src/client/cards/view.ts +39 -0
  28. package/src/client/components/ImageWithFallback.tsx +38 -0
  29. package/src/client/index.ts +21 -4
  30. package/src/client/styles/member.css +1734 -0
  31. package/src/client/types/css.d.ts +2 -0
  32. package/src/server/action.ts +348 -0
  33. package/src/server/cards/index.ts +31 -0
  34. package/src/server/cards/member-card-summary/meta.ts +42 -0
  35. package/src/server/cards/member-card-summary/samples.ts +272 -0
  36. package/src/server/cards/member-enrollment-cta/meta.ts +19 -0
  37. package/src/server/cards/member-enrollment-form/meta.ts +26 -0
  38. package/src/server/cards/member-enrollment-status/meta.ts +18 -0
  39. package/src/server/cards/member-info/meta.ts +19 -0
  40. package/src/server/cards/member-login-cta/meta.ts +16 -0
  41. package/src/server/cards/member-point-usage-list/meta.ts +19 -0
  42. package/src/server/handler.ts +170 -16
  43. package/src/server/index.ts +136 -24
  44. package/src/server/ports.ts +66 -0
  45. package/src/server/service.ts +188 -36
  46. package/src/server/status.ts +87 -0
  47. package/src/server/subject-binding.ts +19 -0
  48. package/src/server/tool.ts +514 -19
  49. package/src/server/util.ts +4 -0
  50. package/tbox.module.json +124 -0
  51. package/tests/cards-render.test.tsx +526 -0
  52. package/tests/enrollment.test.ts +121 -0
  53. package/tests/ports-resolve.test.ts +95 -0
  54. package/tests/preference-memory.test.ts +86 -0
  55. package/tests/samples.test.ts +61 -0
  56. package/tests/service.test.ts +597 -36
  57. package/tests/skills.test.ts +65 -0
  58. package/tests/status.test.ts +127 -0
  59. package/tests/view.test.ts +33 -0
  60. package/tsup.config.ts +9 -1
  61. package/dist/chunk-F6FHBH3N.js +0 -160
  62. package/dist/chunk-OXXT6F5B.js +0 -33
  63. package/src/client/cards/points/index.tsx +0 -19
  64. package/src/server/cards/points/meta.ts +0 -22
  65. package/tbox.component.json +0 -20
@@ -0,0 +1,1565 @@
1
+ // src/server/index.ts
2
+ import { loadModuleSkills, withCardEmissionHint, withEarlyExecution } from "@tbox.cn/app-sdk/server";
3
+
4
+ // src/server/cards/member-info/meta.ts
5
+ import { memberInfoCardDataSchema } from "@tbox.cn/app-contracts-mall";
6
+ var meta = {
7
+ cardType: "member-info",
8
+ dataSchema: memberInfoCardDataSchema,
9
+ displayName: "\u4F1A\u5458\u4FE1\u606F",
10
+ description: "\u4F1A\u5458\u8D26\u6237\u72B6\u6001\u4E0E\u4E2A\u4EBA\u8D44\u6599",
11
+ sampleData: {
12
+ userId: "u_1001",
13
+ authState: "authenticated",
14
+ state: "active",
15
+ displayName: "\u793A\u4F8B\u4F1A\u5458",
16
+ labels: [{ labelRef: "member", name: "\u4F1A\u5458" }]
17
+ },
18
+ schemaVersion: 1
19
+ };
20
+ var meta_default = meta;
21
+
22
+ // src/server/cards/member-card-summary/meta.ts
23
+ import { memberCardSummaryCardDataSchema } from "@tbox.cn/app-contracts-mall";
24
+
25
+ // src/server/cards/member-card-summary/samples.ts
26
+ function cardFace(from, to) {
27
+ const marks = '<rect x="58" y="52" width="210" height="42" rx="8" fill="%23000000" opacity="0.14"/><rect x="58" y="130" width="132" height="26" rx="6" fill="%23000000" opacity="0.10"/>';
28
+ return `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="672" height="347"><defs><linearGradient id="f" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="%23${from}"/><stop offset="1" stop-color="%23${to}"/></linearGradient></defs><rect width="672" height="347" fill="url(%23f)"/><circle cx="548" cy="60" r="150" fill="%23ffffff" opacity="0.16"/>` + marks + "</svg>";
29
+ }
30
+ var FACE = {
31
+ blue: cardFace("dce8fb", "aecdf5"),
32
+ silver: cardFace("eef1f7", "c4cddf"),
33
+ gold: cardFace("f9ecca", "e3c283"),
34
+ black: cardFace("1b1e26", "4a412c")
35
+ };
36
+ var POINTS_RULE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="750" height="1600"><rect width="750" height="1600" fill="%23fffdf8"/><rect x="60" y="80" width="380" height="44" rx="10" fill="%23e2c78f"/><rect x="60" y="180" width="630" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="230" width="560" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="280" width="610" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="700" width="630" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="1500" width="400" height="24" rx="8" fill="%23ece7dd"/></svg>';
37
+ var ICON_COLORS = {
38
+ blue: { fg: "254077", bg: "dce8fb" },
39
+ silver: { fg: "303443", bg: "e8ecf3" },
40
+ gold: { fg: "683d08", bg: "f3e3b8" },
41
+ black: { fg: "e9d7ac", bg: "2a2d35" }
42
+ };
43
+ function benefitIcon(variant, benefitRef) {
44
+ const { fg, bg } = ICON_COLORS[variant];
45
+ let hash = 0;
46
+ for (const ch of benefitRef) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
47
+ const shape = hash % 4;
48
+ const drawn = shape === 0 ? `<circle cx="24" cy="24" r="10" fill="%23${fg}"/>` : shape === 1 ? `<circle cx="24" cy="24" r="10" fill="none" stroke="%23${fg}" stroke-width="5"/>` : shape === 2 ? `<rect x="15" y="15" width="18" height="18" rx="4" fill="%23${fg}"/>` : `<path d="M24 12 L36 34 L12 34 Z" fill="%23${fg}"/>`;
49
+ return `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><rect width="48" height="48" rx="12" fill="%23${bg}"/>${drawn}</svg>`;
50
+ }
51
+ function benefitsOf(variant) {
52
+ const BASE_NAMES = [
53
+ { ref: "benefit:points", name: "\u6D88\u8D39\u79EF\u5206" },
54
+ { ref: "benefit:gifts", name: "\u79EF\u5206\u6362\u793C" },
55
+ { ref: "benefit:events", name: "\u4F1A\u5458\u6D3B\u52A8" },
56
+ { ref: "benefit:express", name: "\u987A\u4E3085\u6298" },
57
+ { ref: "benefit:points-parking", name: "\u79EF\u5206\u5151\u505C\u8F66" },
58
+ { ref: "benefit:storage", name: "\u7269\u54C1\u5B58\u50A8" },
59
+ { ref: "benefit:care", name: "\u6696\u5FC3\u5B88\u62A4" },
60
+ { ref: "benefit:pet", name: "\u840C\u5BA0\u9A7F\u7AD9" },
61
+ { ref: "benefit:service", name: "\u4E13\u5C5E\u5BA2\u670D" },
62
+ { ref: "benefit:umbrella", name: "\u96E8\u4F1E\u79DF\u501F" },
63
+ { ref: "benefit:wheelchair", name: "\u8F6E\u6905\u79DF\u501F" },
64
+ { ref: "benefit:stroller", name: "\u5A74\u513F\u8F66\u79DF\u501F" },
65
+ { ref: "benefit:birthday", name: "\u751F\u65E5\u793C\u9047" },
66
+ { ref: "benefit:wifi", name: "\u514D\u8D39WIFI" }
67
+ ];
68
+ const items = BASE_NAMES.map((b) => ({
69
+ benefitRef: b.ref,
70
+ name: b.name,
71
+ iconUrl: benefitIcon(variant, b.ref)
72
+ }));
73
+ if (variant === "gold" || variant === "black") {
74
+ return [
75
+ {
76
+ benefitRef: "benefit:parking",
77
+ name: "\u514D\u8D39\u505C\u8F66",
78
+ iconUrl: benefitIcon(variant, "benefit:parking"),
79
+ ...variant === "gold" ? { description: "\u53EF\u514D1\u5C0F\u65F6" } : { description: "\u53EF\u514D2\u5C0F\u65F6" }
80
+ },
81
+ ...items
82
+ ];
83
+ }
84
+ return items;
85
+ }
86
+ var PROGRESS_SHORT = {
87
+ daySpentCents: 12345,
88
+ yearSpentCents: 5e5,
89
+ percent: 25,
90
+ message: "\u4ECA\u65E5\u5DF2\u6D88\u8D39123.45\u5143"
91
+ };
92
+ var samples = [
93
+ {
94
+ label: "\u5361\u9762\xB7\u91D1\u5361",
95
+ note: "\u57FA\u51C6\u5F62\u6001\uFF1A\u5361\u540D\u4E0E\u300C\u6211\u7684\u79EF\u5206\u300D\u7531\u5361\u56FE\u81EA\u5E26\uFF0C\u524D\u7AEF\u53EA\u53E0\u79EF\u5206\u6570\u3002\u5E26 pointsRuleImageUrl \u65F6\u89E6\u53D1\u5668\u662F\u76D6\u5728\u5361\u56FE\u81EA\u5370\u6309\u94AE\u4E0A\u7684\u900F\u660E\u70ED\u533A\uFF08\u89C6\u89C9\u4E0A\u770B\u4E0D\u51FA\uFF0C\u5F39\u5C42\u6001\u9700\u624B\u52A8\u70B9\u5B83\uFF09",
96
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
97
+ data: {
98
+ userId: "u_1001",
99
+ cardRef: "card:u_1001",
100
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
101
+ status: "active",
102
+ availablePoints: 860,
103
+ cardImageUrl: FACE.gold,
104
+ pointsRuleImageUrl: POINTS_RULE_IMAGE,
105
+ presentation: { variant: "gold", labelsEmbedded: true },
106
+ progress: PROGRESS_SHORT,
107
+ benefits: benefitsOf("gold")
108
+ }
109
+ },
110
+ {
111
+ label: "\u5361\u9762\xB7\u84DD\u5361",
112
+ note: "variant=blue\uFF08\u5165\u95E8\u7B49\u7EA7\uFF09\u2014\u2014\u53E0\u5B57\u5BF9\u6BD4\u5EA6\u4E4B\u5916\uFF0C\u6743\u76CA 14 \u9879\u4E14\u65E0\u300C\u514D\u8D39\u505C\u8F66\u300D\uFF0C\u4E0E\u91D1/\u9ED1\u7684 15 \u9879\u5BF9\u7167",
113
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
114
+ data: {
115
+ userId: "u_1001",
116
+ cardRef: "card:u_1001",
117
+ cardDisplay: "\u60A6\u4EAB\u5361",
118
+ status: "active",
119
+ availablePoints: 120,
120
+ cardImageUrl: FACE.blue,
121
+ presentation: { variant: "blue", labelsEmbedded: true },
122
+ progress: { daySpentCents: 0, yearSpentCents: 8800, percent: 4, message: "\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D3988.00\u5143" },
123
+ benefits: benefitsOf("blue")
124
+ }
125
+ },
126
+ {
127
+ label: "\u5361\u9762\xB7\u94F6\u5361",
128
+ note: "variant=silver\u2014\u2014\u6D45\u5E95\u6700\u5BB9\u6613\u538B\u4E0D\u4F4F\u767D\u5B57\uFF1B\u6743\u76CA\u540C\u4E3A 14 \u9879\uFF0C\u56FE\u6807\u662F\u94F6\u5361\u914D\u8272\u7248",
129
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
130
+ data: {
131
+ userId: "u_1001",
132
+ cardRef: "card:u_1001",
133
+ cardDisplay: "\u94F6\u5361\u4F1A\u5458",
134
+ status: "active",
135
+ availablePoints: 3400,
136
+ cardImageUrl: FACE.silver,
137
+ presentation: { variant: "silver", labelsEmbedded: true },
138
+ progress: { daySpentCents: 4500, yearSpentCents: 12e4, percent: 48, message: "\u4ECA\u65E5\u5DF2\u6D88\u8D3945.00\u5143\uFF0C\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D391200.00\u5143" },
139
+ benefits: benefitsOf("silver")
140
+ }
141
+ },
142
+ {
143
+ label: "\u5361\u9762\xB7\u9ED1\u91D1\u5361",
144
+ note: "variant=black\uFF08\u6700\u9AD8\u7B49\u7EA7\uFF09\u2014\u2014\u91D1\u8272\u63CF\u8FB9\u4E0E\u6DF1\u5E95\u7684\u5C42\u6B21\uFF1B15 \u9879\u6743\u76CA\uFF0C\u514D\u8D39\u505C\u8F66\u5E26\u300C\u53EF\u514D2\u5C0F\u65F6\u300D\u8BF4\u660E",
145
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
146
+ data: {
147
+ userId: "u_1001",
148
+ cardRef: "card:u_1001",
149
+ cardDisplay: "\u9ED1\u91D1\u4F1A\u5458",
150
+ status: "active",
151
+ availablePoints: 128600,
152
+ cardImageUrl: FACE.black,
153
+ presentation: { variant: "black", labelsEmbedded: true },
154
+ progress: { daySpentCents: 98800, yearSpentCents: 24e5, percent: 100, message: "\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D3924000.00\u5143\uFF0C\u5DF2\u8FBE\u6700\u9AD8\u7B49\u7EA7" },
155
+ benefits: benefitsOf("black")
156
+ }
157
+ },
158
+ {
159
+ label: "\u5361\u9762\xB7\u5361\u56FE\u7F3A\u5931",
160
+ note: "presentation \u5728\u4F46 cardImageUrl \u7F3A\u7701\u2014\u2014\u9000\u5230\u6E10\u53D8\u515C\u5E95\u5E95\u7EB9\uFF0C\u53E0\u5B57\u6392\u5E03\u5E94\u4E0D\u53D8",
161
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
162
+ data: {
163
+ userId: "u_1001",
164
+ cardRef: "card:u_1001",
165
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
166
+ status: "active",
167
+ availablePoints: 860,
168
+ presentation: { variant: "gold", labelsEmbedded: true },
169
+ progress: PROGRESS_SHORT,
170
+ benefits: benefitsOf("gold")
171
+ }
172
+ },
173
+ {
174
+ label: "\u5361\u9762\xB7\u5916\u94FE\u52A0\u8F7D\u5931\u8D25",
175
+ note: "\u7EBF\u4E0A\u5361\u56FE 404 / \u65AD\u7F51\u7684\u5B9E\u51B5\u2014\u2014ImageWithFallback \u5360\u4F4D\u4E0D\u5E94\u628A\u5361\u9762\u6491\u53D8\u5F62",
176
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
177
+ data: {
178
+ userId: "u_1001",
179
+ cardRef: "card:u_1001",
180
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
181
+ status: "active",
182
+ availablePoints: 860,
183
+ cardImageUrl: "https://assets.example.com/member/gold-card.png",
184
+ presentation: { variant: "gold", labelsEmbedded: true },
185
+ progress: PROGRESS_SHORT,
186
+ benefits: benefitsOf("gold")
187
+ }
188
+ },
189
+ {
190
+ label: "\u5361\u9762\xB7\u8FDB\u5EA6\u957F\u6587\u6848",
191
+ note: "progress.message \u8D85 24 \u5B57\u89E6\u53D1 --scroll \u8DD1\u9A6C\u706F\uFF1Bpercent=0 \u65F6\u8FDB\u5EA6\u6761\u5E94\u53EF\u89C1\u8F68\u9053",
192
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
193
+ data: {
194
+ userId: "u_1001",
195
+ cardRef: "card:u_1001",
196
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
197
+ status: "active",
198
+ availablePoints: 860,
199
+ cardImageUrl: FACE.gold,
200
+ presentation: { variant: "gold", labelsEmbedded: true },
201
+ progress: {
202
+ daySpentCents: 0,
203
+ yearSpentCents: 0,
204
+ percent: 0,
205
+ message: "\u4ECA\u5E74\u8FD8\u6CA1\u6709\u6D88\u8D39\u8BB0\u5F55\uFF0C\u7D2F\u8BA1\u6D88\u8D39\u6EE15000.00\u5143\u53EF\u5347\u7EA7\u4E3A\u94F6\u5361\u4F1A\u5458"
206
+ },
207
+ benefits: benefitsOf("gold")
208
+ }
209
+ },
210
+ {
211
+ label: "\u5361\u9762\xB7\u65E0\u8FDB\u5EA6\u65E0\u6743\u76CA",
212
+ note: "progress \u4E0E benefits \u540C\u65F6\u7F3A\u7701\u2014\u2014\u53EA\u5269\u5361\u9762 + \u504F\u597D\u5F15\u5BFC\uFF0C\u68C0\u67E5\u5E95\u90E8\u6536\u53E3",
213
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
214
+ data: {
215
+ userId: "u_1001",
216
+ cardRef: "card:u_1001",
217
+ cardDisplay: "\u60A6\u4EAB\u5361",
218
+ status: "active",
219
+ availablePoints: 0,
220
+ cardImageUrl: FACE.blue,
221
+ presentation: { variant: "blue", labelsEmbedded: true }
222
+ }
223
+ },
224
+ {
225
+ label: "\u5361\u9762\xB7\u6743\u76CA\u56FE\u6807\u5931\u8D25",
226
+ note: "iconUrl \u52A0\u8F7D\u5931\u8D25 \u2192 \u6309\u6743\u76CA\u540D\u5339\u914D\u7684\u515C\u5E95\u56FE\u6807\uFF08\u505C\u8F66/\u79EF\u5206/\u6D3B\u52A8/\u5176\u5B83\u56DB\u6761\u5206\u652F\uFF09",
227
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
228
+ data: {
229
+ userId: "u_1001",
230
+ cardRef: "card:u_1001",
231
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
232
+ status: "active",
233
+ availablePoints: 860,
234
+ cardImageUrl: FACE.gold,
235
+ presentation: { variant: "gold", labelsEmbedded: true },
236
+ progress: PROGRESS_SHORT,
237
+ benefits: [
238
+ { benefitRef: "b1", name: "\u514D\u8D39\u505C\u8F66", iconUrl: "https://assets.example.com/x.png" },
239
+ { benefitRef: "b2", name: "\u6D88\u8D39\u79EF\u5206", iconUrl: "https://assets.example.com/x.png" },
240
+ { benefitRef: "b3", name: "\u4F1A\u5458\u6D3B\u52A8", iconUrl: "https://assets.example.com/x.png" },
241
+ { benefitRef: "b4", name: "\u751F\u65E5\u793C\u9047", iconUrl: "https://assets.example.com/x.png" }
242
+ ]
243
+ }
244
+ },
245
+ {
246
+ label: "\u5361\u9762\xB7\u5355\u6761\u504F\u597D",
247
+ note: "preferencePrompts \u663E\u5F0F\u7ED9\u4E00\u9879\u2014\u2014\u7F3A\u7701\u65F6\u7EC4\u4EF6\u515C\u5E95\u6210 contracts \u56FA\u5B9A\u4E09\u9879\uFF0C\u5355\u9879\u65F6\u770B\u6309\u94AE\u662F\u5426\u88AB\u62C9\u4F38",
248
+ group: "\u504F\u597D\u5F15\u5BFC\u5206\u652F",
249
+ data: {
250
+ userId: "u_1001",
251
+ cardRef: "card:u_1001",
252
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
253
+ status: "active",
254
+ availablePoints: 860,
255
+ cardImageUrl: FACE.gold,
256
+ presentation: { variant: "gold", labelsEmbedded: true },
257
+ progress: PROGRESS_SHORT,
258
+ benefits: benefitsOf("gold"),
259
+ preferencePrompts: [{ value: "\u6211\u5E38\u6765\u5403\u7F8E\u98DF", query: "\u63A8\u8350\u503C\u5F97\u53BB\u7684\u9910\u996E\u7F8E\u98DF\u95E8\u5E97" }]
260
+ }
261
+ },
262
+ {
263
+ label: "\u5361\u9762\xB7\u5386\u53F2\u5361",
264
+ note: "isHistory\u2014\u2014\u504F\u597D\u5F15\u5BFC\u6309\u94AE\u5168\u7981\u7528\uFF08\u70B9\u4E86\u4E5F\u4E0D\u4F1A\u53D1\u6D88\u606F\uFF09\uFF0C\u5361\u9762\u4E0E\u6743\u76CA\u533A\u7167\u5E38\u6E32\u67D3",
265
+ group: "\u504F\u597D\u5F15\u5BFC\u5206\u652F",
266
+ data: {
267
+ userId: "u_1001",
268
+ cardRef: "card:u_1001",
269
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
270
+ status: "active",
271
+ availablePoints: 860,
272
+ cardImageUrl: FACE.gold,
273
+ presentation: { variant: "gold", labelsEmbedded: true },
274
+ progress: PROGRESS_SHORT,
275
+ benefits: benefitsOf("gold")
276
+ },
277
+ isHistory: true
278
+ }
279
+ ];
280
+
281
+ // src/server/cards/member-card-summary/meta.ts
282
+ var meta2 = {
283
+ cardType: "member-card-summary",
284
+ dataSchema: memberCardSummaryCardDataSchema,
285
+ displayName: "\u4F1A\u5458\u5361\u9762",
286
+ description: "\u4F1A\u5458\u5361\u4FE1\u606F\uFF08\u771F\u5B9E\u5361\u540D\u3001\u52A8\u6001\u5361\u9762\u3001\u79EF\u5206\u4E0E\u6D88\u8D39\u8FDB\u5EA6\u6458\u8981\uFF09",
287
+ sampleData: {
288
+ userId: "u_1001",
289
+ cardRef: "card:u_1001",
290
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
291
+ status: "active",
292
+ availablePoints: 860,
293
+ cardImageUrl: "https://assets.example.com/member/gold-card.png",
294
+ pointsRuleImageUrl: "https://assets.example.com/member/points-rule.jpg",
295
+ presentation: { variant: "gold", labelsEmbedded: true },
296
+ progress: {
297
+ daySpentCents: 12345,
298
+ yearSpentCents: 5e5,
299
+ percent: 25,
300
+ message: "\u4ECA\u65E5\u5DF2\u6D88\u8D39123.45\u5143\uFF0C\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D395000.00\u5143"
301
+ },
302
+ benefits: [
303
+ { benefitRef: "benefit:parking", name: "\u514D\u8D39\u505C\u8F66", iconUrl: "https://assets.example.com/member/parking.png" },
304
+ { benefitRef: "benefit:points", name: "\u6D88\u8D39\u79EF\u5206", iconUrl: "https://assets.example.com/member/points.png" },
305
+ { benefitRef: "benefit:gifts", name: "\u79EF\u5206\u6362\u793C", iconUrl: "https://assets.example.com/member/gifts.png" },
306
+ { benefitRef: "benefit:events", name: "\u4F1A\u5458\u6D3B\u52A8", iconUrl: "https://assets.example.com/member/events.png" }
307
+ ],
308
+ preferencePrompts: [
309
+ { value: "\u6211\u5E38\u6765\u4E70\u8863\u670D", query: "\u6211\u5E38\u6765\u4E70\u8863\u670D" },
310
+ { value: "\u6211\u5E38\u5E26\u5A03\u6765\u73A9", query: "\u6211\u5E38\u5E26\u5A03\u6765\u73A9" },
311
+ { value: "\u6211\u5E38\u6765\u5403\u7F8E\u98DF", query: "\u6211\u5E38\u6765\u5403\u7F8E\u98DF" }
312
+ ]
313
+ },
314
+ samples,
315
+ schemaVersion: 5,
316
+ migrate: (oldData) => oldData
317
+ };
318
+ var meta_default2 = meta2;
319
+
320
+ // src/server/cards/member-point-usage-list/meta.ts
321
+ import { memberPointUsageListCardDataSchema } from "@tbox.cn/app-contracts-mall";
322
+ var meta3 = {
323
+ cardType: "member-point-usage-list",
324
+ dataSchema: memberPointUsageListCardDataSchema,
325
+ displayName: "\u79EF\u5206\u4F59\u989D\u4E0E\u6D41\u6C34",
326
+ description: "\u4F1A\u5458\u79EF\u5206\u4F59\u989D\u3001\u5382\u5546\u4F1A\u5458\u6807\u7B7E\u4E0E\u53D8\u52A8\u6D41\u6C34\uFF08\u5438\u6536\u6559\u5B66 points \u5361\uFF09",
327
+ sampleData: {
328
+ userId: "u_1001",
329
+ balance: 860,
330
+ membershipLabel: "\u60A6\u4EAB\u5361",
331
+ items: [{ usageRecordRef: "r1", title: "\u6D88\u8D39\u5956\u52B1", amount: "+100", usedAt: "2026-08-13T00:00:00Z" }],
332
+ total: 1
333
+ },
334
+ schemaVersion: 1
335
+ };
336
+ var meta_default3 = meta3;
337
+
338
+ // src/server/cards/member-enrollment-form/meta.ts
339
+ import { memberEnrollmentFormCardDataSchema } from "@tbox.cn/app-contracts-mall";
340
+ var meta4 = {
341
+ cardType: "member-enrollment-form",
342
+ dataSchema: memberEnrollmentFormCardDataSchema,
343
+ displayName: "\u5165\u4F1A\u8868\u5355",
344
+ description: "\u4F1A\u5458\u5165\u4F1A\u8868\u5355\uFF08\u652F\u4ED8\u5B9D\u6388\u6743\u9884\u586B + \u624B\u52A8\u964D\u7EA7\uFF1B\u52A8\u4F5C member.request-alipay-authorization / member.enroll\uFF09",
345
+ sampleData: {
346
+ userId: "u_1001",
347
+ state: "not_enrolled",
348
+ formVersion: "v2026.08",
349
+ fields: [
350
+ { fieldId: "mobile", label: "\u624B\u673A\u53F7", type: "tel", required: true, placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7" },
351
+ { fieldId: "displayName", label: "\u59D3\u540D", type: "text", required: false }
352
+ ],
353
+ consents: [
354
+ { consentId: "privacy", version: "1", title: "\u9690\u79C1\u653F\u7B56", required: true },
355
+ { consentId: "membership-terms", version: "1", title: "\u4F1A\u5458\u670D\u52A1\u6761\u6B3E", required: false }
356
+ ],
357
+ authorization: { status: "required" }
358
+ },
359
+ schemaVersion: 2
360
+ };
361
+ var meta_default4 = meta4;
362
+
363
+ // src/server/cards/member-enrollment-status/meta.ts
364
+ import { memberEnrollmentStatusCardDataSchema } from "@tbox.cn/app-contracts-mall";
365
+ var meta5 = {
366
+ cardType: "member-enrollment-status",
367
+ dataSchema: memberEnrollmentStatusCardDataSchema,
368
+ displayName: "\u5165\u4F1A\u72B6\u6001",
369
+ description: "\u5165\u4F1A\u7ED3\u679C\u72B6\u6001\uFF08pending \u6001\u9644\u663E\u5F0F\u5237\u65B0\u6309\u94AE\uFF0C\u52A8\u4F5C member.query-enrollment-status\uFF09",
370
+ sampleData: {
371
+ userId: "u_1001",
372
+ attemptRef: "att_1",
373
+ state: "pending_confirmation",
374
+ message: "\u5382\u5546\u786E\u8BA4\u4E2D\uFF0C\u53EF\u7A0D\u540E\u518D\u5237\u65B0"
375
+ },
376
+ schemaVersion: 2
377
+ };
378
+ var meta_default5 = meta5;
379
+
380
+ // src/server/cards/member-enrollment-cta/meta.ts
381
+ import { memberEnrollmentCtaCardDataSchema } from "@tbox.cn/app-contracts-mall";
382
+ var meta6 = {
383
+ cardType: "member-enrollment-cta",
384
+ dataSchema: memberEnrollmentCtaCardDataSchema,
385
+ displayName: "\u5F00\u5361\u5165\u4F1A",
386
+ description: "\u5F00\u5361\u5165\u4F1A\u5F15\u5BFC\u5361\u2014\u2014\u70B9\u51FB\u540E\u8DF3\u8F6C alipays \u94FE\u63A5\u5B8C\u6210\u5165\u4F1A",
387
+ sampleData: {
388
+ userId: "u_1001",
389
+ authState: "authenticated",
390
+ state: "not_enrolled",
391
+ enrollUrl: "alipays://platformapi/startapp?appId=demo&page=v/index/index",
392
+ title: "\u5165\u4F1A\u540E\u7EE7\u7EED"
393
+ },
394
+ schemaVersion: 2
395
+ };
396
+ var meta_default6 = meta6;
397
+
398
+ // src/server/cards/member-login-cta/meta.ts
399
+ import { memberLoginCtaCardDataSchema } from "@tbox.cn/app-contracts-mall";
400
+ var meta7 = {
401
+ cardType: "member-login-cta",
402
+ dataSchema: memberLoginCtaCardDataSchema,
403
+ displayName: "\u767B\u5F55\u5F15\u5BFC",
404
+ description: "\u672A\u767B\u5F55\u5F15\u5BFC\u5361\u2014\u2014\u70B9\u51FB\u8DF3\u8F6C\u5546\u573A\u5C0F\u7A0B\u5E8F\u767B\u5F55\u9875\uFF0C\u767B\u5F55\u540E\u56DE\u5230\u667A\u80FD\u4F53\u7EE7\u7EED",
405
+ sampleData: {
406
+ loginUrl: "alipays://platformapi/startapp?appId=demo&page=v/login/index",
407
+ title: "\u767B\u5F55\u540E\u7EE7\u7EED"
408
+ },
409
+ schemaVersion: 1
410
+ };
411
+ var meta_default7 = meta7;
412
+
413
+ // src/server/tool.ts
414
+ import { createTool } from "@tbox.cn/app-sdk/server";
415
+ import { requireToolRequestContext } from "@tbox.cn/app-sdk/server";
416
+ import { MEMBER_PREFERENCE_PROMPTS } from "@tbox.cn/app-contracts-mall";
417
+ import { classifyMemberOutcome as classifyMemberOutcome2, memberGateCardOf } from "@tbox.cn/app-contracts-mall";
418
+
419
+ // src/server/status.ts
420
+ import { classifyMemberOutcome } from "@tbox.cn/app-contracts-mall";
421
+
422
+ // src/server/ports.ts
423
+ import {
424
+ MEMBER_ACCOUNT_SERVICE,
425
+ MEMBER_BENEFITS_SERVICE,
426
+ MEMBER_CARD_SERVICE,
427
+ MEMBER_ENROLLMENT_SERVICE,
428
+ MEMBER_PROFILE_SERVICE
429
+ } from "@tbox.cn/app-contracts-mall";
430
+ import { mallScopeOf } from "@tbox.cn/app-contracts-mall";
431
+
432
+ // src/server/service.ts
433
+ var InMemoryMemberService = class {
434
+ enrollUrl = "";
435
+ balances = /* @__PURE__ */ new Map();
436
+ idempotentKeys = /* @__PURE__ */ new Set();
437
+ usages = [];
438
+ profiles = /* @__PURE__ */ new Map();
439
+ enrollmentAttempts = /* @__PURE__ */ new Map();
440
+ pendingConfirmations = /* @__PURE__ */ new Set();
441
+ statusQueries = /* @__PURE__ */ new Map();
442
+ audit = [];
443
+ constructor(initial = {}) {
444
+ for (const [userId, balance] of Object.entries(initial)) {
445
+ this.balances.set(userId, balance);
446
+ }
447
+ }
448
+ // ===== 1. MemberAccountPort =====
449
+ async getAccount(userId) {
450
+ return {
451
+ authState: "authenticated",
452
+ state: this.balances.has(userId) ? "active" : "not_enrolled",
453
+ ...this.balances.has(userId) ? { memberRef: `member:${userId}` } : {},
454
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
455
+ };
456
+ }
457
+ // ===== 2. MemberProfilePort =====
458
+ async getProfile(userId) {
459
+ return this.profiles.get(userId) ?? {
460
+ labels: [],
461
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
462
+ };
463
+ }
464
+ // ===== 3. MemberCardPort =====
465
+ async getCard(userId) {
466
+ if (!this.balances.has(userId)) return null;
467
+ return {
468
+ cardRef: `card:${userId}`,
469
+ cardDisplay: "\u793A\u4F8B\u4F1A\u5458\u5361",
470
+ status: "active",
471
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
472
+ };
473
+ }
474
+ // ===== 4. MemberBenefitsPort =====
475
+ async queryPoints(userId) {
476
+ return {
477
+ userId,
478
+ balance: this.balances.get(userId) ?? 0,
479
+ membershipLabel: "\u6D4B\u8BD5\u4F1A\u5458\u5361"
480
+ };
481
+ }
482
+ async getBenefits(userId) {
483
+ return {
484
+ availablePoints: this.balances.get(userId) ?? 0,
485
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
486
+ };
487
+ }
488
+ async getEnrollmentFormDefinition() {
489
+ return {
490
+ formVersion: "fixture-v1",
491
+ fields: [
492
+ { fieldId: "mobile", label: "\u624B\u673A\u53F7", type: "tel", required: true, placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7" },
493
+ { fieldId: "displayName", label: "\u59D3\u540D", type: "text", required: false }
494
+ ],
495
+ consents: [{ consentId: "privacy", version: "1", title: "\u9690\u79C1\u653F\u7B56", required: true }],
496
+ authorizationStatus: "required"
497
+ };
498
+ }
499
+ async listPointUsage(userId, page, pageSize) {
500
+ const all = this.usages.filter((u) => u.usageRecordRef.startsWith(userId));
501
+ const start = (page - 1) * pageSize;
502
+ return { items: all.slice(start, start + pageSize), total: all.length };
503
+ }
504
+ async queryDiscount(userId) {
505
+ void userId;
506
+ return void 0;
507
+ }
508
+ async deductPoints(userId, amount, idempotencyKey) {
509
+ if (this.idempotentKeys.has(idempotencyKey)) return true;
510
+ const current = this.balances.get(userId) ?? 0;
511
+ if (amount <= 0 || current < amount) return false;
512
+ this.balances.set(userId, current - amount);
513
+ this.idempotentKeys.add(idempotencyKey);
514
+ this.audit.push({ action: "deduct", userId, amount, key: idempotencyKey });
515
+ this.usages.push({
516
+ usageRecordRef: `${userId}:${Date.now()}`,
517
+ title: "\u79EF\u5206\u62B5\u6263",
518
+ amount: `-${amount}`,
519
+ usedAt: (/* @__PURE__ */ new Date()).toISOString()
520
+ });
521
+ return true;
522
+ }
523
+ async refundPoints(userId, amount, idempotencyKey) {
524
+ if (this.idempotentKeys.has(`refund:${idempotencyKey}`)) return true;
525
+ const current = this.balances.get(userId) ?? 0;
526
+ this.balances.set(userId, current + amount);
527
+ this.idempotentKeys.add(`refund:${idempotencyKey}`);
528
+ this.audit.push({ action: "refund", userId, amount, key: idempotencyKey });
529
+ this.usages.push({
530
+ usageRecordRef: `${userId}:${Date.now()}`,
531
+ title: "\u79EF\u5206\u9000\u56DE",
532
+ amount: `+${amount}`,
533
+ usedAt: (/* @__PURE__ */ new Date()).toISOString()
534
+ });
535
+ return true;
536
+ }
537
+ async awardPoints(userId, amount) {
538
+ const current = this.balances.get(userId) ?? 0;
539
+ this.balances.set(userId, current + amount);
540
+ this.audit.push({ action: "award", userId, amount });
541
+ this.usages.push({
542
+ usageRecordRef: `${userId}:${Date.now()}`,
543
+ title: "\u6D88\u8D39\u5956\u52B1",
544
+ amount: `+${amount}`,
545
+ usedAt: (/* @__PURE__ */ new Date()).toISOString()
546
+ });
547
+ return true;
548
+ }
549
+ // ===== 5. MemberCouponPort(0.4.0 净删——券域 promotion 承接,恒空面移除) =====
550
+ // ===== 6. MemberEnrollmentPort(015:授权预填 + 幂等 + pending 收口) =====
551
+ async enroll(input) {
552
+ const existing = this.enrollmentAttempts.get(input.idempotencyKey);
553
+ if (existing) return existing.result;
554
+ const mobile = input.fieldValues.mobile ?? "";
555
+ if (!/^1[3-9]\d{9}$/.test(mobile)) {
556
+ return { status: "rejected", reason: "\u624B\u673A\u53F7\u683C\u5F0F\u4E0D\u6B63\u786E" };
557
+ }
558
+ const result = this.balances.has(input.userId) || this.pendingConfirmations.has(input.userId) ? { status: "active", memberRef: `member:${input.userId}` } : { status: "pending_confirmation", attemptRef: input.idempotencyKey };
559
+ if (result.status === "active") {
560
+ this.profiles.set(input.userId, {
561
+ ...input.fieldValues.displayName ? { displayName: input.fieldValues.displayName } : {},
562
+ contactDisplay: mobile.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2"),
563
+ labels: [{ labelRef: "member", name: "\u4F1A\u5458" }],
564
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
565
+ });
566
+ } else {
567
+ this.pendingConfirmations.add(input.userId);
568
+ }
569
+ this.enrollmentAttempts.set(input.idempotencyKey, { result, userId: input.userId });
570
+ return result;
571
+ }
572
+ async getEnrollmentStatus(userId, attemptRef) {
573
+ const attempt = this.enrollmentAttempts.get(attemptRef);
574
+ if (!attempt || attempt.userId !== userId) {
575
+ return { status: "failed", reason: "\u5165\u4F1A\u8BB0\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237" };
576
+ }
577
+ if (this.balances.has(userId)) {
578
+ return { status: "active", memberRef: `member:${userId}` };
579
+ }
580
+ const queried = this.statusQueries.get(attemptRef) ?? 0;
581
+ this.statusQueries.set(attemptRef, queried + 1);
582
+ if (queried >= 1) {
583
+ this.balances.set(userId, 0);
584
+ this.pendingConfirmations.delete(userId);
585
+ return { status: "active", memberRef: `member:${userId}` };
586
+ }
587
+ return { status: "pending_confirmation" };
588
+ }
589
+ };
590
+
591
+ // src/server/ports.ts
592
+ async function resolveMemberSlot(service, reqCtx, ctx) {
593
+ const scope = mallScopeOf(reqCtx);
594
+ if (!scope.mallId) throw new Error("\u4F1A\u5458 C \u7AEF\u8BF7\u6C42\u7F3A\u5C11 mallId");
595
+ if (!ctx.integrations) throw new Error("integrations registry \u7F3A\u5931\uFF1Amember \u670D\u52A1\u4E0D\u53EF\u7528");
596
+ return await ctx.integrations.resolveService({
597
+ service,
598
+ scope,
599
+ request: reqCtx
600
+ });
601
+ }
602
+ async function resolveMemberAccountPort(reqCtx, ctx) {
603
+ return resolveMemberSlot(MEMBER_ACCOUNT_SERVICE, reqCtx, ctx);
604
+ }
605
+ async function resolveMemberProfilePort(reqCtx, ctx) {
606
+ return resolveMemberSlot(MEMBER_PROFILE_SERVICE, reqCtx, ctx);
607
+ }
608
+ async function resolveMemberCardPort(reqCtx, ctx) {
609
+ return resolveMemberSlot(MEMBER_CARD_SERVICE, reqCtx, ctx);
610
+ }
611
+ async function resolveMemberBenefitsPort(reqCtx, ctx) {
612
+ return resolveMemberSlot(MEMBER_BENEFITS_SERVICE, reqCtx, ctx);
613
+ }
614
+ async function resolveMemberEnrollmentPort(reqCtx, ctx) {
615
+ return resolveMemberSlot(MEMBER_ENROLLMENT_SERVICE, reqCtx, ctx);
616
+ }
617
+
618
+ // src/server/status.ts
619
+ var MEMBER_LOGIN_NOTICE = "\u8BF7\u5148\u767B\u5F55\uFF1B\u767B\u5F55\u4E0D\u8981\u6C42\u5F00\u901A\u4F1A\u5458";
620
+ var MEMBER_UNKNOWN_NOTICE = "\u5F53\u524D\u65E0\u6CD5\u786E\u8BA4\u4F1A\u5458\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
621
+ var MEMBER_NOT_ENROLLED_NOTICE = "\u5C1A\u672A\u5165\u4F1A\uFF0C\u5165\u4F1A\u540E\u53EF\u67E5\u8BE2\u79EF\u5206\u4E0E\u670D\u52A1";
622
+ var MEMBER_NO_POINTS_NOTICE = "\u5F53\u524D\u6CA1\u6709\u53EF\u67E5\u8BE2\u7684\u4F1A\u5458\u79EF\u5206";
623
+ var MEMBER_QUERY_NOTICES = {
624
+ unauthenticated: "\u8BF7\u5148\u767B\u5F55\u540E\u518D\u67E5\u8BE2\u4F1A\u5458\u4FE1\u606F",
625
+ unknown: MEMBER_UNKNOWN_NOTICE,
626
+ not_enrolled: MEMBER_NOT_ENROLLED_NOTICE,
627
+ closed: MEMBER_NO_POINTS_NOTICE,
628
+ unavailable: "\u4F1A\u5458\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5"
629
+ };
630
+ async function resolveMemberStatus(reqCtx, ctx) {
631
+ if (!reqCtx) {
632
+ ctx.logger?.warn("\u4F1A\u5458\u72B6\u6001\u89E3\u6790\u5931\u8D25\uFF1A\u8FD0\u884C\u4E0A\u4E0B\u6587\u7F3A\u5E2D");
633
+ return { outcome: "unavailable" };
634
+ }
635
+ if (reqCtx.identity?.source === "anonymous") {
636
+ let loginUrl;
637
+ try {
638
+ const accountPort2 = await resolveMemberAccountPort(reqCtx, ctx);
639
+ loginUrl = accountPort2.loginUrl || void 0;
640
+ } catch {
641
+ }
642
+ return loginUrl ? { outcome: "unauthenticated", loginUrl } : { outcome: "unauthenticated" };
643
+ }
644
+ let accountPort;
645
+ let account;
646
+ try {
647
+ accountPort = await resolveMemberAccountPort(reqCtx, ctx);
648
+ account = await accountPort.getAccount(reqCtx.identity.userId);
649
+ } catch (error) {
650
+ ctx.logger?.warn(`\u4F1A\u5458\u72B6\u6001\u89E3\u6790\u5931\u8D25\uFF1Aerr=${error.message}`);
651
+ return { outcome: "unavailable" };
652
+ }
653
+ let enrollUrl;
654
+ try {
655
+ const enrollmentPort = await resolveMemberEnrollmentPort(reqCtx, ctx);
656
+ enrollUrl = enrollmentPort.enrollUrl || void 0;
657
+ } catch (error) {
658
+ ctx.logger?.warn(`\u4F1A\u5458\u5165\u4F1A\u6DF1\u94FE\u89E3\u6790\u5931\u8D25\uFF1Aerr=${error.message}`);
659
+ }
660
+ return {
661
+ outcome: classifyMemberOutcome(reqCtx.identity, account),
662
+ account,
663
+ ...enrollUrl ? { enrollUrl } : {},
664
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
665
+ };
666
+ }
667
+
668
+ // src/server/tool.ts
669
+ import { z } from "zod";
670
+ function withCards(facts, cardType, note) {
671
+ return { ...facts, cards: [{ cardType, data: facts, note }] };
672
+ }
673
+ function memberStateLabel(state) {
674
+ switch (state) {
675
+ case "active":
676
+ return "\u5DF2\u5165\u4F1A";
677
+ case "not_enrolled":
678
+ return "\u672A\u5165\u4F1A";
679
+ case "closed":
680
+ return "\u5DF2\u9000\u4F1A";
681
+ case "unauthenticated":
682
+ return "\u672A\u767B\u5F55";
683
+ default:
684
+ return "\u72B6\u6001\u786E\u8BA4\u4E2D";
685
+ }
686
+ }
687
+ var MEMBER_CONTRACT_NOTICE = "\u4F1A\u5458\u5361\u540D\u4E0D\u662F\u4F1A\u5458\u7B49\u7EA7\uFF1B\u5F53\u524D\u67E5\u8BE2\u672A\u8FD4\u56DE\u4F1A\u5458\u6743\u76CA\uFF0C\u4E0D\u5F97\u63A8\u65AD\u6298\u6263\u3001\u505C\u8F66\u3001\u751F\u65E5\u6216\u6D3B\u52A8\u6743\u76CA";
688
+ var MEMBER_PRIVACY_REFUSAL = "\u51FA\u4E8E\u4F1A\u5458\u9690\u79C1\u4FDD\u62A4\uFF0C\u6211\u53EA\u80FD\u67E5\u8BE2\u5F53\u524D\u767B\u5F55\u4F1A\u5458\u7684\u4FE1\u606F\uFF0C\u4E0D\u80FD\u4FEE\u6539\u4F1A\u5458\u8D44\u6599\u3001\u4EE3\u67E5\u4ED6\u4EBA\u8D26\u6237\uFF0C\u4E5F\u4E0D\u80FD\u901A\u8FC7\u624B\u673A\u53F7\u6216\u4F1A\u5458\u5361\u53F7\u5207\u6362\u8D26\u6237\u3002";
689
+ function memberPrivacyRefusal(query) {
690
+ if (typeof query !== "string" || query.trim().length === 0) return void 0;
691
+ const content = query.replace(/\s+/g, "");
692
+ const profileMutation = /(手机号|手机号码|联系方式).{0,12}(改|修改|更换|变更|替换|更新)|(改|修改|更换|变更|替换|更新).{0,12}(手机号|手机号码|联系方式)/.test(content);
693
+ const thirdPartyLookup = /(老婆|老公|妻子|丈夫|他人|别人|朋友|同事|家人|孩子|女儿|儿子|妈妈|爸爸).{0,20}(手机号|手机号码|会员|积分|卡)/.test(content);
694
+ const suppliedIdentifierLookup = /(用|通过|根据|拿着|拿).{0,20}(手机号|手机号码|会员卡号|卡号).{0,20}(查|查询|切|切换|登录|绑定)/.test(content) || /(切换|切到|换到|登录到|绑定到).{0,15}(账号|账户|会员卡|卡号|手机号|手机号码)/.test(content);
695
+ const explicitIdentifier = /(手机号|手机号码|会员卡号|卡号|账号|账户).{0,16}\d{5,}/.test(content);
696
+ return profileMutation || thirdPartyLookup || suppliedIdentifierLookup || explicitIdentifier ? MEMBER_PRIVACY_REFUSAL : void 0;
697
+ }
698
+ function hasCompleteMemberCard(card) {
699
+ return card?.presentation !== void 0;
700
+ }
701
+ function cardContractNotice(card) {
702
+ return card.benefits && card.benefits.length > 0 ? "\u4F1A\u5458\u5361\u540D\u4E0D\u662F\u4F1A\u5458\u7B49\u7EA7\uFF1B\u4EC5\u5C55\u793A\u7CFB\u7EDF\u8FD4\u56DE\u7684\u5F53\u524D\u7B49\u7EA7\u6743\u76CA\uFF0C\u4E0D\u5F97\u63A8\u65AD\u989D\u5916\u6298\u6263\u6216\u670D\u52A1" : MEMBER_CONTRACT_NOTICE;
703
+ }
704
+ function buildMemberCardFacts(userId, card, availablePoints, includePreferencePrompts = false) {
705
+ return {
706
+ userId,
707
+ cardRef: card.cardRef,
708
+ cardDisplay: card.cardDisplay,
709
+ status: card.status,
710
+ ...card.levelName ? { levelName: card.levelName } : {},
711
+ availablePoints,
712
+ ...card.cardImageUrl ? { cardImageUrl: card.cardImageUrl } : {},
713
+ ...card.pointsRuleImageUrl ? { pointsRuleImageUrl: card.pointsRuleImageUrl } : {},
714
+ ...card.pointsRuleContent ? { pointsRuleContent: card.pointsRuleContent } : {},
715
+ ...card.pointsUrl ? { pointsUrl: card.pointsUrl } : {},
716
+ ...card.presentation ? { presentation: card.presentation } : {},
717
+ ...card.progress ? { progress: card.progress } : {},
718
+ ...card.benefits ? { benefits: [...card.benefits] } : {},
719
+ ...includePreferencePrompts ? { preferencePrompts: [...MEMBER_PREFERENCE_PROMPTS] } : {},
720
+ contractNotice: cardContractNotice(card)
721
+ };
722
+ }
723
+ function memberCardCandidate(facts) {
724
+ return {
725
+ cardType: "member-card-summary",
726
+ data: facts,
727
+ note: `\u4F1A\u5458\u5361\uFF08\u53EF\u7528\u79EF\u5206 ${facts.availablePoints}\uFF09`
728
+ };
729
+ }
730
+ function anonymousShort(userId, action) {
731
+ return {
732
+ userId,
733
+ requiresAuth: true,
734
+ found: false,
735
+ noResultsReason: `\u8BF7\u5148\u767B\u5F55\u540E\u518D${action}`
736
+ };
737
+ }
738
+ async function maybeEnrollmentCta(reqCtx, account, links) {
739
+ const outcome = classifyMemberOutcome2(reqCtx.identity, account);
740
+ if (outcome !== "not_enrolled" && outcome !== "closed") return void 0;
741
+ return memberGateCardOf(reqCtx.identity.userId, {
742
+ outcome,
743
+ ...links.enrollUrl ? { enrollUrl: links.enrollUrl } : {},
744
+ ...links.loginUrl ? { loginUrl: links.loginUrl } : {}
745
+ });
746
+ }
747
+ function queryNoResultsNotice(account) {
748
+ if (account.authState === "unauthenticated") return MEMBER_LOGIN_NOTICE;
749
+ if (account.state === "not_enrolled") return MEMBER_NOT_ENROLLED_NOTICE;
750
+ if (account.state === "closed") return MEMBER_NO_POINTS_NOTICE;
751
+ return MEMBER_UNKNOWN_NOTICE;
752
+ }
753
+ function queryNonActiveNotice(account) {
754
+ return account.state === "closed" ? MEMBER_NO_POINTS_NOTICE : MEMBER_NOT_ENROLLED_NOTICE;
755
+ }
756
+ function createQueryPointsTool(ports) {
757
+ return createTool({
758
+ id: "queryPoints",
759
+ description: "\u67E5\u8BE2\u4F1A\u5458\u5F53\u524D\u79EF\u5206\u4F59\u989D\u3001\u4F1A\u5458\u5361\u9762\u4E0E\u79EF\u5206\u6D41\u6C34\uFF1B\u8FD4\u56DE\u7684 balance \u662F\u5F53\u524D\u79EF\u5206\u70B9\u6570\uFF0Citems \u4E2D title \u662F\u6D41\u6C34\u4E1A\u52A1\u8BF4\u660E\u3001amount \u662F\u672C\u7B14\u53D8\u52A8\uFF08\u6B63\u6570\u589E\u52A0\u3001\u8D1F\u6570\u6263\u51CF\uFF09\u3001usedAt \u662F\u53D1\u751F\u65F6\u95F4\u3001description \u662F\u53EF\u9009\u95E8\u5E97\u540D\u79F0\uFF1B\u6D41\u6C34\u4EC5\u4F9B\u6A21\u578B\u7EC4\u7EC7\u6587\u672C\uFF0C\u4E0D\u6784\u9020\u79EF\u5206\u6D41\u6C34\u5361\u3002\u5DF2\u767B\u5F55\u672A\u5165\u4F1A\u65F6\u8FD4\u56DE\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08member-enrollment-cta\uFF09\uFF0C\u65E0\u9700\u518D\u8C03 queryEnrollmentCta\u3002",
760
+ inputSchema: z.object({
761
+ query: z.string().optional().describe("\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\uFF1B\u7528\u4E8E\u4FDD\u7559\u67E5\u8BE2\u8BED\u5883\uFF0C\u4E0D\u53C2\u4E0E\u8EAB\u4EFD\u6216\u8D44\u6E90\u5B9A\u4F4D")
762
+ }),
763
+ execute: async (input, execCtx) => {
764
+ const reqCtx = requireToolRequestContext(execCtx, "queryPoints");
765
+ const privacyRefusal = memberPrivacyRefusal(input.query);
766
+ if (privacyRefusal) return { found: false, noResultsReason: privacyRefusal };
767
+ const userId = reqCtx.identity.userId;
768
+ if (reqCtx.identity.source === "anonymous") {
769
+ return anonymousShort(userId, "\u67E5\u8BE2\u4F1A\u5458\u79EF\u5206");
770
+ }
771
+ const [accountPort, cardPort, benefitsPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getCard(reqCtx), ports.getBenefits(reqCtx), ports.getEnrollment(reqCtx)]);
772
+ const account = await accountPort.getAccount(userId);
773
+ if (account.authState === "unauthenticated" || account.state !== "active") {
774
+ const enrollCard = await maybeEnrollmentCta(reqCtx, account, { enrollUrl: enrollmentPort.enrollUrl, loginUrl: accountPort.loginUrl });
775
+ if (enrollCard) {
776
+ return {
777
+ userId,
778
+ found: false,
779
+ authState: account.authState,
780
+ state: account.state,
781
+ noResultsReason: queryNonActiveNotice(account),
782
+ cards: [enrollCard]
783
+ };
784
+ }
785
+ return {
786
+ userId,
787
+ found: false,
788
+ authState: account.authState,
789
+ state: account.state,
790
+ noResultsReason: queryNoResultsNotice(account)
791
+ };
792
+ }
793
+ const card = await cardPort.getCard(userId);
794
+ let points = { userId, balance: 0 };
795
+ let pointsUnavailable = false;
796
+ try {
797
+ points = await benefitsPort.queryPoints(userId);
798
+ } catch {
799
+ pointsUnavailable = true;
800
+ }
801
+ const completeCard = hasCompleteMemberCard(card);
802
+ let usage = { items: [], total: 0 };
803
+ let usageUnavailable = false;
804
+ if (!completeCard || card?.pointsUrl) {
805
+ try {
806
+ usage = await benefitsPort.listPointUsage(userId, 1, 10);
807
+ } catch {
808
+ usageUnavailable = true;
809
+ }
810
+ }
811
+ const facts = {
812
+ userId,
813
+ balance: points.balance,
814
+ ...points.membershipLabel ? { membershipLabel: points.membershipLabel } : {},
815
+ items: [...usage.items],
816
+ total: usage.total,
817
+ contractNotice: MEMBER_CONTRACT_NOTICE,
818
+ ...pointsUnavailable ? { pointsNotice: "\u5F53\u524D\u79EF\u5206\u4F59\u989D\u6682\u4E0D\u53EF\u7528\uFF0C\u4F1A\u5458\u5361\u4ECD\u53EF\u6B63\u5E38\u67E5\u770B" } : {},
819
+ ...usageUnavailable ? { pointsHistoryNotice: "\u6682\u65E0\u6700\u8FD1\u79EF\u5206\u6D41\u6C34\u8BB0\u5F55" } : {}
820
+ };
821
+ if (!card) return { ...facts, cards: [] };
822
+ const memberCard = memberCardCandidate(buildMemberCardFacts(userId, card, points.balance, true));
823
+ return { ...facts, cards: [memberCard] };
824
+ }
825
+ });
826
+ }
827
+ function createQueryMemberInfoTool(ports) {
828
+ return createTool({
829
+ id: "queryMemberInfo",
830
+ description: "\u67E5\u8BE2\u660E\u786E\u7684\u4F1A\u5458\u8D26\u6237\u8D44\u6599\uFF08\u662F\u5426\u5165\u4F1A\u3001\u6635\u79F0\u3001\u8054\u7CFB\u65B9\u5F0F\u3001\u6807\u7B7E\uFF09\uFF1B\u4F20\u5165\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\u4F5C\u4E3A query\uFF08\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D\uFF09\uFF1B\u5DF2\u767B\u5F55\u672A\u5165\u4F1A\u65F6\u9644\u5E26\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08member-enrollment-cta\uFF09\uFF0C\u65E0\u9700\u518D\u8C03 queryEnrollmentCta\u3002",
831
+ inputSchema: z.object({ query: z.string().optional().describe("\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\uFF0C\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D") }),
832
+ execute: async (input, execCtx) => {
833
+ const reqCtx = requireToolRequestContext(execCtx, "queryMemberInfo");
834
+ const privacyRefusal = memberPrivacyRefusal(input.query);
835
+ if (privacyRefusal) return { found: false, noResultsReason: privacyRefusal };
836
+ const userId = reqCtx.identity.userId;
837
+ if (reqCtx.identity.source === "anonymous") {
838
+ return anonymousShort(userId, "\u67E5\u8BE2\u4F1A\u5458\u4FE1\u606F");
839
+ }
840
+ const [accountPort, cardPort, benefitsPort, profilePort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getCard(reqCtx), ports.getBenefits(reqCtx), ports.getProfile(reqCtx), ports.getEnrollment(reqCtx)]);
841
+ const account = await accountPort.getAccount(userId);
842
+ const profile = await profilePort.getProfile(userId);
843
+ const facts = {
844
+ userId,
845
+ authState: account.authState,
846
+ state: account.state,
847
+ ...profile.displayName ? { displayName: profile.displayName } : {},
848
+ ...profile.avatarUrl ? { avatarUrl: profile.avatarUrl } : {},
849
+ ...profile.contactDisplay ? { contactDisplay: profile.contactDisplay } : {},
850
+ ...account.memberRef ? { memberRef: account.memberRef } : {},
851
+ ...account.registeredAt ? { registeredAt: account.registeredAt } : {},
852
+ labels: profile.labels ?? [],
853
+ contractNotice: MEMBER_CONTRACT_NOTICE
854
+ };
855
+ const infoCard = {
856
+ cardType: "member-info",
857
+ data: facts,
858
+ // note 契约(W7):账户态中文化 + 姓名要点
859
+ note: `\u4F1A\u5458\u8D26\u6237\uFF08${memberStateLabel(facts.state)}\uFF09${facts.displayName ? ` \xB7 ${facts.displayName}` : ""}`
860
+ };
861
+ if (account.state !== "active") {
862
+ const enrollCard = await maybeEnrollmentCta(reqCtx, account, { enrollUrl: enrollmentPort.enrollUrl, loginUrl: accountPort.loginUrl });
863
+ if (enrollCard) return { ...facts, cards: [enrollCard, infoCard] };
864
+ return { ...facts, cards: [infoCard] };
865
+ }
866
+ const [card, points] = await Promise.all([
867
+ cardPort.getCard(userId),
868
+ benefitsPort.queryPoints(userId)
869
+ ]);
870
+ if (!card) return { ...facts, cards: [infoCard] };
871
+ const memberCard = memberCardCandidate(buildMemberCardFacts(userId, card, points.balance));
872
+ if (hasCompleteMemberCard(card)) return { ...facts, cards: [memberCard] };
873
+ return {
874
+ ...facts,
875
+ cards: [memberCard, infoCard]
876
+ };
877
+ }
878
+ });
879
+ }
880
+ function createQueryMemberCardTool(ports) {
881
+ return createTool({
882
+ id: "queryMemberCard",
883
+ description: "\u67E5\u8BE2\u4F1A\u5458\u5361\u4FE1\u606F\uFF08\u5361\u9762\u3001\u5361\u540D\u3001\u79EF\u5206\u6458\u8981\uFF09\uFF1B\u4F20\u5165\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\u4F5C\u4E3A query\uFF08\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D\uFF09\uFF1B\u5DF2\u767B\u5F55\u672A\u5165\u4F1A\u65F6\u8FD4\u56DE\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08member-enrollment-cta\uFF09\uFF0C\u65E0\u9700\u518D\u8C03 queryEnrollmentCta\u3002",
884
+ inputSchema: z.object({ query: z.string().optional().describe("\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\uFF0C\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D") }),
885
+ execute: async (input, execCtx) => {
886
+ const reqCtx = requireToolRequestContext(execCtx, "queryMemberCard");
887
+ const privacyRefusal = memberPrivacyRefusal(input.query);
888
+ if (privacyRefusal) return { found: false, noResultsReason: privacyRefusal };
889
+ const userId = reqCtx.identity.userId;
890
+ if (reqCtx.identity.source === "anonymous") {
891
+ return anonymousShort(userId, "\u67E5\u8BE2\u4F1A\u5458\u5361");
892
+ }
893
+ const [accountPort, cardPort, benefitsPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getCard(reqCtx), ports.getBenefits(reqCtx), ports.getEnrollment(reqCtx)]);
894
+ const account = await accountPort.getAccount(userId);
895
+ if (account.authState === "unauthenticated" || account.state === "unknown") {
896
+ return {
897
+ userId,
898
+ found: false,
899
+ authState: account.authState,
900
+ state: account.state,
901
+ noResultsReason: account.authState === "unauthenticated" ? MEMBER_LOGIN_NOTICE : MEMBER_UNKNOWN_NOTICE
902
+ };
903
+ }
904
+ if (account.state !== "active") {
905
+ const enrollCard = await maybeEnrollmentCta(reqCtx, account, { enrollUrl: enrollmentPort.enrollUrl, loginUrl: accountPort.loginUrl });
906
+ if (enrollCard) {
907
+ return {
908
+ userId,
909
+ found: false,
910
+ authState: account.authState,
911
+ state: account.state,
912
+ noResultsReason: queryNonActiveNotice(account),
913
+ cards: [enrollCard]
914
+ };
915
+ }
916
+ return {
917
+ userId,
918
+ found: false,
919
+ authState: account.authState,
920
+ state: account.state,
921
+ noResultsReason: "\u5F53\u524D\u7528\u6237\u5C1A\u672A\u5728\u8BE5\u5546\u573A\u5F00\u901A\u4F1A\u5458\u5361"
922
+ };
923
+ }
924
+ const card = await cardPort.getCard(userId);
925
+ const points = await benefitsPort.queryPoints(userId);
926
+ if (!card) {
927
+ return {
928
+ userId,
929
+ found: false,
930
+ authState: account.authState,
931
+ state: "unknown",
932
+ noResultsReason: "\u4F1A\u5458\u8D26\u6237\u5DF2\u6FC0\u6D3B\uFF0C\u4F46\u6682\u672A\u53D6\u5F97\u4F1A\u5458\u5361\u4FE1\u606F\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
933
+ };
934
+ }
935
+ const facts = buildMemberCardFacts(userId, card, points.balance);
936
+ return withCards(facts, "member-card-summary", `\u4F1A\u5458\u5361\uFF08\u53EF\u7528\u79EF\u5206 ${points.balance}\uFF09`);
937
+ }
938
+ });
939
+ }
940
+ function createQueryEnrollmentCtaTool(ports) {
941
+ return createTool({
942
+ id: "queryEnrollmentCta",
943
+ description: "\u5F00\u5361\u5165\u4F1A\u5F15\u5BFC\uFF1A\u5F53\u7528\u6237\u8BE2\u95EE\u5982\u4F55\u6210\u4E3A\u4F1A\u5458\u3001\u600E\u4E48\u5F00\u5361\u3001\u5165\u4F1A\u6D41\u7A0B\u3001\u4F1A\u5458\u6CE8\u518C\u65F6\u8C03\u7528\uFF0C\u8FD4\u56DE\u5F00\u5361\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08\u70B9\u51FB\u540E\u8DF3\u8F6C\u652F\u4ED8\u5B9D\u5C0F\u7A0B\u5E8F\u5B8C\u6210\u5165\u4F1A\uFF09\u3002\u3010\u6CE8\u610F\u3011\u5DF2\u5165\u4F1A\u7528\u6237\u4E0D\u51FA\u6B64\u5361\uFF0C\u6539\u4E3A\u67E5\u8BE2 queryMemberCard\u3002",
944
+ inputSchema: z.object({}),
945
+ execute: async (_input, execCtx) => {
946
+ const reqCtx = requireToolRequestContext(execCtx, "queryEnrollmentCta");
947
+ const userId = reqCtx.identity.userId;
948
+ const [accountPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getEnrollment(reqCtx)]);
949
+ const account = await accountPort.getAccount(userId);
950
+ if (account.state === "active") {
951
+ return {
952
+ userId,
953
+ found: false,
954
+ noResultsReason: "\u60A8\u5DF2\u662F\u4F1A\u5458\uFF0C\u65E0\u9700\u91CD\u590D\u5F00\u5361",
955
+ authState: account.authState,
956
+ state: "active"
957
+ };
958
+ }
959
+ if (account.authState === "unauthenticated") {
960
+ const loginFacts = {
961
+ title: "\u767B\u5F55\u540E\u7EE7\u7EED",
962
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
963
+ };
964
+ return {
965
+ ...loginFacts,
966
+ found: true,
967
+ cards: [{ cardType: "member-login-cta", data: loginFacts, note: "\u672A\u767B\u5F55 \xB7 \u70B9\u51FB\u5361\u7247\u767B\u5F55" }]
968
+ };
969
+ }
970
+ if (account.state === "unknown") {
971
+ return {
972
+ userId,
973
+ found: false,
974
+ authState: account.authState,
975
+ state: account.state,
976
+ noResultsReason: "\u5F53\u524D\u65E0\u6CD5\u786E\u8BA4\u4F1A\u5458\u72B6\u6001\uFF0C\u8BF7\u5237\u65B0\u767B\u5F55\u72B6\u6001\u540E\u91CD\u8BD5"
977
+ };
978
+ }
979
+ const status = {
980
+ outcome: account.state,
981
+ ...enrollmentPort.enrollUrl ? { enrollUrl: enrollmentPort.enrollUrl } : {},
982
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
983
+ };
984
+ const card = memberGateCardOf(reqCtx.identity.userId, status);
985
+ if (!card) {
986
+ return {
987
+ userId,
988
+ found: false,
989
+ authState: account.authState,
990
+ state: account.state,
991
+ noResultsReason: "\u5F53\u524D\u65E0\u6CD5\u786E\u8BA4\u4F1A\u5458\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
992
+ };
993
+ }
994
+ const facts = { userId, ...card.data };
995
+ return withCards(facts, card.cardType, card.note);
996
+ }
997
+ });
998
+ }
999
+ function createQueryEnrollmentFormTool(ports) {
1000
+ return createTool({
1001
+ id: "queryEnrollmentForm",
1002
+ description: "\u67E5\u8BE2\u4F1A\u5458\u5165\u4F1A\u72B6\u6001\u5E76\u8FD4\u56DE\u5165\u4F1A\u8868\u5355\uFF08\u542B\u652F\u4ED8\u5B9D\u6388\u6743\u9884\u586B\u5165\u53E3\u3001\u9690\u79C1\u540C\u610F\u9879\uFF1B\u672A\u5165\u4F1A\u65F6\u7528\u6237\u53EF\u6388\u6743\u6216\u624B\u52A8\u586B\u5199\u540E\u63D0\u4EA4\uFF09",
1003
+ inputSchema: z.object({}),
1004
+ execute: async (_input, execCtx) => {
1005
+ const reqCtx = requireToolRequestContext(execCtx, "queryEnrollmentForm");
1006
+ const userId = reqCtx.identity.userId;
1007
+ if (reqCtx.identity.source === "anonymous") {
1008
+ return anonymousShort(userId, "\u67E5\u8BE2\u5165\u4F1A\u8868\u5355");
1009
+ }
1010
+ const [accountPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getEnrollment(reqCtx)]);
1011
+ const account = await accountPort.getAccount(userId);
1012
+ const notEnrolled = account.authState === "authenticated" && (account.state === "not_enrolled" || account.state === "closed");
1013
+ const definition = notEnrolled && typeof enrollmentPort.getEnrollmentFormDefinition === "function" ? await enrollmentPort.getEnrollmentFormDefinition(userId) : void 0;
1014
+ if (!notEnrolled) {
1015
+ return {
1016
+ userId,
1017
+ authState: account.authState,
1018
+ state: account.state,
1019
+ found: false,
1020
+ noResultsReason: account.authState === "unauthenticated" ? MEMBER_LOGIN_NOTICE : account.state === "active" ? "\u5F53\u524D\u7528\u6237\u5DF2\u662F\u4F1A\u5458\uFF0C\u65E0\u9700\u91CD\u590D\u5F00\u5361" : MEMBER_UNKNOWN_NOTICE
1021
+ };
1022
+ }
1023
+ const facts = {
1024
+ userId,
1025
+ state: account.state,
1026
+ formVersion: definition?.formVersion ?? "unknown",
1027
+ fields: definition ? [...definition.fields] : [],
1028
+ consents: definition ? [...definition.consents] : [],
1029
+ authorization: { status: definition?.authorizationStatus ?? "unavailable" }
1030
+ };
1031
+ return withCards(facts, "member-enrollment-form", "\u672A\u5165\u4F1A \xB7 \u586B\u5199\u8868\u5355\u5B8C\u6210\u5165\u4F1A");
1032
+ }
1033
+ });
1034
+ }
1035
+ function createQueryLoginCtaTool(ports) {
1036
+ return createTool({
1037
+ id: "queryLoginCta",
1038
+ description: "\u767B\u5F55\u5F15\u5BFC\uFF1A\u5F53\u7528\u6237\u8868\u793A\u8981\u767B\u5F55\u3001\u8BF4\u81EA\u5DF1\u672A\u767B\u5F55\uFF0C\u6216\u5176\u5B83\u80FD\u529B\u56E0\u7F3A\u5C11\u767B\u5F55\u8EAB\u4EFD\u65E0\u6CD5\u7EE7\u7EED\u65F6\u8C03\u7528\uFF0C\u8FD4\u56DE\u767B\u5F55\u5F15\u5BFC\u5361\uFF08\u70B9\u51FB\u540E\u8DF3\u8F6C\u5546\u573A\u5C0F\u7A0B\u5E8F\u767B\u5F55\u9875\uFF0C\u5B8C\u6210\u540E\u56DE\u5230\u5BF9\u8BDD\uFF09\u3002",
1039
+ inputSchema: z.object({}),
1040
+ execute: async (_input, execCtx) => {
1041
+ const reqCtx = requireToolRequestContext(execCtx, "queryLoginCta");
1042
+ const accountPort = await ports.getAccount(reqCtx);
1043
+ const loginFacts = {
1044
+ title: "\u767B\u5F55\u540E\u7EE7\u7EED",
1045
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
1046
+ };
1047
+ return withCards(loginFacts, "member-login-cta", "\u672A\u767B\u5F55 \xB7 \u70B9\u51FB\u5361\u7247\u767B\u5F55");
1048
+ }
1049
+ });
1050
+ }
1051
+
1052
+ // src/server/handler.ts
1053
+ import {
1054
+ MEMBER_PREFERENCE_PROMPTS as MEMBER_PREFERENCE_PROMPTS2,
1055
+ MEMBER_PREFERENCE_RECOMMENDATION_QUERIES,
1056
+ MEMBER_PREFERENCE_VALUES,
1057
+ parseMallScope
1058
+ } from "@tbox.cn/app-contracts-mall";
1059
+ function matchMemberPreference(content) {
1060
+ return MEMBER_PREFERENCE_PROMPTS2.find((prompt) => content === prompt.query);
1061
+ }
1062
+ var MEMBER_PREFERENCE_ACK_TEXTS = {
1063
+ "\u6211\u5E38\u6765\u4E70\u8863\u670D": "\u4F60\u5E38\u6765\u4E70\u8863\u670D",
1064
+ "\u6211\u5E38\u5E26\u5A03\u6765\u73A9": "\u4F60\u5E38\u5E26\u5A03\u6765\u73A9",
1065
+ "\u6211\u5E38\u6765\u5403\u7F8E\u98DF": "\u4F60\u5E38\u6765\u5403\u7F8E\u98DF"
1066
+ };
1067
+ function createMemberHandler(ctx) {
1068
+ return {
1069
+ id: "member",
1070
+ // match:'exact':偏好按钮发出的是固定整句值(member-card-summary 按钮组),
1071
+ // 整句全等命中——不入 BM25 词袋(相似查询「2楼有啥美食可逛」曾被词袋误吞,F3)
1072
+ intents: [{ patterns: [...MEMBER_PREFERENCE_VALUES], match: "exact" }],
1073
+ handle: async (action, payload, _ctx, reqCtx) => {
1074
+ if (action === "intent") {
1075
+ const content = typeof payload?.content === "string" ? payload.content.trim() : "";
1076
+ const prompt = matchMemberPreference(content);
1077
+ if (!prompt) return { cards: [], continueWith: { content } };
1078
+ const identity = reqCtx?.identity;
1079
+ if (!identity || identity.source === "anonymous") {
1080
+ return { cards: [], text: "\u767B\u5F55\u540E\u624D\u80FD\u4FDD\u5B58\u4F1A\u5458\u504F\u597D" };
1081
+ }
1082
+ if (!ctx.memory || ctx.memory.capabilities?.save === false) {
1083
+ return { cards: [], text: "\u504F\u597D\u8BB0\u5FC6\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5" };
1084
+ }
1085
+ const mallId = parseMallScope(reqCtx?.attributes)?.mallId;
1086
+ await ctx.memory.save({
1087
+ userId: identity.userId,
1088
+ ...mallId ? { scope: { mallId } } : {},
1089
+ kind: "preference",
1090
+ content: `\u4F1A\u5458\u504F\u597D\uFF1A${prompt.value}`
1091
+ });
1092
+ return {
1093
+ cards: [],
1094
+ text: `\u8BB0\u4F4F\u5566\uFF0C${MEMBER_PREFERENCE_ACK_TEXTS[prompt.value]}\u3002\u6B63\u5728\u4E3A\u4F60\u67E5\u627E\u76F8\u5173\u7684\u95E8\u5E97\u548C\u5BFC\u8D2D\u670D\u52A1\uFF0C\u8BF7\u7A0D\u7B49\u3002
1095
+
1096
+ `,
1097
+ continueWith: { content: MEMBER_PREFERENCE_RECOMMENDATION_QUERIES[prompt.value] }
1098
+ };
1099
+ }
1100
+ if (!reqCtx) return { cards: [], text: "\u4F1A\u5458\u670D\u52A1\u4E0D\u53EF\u7528\uFF08\u8FD0\u884C\u4E0A\u4E0B\u6587\u7F3A\u5931\uFF09" };
1101
+ const query = typeof payload?.query === "string" ? payload.query : "";
1102
+ const privacyRefusal = memberPrivacyRefusal(query);
1103
+ if (privacyRefusal) return { cards: [], text: privacyRefusal };
1104
+ const status = await resolveMemberStatus(reqCtx, ctx);
1105
+ if (status.outcome === "active") {
1106
+ const userId = reqCtx.identity.userId;
1107
+ switch (action) {
1108
+ case "query-points": {
1109
+ let cardPort;
1110
+ let benefitsPort;
1111
+ try {
1112
+ [cardPort, benefitsPort] = await Promise.all([
1113
+ resolveMemberCardPort(reqCtx, ctx),
1114
+ resolveMemberBenefitsPort(reqCtx, ctx)
1115
+ ]);
1116
+ } catch (error) {
1117
+ ctx.logger?.warn(`member handler\uFF1Aactive \u69FD\u89E3\u6790\u5931\u8D25\uFF1Aerr=${error.message}`);
1118
+ return { cards: [], text: MEMBER_QUERY_NOTICES.unavailable };
1119
+ }
1120
+ const card = await cardPort.getCard(userId);
1121
+ let points = { userId, balance: 0 };
1122
+ let pointsUnavailable = false;
1123
+ try {
1124
+ points = await benefitsPort.queryPoints(userId);
1125
+ } catch {
1126
+ pointsUnavailable = true;
1127
+ }
1128
+ if (card?.presentation !== void 0) {
1129
+ let usage2;
1130
+ let usageUnavailable = false;
1131
+ if (card.pointsUrl) {
1132
+ try {
1133
+ usage2 = await benefitsPort.listPointUsage(userId, 1, 10);
1134
+ } catch {
1135
+ usageUnavailable = true;
1136
+ }
1137
+ }
1138
+ const cardData = {
1139
+ userId,
1140
+ cardRef: card.cardRef,
1141
+ cardDisplay: card.cardDisplay,
1142
+ status: card.status,
1143
+ ...card.levelName ? { levelName: card.levelName } : {},
1144
+ availablePoints: points.balance,
1145
+ ...card.cardImageUrl ? { cardImageUrl: card.cardImageUrl } : {},
1146
+ ...card.pointsRuleImageUrl ? { pointsRuleImageUrl: card.pointsRuleImageUrl } : {},
1147
+ ...card.pointsRuleContent ? { pointsRuleContent: card.pointsRuleContent } : {},
1148
+ ...card.pointsUrl ? { pointsUrl: card.pointsUrl } : {},
1149
+ ...card.presentation ? { presentation: card.presentation } : {},
1150
+ ...card.progress ? { progress: card.progress } : {},
1151
+ ...card.benefits ? { benefits: [...card.benefits] } : {}
1152
+ };
1153
+ return {
1154
+ cards: [{ cardType: "member-card-summary", data: cardData }],
1155
+ text: pointsUnavailable ? "\u4F1A\u5458\u5361\u53EF\u6B63\u5E38\u67E5\u770B\uFF0C\u4F46\u5F53\u524D\u79EF\u5206\u4F59\u989D\u6682\u4E0D\u53EF\u7528\uFF0C\u6682\u65E0\u6700\u8FD1\u79EF\u5206\u6D41\u6C34\u8BB0\u5F55" : usageUnavailable ? `\u5F53\u524D\u79EF\u5206 ${points.balance}\uFF0C\u6682\u65E0\u6700\u8FD1\u79EF\u5206\u6D41\u6C34\u8BB0\u5F55` : usage2 && usage2.items.length > 0 ? `\u5F53\u524D\u79EF\u5206 ${points.balance}\uFF0C\u6700\u8FD1\u6D41\u6C34\uFF1A${usage2.items.map((item) => `${item.title}${item.amount}\u5206${item.usedAt ? `\uFF08${item.usedAt}\uFF09` : ""}`).join("\uFF1B")}` : `\u5F53\u524D\u79EF\u5206 ${points.balance}\uFF0C\u6682\u672A\u67E5\u8BE2\u5230\u6700\u8FD1\u79EF\u5206\u6D41\u6C34`
1156
+ };
1157
+ }
1158
+ const usage = await benefitsPort.listPointUsage(userId, 1, 10);
1159
+ const usageText = usage.items.length > 0 ? `\uFF0C\u6700\u8FD1\u6D41\u6C34\uFF1A${usage.items.map((item) => `${item.title}${item.amount}\u5206${item.usedAt ? `\uFF08${item.usedAt}\uFF09` : ""}`).join("\uFF1B")}` : "\uFF0C\u6682\u672A\u67E5\u8BE2\u5230\u6700\u8FD1\u79EF\u5206\u6D41\u6C34";
1160
+ if (!card) {
1161
+ return {
1162
+ cards: [],
1163
+ text: `\u5F53\u524D\u79EF\u5206 ${points.balance}${points.membershipLabel ? `\uFF0C\u4F1A\u5458\u5361 ${points.membershipLabel}` : ""}${usageText}`
1164
+ };
1165
+ }
1166
+ return {
1167
+ cards: [{ cardType: "member-card-summary", data: {
1168
+ userId,
1169
+ cardRef: card.cardRef,
1170
+ cardDisplay: card.cardDisplay,
1171
+ status: card.status,
1172
+ ...card.levelName ? { levelName: card.levelName } : {},
1173
+ availablePoints: points.balance,
1174
+ ...card.cardImageUrl ? { cardImageUrl: card.cardImageUrl } : {},
1175
+ ...card.pointsRuleImageUrl ? { pointsRuleImageUrl: card.pointsRuleImageUrl } : {},
1176
+ ...card.pointsRuleContent ? { pointsRuleContent: card.pointsRuleContent } : {},
1177
+ ...card.pointsUrl ? { pointsUrl: card.pointsUrl } : {},
1178
+ ...card.presentation ? { presentation: card.presentation } : {},
1179
+ ...card.progress ? { progress: card.progress } : {},
1180
+ ...card.benefits ? { benefits: [...card.benefits] } : {}
1181
+ } }],
1182
+ text: `\u5F53\u524D\u79EF\u5206 ${points.balance}${points.membershipLabel ? `\uFF0C\u4F1A\u5458\u5361 ${points.membershipLabel}` : ""}${usageText}`
1183
+ };
1184
+ }
1185
+ default: {
1186
+ return { cards: [], text: "\u4E0D\u652F\u6301\u7684\u4F1A\u5458\u64CD\u4F5C" };
1187
+ }
1188
+ }
1189
+ }
1190
+ return { cards: [], text: MEMBER_QUERY_NOTICES[status.outcome] };
1191
+ }
1192
+ };
1193
+ }
1194
+
1195
+ // src/server/action.ts
1196
+ import { createHash } from "crypto";
1197
+ import { createAlipayUserAuthorization, InMemoryIdempotencyStore } from "@tbox.cn/app-sdk/server";
1198
+ import { parseMallScope as parseMallScope2 } from "@tbox.cn/app-contracts-mall";
1199
+ import { z as z2 } from "zod";
1200
+ var MEMBER_PROFILE_NAMESPACE = "member.enrollment.profile";
1201
+ var PROFILE_TTL_MS = 30 * 60 * 1e3;
1202
+ var MOBILE_PATTERN = /^1[3-9]\d{9}$/;
1203
+ var ENROLLMENT_FORM_VERSION = "v2026.08";
1204
+ var ENROLLMENT_CONSENTS = [
1205
+ { consentId: "privacy", version: "1", title: "\u9690\u79C1\u653F\u7B56", required: true },
1206
+ { consentId: "membership-terms", version: "1", title: "\u4F1A\u5458\u670D\u52A1\u6761\u6B3E", required: false }
1207
+ ];
1208
+ var enrollmentIdempotency = new InMemoryIdempotencyStore();
1209
+ function buildEnrollmentIdempotencyKey(userId, mallId, formVersion) {
1210
+ return createHash("sha256").update(`${userId}\0${mallId}\0${formVersion}`).digest("hex");
1211
+ }
1212
+ function maskMobile(mobile) {
1213
+ return mobile.length >= 7 ? `${mobile.slice(0, 3)}****${mobile.slice(-4)}` : "***";
1214
+ }
1215
+ function mallIdOf(ctx) {
1216
+ return parseMallScope2(ctx.attributes)?.mallId ?? "unknown";
1217
+ }
1218
+ function buildFormCard(input) {
1219
+ return {
1220
+ cardType: "member-enrollment-form",
1221
+ data: {
1222
+ userId: input.userId,
1223
+ state: input.state,
1224
+ formVersion: ENROLLMENT_FORM_VERSION,
1225
+ fields: [
1226
+ {
1227
+ fieldId: "mobile",
1228
+ label: "\u624B\u673A\u53F7",
1229
+ type: "tel",
1230
+ required: true,
1231
+ ...input.authorization.status === "authorized" ? { readOnly: true, prefill: input.prefilled?.mobile ?? "", placeholder: maskMobile(input.authorization.mobileMasked ?? "") } : { placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7" }
1232
+ },
1233
+ { fieldId: "displayName", label: "\u59D3\u540D", type: "text", required: false, ...input.prefilled?.displayName ? { prefill: input.prefilled.displayName } : {} },
1234
+ ...input.prefilled?.birthday ? [{ fieldId: "birthday", label: "\u751F\u65E5", type: "date", required: false, prefill: input.prefilled.birthday }] : []
1235
+ ],
1236
+ consents: ENROLLMENT_CONSENTS,
1237
+ authorization: input.authorization,
1238
+ ...input.manualNotice ? { manualNotice: input.manualNotice } : {}
1239
+ }
1240
+ };
1241
+ }
1242
+ function createMemberActions(ctx, resolveMember) {
1243
+ const state = ctx.state;
1244
+ return {
1245
+ /** 1. 发起支付宝授权(效果动作;resultActionId 由 dispatcher 补签校验) */
1246
+ requestAuthorization: {
1247
+ actionId: "member.request-alipay-authorization",
1248
+ moduleId: "member",
1249
+ cardType: "member-enrollment-form",
1250
+ label: "\u652F\u4ED8\u5B9D\u6388\u6743\u5E76\u586B\u5145",
1251
+ singleUse: true,
1252
+ inputSchema: z2.object({}).passthrough(),
1253
+ async execute() {
1254
+ return {
1255
+ text: "\u6B63\u5728\u62C9\u8D77\u652F\u4ED8\u5B9D\u6388\u6743\u2026",
1256
+ effects: [
1257
+ {
1258
+ type: "authorization.alipay-user-profile",
1259
+ scopes: ["auth_user"],
1260
+ resultActionId: "member.complete-alipay-authorization"
1261
+ }
1262
+ ]
1263
+ };
1264
+ }
1265
+ },
1266
+ /** 2. 授权回调(client getAuthCode → status/authCode 回传;补签 grant 单次消费;不渲染按钮) */
1267
+ completeAuthorization: {
1268
+ actionId: "member.complete-alipay-authorization",
1269
+ moduleId: "member",
1270
+ cardType: "member-enrollment-form",
1271
+ label: "\u6388\u6743\u7ED3\u679C\u56DE\u8C03",
1272
+ singleUse: true,
1273
+ hidden: true,
1274
+ inputSchema: z2.object({
1275
+ status: z2.enum(["success", "cancel", "failure", "unavailable"]),
1276
+ authCode: z2.string().optional()
1277
+ }),
1278
+ async execute(input, reqCtx) {
1279
+ const { status, authCode } = input ?? {};
1280
+ const userId = reqCtx?.identity?.userId ?? "";
1281
+ if (!userId) {
1282
+ return { text: "\u6388\u6743\u5931\u8D25\uFF1A\u7F3A\u5C11\u4F1A\u8BDD\u8EAB\u4EFD" };
1283
+ }
1284
+ if (status !== "success" || !authCode) {
1285
+ return {
1286
+ card: buildFormCard({
1287
+ userId,
1288
+ state: "not_enrolled",
1289
+ authorization: { status: "unavailable" },
1290
+ manualNotice: status === "unavailable" ? "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u652F\u4ED8\u5B9D\u6388\u6743\uFF0C\u53EF\u624B\u52A8\u586B\u5199\u624B\u673A\u53F7\u5165\u4F1A" : "\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u53EF\u624B\u52A8\u586B\u5199\u624B\u673A\u53F7\u5165\u4F1A"
1291
+ }),
1292
+ text: "\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u5DF2\u5207\u6362\u624B\u52A8\u586B\u5199\u6A21\u5F0F"
1293
+ };
1294
+ }
1295
+ let profile;
1296
+ try {
1297
+ const authorized = await createAlipayUserAuthorization().authorizeUserProfile(authCode);
1298
+ profile = {
1299
+ ...authorized.mobile ? { mobile: authorized.mobile } : {},
1300
+ ...authorized.displayName ? { displayName: authorized.displayName } : {},
1301
+ ...authorized.gender ? { gender: authorized.gender } : {},
1302
+ ...authorized.birthday ? { birthday: authorized.birthday } : {}
1303
+ };
1304
+ } catch (e) {
1305
+ ctx.logger?.warn(`\u6388\u6743\u5B8C\u6210\u964D\u7EA7\u624B\u52A8\u5361\uFF1Aerr=${e.message}`);
1306
+ return {
1307
+ card: buildFormCard({
1308
+ userId,
1309
+ state: "not_enrolled",
1310
+ authorization: { status: "unavailable" },
1311
+ manualNotice: `\u6388\u6743\u8D44\u6599\u83B7\u53D6\u5931\u8D25\uFF08${e.message}\uFF09\uFF0C\u53EF\u624B\u52A8\u586B\u5199`
1312
+ }),
1313
+ text: "\u6388\u6743\u8D44\u6599\u83B7\u53D6\u5931\u8D25\uFF0C\u5DF2\u5207\u6362\u624B\u52A8\u586B\u5199\u6A21\u5F0F"
1314
+ };
1315
+ }
1316
+ await state.put(MEMBER_PROFILE_NAMESPACE, userId, profile, PROFILE_TTL_MS);
1317
+ return {
1318
+ card: buildFormCard({
1319
+ userId,
1320
+ state: "not_enrolled",
1321
+ authorization: { status: "authorized", mobileMasked: profile.mobile ?? "" },
1322
+ prefilled: profile
1323
+ }),
1324
+ text: "\u6388\u6743\u6210\u529F\uFF0C\u5DF2\u4E3A\u4F60\u9884\u586B\u624B\u673A\u53F7\uFF0C\u786E\u8BA4\u65E0\u8BEF\u540E\u63D0\u4EA4\u5165\u4F1A"
1325
+ };
1326
+ }
1327
+ },
1328
+ /** 3. 提交入会(身份解析 + consent 校验 + 幂等 + PII consume) */
1329
+ enroll: {
1330
+ actionId: "member.enroll",
1331
+ moduleId: "member",
1332
+ cardType: "member-enrollment-form",
1333
+ label: "\u63D0\u4EA4\u5165\u4F1A",
1334
+ singleUse: true,
1335
+ inputSchema: z2.object({
1336
+ fieldValues: z2.record(z2.string(), z2.string()).optional(),
1337
+ acceptedConsentIds: z2.array(z2.string()).optional(),
1338
+ formVersion: z2.string().optional()
1339
+ }),
1340
+ async execute(input, reqCtx) {
1341
+ const { fieldValues = {}, acceptedConsentIds = [] } = input ?? {};
1342
+ const userId = reqCtx?.identity?.userId ?? "";
1343
+ if (!userId) {
1344
+ return {
1345
+ card: { cardType: "member-enrollment-status", data: { userId: "", state: "failed", message: "\u7F3A\u5C11\u4F1A\u8BDD\u8EAB\u4EFD" } },
1346
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u7F3A\u5C11\u8EAB\u4EFD\u4FE1\u606F"
1347
+ };
1348
+ }
1349
+ const member = await resolveMember(reqCtx);
1350
+ if (!member) {
1351
+ return {
1352
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u4F1A\u5458\u670D\u52A1\u4E0D\u53EF\u7528" } },
1353
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u4F1A\u5458\u670D\u52A1\u4E0D\u53EF\u7528"
1354
+ };
1355
+ }
1356
+ const missing = ENROLLMENT_CONSENTS.filter((c) => c.required && !acceptedConsentIds.includes(c.consentId));
1357
+ if (missing.length > 0) {
1358
+ return {
1359
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: `\u8BF7\u5148\u540C\u610F\uFF1A${missing.map((c) => c.title).join("\u3001")}` } },
1360
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u672A\u5B8C\u6210\u5FC5\u9009\u540C\u610F\u9879"
1361
+ };
1362
+ }
1363
+ const authorized = await state.consume(MEMBER_PROFILE_NAMESPACE, userId) ?? void 0;
1364
+ const manualMobile = fieldValues.mobile ?? "";
1365
+ const mobile = authorized?.mobile ?? manualMobile;
1366
+ const identity = authorized?.mobile ? "alipay_authorized" : "enrollment_submission";
1367
+ if (!mobile || !MOBILE_PATTERN.test(mobile)) {
1368
+ return {
1369
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u624B\u673A\u53F7\u683C\u5F0F\u4E0D\u6B63\u786E\uFF08\u6388\u6743\u9884\u586B\u5931\u6548\u8BF7\u91CD\u65B0\u6388\u6743\u6216\u624B\u586B\uFF09" } },
1370
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u624B\u673A\u53F7\u683C\u5F0F\u4E0D\u6B63\u786E"
1371
+ };
1372
+ }
1373
+ const idempotencyKey = buildEnrollmentIdempotencyKey(userId, mallIdOf(reqCtx), ENROLLMENT_FORM_VERSION);
1374
+ const payloadTag = createHash("sha256").update(`${identity}:${mobile}`).digest("hex").slice(0, 16);
1375
+ const acquired = await enrollmentIdempotency.tryAcquire(idempotencyKey, payloadTag);
1376
+ if (!acquired.acquired) {
1377
+ return {
1378
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u91CD\u590D\u63D0\u4EA4\uFF0C\u8BF7\u7A0D\u540E\u5728\u5165\u4F1A\u72B6\u6001\u4E2D\u5237\u65B0\u786E\u8BA4\u7ED3\u679C" } },
1379
+ text: "\u8BF7\u52FF\u91CD\u590D\u63D0\u4EA4\uFF0C\u53EF\u5237\u65B0\u67E5\u770B\u5165\u4F1A\u72B6\u6001"
1380
+ };
1381
+ }
1382
+ let result;
1383
+ try {
1384
+ result = await member.enroll({
1385
+ userId,
1386
+ identity,
1387
+ fieldValues: {
1388
+ ...fieldValues,
1389
+ mobile,
1390
+ ...fieldValues.displayName || authorized?.displayName ? { displayName: fieldValues.displayName || authorized?.displayName } : {},
1391
+ ...fieldValues.birthday || authorized?.birthday ? { birthday: fieldValues.birthday || authorized?.birthday } : {},
1392
+ ...fieldValues.gender || authorized?.gender ? { gender: fieldValues.gender || authorized?.gender } : {}
1393
+ },
1394
+ acceptedConsentIds,
1395
+ idempotencyKey,
1396
+ formVersion: ENROLLMENT_FORM_VERSION
1397
+ });
1398
+ } catch (e) {
1399
+ enrollmentIdempotency.release?.(idempotencyKey);
1400
+ ctx.logger?.warn(`\u5165\u4F1A\u5931\u8D25\uFF08\u5E42\u7B49\u5DF2\u91CA\u653E key=${idempotencyKey.slice(0, 8)}\u2026\uFF09\uFF1Aerr=${e.message}`);
1401
+ return {
1402
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u5165\u4F1A\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" } },
1403
+ text: "\u5165\u4F1A\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"
1404
+ };
1405
+ }
1406
+ const cardForResult = (r) => ({
1407
+ cardType: "member-enrollment-status",
1408
+ data: r.status === "active" ? { userId, state: "active", memberRef: r.memberRef, message: "\u5165\u4F1A\u6210\u529F" } : r.status === "pending_confirmation" ? { userId, attemptRef: r.attemptRef, state: "pending_confirmation", message: "\u5DF2\u63D0\u4EA4\uFF0C\u7B49\u5F85\u5382\u5546\u786E\u8BA4\uFF08\u53EF\u70B9\u51FB\u5237\u65B0\u72B6\u6001\u67E5\u770B\u7ED3\u679C\uFF09" } : { userId, state: "failed", message: r.reason ?? "\u5165\u4F1A\u88AB\u62D2\u7EDD" }
1409
+ });
1410
+ await enrollmentIdempotency.commit(idempotencyKey, result);
1411
+ return {
1412
+ card: cardForResult(result),
1413
+ text: result.status === "active" ? "\u5165\u4F1A\u6210\u529F\uFF0C\u6B22\u8FCE\u52A0\u5165\uFF01" : result.status === "pending_confirmation" ? "\u5DF2\u63D0\u4EA4\u5165\u4F1A\u7533\u8BF7\uFF0C\u5382\u5546\u786E\u8BA4\u4E2D" : `\u5165\u4F1A\u5931\u8D25\uFF1A${result.status === "rejected" ? result.reason : "\u672A\u77E5\u539F\u56E0"}`
1414
+ };
1415
+ }
1416
+ },
1417
+ /** 4. 入会状态显式刷新(R4:无自动轮询) */
1418
+ queryEnrollmentStatus: {
1419
+ actionId: "member.query-enrollment-status",
1420
+ moduleId: "member",
1421
+ cardType: "member-enrollment-status",
1422
+ label: "\u5237\u65B0\u72B6\u6001",
1423
+ singleUse: true,
1424
+ inputSchema: z2.object({ attemptRef: z2.string().min(1) }),
1425
+ async execute(input, reqCtx) {
1426
+ const { attemptRef } = input ?? {};
1427
+ const userId = reqCtx?.identity?.userId ?? "";
1428
+ const member = await resolveMember(reqCtx);
1429
+ if (!userId || !attemptRef || !member) {
1430
+ return {
1431
+ update: {
1432
+ data: {
1433
+ userId,
1434
+ state: "failed",
1435
+ message: "\u7F3A\u5C11\u5165\u4F1A\u8BB0\u5F55\u5F15\u7528"
1436
+ }
1437
+ }
1438
+ };
1439
+ }
1440
+ const status = await member.getEnrollmentStatus(userId, attemptRef);
1441
+ return {
1442
+ update: {
1443
+ data: {
1444
+ userId,
1445
+ attemptRef,
1446
+ state: status.status,
1447
+ ...status.memberRef ? { memberRef: status.memberRef } : {},
1448
+ ...status.status === "active" ? { message: "\u5165\u4F1A\u6210\u529F\uFF0C\u6B22\u8FCE\u52A0\u5165\uFF01" } : status.status === "pending_confirmation" ? { message: "\u5382\u5546\u786E\u8BA4\u4E2D\uFF0C\u53EF\u7A0D\u540E\u518D\u5237\u65B0" } : { message: status.reason ?? "\u5165\u4F1A\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u63D0\u4EA4" }
1449
+ }
1450
+ }
1451
+ };
1452
+ }
1453
+ }
1454
+ };
1455
+ }
1456
+
1457
+ // src/server/subject-binding.ts
1458
+ import { createHash as createHash2 } from "crypto";
1459
+ function createSubjectBinding(ctx) {
1460
+ return {
1461
+ resolveExternalMemberRef(userId) {
1462
+ void ctx;
1463
+ return userId ? `member-ref:${createHash2("sha256").update(userId).digest("hex").slice(0, 24)}` : void 0;
1464
+ }
1465
+ };
1466
+ }
1467
+
1468
+ // src/server/cards/index.ts
1469
+ var memberCards = {
1470
+ "member-info": meta_default,
1471
+ "member-card-summary": meta_default2,
1472
+ "member-point-usage-list": meta_default3,
1473
+ "member-enrollment-form": meta_default4,
1474
+ "member-enrollment-status": meta_default5,
1475
+ "member-enrollment-cta": meta_default6,
1476
+ "member-login-cta": meta_default7
1477
+ };
1478
+
1479
+ // src/server/index.ts
1480
+ var serverCtx;
1481
+ var getCtx = () => {
1482
+ if (!serverCtx) throw new Error("member \u6A21\u5757\u672A\u6CE8\u518C\uFF08ServerContext \u672A\u7ED1\u5B9A\uFF09");
1483
+ return serverCtx;
1484
+ };
1485
+ var memberPorts = {
1486
+ getAccount: (reqCtx) => resolveMemberAccountPort(reqCtx, getCtx()),
1487
+ getProfile: (reqCtx) => resolveMemberProfilePort(reqCtx, getCtx()),
1488
+ getCard: (reqCtx) => resolveMemberCardPort(reqCtx, getCtx()),
1489
+ getBenefits: (reqCtx) => resolveMemberBenefitsPort(reqCtx, getCtx()),
1490
+ getEnrollment: (reqCtx) => resolveMemberEnrollmentPort(reqCtx, getCtx())
1491
+ };
1492
+ var queryPointsTool = withEarlyExecution(
1493
+ withCardEmissionHint(createQueryPointsTool(memberPorts))
1494
+ );
1495
+ var queryMemberInfoTool = withEarlyExecution(
1496
+ withCardEmissionHint(createQueryMemberInfoTool(memberPorts))
1497
+ );
1498
+ var queryMemberCardTool = withEarlyExecution(
1499
+ withCardEmissionHint(createQueryMemberCardTool(memberPorts))
1500
+ );
1501
+ var queryEnrollmentFormTool = withCardEmissionHint(createQueryEnrollmentFormTool(memberPorts), {
1502
+ actions: true
1503
+ });
1504
+ var queryEnrollmentCtaTool = withCardEmissionHint(createQueryEnrollmentCtaTool(memberPorts), {
1505
+ actions: true
1506
+ });
1507
+ var queryLoginCtaTool = withCardEmissionHint(createQueryLoginCtaTool(memberPorts), {
1508
+ actions: true
1509
+ });
1510
+ var cards = {
1511
+ "member-info": meta_default,
1512
+ "member-card-summary": meta_default2,
1513
+ "member-point-usage-list": meta_default3,
1514
+ "member-enrollment-form": meta_default4,
1515
+ "member-enrollment-status": meta_default5,
1516
+ "member-enrollment-cta": meta_default6,
1517
+ "member-login-cta": meta_default7
1518
+ };
1519
+ var preTools = {};
1520
+ var skills = Object.fromEntries(
1521
+ (await loadModuleSkills("@tbox.cn/app-module-member", import.meta.url)).map((s) => [s.name, s])
1522
+ );
1523
+ var serverModule = {
1524
+ declaration: {
1525
+ moduleId: "member"
1526
+ },
1527
+ cards,
1528
+ preTools,
1529
+ skills,
1530
+ register(ctx) {
1531
+ serverCtx = ctx;
1532
+ ctx.services.register(
1533
+ "member.service",
1534
+ (reqCtx) => resolveMemberBenefitsPort(reqCtx, ctx)
1535
+ );
1536
+ ctx.services.register(
1537
+ "member.status",
1538
+ (reqCtx) => resolveMemberStatus(reqCtx, ctx)
1539
+ );
1540
+ ctx.tools.register(queryPointsTool);
1541
+ ctx.tools.register(queryMemberInfoTool);
1542
+ ctx.tools.register(queryMemberCardTool);
1543
+ ctx.tools.register(queryEnrollmentFormTool);
1544
+ ctx.tools.register(queryEnrollmentCtaTool);
1545
+ ctx.tools.register(queryLoginCtaTool);
1546
+ ctx.cards.registerMap(cards);
1547
+ ctx.handlers.register(createMemberHandler(ctx));
1548
+ const actions = createMemberActions(ctx, (reqCtx) => resolveMemberEnrollmentPort(reqCtx, ctx));
1549
+ for (const action of Object.values(actions)) {
1550
+ ctx.cardActions.register(action);
1551
+ }
1552
+ ctx.services.register("member.subject-binding", createSubjectBinding(ctx));
1553
+ }
1554
+ };
1555
+
1556
+ export {
1557
+ InMemoryMemberService,
1558
+ resolveMemberAccountPort,
1559
+ resolveMemberProfilePort,
1560
+ resolveMemberCardPort,
1561
+ resolveMemberBenefitsPort,
1562
+ resolveMemberEnrollmentPort,
1563
+ memberCards,
1564
+ serverModule
1565
+ };