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.
- package/.codex-plugin/plugin.json +2 -1
- package/README.md +35 -14
- package/docs/firstmate-taskchef-comparison.md +9 -7
- package/docs/spec.md +57 -28
- package/docs/workflows.md +42 -23
- package/index.js +3 -0
- package/package.json +2 -1
- package/skills/taskchef-bootstrap/SKILL.md +1 -1
- package/skills/taskchef-delegate/SKILL.md +12 -30
- package/skills/taskchef-executor/SKILL.md +69 -0
- package/skills/taskchef-executor/agents/openai.yaml +4 -0
- package/skills/taskchef-report/SKILL.md +14 -14
- package/src/cli.js +7 -3
- package/src/dashboard/app.js +8 -4
- package/src/dashboard/state.js +1 -0
- package/src/dashboard.js +2 -0
- package/src/delegation.js +18 -6
- package/src/mcp.js +38 -4
- package/src/workspace.js +289 -38
|
@@ -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.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
`
|
|
173
|
-
`
|
|
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");
|
package/src/dashboard/app.js
CHANGED
|
@@ -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
|
|
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("
|
|
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)}`;
|
package/src/dashboard/state.js
CHANGED
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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(),
|