dsh-global-todo 0.6.8 → 0.7.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 +18 -0
- package/lib/client.js +43 -5
- package/lib/index.js +1 -1
- package/lib/model.js +80 -1
- package/lib/routes.js +6 -0
- package/lib/service.js +29 -0
- package/lib/tools.js +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [0.7.0] — 2026-10-20
|
|
2
|
+
|
|
3
|
+
An agent's "done" becomes a claim a person confirms, instead of a fact.
|
|
4
|
+
|
|
5
|
+
- **Completing as an agent now leaves a review mark.** `todo.review = { pending, at, by }` is set
|
|
6
|
+
by the model whenever the actor is `agent` (both `setStatus … done` and the completion path the
|
|
7
|
+
tools use); a person finishing something sets none, and anything that leaves `done` clears it.
|
|
8
|
+
- **`POST /api/global-todo/review`** answers it: `confirm` keeps the todo finished and drops the
|
|
9
|
+
mark, `reject` puts it back on the list with the completion record removed — "not actually done"
|
|
10
|
+
is the only honest reading of a rejected claim. Answering twice is refused, not silently
|
|
11
|
+
accepted.
|
|
12
|
+
- **The panel shows it**: a `待确认` chip on the row, a `待确认` filter that is the done list
|
|
13
|
+
narrowed to pending marks, and `确认完成` / `退回清单` in the row menu. `counts.review` drives the
|
|
14
|
+
chip and the filter.
|
|
15
|
+
- **Agents see it too**: `presentTodo` carries `review`, so an agent can tell that its completion
|
|
16
|
+
is still waiting rather than assuming it landed.
|
|
17
|
+
- Two new model tests cover the rule (agent vs person, confirm vs reject, no double answers).
|
|
18
|
+
|
|
1
19
|
## [0.6.8] — 2026-10-20
|
|
2
20
|
|
|
3
21
|
Every conditional state now has a baseline image, and the harness refuses to render a blank
|
package/lib/client.js
CHANGED
|
@@ -50,7 +50,7 @@ window.__ModuleLoader__.load({
|
|
|
50
50
|
* different mechanisms (restart vs. hot reload), so "which one am I looking
|
|
51
51
|
* at" needs an answer that the bundle itself carries.
|
|
52
52
|
*/
|
|
53
|
-
const UI_REV = '
|
|
53
|
+
const UI_REV = 'ui45'
|
|
54
54
|
/** Last language resolved for the shell; module-level so a fetch failure can be worded too. */
|
|
55
55
|
let activeLang = 'zh'
|
|
56
56
|
|
|
@@ -107,6 +107,12 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
'settings.saved': '设置已保存',
|
|
108
108
|
'settings.about': '关于',
|
|
109
109
|
'action.keys': '快捷键',
|
|
110
|
+
'review.confirm': '确认完成',
|
|
111
|
+
'review.reject': '退回清单',
|
|
112
|
+
'review.badge': '待确认',
|
|
113
|
+
'review.hint': 'Agent 标记完成,等你确认;确认后才算真的完成。',
|
|
114
|
+
'review.explain': '以下由 Agent 标记完成,确认后才算真的完成。',
|
|
115
|
+
'filter.review': '待确认',
|
|
110
116
|
'keys.search': '聚焦搜索',
|
|
111
117
|
'keys.new': '开始记一条',
|
|
112
118
|
'keys.save': '保存这条(在标题里)',
|
|
@@ -373,6 +379,12 @@ window.__ModuleLoader__.load({
|
|
|
373
379
|
'settings.saved': 'Settings saved',
|
|
374
380
|
'settings.about': 'About',
|
|
375
381
|
'action.keys': 'Keyboard shortcuts',
|
|
382
|
+
'review.confirm': 'Confirm done',
|
|
383
|
+
'review.reject': 'Send back',
|
|
384
|
+
'review.badge': 'Needs review',
|
|
385
|
+
'review.hint': 'The agent says this is done; confirm it or send it back.',
|
|
386
|
+
'review.explain': 'Finished by the agent. Confirm before it counts as done.',
|
|
387
|
+
'filter.review': 'Review',
|
|
376
388
|
'keys.search': 'Focus the search box',
|
|
377
389
|
'keys.new': 'Start a new todo',
|
|
378
390
|
'keys.save': 'Save it (from the title field)',
|
|
@@ -1210,6 +1222,8 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1210
1222
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1211
1223
|
.gt-truncated { color: var(--dsw-alias-state-warn-primary); font-size: 12px; }
|
|
1212
1224
|
.gt-bulk-tag { flex: 0 1 120px; min-width: 90px; }
|
|
1225
|
+
.gt-review { padding: 1px 6px; border-radius: 999px; font-size: 11px; font-weight: 600;
|
|
1226
|
+
color: var(--dsw-alias-state-warn-primary); border: 1px solid var(--dsw-alias-state-warn-primary); }
|
|
1213
1227
|
.gt-inbox { margin-left: 6px; padding: 1px 6px; border-radius: 999px; font-size: 11px; font-weight: 600;
|
|
1214
1228
|
color: var(--dsw-alias-state-warn-primary); border: 1px solid var(--dsw-alias-state-warn-primary);
|
|
1215
1229
|
vertical-align: 1px; }
|
|
@@ -1674,6 +1688,9 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1674
1688
|
if (todo.completion !== null && todo.completion !== undefined) {
|
|
1675
1689
|
meta.push(h('span', { key: 'completed', title: todo.completion.summary }, t('meta.completed', { when: absoluteTime(todo.completion.at) })))
|
|
1676
1690
|
}
|
|
1691
|
+
if (todo.review?.pending === true) {
|
|
1692
|
+
meta.push(h('span', { key: 'review', className: 'gt-review', title: t('review.hint') }, t('review.badge')))
|
|
1693
|
+
}
|
|
1677
1694
|
meta.push(h('span', { key: 'by' }, t(`meta.by.${todo.createdBy === 'agent' ? 'agent' : 'human'}`)))
|
|
1678
1695
|
if (todo.sessionId !== '') {
|
|
1679
1696
|
const stranded = todo.sessionAlive === true && (todo.sessionDir ?? '') === ''
|
|
@@ -1884,6 +1901,12 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1884
1901
|
h(Menu, {
|
|
1885
1902
|
label: t('action.menu'),
|
|
1886
1903
|
items: [
|
|
1904
|
+
...(todo.review?.pending === true
|
|
1905
|
+
? [
|
|
1906
|
+
{ key: 'confirmReview', label: t('review.confirm'), icon: 'check', onSelect: () => onAction('review-confirm', todo) },
|
|
1907
|
+
{ key: 'rejectReview', label: t('review.reject'), icon: 'refresh', danger: true, onSelect: () => onAction('review-reject', todo) },
|
|
1908
|
+
]
|
|
1909
|
+
: []),
|
|
1887
1910
|
...(todo.tags.some((tag) => tag.toLowerCase() === 'inbox')
|
|
1888
1911
|
? [
|
|
1889
1912
|
{ key: 'confirm', label: t('action.confirm'), icon: 'check', onSelect: () => onAction('confirm', todo) },
|
|
@@ -2488,8 +2511,9 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2488
2511
|
const reload = useCallback(
|
|
2489
2512
|
async (next = {}) => {
|
|
2490
2513
|
const selected = next.filter ?? filter
|
|
2491
|
-
//
|
|
2492
|
-
|
|
2514
|
+
// Two views sit on top of a status instead of being one: the inbox is a tag over
|
|
2515
|
+
// active records, and review is "done, but a person has not agreed yet".
|
|
2516
|
+
const status = selected === 'inbox' ? 'active' : selected === 'review' ? 'done' : selected
|
|
2493
2517
|
const search = next.query ?? query
|
|
2494
2518
|
try {
|
|
2495
2519
|
const data = await call('list', {
|
|
@@ -2502,7 +2526,12 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2502
2526
|
if (!mounted.current) return
|
|
2503
2527
|
setState((current) => ({
|
|
2504
2528
|
...current,
|
|
2505
|
-
|
|
2529
|
+
// The review view is the done list narrowed to records whose agent
|
|
2530
|
+
// completion nobody has answered yet.
|
|
2531
|
+
todos:
|
|
2532
|
+
selected === 'review'
|
|
2533
|
+
? data.todos.filter((todo) => todo.review?.pending === true)
|
|
2534
|
+
: data.todos,
|
|
2506
2535
|
counts: data.counts,
|
|
2507
2536
|
matched: Number.isInteger(data.matched) ? data.matched : data.todos.length,
|
|
2508
2537
|
truncated: data.truncated === true,
|
|
@@ -3144,7 +3173,15 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3144
3173
|
}
|
|
3145
3174
|
|
|
3146
3175
|
const onAction = (kind, todo, payload) => {
|
|
3147
|
-
if (kind === 'done') return run(todo.id, () => call('status', { method: 'POST', body: { id: todo.id, status: 'done' } }))
|
|
3176
|
+
if (kind === 'done') return run(todo.id, () => call('status', { method: 'POST', body: { id: todo.id, status: 'done', by: 'human' } }))
|
|
3177
|
+
// Answering an agent's completion. Confirming keeps it done; rejecting sends it back
|
|
3178
|
+
// to the list, because "not actually done" is the only honest reading.
|
|
3179
|
+
if (kind === 'review-confirm') {
|
|
3180
|
+
return run(todo.id, () => call('review', { method: 'POST', body: { id: todo.id, decision: 'confirm' } }))
|
|
3181
|
+
}
|
|
3182
|
+
if (kind === 'review-reject') {
|
|
3183
|
+
return run(todo.id, () => call('review', { method: 'POST', body: { id: todo.id, decision: 'reject' } }))
|
|
3184
|
+
}
|
|
3148
3185
|
if (kind === 'reopen') return run(todo.id, () => call('status', { method: 'POST', body: { id: todo.id, status: 'open' } }))
|
|
3149
3186
|
if (kind === 'doing') return run(todo.id, () => call('status', { method: 'POST', body: { id: todo.id, status: 'doing' } }))
|
|
3150
3187
|
// Confirming keeps the record on the list; discarding archives it. Both only remove
|
|
@@ -3766,6 +3803,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3766
3803
|
// day one is furniture, not a control.
|
|
3767
3804
|
...(counts.archived > 0 ? [['archived', counts.archived]] : []),
|
|
3768
3805
|
...(counts.inbox > 0 ? [['inbox', counts.inbox]] : []),
|
|
3806
|
+
...(counts.review > 0 ? [['review', counts.review]] : []),
|
|
3769
3807
|
].map(([key, count]) => filterChip(key, key === 'all' ? undefined : count)),
|
|
3770
3808
|
),
|
|
3771
3809
|
h('span', { className: 'gt-divider' }),
|
package/lib/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import { createStore, resolveStorePath } from './store.js'
|
|
|
36
36
|
import { createTools } from './tools.js'
|
|
37
37
|
|
|
38
38
|
/** Must equal `package.json`'s version; `npm run check` enforces that. */
|
|
39
|
-
export const VERSION = '0.
|
|
39
|
+
export const VERSION = '0.7.0'
|
|
40
40
|
|
|
41
41
|
/** Plugin id. Also the Loader row id in `cordis.patch.yml`. */
|
|
42
42
|
export const name = 'dsh-global-todo'
|
package/lib/model.js
CHANGED
|
@@ -217,6 +217,9 @@ export function createTodo(input, options = {}) {
|
|
|
217
217
|
// beside the status rather than inside it, because "done" alone says
|
|
218
218
|
// nothing about what was done.
|
|
219
219
|
completion: null,
|
|
220
|
+
// Set when an agent finishes something: the person who owns the list confirms it or
|
|
221
|
+
// sends it back. An agent's "done" is a claim, not a decision.
|
|
222
|
+
review: null,
|
|
220
223
|
// An append-only trail of what happened while the todo was open. Unlike the
|
|
221
224
|
// description (the user's own text) this is where an agent reports progress or
|
|
222
225
|
// says it is blocked, so reporting never has to rewrite someone's words.
|
|
@@ -312,6 +315,7 @@ export function normalizeStored(raw, options = {}) {
|
|
|
312
315
|
originSessionId: typeof raw.originSessionId === 'string' ? raw.originSessionId : '',
|
|
313
316
|
lastDispatch: raw.lastDispatch !== null && typeof raw.lastDispatch === 'object' ? raw.lastDispatch : null,
|
|
314
317
|
completion: normalizeCompletion(raw.completion),
|
|
318
|
+
review: normalizeReview(raw.review),
|
|
315
319
|
notes: normalizeNotes(raw.notes),
|
|
316
320
|
activity: Array.isArray(raw.activity)
|
|
317
321
|
? raw.activity
|
|
@@ -328,6 +332,64 @@ export function normalizeStored(raw, options = {}) {
|
|
|
328
332
|
return todo
|
|
329
333
|
}
|
|
330
334
|
|
|
335
|
+
/**
|
|
336
|
+
* A stored review mark, repaired rather than rejected.
|
|
337
|
+
*
|
|
338
|
+
* Only one shape exists today — an agent finished this and the owner has not answered —
|
|
339
|
+
* so anything else is dropped instead of guessed at.
|
|
340
|
+
*
|
|
341
|
+
* @param {unknown} raw
|
|
342
|
+
* @returns {{ pending: true, at: number, by: string } | null}
|
|
343
|
+
*/
|
|
344
|
+
function normalizeReview(raw) {
|
|
345
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return null
|
|
346
|
+
if (raw.pending !== true) return null
|
|
347
|
+
return {
|
|
348
|
+
pending: true,
|
|
349
|
+
at: Number.isFinite(raw.at) ? Number(raw.at) : Date.now(),
|
|
350
|
+
by: normalizeActor(raw.by),
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Answer the question an agent left behind.
|
|
356
|
+
*
|
|
357
|
+
* Confirm keeps the todo finished and drops the mark; reject puts it back on the list,
|
|
358
|
+
* because "not actually done" is the only honest reading of a rejected claim.
|
|
359
|
+
*
|
|
360
|
+
* @param {object} todo
|
|
361
|
+
* @param {'confirm' | 'reject'} decision
|
|
362
|
+
* @param {{ now?: number, by?: 'human' | 'agent', reason?: string }} [options]
|
|
363
|
+
* @returns {boolean} whether the record changed
|
|
364
|
+
*/
|
|
365
|
+
export function reviewTodo(todo, decision, options = {}) {
|
|
366
|
+
const now = options.now ?? Date.now()
|
|
367
|
+
const by = normalizeActor(options.by ?? 'human')
|
|
368
|
+
const next = normalizeEnum(decision, ['confirm', 'reject'], 'invalid-decision', '决定', 'confirm')
|
|
369
|
+
if (todo.review === null || todo.review === undefined) return false
|
|
370
|
+
todo.review = null
|
|
371
|
+
todo.updatedAt = now
|
|
372
|
+
todo.updatedBy = by
|
|
373
|
+
const reason = typeof options.reason === 'string' ? options.reason.trim().slice(0, 120) : ''
|
|
374
|
+
if (next === 'reject') {
|
|
375
|
+
todo.status = 'open'
|
|
376
|
+
todo.doneAt = null
|
|
377
|
+
todo.completion = null
|
|
378
|
+
pushActivity(
|
|
379
|
+
todo,
|
|
380
|
+
{ kind: 'reopened', by, at: now, summary: reason === '' ? '确认未完成,退回清单' : reason },
|
|
381
|
+
options.activityLimit ?? LIMITS.activity,
|
|
382
|
+
)
|
|
383
|
+
return true
|
|
384
|
+
}
|
|
385
|
+
pushActivity(
|
|
386
|
+
todo,
|
|
387
|
+
{ kind: 'reviewed', by, at: now, summary: reason === '' ? '确认完成' : reason },
|
|
388
|
+
options.activityLimit ?? LIMITS.activity,
|
|
389
|
+
)
|
|
390
|
+
return true
|
|
391
|
+
}
|
|
392
|
+
|
|
331
393
|
/**
|
|
332
394
|
* A stored completion record, repaired rather than rejected.
|
|
333
395
|
*
|
|
@@ -428,6 +490,7 @@ export function completeTodo(todo, evidence = {}, options = {}) {
|
|
|
428
490
|
todo.doneAt = now
|
|
429
491
|
todo.updatedAt = now
|
|
430
492
|
todo.updatedBy = by
|
|
493
|
+
todo.review = by === 'agent' ? { pending: true, at: now, by } : null
|
|
431
494
|
todo.completion = {
|
|
432
495
|
at: now,
|
|
433
496
|
by,
|
|
@@ -546,6 +609,9 @@ export function setStatus(todo, status, options = {}) {
|
|
|
546
609
|
// A completion record describes a finished job, so reopening clears it: a
|
|
547
610
|
// record that still claims the work is done is worse than no record.
|
|
548
611
|
if (next !== 'done') todo.completion = null
|
|
612
|
+
// Who finished it decides whether a person still has to agree: an agent's "done" is a
|
|
613
|
+
// claim, the owner's is a decision. Leaving done drops the mark with the record.
|
|
614
|
+
todo.review = next === 'done' && by === 'agent' ? { pending: true, at: now, by } : null
|
|
549
615
|
todo.updatedAt = now
|
|
550
616
|
todo.updatedBy = by
|
|
551
617
|
const kind = next === 'done' ? 'done' : next === 'open' ? 'reopened' : 'status'
|
|
@@ -666,7 +732,19 @@ export function isOverdue(todo, now = Date.now()) {
|
|
|
666
732
|
* @returns {{ total: number, open: number, doing: number, done: number, cancelled: number, archived: number, overdue: number }}
|
|
667
733
|
*/
|
|
668
734
|
export function counts(todos, now = Date.now()) {
|
|
669
|
-
const out = {
|
|
735
|
+
const out = {
|
|
736
|
+
total: todos.length,
|
|
737
|
+
open: 0,
|
|
738
|
+
doing: 0,
|
|
739
|
+
done: 0,
|
|
740
|
+
cancelled: 0,
|
|
741
|
+
archived: 0,
|
|
742
|
+
overdue: 0,
|
|
743
|
+
inbox: 0,
|
|
744
|
+
// Finished by an agent and not yet confirmed by a person. Counted separately from
|
|
745
|
+
// done because "the agent says so" is not the same fact as "a person agreed".
|
|
746
|
+
review: 0,
|
|
747
|
+
}
|
|
670
748
|
for (const todo of todos) {
|
|
671
749
|
if (todo.status in out) out[todo.status] += 1
|
|
672
750
|
if (isOverdue(todo, now)) out.overdue += 1
|
|
@@ -675,6 +753,7 @@ export function counts(todos, now = Date.now()) {
|
|
|
675
753
|
if ((todo.status === 'open' || todo.status === 'doing') && (todo.tags ?? []).some((tag) => tag.toLowerCase() === 'inbox')) {
|
|
676
754
|
out.inbox += 1
|
|
677
755
|
}
|
|
756
|
+
if (todo.status === 'done' && todo.review?.pending === true) out.review += 1
|
|
678
757
|
}
|
|
679
758
|
return out
|
|
680
759
|
}
|
package/lib/routes.js
CHANGED
|
@@ -578,6 +578,12 @@ export function createRoutes(deps) {
|
|
|
578
578
|
return { todo: await service.setStatus(body.id, body.status ?? 'done', { by }) }
|
|
579
579
|
}),
|
|
580
580
|
|
|
581
|
+
// Answering the mark an agent's completion leaves behind: confirm, or send it back.
|
|
582
|
+
exact('POST', `${ROUTE_BASE}/review`, async (request) => {
|
|
583
|
+
const body = await readJsonBody(request)
|
|
584
|
+
return { todo: await service.review(body.id, body.decision ?? 'confirm', { by: 'human', reason: body.reason }) }
|
|
585
|
+
}),
|
|
586
|
+
|
|
581
587
|
exact('POST', `${ROUTE_BASE}/remove`, async (request) => {
|
|
582
588
|
const body = await readJsonBody(request)
|
|
583
589
|
return { todo: await service.remove(body.id, { by: 'human' }) }
|
package/lib/service.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
pushActivity,
|
|
32
32
|
recordDispatch as recordDispatchOn,
|
|
33
33
|
removeLink as removeLinkFrom,
|
|
34
|
+
reviewTodo,
|
|
34
35
|
setStatus as setStatusOn,
|
|
35
36
|
} from './model.js'
|
|
36
37
|
|
|
@@ -318,6 +319,33 @@ export function createService(options) {
|
|
|
318
319
|
})
|
|
319
320
|
}
|
|
320
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Answer the review mark an agent's completion left behind.
|
|
324
|
+
*
|
|
325
|
+
* Kept separate from {@link setStatus} because it is a different decision: the record is
|
|
326
|
+
* already done, and the owner is either agreeing or sending it back — not changing a
|
|
327
|
+
* status that nobody asked about. A record without a pending mark is refused, so the
|
|
328
|
+
* panel cannot confirm something twice.
|
|
329
|
+
*
|
|
330
|
+
* @param {string} id
|
|
331
|
+
* @param {'confirm' | 'reject'} decision
|
|
332
|
+
* @param {{ by?: 'human' | 'agent', reason?: string }} [context]
|
|
333
|
+
* @returns {Promise<object>} the todo after the decision
|
|
334
|
+
*/
|
|
335
|
+
async function review(id, decision, context = {}) {
|
|
336
|
+
return store.update((doc) => {
|
|
337
|
+
const todo = requireTodo(doc, id)
|
|
338
|
+
if (todo.review?.pending !== true) {
|
|
339
|
+
throw new TodoError('invalid-status', '这条待办没有等你确认的完成标记')
|
|
340
|
+
}
|
|
341
|
+
const changed = reviewTodo(todo, decision, {
|
|
342
|
+
...modelOptions(context.by ?? 'human'),
|
|
343
|
+
reason: context.reason,
|
|
344
|
+
})
|
|
345
|
+
return changed ? { next: doc.todos, result: todo } : { result: todo }
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
321
349
|
/**
|
|
322
350
|
* Put a wrongly-finished todo back on the list.
|
|
323
351
|
*
|
|
@@ -763,6 +791,7 @@ export function createService(options) {
|
|
|
763
791
|
create,
|
|
764
792
|
update,
|
|
765
793
|
setStatus,
|
|
794
|
+
review,
|
|
766
795
|
reopen,
|
|
767
796
|
complete,
|
|
768
797
|
note,
|
package/lib/tools.js
CHANGED
|
@@ -162,6 +162,9 @@ export function presentTodo(todo, now = Date.now()) {
|
|
|
162
162
|
originDir: todo.originDir ?? '',
|
|
163
163
|
originSessionId: todo.originSessionId ?? '',
|
|
164
164
|
notes: (todo.notes ?? []).map((entry) => ({ at: entry.at, by: entry.by, text: entry.text })),
|
|
165
|
+
// Present so an agent can see it left a claim waiting: a record an agent finished is
|
|
166
|
+
// not settled until a person confirms it, and saying so beats assuming it landed.
|
|
167
|
+
review: todo.review?.pending === true ? { pending: true, at: todo.review.at, by: todo.review.by } : null,
|
|
165
168
|
completion:
|
|
166
169
|
todo.completion === null || todo.completion === undefined
|
|
167
170
|
? null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-global-todo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"icon": "./docs/icon.svg",
|
|
5
5
|
"description": "Global, cross-session todos for DeepSeek Harness: one list owned by the human and the agent together, with creation time, attached documents and links, and one-click hand-off to an agent session. 全局待办:跨会话一份清单,记录加入时间,可挂文档与地址,可一键交给 Agent 继续干。",
|
|
6
6
|
"type": "module",
|