@bumpyclock/pi-tasque 0.1.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/LICENSE +21 -0
- package/NOTICE.md +7 -0
- package/README.md +315 -0
- package/package.json +39 -0
- package/src/bridge/bridge-tool.ts +185 -0
- package/src/bridge/import-tsq.ts +502 -0
- package/src/bridge/link-store.ts +97 -0
- package/src/bridge/promote-todo.ts +331 -0
- package/src/bridge/types.ts +156 -0
- package/src/durable-tasks/cache.ts +167 -0
- package/src/durable-tasks/mutation-queue.ts +30 -0
- package/src/durable-tasks/runner.ts +234 -0
- package/src/durable-tasks/status.ts +184 -0
- package/src/durable-tasks/tools-change.ts +600 -0
- package/src/durable-tasks/tools-claim.ts +426 -0
- package/src/durable-tasks/tools-query.ts +496 -0
- package/src/durable-tasks/types.ts +193 -0
- package/src/index.ts +21 -0
- package/src/session-todos/state/invariants.ts +17 -0
- package/src/session-todos/state/replay.ts +272 -0
- package/src/session-todos/state/selectors.ts +140 -0
- package/src/session-todos/state/state-reducer.ts +292 -0
- package/src/session-todos/state/state.ts +69 -0
- package/src/session-todos/state/store.ts +37 -0
- package/src/session-todos/state/task-graph.ts +58 -0
- package/src/session-todos/todo-overlay.ts +223 -0
- package/src/session-todos/todo.ts +239 -0
- package/src/session-todos/tool/response-envelope.ts +143 -0
- package/src/session-todos/tool/types.ts +149 -0
- package/src/session-todos/view/format.ts +264 -0
- package/src/shared/tool-result.ts +81 -0
- package/src/shared/truncation.ts +150 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya Sharma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Notices
|
|
2
|
+
|
|
3
|
+
`@bumpyclock/pi-tasque` is distributed under the MIT License.
|
|
4
|
+
|
|
5
|
+
The session todo layer adapts behavior and structure from the MIT-licensed `@juicesharp/rpiv-todo` package.
|
|
6
|
+
|
|
7
|
+
`pi-tasque` owns the runtime `todo` tool, `/todos` command, and todo overlay. `@juicesharp/rpiv-todo` should not be enabled at the same time; the compatibility is for replay/data behavior, not a runtime dependency.
|
package/README.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# pi-tasque
|
|
2
|
+
|
|
3
|
+
Pi extension package for Tasque durable tasks plus branch-replayed in-session todos.
|
|
4
|
+
|
|
5
|
+
## What this package provides
|
|
6
|
+
|
|
7
|
+
`pi-tasque` gives agents two task layers:
|
|
8
|
+
|
|
9
|
+
- **Session todos**: current-session execution checklist for inspect/edit/verify/handoff steps.
|
|
10
|
+
- **Durable Tasque tasks**: repo-local backlog, specs, dependencies, notes, and ownership through `tsq --format json`.
|
|
11
|
+
|
|
12
|
+
The bridge between the layers is explicit. `pi-tasque` can link, promote, or import tasks, but it does **not** automatically sync lifecycle state between todos and Tasque.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Install with Pi:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pi install npm:@bumpyclock/pi-tasque
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For local development, install the local package path once:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pi install /Users/adityasharma/Projects/pi-tasque
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then use this edit loop:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
edit TypeScript files -> /reload -> test behavior
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pi loads the extension entrypoint from the installed package path, so TypeScript source edits do not require reinstalling the local package. Use `/reload` in the Pi session after changing files under `src/`.
|
|
35
|
+
|
|
36
|
+
### Remove `@juicesharp/rpiv-todo`
|
|
37
|
+
|
|
38
|
+
Remove or disable `@juicesharp/rpiv-todo` before enabling `pi-tasque`:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pi remove npm:@juicesharp/rpiv-todo
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`pi-tasque` owns the same user-facing session todo surface:
|
|
45
|
+
|
|
46
|
+
- `todo` tool
|
|
47
|
+
- `/todos` command
|
|
48
|
+
- above-editor todo overlay
|
|
49
|
+
|
|
50
|
+
Running both packages together can register duplicate tools, commands, or widgets.
|
|
51
|
+
|
|
52
|
+
## Agent guidance
|
|
53
|
+
|
|
54
|
+
Use the smallest task layer that matches the work:
|
|
55
|
+
|
|
56
|
+
- Use `todo` for tactical steps inside the current session: inspect, edit, verify, and handoff.
|
|
57
|
+
- Use `tsq_query` for fresh read-only Tasque state.
|
|
58
|
+
- Use `tsq_change` for explicit durable Tasque mutations, including lifecycle changes and block/order edges.
|
|
59
|
+
- Use `block`/`unblock` for hard dependency edges and `order`/`unorder` for sequencing edges via `tsq_change`.
|
|
60
|
+
- Use `tsq_claim` when taking ownership of a named durable Tasque task.
|
|
61
|
+
- Pass an explicit `assignee` when the agent has a role/name, such as `developer`, `oracle`, or a worker name.
|
|
62
|
+
- If no `assignee` is provided, `tsq_claim` defaults to `pi`.
|
|
63
|
+
- `tsq_claim` defaults `start` to true. Use `requireSpec` when the durable task must have an attached spec before work begins.
|
|
64
|
+
- Use `tsq_claim` with `createTodo: true` when claiming durable work should also create one linked session todo.
|
|
65
|
+
- Use `task_bridge promote_todo` when a session todo should become durable Tasque work.
|
|
66
|
+
- Use `task_bridge import_tsq` when a durable Tasque task should become current-session todo work.
|
|
67
|
+
- Do not create durable Tasque tasks for every session todo.
|
|
68
|
+
- Do not mark a Tasque task done just because linked todos are completed; durable completion requires explicit verification and an explicit Tasque mutation.
|
|
69
|
+
|
|
70
|
+
## Lifecycle boundaries
|
|
71
|
+
|
|
72
|
+
There is no automatic lifecycle sync between session todos and Tasque:
|
|
73
|
+
|
|
74
|
+
- Completing a `todo` does not mark a Tasque task done.
|
|
75
|
+
- Marking a Tasque task done does not complete linked todos.
|
|
76
|
+
- `task_bridge link` records a relationship only.
|
|
77
|
+
- `task_bridge promote_todo` creates a Tasque task, links it, and completes the source todo as part of the explicit promotion flow.
|
|
78
|
+
- `task_bridge import_tsq` creates or reuses session todos for Tasque tasks, but later status changes remain explicit.
|
|
79
|
+
|
|
80
|
+
## Example workflow
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"action": "create",
|
|
85
|
+
"subject": "Investigate failing release check",
|
|
86
|
+
"owner": "developer"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{ "id": "tsq-349aqgsj.12", "assignee": "developer", "createTodo": true }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{ "action": "list_links" }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"action": "done",
|
|
101
|
+
"id": "tsq-349aqgsj.12",
|
|
102
|
+
"note": "Verified docs and package checks."
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## v1 tool and command reference
|
|
107
|
+
|
|
108
|
+
| Surface | Purpose | Notes |
|
|
109
|
+
| -------------------- | --------------------------------------------- | --------------------------------------------------------------- |
|
|
110
|
+
| `todo` | Manage current-session tactical todos. | Session-local; branch-replayed from successful tool results. |
|
|
111
|
+
| `/todos` | Show grouped current-session todos. | Interactive UI only. |
|
|
112
|
+
| `tsq_query` | Read fresh Tasque state. | Read-only; does not mutate Tasque. |
|
|
113
|
+
| `tsq_change` | Run explicit durable Tasque mutations. | Mutations are queued per cwd. |
|
|
114
|
+
| `tsq_claim` | Claim a named durable Tasque task. | `id` required; `assignee` defaults `pi`; `start` defaults true. |
|
|
115
|
+
| `task_bridge` | Link/promote/import between todos and Tasque. | Explicit bridge only; no automatic lifecycle sync. |
|
|
116
|
+
| Todo overlay | Show active session todos above the editor. | Completed todos hide on next turn. |
|
|
117
|
+
| Tasque status footer | Show cached durable-task status. | Display-only; agents should call `tsq_query` for fresh data. |
|
|
118
|
+
|
|
119
|
+
### `todo`
|
|
120
|
+
|
|
121
|
+
Manage current-session tactical todos. Todos are branch-replayed from previous successful `todo` results in the current session branch.
|
|
122
|
+
|
|
123
|
+
Actions:
|
|
124
|
+
|
|
125
|
+
- `create`: add a todo.
|
|
126
|
+
- `update`: change status, text, owner, metadata, or dependencies.
|
|
127
|
+
- `list`: list visible todos; deleted tombstones are hidden unless `includeDeleted` is true.
|
|
128
|
+
- `get`: fetch one todo by id.
|
|
129
|
+
- `delete`: tombstone one todo.
|
|
130
|
+
- `clear`: clear current todo state.
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"action": "create",
|
|
137
|
+
"subject": "Run README verification",
|
|
138
|
+
"owner": "developer"
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"action": "update",
|
|
145
|
+
"id": 1,
|
|
146
|
+
"status": "in_progress",
|
|
147
|
+
"activeForm": "verifying docs"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `/todos`
|
|
152
|
+
|
|
153
|
+
Show current-session todos grouped by status in interactive Pi UI. It reports pending, in-progress, and completed groups and requires interactive mode.
|
|
154
|
+
|
|
155
|
+
Example:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
/todos
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Above-editor todo overlay
|
|
162
|
+
|
|
163
|
+
Shows active session todos above the editor. The overlay rebuilds from branch replay on session lifecycle events and updates after successful `todo`, `task_bridge`, and `tsq_claim` executions that affect todo state.
|
|
164
|
+
|
|
165
|
+
Completed todos remain visible until the next turn, then hide from the overlay.
|
|
166
|
+
|
|
167
|
+
### `tsq_query`
|
|
168
|
+
|
|
169
|
+
Run read-only Tasque queries through `tsq --format json`. Use this for fresh durable task state without mutating Tasque.
|
|
170
|
+
|
|
171
|
+
Actions:
|
|
172
|
+
|
|
173
|
+
- `doctor`
|
|
174
|
+
- `find_ready`
|
|
175
|
+
- `find_open`
|
|
176
|
+
- `show`
|
|
177
|
+
- `show_with_spec`
|
|
178
|
+
- `deps`
|
|
179
|
+
- `notes`
|
|
180
|
+
- `find_tree`
|
|
181
|
+
- `similar`
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{ "action": "find_ready", "lane": "coding", "assignee": "developer" }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{ "action": "show_with_spec", "id": "tsq-349aqgsj.12" }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `tsq_change`
|
|
194
|
+
|
|
195
|
+
Run approved durable Tasque mutations. Mutations are queued per working directory so concurrent agents do not race `tsq` writes.
|
|
196
|
+
|
|
197
|
+
Actions:
|
|
198
|
+
|
|
199
|
+
- `create`
|
|
200
|
+
- `note`
|
|
201
|
+
- `done`
|
|
202
|
+
- `reopen`
|
|
203
|
+
- `defer`
|
|
204
|
+
- `start`
|
|
205
|
+
- `claim_assign_only`
|
|
206
|
+
- `block`: make `child` blocked by `blocker`.
|
|
207
|
+
- `unblock`: remove a block edge between `child` and `blocker`.
|
|
208
|
+
- `order`: make `later` start after `earlier`.
|
|
209
|
+
- `unorder`: remove an order edge between `later` and `earlier`.
|
|
210
|
+
|
|
211
|
+
Use `block` for hard blockers and `order` for sequencing. Use `tsq_query` with `deps` or `show` to inspect durable graph state. Edge actions cannot create self-edges.
|
|
212
|
+
|
|
213
|
+
Examples:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"action": "note",
|
|
218
|
+
"id": "tsq-349aqgsj.12",
|
|
219
|
+
"note": "README docs updated; running verification."
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"action": "done",
|
|
226
|
+
"id": "tsq-349aqgsj.12",
|
|
227
|
+
"note": "Docs and typecheck/test verification passed."
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{ "action": "block", "child": "tsq-abc123.2", "blocker": "tsq-abc123.1" }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{ "action": "order", "later": "tsq-abc123.3", "earlier": "tsq-abc123.2" }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `tsq_claim`
|
|
240
|
+
|
|
241
|
+
Claim a named durable Tasque task. This never auto-selects work; callers must provide the task id. `start` defaults to true, `assignee` defaults to `pi`, and `createTodo` optionally creates one linked session todo for the claimed task.
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"id": "tsq-349aqgsj.12",
|
|
248
|
+
"assignee": "developer",
|
|
249
|
+
"requireSpec": true,
|
|
250
|
+
"createTodo": true
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### `task_bridge`
|
|
255
|
+
|
|
256
|
+
Explicitly connect session todos and durable Tasque tasks. Bridge actions do not create implicit lifecycle sync.
|
|
257
|
+
|
|
258
|
+
Actions:
|
|
259
|
+
|
|
260
|
+
- `link`: associate an existing todo with an existing Tasque task via todo metadata.
|
|
261
|
+
- `list_links`: inspect current session todo ↔ Tasque associations.
|
|
262
|
+
- `promote_todo`: create a Tasque task from an existing todo, add a promotion note, link metadata, and complete the source todo.
|
|
263
|
+
- `import_tsq`: import a Tasque task, or an open-tree task plus children when available, into session todos with `tsqId` metadata links.
|
|
264
|
+
|
|
265
|
+
Link example:
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{ "action": "link", "todoId": 3, "tsqId": "tsq-349aqgsj.12" }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Promote example:
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"action": "promote_todo",
|
|
276
|
+
"todoId": 4,
|
|
277
|
+
"kind": "task",
|
|
278
|
+
"priority": 2,
|
|
279
|
+
"assignee": "developer",
|
|
280
|
+
"parent": "tsq-349aqgsj"
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Import example:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{ "action": "import_tsq", "tsqId": "tsq-349aqgsj.12", "owner": "developer" }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
List links example:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{ "action": "list_links" }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Tasque status footer
|
|
297
|
+
|
|
298
|
+
The status footer shows cached Tasque status in interactive UI:
|
|
299
|
+
|
|
300
|
+
- ready coding count
|
|
301
|
+
- ready planning count
|
|
302
|
+
- `mine` count for tasks assigned to `pi`
|
|
303
|
+
- loading, stale, and error states
|
|
304
|
+
|
|
305
|
+
It refreshes on session start, periodically, and after successful durable mutations from `tsq_change`, `tsq_claim`, or `task_bridge`.
|
|
306
|
+
|
|
307
|
+
The footer is display-only. Refreshed status is not injected into the model context; agents should use `tsq_query` for fresh durable task data. If agents claim with a non-`pi` assignee, those tasks may not appear in the footer's `mine` count.
|
|
308
|
+
|
|
309
|
+
## Verification
|
|
310
|
+
|
|
311
|
+
Run full local verification before handoff:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npm run typecheck && npm test
|
|
315
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bumpyclock/pi-tasque",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pi extension package for Tasque durable tasks plus in-session todos.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/BumpyClock/pi-tasque.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/BumpyClock/pi-tasque/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/BumpyClock/pi-tasque#readme",
|
|
15
|
+
"keywords": ["pi-package", "pi-extension", "tasque", "tsq", "todo"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"test": "vitest run"
|
|
19
|
+
},
|
|
20
|
+
"files": ["src/", "README.md", "LICENSE", "NOTICE.md"],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"provenance": true
|
|
24
|
+
},
|
|
25
|
+
"pi": {
|
|
26
|
+
"extensions": ["./src/index.ts"]
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@earendil-works/pi-ai": "*",
|
|
30
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
31
|
+
"@earendil-works/pi-tui": "*",
|
|
32
|
+
"typebox": "*"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "latest",
|
|
36
|
+
"typescript": "latest",
|
|
37
|
+
"vitest": "latest"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineTool,
|
|
3
|
+
type AgentToolResult,
|
|
4
|
+
type ExtensionAPI,
|
|
5
|
+
type ExtensionContext,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import {
|
|
8
|
+
errorToolDetails,
|
|
9
|
+
okToolDetails,
|
|
10
|
+
textToolResult,
|
|
11
|
+
} from "../shared/tool-result.js";
|
|
12
|
+
import { deriveTaskLinks, linkTodoToTsq } from "./link-store.js";
|
|
13
|
+
import {
|
|
14
|
+
TASK_BRIDGE_TOOL_NAME,
|
|
15
|
+
TaskBridgeParamsSchema,
|
|
16
|
+
type ImportTsqBridgeParams,
|
|
17
|
+
type LinkBridgeParams,
|
|
18
|
+
type ListLinksBridgeParams,
|
|
19
|
+
type PromoteTodoBridgeParams,
|
|
20
|
+
type TaskBridgeAction,
|
|
21
|
+
type TaskBridgeDetails,
|
|
22
|
+
type TaskBridgeHandlerContext,
|
|
23
|
+
type TaskBridgeHandlers,
|
|
24
|
+
type TaskBridgeLink,
|
|
25
|
+
type TaskBridgeParams,
|
|
26
|
+
} from "./types.js";
|
|
27
|
+
|
|
28
|
+
export function registerTaskBridgeTool(
|
|
29
|
+
pi: ExtensionAPI,
|
|
30
|
+
handlers: TaskBridgeHandlers = {},
|
|
31
|
+
): void {
|
|
32
|
+
pi.registerTool(
|
|
33
|
+
defineTool({
|
|
34
|
+
name: TASK_BRIDGE_TOOL_NAME,
|
|
35
|
+
label: "Task Bridge",
|
|
36
|
+
description:
|
|
37
|
+
"Explicitly link, list, promote, or import between session todos and durable Tasque tasks. No automatic lifecycle sync.",
|
|
38
|
+
promptSnippet:
|
|
39
|
+
"task_bridge links, lists, promotes, and imports between session todos and durable Tasque tasks; it never auto-completes one layer from the other.",
|
|
40
|
+
promptGuidelines: [
|
|
41
|
+
"Use link to associate an existing todo with an existing Tasque task via todo metadata tsqId.",
|
|
42
|
+
"Use list_links to inspect current session todo ↔ Tasque associations.",
|
|
43
|
+
"Use promote_todo to create a Tasque task from a todo and link the promoted todo explicitly.",
|
|
44
|
+
"Use import_tsq to create or reuse session todos from Tasque task state and link them explicitly.",
|
|
45
|
+
"Todo completion does not mark Tasque done; durable completion stays explicit.",
|
|
46
|
+
],
|
|
47
|
+
parameters: TaskBridgeParamsSchema,
|
|
48
|
+
executionMode: "sequential",
|
|
49
|
+
|
|
50
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
51
|
+
return executeTaskBridge(pi, params, signal, ctx, handlers);
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function executeTaskBridge(
|
|
58
|
+
pi: ExtensionAPI,
|
|
59
|
+
params: TaskBridgeParams,
|
|
60
|
+
signal: AbortSignal | undefined,
|
|
61
|
+
ctx: ExtensionContext,
|
|
62
|
+
handlers: TaskBridgeHandlers = {},
|
|
63
|
+
): Promise<AgentToolResult<TaskBridgeDetails>> {
|
|
64
|
+
switch (params.action) {
|
|
65
|
+
case "link":
|
|
66
|
+
return executeLink(params as LinkBridgeParams);
|
|
67
|
+
case "list_links":
|
|
68
|
+
return executeListLinks(params as ListLinksBridgeParams);
|
|
69
|
+
case "promote_todo":
|
|
70
|
+
return executeInjectedHandler(
|
|
71
|
+
"promote_todo",
|
|
72
|
+
handlers.promote_todo,
|
|
73
|
+
params as PromoteTodoBridgeParams,
|
|
74
|
+
buildHandlerContext(pi, ctx, signal),
|
|
75
|
+
);
|
|
76
|
+
case "import_tsq":
|
|
77
|
+
return executeInjectedHandler(
|
|
78
|
+
"import_tsq",
|
|
79
|
+
handlers.import_tsq,
|
|
80
|
+
params as ImportTsqBridgeParams,
|
|
81
|
+
buildHandlerContext(pi, ctx, signal),
|
|
82
|
+
);
|
|
83
|
+
default:
|
|
84
|
+
return errorResult(
|
|
85
|
+
"validation_error",
|
|
86
|
+
"action must be a supported task_bridge action",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function executeLink(
|
|
92
|
+
params: LinkBridgeParams,
|
|
93
|
+
): AgentToolResult<TaskBridgeDetails> {
|
|
94
|
+
if (typeof params.todoId !== "number" || !Number.isInteger(params.todoId)) {
|
|
95
|
+
return errorResult("validation_error", "todoId is required");
|
|
96
|
+
}
|
|
97
|
+
if (typeof params.tsqId !== "string") {
|
|
98
|
+
return errorResult("validation_error", "tsqId is required");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const result = linkTodoToTsq(params.todoId, params.tsqId);
|
|
102
|
+
if (!result.ok) {
|
|
103
|
+
return errorResult("validation_error", result.message);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return textToolResult(
|
|
107
|
+
`Linked todo #${result.link.todoId} to ${result.link.tsqId}`,
|
|
108
|
+
okToolDetails({
|
|
109
|
+
action: "link",
|
|
110
|
+
link: result.link,
|
|
111
|
+
todo: result.todo,
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function executeListLinks(
|
|
117
|
+
_params: ListLinksBridgeParams,
|
|
118
|
+
): AgentToolResult<TaskBridgeDetails> {
|
|
119
|
+
const links = deriveTaskLinks();
|
|
120
|
+
return textToolResult(
|
|
121
|
+
formatLinks(links),
|
|
122
|
+
okToolDetails({ action: "list_links", links }),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function executeInjectedHandler<TParams extends TaskBridgeParams>(
|
|
127
|
+
action: TaskBridgeAction,
|
|
128
|
+
handler:
|
|
129
|
+
| ((
|
|
130
|
+
params: TParams,
|
|
131
|
+
ctx: TaskBridgeHandlerContext,
|
|
132
|
+
) =>
|
|
133
|
+
| AgentToolResult<TaskBridgeDetails>
|
|
134
|
+
| Promise<AgentToolResult<TaskBridgeDetails>>)
|
|
135
|
+
| undefined,
|
|
136
|
+
params: TParams,
|
|
137
|
+
ctx: TaskBridgeHandlerContext,
|
|
138
|
+
): Promise<AgentToolResult<TaskBridgeDetails>> {
|
|
139
|
+
if (handler === undefined) {
|
|
140
|
+
return notImplementedResult(action);
|
|
141
|
+
}
|
|
142
|
+
return handler(params, ctx);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildHandlerContext(
|
|
146
|
+
pi: ExtensionAPI,
|
|
147
|
+
ctx: ExtensionContext,
|
|
148
|
+
signal: AbortSignal | undefined,
|
|
149
|
+
): TaskBridgeHandlerContext {
|
|
150
|
+
return {
|
|
151
|
+
pi,
|
|
152
|
+
cwd: ctx.cwd,
|
|
153
|
+
extensionContext: ctx,
|
|
154
|
+
...(signal === undefined ? {} : { signal }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function notImplementedResult(
|
|
159
|
+
action: "promote_todo" | "import_tsq" | TaskBridgeAction,
|
|
160
|
+
): AgentToolResult<TaskBridgeDetails> {
|
|
161
|
+
return errorResult(
|
|
162
|
+
"not_implemented",
|
|
163
|
+
`task_bridge action ${action} handler is not configured`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function errorResult(
|
|
168
|
+
code: "validation_error" | "not_implemented",
|
|
169
|
+
message: string,
|
|
170
|
+
): AgentToolResult<TaskBridgeDetails> {
|
|
171
|
+
return textToolResult(
|
|
172
|
+
`Error: ${message}`,
|
|
173
|
+
errorToolDetails({ code, message }),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function formatLinks(links: readonly TaskBridgeLink[]): string {
|
|
178
|
+
if (links.length === 0) return "No linked todos";
|
|
179
|
+
return [
|
|
180
|
+
`${links.length} linked ${links.length === 1 ? "todo" : "todos"}`,
|
|
181
|
+
...links.map(
|
|
182
|
+
(link) => `#${link.todoId} ${link.todoSubject} ↔ ${link.tsqId}`,
|
|
183
|
+
),
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|