taskchef 7.2.0 → 7.3.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.2.0",
3
+ "version": "7.3.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
@@ -87,17 +87,18 @@ $taskchef-delegate In payments, add structured logs for failed retries and test
87
87
 
88
88
  TaskChef prepares a UUID and marker, persists the task before native creation,
89
89
  creates the executor, and returns its task link. New executor instructions keep
90
- the assignment visible immediately after the marker and end with an explicit
91
- `$taskchef-executor` invocation. That skill reads the executor's own
92
- `CODEX_THREAD_ID`, self-links, and reports lifecycle state. Independent outcomes
93
- may become separate executors; dependent work should stay together.
90
+ the assignment visible from the first line, then place the correlation marker
91
+ immediately before an explicit `$taskchef-executor` invocation. That skill
92
+ reads the executor's own `CODEX_THREAD_ID`, self-links, and reports lifecycle
93
+ state. Independent outcomes may become separate executors; dependent work
94
+ should stay together.
94
95
 
95
96
  For example, TaskChef generates this shape:
96
97
 
97
98
  ```text
98
- <!-- taskchef_id=c0f010ff-84f2-4838-a69d-0ff1f5d721d7 -->
99
99
  Fix duplicate charges after a retry and add a regression test.
100
100
 
101
+ <!-- taskchef_id=c0f010ff-84f2-4838-a69d-0ff1f5d721d7 -->
101
102
  Use $taskchef-executor to execute and report this delegated TaskChef assignment.
102
103
  ```
103
104
 
@@ -108,7 +108,7 @@ also insert an interpretation layer between user and worker.
108
108
 
109
109
  ### TaskChef
110
110
 
111
- **Fact:** `prepare_dispatch` allocates a TaskChef UUID and exact first-line
111
+ **Fact:** `prepare_dispatch` allocates a TaskChef UUID and exact correlation
112
112
  marker. `record_task` persists the marked instruction before native task
113
113
  creation. The executor then reads its own `CODEX_THREAD_ID` and calls
114
114
  `link_task`; the dispatcher neither searches recent tasks nor repairs identity.
package/docs/spec.md CHANGED
@@ -18,7 +18,7 @@ is dated research, not contract.
18
18
  | **Delegated task** | One independently useful outcome represented by one TaskChef task UUID and snapshot. |
19
19
  | **Executor** | The native Codex task created to own and perform one delegated task. |
20
20
  | **Task record** | One complete JSON object in `tasks.jsonl`; it contains immutable intent/project fields and mutable identity/result fields. |
21
- | **Marker** | The exact first instruction line `<!-- taskchef_id=<lowercase full UUID> -->`; new instructions begin the assignment on the next line. |
21
+ | **Marker** | The exact correlation line `<!-- taskchef_id=<lowercase full UUID> -->`; new instructions place it after the complete assignment and immediately before the executor-skill invocation. |
22
22
  | **Record-before-create** | Persisting a link-pending task before asking Codex to create its executor. |
23
23
  | **Self-linking** | The executor's one-way registration of its own canonical Codex UUIDv7 from `CODEX_THREAD_ID`. |
24
24
  | **Link-pending** | A working task whose `threadId` is null and `updatedBy` is `dispatcher`. |
@@ -102,10 +102,11 @@ fields MUST NOT change after recording.
102
102
 
103
103
  1. The dispatcher MUST call `prepare_dispatch` once per outcome.
104
104
  2. It MUST choose exactly one configured project and exact native-project path.
105
- 3. It MUST build the instruction with the returned marker as line 1, the user's
106
- outcome beginning on line 2, and exactly one concise explicit
107
- `$taskchef-executor` invocation at the end after one blank line. It MUST NOT
108
- inline the executor protocol into a new instruction.
105
+ 3. It MUST build the instruction with the user's outcome beginning on line 1
106
+ and remaining uninterrupted, followed by one blank line, the returned
107
+ marker, and exactly one concise explicit `$taskchef-executor` invocation on
108
+ the final line. It MUST NOT inline the executor protocol into a new
109
+ instruction.
109
110
  4. It MUST call `record_task` with `threadId: null` before native creation.
110
111
  5. It MUST create exactly one native Codex executor and return immediately.
111
112
  6. The executor MUST read its own `CODEX_THREAD_ID` and call `link_task`
@@ -122,10 +123,15 @@ If native creation fails after recording, the dispatcher MUST call
122
123
  A link failure MUST remain visible and retryable; the executor MUST report it
123
124
  visibly and MUST NOT continue substantive work.
