taskchef 7.14.1 → 7.15.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "7.14.1",
3
+ "version": "7.15.0",
4
4
  "description": "Dispatch work from a data-only workspace to visible Codex project tasks.",
5
5
  "author": {
6
6
  "name": "Favo Yang",
package/README.md CHANGED
@@ -49,7 +49,7 @@ The canonical workspace is `~/.agents/taskchef`. TaskChef owns only:
49
49
  ```text
50
50
  AGENTS.md managed dispatcher instructions plus user additions
51
51
  taskchef.json schema-2 configured projects and routing metadata
52
- tasks.jsonl one task snapshot per line (schema 8; schema 4/5/6/7 migration supported)
52
+ tasks.jsonl one task snapshot per line (schema 9; schema 4-8 migration supported)
53
53
  ```
54
54
 
55
55
  List or change routing targets conversationally:
@@ -124,6 +124,10 @@ appends a `turns` entry that pairs that request with a null result while working
124
124
  then fills the same entry with the semantic outcome. A follow-up therefore shows
125
125
  its own request with “In progress,” never the preceding turn's result. Returned
126
126
  tasks still derive `results` and `lastResult` as compatibility projections.
127
+ Every entry has a required `turnRef`, which is the lifecycle identity. When
128
+ Codex exposes a native turn ID, both `turnRef` and `turnId` contain that value.
129
+ Otherwise the executor retains a fresh UUID in `turnRef` and stores
130
+ `turnId: null`; native turn lookup is never a prerequisite for work or reporting.
127
131
  If Codex crashes, an MCP call is lost, or the app restarts before that terminal
128
132
  report, the next newer `working` report atomically marks the unfinished turn
129
133
  `interrupted` and appends the new active turn. `interrupted` is TaskChef-authored
@@ -246,8 +250,9 @@ taskchef doctor
246
250
 
247
251
  `doctor` is read-only. `workspace init` creates missing files and refreshes
248
252
  managed instructions. `workspace migrate` explicitly upgrades supported schema
249
- 4/5/6/7 task lines to schema 8 under the workspace lock. It validates the complete
250
- source and converted log before writing, creates an exclusive `tasks.jsonl.pre-v8-*.bak`
253
+ 4-8 task lines to schema 9 under the workspace lock. It validates task and turn
254
+ counts plus the complete source and converted log before writing, creates an
255
+ exclusive `tasks.jsonl.pre-v9-*.bak`
251
256
  backup, atomically replaces the log, validates the result, and becomes an
252
257
  idempotent no-op after migration. If replacement fails, the original remains
253
258
  or the reported backup can be restored; unsupported or invalid input is rejected
@@ -256,9 +261,9 @@ before a backup or rewrite.
256
261
  If a new record has no thread ID, the executor is link-pending. Reopen that
257
262
  executor so its first action can retry `link_task`. Do not guess an identity
258
263
  or edit `tasks.jsonl`. If native task creation failed, the record is retained
259
- as `failed` with null thread and turn IDs.
264
+ as `failed` with a retained fallback `turnRef` and null thread and Codex turn IDs.
260
265
 
261
- Schemas other than 4, 5, 6, 7, and 8 remain unsupported. Retain such a workspace
266
+ Schemas other than 4, 5, 6, 7, 8, and 9 remain unsupported. Retain such a workspace
262
267
  unchanged and create a current workspace; the migration command deliberately
263
268
  does not guess how to convert unknown formats.
264
269
 
@@ -115,10 +115,10 @@ creation. The executor then reads its own `CODEX_THREAD_ID` and calls
115
115
  The task record keeps one immutable intent/project snapshot plus the latest
116
116
  identity and semantic result fields.
117
117
 
118
- **Fact:** Schema 4 linking accepts one atomic null-to-canonical-Codex-UUIDv7
119
- transition. Exact retries are idempotent, conflicts fail, and link interruption
120
- remains visibly pending. Follow-up semantic results require a newer exact
121
- native turn ID.
118
+ **Fact:** Linking accepts one atomic null-to-canonical-Codex-UUIDv7 transition.
119
+ Exact retries are idempotent, conflicts fail, and link interruption remains
120
+ visibly pending. Schema 9 lifecycle callbacks use a required `turnRef`: the
121
+ native Codex turn ID when available, or a retained UUID with `turnId: null`.
122
122
 
123
123
  ### FirstMate
124
124
 
@@ -162,7 +162,7 @@ backends have different verification or experimental status.
162
162
 
163
163
  **Fact:** Persisted statuses are `working`, `needs_input`, `completed`, and
164
164
  `failed`. Executors report
165
- `working` at turn start and a semantic state before ending. Schema 8 preserves
165
+ `working` at turn start and a semantic state before ending. Schema 9 preserves
166
166
  the paired turn timeline and semantic-only compatibility results while a newer
167
167
  turn is working. If a terminal report is lost, the next valid working start
168
168
  closes the predecessor with a timeline-only `interrupted` outcome rather than
package/docs/spec.md CHANGED
@@ -26,7 +26,8 @@ is dated research, not contract.
26
26
  | **Turn timeline** | The ordered collection pairing each turn's concise request summary with its semantic result, TaskChef-generated interruption outcome, or current in-progress state. |
27
27
  | **Interrupted turn** | A formerly active turn that lacked a terminal report when a newer valid turn started; TaskChef closes it with the fixed timeline-only `interrupted` outcome. |
28
28
  | **Last semantic result** | The final result-history entry, exposed through the derived `lastResult` compatibility alias. |
29
- | **Current turn ID** | The canonical Codex UUIDv7 returned by an exact native read of the linked executor for the turn being reported. |
29
+ | **Turn reference** | Required lifecycle identity for one executor prompt. It is the native Codex turn ID when available, otherwise a retained client-generated UUID. |
30
+ | **Current turn ID** | Optional Codex metadata for the reported prompt; null when native turn reading is unavailable. |
30
31
  | **Dashboard** | The loopback, read-only UI derived from validated workspace snapshots and bounded native actions. |
31
32
  | **Skill** | One packaged agent procedure: `taskchef-bootstrap`, `taskchef-delegate`, `taskchef-executor`, or `taskchef-copilot`. |
32
33
 
@@ -71,8 +72,8 @@ Repository URLs MUST canonicalize to `https://github.com/<owner>/<repository>`
71
72
  and be case-insensitively deduplicated.
72
73
 
73
74
  `tasks.jsonl` MUST contain zero or more newline-terminated schema-4 through
74
- schema-8 records, one per line. Schemas 4 through 7 are supported
75
- migration/read formats; every new record and state mutation MUST write schema 8. Other schemas
75
+ schema-9 records, one per line. Schemas 4 through 8 are supported
76
+ migration/read formats; every new record and state mutation MUST write schema 9. Other schemas
76
77
  or unsupported fields MUST be rejected without conversion.
77
78
  Reads and writes MUST reject symlinked managed files. Mutations
78
79
  MUST hold the shared workspace lock and replace state atomically; read-only
@@ -84,7 +85,7 @@ Every record MUST contain exactly these fields:
84
85
 
85
86
  | Field | Contract |
86
87
  | --- | --- |
87
- | `schemaVersion` | Integer `8`; schema-4/5/6/7 records remain readable until explicit migration or their next mutation. |
88
+ | `schemaVersion` | Integer `9`; schema-4/5/6/7/8 records remain readable until explicit migration or their next mutation. |
88
89
  | `id` | Unique safe TaskChef ID; delegation uses a lowercase full UUID. |
89
90
  | `project` | Immutable configured-project snapshot. |
90
91
  | `title` | Non-empty display title. |
@@ -93,16 +94,17 @@ Every record MUST contain exactly these fields:
93
94
  | `createdAt` | ISO 8601 creation timestamp. |
94
95
  | `status` | `working`, `needs_input`, `completed`, or `failed`. |
95
96
  | `summary` | Null while working; otherwise the current semantic state's non-empty summary of at most 2,000 characters. |
96
- | `turnId` | Null before turn reporting; otherwise the current reported turn. Linked MCP journeys use a canonical Codex UUIDv7. |
97
+ | `turnRef` | Null before reporting begins; otherwise the required lifecycle identity of the current turn. Self-linking journeys use a native or fallback UUID. Migrated low-level `report_result` compatibility records may retain an opaque ref equal to their opaque `turnId`. |
98
+ | `turnId` | Optional Codex metadata. When non-null it equals `turnRef`; null indicates fallback identity. |
97
99
  | `updatedAt` | ISO 8601 timestamp not earlier than `createdAt` or the prior `updatedAt`; clock rollback cannot backdate a transition. |
98
100
  | `updatedBy` | `dispatcher` or `mcp`. |
99
- | `turns` | Ordered oldest-first array of `{turnId, requestSummary, startedAt, result}`. `requestSummary` is null only for migrated/compatibility turns. `result` is null only for the latest working turn, a semantic `{status, summary, updatedAt}` result, or the TaskChef-generated `{status: "interrupted", summary: "Turn interrupted before a terminal report.", updatedAt}` outcome. Canonical self-linking turn IDs are unique. A migrated low-level opaque record may retain one final reused ID to represent its legacy completed-result-plus-working-state ambiguity. |
101
+ | `turns` | Ordered oldest-first array of `{turnRef, turnId, requestSummary, startedAt, result}`. Every `turnRef` is required. `requestSummary` is null only for migrated/compatibility turns. `result` is null only for the latest working turn, a semantic `{status, summary, updatedAt}` result, or the fixed TaskChef `interrupted` outcome. New self-linking turn refs are unique. A migrated low-level opaque record may retain one final reused native-derived ref for its legacy ambiguity. |
100
102
 
101
103
  Returned Task objects MUST additionally expose `latestTurn` as null for an empty
102
104
  timeline or the final `turns` entry. They MUST derive `results` only from
103
105
  semantic `needs_input`, `completed`, and `failed` turn results and `lastResult`
104
106
  from the final derived semantic result. Interrupted outcomes MUST be excluded.
105
- These projections MUST NOT be persisted in schema 8 and remain compatibility
107
+ These projections MUST NOT be persisted in schema 9 and remain compatibility
106
108
  aliases for existing callers.
107
109
 
108
110
  Task IDs and non-null thread identities MUST be unique. The immutable intent
@@ -129,12 +131,15 @@ the final link, preserving the delegate skill's immediate-return contract.
129
131
  5. It MUST create exactly one native Codex executor and return immediately.
130
132
  6. The executor MUST read its own `CODEX_THREAD_ID` and call `link_task`
131
133
  before substantive work. It MUST NOT use parent/session identity or guess.
132
- 7. After initial linking, the executor MUST exactly read its linked task and
133
- call `report_state` with that turn ID, `working`, and no summary before work.
134
- 8. Before ending, it MUST call `report_state` for the same working turn with a
134
+ 7. After initial linking, the executor MUST establish a `turnRef`. It MUST use
135
+ the native Codex turn ID for both `turnRef` and `turnId` when available;
136
+ otherwise it MUST retain a fresh UUID `turnRef` and use `turnId: null`.
137
+ It MUST call `report_state` with that identity, `working`, and no summary before work.
138
+ 8. Before ending, it MUST call `report_state` with the same `turnRef` and
139
+ `turnId` metadata as the working report and a
135
140
  semantic status and concise summary.
136
- 9. A follow-up MUST report `working` with its new current turn ID before work.
137
- It MUST NOT reuse a prior turn. If the preceding turn is still unfinished,
141
+ 9. A follow-up MUST report `working` with a new `turnRef` before work.
142
+ It MUST NOT reuse a prior prompt's ref. If the preceding turn is still unfinished,
138
143
  TaskChef MUST atomically close it as `interrupted` before appending the new
139
144
  working turn; the executor MUST NOT report semantic `failed` for recovery.
140
145
 
@@ -146,7 +151,8 @@ managed child repository and its workspace/root repository MUST include both
146
151
  pull-request URLs. Executors MUST NOT guess unresolved repository identity.
147
152
 
148
153
  If native creation fails after recording, the dispatcher MUST call
149
- `report_state` with `failed`, null thread/turn IDs, and a bounded summary.
154
+ `report_state` with `failed`, a retained fallback UUID `turnRef`, null thread
155
+ and Codex turn IDs, and a bounded summary.
150
156
  A link failure MUST remain visible and retryable; the executor MUST report it
151
157
  visibly and MUST NOT continue substantive work.
152
158
 
@@ -254,7 +260,7 @@ new preparation values, though it writes no state.
254
260
 
255
261
  **Structured output:** `{ task: Task }`.
256
262
 
257
- The returned task has schema 8, `working`, null summary/turn/thread/latestTurn/lastResult,
263
+ The returned task has schema 9, `working`, null summary/turn/thread/latestTurn/lastResult,
258
264
  empty `turns` and derived `results` arrays,
259
265
  `updatedBy: dispatcher`, and equal creation/update timestamps. Duplicate IDs,
260
266
  unknown projects, malformed markers, and invalid input fail. Repeating a
@@ -292,23 +298,29 @@ marker, or ineligible state fails.
292
298
  | --- | --- |
293
299
  | `taskId` | Non-empty string. |
294
300
  | `threadId` | Matching non-empty ID for a linked task; null only for creation failure. |
295
- | `turnId` | Current canonical Codex UUIDv7 for a linked MCP journey; null only for creation failure. Maximum 256 characters at the MCP boundary. |
301
+ | `turnRef` | Stable UUID for this prompt. It may be omitted only by legacy callers that supply a non-null `turnId`, in which case TaskChef derives the same value. |
302
+ | `turnId` | Optional Codex metadata. When present it MUST equal `turnRef`; null is valid for fallback UUID refs. Maximum 256 characters. |
296
303
  | `status` | `working`, `needs_input`, `completed`, or `failed`. |
297
304
  | `summary` | Omitted or null for `working`; required non-empty string of at most 2,000 characters otherwise. Known GitHub issues and pull requests use canonical URLs. |
298
305
  | `requestSummary` | Concise current request of at most 1,000 characters for `working`; optional for backward compatibility and omitted for semantic states. A known selected GitHub repository uses its canonical URL. |
299
306
 
300
307
  **Structured output:** `{ task: Task }`.
301
308
 
302
- For a linked self-linking journey, a new `working` state MUST identify a turn
303
- newer than the current turn and last semantic result. An exact retry of any
304
- already-recorded working start MUST return the current task without mutation.
309
+ For a linked self-linking journey, a new `working` state MUST use a `turnRef`
310
+ not already assigned to a different turn. An exact retry of the current
311
+ working start MUST return the current task without mutation.
312
+ When `turnId` is present, its native-backed `turnRef` MUST also be newer than
313
+ every previously stored native-backed ref. Fallback UUID refs are opaque and
314
+ MUST NOT be lexically ordered.
305
315
  A semantic state MUST match the current working turn. Conflicting or stale
306
- state fails. A null-identity record accepts only a fresh executor
307
- creation `failed` state with both IDs null. Starting work appends one turn with
316
+ state fails. A null-thread record accepts only a fresh executor creation
317
+ `failed` state with a retained `turnRef` and null thread/turn IDs. Starting work appends one turn with
308
318
  its request and a null result. When the previous latest turn is unfinished, the
309
319
  same locked atomic rewrite MUST first fill it with the fixed TaskChef-generated
310
320
  `interrupted` outcome. The semantic report fills the active turn's result.
311
- An identical retry for any settled turn returns success without an append. A
321
+ An identical retry for the current settled turn returns success without an
322
+ append. Once a newer turn starts, every callback for a historical ref is stale
323
+ and fails, even when its content exactly matches the stored callback. A
312
324
  different request or result for the same turn, a stale turn, or a semantic
313
325
  result that does not match the active working turn MUST fail.
314
326
 
@@ -325,8 +337,9 @@ MUST contain no crash output, transcript, user text, or inferred failure cause.
325
337
  temporary compatibility alias. It implicitly accepts a fresh supplied turn and
326
338
  stores its semantic result in a request-unknown turn, including for supported schema-4/5/6 records and
327
339
  low-level opaque direct records. It does not accept `working`. New executor
328
- instructions MUST use `report_state`. Successful mutation upgrades schema 4/5/6/7
329
- to schema 8; unsupported schemas remain rejected.
340
+ instructions MUST use `report_state`. Successful mutation upgrades schema 4-8
341
+ to schema 9; unsupported schemas remain rejected. Legacy callers that omit
342
+ `turnRef` remain compatible when `turnId` is non-null.
330
343
 
331
344
  ## Copilot and dashboard
332
345
 
@@ -377,10 +390,10 @@ Snapshot and SSE list payloads MUST omit full `turns` and derived `results`
377
390
  history and include `latestTurn`. The bounded per-task detail endpoint MAY
378
391
  return the full validated task so the dialog can render the paired timeline newest first.
379
392
  Dashboard notifications MUST capture an immutable event-time projection of the
380
- task title, lifecycle state and event, turn ID when present, event timestamp,
393
+ task title, lifecycle state and event, turn ref and optional Codex turn ID, event timestamp,
381
394
  and relevant concise summary. Rendering MUST NOT resolve historical notice text
382
395
  from the task's later current state. Notice identity and deduplication MUST use
383
- task ID, turn ID, and lifecycle event; a creation notice without a turn ID MUST
396
+ task ID, turn ref, and lifecycle event; a creation notice without a turn ref MUST
384
397
  fall back to task ID plus the immutable creation timestamp. Dashboard revision
385
398
  MUST NOT be the sole identity. Identical snapshots, SSE reconnects, idempotent
386
399
  reports, schema normalization, and non-semantic rewrites MUST NOT add notices.
@@ -407,13 +420,15 @@ its displayed summary when present, event time, and missing-task state.
407
420
 
408
421
  ## Task-log migration
409
422
 
410
- `workspace migrate` MUST explicitly convert every supported schema-4/5/6/7 record
423
+ `workspace migrate` MUST explicitly convert every supported schema-4/5/6/7/8 record
411
424
  under the shared lock. Each legacy semantic result becomes a request-unknown
412
425
  completed turn; a newer working state becomes a final unfinished turn, and a
413
- schema-7 timeline is preserved losslessly. Migration MUST validate the complete
414
- source and complete schema-8 candidate before changing the task log,
426
+ schema-7/8 timeline is preserved. Each non-null legacy `turnId` becomes the
427
+ same `turnRef`; each null legacy `turnId` receives one durably persisted UUID.
428
+ Migration MUST validate the complete source, record/turn counts, turn-ref
429
+ invariants, and complete schema-9 candidate before changing the task log,
415
430
  create and read back an exclusive recovery backup, atomically replace the log,
416
- and validate the installed result. A fully schema-8 log MUST be an idempotent
431
+ and validate the installed result. A fully schema-9 log MUST be an idempotent
417
432
  no-op without another backup. Invalid/unsupported input MUST remain untouched;
418
433
  failures after backup creation MUST report the backup path and MUST never
419
434
  partially rewrite individual lines.
package/docs/workflows.md CHANGED
@@ -89,7 +89,7 @@ sequenceDiagram
89
89
  D->>D: Choose one configured and native project
90
90
  D->>M: record_task(id, project, title, instruction, null)
91
91
  M->>W: recordTask()
92
- W->>W: Lock, validate, append schema-8 snapshot
92
+ W->>W: Lock, validate, append schema-9 snapshot
93
93
  W-->>M: working link-pending task
94
94
  M-->>D: task
95
95
  D->>C: Create executor with marked instruction
@@ -116,9 +116,10 @@ semantic callbacks.
116
116
 
117
117
  ## State reporting
118
118
 
119
- The executor obtains the turn identity from an exact native read of its own
120
- linked task. `report_state` records live turn state as a paired request/result
121
- timeline.
119
+ The executor always establishes a `turnRef` before work. An exact native read
120
+ supplies the same value as optional `turnId` metadata when available; otherwise
121
+ the executor retains a fresh UUID and reports `turnId: null`. `report_state`
122
+ records live turn state as a paired request/result timeline.
122
123
 
123
124
  That timeline is also the durable GitHub-link source. A working request names a
124
125
  known selected repository with its canonical GitHub URL. A terminal result
@@ -134,13 +135,16 @@ sequenceDiagram
134
135
  participant C as Native Codex task API
135
136
  participant M as TaskChef MCP
136
137
  participant W as workspace.js
137
- E->>C: Exact read of linked executor
138
- C-->>E: Current turn ID
139
- E->>M: report_state(..., working, requestSummary)
138
+ opt Native turn metadata available
139
+ E->>C: Exact read of linked executor
140
+ C-->>E: Current turn ID
141
+ end
142
+ E->>E: Retain native ID or generate fallback UUID turnRef
143
+ E->>M: report_state(..., turnRef, turnId, working, requestSummary)
140
144
  M->>W: reportTaskState()
141
145
  W->>W: Append turn with request and null result
142
146
  E->>E: Work, finish, or reach semantic decision
143
- E->>M: report_state(..., semantic status, summary)
147
+ E->>M: report_state(..., same turnRef, same turnId, semantic status, summary)
144
148
  M->>W: reportTaskState()
145
149
  W->>W: Lock and validate identity and freshness
146
150
  alt Same current working turn
@@ -161,8 +165,10 @@ for a user decision or fact required to proceed.
161
165
 
162
166
  ## Follow-up turns
163
167
 
164
- Turn IDs are freshness tokens for semantic callbacks. Lexical UUIDv7 order lets
165
- the workspace reject a callback from an older executor turn.
168
+ Turn refs are freshness tokens for semantic callbacks. The workspace rejects a
169
+ callback whose ref belongs to a historical turn. Fallback UUID refs are opaque
170
+ and unordered; native-backed UUIDv7 refs retain native ordering so a delayed,
171
+ previously unseen older start cannot replace newer native state.
166
172
 
167
173
  ```mermaid
168
174
  sequenceDiagram
@@ -181,7 +187,7 @@ sequenceDiagram
181
187
  C-->>E: turnB
182
188
  E->>M: report_state(..., turnB, working, requestB)
183
189
  M->>W: reportTaskState()
184
- W->>W: Require turnB greater and append request B
190
+ W->>W: Require a fresh turnB ref and append request B
185
191
  W-->>M: working snapshot plus paired timeline
186
192
  M-->>E: working snapshot plus paired timeline
187
193
  E->>M: report_state(..., turnB, completed, summaryB)
@@ -190,12 +196,13 @@ sequenceDiagram
190
196
  M-->>E: completed snapshot plus result B
191
197
  E->>M: report_state(..., turnA, completed, staleSummary)
192
198
  M->>W: Validate freshness
193
- W-->>M: Error: turn is not newer
199
+ W-->>M: Error: stale turnRef
194
200
  M-->>E: Visible tool error
195
201
  ```
196
202
 
197
- The executor contract therefore requires a new exact read on every follow-up;
198
- cached or inherited turn IDs are invalid.
203
+ The executor contract therefore requires a new `turnRef` on every follow-up.
204
+ Native turn reads are preferred metadata but optional; cached or inherited IDs
205
+ and replacement fallback UUIDs for the same prompt are invalid.
199
206
 
200
207
  ## Interrupted-turn recovery
201
208
 
@@ -211,12 +218,12 @@ sequenceDiagram
211
218
  participant M as TaskChef MCP
212
219
  participant W as workspace.js
213
220
  participant F as tasks.jsonl
214
- E->>M: report_state(..., turnB, working, requestB)
221
+ E->>M: report_state(..., turnRefB, working, requestB)
215
222
  M->>W: reportTaskState()
216
- W->>W: Acquire workspace lock and validate turnB > turnA
223
+ W->>W: Acquire workspace lock and validate turnRefB is new
217
224
  W->>W: Close unfinished turnA as interrupted
218
225
  W->>W: Append turnB with requestB and null result
219
- W->>F: One atomic schema-8 replacement
226
+ W->>F: One atomic schema-9 replacement
220
227
  W-->>M: working task projected from turnB
221
228
  M-->>E: Idempotent recovery success
222
229
  E->>M: late semantic result for turnA
@@ -277,10 +284,11 @@ sequenceDiagram
277
284
  W-->>D: Recorded task
278
285
  D->>C: Create executor
279
286
  C--xD: Creation error
280
- D->>M: report_state(taskId, null, null, failed, boundedSummary)
287
+ D->>D: Generate and retain fallback UUID turnRef
288
+ D->>M: report_state(taskId, null, turnRef, null, failed, boundedSummary)
281
289
  M->>W: Lock and store creation failure
282
- W-->>D: Failed task with null IDs
283
- D-->>D: Preserve original creation error and task ID
290
+ W-->>D: Failed task with turnRef and null native IDs
291
+ D-->>D: Preserve original creation error, task ID, and turnRef
284
292
  ```
285
293
 
286
294
  The summary is bounded and excludes secrets, transcripts, and raw command
@@ -331,8 +339,8 @@ against current configuration.
331
339
 
332
340
  The browser derives one immutable Updates-panel snapshot when it first observes
333
341
  a semantic lifecycle transition. The snapshot keeps its event-time title,
334
- state/event, turn ID, timestamp, and relevant summary. Its identity combines
335
- task ID, turn ID, and event, with creation time as the fallback when creation
342
+ state/event, turn ref, optional Codex turn ID, timestamp, and relevant summary.
343
+ Its identity combines task ID, turn ref, and event, with creation time as the fallback when creation
336
344
  has no turn. A separate seen-identity set outlives the bounded visible notices,
337
345
  individual dismissals, and Clear all, preventing replay after reconnect,
338
346
  normalization, or disappearance and reappearance. Per-task semantic signatures
@@ -351,15 +359,16 @@ all, and ordinary rerendering do not re-announce retained history. Toast action
351
359
  labels remain concise while `aria-describedby` connects the visible summary,
352
360
  event time, and missing-task explanation for assistive technology.
353
361
 
354
- ## Schema 4/5/6/7 migration
362
+ ## Schema 4-8 migration
355
363
 
356
364
  `taskchef workspace migrate` acquires the same workspace lock as lifecycle
357
365
  writers, validates the complete legacy log, converts schema-4/5/6 results into
358
366
  request-unknown completed turns and preserves a newer working turn, then validates the
359
367
  complete candidate. Before replacement it writes and reads back an exclusive
360
- `tasks.jsonl.pre-v8-*.bak` file. Schema-7 timelines are copied losslessly into
361
- schema 8. The task log is replaced atomically and validated again. A second run
362
- sees only schema 8 and returns unchanged without
368
+ `tasks.jsonl.pre-v9-*.bak` file. Schema-7/8 timelines gain a durable `turnRef`:
369
+ non-null `turnId` values are copied exactly and null IDs receive persisted UUIDs.
370
+ Task and turn counts plus all refs are validated before and after the atomic
371
+ replacement. A second run sees only schema 9 and returns unchanged without
363
372
  another backup. Unsupported or malformed input fails before backup/rewrite;
364
373
  after a later filesystem failure, the reported backup is the recovery source.
365
374
 
@@ -376,9 +385,9 @@ summary is cryptographically authenticated; this is a local single-user trust
376
385
  model. Managed files, instructions, project snapshots, MCP inputs, and dashboard
377
386
  requests are validated at every action boundary.
378
387
 
379
- Configuration schema 2 and task schemas 4, 5, 6, 7, and 8 are accepted. Schemas
380
- 4/5/6/7 are read/migration compatibility until an explicit migration or lifecycle
381
- mutation upgrades each record to schema 8. Schema 8 persists `turns`, including
388
+ Configuration schema 2 and task schemas 4 through 9 are accepted. Schemas
389
+ 4-8 are read/migration compatibility until an explicit migration or lifecycle
390
+ mutation upgrades each record to schema 9. Schema 9 persists `turns`, including
382
391
  timeline-only interrupted outcomes, and derives semantic-only `results` and
383
392
  `lastResult` plus `latestTurn` for compact compatibility. Other schemas
384
393
  are rejected without rewrite.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "7.14.1",
3
+ "version": "7.15.0",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
@@ -70,5 +70,7 @@ stop and report that the TaskChef plugin must be reloaded or installed.
70
70
  7. Return immediately. Preserve a returned provisional client ID only for the
71
71
  created-thread directive. Do not call `link_task` from the dispatcher even
72
72
  when creation returns a durable ID; the child must self-link.
73
- 8. If creation fails after recording, call `report_state` with `failed`, null
74
- thread/turn IDs, and a bounded summary before returning the failure.
73
+ 8. If creation fails after recording, generate a fresh UUID and call
74
+ `report_state` with it as `turnRef`, `failed`, null thread and Codex turn IDs,
75
+ and a bounded summary before returning the failure. Retain that UUID for an
76
+ exact retry.
@@ -30,10 +30,14 @@ Complete this lifecycle setup before substantive assignment work:
30
30
  that exact thread ID as the first TaskChef action. An identical retry is
31
31
  idempotent. On a follow-up, retry the same link only when the prior link
32
32
  cannot be established from the task context.
33
- 3. Read this exact Codex thread natively and obtain the current turn ID. Do not
34
- infer it or reuse an earlier turn ID.
33
+ 3. Establish this prompt's lifecycle identity before reporting. If this exact
34
+ Codex thread can be read natively and exposes the current turn ID, use that
35
+ exact value for both `turnRef` and `turnId`. Otherwise generate one fresh
36
+ UUID locally, retain it for this entire prompt, use it as `turnRef`, and use
37
+ `turnId: null`. Do not infer a native ID, reuse an earlier prompt's
38
+ `turnRef`, or let a retry generate a replacement UUID.
35
39
  4. Call TaskChef `report_state` with the marked task ID, self-linked thread ID,
36
- current turn ID, `status: working`, an omitted or null result summary, and a
40
+ this prompt's `turnRef` and optional Codex `turnId`, `status: working`, an omitted or null result summary, and a
37
41
  concise `requestSummary` describing this turn's assignment or follow-up.
38
42
  When the turn targets a known GitHub repository, include its canonical
39
43
  `https://github.com/<owner>/<repository>` URL so multi-repository projects
@@ -45,15 +49,17 @@ as interrupted and starts the current turn. Continue the real assignment from
45
49
  the current request. Do not manufacture a semantic `failed` result for the old
46
50
  turn and do not retry an old terminal report.
47
51
 
48
- If `CODEX_THREAD_ID`, exact native thread reading, or a required TaskChef tool
49
- is unavailable, or if linking or the working-state report fails, report the
50
- failure visibly and stop before substantive work. Retry on a later turn. Never
51
- guess identity or bypass a link-pending state.
52
+ If `CODEX_THREAD_ID` or a required TaskChef tool is unavailable, or if linking
53
+ or the working-state report fails, report the failure visibly and stop before
54
+ substantive work. Native turn reading is optional because the retained fallback
55
+ UUID is the lifecycle identity. Retry a possibly lost working callback with the
56
+ same `turnRef`; never generate a replacement for the same prompt or bypass a
57
+ link-pending state.
52
58
 
53
59
  ## Finish every execution turn
54
60
 
55
- Before ending, read this exact Codex thread again and call `report_state` for
56
- the current turn with one semantic status and a concise summary:
61
+ Before ending, call `report_state` with the same `turnRef` and `turnId` values
62
+ used by this prompt's working report, one semantic status, and a concise summary:
57
63
 
58
64
  - `completed` only when the assignment is genuinely complete.
59
65
  - `needs_input` only when a semantic decision or missing information must come
@@ -62,16 +68,16 @@ the current turn with one semantic status and a concise summary:
62
68
 
63
69
  A live native approval prompt is Codex state, not semantic `needs_input`; leave
64
70
  the approval live instead of storing it as a TaskChef result. Never invent or
65
- reuse a turn ID after a follow-up. If a final-report response is lost, an
71
+ reuse a `turnRef` after a follow-up. If a final-report response is lost, an
66
72
  identical terminal retry is safe only while the same turn remains current. On
67
- a later turn, run the start lifecycle with its new current turn ID; TaskChef
73
+ a later prompt, run the start lifecycle with a new `turnRef`; TaskChef
68
74
  will preserve the predecessor as interrupted, and only the new turn may receive
69
75
  a semantic result. Say reporting failures visibly instead of claiming a tracked
70
76
  outcome.
71
77
 
72
78
  Request and result summaries must omit secrets, transcripts, raw command output, hidden reasoning,
73
79
  and unnecessary personal data. Identical lifecycle retries are safe; never
74
- replace a same-turn report with different content or let an older turn
80
+ replace a same-`turnRef` report with different content or let an older turn
75
81
  overwrite newer state.
76
82
 
77
83
  ### Preserve repository and delivery links
package/src/cli.js CHANGED
@@ -173,10 +173,12 @@ function taskDetails(task) {
173
173
  `Title: ${singleLineDetail(task.title)}`,
174
174
  `Project: ${singleLineDetail(task.project.name)}`,
175
175
  `Current status: ${singleLineDetail(task.status ?? "unknown")}`,
176
- `Current turn ID: ${singleLineDetail(task.turnId ?? "-")}`,
176
+ `Current turn ref: ${singleLineDetail(task.turnRef ?? "-")}`,
177
+ `Current Codex turn ID: ${singleLineDetail(task.turnId ?? "-")}`,
177
178
  `Last result status: ${singleLineDetail(lastResult?.status ?? "-")}`,
178
179
  `Last result summary: ${singleLineDetail(lastResult?.summary ?? "-")}`,
179
- `Last result turn ID: ${singleLineDetail(lastResult?.turnId ?? "-")}`,
180
+ `Last result turn ref: ${singleLineDetail(lastResult?.turnRef ?? "-")}`,
181
+ `Last result Codex turn ID: ${singleLineDetail(lastResult?.turnId ?? "-")}`,
180
182
  `Last result updated: ${singleLineDetail(lastResult?.updatedAt ?? "-")}`,
181
183
  `Project path: ${singleLineDetail(task.project.path)}`,
182
184
  `Created: ${singleLineDetail(task.createdAt)}`,
@@ -187,7 +189,7 @@ function taskDetails(task) {
187
189
  `Turn count: ${task.turns.length}`,
188
190
  "Activity timeline (newest first):",
189
191
  ...[...task.turns].reverse().map((turn) => (
190
- `- ${singleLineDetail(turn.startedAt)} | ${singleLineDetail(turn.result?.status ?? "working")} | turn ${singleLineDetail(turn.turnId ?? "-")} | request: ${singleLineDetail(turn.requestSummary ?? "not recorded")} | result: ${singleLineDetail(turn.result?.summary ?? "in progress")}`
192
+ `- ${singleLineDetail(turn.startedAt)} | ${singleLineDetail(turn.result?.status ?? "working")} | ref ${singleLineDetail(turn.turnRef ?? "-")} | Codex turn ${singleLineDetail(turn.turnId ?? "-")} | request: ${singleLineDetail(turn.requestSummary ?? "not recorded")} | result: ${singleLineDetail(turn.result?.summary ?? "in progress")}`
191
193
  )),
192
194
  "Instruction:",
193
195
  task.instruction,
@@ -200,7 +202,10 @@ async function migrate(args) {
200
202
  print(result, args, (value) => [
201
203
  `Task log: ${value.action}`,
202
204
  `Tasks: ${value.taskCount}`,
205
+ `Turns: ${value.turnCount}`,
203
206
  `Migrated: ${value.migratedCount}`,
207
+ `Native turn refs: ${value.nativeTurnRefCount}`,
208
+ `Fallback turn refs: ${value.fallbackTurnRefCount}`,
204
209
  `Backup: ${value.backupPath ?? "not needed"}`,
205
210
  ].join("\n"));
206
211
  return 0;