dsh-global-todo 0.12.9 → 0.12.10

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 CHANGED
@@ -1,3 +1,18 @@
1
+ ## [0.12.10] — 2026-10-21
2
+
3
+ Two follow-ups from #3, plus the notification channel this repository actually has.
4
+
5
+ - **`/health` names what waits for review.** `review.count` said something was waiting;
6
+ `review.items` says which (id, seq, title, doneAt, newest first) — checking the state no longer
7
+ means opening the panel and clicking through the list.
8
+ - **The panel says when the running version is older than the installed one.** `stale` was added to
9
+ `/health` in 0.12.9; the panel now shows a one-time notice naming both versions and saying to
10
+ restart `dsh web`. This is the trap where the client is new, the module graph is old, and the
11
+ panel calls routes that 404.
12
+ - **`tools/notify.mjs`** — pushes release / CI-failed / new-issue messages to WeChat through the
13
+ host's delivery library, which owns the freshness window and the outbox. `--dry-run` composes and
14
+ reports the window without sending; a closed window queues instead of failing.
15
+
1
16
  ## [0.12.9] — 2026-10-21
2
17
 
3
18
  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/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.9'
40
+ export const VERSION = '0.12.10'
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.
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.9",
3
+ "version": "0.12.10",
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",