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.
@@ -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": "6.1.3",
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/delegation-design.md",
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 legacy = [];
36
+ const baseline = [];
37
37
  const prepared = [];
38
38
  for (let index = 0; index < sampleCount; index += 1) {
39
- legacy.push(measure(() => {
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 legacyStats = statistics(legacy);
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
- legacy: {
74
+ baseline: {
75
75
  description: "workspace path + project list + external UUID/timestamp process",
76
76
  processCalls: 3,
77
- ...legacyStats,
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((legacyStats.medianMs / preparedStats.medianMs).toFixed(2)),
85
+ medianSpeedup: Number((baselineStats.medianMs / preparedStats.medianMs).toFixed(2)),
86
86
  medianReductionPercent: Number(
87
- ((1 - preparedStats.medianMs / legacyStats.medianMs) * 100).toFixed(1),
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
- - Keep only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in a dispatcher
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, refreshes
41
- managed instructions, and removes legacy TaskChef skill links. The installed
42
- plugin provides all three TaskChef skills outside the dispatcher workspace.
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
- `workspace init` safely migrates schema-version-1 configuration: a string
90
- `githubRepo` becomes a one-item `githubRepos` list and `null` becomes
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
- `report_result` MCP tools directly. Never fall back to shell writes. If a
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
- - Keep only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in the dispatcher
27
- workspace.
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
- > Before ending, call the TaskChef report_result MCP tool with the marked task ID, this executor's self-linked thread ID, the current turn ID from an exact native read of that same thread, completed, needs_input, or failed, and a concise summary. Never reuse a prior turn ID after a follow-up. 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
+ > 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 `report_result` with `failed`, null
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
- For every semantic result, the executor supplies its linked thread ID and the
75
- current turn ID obtained by reading that exact thread. A follow-up must use the
76
- new turn ID. Do not reuse the initial turn ID. `needs_input` is only for a real
77
- user decision, not live approval UI.
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 `report_result` writes to the
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`, null-status legacy entries, and
33
- entries with a null `threadId`;
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. Treat `updatedBy: mcp`, `status: failed`, and null thread/turn IDs as a fresh
48
- executor-creation failure. No live read is possible or needed; report the
49
- stored failure summary, not unresolved. Otherwise, only a snapshot with
50
- `updatedBy: mcp`, a result status, a non-null summary, and a non-null turn ID
51
- is a cached semantic result. Any `working` snapshot has no semantic
52
- callback, including a self-linked `updatedBy: mcp` snapshot, and requires
53
- one live task query when selected; if the task is inactive and no callback
54
- exists, report the outcome as unknown rather than treating `working` as
55
- fresh. When identity is certain and metadata says the thread is
56
- inactive, trust the latest MCP result by default in a broad overview. Do not
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 the cached result `updatedAt`, by any amount. Read once as well
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 stored `turnId`: a newer turn without a callback
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 cached result stale rather than overwriting it.
78
- 6. Never edit `tasks.jsonl` directly during reporting. The task API preserves
79
- each record's persisted `schemaVersion`: a schema 4 null identity is executor
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
- `Status: ${singleLineDetail(task.status ?? "unknown")}`,
174
- `Summary: ${singleLineDetail(task.summary ?? "-")}`,
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);
@@ -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 ?? "No semantic result has been reported yet.";
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("Status", taskStatusLabel(task)),
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)}`;
@@ -51,6 +51,7 @@ export function taskSignature(task) {
51
51
  task.turnId,
52
52
  task.updatedAt,
53
53
  task.updatedBy,
54
+ task.lastResult,
54
55
  ]);
55
56
  }
56
57
 
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 legacy thread ID cannot use direct navigation."
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 {