taskchef 5.7.2 → 5.9.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/.codex-plugin/plugin.json +2 -2
- package/BACKLOG.md +4 -4
- package/README.md +125 -45
- package/SPEC.md +106 -89
- package/docs/delegation-design.md +158 -254
- package/hooks/hooks.json +18 -0
- package/hooks/taskchef-initial-prompt.js +17 -0
- package/index.js +13 -1
- package/package.json +3 -2
- package/skills/taskchef-bootstrap/SKILL.md +6 -3
- package/skills/taskchef-delegate/SKILL.md +49 -91
- package/skills/taskchef-report/SKILL.md +60 -27
- package/src/cli.js +52 -2
- package/src/dashboard/app.js +255 -0
- package/src/dashboard/index.html +86 -0
- package/src/dashboard/state.js +40 -0
- package/src/dashboard/styles.css +147 -0
- package/src/dashboard.js +597 -0
- package/src/delegation.js +109 -359
- package/src/hook.js +60 -0
- package/src/mcp.js +35 -2
- package/src/workspace-path.js +5 -1
- package/src/workspace.js +239 -23
|
@@ -1,270 +1,174 @@
|
|
|
1
|
-
# Delegation design
|
|
1
|
+
# Delegation and result design
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
TaskChef is deliberately smaller than a workflow engine. It records one useful
|
|
4
|
+
snapshot per delegated task, links the executor identity once, accepts semantic
|
|
5
|
+
results from the executor, and performs cheap freshness checks when reporting.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
TaskChef is the dispatcher and Codex tasks are the executors. TaskChef chooses
|
|
10
|
-
where work belongs, creates a normal Codex task there, and appends one entry to
|
|
11
|
-
the canonical `~/.agents/taskchef/tasks.jsonl` history. It returns after the
|
|
12
|
-
task is recorded; it does not wait for executor completion.
|
|
13
|
-
|
|
14
|
-
```mermaid
|
|
15
|
-
flowchart LR
|
|
16
|
-
U["User request"] --> D["TaskChef dispatcher"]
|
|
17
|
-
D --> P["Choose configured project"]
|
|
18
|
-
P --> C["Create Codex executor"]
|
|
19
|
-
C --> R["Record task atomically"]
|
|
20
|
-
R --> U2["Return executor link"]
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Three focused local tools handle TaskChef-owned data operations:
|
|
24
|
-
|
|
25
|
-
| Tool | Responsibility | Mutates history? |
|
|
26
|
-
| --- | --- | --- |
|
|
27
|
-
| `prepare_dispatch` | Load routes and generate the UUID, timestamp, and exact marker | No |
|
|
28
|
-
| `record_task` | Append the created task with a durable ID or `null` | Yes, one atomic append |
|
|
29
|
-
| `resolve_task` | Fill a recorded `null` ID after one exact marker match | Yes, one-way and atomic |
|
|
30
|
-
|
|
31
|
-
The tools never create Codex tasks. Task creation and thread discovery remain
|
|
32
|
-
native Codex operations.
|
|
33
|
-
|
|
34
|
-
## Before and after structured tools
|
|
35
|
-
|
|
36
|
-
The following values come from separate real traces. The MCP record calls used
|
|
37
|
-
an existing record and therefore measured duplicate validation, locking, tool
|
|
38
|
-
transport, and permission overhead rather than a new append. The resolve calls
|
|
39
|
-
were idempotent. Treat the operation comparison as strong evidence about
|
|
40
|
-
orchestration overhead, not as a complete post-release delegation benchmark.
|
|
41
|
-
|
|
42
|
-
| Stage | Earlier CLI path | TaskChef 5.5 MCP path | What changed |
|
|
43
|
-
| --- | ---: | ---: | --- |
|
|
44
|
-
| Prepare routing data and correlation values | Several operations; roughly 0.4–1.0 s in the original trace | `prepare_dispatch`: 79 ms | One call now loads routes and generates UUID, timestamp, and marker internally |
|
|
45
|
-
| Native Codex project list | About 0.6–0.7 s | Still about 0.6–0.7 s | Runs concurrently with preparation |
|
|
46
|
-
| Create Codex task | About 0.3 s | Fundamentally unchanged | Still a native Codex operation |
|
|
47
|
-
| Record permission-aware operation | 7.167 s | 62 ms, then 58 ms | Removed shell, stdin, temporary-file, sandbox-failure, and approval paths |
|
|
48
|
-
| Resolve permission-aware operation | 8.982 s | 11 ms, then 8 ms | Uses a structured atomic call instead of a new shell command |
|
|
49
|
-
| Approval prompts | Required in the failing trace | None in the MCP benchmark | The installed local tool process has the appropriate tool authorization |
|
|
50
|
-
|
|
51
|
-
Combining measurements from different runs suggests a durable fast path near
|
|
52
|
-
1.0–1.2 seconds, compared with roughly 8–9 seconds for the later CLI benchmark.
|
|
53
|
-
This is an estimate until another full post-release delegation benchmark
|
|
54
|
-
measures every stage in one run.
|
|
55
|
-
|
|
56
|
-
### Where the time was saved
|
|
57
|
-
|
|
58
|
-
MCP is not inherently thousands of times faster than invoking a CLI. Both
|
|
59
|
-
paths ultimately call the same TaskChef validation, lock, and atomic-write
|
|
60
|
-
code. The large difference came from work surrounding that code:
|
|
61
|
-
|
|
62
|
-
| Removed overhead | Evidence from the original trace |
|
|
63
|
-
| --- | --- |
|
|
64
|
-
| Interactive stdin and EOF handling | The first TTY attempt consumed several tool round trips and did not terminate cleanly |
|
|
65
|
-
| Temporary-file/redirection orchestration | A second invocation was needed to pass one exact JSON value non-interactively |
|
|
66
|
-
| Late sandbox failure | The redirected attempt waited about 7.3 seconds before `EPERM` on the canonical workspace lock |
|
|
67
|
-
| Approval and retry | The escalated retry took about 11 seconds including review and then succeeded |
|
|
68
|
-
| Separate UUID/timestamp shell work | Those values are now generated inside `prepare_dispatch` |
|
|
69
|
-
| Repeated process/tool boundaries | Preparation and both writes are focused structured calls with validated schemas |
|
|
70
|
-
|
|
71
|
-
The actual TaskChef write is small. The MCP benchmark reached duplicate-record
|
|
72
|
-
validation in tens of milliseconds. The improvement comes mainly from avoiding
|
|
73
|
-
a known-to-fail sandboxed command followed by an approved retry, not from
|
|
74
|
-
weakening locking, atomicity, or validation.
|
|
75
|
-
|
|
76
|
-
## Durable-ID fast path
|
|
7
|
+
## Minimal workflow
|
|
77
8
|
|
|
78
9
|
```mermaid
|
|
79
10
|
sequenceDiagram
|
|
80
11
|
participant U as User
|
|
81
|
-
participant
|
|
12
|
+
participant D as Delegate skill
|
|
82
13
|
participant M as TaskChef MCP
|
|
83
|
-
participant C as Codex
|
|
84
|
-
participant
|
|
14
|
+
participant C as Codex executor
|
|
15
|
+
participant H as Initial hook
|
|
16
|
+
participant W as tasks.jsonl
|
|
85
17
|
|
|
86
|
-
U->>
|
|
87
|
-
par
|
|
88
|
-
|
|
89
|
-
M->>W: Load and validate routes
|
|
90
|
-
M-->>S: UUID, timestamp, marker, projects
|
|
18
|
+
U->>D: Delegate work
|
|
19
|
+
par Prepare routing
|
|
20
|
+
D->>M: prepare_dispatch
|
|
91
21
|
and
|
|
92
|
-
|
|
93
|
-
C-->>S: Native projects
|
|
22
|
+
D->>C: List native projects
|
|
94
23
|
end
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
C
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```json
|
|
109
|
-
{
|
|
110
|
-
"schemaVersion": 1,
|
|
111
|
-
"workspace": "/home/example/.agents/taskchef",
|
|
112
|
-
"taskId": "c0f010ff-84f2-4838-a69d-0ff1f5d721d7",
|
|
113
|
-
"preparedAt": "2026-08-14T09:30:00.000Z",
|
|
114
|
-
"marker": "<!-- taskchef_id=c0f010ff-84f2-4838-a69d-0ff1f5d721d7 -->",
|
|
115
|
-
"projectCount": 1,
|
|
116
|
-
"projects": [
|
|
117
|
-
{
|
|
118
|
-
"name": "t2",
|
|
119
|
-
"path": "/projects/t2",
|
|
120
|
-
"isGitRepository": true,
|
|
121
|
-
"githubRepos": [],
|
|
122
|
-
"description": "Small Python fixture project"
|
|
123
|
-
}
|
|
124
|
-
]
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
The exact executor instruction becomes:
|
|
129
|
-
|
|
130
|
-
```text
|
|
131
|
-
<!-- taskchef_id=c0f010ff-84f2-4838-a69d-0ff1f5d721d7 -->
|
|
132
|
-
|
|
133
|
-
This task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.
|
|
134
|
-
|
|
135
|
-
Return exactly the integers 1 through 10, one per line.
|
|
136
|
-
Do not modify files.
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
If Codex returns a durable ID such as
|
|
140
|
-
`019ffbd4-5d96-79c0-9364-130d58156b76`, TaskChef sends the same marked
|
|
141
|
-
instruction and durable ID to `record_task`. The tool rejects a mismatched
|
|
142
|
-
marker, duplicate task ID, or duplicate durable thread ID before appending.
|
|
143
|
-
|
|
144
|
-
## Provisional-ID recovery path
|
|
145
|
-
|
|
146
|
-
Codex can sometimes return a `clientThreadId` or `pendingWorktreeId` before the
|
|
147
|
-
durable task is discoverable. TaskChef never writes that provisional value into
|
|
148
|
-
the canonical `threadId` field.
|
|
149
|
-
|
|
150
|
-
```mermaid
|
|
151
|
-
flowchart TD
|
|
152
|
-
C["Creation returns provisional ID"] --> N["Record task with threadId: null"]
|
|
153
|
-
N --> S1["First recent-task snapshot around 10 seconds"]
|
|
154
|
-
S1 --> B1["Filter candidates and batch-read structured inputs"]
|
|
155
|
-
B1 --> M1{"Exactly one exact marker match?"}
|
|
156
|
-
M1 -->|Yes| R["resolve_task: null to durable ID"]
|
|
157
|
-
M1 -->|No| S2["Second snapshot at least 20 seconds after first start"]
|
|
158
|
-
S2 --> B2["Filter candidates and batch-read structured inputs"]
|
|
159
|
-
B2 --> M2{"Exactly one exact marker match?"}
|
|
160
|
-
M2 -->|Yes| R
|
|
161
|
-
M2 -->|Zero, multiple, or error| U["Keep null and report unresolved"]
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
The nominal snapshot starts are 10 and 30 seconds after the provisional
|
|
165
|
-
result. They are catch-up checkpoints rather than expiration deadlines. If
|
|
166
|
-
mandatory recording finishes at 14 seconds, the first snapshot starts
|
|
167
|
-
immediately at 14 seconds, and the second cannot start before 34 seconds. Work
|
|
168
|
-
spent filtering and reading candidates counts toward that 20-second interval.
|
|
169
|
-
TaskChef never takes a third snapshot.
|
|
170
|
-
|
|
171
|
-
### Example
|
|
172
|
-
|
|
173
|
-
Suppose task creation initially returns only:
|
|
174
|
-
|
|
175
|
-
```json
|
|
176
|
-
{
|
|
177
|
-
"clientThreadId": "local:pending-123"
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
TaskChef keeps that value for diagnostics only and records the complete task
|
|
182
|
-
with `threadId: null`:
|
|
183
|
-
|
|
184
|
-
```json
|
|
185
|
-
{
|
|
186
|
-
"id": "c0f010ff-84f2-4838-a69d-0ff1f5d721d7",
|
|
187
|
-
"project": "/projects/t2",
|
|
188
|
-
"title": "Count from 1 to 10",
|
|
189
|
-
"instruction": "<!-- taskchef_id=c0f010ff-84f2-4838-a69d-0ff1f5d721d7 -->\n\nThis task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.\n\nReturn exactly the integers 1 through 10, one per line.\nDo not modify files.",
|
|
190
|
-
"threadId": null
|
|
191
|
-
}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
A later candidate read might contain this structured delegated input:
|
|
195
|
-
|
|
196
|
-
```json
|
|
197
|
-
{
|
|
198
|
-
"userMessage": {
|
|
199
|
-
"content": [
|
|
200
|
-
{
|
|
201
|
-
"codexDelegation": {
|
|
202
|
-
"input": "<!-- taskchef_id=c0f010ff-84f2-4838-a69d-0ff1f5d721d7 -->\n\nThis task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.\n\nReturn exactly the integers 1 through 10, one per line.\nDo not modify files."
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
]
|
|
206
|
-
},
|
|
207
|
-
"threadId": "019ffbd4-5d96-79c0-9364-130d58156b76"
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
If this is the only exact marker match, TaskChef calls:
|
|
212
|
-
|
|
213
|
-
```json
|
|
214
|
-
{
|
|
215
|
-
"tool": "resolve_task",
|
|
216
|
-
"arguments": {
|
|
217
|
-
"taskId": "c0f010ff-84f2-4838-a69d-0ff1f5d721d7",
|
|
218
|
-
"threadId": "019ffbd4-5d96-79c0-9364-130d58156b76"
|
|
219
|
-
}
|
|
220
|
-
}
|
|
24
|
+
D->>M: record_task(threadId: null)
|
|
25
|
+
M->>W: Append working task under lock
|
|
26
|
+
D->>C: Create task with exact marker
|
|
27
|
+
alt Durable ID returned
|
|
28
|
+
D->>M: resolve_task
|
|
29
|
+
else Provisional ID returned
|
|
30
|
+
C->>H: Initial UserPromptSubmit
|
|
31
|
+
H->>W: Resolve root session ID and initial turn
|
|
32
|
+
end
|
|
33
|
+
D-->>U: Return immediately
|
|
34
|
+
C->>M: report_result(needs_input | completed | failed)
|
|
35
|
+
M->>W: Replace latest semantic snapshot under lock
|
|
221
36
|
```
|
|
222
37
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
38
|
+
Recording happens before creation. This closes the only important race: when
|
|
39
|
+
the initial hook runs, the exact TaskChef marker already has an entry to update.
|
|
40
|
+
There is no 10/30-second discovery loop, scheduler, daemon, or dispatcher
|
|
41
|
+
wakeup.
|
|
226
42
|
|
|
227
|
-
|
|
43
|
+
Every executor receives this ownership instruction unchanged:
|
|
228
44
|
|
|
229
|
-
|
|
230
|
-
2. Filters by available host, project, creation time, and worktree metadata.
|
|
231
|
-
3. Uses title only to prioritize reads, never as correlation proof.
|
|
232
|
-
4. Reads every remaining candidate together in one programmatic batch.
|
|
233
|
-
5. Examines only structured `codexDelegation.input`.
|
|
234
|
-
6. Accepts only one input beginning with the exact marker and blank line.
|
|
235
|
-
7. Calls `resolve_task` once to atomically change `null` to the durable ID.
|
|
45
|
+
> This task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.
|
|
236
46
|
|
|
237
|
-
|
|
238
|
-
the existing nullable record intact. TaskChef reports that state and never
|
|
239
|
-
guesses.
|
|
47
|
+
## Who writes what
|
|
240
48
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
49
|
+
| Writer | Trigger and condition | Fields it owns |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Dispatcher via `record_task` | Before executor creation | New entry, `status: working`, null identity/result, server timestamps |
|
|
52
|
+
| Dispatcher via `resolve_task` | Creation immediately returns a durable root ID | `threadId` only |
|
|
53
|
+
| Initial `UserPromptSubmit` hook | Prompt starts with the exact TaskChef marker and the entry exists | `threadId`, initial `turnId`, `status: working`, `updatedAt`, `updatedBy: hook` |
|
|
54
|
+
| Follow-up `UserPromptSubmit` hook | Session ID exactly matches a recorded executor | Nothing; reads the snapshot and injects the current `turnId` for the MCP callback |
|
|
55
|
+
| Executor via `report_result` | Work has a semantic outcome | `status`, bounded `summary`, result `turnId`, `updatedAt`, `updatedBy: mcp` |
|
|
56
|
+
| Reporter | On explicit report request | Nothing; inferred live state is never persisted |
|
|
57
|
+
|
|
58
|
+
The hook does not write needs-input, completed, or failed. Its follow-up path is
|
|
59
|
+
read-only and exists only so the executor can report the current turn. A native permission
|
|
60
|
+
request is live Codex state; it is not a TaskChef semantic result. The executor
|
|
61
|
+
uses `needs_input` only when it truly requires a user decision or information.
|
|
62
|
+
|
|
63
|
+
## Task snapshot
|
|
64
|
+
|
|
65
|
+
Schema version 3 retains the delegation fields and adds:
|
|
66
|
+
|
|
67
|
+
- `status`: `working`, `needs_input`, `completed`, or `failed`
|
|
68
|
+
- `summary`: null while working, otherwise a concise result capped at 2,000 characters
|
|
69
|
+
- `turnId`: initial or latest reported turn; linked MCP results require it, and
|
|
70
|
+
only a pre-thread creation failure may report null
|
|
71
|
+
- `updatedAt`: server-side timestamp
|
|
72
|
+
- `updatedBy`: `dispatcher`, `hook`, or `mcp`
|
|
73
|
+
|
|
74
|
+
Schema versions 1 and 2 remain readable and normalize to nullable result fields.
|
|
75
|
+
There is no result-event file: each callback replaces the latest snapshot on the
|
|
76
|
+
same JSONL line.
|
|
77
|
+
Result instructions forbid secrets, transcripts, and raw command output; the
|
|
78
|
+
server also caps the stored summary at 2,000 characters.
|
|
79
|
+
|
|
80
|
+
## Locking and conflicts
|
|
81
|
+
|
|
82
|
+
All configuration, identity, and result writes use the existing cross-process
|
|
83
|
+
workspace lock. A writer acquires the lock, rereads and validates the complete
|
|
84
|
+
JSONL file, changes one exact task, and publishes a complete replacement with
|
|
85
|
+
an atomic rename. Concurrent writers therefore cannot create partial JSON,
|
|
86
|
+
duplicate entries, or lose changes to different tasks. Sequential callbacks for
|
|
87
|
+
the same task use last accepted write wins; normal executor turns are already
|
|
88
|
+
sequential.
|
|
89
|
+
|
|
90
|
+
SQLite is postponed because this file-level write volume is tiny and the
|
|
91
|
+
existing lock provides the property users need. SQLite becomes worthwhile only
|
|
92
|
+
if TaskChef later adds high-frequency event history or many continuous writers.
|
|
93
|
+
|
|
94
|
+
## Result trust
|
|
95
|
+
|
|
96
|
+
The MCP server does not receive an independently authenticated caller task ID
|
|
97
|
+
from the model transport. It validates that the supplied task exists and that
|
|
98
|
+
the supplied durable thread ID exactly matches the recorded thread. The turn ID
|
|
99
|
+
is stored as evidence but remains model-supplied. A trusted plugin install,
|
|
100
|
+
local-only MCP server, bounded summary, and exact task/thread match are the
|
|
101
|
+
current trust boundary.
|
|
102
|
+
|
|
103
|
+
This is sufficient for a lightweight personal dispatcher, but not a
|
|
104
|
+
multi-tenant authorization boundary. Transport-authenticated caller identity is
|
|
105
|
+
postponed until Codex exposes it.
|
|
106
|
+
|
|
107
|
+
## Fresh reporting without reading every task
|
|
108
|
+
|
|
109
|
+
A stored result is cached evidence, not permanent truth. Overview reports:
|
|
110
|
+
|
|
111
|
+
1. Load `tasks.jsonl` once.
|
|
112
|
+
2. Always consider working, needs-input, unresolved, and legacy entries.
|
|
113
|
+
3. Consider completed or failed entries updated in the last seven days.
|
|
114
|
+
4. Take one recent-thread metadata snapshot for all selected tasks. Include an
|
|
115
|
+
older terminal task in an overview when the snapshot shows it is active or
|
|
116
|
+
awaiting native approval.
|
|
117
|
+
5. A null-thread/null-turn `failed` snapshot written by MCP is a fresh creation
|
|
118
|
+
failure and needs no live task lookup because no executor exists.
|
|
119
|
+
6. Treat only `updatedBy: mcp` as a semantic cache. Dispatcher- and hook-written
|
|
120
|
+
`working` snapshots require a targeted live read; an inactive task with no
|
|
121
|
+
callback has an unknown outcome.
|
|
122
|
+
7. In a broad overview, use an MCP result directly when identity is certain and
|
|
123
|
+
the task is inactive; do not fan out detailed reads over idle terminal tasks
|
|
124
|
+
solely because their timestamps are newer.
|
|
125
|
+
8. Active or awaiting-approval metadata overrides the cached result directly.
|
|
126
|
+
For a focused task, title, or project report, read each selected inactive
|
|
127
|
+
task at most once when matched metadata is newer than the callback by any
|
|
128
|
+
amount. Batch targeted immediate reads, at most eight tasks per call, also
|
|
129
|
+
for a missing callback, uncertain or contradictory state, or an explicitly
|
|
130
|
+
fully-live request.
|
|
131
|
+
9. If an anomaly triggers a detailed read, compare its latest structured turn
|
|
132
|
+
ID and native turn state with stored `turnId`. A newer turn without a
|
|
133
|
+
callback makes the cache stale. An interrupted or cancelled callback turn
|
|
134
|
+
cannot prove completion.
|
|
135
|
+
|
|
136
|
+
An explicit task, title, or project report bypasses the seven-day overview
|
|
137
|
+
filter. Old terminal tasks skipped from an overview are counted so the user
|
|
138
|
+
knows history was intentionally omitted.
|
|
139
|
+
|
|
140
|
+
The cheap operation is the single list/metadata snapshot, not one read per
|
|
141
|
+
historical task. It is sufficient to expose active and native-approval state for
|
|
142
|
+
many recent tasks at once. It does not prove completion; semantic outcomes come
|
|
143
|
+
from MCP callbacks. Detailed thread reads are the exceptional fallback.
|
|
144
|
+
Timestamps are a pragmatic anomaly filter; turn IDs and native turn state
|
|
145
|
+
provide the stronger check whenever a targeted response is necessary.
|
|
146
|
+
|
|
147
|
+
## Permission and follow-up example
|
|
148
|
+
|
|
149
|
+
1. Delegation records one `working` entry with null identity.
|
|
150
|
+
2. The initial hook resolves the root thread and initial turn.
|
|
151
|
+
3. The executor reaches a real product decision and calls `report_result` with
|
|
152
|
+
`needs_input` plus “Approve deployment to production.”
|
|
153
|
+
4. The user opens that executor and approves. The same hook reads the matching
|
|
154
|
+
task and injects the new turn ID without changing the stored snapshot. Until
|
|
155
|
+
the final callback, a report sees newer/active live metadata and labels the
|
|
156
|
+
cached needs-input result stale.
|
|
157
|
+
5. The executor finishes and calls `report_result` with `completed`, the new
|
|
158
|
+
turn ID, and a concise outcome. The same JSONL line now contains the completed
|
|
159
|
+
snapshot.
|
|
160
|
+
|
|
161
|
+
If step 3 were merely Codex asking for filesystem or command approval, no MCP
|
|
162
|
+
callback would be written. Reporting would show “awaiting native approval” from
|
|
163
|
+
live task state.
|
|
164
|
+
|
|
165
|
+
## Explicitly postponed
|
|
166
|
+
|
|
167
|
+
- append-only result or transition events
|
|
168
|
+
- lifecycle event types beyond `UserPromptSubmit`
|
|
169
|
+
- fork tracking or result merging
|
|
170
|
+
- SQLite
|
|
171
|
+
- polling, reconciliation schedules, daemons, and dispatcher wakeups
|
|
172
|
+
- durable report watermarks
|
|
173
|
+
- transcript or assistant-prose classification
|
|
174
|
+
- transport-authenticated caller thread/turn identity
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Link TaskChef executor identity and provide the current callback turn.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"UserPromptSubmit": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"$PLUGIN_ROOT/hooks/taskchef-initial-prompt.js\"",
|
|
10
|
+
"commandWindows": "node \"%PLUGIN_ROOT%\\hooks\\taskchef-initial-prompt.js\"",
|
|
11
|
+
"timeout": 10,
|
|
12
|
+
"statusMessage": "Linking TaskChef task"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { handleInitialPromptHook } from "../src/hook.js";
|
|
4
|
+
|
|
5
|
+
let raw = "";
|
|
6
|
+
process.stdin.setEncoding("utf8");
|
|
7
|
+
for await (const chunk of process.stdin) raw += chunk;
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const input = JSON.parse(raw);
|
|
11
|
+
process.stdout.write(`${JSON.stringify(await handleInitialPromptHook(input))}\n`);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
process.stdout.write(`${JSON.stringify({
|
|
14
|
+
continue: true,
|
|
15
|
+
systemMessage: `TaskChef initial identity hook failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
16
|
+
})}\n`);
|
|
17
|
+
}
|
package/index.js
CHANGED
|
@@ -15,7 +15,9 @@ export {
|
|
|
15
15
|
listTasks,
|
|
16
16
|
readTask,
|
|
17
17
|
recordTask,
|
|
18
|
+
reportTaskResult,
|
|
18
19
|
resolveTask,
|
|
20
|
+
startTaskFromHook,
|
|
19
21
|
requireSafeId,
|
|
20
22
|
removeProject,
|
|
21
23
|
validateConfig,
|
|
@@ -29,6 +31,7 @@ export {
|
|
|
29
31
|
|
|
30
32
|
export {
|
|
31
33
|
EXECUTOR_OWNERSHIP_PARAGRAPH,
|
|
34
|
+
EXECUTOR_RESULT_PARAGRAPH,
|
|
32
35
|
THREAD_RESOLUTION_CHECKPOINTS_MS,
|
|
33
36
|
THREAD_RESOLUTION_CLOCK_SKEW_MS,
|
|
34
37
|
THREAD_RESOLUTION_RECENT_LIMIT,
|
|
@@ -36,8 +39,8 @@ export {
|
|
|
36
39
|
createAndRecordDelegation,
|
|
37
40
|
filterThreadCandidates,
|
|
38
41
|
hasExactTaskChefMarker,
|
|
39
|
-
listThreadEntries,
|
|
40
42
|
isProvisionalThreadId,
|
|
43
|
+
listThreadEntries,
|
|
41
44
|
normalizeDurableThreadId,
|
|
42
45
|
parseTaskChefMarker,
|
|
43
46
|
prepareDelegation,
|
|
@@ -56,4 +59,13 @@ export {
|
|
|
56
59
|
openWorkspaceInCodex,
|
|
57
60
|
} from "./src/codex-app.js";
|
|
58
61
|
|
|
62
|
+
export {
|
|
63
|
+
DashboardMonitor,
|
|
64
|
+
createDashboardServer,
|
|
65
|
+
dashboardAuthority,
|
|
66
|
+
sortTasksByMeaningfulUpdate,
|
|
67
|
+
} from "./src/dashboard.js";
|
|
68
|
+
|
|
59
69
|
export { createTaskChefMcpServer } from "./src/mcp.js";
|
|
70
|
+
|
|
71
|
+
export { handleInitialPromptHook } from "./src/hook.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taskchef",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"description": "A non-blocking interactive dispatcher for visible Codex tasks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Favo Yang",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"BACKLOG.md",
|
|
25
25
|
"bin",
|
|
26
26
|
"docs/delegation-design.md",
|
|
27
|
+
"hooks",
|
|
27
28
|
"index.js",
|
|
28
29
|
"mcp",
|
|
29
30
|
"SPEC.md",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"scripts": {
|
|
45
46
|
"benchmark:dispatch": "node scripts/benchmark-dispatch-prepare.js",
|
|
46
47
|
"benchmark:e2e": "node scripts/e2e-benchmark.js",
|
|
47
|
-
"test": "node --test tests
|
|
48
|
+
"test": "node --test tests/*.test.js"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
51
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -19,13 +19,16 @@ all deterministic workspace operations.
|
|
|
19
19
|
workspace.
|
|
20
20
|
- Do not dispatch tasks or report on executor threads during bootstrap unless
|
|
21
21
|
the user separately requests those actions.
|
|
22
|
-
- Never
|
|
22
|
+
- Never create ad hoc hooks, schedules, polling, or daemons. The installed
|
|
23
|
+
plugin's initial-identity hook is part of normal TaskChef execution, not
|
|
24
|
+
bootstrap work.
|
|
23
25
|
|
|
24
26
|
## Initialize and repair
|
|
25
27
|
|
|
26
28
|
1. Run `workspace path --json` and use its returned canonical path for native
|
|
27
29
|
project comparisons. The CLI resolves `--workspace`, then
|
|
28
|
-
`TASKCHEF_WORKSPACE`, then
|
|
30
|
+
an absolute (or `~/`-prefixed) `TASKCHEF_WORKSPACE`, then
|
|
31
|
+
`~/.agents/taskchef`; do not infer a workspace
|
|
29
32
|
from the current project.
|
|
30
33
|
2. List native Codex projects once. If an exact canonical-path local project
|
|
31
34
|
already exists, run `workspace init --json`. Otherwise run
|
|
@@ -35,7 +38,7 @@ all deterministic workspace operations.
|
|
|
35
38
|
CLI discovered from the current desktop environment; never invoke
|
|
36
39
|
`codex add` or hard-code an application bundle path.
|
|
37
40
|
3. `workspace init` takes no stdin, creates an empty
|
|
38
|
-
configuration when missing, creates the
|
|
41
|
+
configuration when missing, creates the one-entry-per-task JSONL log, refreshes
|
|
39
42
|
managed instructions, and removes legacy TaskChef skill links. The installed
|
|
40
43
|
plugin provides all three TaskChef skills outside the dispatcher workspace.
|
|
41
44
|
4. Run `doctor --json` after setup or when the user asks to diagnose the
|