124
125
 
125
- Previously recorded instructions with the former blank line and inline
126
- executor protocol MUST remain marker-readable and executable. Their
126
+ Previously recorded instructions with a first-line HTML marker, the older
127
+ first-line `# taskchef_id=<full UUID>` heading, or the former blank line and
128
+ inline executor protocol MUST remain marker-readable and executable. Their
127
129
  `report_result` calls MUST remain supported by the deprecated alias. New
128
- instructions MUST use the explicit executor skill contract above.
130
+ instructions MUST use the trailing marker and explicit executor skill contract
131
+ above. A historical first-line instruction with an executor-skill invocation
132
+ MUST contain exactly one invocation as its final line. A former inline-protocol
133
+ instruction MUST retain non-whitespace task-specific content beyond its known
134
+ lifecycle paragraphs.
129
135
 
130
136
  `needs_input` MUST mean a semantic user decision or missing fact. A native
131
137
  approval prompt MUST remain live Codex state and MUST NOT be stored as
@@ -176,7 +182,7 @@ new preparation values, though it writes no state.
176
182
  | `id` | Non-empty string; MUST equal the instruction marker. |
177
183
  | `project` | Non-empty configured project path. |
178
184
  | `title` | Non-empty string. |
179
- | `instruction` | Non-empty string beginning with the exact marker and at least one following instruction line. |
185
+ | `instruction` | Non-empty string containing exactly one accepted marker and a non-empty assignment. New instructions use the required trailing marker and executor-invocation scaffold; historical first-line forms remain accepted. |
180
186
  | `threadId` | Literal null. |
181
187
 
182
188
  **Structured output:** `{ task: Task }`.
package/docs/workflows.md CHANGED
@@ -68,10 +68,11 @@ Record-before-create makes native creation failure observable. Executor
68
68
  self-linking removes dispatcher-side polling, task search, title matching, and
69
69
  parent/child identity inference.
70
70
 
71
- The generated task body begins immediately after the first-line marker and
72
- ends with one explicit `$taskchef-executor` invocation. Older recorded tasks
73
- whose marker is followed by a blank line and inline protocol remain readable;
74
- the deprecated `report_result` alias preserves their semantic callbacks.
71
+ The generated task begins with the complete assignment, then places its marker
72
+ immediately before one explicit `$taskchef-executor` invocation. Older
73
+ recorded tasks with first-line HTML or heading markers and former inline
74
+ protocol remain readable; the deprecated `report_result` alias preserves their
75
+ semantic callbacks.
75
76
 
76
77
  ## State reporting
77
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "7.2.0",
3
+ "version": "7.3.0",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
@@ -10,11 +10,18 @@ and return immediately.
10
10
 
11
11
  ## Invocation boundary
12
12
 
13
- A task whose initial structured `codexDelegation.input` starts with an exact
14
- `<!-- taskchef_id=<full UUID> -->` marker already owns that delegated
15
- assignment. Execute it in the current task. Do not re-dispatch it merely
16
- because it concerns TaskChef or a configured project. Explicit requests to
17
- delegate separate work remain valid.
13
+ A task whose initial structured `codexDelegation.input` contains either the
14
+ exact new trailing TaskChef marker plus final `$taskchef-executor` invocation,
15
+ an exact first-line HTML marker, or the historical first-line
16
+ `# taskchef_id=<full UUID>` heading already owns that delegated assignment.
17
+ This includes former inline-protocol tasks that lack the skill invocation.
18
+ An owned instruction must have exactly one accepted marker and a non-whitespace
19
+ task-specific assignment. If it contains an executor-skill invocation, require
20
+ exactly one as the final line. Marker-only, duplicate-marker, scaffold-only, or
21
+ misplaced-invocation inputs are not valid delegated tasks. Execute a valid one
22
+ in the current task. Do not re-dispatch it merely because it concerns TaskChef
23
+ or a configured project. Explicit requests to delegate separate work remain
24
+ valid.
18
25
 
19
26
  Use the bundled `prepare_dispatch`, `record_task`, and `report_state` MCP tools
20
27
  directly. Never fall back to shell writes. If a required tool is unavailable,
@@ -43,11 +50,13 @@ stop and report that the TaskChef plugin must be reloaded or installed.
43
50
  match and an exact native-project path. Ask instead of guessing.
44
51
  4. Build each executor instruction in this exact shape:
45
52
 
