taskchef 6.1.3 → 7.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/.codex-plugin/plugin.json +1 -1
- package/BACKLOG.md +1 -1
- package/README.md +100 -453
- package/docs/firstmate-taskchef-comparison.md +279 -0
- package/docs/spec.md +265 -0
- package/docs/workflows.md +249 -0
- package/index.js +2 -2
- package/package.json +4 -3
- package/scripts/benchmark-dispatch-prepare.js +7 -7
- package/skills/taskchef-bootstrap/SKILL.md +7 -8
- package/skills/taskchef-delegate/SKILL.md +15 -17
- package/skills/taskchef-report/SKILL.md +18 -22
- package/src/cli.js +7 -24
- package/src/dashboard/app.js +8 -4
- package/src/dashboard/state.js +1 -0
- package/src/dashboard.js +3 -1
- package/src/delegation.js +7 -10
- package/src/github.js +0 -4
- package/src/mcp.js +42 -8
- package/src/workspace.js +295 -251
- package/SPEC.md +0 -107
- package/docs/delegation-design.md +0 -227
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# TaskChef workflows
|
|
2
|
+
|
|
3
|
+
This developer and advanced-agent guide explains how the current implementation
|
|
4
|
+
moves data through TaskChef. The [specification](spec.md) is normative; the
|
|
5
|
+
[README](../README.md) owns user operation. The
|
|
6
|
+
[FirstMate comparison](firstmate-taskchef-comparison.md) is non-normative
|
|
7
|
+
research.
|
|
8
|
+
|
|
9
|
+
## Implementation map
|
|
10
|
+
|
|
11
|
+
| Surface | Responsibility |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `skills/taskchef-delegate/SKILL.md` | Split, route, record-before-create, create, return. |
|
|
14
|
+
| `skills/taskchef-bootstrap/SKILL.md` | Initialize current workspace and configure projects. |
|
|
15
|
+
| `skills/taskchef-report/SKILL.md` | Select cached tasks and perform bounded live checks. |
|
|
16
|
+
| `src/mcp.js` | Four primary lifecycle tools, one deprecated alias, and MCP annotations. |
|
|
17
|
+
| `src/delegation.js` | UUID marker, executor contract paragraphs, and creation-failure handling. |
|
|
18
|
+
| `src/workspace.js` | Current schemas, validation, locking, atomic JSONL writes, linking, and result freshness. |
|
|
19
|
+
| `src/cli.js` | Administration, inspection, diagnostics, and dashboard startup. |
|
|
20
|
+
| `src/dashboard.js` | Validated snapshots, SSE fan-out, and bounded open actions. |
|
|
21
|
+
|
|
22
|
+
The MCP process resolves `TASKCHEF_WORKSPACE` once and never accepts a model
|
|
23
|
+
supplied path. The CLI resolves `--workspace`, then the environment, then the
|
|
24
|
+
per-user default.
|
|
25
|
+
|
|
26
|
+
## Normal delegation and self-linking
|
|
27
|
+
|
|
28
|
+
The dispatcher uses native Codex project discovery for routing and MCP for
|
|
29
|
+
TaskChef state. It never supplies executor identity.
|
|
30
|
+
|
|
31
|
+
```mermaid
|
|
32
|
+
sequenceDiagram
|
|
33
|
+
autonumber
|
|
34
|
+
actor U as User
|
|
35
|
+
participant D as Dispatcher skill
|
|
36
|
+
participant M as TaskChef MCP
|
|
37
|
+
participant W as workspace.js
|
|
38
|
+
participant C as Native Codex
|
|
39
|
+
participant E as Executor
|
|
40
|
+
U->>D: Request outcome
|
|
41
|
+
par Route once
|
|
42
|
+
D->>C: List native projects
|
|
43
|
+
and Prepare each outcome
|
|
44
|
+
D->>M: prepare_dispatch()
|
|
45
|
+
M->>W: prepareDispatch()
|
|
46
|
+
W-->>M: UUID, marker, projects
|
|
47
|
+
M-->>D: preparation
|
|
48
|
+
end
|
|
49
|
+
D->>D: Choose one configured and native project
|
|
50
|
+
D->>M: record_task(id, project, title, instruction, null)
|
|
51
|
+
M->>W: recordTask()
|
|
52
|
+
W->>W: Lock, validate, append schema-5 snapshot
|
|
53
|
+
W-->>M: working link-pending task
|
|
54
|
+
M-->>D: task
|
|
55
|
+
D->>C: Create executor with marked instruction
|
|
56
|
+
C-->>D: Created-task reference
|
|
57
|
+
D-->>U: Return immediately
|
|
58
|
+
C->>E: Start executor
|
|
59
|
+
E->>E: Read own CODEX_THREAD_ID
|
|
60
|
+
E->>M: link_task(taskId, threadId)
|
|
61
|
+
M->>W: linkTask()
|
|
62
|
+
W->>W: Lock and replace null identity atomically
|
|
63
|
+
W-->>E: linked working task
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Record-before-create makes native creation failure observable. Executor
|
|
67
|
+
self-linking removes dispatcher-side polling, task search, title matching, and
|
|
68
|
+
parent/child identity inference.
|
|
69
|
+
|
|
70
|
+
## State reporting
|
|
71
|
+
|
|
72
|
+
The executor obtains the turn identity from an exact native read of its own
|
|
73
|
+
linked task. `report_state` records live turn state while preserving the last
|
|
74
|
+
semantic result separately.
|
|
75
|
+
|
|
76
|
+
```mermaid
|
|
77
|
+
sequenceDiagram
|
|
78
|
+
autonumber
|
|
79
|
+
participant E as Executor
|
|
80
|
+
participant C as Native Codex task API
|
|
81
|
+
participant M as TaskChef MCP
|
|
82
|
+
participant W as workspace.js
|
|
83
|
+
E->>C: Exact read of linked executor
|
|
84
|
+
C-->>E: Current turn ID
|
|
85
|
+
E->>M: report_state(..., working, null)
|
|
86
|
+
M->>W: reportTaskState()
|
|
87
|
+
W->>W: Store current turn and preserve lastResult
|
|
88
|
+
E->>E: Work, finish, or reach semantic decision
|
|
89
|
+
E->>M: report_state(..., semantic status, summary)
|
|
90
|
+
M->>W: reportTaskState()
|
|
91
|
+
W->>W: Lock and validate identity and freshness
|
|
92
|
+
alt Same current working turn
|
|
93
|
+
W->>W: Store semantic state and lastResult
|
|
94
|
+
W-->>M: Updated task
|
|
95
|
+
M-->>E: Recorded result
|
|
96
|
+
else Same turn and same result
|
|
97
|
+
W-->>M: Existing task
|
|
98
|
+
M-->>E: Idempotent success
|
|
99
|
+
else Conflict or stale turn
|
|
100
|
+
W-->>M: Tool error
|
|
101
|
+
M-->>E: Visible failure
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A native approval prompt is not a semantic result. `needs_input` is reserved
|
|
106
|
+
for a user decision or fact required to proceed.
|
|
107
|
+
|
|
108
|
+
## Follow-up turns
|
|
109
|
+
|
|
110
|
+
Turn IDs are freshness tokens for semantic callbacks. Lexical UUIDv7 order lets
|
|
111
|
+
the workspace reject a callback from an older executor turn.
|
|
112
|
+
|
|
113
|
+
```mermaid
|
|
114
|
+
sequenceDiagram
|
|
115
|
+
autonumber
|
|
116
|
+
actor U as User
|
|
117
|
+
participant E as Linked executor
|
|
118
|
+
participant C as Native Codex task API
|
|
119
|
+
participant M as TaskChef MCP
|
|
120
|
+
participant W as workspace.js
|
|
121
|
+
E->>M: report_state(..., turnA, needs_input, summaryA)
|
|
122
|
+
M->>W: reportTaskState()
|
|
123
|
+
W-->>M: needs_input snapshot
|
|
124
|
+
M-->>E: needs_input snapshot
|
|
125
|
+
U->>E: Provide decision
|
|
126
|
+
E->>C: Read exact executor after follow-up
|
|
127
|
+
C-->>E: turnB
|
|
128
|
+
E->>M: report_state(..., turnB, working, null)
|
|
129
|
+
M->>W: reportTaskState()
|
|
130
|
+
W->>W: Require turnB greater and preserve result A
|
|
131
|
+
W-->>M: working snapshot plus lastResult A
|
|
132
|
+
M-->>E: working snapshot plus lastResult A
|
|
133
|
+
E->>M: report_state(..., turnB, completed, summaryB)
|
|
134
|
+
M->>W: reportTaskState()
|
|
135
|
+
W-->>M: completed snapshot plus result B
|
|
136
|
+
M-->>E: completed snapshot plus result B
|
|
137
|
+
E->>M: report_state(..., turnA, completed, staleSummary)
|
|
138
|
+
M->>W: Validate freshness
|
|
139
|
+
W-->>M: Error: turn is not newer
|
|
140
|
+
M-->>E: Visible tool error
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The executor contract therefore requires a new exact read on every follow-up;
|
|
144
|
+
cached or inherited turn IDs are invalid.
|
|
145
|
+
|
|
146
|
+
## Link-pending and failure paths
|
|
147
|
+
|
|
148
|
+
A failed or interrupted link never authorizes substantive work. The record
|
|
149
|
+
remains a visible retry point for the same executor.
|
|
150
|
+
|
|
151
|
+
```mermaid
|
|
152
|
+
sequenceDiagram
|
|
153
|
+
autonumber
|
|
154
|
+
participant E as Executor
|
|
155
|
+
participant M as TaskChef MCP
|
|
156
|
+
participant W as workspace.js
|
|
157
|
+
E->>M: link_task(taskId, assertedThreadId)
|
|
158
|
+
M->>W: linkTask()
|
|
159
|
+
alt Exact eligible record and unused canonical UUIDv7
|
|
160
|
+
W->>W: Atomic null-to-thread transition
|
|
161
|
+
W-->>E: Linked task
|
|
162
|
+
else MCP unavailable or call interrupted
|
|
163
|
+
M--xE: Visible failure
|
|
164
|
+
Note over E,W: Record remains link-pending
|
|
165
|
+
else Wrong task, marker, state, or identity
|
|
166
|
+
W-->>E: Validation error
|
|
167
|
+
Note over E,W: No mutation
|
|
168
|
+
end
|
|
169
|
+
E->>E: Retry linking on a later turn before work
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If `CODEX_THREAD_ID` is missing, the executor reports the problem visibly and
|
|
173
|
+
does not substitute `CODEX_SESSION_ID`, a parent ID, or search results.
|
|
174
|
+
|
|
175
|
+
Native creation can fail after the durable record exists:
|
|
176
|
+
|
|
177
|
+
```mermaid
|
|
178
|
+
sequenceDiagram
|
|
179
|
+
autonumber
|
|
180
|
+
participant D as Dispatcher
|
|
181
|
+
participant M as TaskChef MCP
|
|
182
|
+
participant W as workspace.js
|
|
183
|
+
participant C as Native Codex
|
|
184
|
+
D->>M: record_task(..., threadId null)
|
|
185
|
+
M->>W: Append working record
|
|
186
|
+
W-->>D: Recorded task
|
|
187
|
+
D->>C: Create executor
|
|
188
|
+
C--xD: Creation error
|
|
189
|
+
D->>M: report_state(taskId, null, null, failed, boundedSummary)
|
|
190
|
+
M->>W: Lock and store creation failure
|
|
191
|
+
W-->>D: Failed task with null IDs
|
|
192
|
+
D-->>D: Preserve original creation error and task ID
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The summary is bounded and excludes secrets, transcripts, and raw command
|
|
196
|
+
output. A creation-failure record cannot later be linked.
|
|
197
|
+
|
|
198
|
+
## Dashboard update flow
|
|
199
|
+
|
|
200
|
+
Every mutation rewrites `tasks.jsonl` atomically under the workspace lock.
|
|
201
|
+
The monitor tolerates replacement races, validates a complete snapshot, and
|
|
202
|
+
publishes only the newest state to each SSE client.
|
|
203
|
+
|
|
204
|
+
```mermaid
|
|
205
|
+
sequenceDiagram
|
|
206
|
+
autonumber
|
|
207
|
+
participant M as MCP writer
|
|
208
|
+
participant W as workspace.js
|
|
209
|
+
participant F as tasks.jsonl
|
|
210
|
+
participant D as Dashboard monitor
|
|
211
|
+
participant B as Browser client
|
|
212
|
+
participant C as Native Codex
|
|
213
|
+
M->>W: link_task or report_state
|
|
214
|
+
W->>W: Acquire shared lock
|
|
215
|
+
W->>F: Atomic replacement
|
|
216
|
+
W-->>M: Updated task
|
|
217
|
+
F-->>D: Filesystem change
|
|
218
|
+
D->>F: Bounded read from one descriptor
|
|
219
|
+
D->>D: Validate current schema and sort
|
|
220
|
+
D-->>B: SSE snapshot
|
|
221
|
+
B->>D: Open task action
|
|
222
|
+
alt Canonical Codex UUIDv7
|
|
223
|
+
D->>C: Direct thread navigation
|
|
224
|
+
else Null or non-native durable ID
|
|
225
|
+
D->>D: Revalidate current project configuration
|
|
226
|
+
D->>C: Open configured project
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The dashboard binds to `127.0.0.1`, has no shared session state, limits
|
|
231
|
+
request bodies, and checks origin/authority for stateful local actions. Historical
|
|
232
|
+
project paths are untrusted until matched against current configuration.
|
|
233
|
+
|
|
234
|
+
## Concurrency and trust boundaries
|
|
235
|
+
|
|
236
|
+
Configuration and task mutations use the same `proper-lockfile` lock. ID and
|
|
237
|
+
identity uniqueness checks, link eligibility, and result freshness occur
|
|
238
|
+
inside the critical section. Writes use temporary files/hard links and atomic
|
|
239
|
+
rename so readers see complete snapshots.
|
|
240
|
+
|
|
241
|
+
The dispatcher controls routing and immutable intent. The executor controls its
|
|
242
|
+
cooperatively asserted identity and semantic result. Neither identity nor
|
|
243
|
+
summary is cryptographically authenticated; this is a local single-user trust
|
|
244
|
+
model. Managed files, instructions, project snapshots, MCP inputs, and dashboard
|
|
245
|
+
requests are validated at every action boundary.
|
|
246
|
+
|
|
247
|
+
Configuration schema 2 and task schemas 4 and 5 are accepted. Schema 4 is
|
|
248
|
+
read-only compatibility until a lifecycle mutation upgrades that record to
|
|
249
|
+
schema 5. Other schemas are rejected without rewrite.
|
package/index.js
CHANGED
|
@@ -5,7 +5,6 @@ export {
|
|
|
5
5
|
canonicalGitRoot,
|
|
6
6
|
doctorWorkspace,
|
|
7
7
|
ensureWorkspaceInstructions,
|
|
8
|
-
ensureWorkspaceSkills,
|
|
9
8
|
filterTasks,
|
|
10
9
|
importProjects,
|
|
11
10
|
initializeWorkspace,
|
|
@@ -16,8 +15,8 @@ export {
|
|
|
16
15
|
listTasks,
|
|
17
16
|
readTask,
|
|
18
17
|
recordTask,
|
|
18
|
+
reportTaskState,
|
|
19
19
|
reportTaskResult,
|
|
20
|
-
resolveTask,
|
|
21
20
|
requireSafeId,
|
|
22
21
|
removeProject,
|
|
23
22
|
validateConfig,
|
|
@@ -33,6 +32,7 @@ export {
|
|
|
33
32
|
EXECUTOR_OWNERSHIP_PARAGRAPH,
|
|
34
33
|
EXECUTOR_LINK_PARAGRAPH,
|
|
35
34
|
EXECUTOR_RESULT_PARAGRAPH,
|
|
35
|
+
EXECUTOR_WORKING_PARAGRAPH,
|
|
36
36
|
createAndRecordDelegation,
|
|
37
37
|
isProvisionalThreadId,
|
|
38
38
|
normalizeCodexThreadId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taskchef",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "A non-blocking interactive dispatcher for visible Codex tasks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Favo Yang",
|
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
"assets",
|
|
24
24
|
"BACKLOG.md",
|
|
25
25
|
"bin",
|
|
26
|
-
"docs/
|
|
26
|
+
"docs/spec.md",
|
|
27
|
+
"docs/workflows.md",
|
|
28
|
+
"docs/firstmate-taskchef-comparison.md",
|
|
27
29
|
"index.js",
|
|
28
30
|
"mcp",
|
|
29
|
-
"SPEC.md",
|
|
30
31
|
"scripts/benchmark-dispatch-prepare.js",
|
|
31
32
|
"scripts/e2e-benchmark.js",
|
|
32
33
|
"src",
|
|
@@ -33,10 +33,10 @@ function measure(operation) {
|
|
|
33
33
|
return performance.now() - startedAt;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const
|
|
36
|
+
const baseline = [];
|
|
37
37
|
const prepared = [];
|
|
38
38
|
for (let index = 0; index < sampleCount; index += 1) {
|
|
39
|
-
|
|
39
|
+
baseline.push(measure(() => {
|
|
40
40
|
runTaskChef(["workspace", "path", "--json", "--workspace", workspace]);
|
|
41
41
|
runTaskChef(["project", "list", "--json", "--workspace", workspace]);
|
|
42
42
|
execFileSync(process.execPath, [
|
|
@@ -66,15 +66,15 @@ function statistics(samples) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const
|
|
69
|
+
const baselineStats = statistics(baseline);
|
|
70
70
|
const preparedStats = statistics(prepared);
|
|
71
71
|
process.stdout.write(`${JSON.stringify({
|
|
72
72
|
schemaVersion: 1,
|
|
73
73
|
comparison: {
|
|
74
|
-
|
|
74
|
+
baseline: {
|
|
75
75
|
description: "workspace path + project list + external UUID/timestamp process",
|
|
76
76
|
processCalls: 3,
|
|
77
|
-
...
|
|
77
|
+
...baselineStats,
|
|
78
78
|
},
|
|
79
79
|
dispatchPrepare: {
|
|
80
80
|
description: "dispatch prepare",
|
|
@@ -82,9 +82,9 @@ process.stdout.write(`${JSON.stringify({
|
|
|
82
82
|
...preparedStats,
|
|
83
83
|
},
|
|
84
84
|
savedProcessCalls: 2,
|
|
85
|
-
medianSpeedup: Number((
|
|
85
|
+
medianSpeedup: Number((baselineStats.medianMs / preparedStats.medianMs).toFixed(2)),
|
|
86
86
|
medianReductionPercent: Number(
|
|
87
|
-
((1 - preparedStats.medianMs /
|
|
87
|
+
((1 - preparedStats.medianMs / baselineStats.medianMs) * 100).toFixed(1),
|
|
88
88
|
),
|
|
89
89
|
},
|
|
90
90
|
}, null, 2)}\n`);
|
|
@@ -15,8 +15,8 @@ all deterministic workspace operations.
|
|
|
15
15
|
## Boundaries
|
|
16
16
|
|
|
17
17
|
- Keep implementation, tests, and reports in the TaskChef source repository.
|
|
18
|
-
-
|
|
19
|
-
workspace.
|
|
18
|
+
- Create and manage only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in a
|
|
19
|
+
dispatcher workspace. Preserve unrelated user-owned paths.
|
|
20
20
|
- Do not dispatch tasks or report on executor threads during bootstrap unless
|
|
21
21
|
the user separately requests those actions.
|
|
22
22
|
- Never create hooks, schedules, polling, or daemons. TaskChef executors
|
|
@@ -37,9 +37,9 @@ all deterministic workspace operations.
|
|
|
37
37
|
CLI discovered from the current desktop environment; never invoke
|
|
38
38
|
`codex add` or hard-code an application bundle path.
|
|
39
39
|
3. `workspace init` takes no stdin, creates an empty
|
|
40
|
-
configuration when missing, creates the one-entry-per-task JSONL log,
|
|
41
|
-
managed instructions
|
|
42
|
-
|
|
40
|
+
configuration when missing, creates the one-entry-per-task JSONL log, and
|
|
41
|
+
refreshes managed instructions. The installed plugin provides all three
|
|
42
|
+
TaskChef skills outside the dispatcher workspace.
|
|
43
43
|
4. Run `doctor --json` after setup or when the user asks to diagnose the
|
|
44
44
|
workspace. Doctor is read-only. Rerun `workspace init --json` to repair the
|
|
45
45
|
managed scaffold.
|
|
@@ -86,6 +86,5 @@ Example managed-workspace import entry:
|
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`githubRepos: []`. All subsequent configuration writes use schema version 2.
|
|
89
|
+
TaskChef accepts configuration schema version 2 only. `githubRepos` is always
|
|
90
|
+
an array; unsupported configuration is rejected without being rewritten.
|
|
@@ -17,14 +17,15 @@ because it concerns TaskChef or a configured project. Explicit requests to
|
|
|
17
17
|
delegate separate work remain valid.
|
|
18
18
|
|
|
19
19
|
Use the bundled `prepare_dispatch`, `record_task`, `link_task`, and
|
|
20
|
-
`
|
|
20
|
+
`report_state` MCP tools directly. `report_result` is a deprecated compatibility
|
|
21
|
+
alias and must not be emitted into new executor instructions. Never fall back to shell writes. If a
|
|
21
22
|
required tool is unavailable, stop and report that the TaskChef plugin must be
|
|
22
23
|
reloaded or installed.
|
|
23
24
|
|
|
24
25
|
## Boundaries
|
|
25
26
|
|
|
26
|
-
-
|
|
27
|
-
|
|
27
|
+
- Treat only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` as TaskChef-managed
|
|
28
|
+
dispatcher files. Preserve unrelated user-owned paths.
|
|
28
29
|
- Use real Codex tasks, never collaboration or subagent tools.
|
|
29
30
|
- Never use hooks, schedules, daemons, background monitors, recent-task
|
|
30
31
|
searches, transcripts, hidden reasoning, or polling for identity.
|
|
@@ -49,7 +50,9 @@ reloaded or installed.
|
|
|
49
50
|
|
|
50
51
|
> Before any other work, read this executor's own durable Codex thread ID from the current task's CODEX_THREAD_ID environment value and call the TaskChef link_task MCP tool with that thread ID and the marked TaskChef task ID. Never use CODEX_SESSION_ID or the parent or delegator thread ID. If linking fails, CODEX_THREAD_ID is unavailable, or the tool is unavailable, report the failure visibly and retry on a later turn; do not guess an identity or continue substantive work while the task is link-pending.
|
|
51
52
|
|
|
52
|
-
>
|
|
53
|
+
> After a successful initial link, and at the start of every follow-up turn before substantive work, read this exact Codex thread natively to obtain the current turn ID and call TaskChef report_state with the marked task ID, the self-linked thread ID, that current turn ID, status working, and summary omitted or null. link_task remains the first TaskChef action on the initial turn; do not report working before identity is linked. Never reuse a prior turn ID after a follow-up.
|
|
54
|
+
|
|
55
|
+
> Before ending, read this exact Codex thread again and call TaskChef report_state for the same current working turn with status completed, needs_input, or failed and a concise summary. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.
|
|
53
56
|
|
|
54
57
|
5. Before creating each executor, call `record_task` exactly once with `id`,
|
|
55
58
|
`project`, `title`, the exact marked `instruction`, and `threadId: null`.
|
|
@@ -58,7 +61,7 @@ reloaded or installed.
|
|
|
58
61
|
7. Return immediately. Preserve a returned provisional client ID only for the
|
|
59
62
|
created-thread directive. Do not call `link_task` from the dispatcher even
|
|
60
63
|
when creation returns a durable ID; the child must self-link.
|
|
61
|
-
8. If creation fails after recording, call `
|
|
64
|
+
8. If creation fails after recording, call `report_state` with `failed`, null
|
|
62
65
|
thread/turn IDs, and a bounded summary before returning the failure.
|
|
63
66
|
|
|
64
67
|
## Executor contract
|
|
@@ -71,17 +74,12 @@ Identical retries are safe. A rejected link, unavailable tool, or interrupted
|
|
|
71
74
|
initial turn leaves the record visibly link-pending and retryable; the executor
|
|
72
75
|
must not guess or do substantive work first.
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
After linking on the initial turn, and before substantive work on every later
|
|
78
|
+
turn, the executor reads the exact thread and calls `report_state` with
|
|
79
|
+
`working`, the current turn ID, and no summary. Before ending that same turn it
|
|
80
|
+
reports a semantic state with the same turn ID and a summary. A follow-up must
|
|
81
|
+
use the new turn ID. `needs_input` is only for a real user decision, not live
|
|
82
|
+
approval UI.
|
|
78
83
|
|
|
79
|
-
The filesystem watcher surfaces `link_task` and `
|
|
84
|
+
The filesystem watcher surfaces `link_task` and `report_state` writes to the
|
|
80
85
|
dashboard. The linked child ID drives the exact Codex deep link.
|
|
81
|
-
|
|
82
|
-
## Legacy recovery
|
|
83
|
-
|
|
84
|
-
`taskchef task resolve` exists only for unresolved records created before the
|
|
85
|
-
self-linking schema. Require an exact marker match and a unique durable child
|
|
86
|
-
ID. The command rejects new self-linking records. Never edit `tasks.jsonl`
|
|
87
|
-
directly.
|
|
@@ -29,8 +29,8 @@ all deterministic task-log operations.
|
|
|
29
29
|
- Use the full list only when the user asks for an overview of the task history.
|
|
30
30
|
2. For an overview, select only attention-worthy candidates before detailed
|
|
31
31
|
reads:
|
|
32
|
-
- always include `working`, `needs_input`,
|
|
33
|
-
|
|
32
|
+
- always include `working`, `needs_input`, and entries with a null
|
|
33
|
+
`threadId`;
|
|
34
34
|
- include `completed` and `failed` entries updated during the last seven
|
|
35
35
|
days;
|
|
36
36
|
- omit older terminal entries by default and report the omitted count;
|
|
@@ -44,43 +44,39 @@ all deterministic task-log operations.
|
|
|
44
44
|
detailed read. Native approval is live Codex state, not a `needs_input`
|
|
45
45
|
callback. An inactive status never proves semantic completion; it only
|
|
46
46
|
permits a trustworthy cached MCP result to stand.
|
|
47
|
-
4.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
read every idle terminal task in an overview merely because native
|
|
47
|
+
4. In schema 5, treat `status`, `turnId`, and `updatedAt` as the latest reported
|
|
48
|
+
execution state and treat `lastResult` as the separately preserved semantic
|
|
49
|
+
result. A `working` state with a non-null `lastResult` means a newer executor
|
|
50
|
+
turn started after that result; show the prior result as history, not as the
|
|
51
|
+
current outcome. Treat a failed `lastResult` with null thread and turn IDs as
|
|
52
|
+
a fresh executor-creation failure. No live read is possible or needed.
|
|
53
|
+
Schema 4 snapshots normalize a structurally complete result into
|
|
54
|
+
`lastResult` without rewriting their log line. When identity is certain and
|
|
55
|
+
metadata says the thread is inactive, trust the latest semantic result by
|
|
56
|
+
default in a broad overview unless a newer working state makes it historical.
|
|
57
|
+
Do not read every idle terminal task in an overview merely because native
|
|
58
58
|
`updatedAt` is later: callbacks normally run before Codex finalizes the same
|
|
59
59
|
turn, and overview performance matters more than investigating every rare
|
|
60
60
|
missed callback.
|
|
61
61
|
|
|
62
62
|
For a focused task, title, or project report, perform at most one detailed
|
|
63
63
|
read for each selected inactive task when matched metadata `updatedAt` is
|
|
64
|
-
later than
|
|
64
|
+
later than `lastResult.updatedAt`, by any amount. Read once as well
|
|
65
65
|
when there is no semantic callback, identity or metadata is uncertain or
|
|
66
66
|
contradictory, or the user explicitly requests a fully live result. If
|
|
67
67
|
focused metadata is not newer, trust the cache. Absence from the bounded
|
|
68
68
|
recent snapshot is not by itself a reason to read every cached terminal
|
|
69
69
|
overview entry. Batch immediate native reads with no more than eight targets
|
|
70
70
|
per call. When a detailed read occurs, compare the latest structured turn ID
|
|
71
|
-
and native turn state with
|
|
71
|
+
and native turn state with `lastResult.turnId`: a newer turn without a callback
|
|
72
72
|
makes the cache stale, while an interrupted or cancelled callback turn
|
|
73
73
|
cannot prove completion. Never classify assistant prose.
|
|
74
74
|
5. Report each task as one of: working, needs input, awaiting native approval,
|
|
75
75
|
completed, failed, unresolved, or unknown. Show the cached summary when it
|
|
76
76
|
remains fresh. If a newer turn exists without a callback, describe the live
|
|
77
|
-
state and label the
|
|
78
|
-
6. Never edit `tasks.jsonl` directly during reporting.
|
|
79
|
-
|
|
80
|
-
link-pending and must be retried by that executor, while a schema 1-3 null
|
|
81
|
-
identity is a legacy recovery candidate. Manual recovery may call
|
|
82
|
-
`task resolve` only for the latter after one exact structured marker match.
|
|
83
|
-
Never persist inferred status,
|
|
77
|
+
state and label the preserved result historical or stale rather than overwriting it.
|
|
78
|
+
6. Never edit `tasks.jsonl` directly during reporting. A null identity is
|
|
79
|
+
executor link-pending and must be retried by that executor. Never persist inferred status,
|
|
84
80
|
transcripts, prose classifications, or hidden reasoning. Do not poll or wait.
|
|
85
81
|
|
|
86
82
|
If the task history is empty, say that TaskChef has not recorded any tasks. If
|
package/src/cli.js
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
recordTask,
|
|
19
19
|
removeProject,
|
|
20
20
|
requireSafeId,
|
|
21
|
-
resolveTask,
|
|
22
21
|
} from "./workspace.js";
|
|
23
22
|
|
|
24
23
|
const BLANK_TABLE_CELL = Symbol("blank table cell");
|
|
@@ -167,18 +166,22 @@ function singleLineDetail(value) {
|
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
function taskDetails(task) {
|
|
169
|
+
const lastResult = task.lastResult;
|
|
170
170
|
return [
|
|
171
171
|
`Title: ${singleLineDetail(task.title)}`,
|
|
172
172
|
`Project: ${singleLineDetail(task.project.name)}`,
|
|
173
|
-
`
|
|
174
|
-
`
|
|
173
|
+
`Current status: ${singleLineDetail(task.status ?? "unknown")}`,
|
|
174
|
+
`Current turn ID: ${singleLineDetail(task.turnId ?? "-")}`,
|
|
175
|
+
`Last result status: ${singleLineDetail(lastResult?.status ?? "-")}`,
|
|
176
|
+
`Last result summary: ${singleLineDetail(lastResult?.summary ?? "-")}`,
|
|
177
|
+
`Last result turn ID: ${singleLineDetail(lastResult?.turnId ?? "-")}`,
|
|
178
|
+
`Last result updated: ${singleLineDetail(lastResult?.updatedAt ?? "-")}`,
|
|
175
179
|
`Project path: ${singleLineDetail(task.project.path)}`,
|
|
176
180
|
`Created: ${singleLineDetail(task.createdAt)}`,
|
|
177
181
|
`Updated: ${singleLineDetail(task.updatedAt ?? "-")}`,
|
|
178
182
|
`Updated by: ${singleLineDetail(task.updatedBy ?? "-")}`,
|
|
179
183
|
`Task ID: ${singleLineDetail(task.id)}`,
|
|
180
184
|
`Thread ID: ${singleLineDetail(task.threadId ?? "-")}`,
|
|
181
|
-
`Turn ID: ${singleLineDetail(task.turnId ?? "-")}`,
|
|
182
185
|
"Instruction:",
|
|
183
186
|
task.instruction,
|
|
184
187
|
].join("\n");
|
|
@@ -241,7 +244,6 @@ async function initialize(args) {
|
|
|
241
244
|
`Configuration: ${value.config.action}`,
|
|
242
245
|
`Task log: ${value.tasks.action}`,
|
|
243
246
|
`Instructions: ${value.instructions.action}`,
|
|
244
|
-
`Legacy skill links removed: ${value.legacySkills.removed.length}`,
|
|
245
247
|
...(value.registration ? [`Codex opening: ${value.registration.status}`] : []),
|
|
246
248
|
].join("\n"));
|
|
247
249
|
return registrationFailed ? 5 : 0;
|
|
@@ -353,22 +355,6 @@ async function taskRecord(args) {
|
|
|
353
355
|
return 0;
|
|
354
356
|
}
|
|
355
357
|
|
|
356
|
-
async function taskResolve(args) {
|
|
357
|
-
if (!args[2] || args[2].startsWith("--")) throw new Error("task resolve requires a task ID");
|
|
358
|
-
validateCommandArgs(args, 3, {
|
|
359
|
-
values: ["--thread-id", "--workspace"],
|
|
360
|
-
switches: ["--json"],
|
|
361
|
-
});
|
|
362
|
-
if (!args.includes("--thread-id")) throw new Error("task resolve requires --thread-id");
|
|
363
|
-
const task = await resolveTask(
|
|
364
|
-
workspaceRoot(args),
|
|
365
|
-
args[2],
|
|
366
|
-
option(args, "--thread-id"),
|
|
367
|
-
);
|
|
368
|
-
print(task, args, (value) => `Resolved ${value.id}: ${value.threadId}`);
|
|
369
|
-
return 0;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
358
|
async function taskShow(args) {
|
|
373
359
|
validateCommandArgs(args, 3, { values: ["--workspace"], switches: ["--json"] });
|
|
374
360
|
print(await readTaskForShow(workspaceRoot(args), args[2]), args, taskDetails);
|
|
@@ -464,13 +450,11 @@ Usage:
|
|
|
464
450
|
taskchef project remove <name> [--json] [--workspace <path>]
|
|
465
451
|
taskchef dispatch prepare [--json] [--workspace <path>]
|
|
466
452
|
taskchef task record [--json] [--workspace <path>]
|
|
467
|
-
taskchef task resolve <legacy-task-id> --thread-id <thread-id> [--json] [--workspace <path>]
|
|
468
453
|
taskchef task show <task-id-or-8-character-prefix> [--json] [--workspace <path>]
|
|
469
454
|
taskchef task list [--project <name-or-path>] [--ascending] [--full-id] [--json] [--workspace <path>]
|
|
470
455
|
taskchef task summary [--json] [--workspace <path>]
|
|
471
456
|
|
|
472
457
|
Task record reads one JSON value from closed, non-interactive standard input.
|
|
473
|
-
Task resolve is a legacy migration command and rejects self-linking task records.
|
|
474
458
|
Task show accepts a full task ID or the exact 8-character ID printed by task list.
|
|
475
459
|
Task show prints human-readable details by default; --json prints the complete task object.
|
|
476
460
|
Project import reads a JSON
|
|
@@ -497,7 +481,6 @@ export async function runCli(args) {
|
|
|
497
481
|
if (args[0] === "project" && args[1] === "remove") return projectRemove(args);
|
|
498
482
|
if (args[0] === "dispatch" && args[1] === "prepare") return dispatchPrepare(args);
|
|
499
483
|
if (args[0] === "task" && args[1] === "record") return taskRecord(args);
|
|
500
|
-
if (args[0] === "task" && args[1] === "resolve") return taskResolve(args);
|
|
501
484
|
if (args[0] === "task" && args[1] === "show" && args[2]) return taskShow(args);
|
|
502
485
|
if (args[0] === "task" && args[1] === "list") return taskList(args);
|
|
503
486
|
if (args[0] === "task" && args[1] === "summary") return taskSummary(args);
|
package/src/dashboard/app.js
CHANGED
|
@@ -123,14 +123,18 @@ function openDialog(task) {
|
|
|
123
123
|
state.selectedTask = task;
|
|
124
124
|
elements.dialogProject.textContent = task.project.name;
|
|
125
125
|
elements.dialogTitle.textContent = task.title;
|
|
126
|
-
elements.dialogSummary.textContent = task.summary
|
|
126
|
+
elements.dialogSummary.textContent = task.lastResult?.summary
|
|
127
|
+
?? "No semantic result has been reported yet.";
|
|
127
128
|
elements.dialogInstruction.textContent = task.instruction;
|
|
128
129
|
elements.copyThreadId.disabled = !task.threadId;
|
|
129
130
|
elements.dialogMetadata.replaceChildren(
|
|
130
|
-
...detailRow("
|
|
131
|
+
...detailRow("Current status", taskStatusLabel(task)),
|
|
132
|
+
...detailRow("Current turn ID", task.turnId),
|
|
133
|
+
...detailRow("Last result status", task.lastResult?.status?.replaceAll("_", " ")),
|
|
134
|
+
...detailRow("Last result turn ID", task.lastResult?.turnId),
|
|
135
|
+
...detailRow("Last result updated", formatTime(task.lastResult?.updatedAt)),
|
|
131
136
|
...detailRow("Task ID", task.id),
|
|
132
137
|
...detailRow("Thread ID", task.threadId),
|
|
133
|
-
...detailRow("Turn ID", task.turnId),
|
|
134
138
|
...detailRow("Project path", task.project.path),
|
|
135
139
|
...detailRow("Created", formatTime(task.createdAt)),
|
|
136
140
|
...detailRow("Updated", formatTime(
|
|
@@ -160,7 +164,7 @@ function taskCard(task) {
|
|
|
160
164
|
project.textContent = task.project.name;
|
|
161
165
|
const summary = document.createElement("p");
|
|
162
166
|
summary.className = "task-summary";
|
|
163
|
-
summary.textContent = task.summary ?? "No semantic result reported yet.";
|
|
167
|
+
summary.textContent = task.lastResult?.summary ?? "No semantic result reported yet.";
|
|
164
168
|
const time = document.createElement("time");
|
|
165
169
|
time.dateTime = task.meaningfulUpdatedAt ?? task.updatedAt ?? task.createdAt;
|
|
166
170
|
time.textContent = `Updated ${formatTime(time.dateTime)}`;
|
package/src/dashboard/state.js
CHANGED
package/src/dashboard.js
CHANGED
|
@@ -131,6 +131,8 @@ function assertDashboardTaskBounds(tasks, maximumTasks) {
|
|
|
131
131
|
boundedText(task.summary, 2_000, `${name} summary`);
|
|
132
132
|
boundedText(task.threadId, 512, `${name} thread ID`);
|
|
133
133
|
boundedText(task.turnId, 512, `${name} turn ID`);
|
|
134
|
+
boundedText(task.lastResult?.summary, 2_000, `${name} last result summary`);
|
|
135
|
+
boundedText(task.lastResult?.turnId, 512, `${name} last result turn ID`);
|
|
134
136
|
boundedText(task.project.name, 1_000, `${name} project name`);
|
|
135
137
|
boundedText(task.project.path, 8_192, `${name} project path`);
|
|
136
138
|
boundedText(task.project.description, 4_000, `${name} project description`);
|
|
@@ -521,7 +523,7 @@ export async function createDashboardServer({
|
|
|
521
523
|
else await openWorkspaceInCodex(canonicalProjectPath);
|
|
522
524
|
sendJson(response, 202, {
|
|
523
525
|
message: task.threadId
|
|
524
|
-
? "Opened the project in Codex; this
|
|
526
|
+
? "Opened the project in Codex; this thread ID cannot use direct navigation."
|
|
525
527
|
: "Opened the project in Codex; this task does not yet have a thread ID.",
|
|
526
528
|
});
|
|
527
529
|
} catch {
|