taskchef 7.13.0 → 7.14.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/package.json +1 -1
- package/src/dashboard/app.js +70 -3
- package/src/dashboard/github-links.js +197 -0
- package/src/dashboard/index.html +1 -0
- package/src/dashboard/styles.css +12 -1
- package/src/dashboard.js +11 -2
package/package.json
CHANGED
package/src/dashboard/app.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
turnPresentation,
|
|
17
17
|
} from "./state.js";
|
|
18
18
|
import { openTaskFromControl } from "./actions.js";
|
|
19
|
+
import { githubReferenceSegments } from "./github-links.js";
|
|
19
20
|
import { formatRelativeTime, RelativeTimeController, parsedTimestamp } from "./time.js";
|
|
20
21
|
|
|
21
22
|
const state = {
|
|
@@ -43,6 +44,7 @@ const elements = {
|
|
|
43
44
|
dialogInstruction: document.querySelector("#dialog-instruction"),
|
|
44
45
|
dialogMetadata: document.querySelector("#dialog-metadata"),
|
|
45
46
|
dialogProject: document.querySelector("#dialog-project"),
|
|
47
|
+
dialogRelatedLinks: document.querySelector("#dialog-related-links"),
|
|
46
48
|
dialogResults: document.querySelector("#dialog-results"),
|
|
47
49
|
dialogTitle: document.querySelector("#dialog-title"),
|
|
48
50
|
dismissDashboardMessage: document.querySelector("#dismiss-dashboard-message"),
|
|
@@ -59,6 +61,58 @@ const elements = {
|
|
|
59
61
|
};
|
|
60
62
|
let notificationDescriptionSerial = 0;
|
|
61
63
|
|
|
64
|
+
function githubLink(link, { compact = false } = {}) {
|
|
65
|
+
const anchor = document.createElement("a");
|
|
66
|
+
anchor.className = compact ? "github-link github-link-compact" : "github-link";
|
|
67
|
+
anchor.href = link.url;
|
|
68
|
+
anchor.target = "_blank";
|
|
69
|
+
anchor.rel = "noopener noreferrer";
|
|
70
|
+
anchor.textContent = link.label ?? link.text;
|
|
71
|
+
anchor.setAttribute(
|
|
72
|
+
"aria-label",
|
|
73
|
+
`${link.label ?? link.text} on GitHub (opens in a new tab)`,
|
|
74
|
+
);
|
|
75
|
+
anchor.addEventListener("click", (event) => event.stopPropagation());
|
|
76
|
+
return anchor;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function appendGitHubText(container, text, task) {
|
|
80
|
+
const children = githubReferenceSegments(text, {
|
|
81
|
+
projectRepositories: task.project?.githubRepos,
|
|
82
|
+
taskRepository: task.relatedGitHubRepository,
|
|
83
|
+
}).map((segment) => {
|
|
84
|
+
if (segment.kind === "text") return document.createTextNode(segment.text);
|
|
85
|
+
if (segment.kind === "link") return githubLink(segment);
|
|
86
|
+
const ambiguous = document.createElement("span");
|
|
87
|
+
ambiguous.className = "github-reference-ambiguous";
|
|
88
|
+
ambiguous.textContent = segment.text;
|
|
89
|
+
ambiguous.tabIndex = 0;
|
|
90
|
+
ambiguous.title = segment.reason;
|
|
91
|
+
ambiguous.setAttribute("aria-label", segment.reason);
|
|
92
|
+
return ambiguous;
|
|
93
|
+
});
|
|
94
|
+
container.replaceChildren(...children);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function relatedGitHubLinks(task, { compact = false } = {}) {
|
|
98
|
+
const links = task.relatedGitHubLinks ?? [];
|
|
99
|
+
const container = document.createElement("nav");
|
|
100
|
+
container.className = `github-links${compact ? " github-links-compact" : ""}`;
|
|
101
|
+
container.setAttribute("aria-label", `Related GitHub links for ${task.title}`);
|
|
102
|
+
const children = links.map((link) => githubLink(link, { compact }));
|
|
103
|
+
if (task.relatedGitHubLinksTruncated) {
|
|
104
|
+
const more = document.createElement("span");
|
|
105
|
+
more.className = "github-links-more";
|
|
106
|
+
more.textContent = "+ more";
|
|
107
|
+
more.title = "Additional related GitHub references are omitted from this bounded dashboard view.";
|
|
108
|
+
more.setAttribute("aria-label", more.title);
|
|
109
|
+
children.push(more);
|
|
110
|
+
}
|
|
111
|
+
container.hidden = children.length === 0;
|
|
112
|
+
container.replaceChildren(...children);
|
|
113
|
+
return container;
|
|
114
|
+
}
|
|
115
|
+
|
|
62
116
|
function timestampControl(value, { accessibleName, key, prefix = "" }) {
|
|
63
117
|
if (!parsedTimestamp(value)) {
|
|
64
118
|
const missing = document.createElement("span");
|
|
@@ -253,12 +307,16 @@ function turnTimeline(task) {
|
|
|
253
307
|
requestLabel.textContent = "Request";
|
|
254
308
|
const request = document.createElement("p");
|
|
255
309
|
request.className = "preserve-lines";
|
|
256
|
-
|
|
310
|
+
appendGitHubText(
|
|
311
|
+
request,
|
|
312
|
+
turn.requestSummary ?? "Request not recorded by this TaskChef version.",
|
|
313
|
+
task,
|
|
314
|
+
);
|
|
257
315
|
const resultLabel = document.createElement("h4");
|
|
258
316
|
resultLabel.textContent = "Result";
|
|
259
317
|
const result = document.createElement("p");
|
|
260
318
|
result.className = "preserve-lines";
|
|
261
|
-
result
|
|
319
|
+
appendGitHubText(result, presentation.summary, task);
|
|
262
320
|
const turnMetadata = document.createElement("p");
|
|
263
321
|
turnMetadata.className = "result-history-turn";
|
|
264
322
|
turnMetadata.textContent = turn.turnId
|
|
@@ -281,6 +339,14 @@ function renderDialog(task) {
|
|
|
281
339
|
state.selectedTask = detailedTask;
|
|
282
340
|
elements.dialogProject.textContent = task.project.name;
|
|
283
341
|
elements.dialogTitle.textContent = task.title;
|
|
342
|
+
elements.dialogRelatedLinks.replaceChildren(...relatedGitHubLinks(detailedTask).children);
|
|
343
|
+
elements.dialogRelatedLinks.setAttribute(
|
|
344
|
+
"aria-label",
|
|
345
|
+
`Related GitHub links for ${task.title}`,
|
|
346
|
+
);
|
|
347
|
+
elements.dialogRelatedLinks.hidden = (
|
|
348
|
+
(task.relatedGitHubLinks?.length ?? 0) === 0 && !task.relatedGitHubLinksTruncated
|
|
349
|
+
);
|
|
284
350
|
elements.dialogResults.replaceChildren(...turnTimeline(detailedTask));
|
|
285
351
|
elements.dialogInstruction.textContent = task.instruction;
|
|
286
352
|
elements.copyThreadId.disabled = !task.threadId;
|
|
@@ -365,6 +431,7 @@ function taskCard(task) {
|
|
|
365
431
|
result.className = "preserve-lines";
|
|
366
432
|
result.textContent = latest.resultSummary;
|
|
367
433
|
summary.replaceChildren(requestLabel, request, resultLabel, result);
|
|
434
|
+
const relatedLinks = relatedGitHubLinks(task, { compact: true });
|
|
368
435
|
const time = timestampControl(
|
|
369
436
|
task.meaningfulUpdatedAt ?? task.updatedAt ?? task.createdAt,
|
|
370
437
|
{
|
|
@@ -383,7 +450,7 @@ function taskCard(task) {
|
|
|
383
450
|
showMessage,
|
|
384
451
|
}));
|
|
385
452
|
footer.append(time, openTask);
|
|
386
|
-
article.append(heading, project, summary, footer);
|
|
453
|
+
article.append(heading, project, summary, relatedLinks, footer);
|
|
387
454
|
return article;
|
|
388
455
|
}
|
|
389
456
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
const GITHUB_REPOSITORY = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\/[A-Za-z0-9._-]+$/;
|
|
2
|
+
const REFERENCE_PATTERN = /https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))\/([A-Za-z0-9._-]+)\/(issues|pull)\/([1-9]\d*)(?![A-Za-z0-9_])|([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))\/([A-Za-z0-9._-]+)#([1-9]\d*)(?![A-Za-z0-9_])|#([1-9]\d*)(?![A-Za-z0-9_])/gi;
|
|
3
|
+
export const MAX_RELATED_GITHUB_LINKS = 20;
|
|
4
|
+
|
|
5
|
+
function canonicalRepository(owner, repository) {
|
|
6
|
+
return `${owner.toLowerCase()}/${repository.toLowerCase()}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function validRepositoryName(repository) {
|
|
10
|
+
return repository !== "." && repository !== "..";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeGitHubRepository(value) {
|
|
14
|
+
if (typeof value !== "string") return null;
|
|
15
|
+
let candidate = value.trim();
|
|
16
|
+
const url = candidate.match(/^https?:\/\/(?:www\.)?github\.com\/([^/?#]+)\/([^/?#]+?)(?:\.git)?\/?$/i);
|
|
17
|
+
if (url) candidate = `${url[1]}/${url[2]}`;
|
|
18
|
+
if (!GITHUB_REPOSITORY.test(candidate)) return null;
|
|
19
|
+
const [owner, repository] = candidate.split("/");
|
|
20
|
+
if (!validRepositoryName(repository)) return null;
|
|
21
|
+
return canonicalRepository(owner, repository);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function rawReferences(text) {
|
|
25
|
+
const value = String(text ?? "");
|
|
26
|
+
const references = [];
|
|
27
|
+
for (const match of value.matchAll(REFERENCE_PATTERN)) {
|
|
28
|
+
const start = match.index;
|
|
29
|
+
const preceding = start > 0 ? value[start - 1] : "";
|
|
30
|
+
if (match[8] && /[\w&#/]/.test(preceding)) continue;
|
|
31
|
+
if ((match[5] || match[1]) && /[\w/]/.test(preceding)) continue;
|
|
32
|
+
|
|
33
|
+
let owner = match[1] ?? match[5] ?? null;
|
|
34
|
+
let repository = match[2] ?? match[6] ?? null;
|
|
35
|
+
const number = match[4] ?? match[7] ?? match[8];
|
|
36
|
+
const pathType = match[3]?.toLowerCase();
|
|
37
|
+
const type = pathType === "pull" ? "pull" : pathType === "issues" ? "issue" : "generic";
|
|
38
|
+
const explicit = Boolean(owner && repository);
|
|
39
|
+
if (explicit) {
|
|
40
|
+
if (!validRepositoryName(repository)) continue;
|
|
41
|
+
const normalized = canonicalRepository(owner, repository);
|
|
42
|
+
[owner, repository] = normalized.split("/");
|
|
43
|
+
}
|
|
44
|
+
references.push({
|
|
45
|
+
end: start + match[0].length,
|
|
46
|
+
explicit,
|
|
47
|
+
number,
|
|
48
|
+
owner,
|
|
49
|
+
repository,
|
|
50
|
+
start,
|
|
51
|
+
text: match[0],
|
|
52
|
+
type,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return references;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function uniqueExplicitRepository(references) {
|
|
59
|
+
const repositories = new Set(
|
|
60
|
+
references.filter(({ explicit }) => explicit).map(({ owner, repository }) => (
|
|
61
|
+
`${owner}/${repository}`
|
|
62
|
+
)),
|
|
63
|
+
);
|
|
64
|
+
return repositories.size === 1 ? [...repositories][0] : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function uniqueProjectRepository(projectRepositories) {
|
|
68
|
+
const repositories = new Set(
|
|
69
|
+
(projectRepositories ?? []).map(normalizeGitHubRepository).filter(Boolean),
|
|
70
|
+
);
|
|
71
|
+
return repositories.size === 1 ? [...repositories][0] : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolvedReference(reference, repositoryContext) {
|
|
75
|
+
const repository = reference.explicit
|
|
76
|
+
? `${reference.owner}/${reference.repository}`
|
|
77
|
+
: repositoryContext;
|
|
78
|
+
if (!repository) return null;
|
|
79
|
+
const [owner, repositoryName] = repository.split("/");
|
|
80
|
+
const path = reference.type === "pull" ? "pull" : "issues";
|
|
81
|
+
return {
|
|
82
|
+
kind: "link",
|
|
83
|
+
number: reference.number,
|
|
84
|
+
owner,
|
|
85
|
+
repository: repositoryName,
|
|
86
|
+
text: reference.text,
|
|
87
|
+
type: reference.type,
|
|
88
|
+
url: `https://github.com/${owner}/${repositoryName}/${path}/${reference.number}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function githubReferenceSegments(text, {
|
|
93
|
+
projectRepositories = [],
|
|
94
|
+
taskRepository = null,
|
|
95
|
+
} = {}) {
|
|
96
|
+
const value = String(text ?? "");
|
|
97
|
+
const references = rawReferences(value);
|
|
98
|
+
const sourceRepository = uniqueExplicitRepository(references);
|
|
99
|
+
const repositoryContext = sourceRepository
|
|
100
|
+
?? normalizeGitHubRepository(taskRepository)
|
|
101
|
+
?? uniqueProjectRepository(projectRepositories);
|
|
102
|
+
const segments = [];
|
|
103
|
+
let offset = 0;
|
|
104
|
+
for (const reference of references) {
|
|
105
|
+
if (reference.start > offset) {
|
|
106
|
+
segments.push({ kind: "text", text: value.slice(offset, reference.start) });
|
|
107
|
+
}
|
|
108
|
+
const resolved = resolvedReference(reference, repositoryContext);
|
|
109
|
+
if (resolved) segments.push(resolved);
|
|
110
|
+
else {
|
|
111
|
+
segments.push({
|
|
112
|
+
kind: "ambiguous",
|
|
113
|
+
reason: `GitHub reference ${reference.text} is not linked because the repository is ambiguous.`,
|
|
114
|
+
text: reference.text,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
offset = reference.end;
|
|
118
|
+
}
|
|
119
|
+
if (offset < value.length) segments.push({ kind: "text", text: value.slice(offset) });
|
|
120
|
+
return segments;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function taskTexts(task) {
|
|
124
|
+
const texts = [task.instruction];
|
|
125
|
+
for (const turn of task.turns ?? []) {
|
|
126
|
+
texts.push(turn.requestSummary, turn.result?.summary);
|
|
127
|
+
}
|
|
128
|
+
if (!(task.turns?.length > 0)) {
|
|
129
|
+
for (const result of task.results ?? []) texts.push(result.summary);
|
|
130
|
+
texts.push(task.summary);
|
|
131
|
+
}
|
|
132
|
+
return texts.filter((text) => typeof text === "string" && text.length > 0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function relatedLinkLabel(link, includeRepository, includeOwner) {
|
|
136
|
+
const reference = link.type === "pull"
|
|
137
|
+
? `PR #${link.number}`
|
|
138
|
+
: link.type === "issue"
|
|
139
|
+
? `Issue #${link.number}`
|
|
140
|
+
: `#${link.number}`;
|
|
141
|
+
if (!includeRepository) return reference;
|
|
142
|
+
const repository = includeOwner ? `${link.owner}/${link.repository}` : link.repository;
|
|
143
|
+
return `${repository} ${reference}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function taskGitHubProjection(task) {
|
|
147
|
+
const texts = taskTexts(task);
|
|
148
|
+
const explicitRepositories = new Set();
|
|
149
|
+
for (const text of texts) {
|
|
150
|
+
for (const reference of rawReferences(text)) {
|
|
151
|
+
if (reference.explicit) {
|
|
152
|
+
explicitRepositories.add(`${reference.owner}/${reference.repository}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const taskRepository = explicitRepositories.size === 1 ? [...explicitRepositories][0] : null;
|
|
157
|
+
const links = [];
|
|
158
|
+
const seen = new Set();
|
|
159
|
+
let truncated = false;
|
|
160
|
+
sourceLoop:
|
|
161
|
+
for (const text of texts) {
|
|
162
|
+
for (const segment of githubReferenceSegments(text, {
|
|
163
|
+
projectRepositories: task.project?.githubRepos,
|
|
164
|
+
taskRepository,
|
|
165
|
+
})) {
|
|
166
|
+
if (segment.kind !== "link") continue;
|
|
167
|
+
const key = segment.url.toLowerCase();
|
|
168
|
+
if (seen.has(key)) continue;
|
|
169
|
+
if (links.length === MAX_RELATED_GITHUB_LINKS) {
|
|
170
|
+
truncated = true;
|
|
171
|
+
break sourceLoop;
|
|
172
|
+
}
|
|
173
|
+
seen.add(key);
|
|
174
|
+
links.push(segment);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const repositories = new Set(links.map(({ owner, repository }) => `${owner}/${repository}`));
|
|
178
|
+
const repositoryNames = new Map();
|
|
179
|
+
for (const link of links) {
|
|
180
|
+
const owners = repositoryNames.get(link.repository) ?? new Set();
|
|
181
|
+
owners.add(link.owner);
|
|
182
|
+
repositoryNames.set(link.repository, owners);
|
|
183
|
+
}
|
|
184
|
+
const includeRepository = repositories.size > 1;
|
|
185
|
+
return {
|
|
186
|
+
relatedGitHubLinks: links.map((link) => ({
|
|
187
|
+
label: relatedLinkLabel(link, includeRepository, repositoryNames.get(link.repository).size > 1),
|
|
188
|
+
number: link.number,
|
|
189
|
+
owner: link.owner,
|
|
190
|
+
repository: link.repository,
|
|
191
|
+
type: link.type,
|
|
192
|
+
url: link.url,
|
|
193
|
+
})),
|
|
194
|
+
relatedGitHubLinksTruncated: truncated,
|
|
195
|
+
relatedGitHubRepository: taskRepository,
|
|
196
|
+
};
|
|
197
|
+
}
|
package/src/dashboard/index.html
CHANGED
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
</button>
|
|
115
115
|
<button id="copy-thread-id" class="secondary-button" type="button">Copy thread ID</button>
|
|
116
116
|
</div>
|
|
117
|
+
<nav id="dialog-related-links" class="github-links" aria-label="Related GitHub links" hidden></nav>
|
|
117
118
|
<section>
|
|
118
119
|
<h3>Activity timeline</h3>
|
|
119
120
|
<div id="dialog-results" class="result-history"></div>
|
package/src/dashboard/styles.css
CHANGED
|
@@ -97,6 +97,15 @@ select { min-width: 180px; min-height: 36px; padding: 7px 32px 7px 10px; border:
|
|
|
97
97
|
.task-title, .task-summary { overflow-wrap: anywhere; }
|
|
98
98
|
.task-summary { display: grid; max-width: 82ch; margin-bottom: 12px; gap: 2px; font-size: 0.9rem; line-height: 1.45; }
|
|
99
99
|
.task-summary strong { margin-top: 4px; color: var(--muted); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
100
|
+
.github-links { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 18px; }
|
|
101
|
+
.github-links[hidden] { display: none; }
|
|
102
|
+
.github-links-compact { margin: -2px 0 12px; gap: 5px; }
|
|
103
|
+
.github-link { color: var(--accent); font-weight: 700; text-decoration-thickness: 1px; text-underline-offset: 3px; overflow-wrap: anywhere; }
|
|
104
|
+
.github-link-compact { padding: 2px 7px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface-muted); font-size: 0.72rem; line-height: 1.35; text-decoration: none; }
|
|
105
|
+
.github-link:hover { text-decoration-thickness: 2px; }
|
|
106
|
+
.github-link-compact:hover { border-color: var(--accent); background: var(--accent-soft); }
|
|
107
|
+
.github-links-more { align-self: center; color: var(--muted); font-size: 0.75rem; font-weight: 700; }
|
|
108
|
+
.github-reference-ambiguous { border-bottom: 2px dotted var(--warning); background: color-mix(in srgb, var(--warning) 12%, transparent); cursor: help; }
|
|
100
109
|
time, .timestamp-missing { font-size: 0.75rem; }
|
|
101
110
|
.timestamp-toggle { padding: 2px 0; border: 0; background: none; cursor: pointer; text-align: left; }
|
|
102
111
|
.timestamp-toggle:hover time { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
|
|
@@ -136,7 +145,7 @@ time, .timestamp-missing { font-size: 0.75rem; }
|
|
|
136
145
|
|
|
137
146
|
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); }
|
|
138
147
|
dialog::backdrop { background: rgb(18 23 21 / 50%); backdrop-filter: blur(2px); }
|
|
139
|
-
.dialog-header { display: flex; justify-content: space-between; gap: 20px; }
|
|
148
|
+
.dialog-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
|
|
140
149
|
.dialog-header h2 { margin-bottom: 12px; font-size: 1.6rem; }
|
|
141
150
|
.dialog-actions { display: flex; gap: 8px; margin-bottom: 26px; }
|
|
142
151
|
.primary-button, .secondary-button { padding: 8px 12px; border-radius: 7px; font-weight: 700; cursor: pointer; }
|
|
@@ -173,6 +182,8 @@ pre { max-height: 280px; margin: 0; padding: 14px; overflow: auto; border-radius
|
|
|
173
182
|
.task-heading { align-items: flex-start; }
|
|
174
183
|
.task-title, .timestamp-toggle { display: flex; align-items: center; min-height: 36px; }
|
|
175
184
|
.task-footer { align-items: flex-start; flex-direction: column; }
|
|
185
|
+
.github-links { max-width: 100%; }
|
|
186
|
+
.github-link-compact { padding-block: 5px; }
|
|
176
187
|
.task-open { align-self: flex-start; }
|
|
177
188
|
select { min-height: 40px; }
|
|
178
189
|
.status-filter-option span { min-height: 38px; }
|
package/src/dashboard.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
readConfig,
|
|
18
18
|
} from "./workspace.js";
|
|
19
19
|
import { DASHBOARD_SERVER_VERSION, TASKCHEF_VERSION } from "./version.js";
|
|
20
|
+
import { taskGitHubProjection } from "./dashboard/github-links.js";
|
|
20
21
|
|
|
21
22
|
const TASKS_FILE_NAME = "tasks.jsonl";
|
|
22
23
|
const STATIC_ROOT = fileURLToPath(new URL("./dashboard/", import.meta.url));
|
|
@@ -43,6 +44,7 @@ const STATIC_FILES = new Map([
|
|
|
43
44
|
["/", [path.join(STATIC_ROOT, "index.html"), "text/html; charset=utf-8"]],
|
|
44
45
|
["/actions.js", [path.join(STATIC_ROOT, "actions.js"), "text/javascript; charset=utf-8"]],
|
|
45
46
|
["/app.js", [path.join(STATIC_ROOT, "app.js"), "text/javascript; charset=utf-8"]],
|
|
47
|
+
["/github-links.js", [path.join(STATIC_ROOT, "github-links.js"), "text/javascript; charset=utf-8"]],
|
|
46
48
|
["/time.js", [path.join(STATIC_ROOT, "time.js"), "text/javascript; charset=utf-8"]],
|
|
47
49
|
["/state.js", [path.join(STATIC_ROOT, "state.js"), "text/javascript; charset=utf-8"]],
|
|
48
50
|
["/styles.css", [path.join(STATIC_ROOT, "styles.css"), "text/css; charset=utf-8"]],
|
|
@@ -171,10 +173,17 @@ function assertDashboardTaskBounds(tasks, maximumTasks) {
|
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
function taskListProjection(task) {
|
|
174
|
-
const { turns: _turns, results: _results, ...projection } =
|
|
176
|
+
const { turns: _turns, results: _results, ...projection } = {
|
|
177
|
+
...task,
|
|
178
|
+
...taskGitHubProjection(task),
|
|
179
|
+
};
|
|
175
180
|
return projection;
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
function taskDetailProjection(task) {
|
|
184
|
+
return { ...task, ...taskGitHubProjection(task) };
|
|
185
|
+
}
|
|
186
|
+
|
|
178
187
|
export class DashboardMonitor extends EventEmitter {
|
|
179
188
|
constructor(workspace, {
|
|
180
189
|
debounceMs = 75,
|
|
@@ -548,7 +557,7 @@ export async function createDashboardServer({
|
|
|
548
557
|
response.writeHead(200, securityHeaders("application/json; charset=utf-8"));
|
|
549
558
|
response.end();
|
|
550
559
|
} else {
|
|
551
|
-
sendJson(response, 200, { schemaVersion: 1, task });
|
|
560
|
+
sendJson(response, 200, { schemaVersion: 1, task: taskDetailProjection(task) });
|
|
552
561
|
}
|
|
553
562
|
return;
|
|
554
563
|
}
|