46
- - Keep the preparation's exact marker as the first line.
47
- - Begin the actual assignment on the second line, with no blank line after
48
- the marker.
49
- - End the instruction after one blank line with exactly:
53
+ - Begin with the actual assignment on the first line and keep its complete
54
+ body uninterrupted.
55
+ - After the assignment, add one blank line and the preparation's exact
56
+ marker on its own line.
57
+ - Immediately after the marker, end the instruction with exactly:
50
58
  `Use $taskchef-executor to execute and report this delegated TaskChef assignment.`
59
+ - Include exactly one marker and exactly one executor-skill invocation.
51
60
  - Do not inline executor ownership, identity, linking, or result-reporting
52
61
  protocol. The explicitly invoked executor skill owns those mechanics.
53
62
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: taskchef-executor
3
- description: "Execute an assignment whose first instruction line is an exact TaskChef task marker, including executor ownership, self-linking, per-turn lifecycle reporting, identity safety, and final semantic state. Use when explicitly invoked by a delegated TaskChef instruction or when resuming that same executor task. Do not use to dispatch work or report on other TaskChef tasks."
3
+ description: "Execute an assignment carrying either the new exact TaskChef marker-plus-invocation scaffold or an accepted historical first-line TaskChef marker or inline protocol. Includes executor ownership, self-linking, per-turn lifecycle reporting, identity safety, and final semantic state. Use when explicitly invoked by a new delegated instruction or when resuming the same new or historical executor task. Do not use to dispatch work or report on other TaskChef tasks."
4
4
  ---
5
5
 
6
6
  # TaskChef Executor
@@ -9,10 +9,12 @@ Own and execute the delegated assignment in the current Codex task. Do not
9
9
  re-dispatch it merely because it concerns TaskChef or a configured project.
10
10
  Explicit requests to delegate separate work remain valid.
11
11
 
12
- The instruction's exact first line is
13
- `<!-- taskchef_id=<full UUID> -->`. Treat that UUID as the TaskChef task ID.
14
- The assignment is the remaining instruction body; the final explicit skill
15
- invocation is lifecycle scaffolding, not part of the requested deliverable.
12
+ New instructions present the complete assignment first, followed by one blank
13
+ line, the exact `<!-- taskchef_id=<full UUID> -->` marker, and the final
14
+ explicit skill invocation. Treat that UUID as the TaskChef task ID. The
15
+ assignment is everything before the blank line that precedes the marker; the
16
+ marker and invocation are lifecycle scaffolding, not part of the deliverable.
17
+ Require exactly one marker and do not infer an ID from similar prose.
16
18
 
17
19
  ## Start every execution turn
18
20
 
@@ -66,4 +68,12 @@ Existing delegated tasks may include the former inline ownership, linking, and
66
68
  re-dispatching. Prefer `report_state` when available. If an older installed
67
69
  TaskChef exposes only `report_result`, follow its inline protocol; after an
68
70
  upgrade, the deprecated `report_result` alias remains available for exact
69
- legacy retries.
71
+ legacy retries. Also accept historical instructions whose exact HTML marker is
72
+ the first line, with or without the former blank line, and the older exact
73
+ first-line `# taskchef_id=<full UUID>` heading. These compatibility forms do
74
+ not change the identity or lifecycle rules above. For either first-line form,
75
+ the assignment follows the marker. Ignore the final executor invocation and
76
+ any recognizable former inline ownership, linking, working-state, or
77
+ result-reporting paragraphs as lifecycle scaffolding; execute the remaining
78
+ task-specific body. Require non-whitespace task-specific content and never
79
+ treat an invocation by itself as an assignment.
package/src/delegation.js CHANGED
@@ -9,11 +9,34 @@ export const EXECUTOR_WORKING_PARAGRAPH = "After a successful initial link, and
9
9
  /** @deprecated Historical v7 inline-prompt snapshot. New delegations use taskchef-executor. */
10
10
  export const EXECUTOR_RESULT_PARAGRAPH = "Before ending, read this exact Codex thread again and call TaskChef report_state for the same current working turn with status completed, needs_input, or failed and a concise summary. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.";
11
11
  export const EXECUTOR_SKILL_INVOCATION = "Use $taskchef-executor to execute and report this delegated TaskChef assignment.";
