taskchef 5.8.0 → 5.10.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 +1 -1
- package/README.md +60 -0
- package/index.js +9 -0
- package/package.json +2 -2
- package/src/cli.js +44 -0
- package/src/codex-app.js +36 -3
- package/src/dashboard/app.js +265 -0
- package/src/dashboard/index.html +94 -0
- package/src/dashboard/state.js +79 -0
- package/src/dashboard/styles.css +147 -0
- package/src/dashboard.js +615 -0
- package/src/workspace.js +28 -9
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const MAX_NOTIFICATIONS = 50;
|
|
2
|
+
export const KNOWN_TASK_STATUSES = [
|
|
3
|
+
"working",
|
|
4
|
+
"needs input",
|
|
5
|
+
"completed",
|
|
6
|
+
"failed",
|
|
7
|
+
"unresolved",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const DATE_WINDOWS_MS = new Map([
|
|
11
|
+
["24h", 24 * 60 * 60 * 1_000],
|
|
12
|
+
["7d", 7 * 24 * 60 * 60 * 1_000],
|
|
13
|
+
["all", null],
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function taskMeaningfulTime(task) {
|
|
17
|
+
return Date.parse(task.meaningfulUpdatedAt ?? task.updatedAt ?? task.createdAt);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function taskStatusLabel(task) {
|
|
21
|
+
return task.status === null ? "unresolved" : task.status.replaceAll("_", " ");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function taskWithinDateFilter(task, filter, now = Date.now()) {
|
|
25
|
+
const windowMs = DATE_WINDOWS_MS.get(filter);
|
|
26
|
+
if (windowMs === null) return true;
|
|
27
|
+
if (windowMs === undefined) return false;
|
|
28
|
+
const meaningfulTime = taskMeaningfulTime(task);
|
|
29
|
+
return Number.isFinite(meaningfulTime) && meaningfulTime >= now - windowMs;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function nextDateFilterRefreshDelay(tasks, filter, now = Date.now()) {
|
|
33
|
+
const windowMs = DATE_WINDOWS_MS.get(filter);
|
|
34
|
+
if (windowMs === null || windowMs === undefined) return null;
|
|
35
|
+
const nextCutoff = tasks
|
|
36
|
+
.map((task) => taskMeaningfulTime(task) + windowMs - now)
|
|
37
|
+
.filter((delay) => Number.isFinite(delay) && delay >= 0)
|
|
38
|
+
.reduce((minimum, delay) => Math.min(minimum, delay), Number.POSITIVE_INFINITY);
|
|
39
|
+
return Number.isFinite(nextCutoff) ? nextCutoff + 1 : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function taskSignature(task) {
|
|
43
|
+
return JSON.stringify([
|
|
44
|
+
task.threadId,
|
|
45
|
+
task.status,
|
|
46
|
+
task.summary,
|
|
47
|
+
task.turnId,
|
|
48
|
+
task.updatedAt,
|
|
49
|
+
task.updatedBy,
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function findCurrentTask(tasks, taskId) {
|
|
54
|
+
return tasks.find((task) => task.id === taskId) ?? null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function reconcileNotifications(
|
|
58
|
+
{ initialized, notifications, signatures },
|
|
59
|
+
tasks,
|
|
60
|
+
revision,
|
|
61
|
+
) {
|
|
62
|
+
const nextSignatures = new Map(tasks.map((task) => [task.id, taskSignature(task)]));
|
|
63
|
+
if (!initialized) return { notifications, signatures: nextSignatures };
|
|
64
|
+
const additions = [];
|
|
65
|
+
for (const task of tasks) {
|
|
66
|
+
let kind = null;
|
|
67
|
+
if (!signatures.has(task.id)) kind = "new";
|
|
68
|
+
else if (signatures.get(task.id) !== nextSignatures.get(task.id)) kind = "changed";
|
|
69
|
+
if (kind) additions.push({
|
|
70
|
+
id: `${revision}:${task.id}`,
|
|
71
|
+
kind,
|
|
72
|
+
taskId: task.id,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
notifications: [...additions, ...notifications].slice(0, MAX_NOTIFICATIONS),
|
|
77
|
+
signatures: nextSignatures,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--background: #f5f4f0;
|
|
4
|
+
--surface: #ffffff;
|
|
5
|
+
--surface-muted: #eeece6;
|
|
6
|
+
--text: #1d211f;
|
|
7
|
+
--muted: #626964;
|
|
8
|
+
--border: #d8d7d1;
|
|
9
|
+
--accent: #215f4a;
|
|
10
|
+
--accent-soft: #dcebe4;
|
|
11
|
+
--danger: #8b3a35;
|
|
12
|
+
--warning: #8a5c17;
|
|
13
|
+
--shadow: 0 12px 30px rgb(24 33 29 / 10%);
|
|
14
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* { box-sizing: border-box; }
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
margin: 0;
|
|
21
|
+
background: var(--background);
|
|
22
|
+
color: var(--text);
|
|
23
|
+
line-height: 1.5;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
button, select { font: inherit; }
|
|
27
|
+
button { color: inherit; }
|
|
28
|
+
|
|
29
|
+
.site-header, main {
|
|
30
|
+
width: min(1080px, calc(100% - 40px));
|
|
31
|
+
margin-inline: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.site-header {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: flex-end;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
gap: 24px;
|
|
39
|
+
padding-block: 48px 28px;
|
|
40
|
+
border-bottom: 1px solid var(--border);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
h1, h2, h3, p { margin-top: 0; }
|
|
44
|
+
h1 { margin-bottom: 6px; font-size: clamp(2rem, 5vw, 3.4rem); line-height: 1; letter-spacing: -0.04em; }
|
|
45
|
+
h2 { line-height: 1.2; }
|
|
46
|
+
h3 { margin-bottom: 8px; font-size: 0.84rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
|
|
47
|
+
|
|
48
|
+
.eyebrow { margin-bottom: 10px; color: var(--accent); font-size: 0.78rem; font-weight: 750; letter-spacing: 0.15em; text-transform: uppercase; }
|
|
49
|
+
.subtitle, .task-project, time { color: var(--muted); }
|
|
50
|
+
.subtitle { margin-bottom: 0; }
|
|
51
|
+
|
|
52
|
+
.connection { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 0.9rem; }
|
|
53
|
+
.connection-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--warning); }
|
|
54
|
+
.connection-dot.connected { background: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
|
|
55
|
+
|
|
56
|
+
main { padding-block: 28px 80px; }
|
|
57
|
+
|
|
58
|
+
.toolbar {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: flex-end;
|
|
61
|
+
gap: 16px;
|
|
62
|
+
margin-bottom: 22px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
label { display: grid; gap: 6px; color: var(--muted); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
66
|
+
select { min-width: 180px; padding: 9px 34px 9px 11px; border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--text); }
|
|
67
|
+
.task-count { margin: 0 0 9px auto; color: var(--muted); font-size: 0.9rem; }
|
|
68
|
+
|
|
69
|
+
.task-list { display: grid; gap: 12px; }
|
|
70
|
+
.task-card { padding: 20px 22px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); box-shadow: 0 1px 0 rgb(0 0 0 / 2%); }
|
|
71
|
+
.task-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; }
|
|
72
|
+
.task-title { padding: 0; border: 0; background: none; font-size: 1.08rem; font-weight: 720; text-align: left; cursor: pointer; }
|
|
73
|
+
.task-title:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
|
|
74
|
+
.task-project { margin: 3px 0 12px; font-size: 0.86rem; }
|
|
75
|
+
.task-summary { max-width: 78ch; margin-bottom: 14px; }
|
|
76
|
+
time { font-size: 0.78rem; }
|
|
77
|
+
|
|
78
|
+
.status { flex: none; padding: 4px 9px; border-radius: 999px; background: var(--surface-muted); color: var(--muted); font-size: 0.72rem; font-weight: 750; text-transform: capitalize; }
|
|
79
|
+
.status-completed { background: var(--accent-soft); color: var(--accent); }
|
|
80
|
+
.status-failed { background: #f3dfdd; color: var(--danger); }
|
|
81
|
+
.status-needs_input { background: #f4e9ce; color: var(--warning); }
|
|
82
|
+
|
|
83
|
+
.empty-state { padding: 60px 20px; border: 1px dashed var(--border); border-radius: 10px; text-align: center; color: var(--muted); }
|
|
84
|
+
.empty-state h2 { margin-bottom: 6px; color: var(--text); }
|
|
85
|
+
|
|
86
|
+
.dashboard-message { margin-bottom: 18px; padding: 11px 14px; border: 1px solid #d8c58e; border-radius: 7px; background: #fff7de; color: #604613; }
|
|
87
|
+
|
|
88
|
+
.notifications { position: fixed; right: 20px; bottom: 20px; z-index: 3; width: min(380px, calc(100vw - 40px)); padding: 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow); }
|
|
89
|
+
.notifications-heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
|
90
|
+
.toast-list { display: grid; gap: 7px; max-height: 50vh; overflow: auto; }
|
|
91
|
+
.toast { display: flex; align-items: flex-start; gap: 8px; padding: 10px; border-radius: 7px; background: var(--surface-muted); }
|
|
92
|
+
.toast-content { display: grid; flex: 1; gap: 2px; padding: 0; border: 0; background: none; text-align: left; cursor: pointer; }
|
|
93
|
+
.toast-content span { color: var(--muted); font-size: 0.85rem; }
|
|
94
|
+
|
|
95
|
+
.text-button, .icon-button { border: 0; background: none; cursor: pointer; }
|
|
96
|
+
.text-button { color: var(--accent); font-size: 0.82rem; font-weight: 700; }
|
|
97
|
+
.icon-button { padding: 0 4px; color: var(--muted); font-size: 1.4rem; line-height: 1; }
|
|
98
|
+
|
|
99
|
+
dialog { width: min(760px, calc(100vw - 32px)); max-height: min(82vh, 900px); padding: 28px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); color: var(--text); box-shadow: var(--shadow); }
|
|
100
|
+
dialog::backdrop { background: rgb(18 23 21 / 50%); backdrop-filter: blur(2px); }
|
|
101
|
+
.dialog-header { display: flex; justify-content: space-between; gap: 20px; }
|
|
102
|
+
.dialog-header h2 { margin-bottom: 12px; font-size: 1.6rem; }
|
|
103
|
+
.dialog-actions { display: flex; gap: 8px; margin-bottom: 26px; }
|
|
104
|
+
.primary-button, .secondary-button { padding: 8px 12px; border-radius: 7px; font-weight: 700; cursor: pointer; }
|
|
105
|
+
.primary-button { border: 1px solid var(--accent); background: var(--accent); color: white; }
|
|
106
|
+
.secondary-button { border: 1px solid var(--border); background: var(--surface); }
|
|
107
|
+
.primary-button:disabled, .secondary-button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
108
|
+
dialog section + section { margin-top: 24px; }
|
|
109
|
+
.preserve-lines { white-space: pre-wrap; }
|
|
110
|
+
pre { max-height: 280px; margin: 0; padding: 14px; overflow: auto; border-radius: 7px; background: var(--surface-muted); white-space: pre-wrap; overflow-wrap: anywhere; font: 0.86rem/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
111
|
+
.metadata { display: grid; grid-template-columns: minmax(100px, 150px) 1fr; margin: 0; font-size: 0.88rem; }
|
|
112
|
+
.metadata dt, .metadata dd { padding: 7px 0; border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }
|
|
113
|
+
.metadata dt { color: var(--muted); }
|
|
114
|
+
.metadata dd { margin: 0; }
|
|
115
|
+
|
|
116
|
+
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
117
|
+
:focus-visible { outline: 3px solid rgb(33 95 74 / 35%); outline-offset: 3px; }
|
|
118
|
+
|
|
119
|
+
@media (max-width: 650px) {
|
|
120
|
+
.site-header { align-items: flex-start; padding-top: 32px; }
|
|
121
|
+
.toolbar { align-items: stretch; flex-direction: column; }
|
|
122
|
+
.toolbar label, .toolbar select { width: 100%; }
|
|
123
|
+
.task-count { margin: 0; }
|
|
124
|
+
.task-heading { align-items: flex-start; }
|
|
125
|
+
.dialog-actions { align-items: stretch; flex-direction: column; }
|
|
126
|
+
.metadata { grid-template-columns: 1fr; }
|
|
127
|
+
.metadata dt { padding-bottom: 0; border-bottom: 0; }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@media (prefers-color-scheme: dark) {
|
|
131
|
+
:root {
|
|
132
|
+
color-scheme: dark;
|
|
133
|
+
--background: #181b19;
|
|
134
|
+
--surface: #222624;
|
|
135
|
+
--surface-muted: #2d322f;
|
|
136
|
+
--text: #edf1ee;
|
|
137
|
+
--muted: #aeb7b1;
|
|
138
|
+
--border: #3d4440;
|
|
139
|
+
--accent: #7dc1a4;
|
|
140
|
+
--accent-soft: #244436;
|
|
141
|
+
--danger: #ef9991;
|
|
142
|
+
--warning: #e4bd77;
|
|
143
|
+
}
|
|
144
|
+
.dashboard-message { border-color: #66592e; background: #38321d; color: #f1d98c; }
|
|
145
|
+
.status-failed { background: #4e2927; }
|
|
146
|
+
.status-needs_input { background: #4c3b20; }
|
|
147
|
+
}
|