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/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* 浏览器半侧(侧栏入口 + 面板)在 lib/client.js,按 package.json 的 dsh.client 组成 bundle。
|
|
17
17
|
*/
|
|
18
18
|
import { readFileSync } from 'node:fs';
|
|
19
|
+
|
|
19
20
|
import { createWorkTodoService } from '../src/service.mjs';
|
|
20
21
|
import { openWorkTodoStore } from '../src/store.mjs';
|
|
21
22
|
|
|
@@ -113,6 +114,46 @@ function readJsonBody(request) {
|
|
|
113
114
|
* @param {object} ctx cordis 上下文
|
|
114
115
|
* @param {object} [config] 组合配置(cordis.patch.yml 的 config)
|
|
115
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* 给模型的用法指引(AI 推荐接口)。
|
|
119
|
+
*
|
|
120
|
+
* 为什么要有它:工具描述只讲「这个工具做什么」,而模型真正需要的是「什么时候该用哪个」——
|
|
121
|
+
* 尤其是本插件与官方 todo_write 的分工(会话内的步骤拆解 vs 人拥有的跨会话工作)。
|
|
122
|
+
* 通过 Host 的 systemPrompt.section() 贡献一段提示,是官方 practices 文档给的推荐做法。
|
|
123
|
+
* order 取 3050:官方 SECTION_ORDERS 里内置工具段是 1000–3000、MCP_SERVERS 是 3100,
|
|
124
|
+
* 插在两者之间读起来就是「插件工具的使用说明」。
|
|
125
|
+
*/
|
|
126
|
+
/**
|
|
127
|
+
* 版本与更新提示。
|
|
128
|
+
*
|
|
129
|
+
* 只查 npm registry 的 dist-tags.latest(不下载、不安装),结果缓存 6 小时,失败静默——
|
|
130
|
+
* 面板角落显示「当前版本」与「有新版本 vX.Y.Z」,绝不让网络问题影响面板。
|
|
131
|
+
* 测试里设 WORKTODO_NO_UPDATE_CHECK=1 可完全关掉(单元测试不该打网络)。
|
|
132
|
+
*/
|
|
133
|
+
const UPDATE_CHECK_OFF = process.env.WORKTODO_NO_UPDATE_CHECK === '1';
|
|
134
|
+
const UPDATE_TTL_MS = 6 * 3600 * 1000;
|
|
135
|
+
const PACKAGE_NAME = 'dsh-worktodo';
|
|
136
|
+
|
|
137
|
+
/** 语义化版本比较:a > b 返回正数。 */
|
|
138
|
+
function compareVersion(a, b) {
|
|
139
|
+
const pa = String(a).replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
|
|
140
|
+
const pb = String(b).replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
|
|
141
|
+
for (let i = 0; i < 3; i += 1) {
|
|
142
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const GUIDANCE_TEXT = [
|
|
148
|
+
'工作待办(worktodo_*):属于用户、跨会话持久的工作清单;侧栏面板与这些工具读写同一份数据。',
|
|
149
|
+
'- 该用:用户说出要办的事、承诺或截止日期(「下周要交」「等对方回复」「记一下」),或你在工作中发现超出本会话、仍需人跟进的待办 → worktodo_add。',
|
|
150
|
+
'- 不该用:当前这次任务的步骤拆解与你自己的执行顺序 → 那是会话内的 todo_write(整表替换、只属于本会话)。两者互补:todo_write 说「这一步怎么走」,工作待办说「这件事还欠着」。',
|
|
151
|
+
'- 动手前先看:worktodo_list({ filter: "today" }) 或 "overdue";按项目看:worktodo_list({ project: "<项目>" })。用户问「今天要做什么」时先查再答,不要凭记忆。',
|
|
152
|
+
'- 加之前先查重:worktodo_list({ query: "<关键词>" }),同一条不要登记两次。',
|
|
153
|
+
'- 做完了 / 状态变了:worktodo_done(ref 可给 id 或标题);改标题、截止、优先级、项目用 worktodo_update。',
|
|
154
|
+
'- 约定:标题写成一句简短的祈使句;project 用仓库名或产品名;priority 只在「真的卡住别人」时给 high;链接、背景与验收标准放 notes。',
|
|
155
|
+
].join('\n');
|
|
156
|
+
|
|
116
157
|
export async function apply(ctx, config = {}) {
|
|
117
158
|
const logger = typeof ctx.logger === 'function' ? ctx.logger(name) : ctx.logger;
|
|
118
159
|
|
|
@@ -159,6 +200,58 @@ export async function apply(ctx, config = {}) {
|
|
|
159
200
|
}, 'worktodo: agent tools');
|
|
160
201
|
});
|
|
161
202
|
|
|
203
|
+
// ---- 版本与更新(后台、不阻塞任何请求)----
|
|
204
|
+
let updateState = { at: 0, latest: null, error: null };
|
|
205
|
+
const refreshUpdate = async () => {
|
|
206
|
+
if (UPDATE_CHECK_OFF) return updateState;
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
if (updateState.at && now - updateState.at < UPDATE_TTL_MS) return updateState;
|
|
209
|
+
updateState = { ...updateState, at: now }; // 先占位,避免并发重复请求
|
|
210
|
+
try {
|
|
211
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`, {
|
|
212
|
+
headers: { accept: 'application/json' },
|
|
213
|
+
signal: AbortSignal.timeout(8000),
|
|
214
|
+
});
|
|
215
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
216
|
+
const body = await res.json();
|
|
217
|
+
const latest = String(body?.['dist-tags']?.latest ?? '');
|
|
218
|
+
updateState = { at: now, latest: /^\d+\.\d+\.\d+$/.test(latest) ? latest : null, error: null };
|
|
219
|
+
} catch (error) {
|
|
220
|
+
updateState = { at: now, latest: null, error: String(error?.message ?? error) };
|
|
221
|
+
}
|
|
222
|
+
return updateState;
|
|
223
|
+
};
|
|
224
|
+
const updatePayload = () => {
|
|
225
|
+
const latest = updateState.latest;
|
|
226
|
+
return {
|
|
227
|
+
current: PLUGIN_VERSION,
|
|
228
|
+
latest: latest ?? undefined,
|
|
229
|
+
hasUpdate: Boolean(latest) && !UPDATE_CHECK_OFF && compareVersion(latest, PLUGIN_VERSION) > 0,
|
|
230
|
+
page: `https://www.npmjs.com/package/${PACKAGE_NAME}`,
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
if (!UPDATE_CHECK_OFF) void refreshUpdate(); // 启动时后台查一次(失败静默)
|
|
234
|
+
|
|
235
|
+
// ---- 给模型的用法指引(可选服务;缺 systemPrompt 时静默跳过)----
|
|
236
|
+
ctx.inject(['systemPrompt'], (spCtx) => {
|
|
237
|
+
spCtx.effect(() => {
|
|
238
|
+
if (typeof spCtx.systemPrompt?.section !== 'function') return () => {};
|
|
239
|
+
let disposeSection = () => {};
|
|
240
|
+
try {
|
|
241
|
+
disposeSection = spCtx.systemPrompt.section({
|
|
242
|
+
name: 'worktodo:guidance',
|
|
243
|
+
order: 3050,
|
|
244
|
+
text: GUIDANCE_TEXT,
|
|
245
|
+
}) ?? (() => {});
|
|
246
|
+
logger?.info?.('已注册给模型的用法指引(systemPrompt.section)');
|
|
247
|
+
} catch (error) {
|
|
248
|
+
// 段落注册失败不影响工具与面板;只记一行,方便排查
|
|
249
|
+
logger?.warn?.(`用法指引注册失败:${error?.message ?? error}`);
|
|
250
|
+
}
|
|
251
|
+
return () => { try { disposeSection?.(); } catch { /* 卸载清理失败不打断 */ } };
|
|
252
|
+
}, 'worktodo: system prompt guidance');
|
|
253
|
+
});
|
|
254
|
+
|
|
162
255
|
// ---- HTTP 路由(可选服务)----
|
|
163
256
|
ctx.inject(['webServer'], (webCtx) => {
|
|
164
257
|
/**
|
|
@@ -211,12 +304,25 @@ export async function apply(ctx, config = {}) {
|
|
|
211
304
|
path: `${ROUTE_BASE}/list`,
|
|
212
305
|
handler: method('GET', async (request, response) => {
|
|
213
306
|
const url = new URL(request.url ?? ROUTE_BASE, 'http://localhost');
|
|
307
|
+
if (!UPDATE_CHECK_OFF && Date.now() - updateState.at >= UPDATE_TTL_MS) void refreshUpdate();
|
|
214
308
|
const result = await service.list({
|
|
215
309
|
filter: url.searchParams.get('filter') ?? 'open',
|
|
216
310
|
project: url.searchParams.get('project') ?? undefined,
|
|
217
311
|
query: url.searchParams.get('query') ?? undefined,
|
|
312
|
+
sort: url.searchParams.get('sort') ?? undefined,
|
|
218
313
|
});
|
|
219
|
-
reply(response, result
|
|
314
|
+
reply(response, result.ok
|
|
315
|
+
? { ...result, version: PLUGIN_VERSION, update: updatePayload() }
|
|
316
|
+
: result);
|
|
317
|
+
}),
|
|
318
|
+
}),
|
|
319
|
+
// 写:手动排序(拖动)。body: { ids: [待办 id,按新顺序排列] }
|
|
320
|
+
webCtx.webServer.register({
|
|
321
|
+
kind: 'exact',
|
|
322
|
+
path: `${ROUTE_BASE}/reorder`,
|
|
323
|
+
handler: method('POST', async (request, response) => {
|
|
324
|
+
const body = await readJsonBody(request);
|
|
325
|
+
reply(response, await service.reorder(body));
|
|
220
326
|
}),
|
|
221
327
|
}),
|
|
222
328
|
// 写:新增
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-worktodo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DSH 工作待办面板(社区插件,非官方):归你所有、跨会话持久的工作待办——优先级 / 截止日期 / 项目 / 逾期与本周视图,agent 通过 worktodo_* 工具读写同一份列表。官方 todo_write 属于单个会话且只能整表替换,这份列表属于人。Human-owned cross-session work todo list for DeepSeek Harness: priorities, due dates, projects, overdue/week views and worktodo_* tools the agent shares with you.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -75,31 +75,22 @@
|
|
|
75
75
|
]
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
|
-
"dependencies": {
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"@deepseek-ai/dsh-home-paths": "^0.2.0-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-storage-domain": "^0.2.0-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-tools": "^0.2.0-rc.2"
|
|
82
|
+
},
|
|
79
83
|
"peerDependencies": {
|
|
80
|
-
"@deepseek-ai/cordis": "^4.0.2"
|
|
81
|
-
"@deepseek-ai/dsh-home-paths": ">=0.1.0-rc.1 <0.3.0-0",
|
|
82
|
-
"@deepseek-ai/dsh-storage-domain": ">=0.1.0-rc.1 <0.3.0-0",
|
|
83
|
-
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.1 <0.3.0-0"
|
|
84
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
84
85
|
},
|
|
85
86
|
"peerDependenciesMeta": {
|
|
86
87
|
"@deepseek-ai/cordis": {
|
|
87
88
|
"optional": true
|
|
88
|
-
},
|
|
89
|
-
"@deepseek-ai/dsh-tools": {
|
|
90
|
-
"optional": true
|
|
91
|
-
},
|
|
92
|
-
"@deepseek-ai/dsh-storage-domain": {
|
|
93
|
-
"optional": true
|
|
94
|
-
},
|
|
95
|
-
"@deepseek-ai/dsh-home-paths": {
|
|
96
|
-
"optional": true
|
|
97
89
|
}
|
|
98
90
|
},
|
|
99
91
|
"devDependencies": {
|
|
100
92
|
"@deepseek-ai/cordis": "4.0.4",
|
|
101
93
|
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
102
|
-
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
103
94
|
"@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
|
|
104
95
|
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
105
96
|
"@deepseek-ai/dsh-ptc-runtime": "0.2.0-rc.2",
|
|
@@ -108,20 +99,19 @@
|
|
|
108
99
|
"@deepseek-ai/dsh-scope": "0.2.0-rc.2",
|
|
109
100
|
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
110
101
|
"@deepseek-ai/dsh-storage": "0.2.0-rc.2",
|
|
111
|
-
"@deepseek-ai/dsh-storage-domain": "0.2.0-rc.2",
|
|
112
102
|
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
|
|
113
|
-
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
114
103
|
"@deepseek-ai/dsh-user-approval": "0.2.0-rc.2",
|
|
115
104
|
"@deepseek-ai/dsh-util-values": "0.2.0-rc.2",
|
|
116
105
|
"esbuild": "^0.28.2"
|
|
117
106
|
},
|
|
118
|
-
"publishConfig": {
|
|
119
|
-
"registry": "https://registry.npmjs.org/"
|
|
120
|
-
},
|
|
121
|
-
"license": "MIT",
|
|
122
107
|
"scripts": {
|
|
123
108
|
"build": "node tools/build-client.mjs",
|
|
124
109
|
"watch": "node tools/build-client.mjs --watch",
|
|
125
|
-
"test": "node tools/verify-all.mjs"
|
|
126
|
-
|
|
127
|
-
}
|
|
110
|
+
"test": "node tools/verify-all.mjs",
|
|
111
|
+
"prepublishOnly": "node tools/build-client.mjs && node tools/verify-all.mjs"
|
|
112
|
+
},
|
|
113
|
+
"publishConfig": {
|
|
114
|
+
"registry": "https://registry.npmjs.org/"
|
|
115
|
+
},
|
|
116
|
+
"license": "MIT"
|
|
117
|
+
}
|