@zhin.js/plugin-reminder 0.0.0-stage → 1.0.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/README.md +87 -2
- package/commands/remind/after/index.js +16 -0
- package/commands/remind/after/index.ts +16 -0
- package/commands/remind/at/index.js +22 -0
- package/commands/remind/at/index.ts +16 -0
- package/commands/remind/cancel/index.js +11 -0
- package/commands/remind/cancel/index.ts +11 -0
- package/commands/remind/every/index.js +16 -0
- package/commands/remind/every/index.ts +16 -0
- package/commands/remind/list/index.js +14 -0
- package/commands/remind/list/index.ts +14 -0
- package/lib/caller.d.ts +5 -0
- package/lib/caller.d.ts.map +1 -0
- package/lib/caller.js +18 -0
- package/lib/caller.js.map +1 -0
- package/lib/config.d.ts +17 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +17 -0
- package/lib/config.js.map +1 -0
- package/lib/duration.d.ts +2 -0
- package/lib/duration.d.ts.map +1 -0
- package/lib/duration.js +31 -0
- package/lib/duration.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -0
- package/lib/runtime.d.ts +3 -0
- package/lib/runtime.d.ts.map +1 -0
- package/lib/runtime.js +3 -0
- package/lib/runtime.js.map +1 -0
- package/lib/service.d.ts +42 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +180 -0
- package/lib/service.js.map +1 -0
- package/lib/tool-actor.d.ts +20 -0
- package/lib/tool-actor.d.ts.map +1 -0
- package/lib/tool-actor.js +38 -0
- package/lib/tool-actor.js.map +1 -0
- package/package.json +102 -3
- package/plugin.js +41 -0
- package/schedules/poll/index.js +8 -0
- package/schedules/poll/index.ts +8 -0
- package/skills/reminder/SKILL.md +45 -0
- package/skills/reminder/tools/reminder_cancel/index.js +24 -0
- package/skills/reminder/tools/reminder_cancel/index.ts +25 -0
- package/skills/reminder/tools/reminder_create/index.js +57 -0
- package/skills/reminder/tools/reminder_create/index.ts +82 -0
- package/skills/reminder/tools/reminder_list/index.js +22 -0
- package/skills/reminder/tools/reminder_list/index.ts +22 -0
- package/src/caller.ts +13 -0
- package/src/config.ts +16 -0
- package/src/duration.ts +31 -0
- package/src/index.ts +4 -0
- package/src/runtime.ts +4 -0
- package/src/service.ts +171 -0
- package/src/tool-actor.ts +52 -0
package/lib/service.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createDeliveryTarget } from 'zhin.js';
|
|
3
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
4
|
+
export const REMINDER_STATE_KEY = 'reminders/v1';
|
|
5
|
+
export const MAX_DELAY_SECONDS = 365 * 24 * 60 * 60;
|
|
6
|
+
export class ReminderInputError extends Error {
|
|
7
|
+
}
|
|
8
|
+
/** Generation-owned worker; saved state contains plain identities, never live objects. */
|
|
9
|
+
export class ReminderService {
|
|
10
|
+
#state;
|
|
11
|
+
#outbound;
|
|
12
|
+
#config;
|
|
13
|
+
#working = false;
|
|
14
|
+
#stopped = false;
|
|
15
|
+
constructor(state, outbound, config) {
|
|
16
|
+
this.#state = state;
|
|
17
|
+
this.#outbound = outbound;
|
|
18
|
+
this.#config = config;
|
|
19
|
+
}
|
|
20
|
+
stop() { this.#stopped = true; }
|
|
21
|
+
async add(actor, text, at, intervalMs) {
|
|
22
|
+
this.#assertLive();
|
|
23
|
+
assertActor(actor);
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
if (!text.trim() || text.length > 500)
|
|
26
|
+
throw new ReminderInputError('提醒内容须为 1–500 个字符');
|
|
27
|
+
if (!Number.isSafeInteger(at) || at <= now || at - now > MAX_DELAY_SECONDS * 1000)
|
|
28
|
+
throw new ReminderInputError('提醒时间须在未来一年内');
|
|
29
|
+
if (intervalMs !== null && (!Number.isSafeInteger(intervalMs) || intervalMs < 60_000 || intervalMs > MAX_DELAY_SECONDS * 1000))
|
|
30
|
+
throw new ReminderInputError('周期须为 60 秒至一年');
|
|
31
|
+
const task = { id: randomUUID(), senderId: actor.senderId, target: createDeliveryTarget({ conversation: actor.target }),
|
|
32
|
+
text, createdAt: now, nextRunAt: at, intervalMs, status: 'pending', claimId: null, lastRunAt: null, detail: '' };
|
|
33
|
+
await this.#state.update(REMINDER_STATE_KEY, current => {
|
|
34
|
+
const book = readBook(current);
|
|
35
|
+
// Completed history is disposable; unconfirmed tasks require an explicit
|
|
36
|
+
// user decision and continue to count against the storage quota.
|
|
37
|
+
const active = book.tasks.filter(item => item.status === 'pending' || item.status === 'unknown');
|
|
38
|
+
if (active.length >= this.#config.maxTasks)
|
|
39
|
+
throw new ReminderInputError('此插件实例的提醒数量已达上限,请先取消任务');
|
|
40
|
+
if (active.filter(item => sameActor(item, actor)).length >= this.#config.maxTasksPerSender)
|
|
41
|
+
throw new ReminderInputError('你在此会话的提醒数量已达上限,请先取消任务');
|
|
42
|
+
const historyLimit = Math.max(0, this.#config.maxTasks - active.length - 1);
|
|
43
|
+
const history = historyLimit > 0 ? book.tasks.filter(item => item.status !== 'pending' && item.status !== 'unknown').slice(-historyLimit) : [];
|
|
44
|
+
return boundedBook([...active, ...history, task]);
|
|
45
|
+
});
|
|
46
|
+
return task;
|
|
47
|
+
}
|
|
48
|
+
async list(actor) {
|
|
49
|
+
this.#assertLive();
|
|
50
|
+
assertActor(actor);
|
|
51
|
+
return readBook(await this.#state.get(REMINDER_STATE_KEY)).tasks.filter(item => sameActor(item, actor));
|
|
52
|
+
}
|
|
53
|
+
async cancel(actor, id) {
|
|
54
|
+
this.#assertLive();
|
|
55
|
+
assertActor(actor);
|
|
56
|
+
const book = await this.#state.update(REMINDER_STATE_KEY, current => ({ version: 1,
|
|
57
|
+
tasks: readBook(current).tasks.map(task => task.id === id && sameActor(task, actor)
|
|
58
|
+
? { ...task, status: 'cancelled', detail: '用户取消;已经开始的投递无法撤销' } : task),
|
|
59
|
+
}));
|
|
60
|
+
return !!book?.tasks.some(task => task.id === id && sameActor(task, actor) && task.status === 'cancelled');
|
|
61
|
+
}
|
|
62
|
+
async tick() {
|
|
63
|
+
if (this.#working || this.#stopped)
|
|
64
|
+
return;
|
|
65
|
+
this.#working = true;
|
|
66
|
+
try {
|
|
67
|
+
for (let count = 0; count < this.#config.batchSize && !this.#stopped; count++) {
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const claimId = randomUUID();
|
|
70
|
+
const book = await this.#state.update(REMINDER_STATE_KEY, current => {
|
|
71
|
+
let claimed = false;
|
|
72
|
+
return { version: 1, tasks: readBook(current).tasks.map(task => {
|
|
73
|
+
if (task.status !== 'pending' || task.nextRunAt > now)
|
|
74
|
+
return task;
|
|
75
|
+
if (task.intervalMs !== null && now - task.nextRunAt > this.#config.missedGraceSeconds * 1000) {
|
|
76
|
+
return { ...task, nextRunAt: nextPeriodicTime(task.nextRunAt, task.intervalMs, now), detail: '已跳过错过的周期轮次' };
|
|
77
|
+
}
|
|
78
|
+
if (claimed)
|
|
79
|
+
return task;
|
|
80
|
+
claimed = true;
|
|
81
|
+
// Persist the conservative disposition before touching the external
|
|
82
|
+
// platform. A crash here cannot cause an automatic duplicate send.
|
|
83
|
+
return { ...task, status: 'unknown', claimId, lastRunAt: now, detail: '投递中或未确认;不会自动重试' };
|
|
84
|
+
}) };
|
|
85
|
+
});
|
|
86
|
+
const task = book?.tasks.find(item => item.claimId === claimId);
|
|
87
|
+
if (!task || this.#stopped)
|
|
88
|
+
break;
|
|
89
|
+
let status = 'unknown';
|
|
90
|
+
let detail = '平台未返回确认的消息 ID;不会自动重试';
|
|
91
|
+
try {
|
|
92
|
+
const id = await this.#outbound.sendTarget(task.target, `⏰ 提醒:${task.text}`);
|
|
93
|
+
if (id) {
|
|
94
|
+
status = task.intervalMs === null ? 'delivered' : 'pending';
|
|
95
|
+
detail = '已确认投递';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error instanceof EndpointDeliveryError && error.disposition !== 'unknown') {
|
|
100
|
+
status = 'failed';
|
|
101
|
+
detail = `投递失败:${error.code};任务已停止`;
|
|
102
|
+
}
|
|
103
|
+
else
|
|
104
|
+
detail = '投递结果未确认;请核对目标会话后取消或重新创建';
|
|
105
|
+
}
|
|
106
|
+
await this.#state.update(REMINDER_STATE_KEY, current => ({ version: 1,
|
|
107
|
+
tasks: readBook(current).tasks.map(item => item.id === task.id && item.claimId === claimId && item.status === 'unknown'
|
|
108
|
+
? { ...item, status, detail, ...(status === 'pending' && task.intervalMs !== null
|
|
109
|
+
? { nextRunAt: nextPeriodicTime(task.nextRunAt, task.intervalMs, Date.now()) } : {}) } : item),
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
this.#working = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
describe(task) {
|
|
118
|
+
const date = new Intl.DateTimeFormat('zh-CN', { timeZone: this.#config.timezone, dateStyle: 'short', timeStyle: 'medium' }).format(task.nextRunAt);
|
|
119
|
+
const state = { pending: '待触发', unknown: '投递中或未确认', delivered: '已完成', failed: '投递失败', cancelled: '已取消' }[task.status];
|
|
120
|
+
return `${task.id} | ${state} | ${date} (${this.#config.timezone})${task.intervalMs === null ? '' : ` | 每 ${task.intervalMs / 1000} 秒`}`
|
|
121
|
+
+ `\n${task.text}${task.detail ? `\n${task.detail}` : ''}`;
|
|
122
|
+
}
|
|
123
|
+
#assertLive() { if (this.#stopped)
|
|
124
|
+
throw new Error('提醒服务属于已停止的插件实例'); }
|
|
125
|
+
}
|
|
126
|
+
export function parseReminderInstant(value) {
|
|
127
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/u.test(value))
|
|
128
|
+
throw new TypeError('绝对时间须为带 Z 或 ±hh:mm 的 ISO 时间,例如 2026-10-14T09:00:00+08:00');
|
|
129
|
+
const [year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0] = value.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?/u).slice(1).map(item => Number(item ?? 0));
|
|
130
|
+
const calendar = new Date(0);
|
|
131
|
+
calendar.setUTCFullYear(year, month, 0);
|
|
132
|
+
const daysInMonth = calendar.getUTCDate();
|
|
133
|
+
if (month < 1 || month > 12 || day < 1 || day > daysInMonth || hour > 23 || minute > 59 || second > 59)
|
|
134
|
+
throw new TypeError('提醒时间无效');
|
|
135
|
+
const time = Date.parse(value);
|
|
136
|
+
if (!Number.isSafeInteger(time))
|
|
137
|
+
throw new TypeError('提醒时间无效');
|
|
138
|
+
return time;
|
|
139
|
+
}
|
|
140
|
+
function nextPeriodicTime(from, interval, now) {
|
|
141
|
+
return from + (Math.floor((now - from) / interval) + 1) * interval;
|
|
142
|
+
}
|
|
143
|
+
function sameActor(task, actor) {
|
|
144
|
+
return task.senderId === actor.senderId && JSON.stringify(task.target) === JSON.stringify(createDeliveryTarget({ conversation: actor.target }));
|
|
145
|
+
}
|
|
146
|
+
function readBook(value) {
|
|
147
|
+
if (value === undefined)
|
|
148
|
+
return { version: 1, tasks: [] };
|
|
149
|
+
try {
|
|
150
|
+
if (value.version !== 1 || !Array.isArray(value.tasks) || value.tasks.length > 32)
|
|
151
|
+
throw new Error('Invalid book');
|
|
152
|
+
for (const task of value.tasks) {
|
|
153
|
+
if (!task || typeof task.id !== 'string' || !task.id || typeof task.senderId !== 'string' || !task.senderId
|
|
154
|
+
|| typeof task.text !== 'string' || !task.text.trim() || task.text.length > 500
|
|
155
|
+
|| !Number.isSafeInteger(task.createdAt) || !Number.isSafeInteger(task.nextRunAt)
|
|
156
|
+
|| (task.intervalMs !== null && (!Number.isSafeInteger(task.intervalMs) || task.intervalMs < 60_000 || task.intervalMs > MAX_DELAY_SECONDS * 1000))
|
|
157
|
+
|| !['pending', 'unknown', 'delivered', 'failed', 'cancelled'].includes(task.status)
|
|
158
|
+
|| (task.claimId !== null && typeof task.claimId !== 'string')
|
|
159
|
+
|| (task.lastRunAt !== null && !Number.isSafeInteger(task.lastRunAt)) || typeof task.detail !== 'string')
|
|
160
|
+
throw new Error('Invalid task');
|
|
161
|
+
createDeliveryTarget({ conversation: task.target });
|
|
162
|
+
}
|
|
163
|
+
return boundedBook(value.tasks);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
throw new Error('提醒持久状态格式无效,已停止投递');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function boundedBook(tasks) {
|
|
170
|
+
const book = { version: 1, tasks };
|
|
171
|
+
if (new TextEncoder().encode(JSON.stringify(book)).byteLength > 60 * 1024)
|
|
172
|
+
throw new ReminderInputError('提醒存储达到大小上限,请先取消旧任务');
|
|
173
|
+
return book;
|
|
174
|
+
}
|
|
175
|
+
function assertActor(actor) {
|
|
176
|
+
if (!actor.senderId)
|
|
177
|
+
throw new ReminderInputError('提醒需要真实发送者');
|
|
178
|
+
createDeliveryTarget({ conversation: actor.target });
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a ReminderActor from an Agent Tool invocation (IM origin only).
|
|
3
|
+
*/
|
|
4
|
+
import { type DeliveryTarget } from "zhin.js";
|
|
5
|
+
import { adapterFeatureId } from "zhin.js/adapter";
|
|
6
|
+
import type { ToolInvocationOrigin } from "@zhin.js/tool";
|
|
7
|
+
import { type ReminderActor } from "./service.js";
|
|
8
|
+
export interface ToolActorContext {
|
|
9
|
+
readonly origin: ToolInvocationOrigin;
|
|
10
|
+
readonly principal: Readonly<{
|
|
11
|
+
subjectId: string;
|
|
12
|
+
roles: readonly string[];
|
|
13
|
+
}>;
|
|
14
|
+
project<T>(feature: typeof adapterFeatureId): T;
|
|
15
|
+
}
|
|
16
|
+
/** Build the exact conversation DeliveryTarget for the current tool turn. */
|
|
17
|
+
export declare function toolDeliveryTarget(context: ToolActorContext): DeliveryTarget;
|
|
18
|
+
/** Actor bound to the invoking principal and the current IM conversation. */
|
|
19
|
+
export declare function reminderActorFromTool(context: ToolActorContext): ReminderActor;
|
|
20
|
+
//# sourceMappingURL=tool-actor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-actor.d.ts","sourceRoot":"","sources":["../src/tool-actor.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAqB,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAEtE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC9E,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,gBAAgB,GAAG,CAAC,CAAC;CACjD;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,cAAc,CA2B5E;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,gBAAgB,GACxB,aAAa,CAIf"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a ReminderActor from an Agent Tool invocation (IM origin only).
|
|
3
|
+
*/
|
|
4
|
+
import { createDeliveryTarget } from "zhin.js";
|
|
5
|
+
import { adapterFeatureId } from "zhin.js/adapter";
|
|
6
|
+
import { ReminderInputError } from "./service.js";
|
|
7
|
+
/** Build the exact conversation DeliveryTarget for the current tool turn. */
|
|
8
|
+
export function toolDeliveryTarget(context) {
|
|
9
|
+
const origin = context.origin;
|
|
10
|
+
if (origin.kind !== "im") {
|
|
11
|
+
throw new ReminderInputError("提醒工具只能在聊天会话中使用(不能用于 http/schedule 等来源)");
|
|
12
|
+
}
|
|
13
|
+
const index = context.project(adapterFeatureId);
|
|
14
|
+
const matches = index
|
|
15
|
+
.describe()
|
|
16
|
+
.filter((endpoint) => (String(endpoint.id) === origin.endpoint ||
|
|
17
|
+
endpoint.name === origin.endpoint) &&
|
|
18
|
+
index.clientAdapter(endpoint.id) === origin.platform);
|
|
19
|
+
if (matches.length !== 1) {
|
|
20
|
+
throw new ReminderInputError("无法确定当前会话所属的机器人账号");
|
|
21
|
+
}
|
|
22
|
+
return createDeliveryTarget({
|
|
23
|
+
conversation: {
|
|
24
|
+
endpoint: { id: String(matches[0].id), adapter: origin.platform },
|
|
25
|
+
kind: origin.scope,
|
|
26
|
+
id: origin.sceneId,
|
|
27
|
+
...(origin.threadId !== undefined ? { threadId: origin.threadId } : {}),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Actor bound to the invoking principal and the current IM conversation. */
|
|
32
|
+
export function reminderActorFromTool(context) {
|
|
33
|
+
const senderId = context.principal.subjectId?.trim();
|
|
34
|
+
if (!senderId)
|
|
35
|
+
throw new ReminderInputError("提醒工具需要真实发送者");
|
|
36
|
+
return { senderId, target: toolDeliveryTarget(context) };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=tool-actor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-actor.js","sourceRoot":"","sources":["../src/tool-actor.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,oBAAoB,EAAuB,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAqB,MAAM,iBAAiB,CAAC;AAEtE,OAAO,EAAE,kBAAkB,EAAsB,MAAM,cAAc,CAAC;AAQtE,6EAA6E;AAC7E,MAAM,UAAU,kBAAkB,CAAC,OAAyB;IAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9B,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,kBAAkB,CAC1B,wCAAwC,CACzC,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAe,gBAAgB,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,KAAK;SAClB,QAAQ,EAAE;SACV,MAAM,CACL,CAAC,QAAQ,EAAE,EAAE,CACX,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,QAAQ;QACtC,QAAQ,CAAC,IAAI,KAAK,MAAM,CAAC,QAAQ,CAAC;QACpC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,QAAQ,CACvD,CAAC;IACJ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,oBAAoB,CAAC;QAC1B,YAAY,EAAE;YACZ,QAAQ,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE;YAClE,IAAI,EAAE,MAAM,CAAC,KAAK;YAClB,EAAE,EAAE,MAAM,CAAC,OAAO;YAClB,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE;KACF,CAAC,CAAC;AACL,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,qBAAqB,CACnC,OAAyB;IAEzB,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;AAC3D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,105 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-reminder",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Persistent personal reminders for Zhin Plugin Runtime",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"development": "./src/index.ts",
|
|
12
|
+
"import": "./lib/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"README.md",
|
|
17
|
+
"commands",
|
|
18
|
+
"lib",
|
|
19
|
+
"plugin.js",
|
|
20
|
+
"schedules",
|
|
21
|
+
"skills",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@zhin.js/im-contract": "1.1.3"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"zod": "^4.0.0",
|
|
29
|
+
"zhin.js": "^1.1.7",
|
|
30
|
+
"@zhin.js/command": "^1.1.5",
|
|
31
|
+
"@zhin.js/schedule-feature": "^1.1.3",
|
|
32
|
+
"@zhin.js/tool": "^1.1.4",
|
|
33
|
+
"@zhin.js/skill": "^1.1.4"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"@zhin.js/command": {
|
|
37
|
+
"optional": true
|
|
38
|
+
},
|
|
39
|
+
"@zhin.js/schedule-feature": {
|
|
40
|
+
"optional": true
|
|
41
|
+
},
|
|
42
|
+
"@zhin.js/tool": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"@zhin.js/skill": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"zod": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^4.1.10",
|
|
55
|
+
"zod": "^4.4.3",
|
|
56
|
+
"zhin.js": "1.1.7",
|
|
57
|
+
"@zhin.js/tool": "1.1.4",
|
|
58
|
+
"@zhin.js/schedule-feature": "1.1.3",
|
|
59
|
+
"@zhin.js/skill": "1.1.4"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
63
|
+
},
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"repository": {
|
|
66
|
+
"type": "git",
|
|
67
|
+
"url": "git+https://github.com/zhinjs/zhin.git",
|
|
68
|
+
"directory": "plugins/utils/reminder"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public",
|
|
72
|
+
"registry": "https://registry.npmjs.org"
|
|
73
|
+
},
|
|
74
|
+
"zhin": {
|
|
75
|
+
"protocol": 1,
|
|
76
|
+
"type": "plugin",
|
|
77
|
+
"entry": "./plugin.js",
|
|
78
|
+
"engine": "^1.0.0",
|
|
79
|
+
"runtime": "trusted",
|
|
80
|
+
"features": [
|
|
81
|
+
{
|
|
82
|
+
"package": "@zhin.js/command",
|
|
83
|
+
"api": "^1.0.0"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"package": "@zhin.js/schedule-feature",
|
|
87
|
+
"api": "^1.0.0"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"package": "@zhin.js/tool",
|
|
91
|
+
"api": "^1.0.0"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"package": "@zhin.js/skill",
|
|
95
|
+
"api": "^1.0.0"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"plugins": []
|
|
99
|
+
},
|
|
100
|
+
"scripts": {
|
|
101
|
+
"build": "tsc",
|
|
102
|
+
"check": "tsc -p tsconfig.check.json",
|
|
103
|
+
"test": "vitest run --root ../../.. plugins/utils/reminder/tests"
|
|
104
|
+
}
|
|
6
105
|
}
|
package/plugin.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { databaseHostToken, definePlugin, outboundHostToken } from 'zhin.js';
|
|
3
|
+
import { pluginVerificationToken } from 'zhin.js/plugin-runtime';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { assertReminderTimezone, reminderConfig } from "./lib/config.js";
|
|
6
|
+
import { ReminderService } from "./lib/service.js";
|
|
7
|
+
import { reminderServiceToken } from "./lib/runtime.js";
|
|
8
|
+
export default definePlugin({
|
|
9
|
+
name: 'reminder', config: reminderConfig,
|
|
10
|
+
metadata: { displayName: '持久提醒' },
|
|
11
|
+
requires: [databaseHostToken, outboundHostToken],
|
|
12
|
+
setup(context) {
|
|
13
|
+
const config = context.config.get();
|
|
14
|
+
assertReminderTimezone(config.timezone);
|
|
15
|
+
const state = context.resources.use(databaseHostToken).state;
|
|
16
|
+
const outbound = context.resources.use(outboundHostToken);
|
|
17
|
+
if (!outbound.sendTarget)
|
|
18
|
+
throw new Error('提醒需要支持精确 DeliveryTarget 的 Outbound Host');
|
|
19
|
+
const service = new ReminderService(state, outbound, config);
|
|
20
|
+
context.resources.provide(reminderServiceToken, service);
|
|
21
|
+
context.resources.provide(pluginVerificationToken, {
|
|
22
|
+
checks: () => [{ id: 'local-state', title: '持久状态读写检查', scope: 'local', effects: ['写入、读取并删除一个独立探针键;不创建提醒、不发送消息'] }],
|
|
23
|
+
async run(checkId) {
|
|
24
|
+
if (checkId !== 'local-state')
|
|
25
|
+
throw new Error('Unknown reminder verification check');
|
|
26
|
+
const key = `verification/${randomUUID()}`;
|
|
27
|
+
try {
|
|
28
|
+
await state.set(key, { probe: true }, { ttlMs: 60_000 });
|
|
29
|
+
const value = await state.get(key);
|
|
30
|
+
if (value?.probe !== true)
|
|
31
|
+
throw new Error('持久状态读写结果不一致');
|
|
32
|
+
return { status: 'passed', summary: '本地持久状态读写通过', evidence: ['独立探针写入并成功读回;清理后删除', '仅验证当前 owner 的数据库状态;不证明调度或平台投递健康'] };
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await state.delete(key);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return () => service.stop();
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineSchedule } from '@zhin.js/schedule-feature';
|
|
3
|
+
import { reminderServiceToken } from "../../lib/runtime.js";
|
|
4
|
+
export default defineSchedule({
|
|
5
|
+
cron: '*/5 * * * * *',
|
|
6
|
+
description: '扫描持久提醒;周期错过轮次不补发',
|
|
7
|
+
execute: context => context.use(reminderServiceToken).tick(),
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineSchedule } from '@zhin.js/schedule-feature';
|
|
2
|
+
import { reminderServiceToken } from '../../src/runtime.js';
|
|
3
|
+
|
|
4
|
+
export default defineSchedule({
|
|
5
|
+
cron: '*/5 * * * * *',
|
|
6
|
+
description: '扫描持久提醒;周期错过轮次不补发',
|
|
7
|
+
execute: context => context.use(reminderServiceToken).tick(),
|
|
8
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reminder
|
|
3
|
+
description: >-
|
|
4
|
+
在当前聊天会话创建/查看/取消个人提醒(到点向该会话发一条消息)。
|
|
5
|
+
Use when the user asks to be reminded later in this chat.
|
|
6
|
+
Do NOT use for agent re-invocation or workday/lunar cron jobs — those use schedule_add.
|
|
7
|
+
Triggers: 提醒我、定时、闹钟、过会儿叫我、取消提醒.
|
|
8
|
+
keywords:
|
|
9
|
+
- 提醒
|
|
10
|
+
- reminder
|
|
11
|
+
- 定时
|
|
12
|
+
- 闹钟
|
|
13
|
+
- remind
|
|
14
|
+
tags:
|
|
15
|
+
- reminder
|
|
16
|
+
- schedule
|
|
17
|
+
tools:
|
|
18
|
+
- reminder_create
|
|
19
|
+
- reminder_list
|
|
20
|
+
- reminder_cancel
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# 个人提醒
|
|
24
|
+
|
|
25
|
+
## 何时使用本技能
|
|
26
|
+
|
|
27
|
+
- 用户要「X 分钟后提醒我…」「每天每隔 N 秒…」(消息回到**当前会话**)
|
|
28
|
+
- 查看或取消**自己**在当前会话的提醒
|
|
29
|
+
|
|
30
|
+
## 何时改用 `schedule_add`
|
|
31
|
+
|
|
32
|
+
- 需要到点**再次唤醒 Agent**(执行 prompt、跑工具),而不是只发一条提醒文案
|
|
33
|
+
- 需要公历/农历 cron、工作日、节假日等日历语义(见原生 `schedule_*` 工具)
|
|
34
|
+
|
|
35
|
+
## 工作流
|
|
36
|
+
|
|
37
|
+
1. 确认提醒文案与时间。绝对时间必须带 `Z` 或 `±hh:mm`(例如 `2026-10-14T09:00:00+08:00`)。
|
|
38
|
+
2. 一次性:`after`(`10m` / `600`)或 `at`;周期:`every_seconds`(≥ 60)。
|
|
39
|
+
3. `reminder_create` / `reminder_cancel` 需要用户批准;创建前复述内容与时间。
|
|
40
|
+
4. 用 `reminder_list` 核对;取消时带上 id。
|
|
41
|
+
|
|
42
|
+
## 注意
|
|
43
|
+
|
|
44
|
+
- 提醒绑定发送者 + 精确会话(含 endpoint / 线程);别人看不见、取消不了你的任务。
|
|
45
|
+
- 本插件没有 5 段 cron;周期只有固定间隔秒数。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from "@zhin.js/tool";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { replyReminder } from "../../../../lib/caller.js";
|
|
5
|
+
import { reminderServiceToken } from "../../../../lib/runtime.js";
|
|
6
|
+
import { reminderActorFromTool } from "../../../../lib/tool-actor.js";
|
|
7
|
+
export default defineAgentTool({
|
|
8
|
+
description: "Cancel one of your reminders in the current chat by id from reminder_list.",
|
|
9
|
+
requiresApproval: "always",
|
|
10
|
+
tags: ["reminder"],
|
|
11
|
+
keywords: ["取消提醒", "cancel reminder"],
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
id: z.string().min(1).describe("Reminder id"),
|
|
14
|
+
}),
|
|
15
|
+
async execute({ id }, context) {
|
|
16
|
+
return replyReminder(async () => {
|
|
17
|
+
const service = context.use(reminderServiceToken);
|
|
18
|
+
const ok = await service.cancel(reminderActorFromTool(context), id);
|
|
19
|
+
return ok
|
|
20
|
+
? `已取消提醒 ${id}`
|
|
21
|
+
: `找不到提醒 ${id}(或不属于你在当前会话的任务)`;
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineAgentTool } from "@zhin.js/tool";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { replyReminder } from "../../../../src/caller.js";
|
|
4
|
+
import { reminderServiceToken } from "../../../../src/runtime.js";
|
|
5
|
+
import { reminderActorFromTool } from "../../../../src/tool-actor.js";
|
|
6
|
+
|
|
7
|
+
export default defineAgentTool({
|
|
8
|
+
description:
|
|
9
|
+
"Cancel one of your reminders in the current chat by id from reminder_list.",
|
|
10
|
+
requiresApproval: "always",
|
|
11
|
+
tags: ["reminder"],
|
|
12
|
+
keywords: ["取消提醒", "cancel reminder"],
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
id: z.string().min(1).describe("Reminder id"),
|
|
15
|
+
}),
|
|
16
|
+
async execute({ id }, context) {
|
|
17
|
+
return replyReminder(async () => {
|
|
18
|
+
const service = context.use(reminderServiceToken);
|
|
19
|
+
const ok = await service.cancel(reminderActorFromTool(context), id);
|
|
20
|
+
return ok
|
|
21
|
+
? `已取消提醒 ${id}`
|
|
22
|
+
: `找不到提醒 ${id}(或不属于你在当前会话的任务)`;
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineAgentTool } from "@zhin.js/tool";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { replyReminder } from "../../../../lib/caller.js";
|
|
5
|
+
import { parseRelativeDelayMs } from "../../../../lib/duration.js";
|
|
6
|
+
import { reminderServiceToken } from "../../../../lib/runtime.js";
|
|
7
|
+
import { ReminderInputError, parseReminderInstant, } from "../../../../lib/service.js";
|
|
8
|
+
import { reminderActorFromTool } from "../../../../lib/tool-actor.js";
|
|
9
|
+
export default defineAgentTool({
|
|
10
|
+
description: "Create a personal reminder that delivers a message back to the current chat. " +
|
|
11
|
+
"Use after (relative) or at (ISO with offset) for one-off; every_seconds for repeating. " +
|
|
12
|
+
"Do NOT use this for agent re-invocation jobs — use schedule_add instead.",
|
|
13
|
+
requiresApproval: "always",
|
|
14
|
+
tags: ["reminder"],
|
|
15
|
+
keywords: ["提醒", "reminder", "定时", "闹钟"],
|
|
16
|
+
inputSchema: z.object({
|
|
17
|
+
text: z.string().min(1).max(500).describe("Reminder message body"),
|
|
18
|
+
after: z.string().optional().describe("Relative delay: 600, 10m, 2h, 1d"),
|
|
19
|
+
at: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Absolute ISO time with Z or ±hh:mm, e.g. 2026-10-14T09:00:00+08:00"),
|
|
23
|
+
every_seconds: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(60)
|
|
27
|
+
.max(365 * 24 * 60 * 60)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Repeat interval in seconds (min 60). First fire is now+interval."),
|
|
30
|
+
}),
|
|
31
|
+
async execute(input, context) {
|
|
32
|
+
return replyReminder(async () => {
|
|
33
|
+
const modes = [
|
|
34
|
+
input.after,
|
|
35
|
+
input.at,
|
|
36
|
+
input.every_seconds !== undefined ? "every" : undefined,
|
|
37
|
+
].filter(Boolean);
|
|
38
|
+
if (modes.length !== 1) {
|
|
39
|
+
throw new ReminderInputError("请在 after / at / every_seconds 中只选一种时间表达");
|
|
40
|
+
}
|
|
41
|
+
const service = context.use(reminderServiceToken);
|
|
42
|
+
const actor = reminderActorFromTool(context);
|
|
43
|
+
if (input.every_seconds !== undefined) {
|
|
44
|
+
const intervalMs = input.every_seconds * 1000;
|
|
45
|
+
const task = await service.add(actor, input.text, Date.now() + intervalMs, intervalMs);
|
|
46
|
+
return `已创建周期提醒\n${service.describe(task)}`;
|
|
47
|
+
}
|
|
48
|
+
if (input.after) {
|
|
49
|
+
const delayMs = parseRelativeDelayMs(input.after);
|
|
50
|
+
const task = await service.add(actor, input.text, Date.now() + delayMs, null);
|
|
51
|
+
return `已创建提醒\n${service.describe(task)}`;
|
|
52
|
+
}
|
|
53
|
+
const task = await service.add(actor, input.text, parseReminderInstant(input.at), null);
|
|
54
|
+
return `已创建提醒\n${service.describe(task)}`;
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
});
|