libero-mcp 0.2.8 → 0.2.10

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.
@@ -72,7 +72,7 @@ export function buildServer(db) {
72
72
  const matrix = createMatrixTools(statsRepo); // matrix reuses StatsRepo
73
73
  const timeline = createTimelineTools(timelineRepo);
74
74
  const timer = createTimerTools(timerRepo, timeblockRepo, db);
75
- const profile = createProfileTools(profileRepo);
75
+ const profile = createProfileTools(profileRepo, identityRepo);
76
76
  const profileConfirm = createProfileConfirmTools(db);
77
77
  const reminder = createReminderTools(reminderRepo, undefined, db);
78
78
  const scheduler = createSchedulerTools(timeblockRepo, reminderRepo, categoryRepo);
@@ -193,7 +193,19 @@ export function buildServer(db) {
193
193
  return errorContent(r.error);
194
194
  return asContent(await tb.create(r));
195
195
  });
196
- server.registerTool("timeblock.getById", { description: "按 id 获取单条时间记录", inputSchema: { id: z.string().min(1) } }, async ({ id }) => asContent(await tb.getById(id)));
196
+ server.registerTool("timeblock.getById", {
197
+ description: "按 id 获取单条时间记录。" +
198
+ "S41 多用户:默认按当前 user_id 过滤,跨用户查不到返回 null。",
199
+ inputSchema: {
200
+ id: z.string().min(1),
201
+ user_id: z.string().optional(),
202
+ },
203
+ }, async (rawInput) => {
204
+ const r = injector(rawInput);
205
+ if ("error" in r)
206
+ return errorContent(r.error);
207
+ return asContent(await tb.getById(rawInput.id, r.user_id));
208
+ });
197
209
  server.registerTool("timeblock.update", {
198
210
  description: "更新一条时间记录(标题/时间/评分/status 等)。" +
199
211
  "MV-COACH-3 Slice 4 用户主权状态机:" +
@@ -470,6 +482,38 @@ export function buildServer(db) {
470
482
  })),
471
483
  });
472
484
  });
485
+ server.registerTool("profile.createUser", {
486
+ description: "S41 多用户建档:建立新 libero 用户(父母代管模式)。" +
487
+ "用户体感:「帮我建一个新用户」「我要给我儿子建档」时调用。" +
488
+ "返回新 libero_user_id,建档后该 user_id 立即可用于所有 libero 工具调用。" +
489
+ "可选绑定平台 alias(如果建档时已知 chat_id)。" +
490
+ "⚠️ 不查重(同名不冲突);display_name 必填;platform 和 platform_user_id 必须同时给或同时不给。",
491
+ inputSchema: {
492
+ display_name: z.string().min(1),
493
+ focus_areas: z.array(z.string()).optional(),
494
+ goals: z.string().optional(),
495
+ platform: z
496
+ .enum(["wecom", "feishu", "telegram", "discord", "signal", "unknown"])
497
+ .optional(),
498
+ platform_user_id: z.string().optional(),
499
+ chat_id: z.string().optional(),
500
+ },
501
+ }, async (rawInput) => {
502
+ try {
503
+ const result = await profile.createUser({
504
+ display_name: rawInput.display_name,
505
+ focus_areas: rawInput.focus_areas,
506
+ goals: rawInput.goals,
507
+ platform: rawInput.platform,
508
+ platform_user_id: rawInput.platform_user_id,
509
+ chat_id: rawInput.chat_id,
510
+ });
511
+ return asContent(result);
512
+ }
513
+ catch (e) {
514
+ return errorContent(e.message);
515
+ }
516
+ });
473
517
  // ============================================================
474
518
  // reminder (6 tools — S11)
475
519
  // ============================================================
