level-up-mcp-server-cn 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/projects/c--Users-klexi-OneDrive-Desktop-Levelup-level-up-mcp-server/memory/project_testing_service.md +11 -0
- package/.claude/settings.local.json +10 -0
- package/.env.example +19 -0
- package/CLAUDE.md +222 -0
- package/CODE_REVIEW.md +282 -0
- package/LICENSE +64 -0
- package/README.md +198 -0
- package/dist/constants.d.ts +33 -0
- package/dist/constants.js +78 -0
- package/dist/constants.js.map +1 -0
- package/dist/data/quest-seeds.d.ts +18 -0
- package/dist/data/quest-seeds.js +380 -0
- package/dist/data/quest-seeds.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/common.d.ts +33 -0
- package/dist/schemas/common.js +96 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/services/dispatcher.d.ts +27 -0
- package/dist/services/dispatcher.js +47 -0
- package/dist/services/dispatcher.js.map +1 -0
- package/dist/services/errors.d.ts +56 -0
- package/dist/services/errors.js +99 -0
- package/dist/services/errors.js.map +1 -0
- package/dist/services/format.d.ts +74 -0
- package/dist/services/format.js +144 -0
- package/dist/services/format.js.map +1 -0
- package/dist/services/ownership.d.ts +19 -0
- package/dist/services/ownership.js +79 -0
- package/dist/services/ownership.js.map +1 -0
- package/dist/services/quality-gate.d.ts +45 -0
- package/dist/services/quality-gate.js +131 -0
- package/dist/services/quality-gate.js.map +1 -0
- package/dist/services/rate-limit.d.ts +12 -0
- package/dist/services/rate-limit.js +49 -0
- package/dist/services/rate-limit.js.map +1 -0
- package/dist/services/register.d.ts +49 -0
- package/dist/services/register.js +63 -0
- package/dist/services/register.js.map +1 -0
- package/dist/services/supabase.d.ts +10 -0
- package/dist/services/supabase.js +79 -0
- package/dist/services/supabase.js.map +1 -0
- package/dist/tools/achievements.d.ts +6 -0
- package/dist/tools/achievements.js +242 -0
- package/dist/tools/achievements.js.map +1 -0
- package/dist/tools/admin.d.ts +16 -0
- package/dist/tools/admin.js +328 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/agents.d.ts +3 -0
- package/dist/tools/agents.js +400 -0
- package/dist/tools/agents.js.map +1 -0
- package/dist/tools/bootstrap.d.ts +17 -0
- package/dist/tools/bootstrap.js +565 -0
- package/dist/tools/bootstrap.js.map +1 -0
- package/dist/tools/dispatchers/admin.d.ts +3 -0
- package/dist/tools/dispatchers/admin.js +50 -0
- package/dist/tools/dispatchers/admin.js.map +1 -0
- package/dist/tools/dispatchers/eval.d.ts +3 -0
- package/dist/tools/dispatchers/eval.js +40 -0
- package/dist/tools/dispatchers/eval.js.map +1 -0
- package/dist/tools/dispatchers/quests.d.ts +3 -0
- package/dist/tools/dispatchers/quests.js +60 -0
- package/dist/tools/dispatchers/quests.js.map +1 -0
- package/dist/tools/dispatchers/session.d.ts +3 -0
- package/dist/tools/dispatchers/session.js +38 -0
- package/dist/tools/dispatchers/session.js.map +1 -0
- package/dist/tools/dispatchers/skills.d.ts +3 -0
- package/dist/tools/dispatchers/skills.js +49 -0
- package/dist/tools/dispatchers/skills.js.map +1 -0
- package/dist/tools/dispatchers/tasks.d.ts +3 -0
- package/dist/tools/dispatchers/tasks.js +53 -0
- package/dist/tools/dispatchers/tasks.js.map +1 -0
- package/dist/tools/dispatchers/users.d.ts +3 -0
- package/dist/tools/dispatchers/users.js +65 -0
- package/dist/tools/dispatchers/users.js.map +1 -0
- package/dist/tools/dispatchers/xp.d.ts +3 -0
- package/dist/tools/dispatchers/xp.js +51 -0
- package/dist/tools/dispatchers/xp.js.map +1 -0
- package/dist/tools/growth-plan.d.ts +5 -0
- package/dist/tools/growth-plan.js +791 -0
- package/dist/tools/growth-plan.js.map +1 -0
- package/dist/tools/leaderboards.d.ts +10 -0
- package/dist/tools/leaderboards.js +279 -0
- package/dist/tools/leaderboards.js.map +1 -0
- package/dist/tools/leveling.d.ts +24 -0
- package/dist/tools/leveling.js +356 -0
- package/dist/tools/leveling.js.map +1 -0
- package/dist/tools/metrics.d.ts +3 -0
- package/dist/tools/metrics.js +247 -0
- package/dist/tools/metrics.js.map +1 -0
- package/dist/tools/quests.d.ts +5 -0
- package/dist/tools/quests.js +586 -0
- package/dist/tools/quests.js.map +1 -0
- package/dist/tools/ratings.d.ts +11 -0
- package/dist/tools/ratings.js +564 -0
- package/dist/tools/ratings.js.map +1 -0
- package/dist/tools/skills.d.ts +66 -0
- package/dist/tools/skills.js +1112 -0
- package/dist/tools/skills.js.map +1 -0
- package/dist/tools/system.d.ts +31 -0
- package/dist/tools/system.js +605 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/tasks.d.ts +73 -0
- package/dist/tools/tasks.js +1572 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/users.d.ts +97 -0
- package/dist/tools/users.js +1306 -0
- package/dist/tools/users.js.map +1 -0
- package/dist/tools/xp.d.ts +38 -0
- package/dist/tools/xp.js +670 -0
- package/dist/tools/xp.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/docs/recommended-skillsets.md +622 -0
- package/docs/skills-and-abilities-review.md +672 -0
- package/docs/v0.3-roadmap.md +191 -0
- package/package.json +35 -0
- package/sql/agent_pending_installs.sql +28 -0
- package/sql/award_class_xp.sql +81 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/migrations/20260314000000_anon_rls_policies.sql +311 -0
- package/supabase/migrations/20260314000001_ownership_rpcs.sql +382 -0
- package/supabase/migrations/20260314000002_evidence_and_growth_plan.sql +97 -0
- package/supabase/migrations/20260317000000_seed_quests.sql +62 -0
- package/supabase/migrations/20260317000001_star_cooldown_and_fixes.sql +16 -0
- package/supabase/migrations/20260318000000_restore_rank_names.sql +25 -0
- package/supabase/migrations/20260320000000_chinese_rank_names.sql +24 -0
- package/vitest.config.ts +11 -0
package/dist/tools/xp.js
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// tools/xp.ts — Section 8: XP & Progression (5 tools)
|
|
3
|
+
// ============================================================
|
|
4
|
+
// The "read" side of XP — history, summaries, projections,
|
|
5
|
+
// split rules, weekly recaps.
|
|
6
|
+
// ============================================================
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { supabase } from "../services/supabase.js";
|
|
9
|
+
import { ok, handleSupabaseError } from "../services/errors.js";
|
|
10
|
+
import { paginate } from "../services/format.js";
|
|
11
|
+
import { toMcpResponse, logRegistered } from "../services/register.js";
|
|
12
|
+
import { uuidSchema, entityTypeSchema, performerTypeSchema, offsetSchema } from "../schemas/common.js";
|
|
13
|
+
import { verifyOwnership } from "../services/ownership.js";
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Exported handler functions for dispatcher use
|
|
16
|
+
// ============================================================
|
|
17
|
+
export async function handleGetXpHistory(params) {
|
|
18
|
+
const caller_user_id = params.caller_user_id;
|
|
19
|
+
const entity_type = params.entity_type;
|
|
20
|
+
const entity_id = params.entity_id;
|
|
21
|
+
const source_type = params.source_type;
|
|
22
|
+
const since = params.since;
|
|
23
|
+
const limit = params.limit ?? 50;
|
|
24
|
+
const offset = params.offset ?? 0;
|
|
25
|
+
const denied = await verifyOwnership({
|
|
26
|
+
caller_user_id,
|
|
27
|
+
target_user_id: entity_type === "user" ? entity_id : undefined,
|
|
28
|
+
target_agent_id: entity_type === "agent" ? entity_id : undefined,
|
|
29
|
+
});
|
|
30
|
+
if (denied)
|
|
31
|
+
return denied;
|
|
32
|
+
let query = supabase
|
|
33
|
+
.from("xp_ledger")
|
|
34
|
+
.select("id, entity_type, entity_id, xp_type, amount, source_type, source_id, description, running_total, tx_hash, chain_verified, chain_id, created_at", { count: "exact" })
|
|
35
|
+
.eq("entity_type", entity_type)
|
|
36
|
+
.eq("entity_id", entity_id);
|
|
37
|
+
if (source_type)
|
|
38
|
+
query = query.eq("source_type", source_type);
|
|
39
|
+
if (since)
|
|
40
|
+
query = query.gte("created_at", since);
|
|
41
|
+
query = query.order("created_at", { ascending: false }).range(offset, offset + limit - 1);
|
|
42
|
+
const { data, count, error } = await query;
|
|
43
|
+
if (error)
|
|
44
|
+
return toMcpResponse(handleSupabaseError(error, "get_xp_history"));
|
|
45
|
+
return toMcpResponse(ok(paginate(data || [], count || 0, offset)));
|
|
46
|
+
}
|
|
47
|
+
export async function handleGetXpSummary(params) {
|
|
48
|
+
const caller_user_id = params.caller_user_id;
|
|
49
|
+
const entity_type = params.entity_type;
|
|
50
|
+
const entity_id = params.entity_id;
|
|
51
|
+
const period = params.period || "all_time";
|
|
52
|
+
const denied = await verifyOwnership({
|
|
53
|
+
caller_user_id,
|
|
54
|
+
target_user_id: entity_type === "user" ? entity_id : undefined,
|
|
55
|
+
target_agent_id: entity_type === "agent" ? entity_id : undefined,
|
|
56
|
+
});
|
|
57
|
+
if (denied)
|
|
58
|
+
return denied;
|
|
59
|
+
let query = supabase
|
|
60
|
+
.from("xp_ledger")
|
|
61
|
+
.select("amount, source_type, xp_type, chain_verified")
|
|
62
|
+
.eq("entity_type", entity_type)
|
|
63
|
+
.eq("entity_id", entity_id);
|
|
64
|
+
if (period === "week") {
|
|
65
|
+
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
66
|
+
query = query.gte("created_at", weekAgo);
|
|
67
|
+
}
|
|
68
|
+
else if (period === "month") {
|
|
69
|
+
const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
70
|
+
query = query.gte("created_at", monthAgo);
|
|
71
|
+
}
|
|
72
|
+
const { data: entries, error } = await query;
|
|
73
|
+
if (error)
|
|
74
|
+
return toMcpResponse(handleSupabaseError(error, "get_xp_summary"));
|
|
75
|
+
const rows = entries || [];
|
|
76
|
+
const totalXp = rows.reduce((sum, r) => sum + (r.amount || 0), 0);
|
|
77
|
+
const bySource = {};
|
|
78
|
+
for (const r of rows) {
|
|
79
|
+
bySource[r.source_type] = (bySource[r.source_type] || 0) + r.amount;
|
|
80
|
+
}
|
|
81
|
+
const byType = {};
|
|
82
|
+
for (const r of rows) {
|
|
83
|
+
byType[r.xp_type || "main"] = (byType[r.xp_type || "main"] || 0) + r.amount;
|
|
84
|
+
}
|
|
85
|
+
const chainVerified = rows.filter((r) => r.chain_verified).reduce((sum, r) => sum + r.amount, 0);
|
|
86
|
+
return toMcpResponse(ok({
|
|
87
|
+
entity_type, entity_id, period,
|
|
88
|
+
total_xp: totalXp,
|
|
89
|
+
entry_count: rows.length,
|
|
90
|
+
by_source: bySource,
|
|
91
|
+
by_type: byType,
|
|
92
|
+
verification: { chain_verified_xp: chainVerified, unverified_xp: totalXp - chainVerified },
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
export async function handleGetSplitRules(params) {
|
|
96
|
+
const performer_type = params.performer_type;
|
|
97
|
+
const rule_id = params.rule_id;
|
|
98
|
+
let query = supabase.from("xp_split_rules").select("*");
|
|
99
|
+
if (performer_type)
|
|
100
|
+
query = query.eq("performer_type", performer_type);
|
|
101
|
+
if (rule_id)
|
|
102
|
+
query = query.eq("id", rule_id);
|
|
103
|
+
query = query.order("performer_type");
|
|
104
|
+
const { data: rules, error } = await query;
|
|
105
|
+
if (error)
|
|
106
|
+
return toMcpResponse(handleSupabaseError(error, "get_split_rules"));
|
|
107
|
+
const enriched = await Promise.all((rules || []).map(async (rule) => {
|
|
108
|
+
const { data: entries } = await supabase
|
|
109
|
+
.from("xp_split_rule_entries").select("recipient_role, split_pct, sort_order")
|
|
110
|
+
.eq("rule_id", rule.id).order("sort_order");
|
|
111
|
+
return { ...rule, entries: entries || [] };
|
|
112
|
+
}));
|
|
113
|
+
return toMcpResponse(ok(enriched));
|
|
114
|
+
}
|
|
115
|
+
export async function handleGetXpProjection(params) {
|
|
116
|
+
const caller_user_id = params.caller_user_id;
|
|
117
|
+
const entity_type = params.entity_type;
|
|
118
|
+
const entity_id = params.entity_id;
|
|
119
|
+
const target_level = params.target_level;
|
|
120
|
+
const denied = await verifyOwnership({
|
|
121
|
+
caller_user_id,
|
|
122
|
+
target_user_id: entity_type === "user" ? entity_id : undefined,
|
|
123
|
+
target_agent_id: entity_type === "agent" ? entity_id : undefined,
|
|
124
|
+
});
|
|
125
|
+
if (denied)
|
|
126
|
+
return denied;
|
|
127
|
+
let currentLevel = 1;
|
|
128
|
+
let currentXp = 0;
|
|
129
|
+
if (entity_type === "user") {
|
|
130
|
+
const { data } = await supabase.from("users").select("main_level, main_xp").eq("id", entity_id).single();
|
|
131
|
+
if (data) {
|
|
132
|
+
currentLevel = data.main_level;
|
|
133
|
+
currentXp = data.main_xp;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const { data } = await supabase.from("agents").select("level, xp").eq("id", entity_id).single();
|
|
138
|
+
if (data) {
|
|
139
|
+
currentLevel = data.level;
|
|
140
|
+
currentXp = data.xp;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const fourWeeksAgo = new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString();
|
|
144
|
+
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString();
|
|
145
|
+
const { data: recentEntries } = await supabase
|
|
146
|
+
.from("xp_ledger").select("amount, created_at")
|
|
147
|
+
.eq("entity_type", entity_type).eq("entity_id", entity_id)
|
|
148
|
+
.gte("created_at", fourWeeksAgo);
|
|
149
|
+
const rows = recentEntries || [];
|
|
150
|
+
const totalRecent = rows.reduce((sum, r) => sum + (r.amount || 0), 0);
|
|
151
|
+
const weeklyRate = Math.round(totalRecent / 4);
|
|
152
|
+
const firstHalf = rows.filter((r) => r.created_at < twoWeeksAgo).reduce((s, r) => s + r.amount, 0);
|
|
153
|
+
const secondHalf = rows.filter((r) => r.created_at >= twoWeeksAgo).reduce((s, r) => s + r.amount, 0);
|
|
154
|
+
const trend = secondHalf > firstHalf * 1.1 ? "increasing" : secondHalf < firstHalf * 0.9 ? "decreasing" : "stable";
|
|
155
|
+
const { data: nextCurve } = await supabase
|
|
156
|
+
.from("level_curves").select("xp_required")
|
|
157
|
+
.eq("entity_type", entity_type).eq("level", currentLevel + 1).maybeSingle();
|
|
158
|
+
const xpForNext = nextCurve?.xp_required || 0;
|
|
159
|
+
const xpRemaining = Math.max(0, xpForNext - currentXp);
|
|
160
|
+
const weeksToNext = weeklyRate > 0 ? Math.ceil(xpRemaining / weeklyRate) : null;
|
|
161
|
+
let weeksToTarget = null;
|
|
162
|
+
if (target_level && target_level > currentLevel) {
|
|
163
|
+
const { data: targetCurve } = await supabase
|
|
164
|
+
.from("level_curves").select("cumulative_xp")
|
|
165
|
+
.eq("entity_type", entity_type).eq("level", target_level).maybeSingle();
|
|
166
|
+
if (targetCurve && weeklyRate > 0) {
|
|
167
|
+
const xpToTarget = Math.max(0, targetCurve.cumulative_xp - currentXp);
|
|
168
|
+
weeksToTarget = Math.ceil(xpToTarget / weeklyRate);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return toMcpResponse(ok({
|
|
172
|
+
entity_type, entity_id,
|
|
173
|
+
current_level: currentLevel,
|
|
174
|
+
current_xp: currentXp,
|
|
175
|
+
weekly_xp_rate: weeklyRate,
|
|
176
|
+
trend,
|
|
177
|
+
next_level: {
|
|
178
|
+
level: currentLevel + 1,
|
|
179
|
+
xp_required: xpForNext,
|
|
180
|
+
xp_remaining: xpRemaining,
|
|
181
|
+
estimated_weeks: weeksToNext,
|
|
182
|
+
},
|
|
183
|
+
target: target_level ? {
|
|
184
|
+
level: target_level,
|
|
185
|
+
estimated_weeks: weeksToTarget,
|
|
186
|
+
} : null,
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
export async function handleGetWeeklyRecap(params) {
|
|
190
|
+
const caller_user_id = params.caller_user_id;
|
|
191
|
+
const user_id = params.user_id;
|
|
192
|
+
const include_agents = params.include_agents ?? true;
|
|
193
|
+
const week_of = params.week_of;
|
|
194
|
+
const denied = await verifyOwnership({
|
|
195
|
+
caller_user_id,
|
|
196
|
+
target_user_id: user_id,
|
|
197
|
+
});
|
|
198
|
+
if (denied)
|
|
199
|
+
return denied;
|
|
200
|
+
const refDate = week_of ? new Date(week_of) : new Date();
|
|
201
|
+
const dayOfWeek = refDate.getDay();
|
|
202
|
+
const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
|
203
|
+
const weekStart = new Date(refDate);
|
|
204
|
+
weekStart.setDate(refDate.getDate() + mondayOffset);
|
|
205
|
+
weekStart.setHours(0, 0, 0, 0);
|
|
206
|
+
const weekEnd = new Date(weekStart);
|
|
207
|
+
weekEnd.setDate(weekStart.getDate() + 7);
|
|
208
|
+
const startIso = weekStart.toISOString();
|
|
209
|
+
const endIso = weekEnd.toISOString();
|
|
210
|
+
const { data: xpEntries } = await supabase
|
|
211
|
+
.from("xp_ledger").select("amount, source_type, xp_type")
|
|
212
|
+
.eq("entity_type", "user").eq("entity_id", user_id)
|
|
213
|
+
.gte("created_at", startIso).lt("created_at", endIso);
|
|
214
|
+
const xpRows = xpEntries || [];
|
|
215
|
+
const totalXp = xpRows.reduce((s, r) => s + r.amount, 0);
|
|
216
|
+
const bySource = {};
|
|
217
|
+
for (const r of xpRows)
|
|
218
|
+
bySource[r.source_type] = (bySource[r.source_type] || 0) + r.amount;
|
|
219
|
+
const { data: tasks } = await supabase
|
|
220
|
+
.from("tasks").select("id, status, completion_pct, output_type")
|
|
221
|
+
.eq("user_id", user_id)
|
|
222
|
+
.gte("created_at", startIso).lt("created_at", endIso);
|
|
223
|
+
const taskRows = tasks || [];
|
|
224
|
+
const taskStats = {
|
|
225
|
+
total: taskRows.length,
|
|
226
|
+
completed: taskRows.filter((t) => t.status === "completed").length,
|
|
227
|
+
failed: taskRows.filter((t) => t.status === "failed").length,
|
|
228
|
+
in_progress: taskRows.filter((t) => t.status === "in_progress").length,
|
|
229
|
+
};
|
|
230
|
+
const { data: user } = await supabase
|
|
231
|
+
.from("users").select("main_level, main_xp, username").eq("id", user_id).single();
|
|
232
|
+
const { data: classes } = await supabase
|
|
233
|
+
.from("user_class_progress").select("class_name, class_level, class_xp").eq("user_id", user_id);
|
|
234
|
+
let agentSummaries = [];
|
|
235
|
+
if (include_agents) {
|
|
236
|
+
const { data: agents } = await supabase
|
|
237
|
+
.from("agents").select("id, name").eq("owner_user_id", user_id).eq("status", "active");
|
|
238
|
+
agentSummaries = await Promise.all((agents || []).map(async (agent) => {
|
|
239
|
+
const { data: agentXp } = await supabase
|
|
240
|
+
.from("xp_ledger").select("amount")
|
|
241
|
+
.eq("entity_type", "agent").eq("entity_id", agent.id)
|
|
242
|
+
.gte("created_at", startIso).lt("created_at", endIso);
|
|
243
|
+
const { count: agentTasks } = await supabase
|
|
244
|
+
.from("tasks").select("id", { count: "exact", head: true })
|
|
245
|
+
.eq("agent_id", agent.id).gte("created_at", startIso).lt("created_at", endIso);
|
|
246
|
+
return {
|
|
247
|
+
id: agent.id, name: agent.name,
|
|
248
|
+
xp_this_week: (agentXp || []).reduce((s, r) => s + r.amount, 0),
|
|
249
|
+
tasks_this_week: agentTasks || 0,
|
|
250
|
+
};
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
const highlights = [];
|
|
254
|
+
if (totalXp > 0)
|
|
255
|
+
highlights.push(`本周获得 ${totalXp} 经验值`);
|
|
256
|
+
if (taskStats.completed > 0)
|
|
257
|
+
highlights.push(`完成了 ${taskStats.completed} 个任务`);
|
|
258
|
+
if (agentSummaries.length > 0) {
|
|
259
|
+
const topAgent = agentSummaries.sort((a, b) => b.xp_this_week - a.xp_this_week)[0];
|
|
260
|
+
if (topAgent.xp_this_week > 0)
|
|
261
|
+
highlights.push(`最佳智能体: ${topAgent.name} (${topAgent.xp_this_week} 经验值)`);
|
|
262
|
+
}
|
|
263
|
+
return toMcpResponse(ok({
|
|
264
|
+
user_id,
|
|
265
|
+
username: user?.username,
|
|
266
|
+
week_start: startIso,
|
|
267
|
+
week_end: endIso,
|
|
268
|
+
xp: { total: totalXp, by_source: bySource },
|
|
269
|
+
tasks: taskStats,
|
|
270
|
+
progress: {
|
|
271
|
+
main: { level: user?.main_level, xp: user?.main_xp },
|
|
272
|
+
classes: (classes || []).reduce((m, c) => {
|
|
273
|
+
m[c.class_name] = { level: c.class_level, xp: c.class_xp };
|
|
274
|
+
return m;
|
|
275
|
+
}, {}),
|
|
276
|
+
},
|
|
277
|
+
agents: agentSummaries,
|
|
278
|
+
highlights,
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
export function registerXpTools(server) {
|
|
282
|
+
console.error("\n📋 Section 8: XP & Progression");
|
|
283
|
+
// ================================================================
|
|
284
|
+
// Tool 52: levelup_get_xp_history
|
|
285
|
+
// ================================================================
|
|
286
|
+
server.registerTool("levelup_get_xp_history", {
|
|
287
|
+
title: "获取经验值历史",
|
|
288
|
+
description: `获取用户或智能体的经验值账本 — 类似银行对账单的每笔经验值交易。
|
|
289
|
+
|
|
290
|
+
参数:
|
|
291
|
+
- caller_user_id (uuid): 你的用户ID(必填 — 用于验证所有权)。
|
|
292
|
+
- entity_type (string): "user" 或 "agent"。
|
|
293
|
+
- entity_id (uuid): 哪个用户或智能体。
|
|
294
|
+
- source_type (string, 可选): 按来源筛选(如 "task"、"quest"、"weekly_bonus")。
|
|
295
|
+
- since (string, 可选): ISO时间戳 — 此时间之后的条目。
|
|
296
|
+
- limit (integer, 可选): 默认 50,最大 100。
|
|
297
|
+
- offset (integer, 可选): 分页。
|
|
298
|
+
|
|
299
|
+
返回:
|
|
300
|
+
包含来源、金额、xp_type、累计总额、区块链字段的分页账本条目。`,
|
|
301
|
+
inputSchema: {
|
|
302
|
+
caller_user_id: uuidSchema.describe("Your user ID (ownership verification)"),
|
|
303
|
+
entity_type: entityTypeSchema,
|
|
304
|
+
entity_id: uuidSchema.describe("User or agent ID"),
|
|
305
|
+
source_type: z.string().optional().describe("Filter: task, quest, weekly_bonus, etc."),
|
|
306
|
+
since: z.string().optional().describe("ISO timestamp"),
|
|
307
|
+
limit: z.number().int().min(1).max(100).default(50).describe("Max results"),
|
|
308
|
+
offset: offsetSchema,
|
|
309
|
+
},
|
|
310
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
311
|
+
}, async (params) => {
|
|
312
|
+
// Ownership check
|
|
313
|
+
const denied = await verifyOwnership({
|
|
314
|
+
caller_user_id: params.caller_user_id,
|
|
315
|
+
target_user_id: params.entity_type === "user" ? params.entity_id : undefined,
|
|
316
|
+
target_agent_id: params.entity_type === "agent" ? params.entity_id : undefined,
|
|
317
|
+
});
|
|
318
|
+
if (denied)
|
|
319
|
+
return denied;
|
|
320
|
+
let query = supabase
|
|
321
|
+
.from("xp_ledger")
|
|
322
|
+
.select("id, entity_type, entity_id, xp_type, amount, source_type, source_id, description, running_total, tx_hash, chain_verified, chain_id, created_at", { count: "exact" })
|
|
323
|
+
.eq("entity_type", params.entity_type)
|
|
324
|
+
.eq("entity_id", params.entity_id);
|
|
325
|
+
if (params.source_type)
|
|
326
|
+
query = query.eq("source_type", params.source_type);
|
|
327
|
+
if (params.since)
|
|
328
|
+
query = query.gte("created_at", params.since);
|
|
329
|
+
query = query.order("created_at", { ascending: false }).range(params.offset, params.offset + params.limit - 1);
|
|
330
|
+
const { data, count, error } = await query;
|
|
331
|
+
if (error)
|
|
332
|
+
return toMcpResponse(handleSupabaseError(error, "get_xp_history"));
|
|
333
|
+
return toMcpResponse(ok(paginate(data || [], count || 0, params.offset)));
|
|
334
|
+
});
|
|
335
|
+
logRegistered("levelup_get_xp_history");
|
|
336
|
+
// ================================================================
|
|
337
|
+
// Tool 53: levelup_get_xp_summary
|
|
338
|
+
// ================================================================
|
|
339
|
+
server.registerTool("levelup_get_xp_summary", {
|
|
340
|
+
title: "获取经验值摘要",
|
|
341
|
+
description: `获取用户或智能体在某时间段的汇总经验值摘要。
|
|
342
|
+
|
|
343
|
+
参数:
|
|
344
|
+
- caller_user_id (uuid): 你的用户ID(必填 — 用于验证所有权)。
|
|
345
|
+
- entity_type (string): "user" 或 "agent"。
|
|
346
|
+
- entity_id (uuid): 哪个。
|
|
347
|
+
- period (string, 可选): "week"、"month" 或 "all_time"(默认: "all_time")。
|
|
348
|
+
|
|
349
|
+
返回:
|
|
350
|
+
总经验值、按来源类型分解、按 xp_type(main/class)分解、条目数。`,
|
|
351
|
+
inputSchema: {
|
|
352
|
+
caller_user_id: uuidSchema.describe("Your user ID (ownership verification)"),
|
|
353
|
+
entity_type: entityTypeSchema,
|
|
354
|
+
entity_id: uuidSchema.describe("User or agent ID"),
|
|
355
|
+
period: z.enum(["week", "month", "all_time"]).default("all_time").describe("Time period"),
|
|
356
|
+
},
|
|
357
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
358
|
+
}, async (params) => {
|
|
359
|
+
// Ownership check
|
|
360
|
+
const denied = await verifyOwnership({
|
|
361
|
+
caller_user_id: params.caller_user_id,
|
|
362
|
+
target_user_id: params.entity_type === "user" ? params.entity_id : undefined,
|
|
363
|
+
target_agent_id: params.entity_type === "agent" ? params.entity_id : undefined,
|
|
364
|
+
});
|
|
365
|
+
if (denied)
|
|
366
|
+
return denied;
|
|
367
|
+
let query = supabase
|
|
368
|
+
.from("xp_ledger")
|
|
369
|
+
.select("amount, source_type, xp_type, chain_verified")
|
|
370
|
+
.eq("entity_type", params.entity_type)
|
|
371
|
+
.eq("entity_id", params.entity_id);
|
|
372
|
+
// Apply time filter
|
|
373
|
+
if (params.period === "week") {
|
|
374
|
+
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
375
|
+
query = query.gte("created_at", weekAgo);
|
|
376
|
+
}
|
|
377
|
+
else if (params.period === "month") {
|
|
378
|
+
const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
379
|
+
query = query.gte("created_at", monthAgo);
|
|
380
|
+
}
|
|
381
|
+
const { data: entries, error } = await query;
|
|
382
|
+
if (error)
|
|
383
|
+
return toMcpResponse(handleSupabaseError(error, "get_xp_summary"));
|
|
384
|
+
const rows = entries || [];
|
|
385
|
+
const totalXp = rows.reduce((sum, r) => sum + (r.amount || 0), 0);
|
|
386
|
+
// Breakdown by source
|
|
387
|
+
const bySource = {};
|
|
388
|
+
for (const r of rows) {
|
|
389
|
+
bySource[r.source_type] = (bySource[r.source_type] || 0) + r.amount;
|
|
390
|
+
}
|
|
391
|
+
// Breakdown by xp_type
|
|
392
|
+
const byType = {};
|
|
393
|
+
for (const r of rows) {
|
|
394
|
+
byType[r.xp_type || "main"] = (byType[r.xp_type || "main"] || 0) + r.amount;
|
|
395
|
+
}
|
|
396
|
+
// Chain verification stats
|
|
397
|
+
const chainVerified = rows.filter((r) => r.chain_verified).reduce((sum, r) => sum + r.amount, 0);
|
|
398
|
+
return toMcpResponse(ok({
|
|
399
|
+
entity_type: params.entity_type,
|
|
400
|
+
entity_id: params.entity_id,
|
|
401
|
+
period: params.period,
|
|
402
|
+
total_xp: totalXp,
|
|
403
|
+
entry_count: rows.length,
|
|
404
|
+
by_source: bySource,
|
|
405
|
+
by_type: byType,
|
|
406
|
+
verification: { chain_verified_xp: chainVerified, unverified_xp: totalXp - chainVerified },
|
|
407
|
+
}));
|
|
408
|
+
});
|
|
409
|
+
logRegistered("levelup_get_xp_summary");
|
|
410
|
+
// ================================================================
|
|
411
|
+
// Tool 54: levelup_get_split_rules
|
|
412
|
+
// ================================================================
|
|
413
|
+
server.registerTool("levelup_get_split_rules", {
|
|
414
|
+
title: "获取分配规则",
|
|
415
|
+
description: `查看经验值分配规则 — 经验值如何在参与者之间分配。
|
|
416
|
+
|
|
417
|
+
参数:
|
|
418
|
+
- performer_type (string, 可选): 按执行者类型筛选。
|
|
419
|
+
- rule_id (uuid, 可选): 获取特定规则。
|
|
420
|
+
|
|
421
|
+
返回:
|
|
422
|
+
包含各接收者百分比、默认标志、区块链可执行性的规则条目。`,
|
|
423
|
+
inputSchema: {
|
|
424
|
+
performer_type: performerTypeSchema.optional(),
|
|
425
|
+
rule_id: uuidSchema.optional().describe("Specific rule ID"),
|
|
426
|
+
},
|
|
427
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
428
|
+
}, async (params) => {
|
|
429
|
+
let query = supabase.from("xp_split_rules").select("*");
|
|
430
|
+
if (params.performer_type)
|
|
431
|
+
query = query.eq("performer_type", params.performer_type);
|
|
432
|
+
if (params.rule_id)
|
|
433
|
+
query = query.eq("id", params.rule_id);
|
|
434
|
+
query = query.order("performer_type");
|
|
435
|
+
const { data: rules, error } = await query;
|
|
436
|
+
if (error)
|
|
437
|
+
return toMcpResponse(handleSupabaseError(error, "get_split_rules"));
|
|
438
|
+
// Enrich with entries
|
|
439
|
+
const enriched = await Promise.all((rules || []).map(async (rule) => {
|
|
440
|
+
const { data: entries } = await supabase
|
|
441
|
+
.from("xp_split_rule_entries").select("recipient_role, split_pct, sort_order")
|
|
442
|
+
.eq("rule_id", rule.id).order("sort_order");
|
|
443
|
+
return { ...rule, entries: entries || [] };
|
|
444
|
+
}));
|
|
445
|
+
return toMcpResponse(ok(enriched));
|
|
446
|
+
});
|
|
447
|
+
logRegistered("levelup_get_split_rules");
|
|
448
|
+
// ================================================================
|
|
449
|
+
// Tool 55: levelup_get_xp_projection
|
|
450
|
+
// ================================================================
|
|
451
|
+
server.registerTool("levelup_get_xp_projection", {
|
|
452
|
+
title: "获取经验值预测",
|
|
453
|
+
description: `预测用户或智能体何时达到下一等级(或目标等级)。
|
|
454
|
+
|
|
455
|
+
根据近期活动计算经验值速率并估算剩余时间。
|
|
456
|
+
|
|
457
|
+
参数:
|
|
458
|
+
- caller_user_id (uuid): 你的用户ID(必填 — 用于验证所有权)。
|
|
459
|
+
- entity_type (string): "user" 或 "agent"。
|
|
460
|
+
- entity_id (uuid): 哪个。
|
|
461
|
+
- target_level (integer, 可选): 预测到指定等级。
|
|
462
|
+
|
|
463
|
+
返回:
|
|
464
|
+
当前经验值速率(每周)、到下一等级的时间、到目标等级的时间、趋势方向。`,
|
|
465
|
+
inputSchema: {
|
|
466
|
+
caller_user_id: uuidSchema.describe("Your user ID (ownership verification)"),
|
|
467
|
+
entity_type: entityTypeSchema,
|
|
468
|
+
entity_id: uuidSchema.describe("User or agent ID"),
|
|
469
|
+
target_level: z.number().int().min(2).max(30).optional().describe("Target level to project to"),
|
|
470
|
+
},
|
|
471
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
472
|
+
}, async (params) => {
|
|
473
|
+
// Ownership check
|
|
474
|
+
const denied = await verifyOwnership({
|
|
475
|
+
caller_user_id: params.caller_user_id,
|
|
476
|
+
target_user_id: params.entity_type === "user" ? params.entity_id : undefined,
|
|
477
|
+
target_agent_id: params.entity_type === "agent" ? params.entity_id : undefined,
|
|
478
|
+
});
|
|
479
|
+
if (denied)
|
|
480
|
+
return denied;
|
|
481
|
+
// Get current level and XP
|
|
482
|
+
let currentLevel = 1;
|
|
483
|
+
let currentXp = 0;
|
|
484
|
+
if (params.entity_type === "user") {
|
|
485
|
+
const { data } = await supabase.from("users").select("main_level, main_xp").eq("id", params.entity_id).single();
|
|
486
|
+
if (data) {
|
|
487
|
+
currentLevel = data.main_level;
|
|
488
|
+
currentXp = data.main_xp;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
const { data } = await supabase.from("agents").select("level, xp").eq("id", params.entity_id).single();
|
|
493
|
+
if (data) {
|
|
494
|
+
currentLevel = data.level;
|
|
495
|
+
currentXp = data.xp;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// Get XP earned in last 4 weeks for rate calculation
|
|
499
|
+
const fourWeeksAgo = new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString();
|
|
500
|
+
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString();
|
|
501
|
+
const { data: recentEntries } = await supabase
|
|
502
|
+
.from("xp_ledger").select("amount, created_at")
|
|
503
|
+
.eq("entity_type", params.entity_type).eq("entity_id", params.entity_id)
|
|
504
|
+
.gte("created_at", fourWeeksAgo);
|
|
505
|
+
const rows = recentEntries || [];
|
|
506
|
+
const totalRecent = rows.reduce((sum, r) => sum + (r.amount || 0), 0);
|
|
507
|
+
const weeklyRate = Math.round(totalRecent / 4);
|
|
508
|
+
// Trend: compare first 2 weeks vs last 2 weeks
|
|
509
|
+
const firstHalf = rows.filter((r) => r.created_at < twoWeeksAgo).reduce((s, r) => s + r.amount, 0);
|
|
510
|
+
const secondHalf = rows.filter((r) => r.created_at >= twoWeeksAgo).reduce((s, r) => s + r.amount, 0);
|
|
511
|
+
const trend = secondHalf > firstHalf * 1.1 ? "increasing" : secondHalf < firstHalf * 0.9 ? "decreasing" : "stable";
|
|
512
|
+
// Get level curve for next level
|
|
513
|
+
const { data: nextCurve } = await supabase
|
|
514
|
+
.from("level_curves").select("xp_required")
|
|
515
|
+
.eq("entity_type", params.entity_type).eq("level", currentLevel + 1).maybeSingle();
|
|
516
|
+
const xpForNext = nextCurve?.xp_required || 0;
|
|
517
|
+
const xpRemaining = Math.max(0, xpForNext - currentXp);
|
|
518
|
+
const weeksToNext = weeklyRate > 0 ? Math.ceil(xpRemaining / weeklyRate) : null;
|
|
519
|
+
// Target level projection
|
|
520
|
+
let weeksToTarget = null;
|
|
521
|
+
if (params.target_level && params.target_level > currentLevel) {
|
|
522
|
+
const { data: targetCurve } = await supabase
|
|
523
|
+
.from("level_curves").select("cumulative_xp")
|
|
524
|
+
.eq("entity_type", params.entity_type).eq("level", params.target_level).maybeSingle();
|
|
525
|
+
if (targetCurve && weeklyRate > 0) {
|
|
526
|
+
const xpToTarget = Math.max(0, targetCurve.cumulative_xp - currentXp);
|
|
527
|
+
weeksToTarget = Math.ceil(xpToTarget / weeklyRate);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return toMcpResponse(ok({
|
|
531
|
+
entity_type: params.entity_type,
|
|
532
|
+
entity_id: params.entity_id,
|
|
533
|
+
current_level: currentLevel,
|
|
534
|
+
current_xp: currentXp,
|
|
535
|
+
weekly_xp_rate: weeklyRate,
|
|
536
|
+
trend,
|
|
537
|
+
next_level: {
|
|
538
|
+
level: currentLevel + 1,
|
|
539
|
+
xp_required: xpForNext,
|
|
540
|
+
xp_remaining: xpRemaining,
|
|
541
|
+
estimated_weeks: weeksToNext,
|
|
542
|
+
},
|
|
543
|
+
target: params.target_level ? {
|
|
544
|
+
level: params.target_level,
|
|
545
|
+
estimated_weeks: weeksToTarget,
|
|
546
|
+
} : null,
|
|
547
|
+
}));
|
|
548
|
+
});
|
|
549
|
+
logRegistered("levelup_get_xp_projection");
|
|
550
|
+
// ================================================================
|
|
551
|
+
// Tool 56: levelup_get_weekly_recap
|
|
552
|
+
// ================================================================
|
|
553
|
+
server.registerTool("levelup_get_weekly_recap", {
|
|
554
|
+
title: "获取每周回顾",
|
|
555
|
+
description: `用户的每周摘要 — 经验值、任务、技能、连续记录、智能体活动、亮点。
|
|
556
|
+
|
|
557
|
+
参数:
|
|
558
|
+
- caller_user_id (uuid): 你的用户ID(必填 — 用于验证所有权)。
|
|
559
|
+
- user_id (uuid): 哪个用户(必须是你自己)。
|
|
560
|
+
- include_agents (boolean, 可选): 包含智能体摘要(默认: true)。
|
|
561
|
+
- week_of (string, 可选): 该周的ISO日期(默认: 当前周)。
|
|
562
|
+
|
|
563
|
+
返回:
|
|
564
|
+
经验值分解、任务统计、等级进度、智能体摘要、预计算亮点。`,
|
|
565
|
+
inputSchema: {
|
|
566
|
+
caller_user_id: uuidSchema.describe("Your user ID (ownership verification)"),
|
|
567
|
+
user_id: uuidSchema.describe("The user (must be you)"),
|
|
568
|
+
include_agents: z.boolean().default(true).describe("Include agent summaries"),
|
|
569
|
+
week_of: z.string().optional().describe("ISO date for the week (default: current)"),
|
|
570
|
+
},
|
|
571
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
572
|
+
}, async (params) => {
|
|
573
|
+
// Ownership check
|
|
574
|
+
const denied = await verifyOwnership({
|
|
575
|
+
caller_user_id: params.caller_user_id,
|
|
576
|
+
target_user_id: params.user_id,
|
|
577
|
+
});
|
|
578
|
+
if (denied)
|
|
579
|
+
return denied;
|
|
580
|
+
// Determine week boundaries (Monday to Sunday)
|
|
581
|
+
const refDate = params.week_of ? new Date(params.week_of) : new Date();
|
|
582
|
+
const dayOfWeek = refDate.getDay();
|
|
583
|
+
const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
|
584
|
+
const weekStart = new Date(refDate);
|
|
585
|
+
weekStart.setDate(refDate.getDate() + mondayOffset);
|
|
586
|
+
weekStart.setHours(0, 0, 0, 0);
|
|
587
|
+
const weekEnd = new Date(weekStart);
|
|
588
|
+
weekEnd.setDate(weekStart.getDate() + 7);
|
|
589
|
+
const startIso = weekStart.toISOString();
|
|
590
|
+
const endIso = weekEnd.toISOString();
|
|
591
|
+
// User XP this week
|
|
592
|
+
const { data: xpEntries } = await supabase
|
|
593
|
+
.from("xp_ledger").select("amount, source_type, xp_type")
|
|
594
|
+
.eq("entity_type", "user").eq("entity_id", params.user_id)
|
|
595
|
+
.gte("created_at", startIso).lt("created_at", endIso);
|
|
596
|
+
const xpRows = xpEntries || [];
|
|
597
|
+
const totalXp = xpRows.reduce((s, r) => s + r.amount, 0);
|
|
598
|
+
const bySource = {};
|
|
599
|
+
for (const r of xpRows)
|
|
600
|
+
bySource[r.source_type] = (bySource[r.source_type] || 0) + r.amount;
|
|
601
|
+
// Tasks this week
|
|
602
|
+
const { data: tasks } = await supabase
|
|
603
|
+
.from("tasks").select("id, status, completion_pct, output_type")
|
|
604
|
+
.eq("user_id", params.user_id)
|
|
605
|
+
.gte("created_at", startIso).lt("created_at", endIso);
|
|
606
|
+
const taskRows = tasks || [];
|
|
607
|
+
const taskStats = {
|
|
608
|
+
total: taskRows.length,
|
|
609
|
+
completed: taskRows.filter((t) => t.status === "completed").length,
|
|
610
|
+
failed: taskRows.filter((t) => t.status === "failed").length,
|
|
611
|
+
in_progress: taskRows.filter((t) => t.status === "in_progress").length,
|
|
612
|
+
};
|
|
613
|
+
// User progress
|
|
614
|
+
const { data: user } = await supabase
|
|
615
|
+
.from("users").select("main_level, main_xp, username").eq("id", params.user_id).single();
|
|
616
|
+
const { data: classes } = await supabase
|
|
617
|
+
.from("user_class_progress").select("class_name, class_level, class_xp").eq("user_id", params.user_id);
|
|
618
|
+
// Agent summaries
|
|
619
|
+
let agentSummaries = [];
|
|
620
|
+
if (params.include_agents) {
|
|
621
|
+
const { data: agents } = await supabase
|
|
622
|
+
.from("agents").select("id, name").eq("owner_user_id", params.user_id).eq("status", "active");
|
|
623
|
+
agentSummaries = await Promise.all((agents || []).map(async (agent) => {
|
|
624
|
+
const { data: agentXp } = await supabase
|
|
625
|
+
.from("xp_ledger").select("amount")
|
|
626
|
+
.eq("entity_type", "agent").eq("entity_id", agent.id)
|
|
627
|
+
.gte("created_at", startIso).lt("created_at", endIso);
|
|
628
|
+
const { count: agentTasks } = await supabase
|
|
629
|
+
.from("tasks").select("id", { count: "exact", head: true })
|
|
630
|
+
.eq("agent_id", agent.id).gte("created_at", startIso).lt("created_at", endIso);
|
|
631
|
+
return {
|
|
632
|
+
id: agent.id, name: agent.name,
|
|
633
|
+
xp_this_week: (agentXp || []).reduce((s, r) => s + r.amount, 0),
|
|
634
|
+
tasks_this_week: agentTasks || 0,
|
|
635
|
+
};
|
|
636
|
+
}));
|
|
637
|
+
}
|
|
638
|
+
// Build highlights
|
|
639
|
+
const highlights = [];
|
|
640
|
+
if (totalXp > 0)
|
|
641
|
+
highlights.push(`本周获得 ${totalXp} 经验值`);
|
|
642
|
+
if (taskStats.completed > 0)
|
|
643
|
+
highlights.push(`完成了 ${taskStats.completed} 个任务`);
|
|
644
|
+
if (agentSummaries.length > 0) {
|
|
645
|
+
const topAgent = agentSummaries.sort((a, b) => b.xp_this_week - a.xp_this_week)[0];
|
|
646
|
+
if (topAgent.xp_this_week > 0)
|
|
647
|
+
highlights.push(`最佳智能体: ${topAgent.name} (${topAgent.xp_this_week} 经验值)`);
|
|
648
|
+
}
|
|
649
|
+
return toMcpResponse(ok({
|
|
650
|
+
user_id: params.user_id,
|
|
651
|
+
username: user?.username,
|
|
652
|
+
week_start: startIso,
|
|
653
|
+
week_end: endIso,
|
|
654
|
+
xp: { total: totalXp, by_source: bySource },
|
|
655
|
+
tasks: taskStats,
|
|
656
|
+
progress: {
|
|
657
|
+
main: { level: user?.main_level, xp: user?.main_xp },
|
|
658
|
+
classes: (classes || []).reduce((m, c) => {
|
|
659
|
+
m[c.class_name] = { level: c.class_level, xp: c.class_xp };
|
|
660
|
+
return m;
|
|
661
|
+
}, {}),
|
|
662
|
+
},
|
|
663
|
+
agents: agentSummaries,
|
|
664
|
+
highlights,
|
|
665
|
+
}));
|
|
666
|
+
});
|
|
667
|
+
logRegistered("levelup_get_weekly_recap");
|
|
668
|
+
console.error(` → 5 of 5 XP tools registered ✅`);
|
|
669
|
+
}
|
|
670
|
+
//# sourceMappingURL=xp.js.map
|