@fishwithcat/dsh-tool-todo 0.0.0-stage → 0.2.2-alpha.3
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/README.i18n.yaml +88 -0
- package/README.md +170 -2
- package/README.zh.md +171 -0
- package/lib/index.js +219 -0
- package/lib/types/client.d.ts +10 -0
- package/lib/types/client.js +10 -0
- package/lib/types/index.d.ts +33 -0
- package/lib/types/index.js +208 -0
- package/lib/types/types.d.ts +46 -0
- package/lib/types/types.js +11 -0
- package/locale/de.json +6 -0
- package/locale/en.json +5 -0
- package/locale/es.json +6 -0
- package/locale/fr.json +6 -0
- package/locale/ja.json +6 -0
- package/locale/pt.json +6 -0
- package/locale/ru.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +61 -4
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/todo/tool-todo/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: 94e53ef3423c197f
|
|
7
|
+
zh: ad579992506261fa
|
|
8
|
+
/deepseek-ai-dsh-tool-todo:
|
|
9
|
+
en: 06114b7b88112609
|
|
10
|
+
zh: a53c75adeeecf917
|
|
11
|
+
/deepseek-ai-dsh-tool-todo/summary:
|
|
12
|
+
en: b6b3f4e412f33da2
|
|
13
|
+
zh: b4dbbe1a1e093179
|
|
14
|
+
/deepseek-ai-dsh-tool-todo/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/deepseek-ai-dsh-tool-todo/use-this-package:
|
|
18
|
+
en: d10c650faab8695d
|
|
19
|
+
zh: 6018b5a53501d88b
|
|
20
|
+
/deepseek-ai-dsh-tool-todo/use-this-package/when-to-choose-it:
|
|
21
|
+
en: c964ee10289fcd21
|
|
22
|
+
zh: 4c1e68220906eb06
|
|
23
|
+
/deepseek-ai-dsh-tool-todo/use-this-package/minimal-configuration:
|
|
24
|
+
en: 02ee43b537f8ab85
|
|
25
|
+
zh: a20116a4fcc36a33
|
|
26
|
+
/deepseek-ai-dsh-tool-todo/use-this-package/what-each-call-does:
|
|
27
|
+
en: 96e2488d0a5b8f96
|
|
28
|
+
zh: abc33a2f87f539dc
|
|
29
|
+
/deepseek-ai-dsh-tool-todo/use-this-package/single-owner:
|
|
30
|
+
en: f051cbd97dab2436
|
|
31
|
+
zh: 18976a8affed8cab
|
|
32
|
+
/deepseek-ai-dsh-tool-todo/understand-the-implementation:
|
|
33
|
+
en: 8d94a7381bca3fa9
|
|
34
|
+
zh: f4bd2866fff6fa8d
|
|
35
|
+
/deepseek-ai-dsh-tool-todo/understand-the-implementation/design-philosophy:
|
|
36
|
+
en: e9cf462797eaf9f2
|
|
37
|
+
zh: 02d9a7fce90936c3
|
|
38
|
+
/deepseek-ai-dsh-tool-todo/understand-the-implementation/source-map:
|
|
39
|
+
en: 51328ad8c7fcbfd0
|
|
40
|
+
zh: 2614ec42b61ff3ef
|
|
41
|
+
/deepseek-ai-dsh-tool-todo/understand-the-implementation/export-shape:
|
|
42
|
+
en: 35b4f6c718e60dab
|
|
43
|
+
zh: 9bffe86c88a7ad6d
|
|
44
|
+
/deepseek-ai-dsh-tool-todo/understand-the-implementation/session-projection:
|
|
45
|
+
en: 2d49c8af46b16dd2
|
|
46
|
+
zh: 71a3073433b579d4
|
|
47
|
+
/deepseek-ai-dsh-tool-todo/understand-the-implementation/call-mechanics:
|
|
48
|
+
en: d55f6a32b05ed686
|
|
49
|
+
zh: c815f930a2acebda
|
|
50
|
+
/deepseek-ai-dsh-tool-todo/further-exploration:
|
|
51
|
+
en: 38488722b03c25dd
|
|
52
|
+
zh: 23efe2cce1e29917
|
|
53
|
+
/deepseek-ai-dsh-tool-todo/model-experience:
|
|
54
|
+
en: 215e7ba838619b7b
|
|
55
|
+
zh: a311da3843709f90
|
|
56
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-schema:
|
|
57
|
+
en: e4c7376d4f29dbd5
|
|
58
|
+
zh: 6b7a9eaf69c9ea53
|
|
59
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-schema/what-the-model-sees:
|
|
60
|
+
en: 1d761e1652e9fc94
|
|
61
|
+
zh: f847760856f74c8b
|
|
62
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-schema/token-effect:
|
|
63
|
+
en: a0a8f5ec3db0f500
|
|
64
|
+
zh: eac37a524c6e777e
|
|
65
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-schema/kv-cache-effect:
|
|
66
|
+
en: b385b04c94496e5b
|
|
67
|
+
zh: 3a361ac90ef5f037
|
|
68
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-call-history-and-result:
|
|
69
|
+
en: 9717b983b6a857e3
|
|
70
|
+
zh: 611a2dadf7c0c706
|
|
71
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-call-history-and-result/what-the-model-sees:
|
|
72
|
+
en: 05644654d1bce8c6
|
|
73
|
+
zh: ea2a42470fbb2ee1
|
|
74
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-call-history-and-result/token-effect:
|
|
75
|
+
en: 7763a0e875faf53d
|
|
76
|
+
zh: fe33c3973215dc53
|
|
77
|
+
/deepseek-ai-dsh-tool-todo/model-experience/tool-call-history-and-result/kv-cache-effect:
|
|
78
|
+
en: f5d5f97a153f7c70
|
|
79
|
+
zh: a5411047e008a0c1
|
|
80
|
+
/deepseek-ai-dsh-tool-todo/known-limitations-and-deferred-work:
|
|
81
|
+
en: 71e2aed21b2545c3
|
|
82
|
+
zh: 775300a3618a861a
|
|
83
|
+
/deepseek-ai-dsh-tool-todo/known-limitations-and-deferred-work/dev-note:
|
|
84
|
+
en: ac1d08360a93382b
|
|
85
|
+
zh: 703a65fbcc38edc7
|
|
86
|
+
/deepseek-ai-dsh-tool-todo/known-limitations-and-deferred-work/dev-note/future-cross-agent-and-shared-lists:
|
|
87
|
+
en: 071b9c5be71f799f
|
|
88
|
+
zh: dee15fe922a3ab95
|
package/README.md
CHANGED
|
@@ -1,3 +1,171 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "The model-facing todo_write tool over the DeepSeek Harness session log: whole-list replacement, per-session ownership, and the todos projection, for users and maintainers choosing, configuring, or debugging the tool."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @deepseek-ai/dsh-tool-todo
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`dsh-tool-todo` gives the agent a structured task list to plan with: break multi-step work into concrete tasks, mark the task you are working on, and check tasks off as they finish. The list survives across turns and reopened sessions, so the agent and the UI always see the latest plan. One configuration flag decides whether several tasks may be in progress at once, for agents that run work in parallel. Use it wherever an agent should keep a visible task list; each update replaces the whole list, and only the owning agent session can change it.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Use this package when you want the agent to maintain a visible task list while it works: plan multi-step work, show what is active, and record completion. Mounting it with the parallelism flag is the only setup; the agent then updates the list through its own planning tool whenever the plan changes.
|
|
29
|
+
|
|
30
|
+
### When to choose it
|
|
31
|
+
|
|
32
|
+
Choose it when one agent session should own the task list and whole-list updates are fine — the common shape for planning tools. Avoid it when several agents must share one list or when you need per-item edits: the list belongs to one agent and every update replaces the whole list. It needs an agent session to exist at all; automation-only surfaces that never run an agent cannot use it.
|
|
33
|
+
|
|
34
|
+
### Minimal configuration
|
|
35
|
+
|
|
36
|
+
`allowParallelInProgress` is required with no default: a composition that omits it fails at load, and a non-boolean value is rejected. Set `true` for agents that may run work concurrently (subagents, background commands, workflow fan-out) and `false` for the single-active discipline.
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@deepseek-ai/dsh-tool-todo'
|
|
40
|
+
config:
|
|
41
|
+
allowParallelInProgress: true
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Field | Default | Meaning |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `allowParallelInProgress` | required | Whether several todos may be `in_progress` at once; also selects the active-status clause of the model description |
|
|
47
|
+
|
|
48
|
+
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-todo) is the exhaustive source for the accepted field.
|
|
49
|
+
|
|
50
|
+
### What each call does
|
|
51
|
+
|
|
52
|
+
The agent sends the ENTIRE list on every update; the new list replaces the previous one, so there are no partial updates or per-item edits. Each item is a short task description plus a status of `pending`, `in_progress`, or `completed`. A successful update returns the new counts — `Updated todo list: <pending> pending, <inProgress> in progress, <completed> completed.` — and the UI shows the new plan. The terminal surface draws that plan as a checklist — the completed/total count in the card title and one `☐`/`◐`/`☑`-marked line per task, instead of the raw argument JSON. Updates fail visibly when a task description is empty or duplicated, when an item carries fields beyond the description and status, or — when parallel work is disabled — when more than one task is marked in progress.
|
|
53
|
+
|
|
54
|
+
### Single owner
|
|
55
|
+
|
|
56
|
+
The task list belongs to the one agent session that created it — subagents and other agents each keep their own list, and there is no way to share a list between agents. A call from outside an agent session is rejected, so the agent learns the update failed instead of silently writing nowhere. If you need a list shared across agents, this package does not provide it.
|
|
57
|
+
|
|
58
|
+
-----
|
|
59
|
+
|
|
60
|
+
<a id="understand-the-implementation"></a>
|
|
61
|
+
## Understand the implementation
|
|
62
|
+
|
|
63
|
+
<details>
|
|
64
|
+
<summary>Implementation internals — click to expand</summary>
|
|
65
|
+
|
|
66
|
+
This section explains the design decisions behind the tool and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package).
|
|
67
|
+
|
|
68
|
+
### Design philosophy
|
|
69
|
+
|
|
70
|
+
The tool is built on four commitments:
|
|
71
|
+
|
|
72
|
+
- **Whole-list replace, log-backed state.** The model resends the entire list; the `todo/write` snapshot lives on the event-sourced session log, so durability, replay, and resume reconstruction come from the log rather than a service.
|
|
73
|
+
- **Single owner.** The list belongs to the calling agent session; there is no shared or swarm scope, and non-agent callers are rejected.
|
|
74
|
+
- **Deployment policy, not a coded rule.** `allowParallelInProgress` is a required composition choice because the tool cannot observe runtime concurrency.
|
|
75
|
+
- **Validation keeps the logged snapshot honest.** Schema-level rejection of unknown keys and `execute`-level rejection of empty or duplicate content keep the durable snapshot equal to what the model believes it wrote.
|
|
76
|
+
|
|
77
|
+
The [todo_write tool Agent Note](../../../.agents/notes/archived/feature/2026-06-29-todo-write-tool.md) records the original design and alternatives; the [parallel in-progress Agent Note](../../../.agents/notes/archived/feature/2026-07-26-todo-parallel-in-progress.md) records the policy decision.
|
|
78
|
+
|
|
79
|
+
### Source map
|
|
80
|
+
|
|
81
|
+
| File | Role |
|
|
82
|
+
|---|---|
|
|
83
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, tool registration, `todos` projection unit |
|
|
84
|
+
| [`src/types.ts`](src/types.ts) | The one home of the `todos` projection-key declaration and its payload types |
|
|
85
|
+
| [`src/client.ts`](src/client.ts) | Client-namespace re-export of the types outlet |
|
|
86
|
+
|
|
87
|
+
### Export shape
|
|
88
|
+
|
|
89
|
+
The plugin is a function/namespace plugin: it exports `name` / `inject` / `apply` and no default export. A stray `export default` would make the Loader's `unwrapExports` collapse the module and drop `inject` (see [postmortem 0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)).
|
|
90
|
+
|
|
91
|
+
### Session projection
|
|
92
|
+
|
|
93
|
+
When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md)), this package registers the `todos` unit on an injected child: the projection is the standing plan — the latest whole `todo/write` list, `null` before the first write, cleared when the next turn starts while `turn/end` keeps the finished checklist visible. The key merges into `SessionProjectionMap` here; carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected; see [src/index.ts](src/index.ts) for the unit registration.
|
|
94
|
+
|
|
95
|
+
### Call mechanics
|
|
96
|
+
|
|
97
|
+
Each call validates the submitted list against the schema, rejects incoherent input, and on success appends the full snapshot as a `todo/write` session event and returns the new counts; the current list is always the most recent `todo/write` in the log (last-write-wins on replay). See [src/index.ts](src/index.ts) for the exact validation and append steps.
|
|
98
|
+
|
|
99
|
+
</details>
|
|
100
|
+
|
|
101
|
+
-----
|
|
102
|
+
|
|
103
|
+
<a id="further-exploration"></a>
|
|
104
|
+
## Further Exploration
|
|
105
|
+
|
|
106
|
+
Read these pages when the package-level contract is not enough. They move from the session subsystem to the generated catalogs and the decision records behind the tool.
|
|
107
|
+
|
|
108
|
+
- [Todo subsystem](../../../docs/subsystems/todo.md) — the `todo/write` event payload, ownership rules, and `TodoItem`.
|
|
109
|
+
- [todo group map](../README.md) — the sibling group page and its package table.
|
|
110
|
+
- [Generated tool catalog](../../../docs/tool-catalog.md#deepseek-aidsh-tool-todo) — the `todo_write` schema the model receives.
|
|
111
|
+
- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-todo) — every accepted config field and its source declaration.
|
|
112
|
+
- [todo_write tool Agent Note](../../../.agents/notes/archived/feature/2026-06-29-todo-write-tool.md) — the original design, alternatives, and dropped fields.
|
|
113
|
+
- [parallel in-progress Agent Note](../../../.agents/notes/archived/feature/2026-07-26-todo-parallel-in-progress.md) — why the active-count cap is a deployment policy.
|
|
114
|
+
|
|
115
|
+
-----
|
|
116
|
+
|
|
117
|
+
<a id="model-experience"></a>
|
|
118
|
+
## Model Experience
|
|
119
|
+
|
|
120
|
+
### Tool schema
|
|
121
|
+
|
|
122
|
+
#### What the model sees
|
|
123
|
+
|
|
124
|
+
The model sees the generated [`todo_write` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-todo): an object with one required `todos` array of `{ content, status }` items, where `status` is `pending`, `in_progress`, or `completed`. The `todos` description states that each call replaces the whole list; the tool description's active-status clause follows `allowParallelInProgress`.
|
|
125
|
+
|
|
126
|
+
#### Token effect
|
|
127
|
+
|
|
128
|
+
Fixed schema cost on every request where the tool is visible; the description and schema are stable for a given configuration.
|
|
129
|
+
|
|
130
|
+
#### KV Cache effect
|
|
131
|
+
|
|
132
|
+
Prefix-stable while the definition and visibility are unchanged. Plugin lifecycle or scoped restrictions may invalidate reuse from this schema.
|
|
133
|
+
|
|
134
|
+
### Tool-call history and result
|
|
135
|
+
|
|
136
|
+
#### What the model sees
|
|
137
|
+
|
|
138
|
+
Each assistant tool call retains the entire replacement list in its arguments. Success returns exactly `Updated todo list: <pending> pending, <inProgress> in progress, <completed> completed.` Stable failures are ``Error: invalid todo: `content` must be a non-empty string``, `Error: invalid todos: duplicate content "<content>"`, `Error: todo_write requires an owning agent session`, and — only where the deployment set `allowParallelInProgress: false` — `Error: invalid todos: at most one task may be in_progress (got <n>)`. The full `todo/write` session event is UI and replay state, not a second model message.
|
|
139
|
+
|
|
140
|
+
#### Token effect
|
|
141
|
+
|
|
142
|
+
Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape.
|
|
143
|
+
|
|
144
|
+
#### KV Cache effect
|
|
145
|
+
|
|
146
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
147
|
+
|
|
148
|
+
## Known Limitations and Deferred Work
|
|
149
|
+
|
|
150
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
These limits define when the tool is a poor fit. They are current package constraints, not a task backlog.
|
|
154
|
+
|
|
155
|
+
- **Single-owner scope only** — the list belongs to the one calling agent session; subagent, shared, and swarm scopes are a deliberate cut, and a non-agent caller is rejected.
|
|
156
|
+
- **The item shape is deliberately minimal** — `content` plus three-state `status`; whole-list replacement needs no stable id, priority, or active-form fields.
|
|
157
|
+
- **Whole-list replacement is the only operation** — no partial updates, no read-back tool, and no per-item edits; the model must resend the entire list each call.
|
|
158
|
+
|
|
159
|
+
<a id="dev-note"></a>
|
|
160
|
+
### Dev Note
|
|
161
|
+
|
|
162
|
+
<details>
|
|
163
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
164
|
+
|
|
165
|
+
This Dev Note is working context for maintainers: open questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
|
|
166
|
+
|
|
167
|
+
#### Future: cross-agent and shared lists
|
|
168
|
+
|
|
169
|
+
The single-owner scope is a deliberate cut, and cross-agent or shared lists remain a separate future design: they would need per-item log deltas and explicit scope selection, and would change the model-visible contract. No design exists yet.
|
|
170
|
+
|
|
171
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "面向 DeepSeek Harness 会话日志的模型侧 todo_write 工具:整表替换、单一会话归属与 todos 投影,供选择、配置或排查该工具的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-tool-todo
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`dsh-tool-todo` 为 agent(智能体)提供一份可用于规划的结构化任务列表:把多步工作拆成具体任务、标记正在进行的任务、完成后逐项勾掉。列表跨轮次、跨重新打开的会话持续存在,agent 与 UI 始终看到最新计划。一个配置开关决定是否允许多个任务同时处于进行中,适用于并行开展工作的 agent。凡是希望 agent 维护可见任务列表的场景都可以使用它;每次更新整体替换列表,只有拥有该列表的 agent 会话才能修改。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
当你希望 agent 在工作时维护一份可见的任务列表时使用本包:规划多步工作、展示当前进行中的任务、记录完成情况。挂载它并设置并行开关是唯一的配置步骤;此后每次计划变化,agent 都会通过它自己的规划工具更新列表。
|
|
29
|
+
|
|
30
|
+
### 何时选择
|
|
31
|
+
|
|
32
|
+
当某个 agent 会话应当拥有任务列表、且整表更新即可满足需求时选择它——这是规划工具的常见形态。当多个 agent 必须共享同一份列表、或需要逐项编辑时,请避开:列表只属于一个 agent,每次更新都会替换整个列表。它要求环境中确实存在 agent 会话;从不运行 agent 的纯自动化入口无法使用它。
|
|
33
|
+
|
|
34
|
+
### 最小配置
|
|
35
|
+
|
|
36
|
+
`allowParallelInProgress` 是必填项、没有默认值:省略它的组合会在加载时失败,非布尔值也会被拒绝。可能并发运行工作的 agent(subagent、后台命令、工作流扇出)设为 `true`,需要单活跃项纪律的设为 `false`。
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@deepseek-ai/dsh-tool-todo'
|
|
40
|
+
config:
|
|
41
|
+
allowParallelInProgress: true
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| 字段 | 默认值 | 含义 |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `allowParallelInProgress` | 必填 | 是否允许多个 todo 同时处于 `in_progress`;同时选择模型描述中的活跃状态条款 |
|
|
47
|
+
|
|
48
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-todo)是每个受支持字段的穷尽式真源。
|
|
49
|
+
|
|
50
|
+
### 每次调用做什么
|
|
51
|
+
|
|
52
|
+
agent 每次更新都发送完整列表;新列表替换旧列表,因此没有部分更新或逐项编辑。每个条目是一句简短的任务描述,外加 `pending`、`in_progress` 或 `completed` 状态。成功的更新会返回新的计数——`Updated todo list: <pending> pending, <inProgress> in progress, <completed> completed.`——UI 随即展示新计划。终端界面会把该计划画成清单——卡片标题给出已完成/总数,每个任务一行并以 `☐`/`◐`/`☑` 标记状态,而不是展示原始参数 JSON。任务描述为空或重复、条目带有描述与状态之外的字段、或(禁用并行时)多个任务被标记为进行中,这些情况下更新都会明确失败。
|
|
53
|
+
|
|
54
|
+
### 单一所有者
|
|
55
|
+
|
|
56
|
+
任务列表属于创建它的那一个 agent 会话——subagent 与其他 agent 各自维护自己的列表,不存在跨 agent 共享列表的方式。来自 agent 会话之外的调用会被拒绝,因此 agent 会得知更新失败,而不是被静默丢弃。如果你需要多个 agent 共享同一份列表,本包不提供该能力。
|
|
57
|
+
|
|
58
|
+
-----
|
|
59
|
+
|
|
60
|
+
<a id="understand-the-implementation"></a>
|
|
61
|
+
## 理解实现
|
|
62
|
+
|
|
63
|
+
<details>
|
|
64
|
+
<summary>实现细节——点击展开</summary>
|
|
65
|
+
|
|
66
|
+
本节解释工具背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
67
|
+
|
|
68
|
+
### 设计理念
|
|
69
|
+
|
|
70
|
+
本工具建立在四项承诺之上:
|
|
71
|
+
|
|
72
|
+
- **整表替换、日志承载状态。** 模型重新发送整个列表;`todo/write` 快照存放在事件溯源的会话日志上,持久性、回放与恢复重建都来自日志而非服务。
|
|
73
|
+
- **单一所有者。** 列表属于调用 agent 会话;不存在共享或 swarm 作用域,非 agent 调用方会被拒绝。
|
|
74
|
+
- **部署策略,而非编码规则。** `allowParallelInProgress` 是必填组合选择,因为工具无法观测运行时并发。
|
|
75
|
+
- **校验确保日志快照如实反映输入。** schema 层拒绝未知键、`execute` 层拒绝空或重复 content,使持久快照与模型自认为写入的内容一致。
|
|
76
|
+
|
|
77
|
+
[todo_write 工具 Agent Note](../../../.agents/notes/archived/feature/2026-06-29-todo-write-tool.md) 记录原始设计与备选方案;[并行 in-progress Agent Note](../../../.agents/notes/archived/feature/2026-07-26-todo-parallel-in-progress.md) 记录该策略决策。
|
|
78
|
+
|
|
79
|
+
### 源码地图
|
|
80
|
+
|
|
81
|
+
| 文件 | 职责 |
|
|
82
|
+
|---|---|
|
|
83
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、工具注册、`todos` 投影单元 |
|
|
84
|
+
| [`src/types.ts`](src/types.ts) | `todos` 投影键声明及其载荷类型的唯一归属地 |
|
|
85
|
+
| [`src/client.ts`](src/client.ts) | 客户端命名空间对类型出口的再导出 |
|
|
86
|
+
|
|
87
|
+
### 导出形状
|
|
88
|
+
|
|
89
|
+
本插件是函数/命名空间插件:导出 `name`、`inject`、`apply`,没有默认导出。多余的 `export default` 会让 Loader 的 `unwrapExports` 折叠模块并丢弃 `inject`(参见 [postmortem 0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.zh.md))。
|
|
90
|
+
|
|
91
|
+
### 会话投影
|
|
92
|
+
|
|
93
|
+
当组合挂载 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.zh.md))时,本包在注入的子插件中注册 `todos` 单元:投影即有效计划——最新的整份 `todo/write` 列表,首次写入前为 `null`,下一轮次开始时清空,而 `turn/end` 保留刚完成的清单。该键在此处合并进 `SessionProjectionMap`;载体通过历史尾页与 `session/projection` 推送帧提供该值。未挂载注册表的组合不受影响;单元注册见 [src/index.ts](src/index.ts)。
|
|
94
|
+
|
|
95
|
+
### 调用机制
|
|
96
|
+
|
|
97
|
+
每次调用都会先校验提交的列表是否符合 schema,拒绝不一致的输入,成功后把完整快照作为 `todo/write` 会话事件追加并返回新的计数;当前列表始终是日志中最近一次 `todo/write`(回放时后写覆盖先写)。确切的校验与追加步骤见 [src/index.ts](src/index.ts)。
|
|
98
|
+
|
|
99
|
+
</details>
|
|
100
|
+
|
|
101
|
+
-----
|
|
102
|
+
|
|
103
|
+
<a id="further-exploration"></a>
|
|
104
|
+
## 进一步探索
|
|
105
|
+
|
|
106
|
+
当包级约定不够用时阅读以下页面。它们从会话子系统逐步进入生成的目录,以及工具背后的决策记录。
|
|
107
|
+
|
|
108
|
+
- [Todo 子系统](../../../docs/subsystems/todo.zh.md)——`todo/write` 事件载荷、归属规则与 `TodoItem`。
|
|
109
|
+
- [todo 组映射](../README.zh.md)——同级组页面及其包表格。
|
|
110
|
+
- [生成的工具目录](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-todo)——模型接收的 `todo_write` schema。
|
|
111
|
+
- [生成的配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-todo)——每个受支持配置字段及其源声明。
|
|
112
|
+
- [todo_write 工具 Agent Note](../../../.agents/notes/archived/feature/2026-06-29-todo-write-tool.md)——原始设计、备选方案与未采纳的字段。
|
|
113
|
+
- [并行 in-progress Agent Note](../../../.agents/notes/archived/feature/2026-07-26-todo-parallel-in-progress.md)——为何活跃计数上限成为部署策略。
|
|
114
|
+
|
|
115
|
+
-----
|
|
116
|
+
|
|
117
|
+
<a id="model-experience"></a>
|
|
118
|
+
## 模型体验
|
|
119
|
+
|
|
120
|
+
### 工具 schema
|
|
121
|
+
|
|
122
|
+
#### 模型看到什么
|
|
123
|
+
|
|
124
|
+
模型会看到生成的 [`todo_write` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-todo):一个对象,含一个必填的 `todos` 数组,元素为 `{ content, status }`,其中 `status` 为 `pending`、`in_progress` 或 `completed`。`todos` 描述说明每次调用都会替换整个列表;工具描述中的活跃状态条款跟随 `allowParallelInProgress`。
|
|
125
|
+
|
|
126
|
+
#### Token 影响
|
|
127
|
+
|
|
128
|
+
工具可见的每个请求都有固定 schema 开销;在给定配置下描述与 schema 保持稳定。
|
|
129
|
+
|
|
130
|
+
#### KV Cache 影响
|
|
131
|
+
|
|
132
|
+
定义与可见性不变时前缀保持稳定。插件生命周期或作用域限制可能使从此 schema 起的复用失效。
|
|
133
|
+
|
|
134
|
+
### 工具调用历史与结果
|
|
135
|
+
|
|
136
|
+
#### 模型看到什么
|
|
137
|
+
|
|
138
|
+
每次 assistant 工具调用都会在参数中保留整个替换列表。成功时原样返回 `Updated todo list: <pending> pending, <inProgress> in progress, <completed> completed.`。稳定失败文本为 ``Error: invalid todo: `content` must be a non-empty string``、`Error: invalid todos: duplicate content "<content>"`、`Error: todo_write requires an owning agent session`,以及——仅在部署设置 `allowParallelInProgress: false` 时——`Error: invalid todos: at most one task may be in_progress (got <n>)`。完整的 `todo/write` 会话事件是 UI 与回放状态,而非第二条模型消息。
|
|
139
|
+
|
|
140
|
+
#### Token 影响
|
|
141
|
+
|
|
142
|
+
token 用量随模型每次提交的完整列表增长,这些调用参数会保留到压缩(compaction)。结果本身很小且形状固定。
|
|
143
|
+
|
|
144
|
+
#### KV Cache 影响
|
|
145
|
+
|
|
146
|
+
仅追加;新可见内容位于可复用请求前缀之后,不会使既有 KV-cache 条目失效。
|
|
147
|
+
|
|
148
|
+
## 已知限制与延期工作
|
|
149
|
+
|
|
150
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
这些限制说明工具何时不合适。它们是当前包约束,不是任务积压。
|
|
154
|
+
|
|
155
|
+
- **仅单一所有者作用域**——列表属于唯一调用 agent 会话;subagent、共享与 swarm 作用域是有意设置的限制,非 agent 调用方会被拒绝。
|
|
156
|
+
- **条目形状刻意保持最小**——`content` 加三态 `status`;整表替换不需要稳定 id、优先级或 active-form 字段。
|
|
157
|
+
- **整表替换是唯一操作**——没有部分更新、没有回读工具、没有逐项编辑;模型每次调用都必须重新发送完整列表。
|
|
158
|
+
|
|
159
|
+
<a id="dev-note"></a>
|
|
160
|
+
### 开发备注
|
|
161
|
+
|
|
162
|
+
<details>
|
|
163
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
164
|
+
|
|
165
|
+
本开发备注是维护者的工作上下文:未决问题与尚未决定的方向。它明确不具权威性——已交付行为、限制与既定理由以上文、包代码和相关 Agent Note 为准。
|
|
166
|
+
|
|
167
|
+
#### 未来:跨 agent 与共享列表
|
|
168
|
+
|
|
169
|
+
单一所有者作用域是有意设置的限制,跨 agent 或共享列表仍是独立的未来设计:它们需要逐项日志增量与显式作用域选择,并会改变模型可见约定。目前尚不存在设计。
|
|
170
|
+
|
|
171
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { z as z$1 } from "zod";
|
|
3
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
//#region lib/types/index.js
|
|
5
|
+
/**
|
|
6
|
+
* Model-facing whole-list replacement. Each call appends a `todo/write` snapshot to the calling
|
|
7
|
+
* agent's session; replay is last-write-wins, and UIs render from session events. A non-agent
|
|
8
|
+
* caller has no owning list and is rejected. Named exports preserve loader injection metadata.
|
|
9
|
+
* @module @deepseek-ai/dsh-tool-todo
|
|
10
|
+
*/
|
|
11
|
+
const name = "tool-todo";
|
|
12
|
+
const inject = ["tools", "sessionProjections"];
|
|
13
|
+
/** The valid {@link TodoItem} statuses, as a runtime set for input narrowing. */
|
|
14
|
+
const STATUSES = [
|
|
15
|
+
"pending",
|
|
16
|
+
"in_progress",
|
|
17
|
+
"completed"
|
|
18
|
+
];
|
|
19
|
+
/** Checklist glyph for one {@link TodoItem} status; the terminal card draws one per task. */
|
|
20
|
+
const STATUS_MARK = {
|
|
21
|
+
pending: "☐",
|
|
22
|
+
in_progress: "◐",
|
|
23
|
+
completed: "☑"
|
|
24
|
+
};
|
|
25
|
+
/** Schemastery configuration for the todo tool consumer. */
|
|
26
|
+
const Config = z.object({ allowParallelInProgress: z.boolean().required() });
|
|
27
|
+
const DESCRIPTION_HEAD = "Record and update a task list to plan multi-step work and show progress; skip it for trivial single-step tasks. Add one todo per concrete step before you start. ";
|
|
28
|
+
const DESCRIPTION_PARALLEL = "While work remains, keep the todos being worked on `in_progress`, several only when work runs in parallel. ";
|
|
29
|
+
const DESCRIPTION_SINGLE = "While work remains, keep exactly one todo `in_progress`. ";
|
|
30
|
+
const DESCRIPTION_TAIL = "Mark each todo `completed` as soon as it is done.";
|
|
31
|
+
/**
|
|
32
|
+
* The model-facing description for one activation. The active-status clause is the only part that
|
|
33
|
+
* varies, because it is the only instruction the parallel policy changes.
|
|
34
|
+
* @param allowParallel - whether several todos may be `in_progress` at once.
|
|
35
|
+
* @returns the composed tool description.
|
|
36
|
+
*/
|
|
37
|
+
function describe(allowParallel) {
|
|
38
|
+
return DESCRIPTION_HEAD + (allowParallel ? DESCRIPTION_PARALLEL : DESCRIPTION_SINGLE) + DESCRIPTION_TAIL;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validate the value constraints the ParameterSchemaSpec can't express and build the canonical {@link
|
|
42
|
+
* TodoItem}[]: trimmed non-empty unique content, and at most one `in_progress` item unless the
|
|
43
|
+
* deployment allows parallel work. The registry has already enforced the status enum and rejected
|
|
44
|
+
* unknown item keys (`additionalProperties: false` — the logged snapshot must equal what the model
|
|
45
|
+
* believes it wrote, so a nested/extended item shape fails loud at the schema boundary instead of
|
|
46
|
+
* silently flattening); the cast below records that guarantee.
|
|
47
|
+
* @param raw - the model-supplied list, already schema-checked.
|
|
48
|
+
* @param allowParallel - whether several items may be `in_progress` at once.
|
|
49
|
+
* @returns the canonical list.
|
|
50
|
+
*/
|
|
51
|
+
function toTodoList(raw, allowParallel) {
|
|
52
|
+
const todos = [];
|
|
53
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54
|
+
let active = 0;
|
|
55
|
+
for (const item of raw) {
|
|
56
|
+
const content = item.content.trim();
|
|
57
|
+
if (content.length === 0) throw new Error("invalid todo: `content` must be a non-empty string");
|
|
58
|
+
if (seen.has(content)) throw new Error(`invalid todos: duplicate content ${JSON.stringify(content)}`);
|
|
59
|
+
seen.add(content);
|
|
60
|
+
if (item.status === "in_progress") active++;
|
|
61
|
+
todos.push({
|
|
62
|
+
content,
|
|
63
|
+
status: item.status
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!allowParallel && active > 1) throw new Error(`invalid todos: at most one task may be in_progress (got ${active})`);
|
|
67
|
+
return todos;
|
|
68
|
+
}
|
|
69
|
+
/** Wire payload schema of the `todos` projection (whole list or pre-first-write null). */
|
|
70
|
+
const todosProjectionSchema = z$1.union([z$1.array(z$1.object({
|
|
71
|
+
content: z$1.string(),
|
|
72
|
+
status: z$1.union([
|
|
73
|
+
z$1.literal("pending"),
|
|
74
|
+
z$1.literal("in_progress"),
|
|
75
|
+
z$1.literal("completed")
|
|
76
|
+
])
|
|
77
|
+
})), z$1.null()]);
|
|
78
|
+
/**
|
|
79
|
+
* The terminal card for one whole-list update: the completed/total count in the
|
|
80
|
+
* title and one status-marked line per task in model order. The title keeps the
|
|
81
|
+
* count visible when a long list folds, and an empty list keeps the bare title so
|
|
82
|
+
* the row falls back to the model-facing counts sentence.
|
|
83
|
+
* @param todos - the schema-checked task list from the call arguments.
|
|
84
|
+
* @returns the generic card the terminal surface draws.
|
|
85
|
+
*/
|
|
86
|
+
function presentTodoList(todos) {
|
|
87
|
+
const done = todos.filter((todo) => todo.status === "completed").length;
|
|
88
|
+
return {
|
|
89
|
+
card: "generic",
|
|
90
|
+
title: todos.length === 0 ? "Update todo list" : `Update todo list (${String(done)}/${String(todos.length)} done)`,
|
|
91
|
+
kind: "other",
|
|
92
|
+
...todos.length === 0 ? {} : { content: [{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: todos.map((todo) => `${STATUS_MARK[todo.status]} ${todo.content}`).join("\n")
|
|
95
|
+
}] }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Register the `todo_write` tool on `ctx.tools` and the `todos` unit on
|
|
100
|
+
* `ctx.sessionProjections`.
|
|
101
|
+
* @param ctx - registrant context carrying the tool and session-projection registries.
|
|
102
|
+
* @param config - deployment's explicit todo policy.
|
|
103
|
+
* @returns when the plugin is ready.
|
|
104
|
+
*/
|
|
105
|
+
async function apply(ctx, config) {
|
|
106
|
+
const allowParallel = config.allowParallelInProgress;
|
|
107
|
+
await ctx.sessionProjections.register({
|
|
108
|
+
key: "todos",
|
|
109
|
+
stateSchema: todosProjectionSchema,
|
|
110
|
+
init: () => null,
|
|
111
|
+
apply: (state, event) => {
|
|
112
|
+
if (event.type === "todo/write") return event.data.todos;
|
|
113
|
+
if (event.type === "turn/start") return null;
|
|
114
|
+
return state;
|
|
115
|
+
},
|
|
116
|
+
wire: {
|
|
117
|
+
viewSchema: todosProjectionSchema,
|
|
118
|
+
view: (state) => state,
|
|
119
|
+
summary: false
|
|
120
|
+
},
|
|
121
|
+
stateVersion: 2
|
|
122
|
+
});
|
|
123
|
+
ctx.tools.register(defineTool({
|
|
124
|
+
name: "todo_write",
|
|
125
|
+
description: describe(allowParallel),
|
|
126
|
+
parameters: { todos: {
|
|
127
|
+
type: "array",
|
|
128
|
+
required: true,
|
|
129
|
+
description: "The COMPLETE task list, replacing any previous list.",
|
|
130
|
+
items: {
|
|
131
|
+
type: "object",
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
properties: {
|
|
134
|
+
content: {
|
|
135
|
+
type: "string",
|
|
136
|
+
required: true,
|
|
137
|
+
description: "What the task is — a short imperative line."
|
|
138
|
+
},
|
|
139
|
+
status: {
|
|
140
|
+
type: "string",
|
|
141
|
+
required: true,
|
|
142
|
+
enum: [...STATUSES],
|
|
143
|
+
description: "pending (not started) | in_progress (now) | completed (done)."
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} },
|
|
148
|
+
output: {
|
|
149
|
+
schema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
additionalProperties: false,
|
|
152
|
+
properties: {
|
|
153
|
+
todos: {
|
|
154
|
+
type: "array",
|
|
155
|
+
required: true,
|
|
156
|
+
items: {
|
|
157
|
+
type: "object",
|
|
158
|
+
additionalProperties: false,
|
|
159
|
+
properties: {
|
|
160
|
+
content: {
|
|
161
|
+
type: "string",
|
|
162
|
+
required: true
|
|
163
|
+
},
|
|
164
|
+
status: {
|
|
165
|
+
type: "string",
|
|
166
|
+
required: true,
|
|
167
|
+
enum: [...STATUSES]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
counts: {
|
|
173
|
+
type: "object",
|
|
174
|
+
additionalProperties: false,
|
|
175
|
+
required: true,
|
|
176
|
+
properties: {
|
|
177
|
+
pending: {
|
|
178
|
+
type: "integer",
|
|
179
|
+
required: true
|
|
180
|
+
},
|
|
181
|
+
inProgress: {
|
|
182
|
+
type: "integer",
|
|
183
|
+
required: true
|
|
184
|
+
},
|
|
185
|
+
completed: {
|
|
186
|
+
type: "integer",
|
|
187
|
+
required: true
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
render: (_args, value) => [{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: `Updated todo list: ${value.counts.pending} pending, ${value.counts.inProgress} in progress, ${value.counts.completed} completed.`
|
|
196
|
+
}]
|
|
197
|
+
},
|
|
198
|
+
execute(args, exec) {
|
|
199
|
+
const todos = toTodoList(args.todos, allowParallel);
|
|
200
|
+
if (!exec.agent) throw new Error("todo_write requires an owning agent session");
|
|
201
|
+
exec.agent.session.append("todo/write", { todos });
|
|
202
|
+
const count = (status) => todos.filter((t) => t.status === status).length;
|
|
203
|
+
return Promise.resolve({
|
|
204
|
+
todos: todos.map((todo) => ({
|
|
205
|
+
content: todo.content,
|
|
206
|
+
status: todo.status
|
|
207
|
+
})),
|
|
208
|
+
counts: {
|
|
209
|
+
pending: count("pending"),
|
|
210
|
+
inProgress: count("in_progress"),
|
|
211
|
+
completed: count("completed")
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
presentCall: (args) => presentTodoList(args.todos)
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
export { Config, apply, inject, name };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-namespace projection of the todo domain: a pure re-export of the package's
|
|
3
|
+
* types outlet. Client code imports ONLY the client namespace (repo
|
|
4
|
+
* discipline), so `./client` projects the same single-source content
|
|
5
|
+
* `./types` serves to host consumers — zero duplication.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-tool-todo/client
|
|
8
|
+
*/
|
|
9
|
+
export type * from './types.ts';
|
|
10
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-namespace projection of the todo domain: a pure re-export of the package's
|
|
3
|
+
* types outlet. Client code imports ONLY the client namespace (repo
|
|
4
|
+
* discipline), so `./client` projects the same single-source content
|
|
5
|
+
* `./types` serves to host consumers — zero duplication.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-tool-todo/client
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing whole-list replacement. Each call appends a `todo/write` snapshot to the calling
|
|
3
|
+
* agent's session; replay is last-write-wins, and UIs render from session events. A non-agent
|
|
4
|
+
* caller has no owning list and is rejected. Named exports preserve loader injection metadata.
|
|
5
|
+
* @module @deepseek-ai/dsh-tool-todo
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
import z from '@deepseek-ai/schemastery';
|
|
9
|
+
export type * from './types.ts';
|
|
10
|
+
export declare const name = "tool-todo";
|
|
11
|
+
export declare const inject: string[];
|
|
12
|
+
/** Model-facing todo tool configuration. */
|
|
13
|
+
export interface Config {
|
|
14
|
+
/**
|
|
15
|
+
* Required deployment choice for whether several todos may be `in_progress` at once. True suits
|
|
16
|
+
* agents that run work concurrently — subagents, background commands, workflow fan-out — and the
|
|
17
|
+
* description then instructs the model to mark every actively worked task. False restores the
|
|
18
|
+
* single-active discipline: the description asks for exactly one, and a call marking more is
|
|
19
|
+
* rejected.
|
|
20
|
+
*/
|
|
21
|
+
allowParallelInProgress: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Schemastery configuration for the todo tool consumer. */
|
|
24
|
+
export declare const Config: z<Config>;
|
|
25
|
+
/**
|
|
26
|
+
* Register the `todo_write` tool on `ctx.tools` and the `todos` unit on
|
|
27
|
+
* `ctx.sessionProjections`.
|
|
28
|
+
* @param ctx - registrant context carrying the tool and session-projection registries.
|
|
29
|
+
* @param config - deployment's explicit todo policy.
|
|
30
|
+
* @returns when the plugin is ready.
|
|
31
|
+
*/
|
|
32
|
+
export declare function apply(ctx: Context, config: Config): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing whole-list replacement. Each call appends a `todo/write` snapshot to the calling
|
|
3
|
+
* agent's session; replay is last-write-wins, and UIs render from session events. A non-agent
|
|
4
|
+
* caller has no owning list and is rejected. Named exports preserve loader injection metadata.
|
|
5
|
+
* @module @deepseek-ai/dsh-tool-todo
|
|
6
|
+
*/
|
|
7
|
+
import z from '@deepseek-ai/schemastery';
|
|
8
|
+
import { z as zod } from 'zod';
|
|
9
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
10
|
+
export const name = 'tool-todo';
|
|
11
|
+
export const inject = ['tools', 'sessionProjections'];
|
|
12
|
+
/** The valid {@link TodoItem} statuses, as a runtime set for input narrowing. */
|
|
13
|
+
const STATUSES = ['pending', 'in_progress', 'completed'];
|
|
14
|
+
/** Checklist glyph for one {@link TodoItem} status; the terminal card draws one per task. */
|
|
15
|
+
const STATUS_MARK = {
|
|
16
|
+
pending: '☐',
|
|
17
|
+
in_progress: '◐',
|
|
18
|
+
completed: '☑',
|
|
19
|
+
};
|
|
20
|
+
/** Schemastery configuration for the todo tool consumer. */
|
|
21
|
+
export const Config = z.object({
|
|
22
|
+
allowParallelInProgress: z.boolean().required(),
|
|
23
|
+
});
|
|
24
|
+
const DESCRIPTION_HEAD = 'Record and update a task list to plan multi-step work and show progress; skip it for trivial '
|
|
25
|
+
+ 'single-step tasks. Add one todo per concrete step before you start. ';
|
|
26
|
+
const DESCRIPTION_PARALLEL = 'While work remains, keep the todos being worked on `in_progress`, several only when work runs in parallel. ';
|
|
27
|
+
const DESCRIPTION_SINGLE = 'While work remains, keep exactly one todo `in_progress`. ';
|
|
28
|
+
const DESCRIPTION_TAIL = 'Mark each todo `completed` as soon as it is done.';
|
|
29
|
+
/**
|
|
30
|
+
* The model-facing description for one activation. The active-status clause is the only part that
|
|
31
|
+
* varies, because it is the only instruction the parallel policy changes.
|
|
32
|
+
* @param allowParallel - whether several todos may be `in_progress` at once.
|
|
33
|
+
* @returns the composed tool description.
|
|
34
|
+
*/
|
|
35
|
+
function describe(allowParallel) {
|
|
36
|
+
return DESCRIPTION_HEAD
|
|
37
|
+
+ (allowParallel ? DESCRIPTION_PARALLEL : DESCRIPTION_SINGLE)
|
|
38
|
+
+ DESCRIPTION_TAIL;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validate the value constraints the ParameterSchemaSpec can't express and build the canonical {@link
|
|
42
|
+
* TodoItem}[]: trimmed non-empty unique content, and at most one `in_progress` item unless the
|
|
43
|
+
* deployment allows parallel work. The registry has already enforced the status enum and rejected
|
|
44
|
+
* unknown item keys (`additionalProperties: false` — the logged snapshot must equal what the model
|
|
45
|
+
* believes it wrote, so a nested/extended item shape fails loud at the schema boundary instead of
|
|
46
|
+
* silently flattening); the cast below records that guarantee.
|
|
47
|
+
* @param raw - the model-supplied list, already schema-checked.
|
|
48
|
+
* @param allowParallel - whether several items may be `in_progress` at once.
|
|
49
|
+
* @returns the canonical list.
|
|
50
|
+
*/
|
|
51
|
+
function toTodoList(raw, allowParallel) {
|
|
52
|
+
const todos = [];
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
let active = 0;
|
|
55
|
+
for (const item of raw) {
|
|
56
|
+
const content = item.content.trim();
|
|
57
|
+
if (content.length === 0) {
|
|
58
|
+
throw new Error('invalid todo: `content` must be a non-empty string');
|
|
59
|
+
}
|
|
60
|
+
if (seen.has(content)) {
|
|
61
|
+
throw new Error(`invalid todos: duplicate content ${JSON.stringify(content)}`);
|
|
62
|
+
}
|
|
63
|
+
seen.add(content);
|
|
64
|
+
if (item.status === 'in_progress')
|
|
65
|
+
active++;
|
|
66
|
+
todos.push({ content, status: item.status });
|
|
67
|
+
}
|
|
68
|
+
if (!allowParallel && active > 1) {
|
|
69
|
+
throw new Error(`invalid todos: at most one task may be in_progress (got ${active})`);
|
|
70
|
+
}
|
|
71
|
+
return todos;
|
|
72
|
+
}
|
|
73
|
+
/** Wire payload schema of the `todos` projection (whole list or pre-first-write null). */
|
|
74
|
+
const todosProjectionSchema = zod.union([
|
|
75
|
+
zod.array(zod.object({
|
|
76
|
+
content: zod.string(),
|
|
77
|
+
status: zod.union([zod.literal('pending'), zod.literal('in_progress'), zod.literal('completed')]),
|
|
78
|
+
})),
|
|
79
|
+
zod.null(),
|
|
80
|
+
]);
|
|
81
|
+
/**
|
|
82
|
+
* The terminal card for one whole-list update: the completed/total count in the
|
|
83
|
+
* title and one status-marked line per task in model order. The title keeps the
|
|
84
|
+
* count visible when a long list folds, and an empty list keeps the bare title so
|
|
85
|
+
* the row falls back to the model-facing counts sentence.
|
|
86
|
+
* @param todos - the schema-checked task list from the call arguments.
|
|
87
|
+
* @returns the generic card the terminal surface draws.
|
|
88
|
+
*/
|
|
89
|
+
function presentTodoList(todos) {
|
|
90
|
+
const done = todos.filter(todo => todo.status === 'completed').length;
|
|
91
|
+
return {
|
|
92
|
+
card: 'generic',
|
|
93
|
+
title: todos.length === 0 ? 'Update todo list' : `Update todo list (${String(done)}/${String(todos.length)} done)`,
|
|
94
|
+
kind: 'other',
|
|
95
|
+
...todos.length === 0 ? {} : {
|
|
96
|
+
content: [{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: todos.map(todo => `${STATUS_MARK[todo.status]} ${todo.content}`).join('\n'),
|
|
99
|
+
}],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Register the `todo_write` tool on `ctx.tools` and the `todos` unit on
|
|
105
|
+
* `ctx.sessionProjections`.
|
|
106
|
+
* @param ctx - registrant context carrying the tool and session-projection registries.
|
|
107
|
+
* @param config - deployment's explicit todo policy.
|
|
108
|
+
* @returns when the plugin is ready.
|
|
109
|
+
*/
|
|
110
|
+
export async function apply(ctx, config) {
|
|
111
|
+
const allowParallel = config.allowParallelInProgress;
|
|
112
|
+
// Standing-plan fold: latest whole todo/write list, cleared by the next
|
|
113
|
+
// turn/start (turn/end keeps the finished checklist visible); null before the
|
|
114
|
+
// first write or after a later turn begins; every other event returns the
|
|
115
|
+
// same state reference.
|
|
116
|
+
await ctx.sessionProjections.register({
|
|
117
|
+
key: 'todos',
|
|
118
|
+
stateSchema: todosProjectionSchema,
|
|
119
|
+
init: () => null,
|
|
120
|
+
apply: (state, event) => {
|
|
121
|
+
if (event.type === 'todo/write')
|
|
122
|
+
return event.data.todos;
|
|
123
|
+
if (event.type === 'turn/start')
|
|
124
|
+
return null;
|
|
125
|
+
return state;
|
|
126
|
+
},
|
|
127
|
+
wire: { viewSchema: todosProjectionSchema, view: state => state, summary: false },
|
|
128
|
+
stateVersion: 2,
|
|
129
|
+
});
|
|
130
|
+
ctx.tools.register(defineTool({
|
|
131
|
+
name: 'todo_write',
|
|
132
|
+
description: describe(allowParallel),
|
|
133
|
+
parameters: {
|
|
134
|
+
todos: {
|
|
135
|
+
type: 'array',
|
|
136
|
+
required: true,
|
|
137
|
+
description: 'The COMPLETE task list, replacing any previous list.',
|
|
138
|
+
items: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
properties: {
|
|
142
|
+
content: { type: 'string', required: true, description: 'What the task is — a short imperative line.' },
|
|
143
|
+
status: {
|
|
144
|
+
type: 'string',
|
|
145
|
+
required: true,
|
|
146
|
+
enum: [...STATUSES],
|
|
147
|
+
description: 'pending (not started) | in_progress (now) | completed (done).',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
output: {
|
|
154
|
+
schema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
properties: {
|
|
158
|
+
todos: {
|
|
159
|
+
type: 'array',
|
|
160
|
+
required: true,
|
|
161
|
+
items: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
properties: {
|
|
165
|
+
content: { type: 'string', required: true },
|
|
166
|
+
status: { type: 'string', required: true, enum: [...STATUSES] },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
counts: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
additionalProperties: false,
|
|
173
|
+
required: true,
|
|
174
|
+
properties: {
|
|
175
|
+
pending: { type: 'integer', required: true },
|
|
176
|
+
inProgress: { type: 'integer', required: true },
|
|
177
|
+
completed: { type: 'integer', required: true },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
render: (_args, value) => [{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: `Updated todo list: ${value.counts.pending} pending, ${value.counts.inProgress} in progress, ${value.counts.completed} completed.`,
|
|
185
|
+
}],
|
|
186
|
+
},
|
|
187
|
+
execute(args, exec) {
|
|
188
|
+
const todos = toTodoList(args.todos, allowParallel);
|
|
189
|
+
if (!exec.agent) {
|
|
190
|
+
// The list is per-agent-session state; a non-agent caller (no owning
|
|
191
|
+
// session) has nowhere to write it. Reject rather than silently no-op.
|
|
192
|
+
throw new Error('todo_write requires an owning agent session');
|
|
193
|
+
}
|
|
194
|
+
exec.agent.session.append('todo/write', { todos });
|
|
195
|
+
const count = (status) => todos.filter(t => t.status === status).length;
|
|
196
|
+
return Promise.resolve({
|
|
197
|
+
todos: todos.map(todo => ({ content: todo.content, status: todo.status })),
|
|
198
|
+
counts: {
|
|
199
|
+
pending: count('pending'),
|
|
200
|
+
inProgress: count('in_progress'),
|
|
201
|
+
completed: count('completed'),
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
presentCall: args => presentTodoList(args.todos),
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure types of the todo domain: the ONE home of the `todos` projection-key
|
|
3
|
+
* declaration plus its payload types, free of this package's host-side value
|
|
4
|
+
* imports (dsh-tools, zod). Two namespace projections serve it — `./types`
|
|
5
|
+
* for host consumers, `./client/types` (the browser half-entry's re-export)
|
|
6
|
+
* for client aggregates — with zero content duplication.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-tool-todo/types
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* One entry in an agent's todo list — the unit of the `todo/write`
|
|
12
|
+
* whole-list snapshot declared by this package.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately minimal: a human-readable `content` line and a three-state
|
|
15
|
+
* `status`. No id, priority, or `activeForm` — the list is replaced wholesale
|
|
16
|
+
* on every write (last-write-wins), so entries need no stable identity. The
|
|
17
|
+
* three statuses describe the complete portable lifecycle needed by model and
|
|
18
|
+
* UI consumers.
|
|
19
|
+
*/
|
|
20
|
+
export interface TodoItem {
|
|
21
|
+
/** What this task is — a short imperative line shown in the UI. */
|
|
22
|
+
content: string;
|
|
23
|
+
/** Lifecycle state. `in_progress` marks a task being worked now; parallel work may mark several. */
|
|
24
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
25
|
+
}
|
|
26
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
27
|
+
interface SessionEventMap {
|
|
28
|
+
/** Whole-list snapshot; latest write wins on replay. Log-only UI state; never derived history. */
|
|
29
|
+
'todo/write': {
|
|
30
|
+
todos: TodoItem[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
35
|
+
interface SessionProjectionMap {
|
|
36
|
+
/**
|
|
37
|
+
* The agent's current whole todo list (the latest `todo/write` snapshot),
|
|
38
|
+
* or `null` before the first write. Whole-value rule: every `todo/write`
|
|
39
|
+
* carries the complete replacement list, so the fold is last-wins.
|
|
40
|
+
* Excluded from Session summary payloads, with `null` kept outside the
|
|
41
|
+
* exclusion mark so the pre-first-write value stays expressible.
|
|
42
|
+
*/
|
|
43
|
+
todos: SessionProjectionSummaryExcluded<TodoItem[]> | null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure types of the todo domain: the ONE home of the `todos` projection-key
|
|
3
|
+
* declaration plus its payload types, free of this package's host-side value
|
|
4
|
+
* imports (dsh-tools, zod). Two namespace projections serve it — `./types`
|
|
5
|
+
* for host consumers, `./client/types` (the browser half-entry's re-export)
|
|
6
|
+
* for client aggregates — with zero content duplication.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-tool-todo/types
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
package/locale/de.json
ADDED
package/locale/en.json
ADDED
package/locale/es.json
ADDED
package/locale/fr.json
ADDED
package/locale/ja.json
ADDED
package/locale/pt.json
ADDED
package/locale/ru.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishwithcat/dsh-tool-todo",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "Model-facing todo_write tool over the DeepSeek Harness event-sourced session log",
|
|
4
|
+
"version": "0.2.2-alpha.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/FishwithCat/deepseek-harness-tui",
|
|
11
|
+
"directory": "packages/todo/tool-todo"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./lib/types/client.d.ts",
|
|
23
|
+
"default": "./lib/types/client.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./locale/*.json": "./locale/*.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"locale/*.json",
|
|
31
|
+
"lib/index.js",
|
|
32
|
+
"lib/types/**/*.js",
|
|
33
|
+
"lib/types/**/*.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@deepseek-ai/schemastery": "~3.18.6-alpha.2",
|
|
38
|
+
"zod": "4.4.3"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@deepseek-ai/dsh-agent": "0.2.2-alpha.3",
|
|
42
|
+
"@deepseek-ai/dsh-session": "0.2.2-alpha.3",
|
|
43
|
+
"@deepseek-ai/dsh-session-projection": "0.2.2-alpha.3",
|
|
44
|
+
"@deepseek-ai/dsh-tools": "0.2.2-alpha.3",
|
|
45
|
+
"@deepseek-ai/cordis": "~4.0.6-alpha.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@deepseek-ai/cordis-plugin-include": "~1.0.11-alpha.2",
|
|
49
|
+
"@deepseek-ai/cordis-plugin-loader": "~1.0.7-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-agent": "0.2.2-alpha.3",
|
|
51
|
+
"@deepseek-ai/dsh-agent-loop": "0.2.2-alpha.3",
|
|
52
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "0.2.2-alpha.3",
|
|
53
|
+
"@deepseek-ai/dsh-llm": "0.2.2-alpha.3",
|
|
54
|
+
"@deepseek-ai/dsh-session": "0.2.2-alpha.3",
|
|
55
|
+
"@deepseek-ai/dsh-session-projection": "0.2.2-alpha.3",
|
|
56
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.3",
|
|
57
|
+
"@deepseek-ai/dsh-tool-executor-native": "0.2.2-alpha.3",
|
|
58
|
+
"@deepseek-ai/dsh-tool-testkit": "0.2.2-alpha.3",
|
|
59
|
+
"@deepseek-ai/dsh-tools": "0.2.2-alpha.3",
|
|
60
|
+
"@deepseek-ai/dsh-user-questions": "0.2.2-alpha.3",
|
|
61
|
+
"@deepseek-ai/cordis": "~4.0.6-alpha.2"
|
|
62
|
+
}
|
|
63
|
+
}
|