taskchef 7.0.0 → 7.2.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.
@@ -44,37 +44,37 @@ all deterministic task-log operations.
44
44
  detailed read. Native approval is live Codex state, not a `needs_input`
45
45
  callback. An inactive status never proves semantic completion; it only
46
46
  permits a trustworthy cached MCP result to stand.
47
- 4. Treat `updatedBy: mcp`, `status: failed`, and null thread/turn IDs as a fresh
48
- executor-creation failure. No live read is possible or needed; report the
49
- stored failure summary, not unresolved. Otherwise, only a snapshot with
50
- `updatedBy: mcp`, a result status, a non-null summary, and a non-null turn ID
51
- is a cached semantic result. Any `working` snapshot has no semantic
52
- callback, including a self-linked `updatedBy: mcp` snapshot, and requires
53
- one live task query when selected; if the task is inactive and no callback
54
- exists, report the outcome as unknown rather than treating `working` as
55
- fresh. When identity is certain and metadata says the thread is
56
- inactive, trust the latest MCP result by default in a broad overview. Do not
57
- read every idle terminal task in an overview merely because native
47
+ 4. In schema 5, treat `status`, `turnId`, and `updatedAt` as the latest reported
48
+ execution state and treat `lastResult` as the separately preserved semantic
49
+ result. A `working` state with a non-null `lastResult` means a newer executor
50
+ turn started after that result; show the prior result as history, not as the
51
+ current outcome. Treat a failed `lastResult` with null thread and turn IDs as
52
+ a fresh executor-creation failure. No live read is possible or needed.
53
+ Schema 4 snapshots normalize a structurally complete result into
54
+ `lastResult` without rewriting their log line. When identity is certain and
55
+ metadata says the thread is inactive, trust the latest semantic result by
56
+ default in a broad overview unless a newer working state makes it historical.
57
+ Do not read every idle terminal task in an overview merely because native
58
58
  `updatedAt` is later: callbacks normally run before Codex finalizes the same
59
59
  turn, and overview performance matters more than investigating every rare
60
60
  missed callback.
61
61
 
62
62
  For a focused task, title, or project report, perform at most one detailed
63
63
  read for each selected inactive task when matched metadata `updatedAt` is
64
- later than the cached result `updatedAt`, by any amount. Read once as well
64
+ later than `lastResult.updatedAt`, by any amount. Read once as well
65
65
  when there is no semantic callback, identity or metadata is uncertain or
66
66
  contradictory, or the user explicitly requests a fully live result. If
67
67
  focused metadata is not newer, trust the cache. Absence from the bounded
68
68
  recent snapshot is not by itself a reason to read every cached terminal
69
69
  overview entry. Batch immediate native reads with no more than eight targets
70
70
  per call. When a detailed read occurs, compare the latest structured turn ID
71
- and native turn state with stored `turnId`: a newer turn without a callback
71
+ and native turn state with `lastResult.turnId`: a newer turn without a callback
72
72
  makes the cache stale, while an interrupted or cancelled callback turn
73
73
  cannot prove completion. Never classify assistant prose.
74
74
  5. Report each task as one of: working, needs input, awaiting native approval,
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
- state and label the cached result stale rather than overwriting it.
77
+ state and label the preserved result historical or stale rather than overwriting it.
78
78
  6. Never edit `tasks.jsonl` directly during reporting. A null identity is
79
79
  executor link-pending and must be retried by that executor. Never persist inferred status,
80
80
  transcripts, prose classifications, or hidden reasoning. Do not poll or wait.
package/src/cli.js CHANGED
@@ -166,18 +166,22 @@ function singleLineDetail(value) {
166
166
  }
167
167
 
