taskchef 8.7.1 → 9.0.1

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 (150) hide show
  1. package/.codex-plugin/plugin.json +7 -10
  2. package/.mcp.json +3 -2
  3. package/README.md +35 -158
  4. package/docs/mcp-app.md +23 -6
  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/user-prompt.js +11 -0
  34. package/src/version.js +0 -1
  35. package/BACKLOG.md +0 -48
  36. package/assets/e2e-benchmark-example.json +0 -50
  37. package/assets/taskchef-dispatcher-instructions.md +0 -44
  38. package/bin/taskchef.js +0 -20
  39. package/docs/advanced-guide.md +0 -497
  40. package/docs/dashboard-lifecycle.md +0 -149
  41. package/docs/dashboard-ui.md +0 -40
  42. package/docs/firstmate-taskchef-comparison.md +0 -283
  43. package/docs/images/ccusage-token-consumption.png +0 -0
  44. package/docs/images/dashboard-identity.jpg +0 -0
  45. package/docs/images/dashboard-token-pending.jpg +0 -0
  46. package/docs/images/interrupted-turn-recovery.jpg +0 -0
  47. package/docs/images/notification-event-snapshots.jpg +0 -0
  48. package/docs/images/readme-board.png +0 -0
  49. package/docs/images/readme-completed.png +0 -0
  50. package/docs/images/readme-list.png +0 -0
  51. package/docs/images/readme-needs-input.png +0 -0
  52. package/docs/images/readme-roles.png +0 -0
  53. package/docs/images/reported-work-duration-desktop.jpg +0 -0
  54. package/docs/images/reported-work-duration-mobile.jpg +0 -0
  55. package/docs/images/result-history-dashboard.jpg +0 -0
  56. package/docs/images/taskchef-pr-icon-comparison.jpg +0 -0
  57. package/docs/images/taskchef-unified-status-popup.jpg +0 -0
  58. package/docs/workflows.md +0 -476
  59. package/mcp/dashboard-session.js +0 -51
  60. package/scripts/benchmark-dispatch-prepare.js +0 -90
  61. package/scripts/e2e-benchmark.js +0 -206
  62. package/scripts/roles/resolve_roles.py +0 -293
  63. package/skills/taskchef-bootstrap/SKILL.md +0 -96
  64. package/skills/taskchef-bootstrap/agents/openai.yaml +0 -4
  65. package/skills/taskchef-bootstrap/references/project-index.md +0 -154
  66. package/skills/taskchef-copilot/SKILL.md +0 -75
  67. package/skills/taskchef-copilot/agents/openai.yaml +0 -4
  68. package/skills/taskchef-dashboard/SKILL.md +0 -24
  69. package/skills/taskchef-dashboard/agents/openai.yaml +0 -4
  70. package/skills/taskchef-delegate/SKILL.md +0 -102
  71. package/skills/taskchef-delegate/agents/openai.yaml +0 -4
  72. package/skills/taskchef-delegate/references/model-roles.md +0 -29
  73. package/skills/taskchef-executor/SKILL.md +0 -154
  74. package/skills/taskchef-executor/agents/openai.yaml +0 -4
  75. package/skills/taskchef-executor/references/compatibility.md +0 -22
  76. package/skills/taskchef-executor/references/ending-actions.md +0 -26
  77. package/src/backups.js +0 -359
  78. package/src/cli.js +0 -733
  79. package/src/config-audit.js +0 -99
  80. package/src/config-mutations.js +0 -106
  81. package/src/dashboard/actions.js +0 -136
  82. package/src/dashboard/app.js +0 -1172
  83. package/src/dashboard/dist/app.js +0 -269
  84. package/src/dashboard/dist/app.js.map +0 -1
  85. package/src/dashboard/dist/index.html +0 -16
  86. package/src/dashboard/dist/styles.css +0 -1
  87. package/src/dashboard/index.html +0 -148
  88. package/src/dashboard/react/App.test.tsx +0 -346
  89. package/src/dashboard/react/App.tsx +0 -515
  90. package/src/dashboard/react/api.test.ts +0 -40
  91. package/src/dashboard/react/api.ts +0 -103
  92. package/src/dashboard/react/components/ActivityTimeline.tsx +0 -118
  93. package/src/dashboard/react/components/ModelSettings.test.tsx +0 -180
  94. package/src/dashboard/react/components/ModelSettings.tsx +0 -106
  95. package/src/dashboard/react/components/NotificationCenter.test.tsx +0 -76
  96. package/src/dashboard/react/components/NotificationCenter.tsx +0 -111
  97. package/src/dashboard/react/components/PullRequestBadges.tsx +0 -18
  98. package/src/dashboard/react/components/TaskDetail.test.tsx +0 -255
  99. package/src/dashboard/react/components/TaskDetail.tsx +0 -271
  100. package/src/dashboard/react/components/UsagePanel.tsx +0 -96
  101. package/src/dashboard/react/index.html +0 -15
  102. package/src/dashboard/react/main.tsx +0 -12
  103. package/src/dashboard/react/overlay-state.test.ts +0 -110
  104. package/src/dashboard/react/overlay-state.ts +0 -60
  105. package/src/dashboard/react/presentation.test.tsx +0 -571
  106. package/src/dashboard/styles.css +0 -261
  107. package/src/dashboard-control.js +0 -34
  108. package/src/dashboard-manager.js +0 -339
  109. package/src/dashboard-session-process.js +0 -88
  110. package/src/dashboard-session.js +0 -86
  111. package/src/dashboard.js +0 -1303
  112. package/src/delegation.js +0 -277
  113. package/src/github.js +0 -107
  114. package/src/model-roles.js +0 -70
  115. package/src/recorded-usage-tracker.js +0 -92
  116. package/src/usage-tracker.js +0 -650
  117. package/src/usage.js +0 -367
  118. package/src/workspace-path.js +0 -48
  119. package/src/workspace.js +0 -3629
  120. /package/src/{dashboard/react → ui}/components/AnimatedUsage.test.tsx +0 -0
  121. /package/src/{dashboard/react → ui}/components/CardStatusIcons.test.tsx +0 -0
  122. /package/src/{dashboard/react → ui}/components/ImageGallery.tsx +0 -0
  123. /package/src/{dashboard/react → ui}/components/OpenChatButton.test.tsx +0 -0
  124. /package/src/{dashboard/react → ui}/components/OpenChatButton.tsx +0 -0
  125. /package/src/{dashboard/react → ui}/components/PrGlyph.tsx +0 -0
  126. /package/src/{dashboard/react → ui}/components/QueuedPrompts.test.tsx +0 -0
  127. /package/src/{dashboard/react → ui}/components/QueuedPrompts.tsx +0 -0
  128. /package/src/{dashboard/react → ui}/components/ReplyCover.test.tsx +0 -0
  129. /package/src/{dashboard/react → ui}/components/ReplyCover.tsx +0 -0
  130. /package/src/{dashboard/react → ui}/components/ReplyImagePreview.tsx +0 -0
  131. /package/src/{dashboard/react → ui}/components/ReplyMarkdown.test.tsx +0 -0
  132. /package/src/{dashboard/react → ui}/components/ReplyMarkdown.tsx +0 -0
  133. /package/src/{dashboard/react → ui}/components/SavedUsageFormat.test.tsx +0 -0
  134. /package/src/{dashboard/react → ui}/components/SavedUsageFormat.tsx +0 -0
  135. /package/src/{dashboard/react → ui}/components/ShimmerText.tsx +0 -0
  136. /package/src/{dashboard/react → ui}/components/StatsGrid.tsx +0 -0
  137. /package/src/{dashboard/react → ui}/components/StatusBadge.tsx +0 -0
  138. /package/src/{dashboard/react → ui}/components/TaskBoard.test.tsx +0 -0
  139. /package/src/{dashboard/react → ui}/components/TaskCardStats.test.tsx +0 -0
  140. /package/src/{dashboard/react → ui}/components/TaskCardStats.tsx +0 -0
  141. /package/src/{dashboard/react → ui}/components/TurnContent.test.tsx +0 -0
  142. /package/src/{dashboard/react → ui}/fixtures.ts +0 -0
  143. /package/src/{dashboard → ui}/github-links.js +0 -0
  144. /package/src/{dashboard/react → ui}/image-cache.test.ts +0 -0
  145. /package/src/{dashboard/react → ui}/image-cache.ts +0 -0
  146. /package/src/{dashboard → ui}/state.js +0 -0
  147. /package/src/{dashboard/react → ui}/styles.css +0 -0
  148. /package/src/{dashboard/react → ui}/test-setup.ts +0 -0
  149. /package/src/{dashboard → ui}/time.js +0 -0
  150. /package/src/{dashboard/react → ui}/types.ts +0 -0