12
+ const HISTORICAL_RESULT_WITH_TURN_PARAGRAPH = "Before ending, call the TaskChef report_result MCP tool with the marked task ID, this executor's self-linked thread ID, the current turn ID from an exact native read of that same thread, completed, needs_input, or failed, and a concise summary. Never reuse a prior turn ID after a follow-up. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.";
13
+ const HISTORICAL_RESULT_PARAGRAPH = "Before ending, call the TaskChef report_result MCP tool with completed, needs_input, or failed and a concise summary. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.";
14
+ const HISTORICAL_EXECUTOR_SCAFFOLD_LINES = new Set([
15
+ EXECUTOR_OWNERSHIP_PARAGRAPH,
16
+ EXECUTOR_LINK_PARAGRAPH,
17
+ EXECUTOR_WORKING_PARAGRAPH,
18
+ EXECUTOR_RESULT_PARAGRAPH,
19
+ HISTORICAL_RESULT_WITH_TURN_PARAGRAPH,
20
+ HISTORICAL_RESULT_PARAGRAPH,
21
+ ]);
22
+ const HISTORICAL_INLINE_PROTOCOLS = [
23
+ [EXECUTOR_OWNERSHIP_PARAGRAPH, EXECUTOR_LINK_PARAGRAPH, EXECUTOR_WORKING_PARAGRAPH, EXECUTOR_RESULT_PARAGRAPH],
24
+ [EXECUTOR_OWNERSHIP_PARAGRAPH, EXECUTOR_LINK_PARAGRAPH, HISTORICAL_RESULT_WITH_TURN_PARAGRAPH],
25
+ [EXECUTOR_OWNERSHIP_PARAGRAPH, HISTORICAL_RESULT_PARAGRAPH],
26
+ [EXECUTOR_OWNERSHIP_PARAGRAPH],
27
+ ];
28
+
29
+ function hasTaskSpecificContent(lines) {
30
+ return lines.some((line) => (
31
+ line.trim().length > 0 && !HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line)
32
+ ));
33
+ }
12
34
 
13
35
  const UUID_SOURCE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
14
36
  const UUID_PATTERN = new RegExp(`^${UUID_SOURCE}$`);
15
37
  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}$/;
16
38
  const TASKCHEF_MARKER_PATTERN = new RegExp(`^<!-- taskchef_id=(${UUID_SOURCE}) -->$`);
39
+ const LEGACY_TASKCHEF_MARKER_PATTERN = new RegExp(`^# taskchef_id=(${UUID_SOURCE})$`);
17
40
 