168
168
  function taskDetails(task) {
169
+ const lastResult = task.lastResult;
169
170
  return [
170
171
  `Title: ${singleLineDetail(task.title)}`,
171
172
  `Project: ${singleLineDetail(task.project.name)}`,
172
- `Status: ${singleLineDetail(task.status ?? "unknown")}`,
173
- `Summary: ${singleLineDetail(task.summary ?? "-")}`,
173
+ `Current status: ${singleLineDetail(task.status ?? "unknown")}`,
174
+ `Current turn ID: ${singleLineDetail(task.turnId ?? "-")}`,
175
+ `Last result status: ${singleLineDetail(lastResult?.status ?? "-")}`,
176
+ `Last result summary: ${singleLineDetail(lastResult?.summary ?? "-")}`,
177
+ `Last result turn ID: ${singleLineDetail(lastResult?.turnId ?? "-")}`,
178
+ `Last result updated: ${singleLineDetail(lastResult?.updatedAt ?? "-")}`,
174
179
  `Project path: ${singleLineDetail(task.project.path)}`,
175
180
  `Created: ${singleLineDetail(task.createdAt)}`,
176
181
  `Updated: ${singleLineDetail(task.updatedAt ?? "-")}`,
177
182
  `Updated by: ${singleLineDetail(task.updatedBy ?? "-")}`,
178
183
  `Task ID: ${singleLineDetail(task.id)}`,
179
184
  `Thread ID: ${singleLineDetail(task.threadId ?? "-")}`,
180
- `Turn ID: ${singleLineDetail(task.turnId ?? "-")}`,
181
185
  "Instruction:",
182
186
  task.instruction,
183
187
  ].join("\n");
@@ -123,14 +123,18 @@ function openDialog(task) {
123
123
  state.selectedTask = task;
124
124
  elements.dialogProject.textContent = task.project.name;
125
125
  elements.dialogTitle.textContent = task.title;
126
- elements.dialogSummary.textContent = task.summary ?? "No semantic result has been reported yet.";
126
+ elements.dialogSummary.textContent = task.lastResult?.summary
127
+ ?? "No semantic result has been reported yet.";
127
128
  elements.dialogInstruction.textContent = task.instruction;
128
129
  elements.copyThreadId.disabled = !task.threadId;
129
130
  elements.dialogMetadata.replaceChildren(
130
- ...detailRow("Status", taskStatusLabel(task)),
131
+ ...detailRow("Current status", taskStatusLabel(task)),
132
+ ...detailRow("Current turn ID", task.turnId),
133
+ ...detailRow("Last result status", task.lastResult?.status?.replaceAll("_", " ")),
134
+ ...detailRow("Last result turn ID", task.lastResult?.turnId),
135
+ ...detailRow("Last result updated", formatTime(task.lastResult?.updatedAt)),
131
136
  ...detailRow("Task ID", task.id),
132
137
  ...detailRow("Thread ID", task.threadId),
133
- ...detailRow("Turn ID", task.turnId),
134
138
  ...detailRow("Project path", task.project.path),
135
139
  ...detailRow("Created", formatTime(task.createdAt)),
136
140
  ...detailRow("Updated", formatTime(
@@ -160,7 +164,7 @@ function taskCard(task) {
160
164
  project.textContent = task.project.name;
161
165
  const summary = document.createElement("p");
162
166
  summary.className = "task-summary";
163
- summary.textContent = task.summary ?? "No semantic result reported yet.";
167
+ summary.textContent = task.lastResult?.summary ?? "No semantic result reported yet.";
164
168
  const time = document.createElement("time");
165
169
  time.dateTime = task.meaningfulUpdatedAt ?? task.updatedAt ?? task.createdAt;
166
170
  time.textContent = `Updated ${formatTime(time.dateTime)}`;
@@ -51,6 +51,7 @@ export function taskSignature(task) {
51
51
  task.turnId,
52
52
  task.updatedAt,
53
53
  task.updatedBy,
54
+ task.lastResult,
54
55
  ]);
55
56
  }
56
57
 
package/src/dashboard.js CHANGED
@@ -131,6 +131,8 @@ function assertDashboardTaskBounds(tasks, maximumTasks) {
131
131
  boundedText(task.summary, 2_000, `${name} summary`);
132
132
  boundedText(task.threadId, 512, `${name} thread ID`);
133
133
  boundedText(task.turnId, 512, `${name} turn ID`);
134
+ boundedText(task.lastResult?.summary, 2_000, `${name} last result summary`);
135
+ boundedText(task.lastResult?.turnId, 512, `${name} last result turn ID`);
134
136
  boundedText(task.project.name, 1_000, `${name} project name`);
135
137
  boundedText(task.project.path, 8_192, `${name} project path`);
136
138
  boundedText(task.project.description, 4_000, `${name} project description`);
package/src/delegation.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
 
3
+ /** @deprecated Historical v7 inline-prompt snapshot. New delegations use taskchef-executor. */
3
4
  export const EXECUTOR_OWNERSHIP_PARAGRAPH = "This task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.";
5
+ /** @deprecated Historical v7 inline-prompt snapshot. New delegations use taskchef-executor. */
4
6
  export const EXECUTOR_LINK_PARAGRAPH = "Before any other work, read this executor's own durable Codex thread ID from the current task's CODEX_THREAD_ID environment value and call the TaskChef link_task MCP tool with that thread ID and the marked TaskChef task ID. Never use CODEX_SESSION_ID or the parent or delegator thread ID. If linking fails, CODEX_THREAD_ID is unavailable, or the tool is unavailable, report the failure visibly and retry on a later turn; do not guess an identity or continue substantive work while the task is link-pending.";
5
- export const EXECUTOR_RESULT_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.";
7
+ /** @deprecated Historical v7 inline-prompt snapshot. New delegations use taskchef-executor. */
8
+ export const EXECUTOR_WORKING_PARAGRAPH = "After a successful initial link, and at the start of every follow-up turn before substantive work, read this exact Codex thread natively to obtain the current turn ID and call TaskChef report_state with the marked task ID, the self-linked thread ID, that current turn ID, status working, and summary omitted or null. link_task remains the first TaskChef action on the initial turn; do not report working before identity is linked. Never reuse a prior turn ID after a follow-up.";
9
+ /** @deprecated Historical v7 inline-prompt snapshot. New delegations use taskchef-executor. */
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
+ export const EXECUTOR_SKILL_INVOCATION = "Use $taskchef-executor to execute and report this delegated TaskChef assignment.";
6
12
 
7
13
  const UUID_SOURCE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
8
14
  const UUID_PATTERN = new RegExp(`^${UUID_SOURCE}$`);
@@ -86,17 +92,23 @@ export function parseTaskChefMarker(instruction) {
86
92
  const firstLine = instruction.split(/\r?\n/, 1)[0];
87
93
  const currentMatch = firstLine.match(TASKCHEF_MARKER_PATTERN);
88
94
  if (currentMatch === null) return null;
89
- const prefix = instruction.match(/^([^\r\n]*)(\r?\n)\2/);
90
- return prefix === null ? null : currentMatch[1];
95
+ return /^[^\r\n]*\r?\n[\s\S]+$/.test(instruction) ? currentMatch[1] : null;
91
96
  }
92
97
 
93
98
  export function prepareDelegation(instruction, { taskId = randomUUID() } = {}) {
94
- requireString(instruction, "instruction");
95
- if (parseTaskChefMarker(instruction) !== null) throw new Error("instruction already contains a TaskChef marker");
99
+ const rawBody = requireString(instruction, "instruction");
100
+ if (/^[^\S\r\n]*(?:\r\n|\r|\n)/.test(rawBody)) {
101
+ throw new Error("instruction must begin with useful task content on its first line");
102
+ }
103
+ 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");
105
+ if (/\$taskchef-executor\b/i.test(body)) {
106
+ throw new Error("instruction contains a reserved TaskChef executor skill reference");
107
+ }
96
108
  const id = requireUuid(taskId);
97
109
  return {
98
110
  id,
99
- instruction: `${taskChefMarker(id)}\n\n${EXECUTOR_OWNERSHIP_PARAGRAPH}\n\n${EXECUTOR_LINK_PARAGRAPH}\n\n${EXECUTOR_RESULT_PARAGRAPH}\n\n${instruction}`,
111
+ instruction: `${taskChefMarker(id)}\n${body}\n\n${EXECUTOR_SKILL_INVOCATION}`,
100
112
  };
101
113
  }
102
114
 
package/src/mcp.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  prepareDispatch,
5
5
  linkTask,
6
6
  recordTask,
7
+ reportTaskState,
7
8
  reportTaskResult,
8
9
  } from "./workspace.js";
9
10
  import { parseTaskChefMarker } from "./delegation.js";
@@ -18,7 +19,7 @@ const projectSchema = z.object({
18
19
  });
19
20
 
20
21
  const taskSchema = z.object({
21
- schemaVersion: z.literal(4),
22
+ schemaVersion: z.union([z.literal(4), z.literal(5)]),
22
23
  id: z.string(),
23
24
  project: projectSchema,
24
25
  title: z.string(),
@@ -30,6 +31,12 @@ const taskSchema = z.object({
30
31
  turnId: z.string().nullable(),
31
32
  updatedAt: z.string(),
32
33
  updatedBy: z.enum(["dispatcher", "mcp"]),
34
+ lastResult: z.object({
35
+ status: z.enum(["needs_input", "completed", "failed"]),
36
+ summary: z.string(),
37
+ turnId: z.string().nullable(),
38
+ updatedAt: z.string(),
39
+ }).nullable(),
33
40
  });
34
41
 
35
42
  const preparationSchema = z.object({
@@ -54,13 +61,14 @@ export function createTaskChefMcpServer({
54
61
  prepare = prepareDispatch,
55
62
  record = recordTask,
56
63
  reportResult = reportTaskResult,
64
+ reportState = reportTaskState,
57
65
  link = linkTask,
58
66
  } = {}) {
59
67
  const server = new McpServer(
60
68
  { name: "taskchef", version: "1.0.0" },
61
69
  {
62
70
  instructions:
63
- "Prepare with prepare_dispatch, call record_task before creating the Codex task, then create it natively and return immediately. The executor must call link_task before other work and report_result before ending.",
71
+ "Prepare with prepare_dispatch, call record_task before creating the Codex task, then create it natively and return immediately. Follow the active TaskChef skill for role-specific sequencing of the identity and state tools.",
64
72
  },
65
73
  );
66
74
 
@@ -136,12 +144,38 @@ export function createTaskChefMcpServer({
136
144
  },
137
145
  );
138
146
 
147
+ server.registerTool(
148
+ "report_state",
149
+ {
150
+ title: "Report TaskChef state",
151
+ description:
152
+ "Report this self-linked executor turn's lifecycle state. Use working before substantive work in a newly linked or follow-up turn, with summary omitted or null. Before ending the same turn, report needs_input, completed, or failed with a concise semantic summary. Exact retries are idempotent; stale or mismatched turns are rejected.",
153
+ inputSchema: {
154
+ taskId: z.string().min(1),
155
+ threadId: z.string().min(1).nullable(),
156
+ turnId: z.string().min(1).max(256).nullable(),
157
+ status: z.enum(["working", "needs_input", "completed", "failed"]),
158
+ summary: z.string().min(1).max(2_000).nullable().optional(),
159
+ },
160
+ outputSchema: { task: taskSchema },
161
+ annotations: {
162
+ readOnlyHint: false,
163
+ destructiveHint: true,
164
+ openWorldHint: false,
165
+ },
166
+ },
167
+ async (input) => {
168
+ const task = await reportState(workspace, input);
169
+ return toolResult("task", task, `Recorded ${task.status} state for TaskChef task ${task.id}.`);
170
+ },
171
+ );
172
+
139
173
  server.registerTool(
140
174
  "report_result",
141
175
  {
142
- title: "Report TaskChef result",
176
+ title: "Report TaskChef result (deprecated)",
143
177
  description:
144
- "Store the executor's latest semantic outcome for one recorded TaskChef task. A linked executor must supply its matching durable thread ID and current turn ID. Null IDs are accepted only for a failed executor creation before a thread exists. Use needs_input only for a semantic user decision, not a transient native approval prompt. Summaries must omit secrets, transcripts, and raw command output.",
178
+ "Deprecated compatibility alias for semantic results. New executors must use report_state working at turn start and report_state again with needs_input, completed, or failed before ending. This alias preserves legacy callers by implicitly starting the supplied newer turn before storing its result.",
145
179
  inputSchema: {
146
180
  taskId: z.string().min(1),
147
181
  threadId: z.string().min(1).nullable(),