dsh-global-todo 0.12.9 → 0.12.11
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 +24 -0
- package/README.md +1 -1
- package/docs/DISTRIBUTION.md +2 -2
- package/lib/client.js +18 -0
- package/lib/index.js +1 -1
- package/lib/routes.js +13 -5
- package/lib/service.js +19 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## [0.12.11] — 2026-10-21
|
|
2
|
+
|
|
3
|
+
One more silent fallback, found while re-reading the settings path.
|
|
4
|
+
|
|
5
|
+
- **`{idleMinutes: 30.5}` was accepted and then ignored.** `normalizeSettings` only keeps
|
|
6
|
+
integers for the minute fields and otherwise falls back to the default, so the request looked
|
|
7
|
+
like it worked while the value never changed. The settings route now rejects a fractional
|
|
8
|
+
minute with `400 invalid-body`, like every other out-of-range value.
|
|
9
|
+
|
|
10
|
+
## [0.12.10] — 2026-10-21
|
|
11
|
+
|
|
12
|
+
Two follow-ups from #3, plus the notification channel this repository actually has.
|
|
13
|
+
|
|
14
|
+
- **`/health` names what waits for review.** `review.count` said something was waiting;
|
|
15
|
+
`review.items` says which (id, seq, title, doneAt, newest first) — checking the state no longer
|
|
16
|
+
means opening the panel and clicking through the list.
|
|
17
|
+
- **The panel says when the running version is older than the installed one.** `stale` was added to
|
|
18
|
+
`/health` in 0.12.9; the panel now shows a one-time notice naming both versions and saying to
|
|
19
|
+
restart `dsh web`. This is the trap where the client is new, the module graph is old, and the
|
|
20
|
+
panel calls routes that 404.
|
|
21
|
+
- **`tools/notify.mjs`** — pushes release / CI-failed / new-issue messages to WeChat through the
|
|
22
|
+
host's delivery library, which owns the freshness window and the outbox. `--dry-run` composes and
|
|
23
|
+
reports the window without sending; a closed window queues instead of failing.
|
|
24
|
+
|
|
1
25
|
## [0.12.9] — 2026-10-21
|
|
2
26
|
|
|
3
27
|
Everything a Windows tester found in a 41-case sweep, answered (#3).
|
package/README.md
CHANGED
|
@@ -381,7 +381,7 @@ fence when that service is present, and by a strict loopback check otherwise.
|
|
|
381
381
|
| POST | `/api/global-todo/capture` | `{ sessionId? }` — ask the current conversation to collect its follow-ups |
|
|
382
382
|
| POST | `/api/global-todo/bulk` | `{ ids, status?, addTags?, removeTags? }` — one change across a selection; a stale id lands in `unmatched` instead of failing the batch |
|
|
383
383
|
| POST | `/api/global-todo/ingest` | `{ items: [{ title, source: { kind }, confidence }], dryRun? }` with `Authorization: Bearer <settings.ingestToken>` — external items; below `ingestConfirmBelow` they wait in the inbox |
|
|
384
|
-
| GET | `/api/global-todo/health` | — (store location, counts, dispatch capabilities, plus **tool registration** and **whether the completion watcher attached** — both fail quietly, so both are reported — and `metrics`: dispatch success rate, agent tool calls, list growth; `autoCollect`: why each waiting conversation was or was not asked, counters, and how the settings file loaded; `version` is the running module and `packageVersion`/`stale` say whether the installed package is newer than the running one) |
|
|
384
|
+
| GET | `/api/global-todo/health` | — (store location, counts, dispatch capabilities, plus **tool registration** and **whether the completion watcher attached** — both fail quietly, so both are reported — and `metrics`: dispatch success rate, agent tool calls, list growth; `autoCollect`: why each waiting conversation was or was not asked, counters, and how the settings file loaded; `version` is the running module and `packageVersion`/`stale` say whether the installed package is newer than the running one; `review.items` names the finished todos that still wait for a person, so a check does not have to drive the panel) |
|
|
385
385
|
| POST | `/api/global-todo/create` | `{ title, description?, links?, steps?, tags?, priority?, dueAt? }` |
|
|
386
386
|
| POST | `/api/global-todo/update` | `{ id, ...fields, by?: human\|agent }` |
|
|
387
387
|
| POST | `/api/global-todo/status` | `{ id, status, by?: human\|agent }` |
|
package/docs/DISTRIBUTION.md
CHANGED
|
@@ -15,10 +15,10 @@ of trusting this table.
|
|
|
15
15
|
|
|
16
16
|
| Registry | Form | What it gives you | Verified state |
|
|
17
17
|
|---|---|---|---|
|
|
18
|
-
| **npm** | the package | the install path | `dsh-global-todo@0.
|
|
18
|
+
| **npm** | the package | the install path | `dsh-global-todo@0.12.10` |
|
|
19
19
|
| **1024 Store** ([deepseek1024.com](https://deepseek1024.com)) · [repo](https://github.com/imsai-sh/awesome-deepseek-harness-plugins) | catalog entry, merged [#654](https://github.com/imsai-sh/awesome-deepseek-harness-plugins/pull/654) | searchable, has a public API | 🟡 entry live, but its install line still resolves to `github:LUgABeEEE/dsh-global-todo` — their npm enrichment has not caught up. Install works either way. |
|
|
20
20
|
| **dsh.pub** · [page](https://dsh.pub/en/plugins/dsh-global-todo/) | published page + `dshpub` CLI | `npx dshpub add LUgABeEEE/dsh-global-todo` | ✅ page live, pinned to a commit |
|
|
21
|
-
| **DSH-Plugins-Marketplace** ([bradeGithub](https://github.com/bradeGithub/DSH-Plugins-Marketplace)) | `registry.json`, collected from the `dsh-plugin` topic | one-click install inside DSH | ✅ `v0.
|
|
21
|
+
| **DSH-Plugins-Marketplace** ([bradeGithub](https://github.com/bradeGithub/DSH-Plugins-Marketplace)) | `registry.json`, collected from the `dsh-plugin` topic | one-click install inside DSH | ✅ `v0.12.x`, risk tier `safe` |
|
|
22
22
|
| **awesome-dsh-plugin** ([19.7k★](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)) | one YAML per plugin | the largest directory | ✅ merged [#7498](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/7498) |
|
|
23
23
|
| **walkinglabs/awesome-deepseek-harness-plugins** | README entry (source-verified list) | review-backed listing | ✅ merged [#114](https://github.com/walkinglabs/awesome-deepseek-harness-plugins/pull/114) |
|
|
24
24
|
|
package/lib/client.js
CHANGED
|
@@ -175,6 +175,9 @@ window.__ModuleLoader__.load({
|
|
|
175
175
|
'notice.autoCollectOn': '已开启自动获取:一次人为发言最多自动问一次',
|
|
176
176
|
'notice.autoCollectOff': '已关闭自动获取',
|
|
177
177
|
'notice.externalChange': '清单在别处被改过,已载入最新内容',
|
|
178
|
+
// Installed newer than running: the panel would otherwise call routes the old module
|
|
179
|
+
// graph does not have, and look broken until someone happens to restart the Host.
|
|
180
|
+
'notice.stale': '已安装 {installed},当前运行的是 {running};重启 dsh web 后生效',
|
|
178
181
|
'reminder.title': '到点提醒({n})',
|
|
179
182
|
'reminder.note': '这些是到了设定的提醒时间,和「逾期」不是同一批。',
|
|
180
183
|
'reminder.dismissAll': '全部知道了',
|
|
@@ -472,6 +475,7 @@ window.__ModuleLoader__.load({
|
|
|
472
475
|
'notice.autoCollectOn': 'Auto-collect on: at most one question per message from you',
|
|
473
476
|
'notice.autoCollectOff': 'Auto-collect off',
|
|
474
477
|
'notice.externalChange': 'The list changed somewhere else; the latest content is loaded',
|
|
478
|
+
'notice.stale': 'Installed {installed}, running {running}; restart dsh web to finish the upgrade',
|
|
475
479
|
'reminder.title': 'Due now ({n})',
|
|
476
480
|
'reminder.note': 'These reached their reminder time — a different set from Overdue.',
|
|
477
481
|
'reminder.dismissAll': 'Dismiss all',
|
|
@@ -2715,6 +2719,9 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2715
2719
|
// The store counts file changes made outside this process (another machine, a sync
|
|
2716
2720
|
// client). A new count means the panel's view was stale a moment ago; say so once.
|
|
2717
2721
|
const externalRef = useRef(null)
|
|
2722
|
+
// Warn once per mount: a version mismatch is a fact about this page load, not an
|
|
2723
|
+
// event worth repeating on every refresh.
|
|
2724
|
+
const staleRef = useRef(false)
|
|
2718
2725
|
// The wide-screen detail card floats over the panel, so its offsets are measured from
|
|
2719
2726
|
// the panel's own column rather than derived from the viewport width.
|
|
2720
2727
|
const innerRef = useRef(null)
|
|
@@ -2782,6 +2789,17 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2782
2789
|
setTimeout(() => setNotice(null), 8000)
|
|
2783
2790
|
}
|
|
2784
2791
|
externalRef.current = external
|
|
2792
|
+
if (data?.stale === true && !staleRef.current) {
|
|
2793
|
+
staleRef.current = true
|
|
2794
|
+
setNotice({
|
|
2795
|
+
text: translate(lang, 'notice.stale', {
|
|
2796
|
+
installed: String(data.packageVersion ?? '?'),
|
|
2797
|
+
running: String(data.version ?? '?'),
|
|
2798
|
+
}),
|
|
2799
|
+
sessionId: '',
|
|
2800
|
+
})
|
|
2801
|
+
setTimeout(() => setNotice(null), 12000)
|
|
2802
|
+
}
|
|
2785
2803
|
setState((current) => ({ ...current, health: data }))
|
|
2786
2804
|
} catch {
|
|
2787
2805
|
/* the health mirror is not worth an error banner */
|
package/lib/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import { createStore, resolveStorePath } from './store.js'
|
|
|
37
37
|
import { createTools } from './tools.js'
|
|
38
38
|
|
|
39
39
|
/** Must equal `package.json`'s version; `npm run check` enforces that. */
|
|
40
|
-
export const VERSION = '0.12.
|
|
40
|
+
export const VERSION = '0.12.11'
|
|
41
41
|
|
|
42
42
|
/** Plugin id. Also the Loader row id in `cordis.patch.yml`. */
|
|
43
43
|
export const name = 'dsh-global-todo'
|
package/lib/routes.js
CHANGED
|
@@ -426,6 +426,9 @@ export function createRoutes(deps) {
|
|
|
426
426
|
stale: deps.diskVersion !== undefined && deps.diskVersion !== version,
|
|
427
427
|
store: await service.describe(),
|
|
428
428
|
counts,
|
|
429
|
+
// The count says something waits for a person; the items say what, so a check does not
|
|
430
|
+
// have to drive the UI to find out.
|
|
431
|
+
review: { count: counts.review, items: await service.awaitingReview() },
|
|
429
432
|
// Purely additive, in-memory counters: hand-off success rate, agent tool
|
|
430
433
|
// calls and how the list grew. `null` when they were never mounted — an
|
|
431
434
|
// absent metric is not a zero.
|
|
@@ -739,17 +742,22 @@ export function createRoutes(deps) {
|
|
|
739
742
|
*/
|
|
740
743
|
function assertSettingsRange(body) {
|
|
741
744
|
const ranges = {
|
|
742
|
-
idleMinutes: [5, 24 * 60],
|
|
743
|
-
cooldownMinutes: [5, 24 * 60],
|
|
744
|
-
ingestConfirmBelow: [0, 1],
|
|
745
|
-
reminderLeadMinutes: [0, 24 * 60],
|
|
745
|
+
idleMinutes: [5, 24 * 60, true],
|
|
746
|
+
cooldownMinutes: [5, 24 * 60, true],
|
|
747
|
+
ingestConfirmBelow: [0, 1, false],
|
|
748
|
+
reminderLeadMinutes: [0, 24 * 60, true],
|
|
746
749
|
}
|
|
747
|
-
for (const [field, [low, high]] of Object.entries(ranges)) {
|
|
750
|
+
for (const [field, [low, high, whole]] of Object.entries(ranges)) {
|
|
748
751
|
const value = body[field]
|
|
749
752
|
if (value === undefined || value === null) continue
|
|
750
753
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
751
754
|
throw new TodoError('invalid-body', `${field} 必须是数字(${low}–${high})`)
|
|
752
755
|
}
|
|
756
|
+
// normalizeSettings only accepts integers for the minute fields and otherwise falls back
|
|
757
|
+
// to the default — a silently ignored 30.5 is the same trap as a silent clamp.
|
|
758
|
+
if (whole === true && !Number.isInteger(value)) {
|
|
759
|
+
throw new TodoError('invalid-body', `${field} 必须是整数分钟,收到 ${value}`)
|
|
760
|
+
}
|
|
753
761
|
if (value < low || value > high) {
|
|
754
762
|
throw new TodoError('invalid-body', `${field} 必须在 ${low}–${high} 之间,收到 ${value}`)
|
|
755
763
|
}
|
package/lib/service.js
CHANGED
|
@@ -821,10 +821,29 @@ export function createService(options) {
|
|
|
821
821
|
return counts(todos, now())
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
+
/**
|
|
825
|
+
* Which finished todos an agent marked done that a person has not confirmed.
|
|
826
|
+
*
|
|
827
|
+
* The count in `/health` says something is waiting; this says what, so a check does not
|
|
828
|
+
* have to open the panel and click through the list to find out.
|
|
829
|
+
*
|
|
830
|
+
* @param {number} [limit] newest first, capped
|
|
831
|
+
* @returns {Promise<{ id: string, seq: number, title: string, doneAt: number }[]>}
|
|
832
|
+
*/
|
|
833
|
+
async function awaitingReview(limit = 20) {
|
|
834
|
+
const todos = await store.all()
|
|
835
|
+
return todos
|
|
836
|
+
.filter((todo) => todo.status === 'done' && todo.review?.pending === true)
|
|
837
|
+
.sort((left, right) => (right.doneAt ?? right.updatedAt ?? 0) - (left.doneAt ?? left.updatedAt ?? 0))
|
|
838
|
+
.slice(0, limit)
|
|
839
|
+
.map((todo) => ({ id: todo.id, seq: todo.seq, title: todo.title, doneAt: todo.doneAt ?? todo.updatedAt ?? 0 }))
|
|
840
|
+
}
|
|
841
|
+
|
|
824
842
|
return {
|
|
825
843
|
list,
|
|
826
844
|
listAll,
|
|
827
845
|
get,
|
|
846
|
+
awaitingReview,
|
|
828
847
|
create,
|
|
829
848
|
update,
|
|
830
849
|
setStatus,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-global-todo",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.11",
|
|
4
4
|
"icon": "./docs/icon.svg",
|
|
5
5
|
"description": "Cross-session todo list for DeepSeek Harness: one JSON file a person and an agent both write to, with step plans that ship as a numbered hand-off, session read-back, and a human confirmation before an agent's \"done\" counts.",
|
|
6
6
|
"type": "module",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"check": "node scripts/check.mjs",
|
|
74
74
|
"doctor": "node tools/doctor.mjs",
|
|
75
75
|
"check:windows": "node tools/windows-check.mjs",
|
|
76
|
+
"notify": "node tools/notify.mjs",
|
|
76
77
|
"test": "node --test \"test/**/*.test.mjs\"",
|
|
77
78
|
"verify": "npm run check && npm run check:render && npm test",
|
|
78
79
|
"preview": "node tools/preview.mjs",
|