taskchef 8.7.1 → 9.0.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.
Files changed (149) hide show
  1. package/.codex-plugin/plugin.json +7 -10
  2. package/.mcp.json +3 -2
  3. package/README.md +31 -158
  4. package/docs/mcp-app.md +3 -3
  5. package/docs/spec.md +38 -746
  6. package/docs/taskchef-next-storage.md +2 -2
  7. package/index.js +2 -99
  8. package/package.json +12 -49
  9. package/skills/delegate/SKILL.md +44 -0
  10. package/skills/delegate/agents/openai.yaml +4 -0
  11. package/src/codex-app.js +3 -152
  12. package/src/codex-session-scanner.js +2 -2
  13. package/src/file-lock.js +25 -0
  14. package/src/mcp-app/react/NextTaskDetail.test.tsx +1 -1
  15. package/src/mcp-app/react/NextTaskDetail.tsx +8 -8
  16. package/src/mcp-app/react/main.test.tsx +3 -3
  17. package/src/mcp-app/react/main.tsx +7 -7
  18. package/src/mcp-app.js +2 -2
  19. package/src/mcp.js +4 -542
  20. package/src/next-cache.js +2 -2
  21. package/src/next-github.js +3 -3
  22. package/src/next-notifications.js +2 -2
  23. package/src/{dashboard/react → ui}/components/CardStatusIcons.tsx +1 -1
  24. package/src/{dashboard/react → ui}/components/GitHubLinks.tsx +1 -1
  25. package/src/{dashboard/react → ui}/components/LinkedText.tsx +1 -1
  26. package/src/{dashboard/react → ui}/components/RelativeTime.test.tsx +1 -1
  27. package/src/{dashboard/react → ui}/components/RelativeTime.tsx +1 -1
  28. package/src/{dashboard/react → ui}/components/ReplyImagePreview.test.tsx +1 -1
  29. package/src/{dashboard/react → ui}/components/TaskBoard.tsx +1 -1
  30. package/src/{dashboard/react → ui}/components/TaskCard.tsx +1 -1
  31. package/src/{dashboard/react → ui}/components/TurnContent.tsx +1 -1
  32. package/src/{dashboard/react → ui}/presentation.ts +1 -1
  33. package/src/version.js +0 -1
  34. package/BACKLOG.md +0 -48
  35. package/assets/e2e-benchmark-example.json +0 -50
  36. package/assets/taskchef-dispatcher-instructions.md +0 -44
  37. package/bin/taskchef.js +0 -20
  38. package/docs/advanced-guide.md +0 -497
  39. package/docs/dashboard-lifecycle.md +0 -149
  40. package/docs/dashboard-ui.md +0 -40
  41. package/docs/firstmate-taskchef-comparison.md +0 -283
  42. package/docs/images/ccusage-token-consumption.png +0 -0
  43. package/docs/images/dashboard-identity.jpg +0 -0
  44. package/docs/images/dashboard-token-pending.jpg +0 -0
  45. package/docs/images/interrupted-turn-recovery.jpg +0 -0
  46. package/docs/images/notification-event-snapshots.jpg +0 -0
  47. package/docs/images/readme-board.png +0 -0
  48. package/docs/images/readme-completed.png +0 -0
  49. package/docs/images/readme-list.png +0 -0
  50. package/docs/images/readme-needs-input.png +0 -0
  51. package/docs/images/readme-roles.png +0 -0
  52. package/docs/images/reported-work-duration-desktop.jpg +0 -0
  53. package/docs/images/reported-work-duration-mobile.jpg +0 -0
  54. package/docs/images/result-history-dashboard.jpg +0 -0
  55. package/docs/images/taskchef-pr-icon-comparison.jpg +0 -0
  56. package/docs/images/taskchef-unified-status-popup.jpg +0 -0
  57. package/docs/workflows.md +0 -476
  58. package/mcp/dashboard-session.js +0 -51
  59. package/scripts/benchmark-dispatch-prepare.js +0 -90
  60. package/scripts/e2e-benchmark.js +0 -206
  61. package/scripts/roles/resolve_roles.py +0 -293
  62. package/skills/taskchef-bootstrap/SKILL.md +0 -96
  63. package/skills/taskchef-bootstrap/agents/openai.yaml +0 -4
  64. package/skills/taskchef-bootstrap/references/project-index.md +0 -154
  65. package/skills/taskchef-copilot/SKILL.md +0 -75
  66. package/skills/taskchef-copilot/agents/openai.yaml +0 -4
  67. package/skills/taskchef-dashboard/SKILL.md +0 -24
  68. package/skills/taskchef-dashboard/agents/openai.yaml +0 -4
  69. package/skills/taskchef-delegate/SKILL.md +0 -102
  70. package/skills/taskchef-delegate/agents/openai.yaml +0 -4
  71. package/skills/taskchef-delegate/references/model-roles.md +0 -29
  72. package/skills/taskchef-executor/SKILL.md +0 -154
  73. package/skills/taskchef-executor/agents/openai.yaml +0 -4
  74. package/skills/taskchef-executor/references/compatibility.md +0 -22
  75. package/skills/taskchef-executor/references/ending-actions.md +0 -26
  76. package/src/backups.js +0 -359
  77. package/src/cli.js +0 -733
  78. package/src/config-audit.js +0 -99
  79. package/src/config-mutations.js +0 -106
  80. package/src/dashboard/actions.js +0 -136
  81. package/src/dashboard/app.js +0 -1172
  82. package/src/dashboard/dist/app.js +0 -269
  83. package/src/dashboard/dist/app.js.map +0 -1
  84. package/src/dashboard/dist/index.html +0 -16
  85. package/src/dashboard/dist/styles.css +0 -1
  86. package/src/dashboard/index.html +0 -148
  87. package/src/dashboard/react/App.test.tsx +0 -346
  88. package/src/dashboard/react/App.tsx +0 -515
  89. package/src/dashboard/react/api.test.ts +0 -40
  90. package/src/dashboard/react/api.ts +0 -103
  91. package/src/dashboard/react/components/ActivityTimeline.tsx +0 -118
  92. package/src/dashboard/react/components/ModelSettings.test.tsx +0 -180
  93. package/src/dashboard/react/components/ModelSettings.tsx +0 -106
  94. package/src/dashboard/react/components/NotificationCenter.test.tsx +0 -76
  95. package/src/dashboard/react/components/NotificationCenter.tsx +0 -111
  96. package/src/dashboard/react/components/PullRequestBadges.tsx +0 -18
  97. package/src/dashboard/react/components/TaskDetail.test.tsx +0 -255
  98. package/src/dashboard/react/components/TaskDetail.tsx +0 -271
  99. package/src/dashboard/react/components/UsagePanel.tsx +0 -96
  100. package/src/dashboard/react/index.html +0 -15
  101. package/src/dashboard/react/main.tsx +0 -12
  102. package/src/dashboard/react/overlay-state.test.ts +0 -110
  103. package/src/dashboard/react/overlay-state.ts +0 -60
  104. package/src/dashboard/react/presentation.test.tsx +0 -571
  105. package/src/dashboard/styles.css +0 -261
  106. package/src/dashboard-control.js +0 -34
  107. package/src/dashboard-manager.js +0 -339
  108. package/src/dashboard-session-process.js +0 -88
  109. package/src/dashboard-session.js +0 -86
  110. package/src/dashboard.js +0 -1303
  111. package/src/delegation.js +0 -277
  112. package/src/github.js +0 -107
  113. package/src/model-roles.js +0 -70
  114. package/src/recorded-usage-tracker.js +0 -92
  115. package/src/usage-tracker.js +0 -650
  116. package/src/usage.js +0 -367
  117. package/src/workspace-path.js +0 -48
  118. package/src/workspace.js +0 -3629
  119. /package/src/{dashboard/react → ui}/components/AnimatedUsage.test.tsx +0 -0
  120. /package/src/{dashboard/react → ui}/components/CardStatusIcons.test.tsx +0 -0
  121. /package/src/{dashboard/react → ui}/components/ImageGallery.tsx +0 -0
  122. /package/src/{dashboard/react → ui}/components/OpenChatButton.test.tsx +0 -0
  123. /package/src/{dashboard/react → ui}/components/OpenChatButton.tsx +0 -0
  124. /package/src/{dashboard/react → ui}/components/PrGlyph.tsx +0 -0
  125. /package/src/{dashboard/react → ui}/components/QueuedPrompts.test.tsx +0 -0
  126. /package/src/{dashboard/react → ui}/components/QueuedPrompts.tsx +0 -0
  127. /package/src/{dashboard/react → ui}/components/ReplyCover.test.tsx +0 -0
  128. /package/src/{dashboard/react → ui}/components/ReplyCover.tsx +0 -0
  129. /package/src/{dashboard/react → ui}/components/ReplyImagePreview.tsx +0 -0
  130. /package/src/{dashboard/react → ui}/components/ReplyMarkdown.test.tsx +0 -0
  131. /package/src/{dashboard/react → ui}/components/ReplyMarkdown.tsx +0 -0
  132. /package/src/{dashboard/react → ui}/components/SavedUsageFormat.test.tsx +0 -0
  133. /package/src/{dashboard/react → ui}/components/SavedUsageFormat.tsx +0 -0
  134. /package/src/{dashboard/react → ui}/components/ShimmerText.tsx +0 -0
  135. /package/src/{dashboard/react → ui}/components/StatsGrid.tsx +0 -0
  136. /package/src/{dashboard/react → ui}/components/StatusBadge.tsx +0 -0
  137. /package/src/{dashboard/react → ui}/components/TaskBoard.test.tsx +0 -0
  138. /package/src/{dashboard/react → ui}/components/TaskCardStats.test.tsx +0 -0
  139. /package/src/{dashboard/react → ui}/components/TaskCardStats.tsx +0 -0
  140. /package/src/{dashboard/react → ui}/components/TurnContent.test.tsx +0 -0
  141. /package/src/{dashboard/react → ui}/fixtures.ts +0 -0
  142. /package/src/{dashboard → ui}/github-links.js +0 -0
  143. /package/src/{dashboard/react → ui}/image-cache.test.ts +0 -0
  144. /package/src/{dashboard/react → ui}/image-cache.ts +0 -0
  145. /package/src/{dashboard → ui}/state.js +0 -0
  146. /package/src/{dashboard/react → ui}/styles.css +0 -0
  147. /package/src/{dashboard/react → ui}/test-setup.ts +0 -0
  148. /package/src/{dashboard → ui}/time.js +0 -0
  149. /package/src/{dashboard/react → ui}/types.ts +0 -0
