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
@@ -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
- }