dsh-worktodo 0.1.0 → 0.2.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/CHANGELOG.md +141 -2
- package/README.en.md +13 -3
- package/README.md +18 -3
- package/lib/client.js +597 -73
- package/lib/client.js.map +4 -4
- package/lib/index.js +107 -1
- package/package.json +15 -25
- package/src/client/index.js +462 -71
- package/src/parse-due.mjs +170 -0
- package/src/records.mjs +131 -16
- package/src/schema.mjs +9 -1
- package/src/service.mjs +58 -3
- package/src/store.mjs +4 -1
- package/src/tools.mjs +137 -4
package/src/tools.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { parseNaturalDue } from './parse-due.mjs';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* 面向模型的 worktodo_* 工具。
|
|
3
5
|
*
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
*/
|
|
23
25
|
export const TOOL_NAMES = Object.freeze([
|
|
24
26
|
'worktodo_add',
|
|
27
|
+
'worktodo_add_many',
|
|
25
28
|
'worktodo_list',
|
|
26
29
|
'worktodo_update',
|
|
27
30
|
'worktodo_done',
|
|
@@ -89,6 +92,7 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
89
92
|
parameters: {
|
|
90
93
|
title: { type: 'string', required: true, description: '待办内容,一句简短的祈使句,例如「把 v0.1.0 发到 npm」。' },
|
|
91
94
|
due: { type: 'string', description: '截止日期:YYYY-MM-DD / today / tomorrow / +3d。省略表示没有截止日期。' },
|
|
95
|
+
dueTime: { type: 'string', description: '截止时间 HH:MM(可选,只有给了 due 才有意义;例如 14:30)。' },
|
|
92
96
|
priority: { type: 'string', enum: ['high', 'normal', 'low'], description: '优先级,默认 normal。真正卡住别人的事才给 high。' },
|
|
93
97
|
project: { type: 'string', description: '项目或领域标签,例如 dsh-worktodo。省略表示不归类。' },
|
|
94
98
|
notes: { type: 'string', description: '补充说明:背景、链接、验收标准。默认空。' },
|
|
@@ -102,6 +106,7 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
102
106
|
id: { type: 'string', required: true },
|
|
103
107
|
title: { type: 'string', required: true },
|
|
104
108
|
due: { type: 'string' },
|
|
109
|
+
dueTime: { type: 'string' },
|
|
105
110
|
priority: { type: 'string', required: true },
|
|
106
111
|
project: { type: 'string' },
|
|
107
112
|
status: { type: 'string', required: true },
|
|
@@ -110,15 +115,16 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
110
115
|
},
|
|
111
116
|
render: (_args, value) => [{
|
|
112
117
|
type: 'text',
|
|
113
|
-
text: `已记入工作待办:${value.title}(${value.id}${value.due ? `,截止 ${value.due}` : ''})`
|
|
118
|
+
text: `已记入工作待办:${value.title}(${value.id}${value.due ? `,截止 ${value.due}${value.dueTime ? ` ${value.dueTime}` : ''}` : ''})`
|
|
114
119
|
+ (value.stats ? `|当前未完成 ${value.stats.open} 条,逾期 ${value.stats.overdue} 条` : ''),
|
|
115
120
|
}],
|
|
116
121
|
},
|
|
117
122
|
execute(args) {
|
|
118
|
-
return service.create(args, { source: 'agent' }).then(unwrap).then((result) => withStats(compact({
|
|
123
|
+
return service.create(withParsedDue(args), { source: 'agent' }).then(unwrap).then((result) => withStats(compact({
|
|
119
124
|
id: result.todo.id,
|
|
120
125
|
title: result.todo.title,
|
|
121
126
|
due: result.todo.due ?? undefined,
|
|
127
|
+
dueTime: result.todo.dueTime ?? undefined,
|
|
122
128
|
priority: result.todo.priority,
|
|
123
129
|
project: result.todo.project ?? undefined,
|
|
124
130
|
status: result.todo.status,
|
|
@@ -127,6 +133,112 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
127
133
|
presentCall: (args) => ({ card: 'generic', title: '登记工作待办', kind: 'other', rawInput: args.title }),
|
|
128
134
|
})));
|
|
129
135
|
|
|
136
|
+
// ---- 批量新增(AI 友好:一口气登记多条)----
|
|
137
|
+
disposers.push(ctx.tools.register(defineTool({
|
|
138
|
+
name: 'worktodo_add_many',
|
|
139
|
+
description:
|
|
140
|
+
'一次登记多条工作待办(用户一口气说了好几件事、或你整理出一串要跟进的活时用它;分开一条条调 worktodo_add 也行,但这样更省调用)。'
|
|
141
|
+
+ '某条不合法只跳过那一条并把原因回传,不影响其余条目。',
|
|
142
|
+
parameters: {
|
|
143
|
+
items: {
|
|
144
|
+
type: 'array',
|
|
145
|
+
required: true,
|
|
146
|
+
description: '要登记的事;每项至少给 title。',
|
|
147
|
+
items: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
additionalProperties: false,
|
|
150
|
+
properties: {
|
|
151
|
+
title: { type: 'string', required: true, description: '待办内容(一句简短的祈使句)。' },
|
|
152
|
+
due: { type: 'string', description: '截止日期:YYYY-MM-DD / today / tomorrow / +3d。' },
|
|
153
|
+
dueTime: { type: 'string', description: '截止时间 HH:MM(可选)。' },
|
|
154
|
+
priority: { type: 'string', enum: ['high', 'normal', 'low'], description: '默认 normal。' },
|
|
155
|
+
project: { type: 'string', description: '项目或领域标签。' },
|
|
156
|
+
notes: { type: 'string', description: '背景、链接、验收标准。' },
|
|
157
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'], description: '默认 pending。' },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
output: {
|
|
163
|
+
schema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
additionalProperties: false,
|
|
166
|
+
properties: {
|
|
167
|
+
count: { type: 'number', required: true },
|
|
168
|
+
created: {
|
|
169
|
+
type: 'array',
|
|
170
|
+
required: true,
|
|
171
|
+
items: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
additionalProperties: false,
|
|
174
|
+
properties: {
|
|
175
|
+
id: { type: 'string', required: true },
|
|
176
|
+
title: { type: 'string', required: true },
|
|
177
|
+
due: { type: 'string' },
|
|
178
|
+
dueTime: { type: 'string' },
|
|
179
|
+
priority: { type: 'string', required: true },
|
|
180
|
+
project: { type: 'string' },
|
|
181
|
+
status: { type: 'string', required: true },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
skipped: {
|
|
186
|
+
type: 'array',
|
|
187
|
+
required: true,
|
|
188
|
+
items: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
additionalProperties: false,
|
|
191
|
+
properties: {
|
|
192
|
+
index: { type: 'number', required: true },
|
|
193
|
+
title: { type: 'string', required: true },
|
|
194
|
+
reason: { type: 'string', required: true },
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
stats: { type: 'object', additionalProperties: true },
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
render: (_args, value) => [{
|
|
202
|
+
type: 'text',
|
|
203
|
+
text: `已记入工作待办 ${value.count} 条`
|
|
204
|
+
+ (value.skipped.length ? `,跳过 ${value.skipped.length} 条(${value.skipped.map((entry) => `#${entry.index} ${entry.reason}`).join(';')})` : '')
|
|
205
|
+
+ (value.stats ? `|当前未完成 ${value.stats.open} 条,逾期 ${value.stats.overdue} 条` : ''),
|
|
206
|
+
}],
|
|
207
|
+
},
|
|
208
|
+
async execute(args) {
|
|
209
|
+
const created = [];
|
|
210
|
+
const skipped = [];
|
|
211
|
+
const items = Array.isArray(args.items) ? args.items : [];
|
|
212
|
+
for (const [index, item] of items.entries()) {
|
|
213
|
+
try {
|
|
214
|
+
const result = unwrap(await service.create(withParsedDue(item ?? {}), { source: 'agent' }));
|
|
215
|
+
created.push(compact({
|
|
216
|
+
id: result.todo.id,
|
|
217
|
+
title: result.todo.title,
|
|
218
|
+
due: result.todo.due ?? undefined,
|
|
219
|
+
dueTime: result.todo.dueTime ?? undefined,
|
|
220
|
+
priority: result.todo.priority,
|
|
221
|
+
project: result.todo.project ?? undefined,
|
|
222
|
+
status: result.todo.status,
|
|
223
|
+
}));
|
|
224
|
+
} catch (error) {
|
|
225
|
+
skipped.push({
|
|
226
|
+
index,
|
|
227
|
+
title: typeof item?.title === 'string' ? item.title : '',
|
|
228
|
+
reason: String(error?.message ?? error),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return withStats(compact({ count: created.length, created, skipped }));
|
|
233
|
+
},
|
|
234
|
+
presentCall: (args) => ({
|
|
235
|
+
card: 'generic',
|
|
236
|
+
title: `登记 ${Array.isArray(args.items) ? args.items.length : 0} 条工作待办`,
|
|
237
|
+
kind: 'other',
|
|
238
|
+
rawInput: '',
|
|
239
|
+
}),
|
|
240
|
+
})));
|
|
241
|
+
|
|
130
242
|
// ---- 查询 ----
|
|
131
243
|
disposers.push(ctx.tools.register(defineTool({
|
|
132
244
|
name: 'worktodo_list',
|
|
@@ -138,6 +250,7 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
138
250
|
filter: { type: 'string', enum: ['open', 'today', 'overdue', 'week', 'doing', 'completed', 'all'], description: '默认 open(未完成)。' },
|
|
139
251
|
project: { type: 'string', description: '只看某个项目标签。' },
|
|
140
252
|
query: { type: 'string', description: '按标题或备注里的关键词过滤。' },
|
|
253
|
+
sort: { type: 'string', enum: ['manual', 'priority', 'created'], description: '排序:manual=尊重用户拖动过的顺序(默认,用户看到的顺序);priority=高优先级在前;created=最近添加的在前。' },
|
|
141
254
|
},
|
|
142
255
|
output: {
|
|
143
256
|
schema: {
|
|
@@ -156,7 +269,7 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
156
269
|
lines: result.todos.length
|
|
157
270
|
? result.todos.map((t) => {
|
|
158
271
|
const box = t.status === 'completed' ? '[x]' : t.status === 'in_progress' ? '[~]' : '[ ]';
|
|
159
|
-
const bits = [t.priority === 'high' ? '高' : t.priority === 'low' ? '低' : null, t.due ? `${t.due}${t.due < result.today && t.status !== 'completed' ? '(逾期)' : ''}` : null, t.project ? `#${t.project}` : null].filter(Boolean);
|
|
272
|
+
const bits = [t.priority === 'high' ? '高' : t.priority === 'low' ? '低' : null, t.due ? `${t.due}${t.dueTime ? ` ${t.dueTime}` : ''}${t.due < result.today && t.status !== 'completed' ? '(逾期)' : ''}` : null, t.project ? `#${t.project}` : null].filter(Boolean);
|
|
160
273
|
return `${box} ${t.id} ${t.title}${bits.length ? ` (${bits.join(', ')})` : ''}`;
|
|
161
274
|
}).join('\n')
|
|
162
275
|
: '(没有符合条件的待办)',
|
|
@@ -177,7 +290,8 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
177
290
|
parameters: {
|
|
178
291
|
ref: { type: 'string', required: true, description: '待办的 id(推荐)或标题。' },
|
|
179
292
|
title: { type: 'string', description: '新标题。' },
|
|
180
|
-
due: { type: 'string', description: '新截止日期;传 none
|
|
293
|
+
due: { type: 'string', description: '新截止日期;传 none 清空(清空日期会一并清掉时间)。' },
|
|
294
|
+
dueTime: { type: 'string', description: '新截止时间 HH:MM;传 none 清空。' },
|
|
181
295
|
priority: { type: 'string', enum: ['high', 'normal', 'low'] },
|
|
182
296
|
project: { type: 'string', description: '新项目标签;传 none 清空。' },
|
|
183
297
|
notes: { type: 'string', description: '新备注。' },
|
|
@@ -342,4 +456,23 @@ export async function registerWorkTodoTools({ ctx, service, logger }) {
|
|
|
342
456
|
}
|
|
343
457
|
},
|
|
344
458
|
};
|
|
459
|
+
}/**
|
|
460
|
+
* 让工具也能"说人话":标题里写了「明天下午三点」这类说法时,交给与面板**同一份**解析器
|
|
461
|
+
* (`src/parse-due.mjs`)。显式传了 `due` 就以显式为准,绝不被标题覆盖。
|
|
462
|
+
* @param {object} input
|
|
463
|
+
* @returns {object} 落库用的参数(标题里的日期词会被摘掉)
|
|
464
|
+
*/
|
|
465
|
+
export function withParsedDue(input = {}) {
|
|
466
|
+
const explicit = input.due !== undefined && input.due !== null && input.due !== '';
|
|
467
|
+
if (explicit) return input;
|
|
468
|
+
const parsed = parseNaturalDue(String(input.title ?? ''), Date.now());
|
|
469
|
+
if (!parsed.matched.length) return input;
|
|
470
|
+
return {
|
|
471
|
+
...input,
|
|
472
|
+
title: parsed.title,
|
|
473
|
+
due: parsed.due ?? undefined,
|
|
474
|
+
dueTime: input.dueTime ?? parsed.dueTime ?? undefined,
|
|
475
|
+
};
|
|
345
476
|
}
|
|
477
|
+
|
|
478
|
+
|