@@ -1,99 +0,0 @@
1
- import { open, readFile, readdir, rename, stat, unlink } from "node:fs/promises";
2
- import path from "node:path";
3
- import { taskChefError } from "./state-store.js";
4
-
5
- export async function appendAuditUnlocked(workspace, record) {
6
- const filePath = path.join(workspace, "config-audit.jsonl");
7
- const size = await stat(filePath).then((details) => details.size).catch((error) => {
8
- if (error.code === "ENOENT") return 0;
9
- throw error;
10
- });
11
- if (size >= 10 * 1024 * 1024) {
12
- const timestamp = new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
13
- await rename(filePath, path.join(workspace, `config-audit.${timestamp}.jsonl`));
14
- const archives = (await readdir(workspace))
15
- .filter((name) => /^config-audit\..+\.jsonl$/.test(name))
16
- .sort()
17
- .reverse();
18
- for (const name of archives.slice(10)) await unlink(path.join(workspace, name));
19
- }
20
- const handle = await open(filePath, "a", 0o600);
21
- try {
22
- await handle.writeFile(`${JSON.stringify({ schemaVersion: 1, ...record })}\n`);
23
- await handle.sync();
24
- } finally {
25
- await handle.close();
26
- }
27
- }
28
-
29
- async function readLatestAuditRecordsUnlocked(workspace) {
30
- const filePath = path.join(workspace, "config-audit.jsonl");
31
- const content = await readFile(filePath, "utf8").catch((error) => {
32
- if (error.code === "ENOENT") return "";
33
- throw error;
34
- });
35
- const latest = new Map();
36
- for (const [index, line] of content.trimEnd().split("\n").entries()) {
37
- if (line.length === 0) continue;
38
- let record;
39
- try {
40
- record = JSON.parse(line);
41
- } catch {
42
- throw taskChefError(
43
- "STATE_RECOVERY_REQUIRED",
44
- `configuration audit is malformed at line ${index + 1}`,
45
- );
46
- }
47
- if (record.transactionId) latest.set(record.transactionId, record);
48
- }
49
- return latest;
50
- }
51
-
52
- export async function validateAuditUnlocked(workspace) {
53
- await readLatestAuditRecordsUnlocked(workspace);
54
- }
55
-
56
- export async function reconcileAuditUnlocked(workspace, currentHash) {
57
- const latest = await readLatestAuditRecordsUnlocked(workspace);
58
- for (const record of latest.values()) {
59
- if (record.phase !== "prepared" && record.phase !== "recovery-required") continue;
60
- if (currentHash === record.beforeHash) {
61
- await appendAuditUnlocked(workspace, {
62
- ...record,
63
- timestamp: new Date().toISOString(),
64
- phase: "aborted",
65
- reconciliation: "matched-before",
66
- });
67
- } else if (currentHash === record.afterHash) {
68
- await appendAuditUnlocked(workspace, {
69
- ...record,
70
- timestamp: new Date().toISOString(),
71
- phase: "committed",
72
- reconciliation: "matched-after",
73
- });
74
- } else {
75
- throw taskChefError(
76
- "STATE_RECOVERY_REQUIRED",
77
- `configuration does not match either side of transaction ${record.transactionId}`,
78
- { transactionId: record.transactionId },
79
- );
80
- }
81
- }
82
- }
83
-
84
- export async function supersedeAuditTransactionsUnlocked(workspace, recoveryTransactionId) {
85
- const latest = await readLatestAuditRecordsUnlocked(workspace);
86
- const superseded = [];
87
- for (const record of latest.values()) {
88
- if (record.transactionId === recoveryTransactionId
89
- || !new Set(["prepared", "recovery-required"]).has(record.phase)) continue;
90
- await appendAuditUnlocked(workspace, {
91
- ...record,
92
- timestamp: new Date().toISOString(),
93
- phase: "superseded",
94
- supersededBy: recoveryTransactionId,
95
- });
96
- superseded.push(record.transactionId);
97
- }
98
- return superseded;
99
- }
@@ -1,106 +0,0 @@
1
- import { semanticHash, taskChefError } from "./state-store.js";
2
-
3
- function fieldsChanged(before, after) {
4
- const fields = {};
5
- for (const key of ["name", "description", "isGitRepository", "githubRepos"]) {
6
- if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) {
7
- fields[key] = { before: before[key] ?? null, after: after[key] ?? null };
8
- }
9
- }
10
- return fields;
11
- }
12
-
13
- export function projectSetHash(projects) {
14
- return semanticHash({ schemaVersion: 1, projects }, "taskchef-project-set-v1");
15
- }
16
-
17
- export function configHash(config) {
18
- return semanticHash(config, "taskchef-config-v1");
19
- }
20
-
21
- export function projectDiff(beforeProjects, afterProjects) {
22
- const before = new Map(beforeProjects.map((project) => [project.path, project]));
23
- const after = new Map(afterProjects.map((project) => [project.path, project]));
24
- const added = [];
25
- const changed = [];
26
- const removed = [];
27
- for (const project of afterProjects) {
28
- const prior = before.get(project.path);
29
- if (!prior) added.push(project);
30
- else {
31
- const fields = fieldsChanged(prior, project);
32
- if (Object.keys(fields).length) changed.push({ path: project.path, before: prior, after: project, fields });
33
- }
34
- }
35
- for (const project of beforeProjects) {
36
- if (!after.has(project.path)) removed.push(project);
37
- }
38
- return { added, changed, removed };
39
- }
40
-
41
- export function mutationPreview(workspace, operation, beforeConfig, afterConfig) {
42
- const diff = projectDiff(beforeConfig.projects, afterConfig.projects);
43
- const emptyIndex = { bindings: [], exclusions: [], routingHints: [] };
44
- const beforeProjectIndex = beforeConfig.projectIndex ?? emptyIndex;
45
- const afterProjectIndex = afterConfig.projectIndex ?? emptyIndex;
46
- const projectIndexDiff = JSON.stringify(beforeProjectIndex) === JSON.stringify(afterProjectIndex)
47
- ? null
48
- : { before: beforeProjectIndex, after: afterProjectIndex };
49
- const preview = {
50
- schemaVersion: 1,
51
- operation,
52
- beforeConfigHash: configHash(beforeConfig),
53
- afterConfigHash: configHash(afterConfig),
54
- beforeProjectSetHash: projectSetHash(beforeConfig.projects),
55
- afterProjectSetHash: projectSetHash(afterConfig.projects),
56
- beforeCount: beforeConfig.projects.length,
57
- afterCount: afterConfig.projects.length,
58
- diff,
59
- projectIndexDiff,
60
- projects: afterConfig.projects,
61
- };
62
- return {
63
- ...preview,
64
- planHash: semanticHash({
65
- schemaVersion: 1,
66
- workspace,
67
- operation,
68
- beforeConfigHash: preview.beforeConfigHash,
69
- afterConfigHash: preview.afterConfigHash,
70
- diff,
71
- projectIndexDiff,
72
- }, "taskchef-mutation-plan-v1"),
73
- };
74
- }
75
-
76
- function exactStringSet(actual, expected) {
77
- if (!Array.isArray(actual) || new Set(actual).size !== actual.length) return false;
78
- return actual.length === expected.length
79
- && [...actual].sort().every((value, index) => value === [...expected].sort()[index]);
80
- }
81
-
82
- export function assertMutationAuthorization(preview, authorization = {}, {
83
- destructive = preview.diff.removed.length > 0,
84
- replacement = false,
85
- } = {}) {
86
- if (authorization.expectConfigHash !== preview.beforeConfigHash) {
87
- throw taskChefError("STALE_PREVIEW", "configuration changed or --expect-config-hash is missing", preview);
88
- }
89
- if (!destructive && !replacement) return;
90
- const removedNames = preview.diff.removed.map((project) => project.name);
91
- if (authorization.expectProjectCount !== preview.beforeCount
92
- || authorization.expectAfterCount !== preview.afterCount
93
- || authorization.confirmPlan !== preview.planHash
94
- || !exactStringSet(authorization.confirmRemoved, removedNames)) {
95
- throw taskChefError(
96
- "CONFIRMATION_REQUIRED",
97
- "destructive project mutation requires exact preview-bound count, hash, plan, and removed-name confirmation",
98
- preview,
99
- );
100
- }
101
- const collapses = preview.beforeCount > 0
102
- && (preview.afterCount === 0 || preview.afterCount / preview.beforeCount <= 0.5);
103
- if (collapses && authorization.confirmCountCollapse !== `${preview.beforeCount}:${preview.afterCount}`) {
104
- throw taskChefError("CONFIRMATION_REQUIRED", "project-count collapse requires exact before:after confirmation", preview);
105
- }
106
- }
@@ -1,136 +0,0 @@
1
- export async function openTaskFromControl(event, taskId, {
2
- fetchAction = globalThis.fetch,
3
- showMessage,
4
- } = {}) {
5
- event.stopPropagation();
6
- const control = event.currentTarget;
7
- control.disabled = true;
8
- try {
9
- const response = await fetchAction(`/api/tasks/${encodeURIComponent(taskId)}/open-codex`, {
10
- method: "POST",
11
- });
12
- const result = await response.json();
13
- if (result.message) showMessage(result.message);
14
- } finally {
15
- control.disabled = false;
16
- }
17
- }
18
-
19
- export async function archiveTaskFromControl(event, task, {
20
- confirmAction = globalThis.confirm,
21
- fetchAction = globalThis.fetch,
22
- onArchived = () => {},
23
- showUpdate,
24
- } = {}) {
25
- event.stopPropagation();
26
- const accepted = confirmAction(
27
- `Archive “${task.title}” in Codex?\n\n`
28
- + "The chat will leave active Codex lists, and spawned descendant chats may also be archived. "
29
- + "TaskChef history will remain available.",
30
- );
31
- if (!accepted) return false;
32
- const control = event.currentTarget;
33
- let archived = false;
34
- control.disabled = true;
35
- try {
36
- const response = await fetchAction(
37
- `/api/tasks/${encodeURIComponent(task.id)}/archive-codex`,
38
- { method: "POST" },
39
- );
40
- const result = await response.json();
41
- if (!response.ok) {
42
- showUpdate(result.message ?? "Codex could not archive this chat.", { failed: true });
43
- return false;
44
- }
45
- onArchived(task.threadId);
46
- archived = true;
47
- showUpdate(result.message ?? "Archived the Codex chat. TaskChef history remains available.");
48
- return true;
49
- } catch {
50
- showUpdate("Codex chat archiving is temporarily unavailable. Try again.", { failed: true });
51
- return false;
52
- } finally {
53
- if (!archived) control.disabled = false;
54
- }
55
- }
56
-
57
- export async function manuallyTransitionTaskFromControl(event, task, targetStatus, actionId, {
58
- clearTimer = globalThis.clearTimeout,
59
- fetchAction = globalThis.fetch,
60
- setTimer = globalThis.setTimeout,
61
- timeoutMs = 10_000,
62
- } = {}) {
63
- event?.stopPropagation?.();
64
- const controller = new AbortController();
65
- const timeout = setTimer(() => controller.abort(), timeoutMs);
66
- try {
67
- const response = await fetchAction(
68
- `/api/tasks/${encodeURIComponent(task.id)}/manual-transition`,
69
- {
70
- method: "POST",
71
- headers: { "Content-Type": "application/json" },
72
- body: JSON.stringify({
73
- schemaVersion: 1,
74
- actionId,
75
- expected: {
76
- status: task.status,
77
- turnRef: task.turnRef,
78
- threadId: task.threadId,
79
- updatedAt: task.updatedAt,
80
- },
81
- targetStatus,
82
- }),
83
- signal: controller.signal,
84
- },
85
- );
86
- const result = await response.json();
87
- return response.ok
88
- ? { ok: true, ...result }
89
- : {
90
- ok: false,
91
- code: result.code ?? "dashboard_error",
92
- message: result.message ?? "Task state could not be changed.",
93
- task: result.task ?? null,
94
- };
95
- } catch {
96
- return {
97
- ok: false,
98
- code: controller.signal.aborted ? "request_timeout" : "network_error",
99
- message: controller.signal.aborted
100
- ? "Task state change timed out. Try again."
101
- : "Task state changes are temporarily unavailable. Try again.",
102
- task: null,
103
- };
104
- } finally {
105
- clearTimer(timeout);
106
- }
107
- }
108
-
109
- export function handleManualTransitionEscape(event, {
110
- active,
111
- pending,
112
- cancel,
113
- }) {
114
- if (event.key !== "Escape" || !active) return false;
115
- event.preventDefault();
116
- event.stopPropagation();
117
- if (!pending) cancel();
118
- return true;
119
- }
120
-
121
- export function focusManualTransitionStatus(panel) {
122
- const status = panel.querySelector('[data-manual-focus="pending"]');
123
- status?.focus();
124
- return status !== null;
125
- }
126
-
127
- export function restoreTaskActionMenuFocus(panel, activeElement, fallback) {
128
- if (!activeElement || !panel.contains(activeElement)) return null;
129
- if (!activeElement.hidden && !activeElement.disabled) return activeElement;
130
- const copyTaskId = panel.querySelector('[data-manual-focus="copy"]');
131
- const destination = copyTaskId && !copyTaskId.hidden && !copyTaskId.disabled
132
- ? copyTaskId
133
- : fallback;
134
- destination?.focus();
135
- return destination ?? null;
136
- }