taskchef 6.1.2 → 7.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.
@@ -0,0 +1,236 @@
1
+ # TaskChef workflows
2
+
3
+ This developer and advanced-agent guide explains how the current implementation
4
+ moves data through TaskChef. The [specification](spec.md) is normative; the
5
+ [README](../README.md) owns user operation. The
6
+ [FirstMate comparison](firstmate-taskchef-comparison.md) is non-normative
7
+ research.
8
+
9
+ ## Implementation map
10
+
11
+ | Surface | Responsibility |
12
+ | --- | --- |
13
+ | `skills/taskchef-delegate/SKILL.md` | Split, route, record-before-create, create, return. |
14
+ | `skills/taskchef-bootstrap/SKILL.md` | Initialize current workspace and configure projects. |
15
+ | `skills/taskchef-report/SKILL.md` | Select cached tasks and perform bounded live checks. |
16
+ | `src/mcp.js` | Four structured lifecycle tools and MCP annotations. |
17
+ | `src/delegation.js` | UUID marker, executor contract paragraphs, and creation-failure handling. |
18
+ | `src/workspace.js` | Current schemas, validation, locking, atomic JSONL writes, linking, and result freshness. |
19
+ | `src/cli.js` | Administration, inspection, diagnostics, and dashboard startup. |
20
+ | `src/dashboard.js` | Validated snapshots, SSE fan-out, and bounded open actions. |
21
+
22
+ The MCP process resolves `TASKCHEF_WORKSPACE` once and never accepts a model
23
+ supplied path. The CLI resolves `--workspace`, then the environment, then the
24
+ per-user default.
25
+
26
+ ## Normal delegation and self-linking
27
+
28
+ The dispatcher uses native Codex project discovery for routing and MCP for
29
+ TaskChef state. It never supplies executor identity.
30
+
31
+ ```mermaid
32
+ sequenceDiagram
33
+ autonumber
34
+ actor U as User
35
+ participant D as Dispatcher skill
36
+ participant M as TaskChef MCP
37
+ participant W as workspace.js
38
+ participant C as Native Codex
39
+ participant E as Executor
40
+ U->>D: Request outcome
41
+ par Route once
42
+ D->>C: List native projects
43
+ and Prepare each outcome
44
+ D->>M: prepare_dispatch()
45
+ M->>W: prepareDispatch()
46
+ W-->>M: UUID, marker, projects
47
+ M-->>D: preparation
48
+ end
49
+ D->>D: Choose one configured and native project
50
+ D->>M: record_task(id, project, title, instruction, null)
51
+ M->>W: recordTask()
52
+ W->>W: Lock, validate, append schema-4 snapshot
53
+ W-->>M: working link-pending task
54
+ M-->>D: task
55
+ D->>C: Create executor with marked instruction
56
+ C-->>D: Created-task reference
57
+ D-->>U: Return immediately
58
+ C->>E: Start executor
59
+ E->>E: Read own CODEX_THREAD_ID
60
+ E->>M: link_task(taskId, threadId)
61
+ M->>W: linkTask()
62
+ W->>W: Lock and replace null identity atomically
63
+ W-->>E: linked working task
64
+ ```
65
+
66
+ Record-before-create makes native creation failure observable. Executor
67
+ self-linking removes dispatcher-side polling, task search, title matching, and
68
+ parent/child identity inference.
69
+
70
+ ## Result reporting
71
+
72
+ The executor obtains the turn identity from an exact native read of its own
73
+ linked task. `report_result` updates only the latest semantic snapshot.
74
+
75
+ ```mermaid
76
+ sequenceDiagram
77
+ autonumber
78
+ participant E as Executor
79
+ participant C as Native Codex task API
80
+ participant M as TaskChef MCP
81
+ participant W as workspace.js
82
+ E->>E: Finish or reach semantic decision
83
+ E->>C: Exact read of linked executor
84
+ C-->>E: Current turn ID
85
+ E->>M: report_result(taskId, threadId, turnId, status, summary)
86
+ M->>W: reportTaskResult()
87
+ W->>W: Lock and validate identity and freshness
88
+ alt Fresh result
89
+ W->>W: Replace status, summary, turnId, updatedAt, updatedBy
90
+ W-->>M: Updated task
91
+ M-->>E: Recorded result
92
+ else Same turn and same result
93
+ W-->>M: Existing task
94
+ M-->>E: Idempotent success
95
+ else Conflict or stale turn
96
+ W-->>M: Tool error
97
+ M-->>E: Visible failure
98
+ end
99
+ ```
100
+
101
+ A native approval prompt is not a semantic result. `needs_input` is reserved
102
+ for a user decision or fact required to proceed.
103
+
104
+ ## Follow-up turns
105
+
106
+ Turn IDs are freshness tokens for semantic callbacks. Lexical UUIDv7 order lets
107
+ the workspace reject a callback from an older executor turn.
108
+
109
+ ```mermaid
110
+ sequenceDiagram
111
+ autonumber
112
+ actor U as User
113
+ participant E as Linked executor
114
+ participant C as Native Codex task API
115
+ participant M as TaskChef MCP
116
+ participant W as workspace.js
117
+ E->>M: report_result(..., turnA, needs_input, summaryA)
118
+ M->>W: Store turnA
119
+ W-->>E: needs_input snapshot
120
+ U->>E: Provide decision
121
+ E->>C: Read exact executor after follow-up
122
+ C-->>E: turnB
123
+ E->>M: report_result(..., turnB, completed, summaryB)
124
+ M->>W: Require turnB greater than turnA
125
+ W-->>E: completed snapshot
126
+ E->>M: report_result(..., turnA, completed, staleSummary)
127
+ M->>W: Validate freshness
128
+ W-->>E: Error: turn is not newer
129
+ ```
130
+
131
+ The executor contract therefore requires a new exact read on every follow-up;
132
+ cached or inherited turn IDs are invalid.
133
+
134
+ ## Link-pending and failure paths
135
+
136
+ A failed or interrupted link never authorizes substantive work. The record
137
+ remains a visible retry point for the same executor.
138
+
139
+ ```mermaid
140
+ sequenceDiagram
141
+ autonumber
142
+ participant E as Executor
143
+ participant M as TaskChef MCP
144
+ participant W as workspace.js
145
+ E->>M: link_task(taskId, assertedThreadId)
146
+ M->>W: linkTask()
147
+ alt Exact eligible record and unused canonical UUIDv7
148
+ W->>W: Atomic null-to-thread transition
149
+ W-->>E: Linked task
150
+ else MCP unavailable or call interrupted
151
+ M--xE: Visible failure
152
+ Note over E,W: Record remains link-pending
153
+ else Wrong task, marker, state, or identity
154
+ W-->>E: Validation error
155
+ Note over E,W: No mutation
156
+ end
157
+ E->>E: Retry linking on a later turn before work
158
+ ```
159
+
160
+ If `CODEX_THREAD_ID` is missing, the executor reports the problem visibly and
161
+ does not substitute `CODEX_SESSION_ID`, a parent ID, or search results.
162
+
163
+ Native creation can fail after the durable record exists:
164
+
165
+ ```mermaid
166
+ sequenceDiagram
167
+ autonumber
168
+ participant D as Dispatcher
169
+ participant M as TaskChef MCP
170
+ participant W as workspace.js
171
+ participant C as Native Codex
172
+ D->>M: record_task(..., threadId null)
173
+ M->>W: Append working record
174
+ W-->>D: Recorded task
175
+ D->>C: Create executor
176
+ C--xD: Creation error
177
+ D->>M: report_result(taskId, null, null, failed, boundedSummary)
178
+ M->>W: Lock and store creation failure
179
+ W-->>D: Failed task with null IDs
180
+ D-->>D: Preserve original creation error and task ID
181
+ ```
182
+
183
+ The summary is bounded and excludes secrets, transcripts, and raw command
184
+ output. A creation-failure record cannot later be linked.
185
+
186
+ ## Dashboard update flow
187
+
188
+ Every mutation rewrites `tasks.jsonl` atomically under the workspace lock.
189
+ The monitor tolerates replacement races, validates a complete snapshot, and
190
+ publishes only the newest state to each SSE client.
191
+
192
+ ```mermaid
193
+ sequenceDiagram
194
+ autonumber
195
+ participant M as MCP writer
196
+ participant W as workspace.js
197
+ participant F as tasks.jsonl
198
+ participant D as Dashboard monitor
199
+ participant B as Browser client
200
+ participant C as Native Codex
201
+ M->>W: link_task or report_result
202
+ W->>W: Acquire shared lock
203
+ W->>F: Atomic replacement
204
+ W-->>M: Updated task
205
+ F-->>D: Filesystem change
206
+ D->>F: Bounded read from one descriptor
207
+ D->>D: Validate current schema and sort
208
+ D-->>B: SSE snapshot
209
+ B->>D: Open task action
210
+ alt Canonical Codex UUIDv7
211
+ D->>C: Direct thread navigation
212
+ else Null or non-native durable ID
213
+ D->>D: Revalidate current project configuration
214
+ D->>C: Open configured project
215
+ end
216
+ ```
217
+
218
+ The dashboard binds to `127.0.0.1`, has no shared session state, limits
219
+ request bodies, and checks origin/authority for stateful local actions. Historical
220
+ project paths are untrusted until matched against current configuration.
221
+
222
+ ## Concurrency and trust boundaries
223
+
224
+ Configuration and task mutations use the same `proper-lockfile` lock. ID and
225
+ identity uniqueness checks, link eligibility, and result freshness occur
226
+ inside the critical section. Writes use temporary files/hard links and atomic
227
+ rename so readers see complete snapshots.
228
+
229
+ The dispatcher controls routing and immutable intent. The executor controls its
230
+ cooperatively asserted identity and semantic result. Neither identity nor
231
+ summary is cryptographically authenticated; this is a local single-user trust
232
+ model. Managed files, instructions, project snapshots, MCP inputs, and dashboard
233
+ requests are validated at every action boundary.
234
+
235
+ Only current configuration schema 2 and task schema 4 are accepted.
236
+ Unsupported data is rejected without rewrite.
package/index.js CHANGED
@@ -5,7 +5,6 @@ export {
5
5
  canonicalGitRoot,
6
6
  doctorWorkspace,
7
7
  ensureWorkspaceInstructions,
8
- ensureWorkspaceSkills,
9
8
  filterTasks,
10
9
  importProjects,
11
10
  initializeWorkspace,
@@ -17,7 +16,6 @@ export {
17
16
  readTask,
18
17
  recordTask,
19
18
  reportTaskResult,
20
- resolveTask,
21
19
  requireSafeId,
22
20
  removeProject,
23
21
  validateConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "6.1.2",
3
+ "version": "7.0.0",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
@@ -23,10 +23,11 @@
23
23
  "assets",
24
24
  "BACKLOG.md",
25
25
  "bin",
26
- "docs/delegation-design.md",
26
+ "docs/spec.md",
27
+ "docs/workflows.md",
28
+ "docs/firstmate-taskchef-comparison.md",
27
29
  "index.js",
28
30
  "mcp",
29
- "SPEC.md",
30
31
  "scripts/benchmark-dispatch-prepare.js",
31
32
  "scripts/e2e-benchmark.js",
32
33
  "src",
@@ -33,10 +33,10 @@ function measure(operation) {
33
33
  return performance.now() - startedAt;
34
34
  }
35
35
 
36
- const legacy = [];
36
+ const baseline = [];
37
37
  const prepared = [];
38
38
  for (let index = 0; index < sampleCount; index += 1) {
39
- legacy.push(measure(() => {
39
+ baseline.push(measure(() => {
40
40
  runTaskChef(["workspace", "path", "--json", "--workspace", workspace]);
41
41
  runTaskChef(["project", "list", "--json", "--workspace", workspace]);
42
42
  execFileSync(process.execPath, [
@@ -66,15 +66,15 @@ function statistics(samples) {
66
66
  };
67
67
  }
68
68
 
69
- const legacyStats = statistics(legacy);
69
+ const baselineStats = statistics(baseline);
70
70
  const preparedStats = statistics(prepared);
71
71
  process.stdout.write(`${JSON.stringify({
72
72
  schemaVersion: 1,
73
73
  comparison: {
74
- legacy: {
74
+ baseline: {
75
75
  description: "workspace path + project list + external UUID/timestamp process",
76
76
  processCalls: 3,
77
- ...legacyStats,
77
+ ...baselineStats,
78
78
  },
79
79
  dispatchPrepare: {
80
80
  description: "dispatch prepare",
@@ -82,9 +82,9 @@ process.stdout.write(`${JSON.stringify({
82
82
  ...preparedStats,
83
83
  },
84
84
  savedProcessCalls: 2,
85
- medianSpeedup: Number((legacyStats.medianMs / preparedStats.medianMs).toFixed(2)),
85
+ medianSpeedup: Number((baselineStats.medianMs / preparedStats.medianMs).toFixed(2)),
86
86
  medianReductionPercent: Number(
87
- ((1 - preparedStats.medianMs / legacyStats.medianMs) * 100).toFixed(1),
87
+ ((1 - preparedStats.medianMs / baselineStats.medianMs) * 100).toFixed(1),
88
88
  ),
89
89
  },
90
90
  }, null, 2)}\n`);
@@ -15,8 +15,8 @@ all deterministic workspace operations.
15
15
  ## Boundaries
16
16
 
17
17
  - Keep implementation, tests, and reports in the TaskChef source repository.
18
- - Keep only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in a dispatcher
19
- workspace.
18
+ - Create and manage only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in a
19
+ dispatcher workspace. Preserve unrelated user-owned paths.
20
20
  - Do not dispatch tasks or report on executor threads during bootstrap unless
21
21
  the user separately requests those actions.
22
22
  - Never create hooks, schedules, polling, or daemons. TaskChef executors
@@ -37,9 +37,9 @@ all deterministic workspace operations.
37
37
  CLI discovered from the current desktop environment; never invoke
38
38
  `codex add` or hard-code an application bundle path.
39
39
  3. `workspace init` takes no stdin, creates an empty
40
- configuration when missing, creates the one-entry-per-task JSONL log, refreshes
41
- managed instructions, and removes legacy TaskChef skill links. The installed
42
- plugin provides all three TaskChef skills outside the dispatcher workspace.
40
+ configuration when missing, creates the one-entry-per-task JSONL log, and
41
+ refreshes managed instructions. The installed plugin provides all three
42
+ TaskChef skills outside the dispatcher workspace.
43
43
  4. Run `doctor --json` after setup or when the user asks to diagnose the
44
44
  workspace. Doctor is read-only. Rerun `workspace init --json` to repair the
45
45
  managed scaffold.
@@ -86,6 +86,5 @@ Example managed-workspace import entry:
86
86
  }
87
87
  ```
88
88
 
89
- `workspace init` safely migrates schema-version-1 configuration: a string
90
- `githubRepo` becomes a one-item `githubRepos` list and `null` becomes
91
- `githubRepos: []`. All subsequent configuration writes use schema version 2.
89
+ TaskChef accepts configuration schema version 2 only. `githubRepos` is always
90
+ an array; unsupported configuration is rejected without being rewritten.
@@ -23,8 +23,8 @@ reloaded or installed.
23
23
 
24
24
  ## Boundaries
25
25
 
26
- - Keep only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in the dispatcher
27
- workspace.
26
+ - Treat only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` as TaskChef-managed
27
+ dispatcher files. Preserve unrelated user-owned paths.
28
28
  - Use real Codex tasks, never collaboration or subagent tools.
29
29
  - Never use hooks, schedules, daemons, background monitors, recent-task
30
30
  searches, transcripts, hidden reasoning, or polling for identity.
@@ -78,10 +78,3 @@ user decision, not live approval UI.
78
78
 
79
79
  The filesystem watcher surfaces `link_task` and `report_result` writes to the
80
80
  dashboard. The linked child ID drives the exact Codex deep link.
81
-
82
- ## Legacy recovery
83
-
84
- `taskchef task resolve` exists only for unresolved records created before the
85
- self-linking schema. Require an exact marker match and a unique durable child
86
- ID. The command rejects new self-linking records. Never edit `tasks.jsonl`
87
- directly.
@@ -29,8 +29,8 @@ all deterministic task-log operations.
29
29
  - Use the full list only when the user asks for an overview of the task history.
30
30
  2. For an overview, select only attention-worthy candidates before detailed
31
31
  reads:
32
- - always include `working`, `needs_input`, null-status legacy entries, and
33
- entries with a null `threadId`;
32
+ - always include `working`, `needs_input`, and entries with a null
33
+ `threadId`;
34
34
  - include `completed` and `failed` entries updated during the last seven
35
35
  days;
36
36
  - omit older terminal entries by default and report the omitted count;
@@ -75,12 +75,8 @@ all deterministic task-log operations.
75
75
  completed, failed, unresolved, or unknown. Show the cached summary when it
76
76
  remains fresh. If a newer turn exists without a callback, describe the live
77
77
  state and label the cached result stale rather than overwriting it.
78
- 6. Never edit `tasks.jsonl` directly during reporting. The task API preserves
79
- each record's persisted `schemaVersion`: a schema 4 null identity is executor
80
- link-pending and must be retried by that executor, while a schema 1-3 null
81
- identity is a legacy recovery candidate. Manual recovery may call
82
- `task resolve` only for the latter after one exact structured marker match.
83
- Never persist inferred status,
78
+ 6. Never edit `tasks.jsonl` directly during reporting. A null identity is
79
+ executor link-pending and must be retried by that executor. Never persist inferred status,
84
80
  transcripts, prose classifications, or hidden reasoning. Do not poll or wait.
85
81
 
86
82
  If the task history is empty, say that TaskChef has not recorded any tasks. If
package/src/cli.js CHANGED
@@ -18,7 +18,6 @@ import {
18
18
  recordTask,
19
19
  removeProject,
20
20
  requireSafeId,
21
- resolveTask,
22
21
  } from "./workspace.js";
23
22
 
24
23
  const BLANK_TABLE_CELL = Symbol("blank table cell");
@@ -241,7 +240,6 @@ async function initialize(args) {
241
240
  `Configuration: ${value.config.action}`,
242
241
  `Task log: ${value.tasks.action}`,
243
242
  `Instructions: ${value.instructions.action}`,
244
- `Legacy skill links removed: ${value.legacySkills.removed.length}`,
245
243
  ...(value.registration ? [`Codex opening: ${value.registration.status}`] : []),
246
244
  ].join("\n"));
247
245
  return registrationFailed ? 5 : 0;
@@ -353,22 +351,6 @@ async function taskRecord(args) {
353
351
  return 0;
354
352
  }
355
353
 
356
- async function taskResolve(args) {
357
- if (!args[2] || args[2].startsWith("--")) throw new Error("task resolve requires a task ID");
358
- validateCommandArgs(args, 3, {
359
- values: ["--thread-id", "--workspace"],
360
- switches: ["--json"],
361
- });
362
- if (!args.includes("--thread-id")) throw new Error("task resolve requires --thread-id");
363
- const task = await resolveTask(
364
- workspaceRoot(args),
365
- args[2],
366
- option(args, "--thread-id"),
367
- );
368
- print(task, args, (value) => `Resolved ${value.id}: ${value.threadId}`);
369
- return 0;
370
- }
371
-
372
354
  async function taskShow(args) {
373
355
  validateCommandArgs(args, 3, { values: ["--workspace"], switches: ["--json"] });
374
356
  print(await readTaskForShow(workspaceRoot(args), args[2]), args, taskDetails);
@@ -464,13 +446,11 @@ Usage:
464
446
  taskchef project remove <name> [--json] [--workspace <path>]
465
447
  taskchef dispatch prepare [--json] [--workspace <path>]
466
448
  taskchef task record [--json] [--workspace <path>]
467
- taskchef task resolve <legacy-task-id> --thread-id <thread-id> [--json] [--workspace <path>]
468
449
  taskchef task show <task-id-or-8-character-prefix> [--json] [--workspace <path>]
469
450
  taskchef task list [--project <name-or-path>] [--ascending] [--full-id] [--json] [--workspace <path>]
470
451
  taskchef task summary [--json] [--workspace <path>]
471
452
 
472
453
  Task record reads one JSON value from closed, non-interactive standard input.
473
- Task resolve is a legacy migration command and rejects self-linking task records.
474
454
  Task show accepts a full task ID or the exact 8-character ID printed by task list.
475
455
  Task show prints human-readable details by default; --json prints the complete task object.
476
456
  Project import reads a JSON
@@ -497,7 +477,6 @@ export async function runCli(args) {
497
477
  if (args[0] === "project" && args[1] === "remove") return projectRemove(args);
498
478
  if (args[0] === "dispatch" && args[1] === "prepare") return dispatchPrepare(args);
499
479
  if (args[0] === "task" && args[1] === "record") return taskRecord(args);
500
- if (args[0] === "task" && args[1] === "resolve") return taskResolve(args);
501
480
  if (args[0] === "task" && args[1] === "show" && args[2]) return taskShow(args);
502
481
  if (args[0] === "task" && args[1] === "list") return taskList(args);
503
482
  if (args[0] === "task" && args[1] === "summary") return taskSummary(args);
@@ -25,6 +25,7 @@ const elements = {
25
25
  connectionLabel: document.querySelector("#connection-label"),
26
26
  copyThreadId: document.querySelector("#copy-thread-id"),
27
27
  dashboardMessage: document.querySelector("#dashboard-message"),
28
+ dashboardMessageText: document.querySelector("#dashboard-message-text"),
28
29
  dateFilter: document.querySelector("#date-filter"),
29
30
  dialog: document.querySelector("#task-dialog"),
30
31
  dialogInstruction: document.querySelector("#dialog-instruction"),
@@ -32,6 +33,7 @@ const elements = {
32
33
  dialogProject: document.querySelector("#dialog-project"),
33
34
  dialogSummary: document.querySelector("#dialog-summary"),
34
35
  dialogTitle: document.querySelector("#dialog-title"),
36
+ dismissDashboardMessage: document.querySelector("#dismiss-dashboard-message"),
35
37
  emptyState: document.querySelector("#empty-state"),
36
38
  notifications: document.querySelector("#notifications"),
37
39
  openProject: document.querySelector("#open-codex"),
@@ -227,7 +229,7 @@ function applySnapshot(snapshot) {
227
229
  }
228
230
 
229
231
  function showMessage(message) {
230
- elements.dashboardMessage.textContent = message;
232
+ elements.dashboardMessageText.textContent = message;
231
233
  elements.dashboardMessage.hidden = false;
232
234
  }
233
235
 
@@ -245,6 +247,9 @@ events.addEventListener("dashboard-error", (event) => {
245
247
  elements.projectFilter.addEventListener("change", render);
246
248
  elements.statusFilter.addEventListener("change", render);
247
249
  elements.dateFilter.addEventListener("change", render);
250
+ elements.dismissDashboardMessage.addEventListener("click", () => {
251
+ elements.dashboardMessage.hidden = true;
252
+ });
248
253
  elements.clearNotifications.addEventListener("click", () => {
249
254
  state.notifications = [];
250
255
  renderNotifications();
@@ -52,7 +52,10 @@
52
52
  <p id="task-count" class="task-count" aria-live="polite"></p>
53
53
  </section>
54
54
 
55
- <div id="dashboard-message" class="dashboard-message" role="status" hidden></div>
55
+ <div id="dashboard-message" class="dashboard-message" hidden>
56
+ <span id="dashboard-message-text" role="status" aria-live="polite"></span>
57
+ <button id="dismiss-dashboard-message" class="icon-button" type="button" aria-label="Dismiss dashboard message">×</button>
58
+ </div>
56
59
  <section aria-labelledby="tasks-heading">
57
60
  <h2 id="tasks-heading" class="visually-hidden">Tasks</h2>
58
61
  <div id="task-list" class="task-list"></div>
@@ -90,7 +90,10 @@ time { font-size: 0.78rem; }
90
90
  .empty-state { padding: 60px 20px; border: 1px dashed var(--border); border-radius: 10px; text-align: center; color: var(--muted); }
91
91
  .empty-state h2 { margin-bottom: 6px; color: var(--text); }
92
92
 
93
- .dashboard-message { margin-bottom: 18px; padding: 11px 14px; border: 1px solid #d8c58e; border-radius: 7px; background: #fff7de; color: #604613; }
93
+ .dashboard-message { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; padding: 8px 8px 8px 14px; border: 1px solid #d8c58e; border-radius: 7px; background: #fff7de; color: #604613; }
94
+ .dashboard-message[hidden] { display: none; }
95
+ .dashboard-message span { flex: 1; }
96
+ .dashboard-message .icon-button { flex: none; padding: 4px 7px; color: inherit; }
94
97
 
95
98
  .notifications { position: fixed; right: 20px; bottom: 20px; z-index: 3; width: min(380px, calc(100vw - 40px)); padding: 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow); }
96
99
  .notifications-heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
package/src/dashboard.js CHANGED
@@ -521,7 +521,7 @@ export async function createDashboardServer({
521
521
  else await openWorkspaceInCodex(canonicalProjectPath);
522
522
  sendJson(response, 202, {
523
523
  message: task.threadId
524
- ? "Opened the project in Codex; this legacy thread ID cannot use direct navigation."
524
+ ? "Opened the project in Codex; this thread ID cannot use direct navigation."
525
525
  : "Opened the project in Codex; this task does not yet have a thread ID.",
526
526
  });
527
527
  } catch {
package/src/delegation.js CHANGED
@@ -8,7 +8,6 @@ const UUID_SOURCE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12
8
8
  const UUID_PATTERN = new RegExp(`^${UUID_SOURCE}$`);
9
9
  const CODEX_UUID_V7_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
10
10
  const TASKCHEF_MARKER_PATTERN = new RegExp(`^<!-- taskchef_id=(${UUID_SOURCE}) -->$`);
11
- const LEGACY_TASKCHEF_MARKER_PATTERN = new RegExp(`^# taskchef_id=(${UUID_SOURCE})$`);
12
11
 
13
12
  function requireObject(value, name) {
14
13
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} must be an object`);
@@ -82,16 +81,13 @@ export function taskChefMarker(taskId) {
82
81
  return `<!-- taskchef_id=${requireUuid(taskId)} -->`;
83
82
  }
84
83
 
85
- export function parseTaskChefMarker(instruction, { allowLegacyHeading = false } = {}) {
84
+ export function parseTaskChefMarker(instruction) {
86
85
  if (typeof instruction !== "string") return null;
87
86
  const firstLine = instruction.split(/\r?\n/, 1)[0];
88
87
  const currentMatch = firstLine.match(TASKCHEF_MARKER_PATTERN);
89
- if (currentMatch !== null) {
90
- const prefix = instruction.match(/^([^\r\n]*)(\r?\n)\2/);
91
- return prefix === null ? null : currentMatch[1];
92
- }
93
- if (!allowLegacyHeading) return null;
94
- return firstLine.match(LEGACY_TASKCHEF_MARKER_PATTERN)?.[1] ?? null;
88
+ if (currentMatch === null) return null;
89
+ const prefix = instruction.match(/^([^\r\n]*)(\r?\n)\2/);
90
+ return prefix === null ? null : currentMatch[1];
95
91
  }
96
92
 
97
93
  export function prepareDelegation(instruction, { taskId = randomUUID() } = {}) {
package/src/github.js CHANGED
@@ -59,11 +59,7 @@ export function canonicalGithubRepository(value, name = "githubRepos") {
59
59
  export function normalizeGithubRepositories(
60
60
  value,
61
61
  name = "githubRepos",
62
- { allowLegacyScalar = false } = {},
63
62
  ) {
64
- if (allowLegacyScalar && (value === null || typeof value === "string")) {
65
- value = value === null ? [] : [value];
66
- }
67
63
  if (!Array.isArray(value)) throw new Error(`${name} must be an array of GitHub repository URLs`);
68
64
  const repositories = [];
69
65
  const seen = new Set();
package/src/mcp.js CHANGED
@@ -18,22 +18,22 @@ const projectSchema = z.object({
18
18
  });
19
19
 
20
20
  const taskSchema = z.object({
21
- schemaVersion: z.number(),
21
+ schemaVersion: z.literal(4),
22
22
  id: z.string(),
23
23
  project: projectSchema,
24
24
  title: z.string(),
25
25
  instruction: z.string(),
26
26
  threadId: z.string().nullable(),
27
27
  createdAt: z.string(),
28
- status: z.enum(["working", "needs_input", "completed", "failed"]).nullable(),
28
+ status: z.enum(["working", "needs_input", "completed", "failed"]),
29
29
  summary: z.string().nullable(),
30
30
  turnId: z.string().nullable(),
31
- updatedAt: z.string().nullable(),
32
- updatedBy: z.enum(["dispatcher", "hook", "mcp"]).nullable(),
31
+ updatedAt: z.string(),
32
+ updatedBy: z.enum(["dispatcher", "mcp"]),
33
33
  });
34
34
 
35
35
  const preparationSchema = z.object({
36
- schemaVersion: z.number(),
36
+ schemaVersion: z.literal(1),
37
37
  workspace: z.string(),
38
38
  taskId: z.string(),
39
39
  preparedAt: z.string(),