libero-mcp 0.2.14 → 0.2.16

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.
@@ -301,6 +301,34 @@ function buildToolRegistry(ctx) {
301
301
  return asContent(await tb.list(r));
302
302
  },
303
303
  });
304
+ tools.push({
305
+ name: "timeblock.sweepStale",
306
+ description: "S43 过期扫描:列出某用户当天过期未确认的 timeblock,分两组返回。" +
307
+ "to_unverified:end_time 已过 30min 仍 planned(该升级 unverified)。" +
308
+ "to_ask:end_time 已过 60min 仍 unverified/planned(该主动问用户)。" +
309
+ "⚠️ 纯查询,不改 db——caller(cron-session LLM)需对 to_unverified 显式调 timeblock.update(status=unverified),对 to_ask 调 cronjob create 发追问。" +
310
+ "⚠️ 仅 cron-session 调用;user_id 必填。",
311
+ inputSchema: {
312
+ user_id: z.string().min(1),
313
+ now: z.string().optional(),
314
+ overdue_min_to_unverified: z.number().optional(),
315
+ overdue_min_to_ask: z.number().optional(),
316
+ },
317
+ handler: async (rawInput) => {
318
+ try {
319
+ const result = await tb.sweepStale({
320
+ user_id: rawInput.user_id,
321
+ now: rawInput.now,
322
+ overdue_min_to_unverified: rawInput.overdue_min_to_unverified,
323
+ overdue_min_to_ask: rawInput.overdue_min_to_ask,
324
+ });
325
+ return asContent(result);
326
+ }
327
+ catch (e) {
328
+ return errorContent(e.message);
329
+ }
330
+ },
331
+ });
304
332
  // ============================================================
305
333
  // stats (2 tools — single-object filters)
306
334
  // ============================================================
@@ -345,7 +373,9 @@ function buildToolRegistry(ctx) {
345
373
  // ============================================================
346
374
  tools.push({
347
375
  name: "matrix.renderMatrix",
348
- description: "时间×评分四象限矩阵(能耗黑洞/保持区/亮点区/可放弃)",
376
+ description: "时间×评分四象限矩阵(能耗黑洞/保持区/亮点区/可放弃)。" +
377
+ "⚠️ 用户说「时间矩阵 / 时间表 / 看日程 / 今天时间表」时**不要**调本工具——那些是指 emoji 时间块视图,请改调 schedule.timeline。" +
378
+ "本工具仅用于:阶段复盘(这周/最近 N 天)+ 已评分记录的象限分析。",
349
379
  inputSchema: {
350
380
  user_id: z.string().optional(),
351
381
  from: z.string().optional(),
@@ -365,7 +395,7 @@ function buildToolRegistry(ctx) {
365
395
  tools.push({
366
396
  name: "schedule.timeline",
367
397
  description: "看日程/复盘的首选视图:emoji 矩阵+细项表格,含计划/计时中/未回应/实绩全状态。" +
368
- "用户说「看今天/看日程/在干啥/复盘/接下来 N 小时」时调本工具,并直接 echo rendered_text(不重写、不改格式)。" +
398
+ "用户说「看今天/看日程/在干啥/复盘/接下来 N 小时/时间矩阵/时间表/今天时间表」时调本工具,并直接 echo rendered_text(不重写、不改格式)。" +
369
399
  "不要调 scheduler.listSchedules(那是内部排程查询,不是用户视图)。",
370
400
  inputSchema: {
371
401
  user_id: z.string().optional(),
@@ -600,6 +630,22 @@ function buildToolRegistry(ctx) {
600
630
  }
601
631
  },
602
632
  });
633
+ tools.push({
634
+ name: "profile.listAllUsers",
635
+ description: "S43 多用户扫描:列出所有 libero 用户(libero_user_id + display_name)。" +
636
+ "用途:cron 扫描(每 30min)跑所有用户的状态升级和主动询问,不靠 LLM 从上下文猜。" +
637
+ "⚠️ 仅 cron-session 调用,普通对话不要用。",
638
+ inputSchema: {},
639
+ handler: async () => {
640
+ try {
641
+ const result = await profile.listAllUsers();
642
+ return asContent({ users: result });
643
+ }
644
+ catch (e) {
645
+ return errorContent(e.message);
646
+ }
647
+ },
648
+ });
603
649
  // ============================================================
604
650
  // reminder (6 tools — S11)
605
651
  // ============================================================
@@ -59,4 +59,7 @@ export class InMemoryIdentityRepo {
59
59
  }
60
60
  return null;
61
61
  }
62
+ async listAllIdentities() {
63
+ return Array.from(this.identities.values());
64
+ }
62
65
  }
@@ -171,5 +171,22 @@ export function createProfileTools(repo, identityRepo) {
171
171
  bound_alias,
172
172
  };
173
173
  },
174
+ /**
175
+ * S43(2026-08-07):列出所有 libero 用户。
176
+ * 用途:扫描 cron 跑所有用户的状态升级和主动询问(不靠 LLM 从上下文猜)。
177
+ *
178
+ * 数据源:identity 表(libero 内部身份)。display_name 取 identity 表的。
179
+ * §4.5 静默折叠声明:返回所有 identity 行;若 identityRepo 未注入则抛(不静默返回空)。
180
+ */
181
+ async listAllUsers() {
182
+ if (!identityRepo) {
183
+ throw new Error("listAllUsers 需要 identityRepo(server-core 启动时未注入,本部署不支持多用户扫描)");
184
+ }
185
+ const identities = await identityRepo.listAllIdentities();
186
+ return identities.map((i) => ({
187
+ libero_user_id: i.libero_user_id,
188
+ display_name: i.display_name,
189
+ }));
190
+ },
174
191
  };
175
192
  }
