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/src/delegation.js DELETED
@@ -1,277 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
-
3
- /** @deprecated Historical v7 inline-prompt snapshot. New delegations use taskchef-executor. */
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. */
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.";
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, summary omitted or null, and a concise requestSummary for this turn. 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_REPORTING_AUTHORIZATION = "Report this task and its follow-ups to my local TaskChef dashboard. Relevant private-repository links and concise work, test, and deployment results are authorized; exclude secrets.";
12
- export const EXECUTOR_SKILL_INVOCATION = "Use $taskchef-executor to execute and report this delegated TaskChef assignment.";
13
- 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.";
14
- 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.";
15
- const HISTORICAL_EXECUTOR_SCAFFOLD_LINES = new Set([
16
- EXECUTOR_OWNERSHIP_PARAGRAPH,
17
- EXECUTOR_LINK_PARAGRAPH,
18
- EXECUTOR_WORKING_PARAGRAPH,
19
- EXECUTOR_RESULT_PARAGRAPH,
20
- HISTORICAL_RESULT_WITH_TURN_PARAGRAPH,
21
- HISTORICAL_RESULT_PARAGRAPH,
22
- ]);
23
- const HISTORICAL_INLINE_PROTOCOLS = [
24
- [EXECUTOR_OWNERSHIP_PARAGRAPH, EXECUTOR_LINK_PARAGRAPH, EXECUTOR_WORKING_PARAGRAPH, EXECUTOR_RESULT_PARAGRAPH],
25
- [EXECUTOR_OWNERSHIP_PARAGRAPH, EXECUTOR_LINK_PARAGRAPH, HISTORICAL_RESULT_WITH_TURN_PARAGRAPH],
26
- [EXECUTOR_OWNERSHIP_PARAGRAPH, HISTORICAL_RESULT_PARAGRAPH],
27
- [EXECUTOR_OWNERSHIP_PARAGRAPH],
28
- ];
29
-
30
- function hasTaskSpecificContent(lines) {
31
- return lines.some((line) => (
32
- line.trim().length > 0 && line !== EXECUTOR_REPORTING_AUTHORIZATION && !HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line)
33
- ));
34
- }
35
-
36
- const UUID_SOURCE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
37
- const UUID_PATTERN = new RegExp(`^${UUID_SOURCE}$`);
38
- 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}$/;
39
- const TASKCHEF_MARKER_PATTERN = new RegExp(`^<!-- taskchef_id=(${UUID_SOURCE}) -->$`);
40
- const LEGACY_TASKCHEF_MARKER_PATTERN = new RegExp(`^# taskchef_id=(${UUID_SOURCE})$`);
41
-
42
- function requireObject(value, name) {
43
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} must be an object`);
44
- return value;
45
- }
46
-
47
- function requireString(value, name) {
48
- if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${name} must be a non-empty string`);
49
- return value;
50
- }
51
-
52
- function requireUuid(value, name = "taskId") {
53
- requireString(value, name);
54
- if (!UUID_PATTERN.test(value)) throw new Error(`${name} must be a lowercase full UUID`);
55
- return value;
56
- }
57
-
58
- function parseToolResult(value, name) {
59
- if (typeof value !== "string") return requireObject(value, name);
60
- try {
61
- return requireObject(JSON.parse(value), name);
62
- } catch (error) {
63
- if (error instanceof SyntaxError) throw new Error(`${name} must contain valid JSON`);
64
- throw error;
65
- }
66
- }
67
-
68
- function toolIdentifier(value) {
69
- if (typeof value !== "string") return null;
70
- const normalized = value.trim();
71
- return normalized.length > 0 ? normalized : null;
72
- }
73
-
74
- function attachCreationRecovery(error, taskId, turnRef, resultReporting) {
75
- const creationError = error instanceof Error ? error : new Error(String(error));
76
- try {
77
- Object.defineProperties(creationError, {
78
- taskChefTaskId: { value: taskId, enumerable: true },
79
- taskChefTurnRef: { value: turnRef, enumerable: true },
80
- taskChefResultReporting: { value: resultReporting, enumerable: true },
81
- });
82
- return creationError;
83
- } catch {
84
- const wrapped = new Error(`Executor creation failed for recorded TaskChef task ${taskId}.`, { cause: creationError });
85
- wrapped.taskChefTaskId = taskId;
86
- wrapped.taskChefTurnRef = turnRef;
87
- wrapped.taskChefResultReporting = resultReporting;
88
- return wrapped;
89
- }
90
- }
91
-
92
- export function isProvisionalThreadId(value) {
93
- const id = toolIdentifier(value);
94
- return id !== null && id.startsWith("local:");
95
- }
96
-
97
- export function normalizeDurableThreadId(value, name = "threadId") {
98
- const id = toolIdentifier(value);
99
- if (id === null) throw new Error(`${name} must be a non-empty string`);
100
- if (isProvisionalThreadId(id)) throw new Error(`${name} must be a durable thread ID, not a provisional local ID`);
101
- return id;
102
- }
103
-
104
- export function normalizeCodexThreadId(value, name = "threadId") {
105
- const id = normalizeDurableThreadId(value, name).toLowerCase();
106
- if (!CODEX_UUID_V7_PATTERN.test(id)) {
107
- throw new Error(`${name} must be a canonical Codex UUIDv7`);
108
- }
109
- return id;
110
- }
111
-
112
- export function taskChefMarker(taskId) {
113
- return `<!-- taskchef_id=${requireUuid(taskId)} -->`;
114
- }
115
-
116
- export function parseTaskChefMarker(instruction) {
117
- if (typeof instruction !== "string") return null;
118
- const lines = instruction.split(/\r\n|\r|\n/);
119
- const hasHistoricalAssignment = () => {
120
- const rest = lines.slice(1);
121
- const executorSkillInvocationIndices = rest.flatMap((line, index) => (
122
- line === EXECUTOR_SKILL_INVOCATION ? [index] : []
123
- ));
124
- if (executorSkillInvocationIndices.length > 0) {
125
- const bodyLines = rest.slice(0, -2);
126
- return executorSkillInvocationIndices.length === 1
127
- && executorSkillInvocationIndices[0] === rest.length - 1
128
- && lines.at(-1) === EXECUTOR_SKILL_INVOCATION
129
- && lines.at(-2) === ""
130
- && bodyLines.length > 0
131
- && bodyLines[0].trim().length > 0
132
- && bodyLines.at(-1).trim().length > 0
133
- && hasTaskSpecificContent(bodyLines)
134
- && !bodyLines.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line));
135
- }
136
-
137
- const containsInlineScaffold = rest.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line));
138
- if (!containsInlineScaffold) return hasTaskSpecificContent(rest);
139
- return HISTORICAL_INLINE_PROTOCOLS.some((protocol) => {
140
- const prefix = ["", ...protocol.flatMap((line) => [line, ""])];
141
- if (!prefix.every((line, index) => rest[index] === line)) return false;
142
- const bodyLines = rest.slice(prefix.length);
143
- return hasTaskSpecificContent(bodyLines)
144
- && !bodyLines.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line));
145
- });
146
- };
147
- const currentMatches = lines.flatMap((line, index) => {
148
- const match = line.match(TASKCHEF_MARKER_PATTERN);
149
- return match === null ? [] : [{ id: match[1], index }];
150
- });
151
- const legacyMatches = lines.flatMap((line, index) => {
152
- const match = line.match(LEGACY_TASKCHEF_MARKER_PATTERN);
153
- return match === null ? [] : [{ id: match[1], index }];
154
- });
155
- if (currentMatches.length + legacyMatches.length !== 1) return null;
156
-
157
- if (legacyMatches.length === 1) {
158
- const [{ id, index }] = legacyMatches;
159
- if (index !== 0) return null;
160
- return hasHistoricalAssignment() ? id : null;
161
- }
162
-
163
- const [{ id, index }] = currentMatches;
164
- if (index === 0) {
165
- return hasHistoricalAssignment() ? id : null;
166
- }
167
- const executorSkillReferences = instruction.match(/\$taskchef-executor\b/gi) ?? [];
168
- const hasCurrentCompactBoundary = index >= 2
169
- && lines.at(index - 2).trim().length > 0;
170
- const hasCurrentBlankBoundary = index >= 3
171
- && lines.at(index - 2) === ""
172
- && lines.at(index - 3).trim().length > 0;
173
- const currentAssignmentEnd = hasCurrentCompactBoundary ? index - 1 : index - 2;
174
- const isFinalMarkerScaffold = index === lines.length - 1
175
- && (hasCurrentCompactBoundary || hasCurrentBlankBoundary)
176
- && lines[0].trim().length > 0
177
- && lines.at(-2) === EXECUTOR_SKILL_INVOCATION
178
- && hasTaskSpecificContent(lines.slice(0, currentAssignmentEnd))
179
- && !lines.slice(0, currentAssignmentEnd).some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line))
180
- && executorSkillReferences.length === 1;
181
- if (isFinalMarkerScaffold) return id;
182
-
183
- const hasCompactBoundary = index >= 1
184
- && lines.at(index - 1).trim().length > 0;
185
- const hasHistoricalBlankBoundary = index >= 2
186
- && lines.at(index - 1) === ""
187
- && lines.at(index - 2).trim().length > 0;
188
- const assignmentEnd = hasCompactBoundary ? index : index - 1;
189
- const isTrailingScaffold = index === lines.length - 2
190
- && (hasCompactBoundary || hasHistoricalBlankBoundary)
191
- && lines[0].trim().length > 0
192
- && lines.at(-1) === EXECUTOR_SKILL_INVOCATION
193
- && hasTaskSpecificContent(lines.slice(0, assignmentEnd))
194
- && !lines.slice(0, assignmentEnd).some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line))
195
- && executorSkillReferences.length === 1;
196
- return isTrailingScaffold ? id : null;
197
- }
198
-
199
- export function prepareDelegation(instruction, { taskId = randomUUID() } = {}) {
200
- const rawBody = requireString(instruction, "instruction");
201
- if (/^[^\S\r\n]*(?:\r\n|\r|\n)/.test(rawBody)) {
202
- throw new Error("instruction must begin with useful task content on its first line");
203
- }
204
- const body = rawBody.replace(/(?:(?:\r\n|\r|\n)[^\S\r\n]*)+$/, "");
205
- if (body.split(/\r\n|\r|\n/).some((line) => (
206
- TASKCHEF_MARKER_PATTERN.test(line) || LEGACY_TASKCHEF_MARKER_PATTERN.test(line)
207
- ))) {
208
- throw new Error("instruction already contains a TaskChef marker");
209
- }
210
- if (/\$taskchef-executor\b/i.test(body)) {
211
- throw new Error("instruction contains a reserved TaskChef executor skill reference");
212
- }
213
- const bodyLines = body.split(/\r\n|\r|\n/);
214
- if (!hasTaskSpecificContent(bodyLines)) {
215
- throw new Error("instruction must contain task-specific content, not only TaskChef lifecycle scaffolding");
216
- }
217
- if (bodyLines.some((line) => HISTORICAL_EXECUTOR_SCAFFOLD_LINES.has(line))) {
218
- throw new Error("instruction contains reserved historical TaskChef lifecycle scaffolding");
219
- }
220
- if (bodyLines.includes(EXECUTOR_REPORTING_AUTHORIZATION)) {
221
- throw new Error("instruction contains reserved TaskChef reporting authorization");
222
- }
223
- const id = requireUuid(taskId);
224
- return {
225
- id,
226
- instruction: `${body}\n\n${EXECUTOR_REPORTING_AUTHORIZATION}\n\n${EXECUTOR_SKILL_INVOCATION}\n${taskChefMarker(id)}`,
227
- };
228
- }
229
-
230
- export async function createAndRecordDelegation(input) {
231
- const { project, title, instruction, target, createThread, recordTask, reportRecordedResult = null, taskId = randomUUID() } = input ?? {};
232
- requireString(project, "project");
233
- requireString(title, "title");
234
- requireObject(target, "target");
235
- for (const [value, name] of [[createThread, "createThread"], [recordTask, "recordTask"]]) {
236
- if (typeof value !== "function") throw new Error(`${name} must be a function`);
237
- }
238
- if (reportRecordedResult !== null && typeof reportRecordedResult !== "function") throw new Error("reportRecordedResult must be a function or null");
239
-
240
- const prepared = prepareDelegation(instruction, { taskId });
241
- await recordTask({ id: prepared.id, project, title, instruction: prepared.instruction, threadId: null });
242
-
243
- let createResult;
244
- try {
245
- createResult = parseToolResult(await createThread({ prompt: prepared.instruction, title, target }), "create_thread result");
246
- } catch (error) {
247
- const creationFailureTurnRef = randomUUID();
248
- let resultReporting = "unavailable";
249
- if (reportRecordedResult !== null) {
250
- try {
251
- await reportRecordedResult({
252
- taskId: prepared.id,
253
- threadId: null,
254
- turnRef: creationFailureTurnRef,
255
- turnId: null,
256
- status: "failed",
257
- summary: "Executor creation failed before the executor started.",
258
- });
259
- resultReporting = "recorded";
260
- } catch {
261
- resultReporting = "failed";
262
- }
263
- }
264
- throw attachCreationRecovery(
265
- error,
266
- prepared.id,
267
- creationFailureTurnRef,
268
- resultReporting,
269
- );
270
- }
271
-
272
- const returnedThreadId = toolIdentifier(createResult.threadId);
273
- const clientThreadId = toolIdentifier(createResult.clientThreadId);
274
- const pendingWorktreeId = toolIdentifier(createResult.pendingWorktreeId);
275
- const provisional = clientThreadId ?? pendingWorktreeId ?? (isProvisionalThreadId(returnedThreadId) ? returnedThreadId : null);
276
- return { status: "recorded-link-pending", resolution: "executor-self-link", ...prepared, threadId: null, provisional, hostId: createResult.hostId ?? null };
277
- }
package/src/github.js DELETED
@@ -1,107 +0,0 @@
1
- function requireRepositoryString(value, name) {
2
- if (typeof value !== "string" || value.trim().length === 0) {
3
- throw new Error(`${name} must be a non-empty string`);
4
- }
5
- return value.trim();
6
- }
7
-
8
- function parseGithubUrl(value, name, { allowIssueOrPull = false } = {}) {
9
- let remote = requireRepositoryString(value, name);
10
- const scpMatch = remote.match(/^git@github\.com:([^/]+)\/([^/]+)\/?$/i);
11
- if (scpMatch) {
12
- return {
13
- owner: scpMatch[1],
14
- repository: scpMatch[2].replace(/\.git$/i, ""),
15
- };
16
- }
17
- if (/^(?:www\.)?github\.com\//i.test(remote)) remote = `https://${remote}`;
18
- let url;
19
- try {
20
- url = new URL(remote);
21
- } catch {
22
- throw new Error(`${name} must be a GitHub repository URL`);
23
- }
24
- if (
25
- !["https:", "http:", "ssh:", "git:"].includes(url.protocol)
26
- || !["github.com", "www.github.com"].includes(url.hostname.toLowerCase())
27
- || (url.username && !(url.protocol === "ssh:" && url.username === "git"))
28
- || url.password
29
- || url.port
30
- || (!allowIssueOrPull && (url.search || url.hash))
31
- ) {
32
- throw new Error(`${name} must be a GitHub repository URL`);
33
- }
34
- const segments = url.pathname.split("/").filter(Boolean);
35
- if (segments.length < 2) throw new Error(`${name} must identify one GitHub repository`);
36
- const suffix = segments.slice(2);
37
- if (
38
- (!allowIssueOrPull && suffix.length > 0)
39
- || (
40
- allowIssueOrPull
41
- && suffix.length > 0
42
- && !(suffix.length >= 2 && ["issues", "pull"].includes(suffix[0]) && /^\d+$/.test(suffix[1]))
43
- )
44
- ) {
45
- throw new Error(`${name} must identify one GitHub repository`);
46
- }
47
- return {
48
- owner: segments[0],
49
- repository: segments[1].replace(/\.git$/i, ""),
50
- };
51
- }
52
-
53
- export function canonicalGithubRepository(value, name = "githubRepos") {
54
- const { owner, repository } = parseGithubUrl(value, name);
55
- if (repository.length === 0) throw new Error(`${name} must identify one GitHub repository`);
56
- return `https://github.com/${owner}/${repository}`;
57
- }
58
-
59
- export function normalizeGithubRepositories(
60
- value,
61
- name = "githubRepos",
62
- ) {
63
- if (!Array.isArray(value)) throw new Error(`${name} must be an array of GitHub repository URLs`);
64
- const repositories = [];
65
- const seen = new Set();
66
- for (const [index, repository] of value.entries()) {
67
- const canonical = canonicalGithubRepository(repository, `${name}[${index}]`);
68
- const key = canonical.toLowerCase();
69
- if (!seen.has(key)) {
70
- seen.add(key);
71
- repositories.push(canonical);
72
- }
73
- }
74
- return repositories;
75
- }
76
-
77
- export function matchProjectForGithubUrl(value, projects, routingHints = []) {
78
- if (!Array.isArray(projects)) throw new Error("projects must be an array");
79
- if (!Array.isArray(routingHints)) throw new Error("routingHints must be an array");
80
- let parsed;
81
- try {
82
- parsed = parseGithubUrl(value, "GitHub URL", { allowIssueOrPull: true });
83
- } catch {
84
- return { status: "unmatched", repository: null, projects: [] };
85
- }
86
- const repository = `https://github.com/${parsed.owner}/${parsed.repository}`;
87
- const key = repository.toLowerCase();
88
- const matches = projects.filter((project) => {
89
- const learned = routingHints.find((hint) => hint?.path === project?.path)?.githubRepos ?? [];
90
- return [...(Array.isArray(project?.githubRepos) ? project.githubRepos : []), ...learned]
91
- .some((candidate) => {
92
- try {
93
- return canonicalGithubRepository(candidate).toLowerCase() === key;
94
- } catch {
95
- return false;
96
- }
97
- });
98
- });
99
- if (matches.length === 1) {
100
- return { status: "matched", repository, project: matches[0], projects: matches };
101
- }
102
- return {
103
- status: matches.length > 1 ? "ambiguous" : "unmatched",
104
- repository,
105
- projects: matches,
106
- };
107
- }
@@ -1,70 +0,0 @@
1
- import { execFile as execFileCallback } from 'node:child_process';
2
- import { readdir } from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import { promisify } from 'node:util';
7
-
8
- const execFile = promisify(execFileCallback);
9
- const resolver = fileURLToPath(new URL('../scripts/roles/resolve_roles.py', import.meta.url));
10
-
11
- async function hasAgentFiles(directory) {
12
- try { return (await readdir(directory)).some((name) => name.endsWith('.toml')); }
13
- catch (error) { if (error.code === 'ENOENT') return false; throw error; }
14
- }
15
-
16
- function missingRoles() {
17
- return {
18
- roles: ['orchestrator', 'planner', 'implementer', 'reviewer'].map((role) => ({
19
- role, source: null, effectiveSource: null, model: null, effort: null,
20
- status: 'missing', availability: 'No role files', problems: [],
21
- taskOverrides: {}, subagentOverrides: {},
22
- fallback: role === 'orchestrator' ? 'native new-task default' : 'inherit parent settings',
23
- })),
24
- problems: [], catalogSource: null, modelOptions: [],
25
- precedence: 'explicit user model (and its explicit effort) > personal role > native defaults; explicit effort alone overrides role effort',
26
- scope: 'Model and effort only; agent instructions, tools, permissions, and other TOML keys are not applied by this adapter.',
27
- };
28
- }
29
-
30
- export async function resolveModelRoles({ run = execFile, env = process.env, includeCatalog = false } = {}) {
31
- try {
32
- const home = env.HOME || os.homedir();
33
- const codexHome = (env.CODEX_HOME || path.join(home, '.codex')).replace(/^~(?=\/|$)/, home);
34
- const agentsDirectory = path.join(codexHome, 'agents');
35
- if (!await hasAgentFiles(agentsDirectory) && !includeCatalog) return missingRoles();
36
- const { stdout } = await run('python3', [resolver], {
37
- env, timeout: 5000, maxBuffer: 128 * 1024,
38
- });
39
- const result = JSON.parse(stdout);
40
- result.roles = result.roles.map((role) => ({
41
- ...role,
42
- displaySource: typeof role.source === 'string'
43
- ? `~/.codex/agents/${path.basename(role.source)}`
44
- : role.source,
45
- }));
46
- return result;
47
- } catch {
48
- return { roles: [], problems: ['Model role preview unavailable. Python 3.11+ and readable agent configuration are required when agent files exist; configured roles have not been honored.'], catalogSource: null };
49
- }
50
- }
51
-
52
- export async function updateModelRole(role, model, effort, { run = execFile, env = process.env } = {}) {
53
- const allowedRoles = new Set(['orchestrator', 'planner', 'implementer', 'reviewer']);
54
- if (!allowedRoles.has(role) || typeof model !== 'string' || typeof effort !== 'string') {
55
- const error = new Error('Invalid model role update.');
56
- error.code = 'invalid_request';
57
- throw error;
58
- }
59
- try {
60
- await run('python3', [resolver, '--update', '--role', role, '--model', model, '--effort', effort], {
61
- env, timeout: 5000, maxBuffer: 128 * 1024,
62
- });
63
- return resolveModelRoles({ run, env, includeCatalog: true });
64
- } catch (error) {
65
- if (error.code === 'invalid_request') throw error;
66
- const failure = new Error('Model role could not be saved.');
67
- failure.code = 'role_update_failed';
68
- throw failure;
69
- }
70
- }
@@ -1,92 +0,0 @@
1
- import { CodexSessionScanner } from "./codex-session-scanner.js";
2
- import { acquireWorkspaceLock } from "./workspace.js";
3
- import { readUsageStore, writeUsageStore } from "./usage.js";
4
- import { TASKCHEF_VERSION } from "./version.js";
5
-
6
- function projection(value, provenance, now, sessionCount) {
7
- return {
8
- inputTokens: value.tokens.input_tokens, cachedInputTokens: value.tokens.cached_input_tokens,
9
- outputTokens: value.tokens.output_tokens, reasoningOutputTokens: value.tokens.reasoning_output_tokens,
10
- totalTokens: value.tokens.total_tokens, partial: value.partial, costPartial: value.costPartial,
11
- estimatedCostUsd: value.samples && (!value.costPartial || value.costUsd > 0) ? value.costUsd : null,
12
- costStatus: value.samples && (!value.costPartial || value.costUsd > 0) ? "estimated" : "unavailable",
13
- models: {}, provenance: { ...provenance, sessionCount }, sampledAt: now, sourceUpdatedAt: now,
14
- };
15
- }
16
-
17
- export function createRecordedUsageTracker({ workspace, maxConcurrentJobs = 2, scanner = new CodexSessionScanner() }) {
18
- const pending = new Map(), queue = [];
19
- let active = 0, closed = false;
20
- function drain() {
21
- while (!closed && active < maxConcurrentJobs && queue.length) {
22
- const job = queue.shift(); active++;
23
- job.run().then(job.resolve, job.reject).finally(() => { active--; drain(); });
24
- }
25
- }
26
- async function calculate(task) {
27
- const ref = task.latestTurn?.turnRef;
28
- const now = new Date().toISOString();
29
- let result, failure;
30
- try { result = await scanner.recordedUsage(task.threadId, task.latestTurn?.turnId ?? ref); }
31
- catch { failure = "Saved Codex usage is unavailable. Usage requires Node.js 22.18+ and readable Codex records."; }
32
- if (closed) return null;
33
- const release = await acquireWorkspaceLock(workspace);
34
- try {
35
- const store = await readUsageStore(workspace);
36
- const previous = store.tasks[task.id]?.threadId === task.threadId ? store.tasks[task.id] : null;
37
- if (previous?.generationTurnCount > task.turns.length) return previous;
38
- const provenance = { provider: "taskchef", version: TASKCHEF_VERSION, pricingMode: "standard" };
39
- let total = result?.total.samples ? projection(result.total, provenance, now, result.subagents + 1)
40
- : previous?.task ? { ...previous.task, partial: true, costPartial: true } : null;
41
- // A partial scan cannot erase usage already known from older rollouts.
42
- if (result?.total.partial && previous?.task && total.totalTokens < previous.task.totalTokens) {
43
- total = { ...previous.task, partial: true, costPartial: true };
44
- }
45
- if (result?.total.costPartial && previous?.task?.estimatedCostUsd != null
46
- && (total.estimatedCostUsd == null || total.estimatedCostUsd < previous.task.estimatedCostUsd)) {
47
- total = { ...total, estimatedCostUsd: previous.task.estimatedCostUsd, costStatus: "estimated", costPartial: true };
48
- }
49
- const priorTurn = previous?.turns[ref];
50
- let latest = result?.matchedTurn && result.latest.samples
51
- ? { status: "available", ...projection(result.latest, provenance, now, result.subagents + 1), updatedAt: now }
52
- : priorTurn?.status === "available" ? { ...priorTurn, partial: true, costPartial: true, updatedAt: now }
53
- : { status: "unavailable", reason: failure || (result?.matchedTurn ? "No saved usage for this native turn." : "This dispatcher turn has no matching native Codex turn ID."), updatedAt: now };
54
- if (latest.status === "available" && latest.partial && priorTurn?.status === "available" && latest.totalTokens < priorTurn.totalTokens) {
55
- latest = { ...priorTurn, partial: true, costPartial: true, updatedAt: now };
56
- }
57
- if (latest.status === "available" && latest.costPartial && priorTurn?.estimatedCostUsd != null
58
- && (latest.estimatedCostUsd == null || latest.estimatedCostUsd < priorTurn.estimatedCostUsd)) {
59
- latest = { ...latest, estimatedCostUsd: priorTurn.estimatedCostUsd, costStatus: "estimated", costPartial: true };
60
- }
61
- const record = {
62
- threadId: task.threadId, generationTurnRef: ref, generationTurnCount: task.turns.length,
63
- generationTerminal: !!task.latestTurn?.result, zeroBaselineTurnRef: null,
64
- status: total ? "available" : "unavailable", updatedAt: now, retryAfter: null,
65
- ...(total ? {} : { reason: failure || "No saved usage records were found." }),
66
- task: total, turns: { ...(previous?.turns ?? {}), [ref]: latest }, boundaries: {},
67
- };
68
- store.tasks[task.id] = record;
69
- await writeUsageStore(workspace, store);
70
- return record;
71
- } finally { await release(); }
72
- }
73
- function get(task) {
74
- if (closed) return Promise.resolve(null);
75
- if (!task.threadId || !task.latestTurn) return Promise.resolve({ status: "unavailable", reason: "No linked Codex turn.", task: null, turns: {} });
76
- if (task.latestTurn.provenance?.kind === "dashboard_manual") {
77
- return readUsageStore(workspace).then(store => ({ ...(store.tasks[task.id] ?? { status: "unavailable", task: null }), turns: { ...(store.tasks[task.id]?.turns ?? {}), [task.latestTurn.turnRef]: { status: "unavailable", reason: "Administrative action; no Codex usage boundary." } } }));
78
- }
79
- const key = `${task.id}:${task.latestTurn.turnRef}`;
80
- if (pending.has(key)) return pending.get(key);
81
- const promise = new Promise((resolve, reject) => { queue.push({ run: () => calculate(task), resolve, reject }); drain(); });
82
- pending.set(key, promise);
83
- promise.finally(() => pending.delete(key)).catch(() => {});
84
- return promise;
85
- }
86
- return {
87
- get, observe: get,
88
- // Historical tasks stay lazy; opening Details reads their saved logs.
89
- preload: async () => {},
90
- close() { closed = true; for (const job of queue.splice(0)) job.resolve(null); scanner.close(); },
91
- };
92
- }