@@ -1,6 +1,10 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  /** Validate and delegate profile operations to the repository.
2
- * ADR-0001: Dependency-injected — the caller provides the repo. */
3
- export function createProfileTools(repo) {
3
+ * ADR-0001: Dependency-injected — the caller provides the repo.
4
+ *
5
+ * S41(2026-08-06):新增可选 identityRepo 参数,用于 createUser 工具。
6
+ * 不传 = 不支持 createUser(向后兼容)。 */
7
+ export function createProfileTools(repo, identityRepo) {
4
8
  return {
5
9
  async get(userId) {
6
10
  if (!userId) {
@@ -101,5 +105,71 @@ export function createProfileTools(repo) {
101
105
  });
102
106
  return { won: true, profile };
103
107
  },
108
+ /**
109
+ * S41(2026-08-06):建新 libero 用户(管理员代管模式)。
110
+ *
111
+ * 产品定位:「帮我建一个新用户」「我要给我儿子建档」时调用。
112
+ * 不查重(同名不冲突)、不强制绑 alias、不要求 focus_areas/goals(最小建档)。
113
+ *
114
+ * 必须传 identityRepo 才可用(slice 1:server-core 启动时注入)。
115
+ *
116
+ * 输入校验(§4.5 显式):
117
+ * - display_name 必填非空
118
+ * - platform 和 platform_user_id 必须同时给或同时不给
119
+ *
120
+ * 副作用:建 identity → 建 profile(空白)→ (可选)绑 alias
121
+ * 失败语义:identity 建成功但 profile 建失败 → 抛错,identity 留在表里
122
+ * (可接受:identity 没有 profile 也能用,下次 profile.get 会触发 auto-create;
123
+ * 不做事务回滚是 §3 #4 最小可逆单位原则。)
124
+ */
125
+ async createUser(input) {
126
+ if (!identityRepo) {
127
+ throw new Error("createUser 需要 identityRepo(server-core 启动时未注入,本部署不支持多用户建档)");
128
+ }
129
+ if (!input.display_name || input.display_name.trim() === "") {
130
+ throw new Error("display_name 为必填参数且不能为空");
131
+ }
132
+ const hasPlatform = !!input.platform;
133
+ const hasPlatformUserId = !!input.platform_user_id;
134
+ if (hasPlatform !== hasPlatformUserId) {
135
+ throw new Error("platform 和 platform_user_id 必须同时给或同时不给(绑定平台 alias 时两者都需要)");
136
+ }
137
+ const libero_user_id = `u_libero_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
138
+ await identityRepo.createIdentity({
139
+ libero_user_id,
140
+ display_name: input.display_name,
141
+ });
142
+ const settings = {};
143
+ if (input.focus_areas && input.focus_areas.length > 0) {
144
+ settings.focus_areas = input.focus_areas;
145
+ }
146
+ if (input.goals && input.goals.trim() !== "") {
147
+ settings.goals = input.goals;
148
+ }
149
+ await repo.upsert({
150
+ user_id: libero_user_id,
151
+ display_name: input.display_name,
152
+ settings: Object.keys(settings).length > 0 ? settings : undefined,
153
+ });
154
+ let bound_alias;
155
+ if (hasPlatform && hasPlatformUserId) {
156
+ await identityRepo.bindAlias({
157
+ libero_user_id,
158
+ platform: input.platform,
159
+ platform_user_id: input.platform_user_id,
160
+ chat_id: input.chat_id,
161
+ });
162
+ bound_alias = {
163
+ platform: input.platform,
164
+ platform_user_id: input.platform_user_id,
165
+ chat_id: input.chat_id ?? null,
166
+ };
167
+ }
168
+ return {
169
+ libero_user_id,
170
+ display_name: input.display_name,
171
+ bound_alias,
172
+ };
173
+ },
104
174
  };
105
175
  }
@@ -28,8 +28,14 @@ export function createTimeBlockTools(repo, db) {
28
28
  const row = await repo.create({ ...input, user_id: userId });
29
29
  return { ...row, target_user_label: labelFor(db, userId) };
30
30
  },
31
- async getById(id) {
32
- return repo.getById(id);
31
+ async getById(id, userId) {
32
+ const row = await repo.getById(id);
33
+ // S41 multi-user isolation: 如果调用方传了 user_id,校验 ownership。
34
+ // 没传 user_id = 旧行为(单用户 / 兼容路径),不阻塞。
35
+ if (row && userId && row.user_id !== userId) {
36
+ return null;
37
+ }
38
+ return row;
33
39
  },
34
40
  async list(filters) {
35
41
  if (!filters.user_id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libero-mcp",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "AI 时间管理教练 MCP 工具集——时间块记录、统计、矩阵诊断、计时、分类、profile",
5
5
  "license": "MIT",
6
6
  "author": "amosyuan",