@@ -76,4 +76,12 @@ export class SqliteIdentityRepo {
76
76
  .get(platform_user_id);
77
77
  return row ? { ...row } : null;
78
78
  }
79
+ async listAllIdentities() {
80
+ const rows = this.db
81
+ .prepare(`SELECT libero_user_id, display_name, created_at, updated_at
82
+ FROM user_identity
83
+ ORDER BY created_at ASC`)
84
+ .all();
85
+ return rows.map((r) => ({ ...r }));
86
+ }
79
87
  }
@@ -117,5 +117,67 @@ export function createTimeBlockTools(repo, db) {
117
117
  async restore(id) {
118
118
  return repo.restore(id);
119
119
  },
120
+ /**
121
+ * S43(2026-08-07):扫描某用户过期未确认的 timeblock,分组返回。
122
+ *
123
+ * 用途:cron-session 每 30min 调一次,让 cron LLM 显式调 timeblock.update
124
+ * 升级状态 + 主动问。本工具**不**直接改 db(保留 brain/hand 分离 + 用户主权模型)。
125
+ *
126
+ * 判定逻辑(以 now 为基准):
127
+ * - end_time + overdue_min_to_unverified (默认 30min) 仍 planned → to_unverified
128
+ * - end_time + overdue_min_to_ask (默认 60min) 仍 unverified/planned → to_ask
129
+ * - completed / 未过期 / 软删的 → 都不进
130
+ *
131
+ * §4.5 静默折叠声明:纯查询,无副作用,无折叠。
132
+ */
133
+ async sweepStale(input) {
134
+ if (!input.user_id) {
135
+ throw new Error("user_id 为必填参数");
136
+ }
137
+ const nowIso = input.now ?? new Date().toISOString();
138
+ const nowMs = new Date(nowIso).getTime();
139
+ const thresholdUnverifiedMin = input.overdue_min_to_unverified ?? 30;
140
+ const thresholdAskMin = input.overdue_min_to_ask ?? 60;
141
+ const thresholdUnverifiedMs = thresholdUnverifiedMin * 60 * 1000;
142
+ const thresholdAskMs = thresholdAskMin * 60 * 1000;
143
+ // 用 list 拿到该用户所有非软删的块(list 默认会过滤 deleted_at)
144
+ const blocks = await repo.list({
145
+ user_id: input.user_id,
146
+ });
147
+ const to_unverified = [];
148
+ const to_ask = [];
149
+ for (const b of blocks) {
150
+ if (b.deleted_at)
151
+ continue;
152
+ if (b.status === "completed")
153
+ continue;
154
+ const endMs = new Date(b.end_time).getTime();
155
+ if (Number.isNaN(endMs))
156
+ continue;
157
+ const ageMs = nowMs - endMs;
158
+ if (ageMs <= 0)
159
+ continue; // 还没到 end_time
160
+ const overdueUnverified = ageMs >= thresholdUnverifiedMs;
161
+ const overdueAsk = ageMs >= thresholdAskMs;
162
+ if (overdueUnverified && b.status === "planned") {
163
+ to_unverified.push({
164
+ id: b.id,
165
+ title: b.title,
166
+ start_time: b.start_time,
167
+ end_time: b.end_time,
168
+ });
169
+ }
170
+ if (overdueAsk && (b.status === "unverified" || b.status === "planned")) {
171
+ to_ask.push({
172
+ id: b.id,
173
+ title: b.title,
174
+ start_time: b.start_time,
175
+ end_time: b.end_time,
176
+ status: b.status,
177
+ });
178
+ }
179
+ }
180
+ return { to_unverified, to_ask, now: nowIso };
181
+ },
120
182
  };
121
183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libero-mcp",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "AI 时间管理教练 MCP 工具集——时间块记录、统计、矩阵诊断、计时、分类、profile",
5
5
  "license": "MIT",
6
6
  "author": "amosyuan",