package/docs/workflows.md DELETED
@@ -1,476 +0,0 @@
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
- [advanced guide](advanced-guide.md) owns detailed user operation. The
6
- [FirstMate comparison](firstmate-taskchef-comparison.md) is non-normative
7
- research. The [dashboard lifecycle](dashboard-lifecycle.md) owns the focused
8
- start, upgrade, shutdown, and security rationale.
9
-
10
- ## Implementation map
11
-
12
- | Surface | Responsibility |
13
- | --- | --- |
14
- | `skills/taskchef-delegate/SKILL.md` | Split, route, record-before-create, create, return. |
15
- | `skills/taskchef-executor/SKILL.md` | Own, self-link, execute, and report every executor turn. |
16
- | `skills/taskchef-bootstrap/SKILL.md` | Initialize the workspace and maintain the Codex project index. |
17
- | `skills/taskchef-dashboard/SKILL.md` | Ensure or recover the canonical dashboard and return its URL. |
18
- | `skills/taskchef-copilot/SKILL.md` | Explain normalized cached briefs and coordinate safe next actions. |
19
- | `src/mcp.js` | Dashboard ensure, four primary lifecycle tools, one deprecated alias, MCP cleanup, and annotations. |
20
- | `src/delegation.js` | UUID marker, concise executor-skill invocation shape, and creation-failure handling. |
21
- | `src/workspace.js` | Current schemas, validation, locking, atomic JSONL writes, linking, and result freshness. |
22
- | `src/config-mutations.js` | Canonical hashes and exact field-level project diffs. |
23
- | `src/backups.js` | Private snapshot creation, verification, retention, and restore inputs. |
24
- | `src/config-audit.js` | Durable prepared/terminal configuration-mutation journal. |
25
- | `src/cli.js` | Administration, normalized cached briefs, inspection, diagnostics, and dashboard startup. |
26
- | `src/dashboard.js` | Versioned health identity, validated compact snapshots, SSE fan-out, on-demand details, and bounded open actions. |
27
- | `src/dashboard-manager.js` | Concurrent session ensure, exact health reuse, bounded version replacement, and safe conflicts. |
28
- | `src/dashboard-session-process.js` | Independent loopback server, signals, and in-memory Codex-session lease. |
29
- | `src/dashboard-session.js` | Exact registered-PID liveness checks and grace-period expiry. |
30
- | `src/usage.js` | Normalized private usage projections and historical cache compatibility. |
31
- | `src/usage-tracker.js` | Usage-provider factory and compatibility with injected cumulative readers. |
32
- | `src/recorded-usage-tracker.js` | Lazy native-turn projections, descendant totals, bounded requests, and persistent web usage. |
33
- | `src/codex-session-usage.js` | Saved Codex accounting, incremental file reads, derived disk caches, and shared pricing. |
34
-
35
- The MCP process resolves `TASKCHEF_WORKSPACE` once and never accepts a model
36
- supplied path. The CLI resolves `--workspace`, then the environment, then the
37
- per-user default.
38
-
39
- ## Dispatcher dashboard lifecycle
40
-
41
- MCP activation makes dashboard maintenance a best-effort lifecycle action. The
42
- generated managed `AGENTS.md` block also retries it before dispatcher turns and
43
- keeps response ordering centralized instead of duplicating it across skills.
44
-
45
- ```mermaid
46
- sequenceDiagram
47
- autonumber
48
- participant D as Dispatcher
49
- participant M as TaskChef MCP
50
- participant H as Loopback health
51
- participant S as Dashboard process
52
- M->>M: Read dashboard.autostart (absent means true)
53
- M->>M: Best-effort ensure before MCP transport connects
54
- D->>M: ensure_dashboard()
55
- M->>M: Serialize concurrent ensure calls
56
- M->>H: GET 127.0.0.1:3210/api/health
57
- alt Exact service, versions, canonical workspace, and recognized launcher
58
- H-->>M: Bounded compatible identity
59
- M->>S: Register Codex parent PID when launcher is session
60
- M->>H: Verify exact health again
61
- M-->>D: reused, URL, workspace, versions, actual launcher
62
- else No listener
63
- H--xM: Connection refused
64
- M->>S: Launch session process on 127.0.0.1:3210
65
- S-->>M: Ready after bind, initialization, and initial lease
66
- M-->>D: started, URL, workspace, versions
67
- else Recognized older TaskChef listener for this workspace
68
- M->>S: Simple guarded graceful shutdown request
69
- S-->>M: Port released within bounded wait
70
- M->>S: Launch installed session version
71
- M-->>D: started, URL, workspace, versions
72
- else Newer, unknown, malformed, or different workspace
73
- H-->>M: Missing or incompatible identity
74
- M-->>D: Actionable conflict, listener untouched
75
- end
76
- D-->>D: Answer, report, or dispatch
77
- Note over D: Created-thread directive, when any, precedes final dashboard link
78
- ```
79
-
80
- When one MCP transport or plugin process closes, the dashboard remains while a
81
- registered Codex session PID is alive. After every registered PID disappears
82
- for the grace period, the dashboard closes and exits. A foreground
83
- `taskchef dashboard` listener identifies itself as `standalone`. An
84
- exact-current, healthy same-workspace listener with a recognized `standalone`,
85
- `mcp`, or `session` launcher may be reused; ensure reports its actual launcher,
86
- and only `session` listeners register Codex PID leases. No TaskChef path
87
- terminates an incompatible listener or installs OS persistence.
88
-
89
- Autostart and explicit ensures share the same manager promise, while
90
- cross-process port races converge through exact health recognition and port binding. An
91
- explicit `dashboard.autostart: false` skips only activation-time ensure. Any
92
- failure is reduced to a fixed stderr diagnostic; tool registration and MCP
93
- availability continue. Activation never opens a browser.
94
-
95
- ## Release-install verification
96
-
97
- The practical release-install sequence ends in this order:
98
-
99
- 1. Install the released plugin.
100
- 2. Activate or reload its new TaskChef MCP process.
101
- 3. Run `$taskchef-dashboard` or call `ensure_dashboard`.
102
- 4. Verify the expected TaskChef version, protocol `serverVersion`, reported
103
- launcher, canonical workspace, and canonical URL returned by the dashboard identity.
104
-
105
- Replacing plugin files alone cannot execute autostart because old code remains
106
- in an already-running MCP process. Installation does not necessarily reload
107
- Codex. Activation of the installed MCP asks a recognized older same-workspace
108
- TaskChef listener to shut down gracefully. If the listener accepts, TaskChef
109
- waits boundedly for its port and starts the installed session version. If the
110
- listener lacks the compatible callback or refuses the request, it remains
111
- running and ensure reports a conflict. An exact-compatible same-workspace
112
- dashboard with any recognized launcher may be reused; a newer or unknown
113
- listener is never terminated or replaced. A release using the former HMAC
114
- control protocol needs one verified stop through its existing lifecycle before
115
- the simple control protocol can take over.
116
-
117
- ## Normal delegation and self-linking
118
-
119
- The dispatcher uses native Codex project discovery for routing and MCP for
120
- TaskChef state. It never supplies executor identity.
121
-
122
- ```mermaid
123
- sequenceDiagram
124
- autonumber
125
- actor U as User
126
- participant D as Dispatcher skill
127
- participant M as TaskChef MCP
128
- participant W as workspace.js
129
- participant C as Native Codex
130
- participant E as Executor
131
- U->>D: Request outcome
132
- D->>C: List native projects once
133
- D->>M: reconcile_projects(snapshot)
134
- M-->>D: Counts, available targets, diagnostics
135
- par Prepare each outcome
136
- D->>M: prepare_dispatch()
137
- M-->>D: UUID, marker, projects
138
- end
139
- D->>D: Choose one configured and native project
140
- D->>M: record_task(id, project, title, instruction, null)
141
- M->>W: recordTask()
142
- W->>W: Lock, validate, append schema-10 snapshot
143
- W-->>M: working link-pending task
144
- M-->>D: task
145
- D->>C: Create executor with marked instruction
146
- C-->>D: Created-task reference
147
- D-->>U: Return immediately
148
- C->>E: Start executor and load $taskchef-executor
149
- E->>E: Read own CODEX_THREAD_ID
150
- E->>M: link_task(taskId, threadId)
151
- M->>W: linkTask()
152
- W->>W: Lock and replace null identity atomically
153
- W-->>E: linked working task
154
- ```
155
-
156
- Record-before-create makes native creation failure observable. Executor
157
- self-linking removes dispatcher-side polling, task search, title matching, and
158
- parent/child identity inference.
159
-
160
- The created executor is the stable visible owner. For repository work, an
161
- explicit combined investigate-or-plan-and-implement request runs a fresh
162
- Planner, Implementer, and Reviewer in that order. Direct implementation and a
163
- later request to implement prior investigation skip the Planner and run a fresh
164
- Implementer and Reviewer in the same visible task. Each role is resolved
165
- immediately before use when agent TOML exists; absent configuration inherits the
166
- parent's settings without requiring Python. Only one writer may be active.
167
- TaskChef records only the executor's ordinary request/result timeline; it does
168
- not track child phases or identities.
169
-
170
- The generated task begins with the complete assignment, adds the local reporting
171
- authorization paragraph separated by a blank line, leaves one blank line,
172
- then places one explicit `$taskchef-executor` invocation immediately before its
173
- final marker. Older
174
- recorded tasks with first-line HTML or heading markers and former inline
175
- protocol remain readable; the deprecated `report_result` alias preserves their
176
- semantic callbacks.
177
-
178
- ## State reporting
179
-
180
- The executor always establishes a `turnRef` before work. An exact native read
181
- supplies the same value as optional `turnId` metadata when available; otherwise
182
- the executor retains a fresh UUID and reports `turnId: null`. `report_state`
183
- records live turn state as a paired request/result timeline.
184
-
185
- That timeline is also the durable GitHub-link source. A working request names a
186
- known selected repository with its canonical GitHub URL. A terminal result
187
- keeps full issue and pull-request URLs, including separate child-repository and
188
- workspace/root pull requests. The dashboard does not recover omitted links by
189
- scanning the native Codex transcript, and the executor does not guess an
190
- ambiguous repository.
191
-
192
- ```mermaid
193
- sequenceDiagram
194
- autonumber
195
- participant E as Executor
196
- participant C as Native Codex task API
197
- participant M as TaskChef MCP
198
- participant W as workspace.js
199
- opt Native turn metadata available
200
- E->>C: Exact read of linked executor
201
- C-->>E: Current turn ID
202
- end
203
- E->>E: Retain native ID or generate fallback UUID turnRef
204
- E->>M: report_state(..., turnRef, turnId, working, requestSummary)
205
- M->>W: reportTaskState()
206
- W->>W: Append turn with request and null result
207
- E->>E: Work, finish, or reach semantic decision
208
- E->>M: report_state(..., same turnRef, same turnId, semantic status, summary)
209
- M->>W: reportTaskState()
210
- W->>W: Lock and validate identity and freshness
211
- alt Same current working turn
212
- W->>W: Fill that turn's result and derive compatibility results
213
- W-->>M: Updated task
214
- M-->>E: Recorded result
215
- else Same turn and same result
216
- W-->>M: Existing task
217
- M-->>E: Idempotent success
218
- else Conflict or stale turn
219
- W-->>M: Tool error
220
- M-->>E: Visible failure
221
- end
222
- ```
223
-
224
- A native approval prompt is not a semantic result. `needs_input` is reserved
225
- for a user decision or fact required to proceed.
226
-
227
- Ordinary completion returns after the terminal callback; it does not archive,
228
- hand off, close, navigate away from, or otherwise terminate the native Codex
229
- task. An archive requires an explicit request in the current assignment or
230
- follow-up for that exact task. `finish`, `complete`, `done`, `ship`, and cleanup
231
- alone do not authorize it. Any explicitly requested action that could make the
232
- executor unavailable must follow, never precede, an accepted terminal callback.
233
- If reporting fails, the later action is forbidden and the executor remains
234
- accessible. If the later action fails, its error does not reopen or replace the
235
- accepted semantic state.
236
-
237
- ## Follow-up turns
238
-
239
- Turn refs are freshness tokens for semantic callbacks. The workspace rejects a
240
- callback whose ref belongs to a historical turn. Fallback UUID refs are opaque
241
- and unordered; native-backed UUIDv7 refs retain native ordering so a delayed,
242
- previously unseen older start cannot replace newer native state.
243
-
244
- ```mermaid
245
- sequenceDiagram
246
- autonumber
247
- actor U as User
248
- participant E as Linked executor
249
- participant C as Native Codex task API
250
- participant M as TaskChef MCP
251
- participant W as workspace.js
252
- E->>M: report_state(..., turnA, needs_input, summaryA)
253
- M->>W: reportTaskState()
254
- W-->>M: needs_input snapshot
255
- M-->>E: needs_input snapshot
256
- U->>E: Provide decision
257
- E->>C: Read exact executor after follow-up
258
- C-->>E: turnB
259
- E->>M: report_state(..., turnB, working, requestB)
260
- M->>W: reportTaskState()
261
- W->>W: Require a fresh turnB ref and append request B
262
- W-->>M: working snapshot plus paired timeline
263
- M-->>E: working snapshot plus paired timeline
264
- E->>M: report_state(..., turnB, completed, summaryB)
265
- M->>W: reportTaskState()
266
- W-->>M: completed snapshot plus result B
267
- M-->>E: completed snapshot plus result B
268
- E->>M: report_state(..., turnA, completed, staleSummary)
269
- M->>W: Validate freshness
270
- W-->>M: Error: stale turnRef
271
- M-->>E: Visible tool error
272
- ```
273
-
274
- The executor contract therefore requires a new `turnRef` on every follow-up.
275
- Native turn reads are preferred metadata but optional; cached or inherited IDs
276
- and replacement fallback UUIDs for the same prompt are invalid.
277
-
278
- ## Interrupted-turn recovery
279
-
280
- A crash, MCP failure, app restart, or upgrade can leave the latest turn with a
281
- null result. A newer valid `working` report is the durable recovery signal. The
282
- workspace handles it inside the same lock and atomic replacement as every
283
- other lifecycle mutation:
284
-
285
- ```mermaid
286
- sequenceDiagram
287
- autonumber
288
- participant E as Resumed executor
289
- participant M as TaskChef MCP
290
- participant W as workspace.js
291
- participant F as tasks.jsonl
292
- E->>M: report_state(..., turnRefB, working, requestB)
293
- M->>W: reportTaskState()
294
- W->>W: Acquire workspace lock and validate turnRefB is new
295
- W->>W: Close unfinished turnA as interrupted
296
- W->>W: Append turnB with requestB and null result
297
- W->>F: One atomic schema-10 replacement
298
- W-->>M: working task projected from turnB
299
- M-->>E: Idempotent recovery success
300
- E->>M: late semantic result for turnA
301
- M->>W: Validate active turn and historical outcome
302
- W-->>M: Reject stale result
303
- M-->>E: Visible tool error
304
- ```
305
-
306
- The interrupted outcome uses only the fixed TaskChef-authored summary. It is
307
- visible in CLI and detail timelines but excluded from semantic `results` and
308
- `lastResult`. Compact dashboard cards therefore show request B with “In
309
- progress.” Notification reconciliation observes one new working event and does
310
- not manufacture a failed-result event. Exact retries of either working start
311
- return the current snapshot without reopening or duplicating a turn. Concurrent
312
- newer starts serialize under the lock, leaving one ordered timeline whose only
313
- unfinished entry is the latest turn.
314
-
315
- ## Link-pending and failure paths
316
-
317
- A failed or interrupted link never authorizes substantive work. The record
318
- remains a visible retry point for the same executor.
319
-
320
- ```mermaid
321
- sequenceDiagram
322
- autonumber
323
- participant E as Executor
324
- participant M as TaskChef MCP
325
- participant W as workspace.js
326
- E->>M: link_task(taskId, assertedThreadId)
327
- M->>W: linkTask()
328
- alt Exact eligible record and unused canonical UUIDv7
329
- W->>W: Atomic null-to-thread transition
330
- W-->>E: Linked task
331
- else MCP unavailable or call interrupted
332
- M--xE: Visible failure
333
- Note over E,W: Record remains link-pending
334
- else Wrong task, marker, state, or identity
335
- W-->>E: Validation error
336
- Note over E,W: No mutation
337
- end
338
- E->>E: Retry linking on a later turn before work
339
- ```
340
-
341
- If `CODEX_THREAD_ID` is missing, the executor reports the problem visibly and
342
- does not substitute `CODEX_SESSION_ID`, a parent ID, or search results.
343
-
344
- Native creation can fail after the durable record exists:
345
-
346
- ```mermaid
347
- sequenceDiagram
348
- autonumber
349
- participant D as Dispatcher
350
- participant M as TaskChef MCP
351
- participant W as workspace.js
352
- participant C as Native Codex
353
- D->>M: record_task(..., threadId null)
354
- M->>W: Append working record
355
- W-->>D: Recorded task
356
- D->>C: Create executor
357
- C--xD: Creation error
358
- D->>D: Generate and retain fallback UUID turnRef
359
- D->>M: report_state(taskId, null, turnRef, null, failed, boundedSummary)
360
- M->>W: Lock and store creation failure
361
- W-->>D: Failed task with turnRef and null native IDs
362
- D-->>D: Preserve original creation error, task ID, and turnRef
363
- ```
364
-
365
- The summary is bounded and excludes secrets, transcripts, and raw command
366
- output. A creation-failure record cannot later be linked.
367
-
368
- ## Dashboard update flow
369
-
370
- Every mutation rewrites `tasks.jsonl` atomically under the workspace lock.
371
- The monitor tolerates replacement races, validates a complete snapshot, and
372
- publishes only the newest state to each SSE client.
373
-
374
- ```mermaid
375
- sequenceDiagram
376
- autonumber
377
- participant M as MCP writer
378
- participant W as workspace.js
379
- participant F as tasks.jsonl
380
- participant D as Dashboard monitor
381
- participant B as Browser client
382
- participant C as Native Codex
383
- M->>W: link_task or report_state
384
- W->>W: Acquire shared lock
385
- W->>F: Atomic replacement
386
- W-->>M: Updated task
387
- F-->>D: Filesystem change
388
- D->>F: Bounded read from one descriptor
389
- D->>D: Validate current schema and sort
390
- D-->>B: Compact SSE snapshot with latestTurn only
391
- B->>D: GET task detail on demand
392
- D-->>B: Full validated history
393
- B->>D: Open task action
394
- alt Canonical Codex UUIDv7
395
- D->>C: Direct thread navigation
396
- else Null or non-native durable ID
397
- D->>D: Revalidate current project configuration
398
- D->>C: Open configured project
399
- end
400
- ```
401
-
402
- The dashboard binds to `127.0.0.1`, has no shared session state, limits task
403
- count, file size, result count, and display fields, and checks origin/authority
404
- for stateful local actions. Its bounded identity endpoint contains no task data
405
- or secrets. The monitor already validates the complete log, but
406
- snapshot/SSE list projections omit `turns` and derived `results` so repeated
407
- updates do not resend unnecessary history. A read-only per-task endpoint returns the full timeline only
408
- when the dialog opens. Historical project paths are untrusted until matched
409
- against current configuration.
410
-
411
- The browser derives one immutable Updates-panel snapshot when it first observes
412
- a semantic lifecycle transition. The snapshot keeps its event-time title,
413
- state/event, turn ref, optional Codex turn ID, timestamp, and relevant summary.
414
- Its identity combines task ID, turn ref, and event, with creation time as the fallback when creation
415
- has no turn. A separate seen-identity set outlives the bounded visible notices,
416
- individual dismissals, and Clear all, preventing replay after reconnect,
417
- normalization, or disappearance and reappearance. Per-task semantic signatures
418
- also remain as tombstones while a task is absent. When one compact snapshot
419
- first reveals a latest semantic result plus a newer working turn, the browser
420
- captures both. The first page snapshot is a quiet baseline so opening or
421
- refreshing the dashboard does not replay existing history. If a task is first
422
- observed in a progressed state after that baseline, creation is retained
423
- alongside the observable result and working events. The current task list is
424
- used only as the navigation target: if the task is gone, the notice stays
425
- readable and selection reports that current details are unavailable.
426
-
427
- The retained toast list is not live. Reconciliation passes only its newly added
428
- snapshots to a separate polite status region, so reconnect, dismissal, Clear
429
- all, and ordinary rerendering do not re-announce retained history. Toast action
430
- labels remain concise while `aria-describedby` connects the visible summary,
431
- event time, and missing-task explanation for assistive technology.
432
-
433
- ## Schema 4-9 migration
434
-
435
- `taskchef workspace migrate` acquires the same workspace lock as lifecycle
436
- writers, validates the complete legacy log, converts schema-4/5/6 results into
437
- request-unknown completed turns and preserves a newer working turn, then validates the
438
- complete candidate. Before replacement it writes and reads back an exclusive
439
- `tasks.jsonl.pre-v10-*.bak` file. Schema-7/8 timelines gain a durable `turnRef`:
440
- non-null `turnId` values are copied exactly and null IDs receive persisted UUIDs.
441
- Task and turn counts plus all refs are validated before and after the atomic
442
- replacement. Schema-9 turns gain explicit legacy or MCP provenance. A second
443
- run sees only schema 10 and returns unchanged without
444
- another backup. Unsupported or malformed input fails before backup/rewrite;
445
- after a later filesystem failure, the reported backup is the recovery source.
446
-
447
- ## Concurrency and trust boundaries
448
-
449
- Configuration and task mutations use the same `proper-lockfile` lock. ID and
450
- identity uniqueness checks, link eligibility, and result freshness occur
451
- inside the critical section. Writes use temporary files/hard links and atomic
452
- rename so readers see complete snapshots. Configuration replacement syncs the
453
- new file and workspace directory before its audit transaction is committed.
454
-
455
- Project reconciliation consumes one agent-supplied native schema-2 snapshot;
456
- the Node process never calls native Codex project tools. It filters to eligible
457
- local-host targets, canonicalizes paths, preserves curated and missing entries,
458
- and writes only when projects or identity bindings change. Explicit removal
459
- stores an exclusion. Learned aliases, repository ownership, and responsibilities
460
- stay in bounded configuration slots with compact evidence and never alter
461
- historical task snapshots. Optional learning happens after an accepted terminal
462
- report, so its failure cannot change that task result.
463
-
464
- The dispatcher controls routing and immutable intent. The executor controls its
465
- cooperatively asserted identity and semantic result. Neither identity nor
466
- summary is cryptographically authenticated; this is a local single-user trust
467
- model. Managed files, instructions, project snapshots, MCP inputs, and dashboard
468
- requests are validated at every action boundary.
469
-
470
- Configuration schema 2 and task schemas 4 through 10 are accepted. Schemas
471
- 4-9 are read/migration compatibility until an explicit migration or lifecycle
472
- mutation upgrades each record to schema 10. Schema 10 persists `turns`, including
473
- timeline-only interrupted outcomes, and derives semantic-only `results` and
474
- `lastResult` plus `latestTurn` for compact compatibility. It also records turn
475
- provenance and the optimistic preconditions for manual dashboard outcomes. Other schemas
476
- are rejected without rewrite.
@@ -1,51 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { runDashboardSessionProcess } from "../src/dashboard-session-process.js";
4
-
5
- let cancelled = false;
6
- let runtime = null;
7
- let readySent = false;
8
- const startupController = new AbortController();
9
- process.on("message", (message) => {
10
- if (message?.type !== "cancel") return;
11
- cancelled = true;
12
- startupController.abort();
13
- if (runtime) void runtime.close().finally(() => { process.exitCode = 1; });
14
- });
15
- process.on("disconnect", () => {
16
- if (readySent) return;
17
- cancelled = true;
18
- startupController.abort();
19
- if (runtime) void runtime.close().finally(() => { process.exitCode = 1; });
20
- });
21
-
22
- try {
23
- runtime = await runDashboardSessionProcess({ signal: startupController.signal });
24
- if (cancelled) {
25
- await runtime.close();
26
- throw Object.assign(new Error("dashboard session startup was cancelled"), {
27
- code: "TASKCHEF_DASHBOARD_START_TIMEOUT",
28
- });
29
- }
30
- if (process.send && process.connected) {
31
- readySent = true;
32
- await new Promise((resolve, reject) => {
33
- process.send({ type: "ready", port: runtime.server.port }, (error) =>
34
- error ? reject(error) : resolve());
35
- });
36
- if (process.connected) process.disconnect();
37
- }
38
- } catch (error) {
39
- readySent = false;
40
- if (runtime) await runtime.close().catch(() => {});
41
- if (process.send && process.connected) {
42
- await new Promise((resolve) => {
43
- process.send({
44
- type: "error",
45
- code: typeof error?.code === "string" ? error.code : "TASKCHEF_DASHBOARD_START_FAILED",
46
- }, () => resolve());
47
- }).catch(() => {});
48
- if (process.connected) process.disconnect();
49
- }
50
- process.exitCode = 1;
51
- }
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { execFileSync } from "node:child_process";
4
- import { mkdtemp, mkdir } from "node:fs/promises";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import { performance } from "node:perf_hooks";
8
- import { fileURLToPath } from "node:url";
9
-
10
- import { addProject, initializeWorkspace } from "../index.js";
11
-
12
- const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
- const cli = path.join(repositoryRoot, "bin", "taskchef.js");
14
- const sampleCount = Number.parseInt(process.env.TASKCHEF_BENCHMARK_SAMPLES ?? "30", 10);
15
- if (!Number.isInteger(sampleCount) || sampleCount < 1) {
16
- throw new Error("TASKCHEF_BENCHMARK_SAMPLES must be a positive integer");
17
- }
18
-
19
- const root = await mkdtemp(path.join(os.tmpdir(), "taskchef-dispatch-benchmark-"));
20
- const workspace = path.join(root, "workspace");
21
- const project = path.join(root, "project");
22
- await mkdir(project);
23
- await initializeWorkspace(workspace);
24
- await addProject(workspace, { name: "benchmark", path: project, githubRepos: [] });
25
-
26
- function runTaskChef(args) {
27
- return execFileSync(process.execPath, [cli, ...args], { encoding: "utf8" });
28
- }
29
-
30
- function measure(operation) {
31
- const startedAt = performance.now();
32
- operation();
33
- return performance.now() - startedAt;
34
- }
35
-
36
- const baseline = [];
37
- const prepared = [];
38
- for (let index = 0; index < sampleCount; index += 1) {
39
- baseline.push(measure(() => {
40
- runTaskChef(["workspace", "path", "--json", "--workspace", workspace]);
41
- runTaskChef(["project", "list", "--json", "--workspace", workspace]);
42
- execFileSync(process.execPath, [
43
- "-e",
44
- "const { randomUUID } = require('node:crypto'); JSON.stringify({ taskId: randomUUID(), preparedAt: new Date().toISOString() });",
45
- ]);
46
- }));
47
- prepared.push(measure(() => {
48
- const result = JSON.parse(runTaskChef([
49
- "dispatch", "prepare", "--json", "--workspace", workspace,
50
- ]));
51
- if (result.projectCount !== 1 || !result.marker.includes(result.taskId)) {
52
- throw new Error("dispatch prepare returned an invalid benchmark result");
53
- }
54
- }));
55
- }
56
-
57
- function statistics(samples) {
58
- const sorted = [...samples].sort((left, right) => left - right);
59
- const percentile = (value) => sorted[Math.floor((sorted.length - 1) * value)];
60
- return {
61
- samples: sorted.length,
62
- medianMs: Number(percentile(0.5).toFixed(2)),
63
- p95Ms: Number(percentile(0.95).toFixed(2)),
64
- minMs: Number(sorted[0].toFixed(2)),
65
- maxMs: Number(sorted.at(-1).toFixed(2)),
66
- };
67
- }
68
-
69
- const baselineStats = statistics(baseline);
70
- const preparedStats = statistics(prepared);
71
- process.stdout.write(`${JSON.stringify({
72
- schemaVersion: 1,
73
- comparison: {
74
- baseline: {
75
- description: "workspace path + project list + external UUID/timestamp process",
76
- processCalls: 3,
77
- ...baselineStats,
78
- },
79
- dispatchPrepare: {
80
- description: "dispatch prepare",
81
- processCalls: 1,
82
- ...preparedStats,
83
- },
84
- savedProcessCalls: 2,
85
- medianSpeedup: Number((baselineStats.medianMs / preparedStats.medianMs).toFixed(2)),
86
- medianReductionPercent: Number(
87
- ((1 - preparedStats.medianMs / baselineStats.medianMs) * 100).toFixed(1),
88
- ),
89
- },
90
- }, null, 2)}\n`);