taskchef 7.19.1 → 7.21.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.19.1",
3
+ "version": "7.21.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
@@ -25,6 +25,12 @@ request -> recorded TaskChef task -> Codex executor -> request/result turn timel
25
25
  TaskChef requires Node.js 18 or newer, Git, Codex desktop, and local access to
26
26
  the projects that will receive work.
27
27
 
28
+ Install [`ccusage`](https://github.com/ccusage/ccusage) separately when you want
29
+ the optional dashboard token and API-equivalent cost estimates. TaskChef calls
30
+ its structured offline Codex report and does not parse Codex rollout files
31
+ itself. Lifecycle reporting and the dashboard continue to work when `ccusage`
32
+ is absent or incompatible.
33
+
28
34
  ```sh
29
35
  codex plugin marketplace add favoyang/codex-plugins
30
36
  codex plugin add taskchef@favoyang-plugins
@@ -49,7 +55,8 @@ The canonical workspace is `~/.agents/taskchef`. TaskChef owns only:
49
55
  ```text
50
56
  AGENTS.md managed dispatcher instructions plus user additions
51
57
  taskchef.json schema-2 Codex project index, dashboard preference, and delegation metadata
52
- tasks.jsonl one task snapshot per line (schema 9; schema 4-8 migration supported)
58
+ tasks.jsonl one task snapshot per line (schema 10; schema 4-9 migration supported)
59
+ .taskchef-usage.json optional mode-0600 ccusage snapshot and turn-boundary cache
53
60
  ```
54
61
 
55
62
  Index or inspect Codex projects conversationally:
@@ -263,12 +270,36 @@ The loopback dashboard watches `tasks.jsonl`, groups current states, and opens
263
270
  linked Codex tasks. List snapshots and SSE events carry only the latest
264
271
  request/result pair; opening task details fetches the full newest-first activity
265
272
  timeline, including clearly labeled interrupted turns.
266
- Task details also offer **Archive chat** for every linked task whose current
273
+ Task details offer infrequent administrative actions without cluttering task
274
+ cards. Selecting **More task actions** (`…`) reveals the action list immediately
275
+ beside the disclosure and changes it to a back/hide control. The list contains
276
+ **Copy Task ID**, direct **Mark completed** and **Mark failed** actions for a
277
+ `working` or `needs_input` task, and **Archive chat** whenever archival is
278
+ eligible. The menu disclosure is the deliberate first step; choosing a terminal
279
+ outcome submits it immediately without a second confirmation. There is no
280
+ free-form reason: the audit turn records a fixed summary, timestamp, dashboard
281
+ provenance, optimistic preconditions, and a unique action ID while preserving
282
+ every executor turn. Terminal tasks cannot be rewritten. Stale or concurrent
283
+ changes are rejected and the dialog refreshes to the current task. A stalled
284
+ local request is aborted after a bounded wait so the dialog cannot remain
285
+ permanently locked; retry keeps the same idempotency identity.
286
+
287
+ The More actions list offers **Archive chat** for every linked task whose current
267
288
  TaskChef state is not `working`. After confirmation, TaskChef invokes only the
268
289
  Codex CLI at the canonical ChatGPT or Codex desktop app location under
269
290
  `/Applications`, to archive the exact thread UUID. The Codex chat leaves active chat lists while the TaskChef record
270
291
  and its activity timeline remain unchanged. If the bundled CLI is unavailable,
271
292
  the dashboard does not fall back to another `codex` executable from `PATH`.
293
+
294
+ Task details also show whole-task and per-turn token usage when `ccusage` can
295
+ map the linked Codex thread. A completed turn briefly shows “Tokens:
296
+ calculating…” while TaskChef performs bounded deferred reconciliation, because
297
+ the terminal lifecycle callback precedes Codex's final output write. Historical
298
+ tasks may show a trustworthy task total while older turns remain “Tokens
299
+ unavailable” when no cumulative turn boundaries were recorded. Input, cached
300
+ input, output, reasoning, and total counts retain ccusage's categories. Dollar
301
+ figures are labeled API-equivalent estimates; zero-priced unknown models show
302
+ cost unavailable rather than a misleading `$0.00`.
272
303
  The header shows the running TaskChef package version reported by the same
273
304
  bounded health identity used for compatible-listener checks.
274
305
  The canonical port is owned by a dashboard initialized in the TaskChef MCP host
@@ -286,12 +317,13 @@ revision, so reconnects and non-semantic rewrites do not replay a notice and a
286
317
  later task state cannot rewrite an older notice. A notice remains readable if
287
318
  its task disappears; selecting it then explains that current details are no
288
319
  longer available.
289
- It does not mutate TaskChef data and prints its local URL. A foreground
290
- dashboard identifies itself as standalone and is never reused on the canonical
291
- MCP port. If a standalone, unknown, different-workspace, or stale-version
292
- process owns port 3210, TaskChef reports a concise conflict and never kills or
293
- replaces that process. The foreground CLI similarly asks you to stop the
294
- listener or choose another `--port`.
320
+ Apart from an explicit manual state selection from the task-detail menu, it does not mutate
321
+ TaskChef data and prints its local URL. A foreground dashboard identifies
322
+ itself as standalone and is never reused on the canonical MCP port. If a
323
+ standalone, unknown, different-workspace, or stale-version process owns port
324
+ 3210, TaskChef reports a concise conflict and never kills or replaces that
325
+ process. The foreground CLI similarly asks you to stop the listener or choose
326
+ another `--port`.
295
327
 
296
328
  The health endpoint contains only a fixed service marker, health schema,
297
329
  TaskChef version, dashboard-server version, canonical workspace, and launcher.
@@ -304,6 +336,8 @@ or secrets.
304
336
 
305
337
  ![Task detail activity timeline](docs/images/result-history-dashboard.jpg)
306
338
 
339
+ ![Deferred ccusage token consumption in task details](docs/images/ccusage-token-consumption.png)
340
+
307
341
  ![Interrupted turn followed by active recovery](docs/images/interrupted-turn-recovery.jpg)
308
342
 
309
343
  ## Common recovery
@@ -319,9 +353,9 @@ taskchef doctor
319
353
 
320
354
  `doctor` is read-only. `workspace init` creates missing files and refreshes
321
355
  managed instructions. `workspace migrate` explicitly upgrades supported schema
322
- 4-8 task lines to schema 9 under the workspace lock. It validates task and turn
356
+ 4-9 task lines to schema 10 under the workspace lock. It validates task and turn
323
357
  counts plus the complete source and converted log before writing, creates an
324
- exclusive `tasks.jsonl.pre-v9-*.bak`
358
+ exclusive `tasks.jsonl.pre-v10-*.bak`
325
359
  backup, atomically replaces the log, validates the result, and becomes an
326
360
  idempotent no-op after migration. If replacement fails, the original remains
327
361
  or the reported backup can be restored; unsupported or invalid input is rejected
@@ -332,7 +366,7 @@ executor so its first action can retry `link_task`. Do not guess an identity
332
366
  or edit `tasks.jsonl`. If native task creation failed, the record is retained
333
367
  as `failed` with a retained fallback `turnRef` and null thread and Codex turn IDs.
334
368
 
335
- Schemas other than 4, 5, 6, 7, 8, and 9 remain unsupported. Retain such a workspace
369
+ Schemas other than 4, 5, 6, 7, 8, 9, and 10 remain unsupported. Retain such a workspace
336
370
  unchanged and create a current workspace; the migration command deliberately
337
371
  does not guess how to convert unknown formats.
338
372
 
package/docs/spec.md CHANGED
@@ -28,7 +28,7 @@ is dated research, not contract.
28
28
  | **Last semantic result** | The final result-history entry, exposed through the derived `lastResult` compatibility alias. |
29
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
30
  | **Current turn ID** | Optional Codex metadata for the reported prompt; null when native turn reading is unavailable. |
31
- | **Dashboard** | The loopback, read-only UI derived from validated workspace snapshots and bounded native actions. |
31
+ | **Dashboard** | The loopback UI derived from validated workspace snapshots, with optional local usage projections, bounded native actions, and an explicit audited manual-outcome mutation. |
32
32
  | **Skill** | One packaged agent procedure: `taskchef-bootstrap`, `taskchef-dashboard`, `taskchef-delegate`, `taskchef-executor`, or `taskchef-copilot`. |
33
33
 
34
34
  ## Components and ownership
@@ -50,13 +50,15 @@ is dated research, not contract.
50
50
  and the deprecated `report_result` compatibility alias specified below.
51
51
  - The CLI MAY administer and inspect the workspace, but MUST NOT provide a
52
52
  second agent lifecycle protocol.
53
- - The dashboard MUST be read-only with respect to dispatcher files.
53
+ - The dashboard MUST be read-only with respect to dispatcher files except for
54
+ the explicit audited manual-transition operation defined below.
54
55
  - `docs/spec.md` is the single normative behavior source. Other documents
55
56
  MUST link here rather than redefine the contract.
56
57
 
57
58
  ## Workspace contract
58
59
 
59
- TaskChef MUST manage only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl`
60
+ TaskChef MUST manage only `AGENTS.md`, `taskchef.json`, `tasks.jsonl`, and the
61
+ optional `.taskchef-usage.json` cache
60
62
  inside the dispatcher workspace. It MUST preserve unrelated paths.
61
63
 
62
64
  `taskchef.json` MUST have schema version 2, the following required fields, and
@@ -81,8 +83,8 @@ is backward-compatible and means `true`; new workspaces SHOULD write
81
83
  disable the explicit `ensure_dashboard` tool.
82
84
 
83
85
  `tasks.jsonl` MUST contain zero or more newline-terminated schema-4 through
84
- schema-9 records, one per line. Schemas 4 through 8 are supported
85
- migration/read formats; every new record and state mutation MUST write schema 9. Other schemas
86
+ schema-10 records, one per line. Schemas 4 through 9 are supported
87
+ migration/read formats; every new record and state mutation MUST write schema 10. Other schemas
86
88
  or unsupported fields MUST be rejected without conversion.
87
89
  Reads and writes MUST reject symlinked managed files. Mutations
88
90
  MUST hold the shared workspace lock and replace state atomically; read-only
@@ -94,7 +96,7 @@ Every record MUST contain exactly these fields:
94
96
 
95
97
  | Field | Contract |
96
98
  | --- | --- |
97
- | `schemaVersion` | Integer `9`; schema-4/5/6/7/8 records remain readable until explicit migration or their next mutation. |
99
+ | `schemaVersion` | Integer `10`; schema-4/5/6/7/8/9 records remain readable until explicit migration or their next mutation. |
98
100
  | `id` | Unique safe TaskChef ID; delegation uses a lowercase full UUID. |
99
101
  | `project` | Immutable configured-project snapshot. |
100
102
  | `title` | Non-empty display title. |
@@ -106,16 +108,64 @@ Every record MUST contain exactly these fields:
106
108
  | `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`. |
107
109
  | `turnId` | Optional Codex metadata. When non-null it equals `turnRef`; null indicates fallback identity. |
108
110
  | `updatedAt` | ISO 8601 timestamp not earlier than `createdAt` or the prior `updatedAt`; clock rollback cannot backdate a transition. |
109
- | `updatedBy` | `dispatcher` or `mcp`. |
110
- | `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. |
111
+ | `updatedBy` | `dispatcher`, `mcp`, or `dashboard`. `dashboard` is valid only when the latest turn is a manual dashboard transition. |
112
+ | `turns` | Ordered oldest-first array of `{turnRef, turnId, requestSummary, startedAt, result, provenance}`. 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. `provenance` is `{kind: legacy}` or `{kind: mcp}` for ordinary turns, or the audited manual-transition record defined below. New self-linking and manual turn refs are unique. A migrated low-level opaque record may retain one final reused native-derived ref for its legacy ambiguity. |
111
113
 
112
114
  Returned Task objects MUST additionally expose `latestTurn` as null for an empty
113
115
  timeline or the final `turns` entry. They MUST derive `results` only from
114
116
  semantic `needs_input`, `completed`, and `failed` turn results and `lastResult`
115
117
  from the final derived semantic result. Interrupted outcomes MUST be excluded.
116
- These projections MUST NOT be persisted in schema 9 and remain compatibility
118
+ These projections MUST NOT be persisted in schema 10 and remain compatibility
117
119
  aliases for existing callers.
118
120
 
121
+ ## Optional usage projection
122
+
123
+ TaskChef MAY invoke an installed `ccusage` executable as an optional local
124
+ adapter using `ccusage codex session --json --offline`. TaskChef MUST NOT parse,
125
+ store, or serve raw Codex rollout files, prompts, responses, transcripts, or
126
+ reasoning. Analyzer absence, timeout, malformed output, unknown pricing, or an
127
+ unresolved thread MUST NOT block lifecycle tools or dashboard loading.
128
+
129
+ Dashboard-manual turns are administrative events, not Codex executions. They
130
+ MUST immediately report per-turn usage as unavailable, MUST NOT schedule or
131
+ store a cumulative usage boundary, and MUST NOT receive a token or cost delta.
132
+ A later executor turn MUST use the nearest preceding reliable executor
133
+ boundary, skipping any intervening dashboard-manual turns.
134
+
135
+ The mode-0600 `.taskchef-usage.json` cache stores only normalized cumulative
136
+ token boundaries, per-turn deltas, model names, estimated cost, source version,
137
+ and freshness. It is independent of task-log schema versions so legacy logs remain
138
+ readable. Writes MUST use the workspace lock and atomic replacement. Symlinked
139
+ or unsupported cache files MUST be rejected. Writes MUST compact the derived
140
+ cache to recent task projections, recent per-turn results, and the latest
141
+ cumulative boundary. An oversized legacy cache MUST be treated as rebuildable
142
+ derived data so it cannot permanently disable usage reporting.
143
+
144
+ A linked Codex thread MAY map to multiple ccusage session segments. TaskChef
145
+ MUST aggregate only records whose primary durable thread UUID is that exact
146
+ identity; a UUID appearing only as a nested suffix MUST NOT be attributed to
147
+ the parent or child TaskChef task. It MUST retain input, cached-input, output,
148
+ reasoning-output, and total fields without adding cached or reasoning subsets
149
+ into totals a second time.
150
+
151
+ After a terminal report, TaskChef MUST mark the turn `calculating` and perform
152
+ bounded deferred reconciliation without delaying the lifecycle response.
153
+ Per-turn usage MUST be a non-negative delta between adjacent reliable cumulative
154
+ boundaries. A first recorded turn MAY use zero as its baseline. Historical
155
+ turns without boundaries and decreasing or ambiguous snapshots MUST be labeled
156
+ `unavailable`, never zero or estimated. A historical task MAY still show its
157
+ resolvable cumulative total. A boundary is reliable only after two consecutive
158
+ samples agree. Exhausted unstable sampling and a newer turn beginning before
159
+ stabilization MUST leave that turn unavailable and MUST NOT establish a delta
160
+ baseline. Zero is valid for the first turn only when TaskChef observed that turn
161
+ in progress before its terminal report; a first historical terminal turn MUST
162
+ remain unavailable even when its cumulative task total is resolvable.
163
+
164
+ Every available projection MUST identify ccusage, its version when available,
165
+ and freshness. Dollar values MUST be labeled API-equivalent estimates. Positive
166
+ token usage with a zero or missing analyzer cost MUST display cost unavailable,
167
+ not `$0.00`.
168
+
119
169
  Task IDs and non-null thread identities MUST be unique. The immutable intent
120
170
  fields MUST NOT change after recording.
121
171
 
@@ -307,7 +357,7 @@ new preparation values, though it writes no state.
307
357
 
308
358
  **Structured output:** `{ task: Task }`.
309
359
 
310
- The returned task has schema 9, `working`, null summary/turn/thread/latestTurn/lastResult,
360
+ The returned task has schema 10, `working`, null summary/turn/thread/latestTurn/lastResult,
311
361
  empty `turns` and derived `results` arrays,
312
362
  `updatedBy: dispatcher`, and equal creation/update timestamps. Duplicate IDs,
313
363
  unknown projects, malformed markers, and invalid input fail. Repeating a
@@ -384,8 +434,8 @@ MUST contain no crash output, transcript, user text, or inferred failure cause.
384
434
  temporary compatibility alias. It implicitly accepts a fresh supplied turn and
385
435
  stores its semantic result in a request-unknown turn, including for supported schema-4/5/6 records and
386
436
  low-level opaque direct records. It does not accept `working`. New executor
387
- instructions MUST use `report_state`. Successful mutation upgrades schema 4-8
388
- to schema 9; unsupported schemas remain rejected. Legacy callers that omit
437
+ instructions MUST use `report_state`. Successful mutation upgrades schema 4-9
438
+ to schema 10; unsupported schemas remain rejected. Legacy callers that omit
389
439
  `turnRef` remain compatible when `turnId` is non-null.
390
440
 
391
441
  ## Copilot and dashboard
@@ -433,6 +483,41 @@ startup safely. Direct thread navigation
433
483
  MUST require a canonical Codex UUIDv7. Otherwise it MAY open the revalidated
434
484
  configured project. Project paths from task history MUST be matched against
435
485
  current configuration before use.
486
+ The task-detail dashboard MAY offer manual terminal outcomes only for current
487
+ `working` or `needs_input` tasks. It MUST permit exactly `completed` and
488
+ `failed`, MUST NOT rewrite a terminal task, and MUST keep this infrequent
489
+ administrative action out of list cards. A keyboard-accessible **More task
490
+ actions** disclosure MUST reveal its action list immediately beside it and
491
+ change from an ellipsis to an accessible back/hide control while expanded. The
492
+ list MUST group **Copy Task ID**, **Mark completed**, and **Mark failed** for
493
+ eligible tasks, plus **Archive chat** when archival is eligible. Opening the
494
+ list is the deliberate disclosure step; choosing a terminal outcome submits it
495
+ immediately without a second confirmation. Escape MUST close the idle list,
496
+ pending controls MUST be
497
+ disabled, and failure feedback MUST remain in the dialog. Pending state MUST
498
+ focus and announce a stable status inside the dialog. Failed MUST have
499
+ destructive styling. The client MUST bound and abort a stalled manual-transition
500
+ request, restore the dialog controls, and preserve the action ID so a retry can
501
+ resolve idempotently if the server committed before the timeout.
502
+
503
+ `POST /api/tasks/:id/manual-transition` MUST require the exact loopback origin,
504
+ `application/json`, a bounded body, and exactly this versioned shape:
505
+ `{schemaVersion: 1, actionId, expected: {status, turnRef, threadId, updatedAt},
506
+ targetStatus}`. The server MUST compare every expected field while holding the
507
+ workspace lock. A stale request, invalid transition, or reused action ID with a
508
+ different operation MUST return a conflict without mutation. Replaying the
509
+ same committed action ID and operation MUST return the current task as an
510
+ idempotent success. The operation MUST atomically append one new manual turn;
511
+ it MUST NOT overwrite executor history. An active unfinished turn MUST first
512
+ receive the standard interrupted timeline outcome. The manual turn MUST have a
513
+ new server-generated `turnRef`, null `turnId`, one monotonic timestamp for its
514
+ start, result, and task update, `updatedBy: dashboard`, and deterministic
515
+ request/result summaries. It MUST record provenance
516
+ `{kind: dashboard_manual, actionId, fromStatus, toStatus, expectedTurnRef,
517
+ expectedThreadId, expectedUpdatedAt}`. No free-form reason is collected or
518
+ persisted. A committed write remains successful if the subsequent best-effort
519
+ monitor refresh or notification delivery fails.
520
+
436
521
  The task-detail dashboard MAY offer Codex chat archival only when the stored
437
522
  thread ID is a canonical Codex UUID and the current TaskChef state is not
438
523
  `working`. It MUST revalidate both conditions for the POST request, require the
@@ -478,15 +563,15 @@ its displayed summary when present, event time, and missing-task state.
478
563
 
479
564
  ## Task-log migration
480
565
 
481
- `workspace migrate` MUST explicitly convert every supported schema-4/5/6/7/8 record
566
+ `workspace migrate` MUST explicitly convert every supported schema-4/5/6/7/8/9 record
482
567
  under the shared lock. Each legacy semantic result becomes a request-unknown
483
568
  completed turn; a newer working state becomes a final unfinished turn, and a
484
569
  schema-7/8 timeline is preserved. Each non-null legacy `turnId` becomes the
485
570
  same `turnRef`; each null legacy `turnId` receives one durably persisted UUID.
486
571
  Migration MUST validate the complete source, record/turn counts, turn-ref
487
- invariants, and complete schema-9 candidate before changing the task log,
572
+ invariants, and complete schema-10 candidate before changing the task log,
488
573
  create and read back an exclusive recovery backup, atomically replace the log,
489
- and validate the installed result. A fully schema-9 log MUST be an idempotent
574
+ and validate the installed result. A fully schema-10 log MUST be an idempotent
490
575
  no-op without another backup. Invalid/unsupported input MUST remain untouched;
491
576
  failures after backup creation MUST report the backup path and MUST never
492
577
  partially rewrite individual lines.
package/docs/workflows.md CHANGED
@@ -21,6 +21,8 @@ research.
21
21
  | `src/cli.js` | Administration, normalized cached briefs, inspection, diagnostics, and dashboard startup. |
22
22
  | `src/dashboard.js` | Versioned health identity, validated compact snapshots, SSE fan-out, on-demand details, and bounded open actions. |
23
23
  | `src/dashboard-manager.js` | Concurrent singleton ensure, exact listener reuse, conflicts, and owned shutdown. |
24
+ | `src/usage.js` | Optional bounded ccusage execution, exact primary-thread mapping, normalized aggregation, and the private usage cache. |
25
+ | `src/usage-tracker.js` | Deferred sampling, cumulative boundaries, historical availability, and per-turn deltas. |
24
26
 
25
27
  The MCP process resolves `TASKCHEF_WORKSPACE` once and never accepts a model
26
28
  supplied path. The CLI resolves `--workspace`, then the environment, then the
@@ -114,7 +116,7 @@ sequenceDiagram
114
116
  D->>D: Choose one configured and native project
115
117
  D->>M: record_task(id, project, title, instruction, null)
116
118
  M->>W: recordTask()
117
- W->>W: Lock, validate, append schema-9 snapshot
119
+ W->>W: Lock, validate, append schema-10 snapshot
118
120
  W-->>M: working link-pending task
119
121
  M-->>D: task
120
122
  D->>C: Create executor with marked instruction
@@ -258,7 +260,7 @@ sequenceDiagram
258
260
  W->>W: Acquire workspace lock and validate turnRefB is new
259
261
  W->>W: Close unfinished turnA as interrupted
260
262
  W->>W: Append turnB with requestB and null result
261
- W->>F: One atomic schema-9 replacement
263
+ W->>F: One atomic schema-10 replacement
262
264
  W-->>M: working task projected from turnB
263
265
  M-->>E: Idempotent recovery success
264
266
  E->>M: late semantic result for turnA
@@ -394,16 +396,17 @@ all, and ordinary rerendering do not re-announce retained history. Toast action
394
396
  labels remain concise while `aria-describedby` connects the visible summary,
395
397
  event time, and missing-task explanation for assistive technology.
396
398
 
397
- ## Schema 4-8 migration
399
+ ## Schema 4-9 migration
398
400
 
399
401
  `taskchef workspace migrate` acquires the same workspace lock as lifecycle
400
402
  writers, validates the complete legacy log, converts schema-4/5/6 results into
401
403
  request-unknown completed turns and preserves a newer working turn, then validates the
402
404
  complete candidate. Before replacement it writes and reads back an exclusive
403
- `tasks.jsonl.pre-v9-*.bak` file. Schema-7/8 timelines gain a durable `turnRef`:
405
+ `tasks.jsonl.pre-v10-*.bak` file. Schema-7/8 timelines gain a durable `turnRef`:
404
406
  non-null `turnId` values are copied exactly and null IDs receive persisted UUIDs.
405
407
  Task and turn counts plus all refs are validated before and after the atomic
406
- replacement. A second run sees only schema 9 and returns unchanged without
408
+ replacement. Schema-9 turns gain explicit legacy or MCP provenance. A second
409
+ run sees only schema 10 and returns unchanged without
407
410
  another backup. Unsupported or malformed input fails before backup/rewrite;
408
411
  after a later filesystem failure, the reported backup is the recovery source.
409
412
 
@@ -420,9 +423,10 @@ summary is cryptographically authenticated; this is a local single-user trust
420
423
  model. Managed files, instructions, project snapshots, MCP inputs, and dashboard
421
424
  requests are validated at every action boundary.
422
425
 
423
- Configuration schema 2 and task schemas 4 through 9 are accepted. Schemas
424
- 4-8 are read/migration compatibility until an explicit migration or lifecycle
425
- mutation upgrades each record to schema 9. Schema 9 persists `turns`, including
426
+ Configuration schema 2 and task schemas 4 through 10 are accepted. Schemas
427
+ 4-9 are read/migration compatibility until an explicit migration or lifecycle
428
+ mutation upgrades each record to schema 10. Schema 10 persists `turns`, including
426
429
  timeline-only interrupted outcomes, and derives semantic-only `results` and
427
- `lastResult` plus `latestTurn` for compact compatibility. Other schemas
430
+ `lastResult` plus `latestTurn` for compact compatibility. It also records turn
431
+ provenance and the optimistic preconditions for manual dashboard outcomes. Other schemas
428
432
  are rejected without rewrite.
package/index.js CHANGED
@@ -12,6 +12,7 @@ export {
12
12
  initializeWorkspace,
13
13
  linkTask,
14
14
  listProjects,
15
+ manuallyTransitionTask,
15
16
  migrateTaskLog,
16
17
  prepareDispatch,
17
18
  readConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "7.19.1",
3
+ "version": "7.21.0",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
@@ -27,6 +27,7 @@
27
27
  "docs/workflows.md",
28
28
  "docs/firstmate-taskchef-comparison.md",
29
29
  "docs/images/dashboard-identity.jpg",
30
+ "docs/images/ccusage-token-consumption.png",
30
31
  "docs/images/notification-event-snapshots.jpg",
31
32
  "docs/images/result-history-dashboard.jpg",
32
33
  "docs/images/interrupted-turn-recovery.jpg",
@@ -53,3 +53,84 @@ export async function archiveTaskFromControl(event, task, {
53
53
  if (!archived) control.disabled = false;
54
54
  }
55
55
  }
56
+
57
+ export async function manuallyTransitionTaskFromControl(event, task, targetStatus, actionId, {
58
+ clearTimer = globalThis.clearTimeout,
59
+ fetchAction = globalThis.fetch,
60
+ setTimer = globalThis.setTimeout,
61
+ timeoutMs = 10_000,
62
+ } = {}) {
63
+ event?.stopPropagation?.();
64
+ const controller = new AbortController();
65
+ const timeout = setTimer(() => controller.abort(), timeoutMs);
66
+ try {
67
+ const response = await fetchAction(
68
+ `/api/tasks/${encodeURIComponent(task.id)}/manual-transition`,
69
+ {
70
+ method: "POST",
71
+ headers: { "Content-Type": "application/json" },
72
+ body: JSON.stringify({
73
+ schemaVersion: 1,
74
+ actionId,
75
+ expected: {
76
+ status: task.status,
77
+ turnRef: task.turnRef,
78
+ threadId: task.threadId,
79
+ updatedAt: task.updatedAt,
80
+ },
81
+ targetStatus,
82
+ }),
83
+ signal: controller.signal,
84
+ },
85
+ );
86
+ const result = await response.json();
87
+ return response.ok
88
+ ? { ok: true, ...result }
89
+ : {
90
+ ok: false,
91
+ code: result.code ?? "dashboard_error",
92
+ message: result.message ?? "Task state could not be changed.",
93
+ task: result.task ?? null,
94
+ };
95
+ } catch {
96
+ return {
97
+ ok: false,
98
+ code: controller.signal.aborted ? "request_timeout" : "network_error",
99
+ message: controller.signal.aborted
100
+ ? "Task state change timed out. Try again."
101
+ : "Task state changes are temporarily unavailable. Try again.",
102
+ task: null,
103
+ };
104
+ } finally {
105
+ clearTimer(timeout);
106
+ }
107
+ }
108
+
109
+ export function handleManualTransitionEscape(event, {
110
+ active,
111
+ pending,
112
+ cancel,
113
+ }) {
114
+ if (event.key !== "Escape" || !active) return false;
115
+ event.preventDefault();
116
+ event.stopPropagation();
117
+ if (!pending) cancel();
118
+ return true;
119
+ }
120
+
121
+ export function focusManualTransitionStatus(panel) {
122
+ const status = panel.querySelector('[data-manual-focus="pending"]');
123
+ status?.focus();
124
+ return status !== null;
125
+ }
126
+
127
+ export function restoreTaskActionMenuFocus(panel, activeElement, fallback) {
128
+ if (!activeElement || !panel.contains(activeElement)) return null;
129
+ if (!activeElement.hidden && !activeElement.disabled) return activeElement;
130
+ const copyTaskId = panel.querySelector('[data-manual-focus="copy"]');
131
+ const destination = copyTaskId && !copyTaskId.hidden && !copyTaskId.disabled
132
+ ? copyTaskId
133
+ : fallback;
134
+ destination?.focus();
135
+ return destination ?? null;
136
+ }