18
41
  function requireObject(value, name) {
19
42
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} must be an object`);
@@ -89,10 +112,66 @@ export function taskChefMarker(taskId) {
89
112
 
90
113
  export function parseTaskChefMarker(instruction) {
91
114
  if (typeof instruction !== "string") return null;
92
- const firstLine = instruction.split(/\r?\n/, 1)[0];
93
- const currentMatch = firstLine.match(TASKCHEF_MARKER_PATTERN);
94
- if (currentMatch === null) return null;
95
- return /^[^\r\n]*\r?\n[\s\S]+$/.test(instruction) ? currentMatch[1] : null;
115
+ const lines = instruction.split(/\r\n|\r|\n/);
116
+ const hasHistoricalAssignment = () => {
117
+ const rest = lines.slice(1);
118
+ const executorSkillInvocationIndices = rest.flatMap((line, index) => (
119
+ line === EXECUTOR_SKILL_INVOCATION ? [index] : []
120
+ ));
121
+ if (executorSkillInvocationIndices.length > 0) {
122
+ const bodyLines = rest.slice(0, -2);
123
+ return executorSkillInvocationIndices.length === 1
124
+ && executorSkillInvocationIndices[0] === rest.length - 1
125
+ && lines.at(-1) === EXECUTOR_SKILL_INVOCATION
126
+ && lines.at(-2) === ""
127
+ && bodyLines.length > 0
128
+ && bodyLines[0].trim().length > 0
129
+ && bodyLines.at(-1).trim().length > 0
130
+ && hasTaskSpecificContent(bodyLines)
131
+ && !bodyLines.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line));
132
+ }
133
+
134
+ const containsInlineScaffold = rest.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line));
135
+ if (!containsInlineScaffold) return rest.join("\n").trim().length > 0;
136
+ return HISTORICAL_INLINE_PROTOCOLS.some((protocol) => {
137
+ const prefix = ["", ...protocol.flatMap((line) => [line, ""])];
138
+ if (!prefix.every((line, index) => rest[index] === line)) return false;
139
+ const bodyLines = rest.slice(prefix.length);
140
+ return hasTaskSpecificContent(bodyLines)
141
+ && !bodyLines.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line));
142
+ });
143
+ };
144
+ const currentMatches = lines.flatMap((line, index) => {
145
+ const match = line.match(TASKCHEF_MARKER_PATTERN);
146
+ return match === null ? [] : [{ id: match[1], index }];
147
+ });
148
+ const legacyMatches = lines.flatMap((line, index) => {
149
+ const match = line.match(LEGACY_TASKCHEF_MARKER_PATTERN);
150
+ return match === null ? [] : [{ id: match[1], index }];
151
+ });
152
+ if (currentMatches.length + legacyMatches.length !== 1) return null;
153
+
154
+ if (legacyMatches.length === 1) {
155
+ const [{ id, index }] = legacyMatches;
156
+ if (index !== 0) return null;
157
+ return hasHistoricalAssignment() ? id : null;
158
+ }
159
+
160
+ const [{ id, index }] = currentMatches;
161
+ if (index === 0) {
162
+ return hasHistoricalAssignment() ? id : null;
163
+ }
164
+ const executorSkillReferences = instruction.match(/\$taskchef-executor\b/gi) ?? [];
165
+ const isTrailingScaffold = index === lines.length - 2
166
+ && index >= 2
167
+ && lines[0].trim().length > 0
168
+ && lines.at(-3) === ""
169
+ && lines.at(-4).trim().length > 0
170
+ && lines.at(-1) === EXECUTOR_SKILL_INVOCATION
171
+ && hasTaskSpecificContent(lines.slice(0, index - 1))
172
+ && !lines.slice(0, index - 1).some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line))
173
+ && executorSkillReferences.length === 1;
174
+ return isTrailingScaffold ? id : null;
96
175
  }
97
176
 
98
177
  export function prepareDelegation(instruction, { taskId = randomUUID() } = {}) {
@@ -101,14 +180,25 @@ export function prepareDelegation(instruction, { taskId = randomUUID() } = {}) {
101
180
  throw new Error("instruction must begin with useful task content on its first line");
102
181
  }
103
182
  const body = rawBody.replace(/(?:(?:\r\n|\r|\n)[^\S\r\n]*)+$/, "");
104
- if (parseTaskChefMarker(body) !== null) throw new Error("instruction already contains a TaskChef marker");
183
+ if (body.split(/\r\n|\r|\n/).some((line) => (
184
+ TASKCHEF_MARKER_PATTERN.test(line) || LEGACY_TASKCHEF_MARKER_PATTERN.test(line)
185
+ ))) {
186
+ throw new Error("instruction already contains a TaskChef marker");
187
+ }
105
188
  if (/\$taskchef-executor\b/i.test(body)) {
106
189
  throw new Error("instruction contains a reserved TaskChef executor skill reference");
107
190
  }
191
+ const bodyLines = body.split(/\r\n|\r|\n/);
192
+ if (!hasTaskSpecificContent(bodyLines)) {
193
+ throw new Error("instruction must contain task-specific content, not only TaskChef lifecycle scaffolding");
194
+ }
195
+ if (bodyLines.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line))) {
196
+ throw new Error("instruction contains reserved historical TaskChef lifecycle scaffolding");
197
+ }
108
198
  const id = requireUuid(taskId);
109
199
  return {
110
200
  id,
111
- instruction: `${taskChefMarker(id)}\n${body}\n\n${EXECUTOR_SKILL_INVOCATION}`,
201
+ instruction: `${body}\n\n${taskChefMarker(id)}\n${EXECUTOR_SKILL_INVOCATION}`,
112
202
  };
113
203
  }
114
204
 
package/src/mcp.js CHANGED
@@ -114,7 +114,7 @@ export function createTaskChefMcpServer({
114
114
  },
115
115
  async (input) => {
116
116
  if (parseTaskChefMarker(input.instruction) !== input.id) {
117
- throw new Error("record_task instruction must start with its exact TaskChef marker");
117
+ throw new Error("record_task instruction must contain its exact TaskChef marker in an accepted scaffold");
118
118
  }
119
119
  const task = await record(workspace, input);
120
120
  return toolResult("task", task, `Recorded TaskChef task ${task.id}.`);
package/src/workspace.js CHANGED
@@ -737,7 +737,7 @@ async function validateDispatchShape(dispatch, name = "task") {
737
737
  id,
738
738
  project,
739
739
  title: requireString(dispatch.title, `${name}.title`).trim(),
740
- instruction: requireString(dispatch.instruction, `${name}.instruction`).trim(),
740
+ instruction: requireString(dispatch.instruction, `${name}.instruction`),
741
741
  threadId: dispatch.threadId === null
742
742
  ? null
743
743
  : normalizeDurableThreadId(dispatch.threadId, `${name}.threadId`),