taskchef 7.14.0 → 7.14.2
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/docs/spec.md +9 -2
- package/docs/workflows.md +7 -0
- package/package.json +1 -1
- package/skills/taskchef-executor/SKILL.md +27 -0
- package/src/dashboard/app.js +16 -11
- package/src/dashboard/github-links.js +179 -28
- package/src/mcp.js +1 -1
package/docs/spec.md
CHANGED
|
@@ -138,6 +138,13 @@ the final link, preserving the delegate skill's immediate-return contract.
|
|
|
138
138
|
TaskChef MUST atomically close it as `interrupted` before appending the new
|
|
139
139
|
working turn; the executor MUST NOT report semantic `failed` for recovery.
|
|
140
140
|
|
|
141
|
+
Request and result summaries are the durable source for dashboard related-link
|
|
142
|
+
projection; TaskChef does not scan full Codex transcripts. When known, an
|
|
143
|
+
executor MUST preserve the selected repository as a canonical GitHub repository
|
|
144
|
+
URL and issues or pull requests as canonical URLs. A result spanning both a
|
|
145
|
+
managed child repository and its workspace/root repository MUST include both
|
|
146
|
+
pull-request URLs. Executors MUST NOT guess unresolved repository identity.
|
|
147
|
+
|
|
141
148
|
If native creation fails after recording, the dispatcher MUST call
|
|
142
149
|
`report_state` with `failed`, null thread/turn IDs, and a bounded summary.
|
|
143
150
|
A link failure MUST remain visible and retryable; the executor MUST report it
|
|
@@ -287,8 +294,8 @@ marker, or ineligible state fails.
|
|
|
287
294
|
| `threadId` | Matching non-empty ID for a linked task; null only for creation failure. |
|
|
288
295
|
| `turnId` | Current canonical Codex UUIDv7 for a linked MCP journey; null only for creation failure. Maximum 256 characters at the MCP boundary. |
|
|
289
296
|
| `status` | `working`, `needs_input`, `completed`, or `failed`. |
|
|
290
|
-
| `summary` | Omitted or null for `working`; required non-empty string of at most 2,000 characters otherwise. |
|
|
291
|
-
| `requestSummary` | Concise current request of at most 1,000 characters for `working`; optional for backward compatibility and omitted for semantic states. |
|
|
297
|
+
| `summary` | Omitted or null for `working`; required non-empty string of at most 2,000 characters otherwise. Known GitHub issues and pull requests use canonical URLs. |
|
|
298
|
+
| `requestSummary` | Concise current request of at most 1,000 characters for `working`; optional for backward compatibility and omitted for semantic states. A known selected GitHub repository uses its canonical URL. |
|
|
292
299
|
|
|
293
300
|
**Structured output:** `{ task: Task }`.
|
|
294
301
|
|
package/docs/workflows.md
CHANGED
|
@@ -120,6 +120,13 @@ The executor obtains the turn identity from an exact native read of its own
|
|
|
120
120
|
linked task. `report_state` records live turn state as a paired request/result
|
|
121
121
|
timeline.
|
|
122
122
|
|
|
123
|
+
That timeline is also the durable GitHub-link source. A working request names a
|
|
124
|
+
known selected repository with its canonical GitHub URL. A terminal result
|
|
125
|
+
keeps full issue and pull-request URLs, including separate child-repository and
|
|
126
|
+
workspace/root pull requests. The dashboard does not recover omitted links by
|
|
127
|
+
scanning the native Codex transcript, and the executor does not guess an
|
|
128
|
+
ambiguous repository.
|
|
129
|
+
|
|
123
130
|
```mermaid
|
|
124
131
|
sequenceDiagram
|
|
125
132
|
autonumber
|
package/package.json
CHANGED
|
@@ -35,6 +35,9 @@ Complete this lifecycle setup before substantive assignment work:
|
|
|
35
35
|
4. Call TaskChef `report_state` with the marked task ID, self-linked thread ID,
|
|
36
36
|
current turn ID, `status: working`, an omitted or null result summary, and a
|
|
37
37
|
concise `requestSummary` describing this turn's assignment or follow-up.
|
|
38
|
+
When the turn targets a known GitHub repository, include its canonical
|
|
39
|
+
`https://github.com/<owner>/<repository>` URL so multi-repository projects
|
|
40
|
+
retain the selected repository instead of leaving TaskChef to guess.
|
|
38
41
|
|
|
39
42
|
If the preceding TaskChef turn is still unfinished because its terminal report
|
|
40
43
|
was lost, this newer valid working report atomically records that predecessor
|
|
@@ -71,6 +74,30 @@ and unnecessary personal data. Identical lifecycle retries are safe; never
|
|
|
71
74
|
replace a same-turn report with different content or let an older turn
|
|
72
75
|
overwrite newer state.
|
|
73
76
|
|
|
77
|
+
### Preserve repository and delivery links
|
|
78
|
+
|
|
79
|
+
Treat lifecycle summaries as the durable TaskChef timeline; the dashboard does
|
|
80
|
+
not scan the full Codex transcript later. Preserve known GitHub context in that
|
|
81
|
+
timeline with canonical links:
|
|
82
|
+
|
|
83
|
+
- Use `https://github.com/<owner>/<repository>` when identifying the repository
|
|
84
|
+
selected for a turn. Derive it from established project or Git remote
|
|
85
|
+
evidence; never guess from a similarly named directory.
|
|
86
|
+
- Use the full canonical issue or pull-request URL whenever one is known. Do
|
|
87
|
+
not shorten it to bare `#123` or `PR #123` in a multi-repository project.
|
|
88
|
+
- Include every delivered pull request in the result summary. If work produces
|
|
89
|
+
both a child-repository pull request and a workspace/root pull request,
|
|
90
|
+
preserve both URLs and state which repository each belongs to.
|
|
91
|
+
- Keep references specific to the text being reported. Do not copy unrelated
|
|
92
|
+
links from earlier turns merely to make them visible.
|
|
93
|
+
- Never invent an issue, pull request, or repository link. If repository
|
|
94
|
+
identity is genuinely unresolved, describe that ambiguity without a link.
|
|
95
|
+
|
|
96
|
+
For example, a working summary may say `Update dashboard projection in
|
|
97
|
+
https://github.com/favoyang/taskchef`. A terminal summary may say `Shipped the
|
|
98
|
+
child change in https://github.com/favoyang/taskchef/pull/78 and the workspace
|
|
99
|
+
plan in https://github.com/favoyang/skills-workspace/pull/62`.
|
|
100
|
+
|
|
74
101
|
## Compatibility
|
|
75
102
|
|
|
76
103
|
Existing delegated tasks may include the former inline ownership, linking, and
|
package/src/dashboard/app.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
turnPresentation,
|
|
17
17
|
} from "./state.js";
|
|
18
18
|
import { openTaskFromControl } from "./actions.js";
|
|
19
|
-
import {
|
|
19
|
+
import { referenceSegments } from "./github-links.js";
|
|
20
20
|
import { formatRelativeTime, RelativeTimeController, parsedTimestamp } from "./time.js";
|
|
21
21
|
|
|
22
22
|
const state = {
|
|
@@ -61,28 +61,33 @@ const elements = {
|
|
|
61
61
|
};
|
|
62
62
|
let notificationDescriptionSerial = 0;
|
|
63
63
|
|
|
64
|
-
function
|
|
64
|
+
function referenceLink(link, { compact = false } = {}) {
|
|
65
65
|
const anchor = document.createElement("a");
|
|
66
66
|
anchor.className = compact ? "github-link github-link-compact" : "github-link";
|
|
67
67
|
anchor.href = link.url;
|
|
68
68
|
anchor.target = "_blank";
|
|
69
69
|
anchor.rel = "noopener noreferrer";
|
|
70
70
|
anchor.textContent = link.label ?? link.text;
|
|
71
|
+
const githubKind = link.type === "issue"
|
|
72
|
+
? ", GitHub issue"
|
|
73
|
+
: link.type === "pull"
|
|
74
|
+
? ", GitHub pull request"
|
|
75
|
+
: link.provider === "github" || link.owner ? " on GitHub" : "";
|
|
71
76
|
anchor.setAttribute(
|
|
72
77
|
"aria-label",
|
|
73
|
-
`${link.label ?? link.text}
|
|
78
|
+
`${link.label ?? link.text}${githubKind} (opens in a new tab)`,
|
|
74
79
|
);
|
|
75
80
|
anchor.addEventListener("click", (event) => event.stopPropagation());
|
|
76
81
|
return anchor;
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
function
|
|
80
|
-
const children =
|
|
84
|
+
function appendLinkedText(container, text, task) {
|
|
85
|
+
const children = referenceSegments(text, {
|
|
81
86
|
projectRepositories: task.project?.githubRepos,
|
|
82
87
|
taskRepository: task.relatedGitHubRepository,
|
|
83
88
|
}).map((segment) => {
|
|
84
89
|
if (segment.kind === "text") return document.createTextNode(segment.text);
|
|
85
|
-
if (segment.kind === "link") return
|
|
90
|
+
if (segment.kind === "link") return referenceLink(segment);
|
|
86
91
|
const ambiguous = document.createElement("span");
|
|
87
92
|
ambiguous.className = "github-reference-ambiguous";
|
|
88
93
|
ambiguous.textContent = segment.text;
|
|
@@ -99,7 +104,7 @@ function relatedGitHubLinks(task, { compact = false } = {}) {
|
|
|
99
104
|
const container = document.createElement("nav");
|
|
100
105
|
container.className = `github-links${compact ? " github-links-compact" : ""}`;
|
|
101
106
|
container.setAttribute("aria-label", `Related GitHub links for ${task.title}`);
|
|
102
|
-
const children = links.map((link) =>
|
|
107
|
+
const children = links.map((link) => referenceLink(link, { compact }));
|
|
103
108
|
if (task.relatedGitHubLinksTruncated) {
|
|
104
109
|
const more = document.createElement("span");
|
|
105
110
|
more.className = "github-links-more";
|
|
@@ -307,7 +312,7 @@ function turnTimeline(task) {
|
|
|
307
312
|
requestLabel.textContent = "Request";
|
|
308
313
|
const request = document.createElement("p");
|
|
309
314
|
request.className = "preserve-lines";
|
|
310
|
-
|
|
315
|
+
appendLinkedText(
|
|
311
316
|
request,
|
|
312
317
|
turn.requestSummary ?? "Request not recorded by this TaskChef version.",
|
|
313
318
|
task,
|
|
@@ -316,7 +321,7 @@ function turnTimeline(task) {
|
|
|
316
321
|
resultLabel.textContent = "Result";
|
|
317
322
|
const result = document.createElement("p");
|
|
318
323
|
result.className = "preserve-lines";
|
|
319
|
-
|
|
324
|
+
appendLinkedText(result, presentation.summary, task);
|
|
320
325
|
const turnMetadata = document.createElement("p");
|
|
321
326
|
turnMetadata.className = "result-history-turn";
|
|
322
327
|
turnMetadata.textContent = turn.turnId
|
|
@@ -424,12 +429,12 @@ function taskCard(task) {
|
|
|
424
429
|
requestLabel.textContent = "Request";
|
|
425
430
|
const request = document.createElement("span");
|
|
426
431
|
request.className = "preserve-lines";
|
|
427
|
-
request
|
|
432
|
+
appendLinkedText(request, latest.requestSummary, task);
|
|
428
433
|
const resultLabel = document.createElement("strong");
|
|
429
434
|
resultLabel.textContent = "Result";
|
|
430
435
|
const result = document.createElement("span");
|
|
431
436
|
result.className = "preserve-lines";
|
|
432
|
-
result
|
|
437
|
+
appendLinkedText(result, latest.resultSummary, task);
|
|
433
438
|
summary.replaceChildren(requestLabel, request, resultLabel, result);
|
|
434
439
|
const relatedLinks = relatedGitHubLinks(task, { compact: true });
|
|
435
440
|
const time = timestampControl(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
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;
|
|
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_])|https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))\/([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9_-])?)\/?(?=$|[\s#),.;:!?'"\]}>])/gi;
|
|
3
|
+
const ABSOLUTE_HTTP_PATTERN = /https?:\/\/[^\s<>"'`]+/gi;
|
|
3
4
|
export const MAX_RELATED_GITHUB_LINKS = 20;
|
|
4
5
|
|
|
5
6
|
function canonicalRepository(owner, repository) {
|
|
@@ -23,18 +24,46 @@ export function normalizeGitHubRepository(value) {
|
|
|
23
24
|
|
|
24
25
|
function rawReferences(text) {
|
|
25
26
|
const value = String(text ?? "");
|
|
27
|
+
const absoluteRanges = absoluteHttpCandidates(value);
|
|
26
28
|
const references = [];
|
|
27
29
|
for (const match of value.matchAll(REFERENCE_PATTERN)) {
|
|
28
30
|
const start = match.index;
|
|
29
31
|
const preceding = start > 0 ? value[start - 1] : "";
|
|
32
|
+
let explicitAbsoluteUrl = null;
|
|
33
|
+
const containingAbsoluteUrl = absoluteRanges.find((range) => (
|
|
34
|
+
start >= range.start && start < range.end
|
|
35
|
+
));
|
|
36
|
+
if (containingAbsoluteUrl) {
|
|
37
|
+
const explicitGitHubUrl = Boolean(match[1] || match[9]);
|
|
38
|
+
let absoluteHost = null;
|
|
39
|
+
try {
|
|
40
|
+
absoluteHost = new URL(containingAbsoluteUrl.text).hostname.toLowerCase();
|
|
41
|
+
} catch {
|
|
42
|
+
// Invalid absolute URL tokens still block nested shorthand recognition.
|
|
43
|
+
}
|
|
44
|
+
const completeGitHubUrl = explicitGitHubUrl
|
|
45
|
+
&& start === containingAbsoluteUrl.start
|
|
46
|
+
&& (absoluteHost === "github.com" || absoluteHost === "www.github.com");
|
|
47
|
+
if (!completeGitHubUrl) continue;
|
|
48
|
+
const safeUrl = safeAbsoluteHttpUrl(containingAbsoluteUrl.text);
|
|
49
|
+
if (!safeUrl) continue;
|
|
50
|
+
explicitAbsoluteUrl = {
|
|
51
|
+
end: containingAbsoluteUrl.end,
|
|
52
|
+
suffix: containingAbsoluteUrl.text.slice(match[0].length),
|
|
53
|
+
text: containingAbsoluteUrl.text,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
30
56
|
if (match[8] && /[\w&#/]/.test(preceding)) continue;
|
|
31
|
-
if ((match[5] || match[1]) && /[\w/]/.test(preceding)) continue;
|
|
57
|
+
if ((match[5] || match[1] || match[9]) && /[\w/]/.test(preceding)) continue;
|
|
32
58
|
|
|
33
|
-
let owner = match[1] ?? match[5] ?? null;
|
|
34
|
-
let repository = match[2] ?? match[6] ?? null;
|
|
35
|
-
|
|
59
|
+
let owner = match[1] ?? match[5] ?? match[9] ?? null;
|
|
60
|
+
let repository = match[2] ?? match[6] ?? match[10] ?? null;
|
|
61
|
+
if (match[9]) repository = repository.replace(/\.git$/i, "");
|
|
62
|
+
const number = match[4] ?? match[7] ?? match[8] ?? null;
|
|
36
63
|
const pathType = match[3]?.toLowerCase();
|
|
37
|
-
const type =
|
|
64
|
+
const type = match[9]
|
|
65
|
+
? "repository"
|
|
66
|
+
: pathType === "pull" ? "pull" : pathType === "issues" ? "issue" : "generic";
|
|
38
67
|
const explicit = Boolean(owner && repository);
|
|
39
68
|
if (explicit) {
|
|
40
69
|
if (!validRepositoryName(repository)) continue;
|
|
@@ -42,14 +71,15 @@ function rawReferences(text) {
|
|
|
42
71
|
[owner, repository] = normalized.split("/");
|
|
43
72
|
}
|
|
44
73
|
references.push({
|
|
45
|
-
end: start + match[0].length,
|
|
74
|
+
end: explicitAbsoluteUrl?.end ?? start + match[0].length,
|
|
46
75
|
explicit,
|
|
47
76
|
number,
|
|
48
77
|
owner,
|
|
49
78
|
repository,
|
|
50
79
|
start,
|
|
51
|
-
text: match[0],
|
|
80
|
+
text: explicitAbsoluteUrl?.text ?? match[0],
|
|
52
81
|
type,
|
|
82
|
+
urlSuffix: explicitAbsoluteUrl?.suffix ?? "",
|
|
53
83
|
});
|
|
54
84
|
}
|
|
55
85
|
return references;
|
|
@@ -77,18 +107,114 @@ function resolvedReference(reference, repositoryContext) {
|
|
|
77
107
|
: repositoryContext;
|
|
78
108
|
if (!repository) return null;
|
|
79
109
|
const [owner, repositoryName] = repository.split("/");
|
|
110
|
+
if (reference.type === "repository") {
|
|
111
|
+
return {
|
|
112
|
+
kind: "link",
|
|
113
|
+
label: `${owner}/${repositoryName}`,
|
|
114
|
+
number: null,
|
|
115
|
+
owner,
|
|
116
|
+
provider: "github",
|
|
117
|
+
repository: repositoryName,
|
|
118
|
+
text: reference.text,
|
|
119
|
+
type: reference.type,
|
|
120
|
+
url: `https://github.com/${owner}/${repositoryName}${reference.urlSuffix}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
80
123
|
const path = reference.type === "pull" ? "pull" : "issues";
|
|
81
124
|
return {
|
|
82
125
|
kind: "link",
|
|
126
|
+
label: `${owner}/${repositoryName}#${reference.number}`,
|
|
83
127
|
number: reference.number,
|
|
84
128
|
owner,
|
|
129
|
+
provider: "github",
|
|
85
130
|
repository: repositoryName,
|
|
86
131
|
text: reference.text,
|
|
87
132
|
type: reference.type,
|
|
88
|
-
url: `https://github.com/${owner}/${repositoryName}/${path}/${reference.number}`,
|
|
133
|
+
url: `https://github.com/${owner}/${repositoryName}/${path}/${reference.number}${reference.urlSuffix}`,
|
|
89
134
|
};
|
|
90
135
|
}
|
|
91
136
|
|
|
137
|
+
function trimAbsoluteUrl(candidate) {
|
|
138
|
+
let trimmed = candidate;
|
|
139
|
+
let previous;
|
|
140
|
+
do {
|
|
141
|
+
previous = trimmed;
|
|
142
|
+
trimmed = trimmed.replace(/[.,;:!?]+$/u, "");
|
|
143
|
+
for (const [opening, closing] of [["(", ")"], ["[", "]"], ["{", "}"]]) {
|
|
144
|
+
while (trimmed.endsWith(closing)) {
|
|
145
|
+
const openings = [...trimmed].filter((character) => character === opening).length;
|
|
146
|
+
const closings = [...trimmed].filter((character) => character === closing).length;
|
|
147
|
+
if (closings <= openings) break;
|
|
148
|
+
trimmed = trimmed.slice(0, -1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} while (trimmed !== previous);
|
|
152
|
+
return trimmed;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function absoluteHttpCandidates(text) {
|
|
156
|
+
const candidates = [];
|
|
157
|
+
for (const match of String(text ?? "").matchAll(ABSOLUTE_HTTP_PATTERN)) {
|
|
158
|
+
const candidate = trimAbsoluteUrl(match[0]);
|
|
159
|
+
if (!candidate) continue;
|
|
160
|
+
candidates.push({
|
|
161
|
+
end: match.index + candidate.length,
|
|
162
|
+
start: match.index,
|
|
163
|
+
text: candidate,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return candidates;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function hasDotPathSegment(candidate) {
|
|
170
|
+
const authorityAndPath = candidate.slice(candidate.indexOf("//") + 2).split(/[?#]/u, 1)[0];
|
|
171
|
+
const pathStart = authorityAndPath.indexOf("/");
|
|
172
|
+
if (pathStart < 0) return false;
|
|
173
|
+
return authorityAndPath.slice(pathStart).split("/").some((segment) => {
|
|
174
|
+
try {
|
|
175
|
+
const decoded = decodeURIComponent(segment);
|
|
176
|
+
return decoded === "." || decoded === "..";
|
|
177
|
+
} catch {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function safeAbsoluteHttpUrl(candidate) {
|
|
184
|
+
try {
|
|
185
|
+
const url = new URL(candidate);
|
|
186
|
+
if (
|
|
187
|
+
!url.hostname
|
|
188
|
+
|| url.username
|
|
189
|
+
|| url.password
|
|
190
|
+
|| candidate.includes("\\")
|
|
191
|
+
|| hasDotPathSegment(candidate)
|
|
192
|
+
) return null;
|
|
193
|
+
return url.href;
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function rawAbsoluteHttpReferences(text) {
|
|
200
|
+
const references = [];
|
|
201
|
+
for (const candidate of absoluteHttpCandidates(text)) {
|
|
202
|
+
const url = safeAbsoluteHttpUrl(candidate.text);
|
|
203
|
+
if (!url) continue;
|
|
204
|
+
references.push({
|
|
205
|
+
end: candidate.end,
|
|
206
|
+
kind: "link",
|
|
207
|
+
label: candidate.text,
|
|
208
|
+
provider: "web",
|
|
209
|
+
start: candidate.start,
|
|
210
|
+
text: candidate.text,
|
|
211
|
+
type: "url",
|
|
212
|
+
url,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return references;
|
|
216
|
+
}
|
|
217
|
+
|
|
92
218
|
export function githubReferenceSegments(text, {
|
|
93
219
|
projectRepositories = [],
|
|
94
220
|
taskRepository = null,
|
|
@@ -120,6 +246,45 @@ export function githubReferenceSegments(text, {
|
|
|
120
246
|
return segments;
|
|
121
247
|
}
|
|
122
248
|
|
|
249
|
+
export function referenceSegments(text, options = {}) {
|
|
250
|
+
const value = String(text ?? "");
|
|
251
|
+
const githubSegments = githubReferenceSegments(value, options);
|
|
252
|
+
const references = [];
|
|
253
|
+
let cursor = 0;
|
|
254
|
+
for (const segment of githubSegments) {
|
|
255
|
+
const start = value.indexOf(segment.text, cursor);
|
|
256
|
+
if (start < 0) continue;
|
|
257
|
+
if (segment.kind !== "text") {
|
|
258
|
+
references.push({ end: start + segment.text.length, segment, start });
|
|
259
|
+
}
|
|
260
|
+
cursor = start + segment.text.length;
|
|
261
|
+
}
|
|
262
|
+
const absoluteReferences = rawAbsoluteHttpReferences(value).filter((reference) => (
|
|
263
|
+
!references.some(({ end, start }) => reference.start < end && reference.end > start)
|
|
264
|
+
));
|
|
265
|
+
if (absoluteReferences.length === 0) return githubSegments;
|
|
266
|
+
|
|
267
|
+
references.push(...absoluteReferences.map((segment) => ({
|
|
268
|
+
end: segment.end,
|
|
269
|
+
segment,
|
|
270
|
+
start: segment.start,
|
|
271
|
+
})));
|
|
272
|
+
references.sort((left, right) => left.start - right.start);
|
|
273
|
+
|
|
274
|
+
const segments = [];
|
|
275
|
+
let offset = 0;
|
|
276
|
+
for (const reference of references) {
|
|
277
|
+
if (reference.start < offset) continue;
|
|
278
|
+
if (reference.start > offset) {
|
|
279
|
+
segments.push({ kind: "text", text: value.slice(offset, reference.start) });
|
|
280
|
+
}
|
|
281
|
+
segments.push(reference.segment);
|
|
282
|
+
offset = reference.end;
|
|
283
|
+
}
|
|
284
|
+
if (offset < value.length) segments.push({ kind: "text", text: value.slice(offset) });
|
|
285
|
+
return segments;
|
|
286
|
+
}
|
|
287
|
+
|
|
123
288
|
function taskTexts(task) {
|
|
124
289
|
const texts = [task.instruction];
|
|
125
290
|
for (const turn of task.turns ?? []) {
|
|
@@ -132,15 +297,9 @@ function taskTexts(task) {
|
|
|
132
297
|
return texts.filter((text) => typeof text === "string" && text.length > 0);
|
|
133
298
|
}
|
|
134
299
|
|
|
135
|
-
function relatedLinkLabel(link
|
|
136
|
-
|
|
137
|
-
|
|
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}`;
|
|
300
|
+
function relatedLinkLabel(link) {
|
|
301
|
+
if (link.type === "repository") return `${link.owner}/${link.repository}`;
|
|
302
|
+
return `${link.owner}/${link.repository}#${link.number}`;
|
|
144
303
|
}
|
|
145
304
|
|
|
146
305
|
export function taskGitHubProjection(task) {
|
|
@@ -164,7 +323,7 @@ export function taskGitHubProjection(task) {
|
|
|
164
323
|
taskRepository,
|
|
165
324
|
})) {
|
|
166
325
|
if (segment.kind !== "link") continue;
|
|
167
|
-
const key = segment.url
|
|
326
|
+
const key = segment.url;
|
|
168
327
|
if (seen.has(key)) continue;
|
|
169
328
|
if (links.length === MAX_RELATED_GITHUB_LINKS) {
|
|
170
329
|
truncated = true;
|
|
@@ -174,17 +333,9 @@ export function taskGitHubProjection(task) {
|
|
|
174
333
|
links.push(segment);
|
|
175
334
|
}
|
|
176
335
|
}
|
|
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
336
|
return {
|
|
186
337
|
relatedGitHubLinks: links.map((link) => ({
|
|
187
|
-
label: relatedLinkLabel(link
|
|
338
|
+
label: relatedLinkLabel(link),
|
|
188
339
|
number: link.number,
|
|
189
340
|
owner: link.owner,
|
|
190
341
|
repository: link.repository,
|
package/src/mcp.js
CHANGED
|
@@ -225,7 +225,7 @@ export function createTaskChefMcpServer({
|
|
|
225
225
|
{
|
|
226
226
|
title: "Report TaskChef state",
|
|
227
227
|
description:
|
|
228
|
-
"Report this self-linked executor turn's lifecycle state. Use working before substantive work in a newly linked or follow-up turn, with summary omitted or null and a concise requestSummary. Before ending the same turn, report needs_input, completed, or failed with a concise semantic summary and requestSummary omitted. Exact retries are idempotent; stale or mismatched turns are rejected.",
|
|
228
|
+
"Report this self-linked executor turn's lifecycle state. Use working before substantive work in a newly linked or follow-up turn, with summary omitted or null and a concise requestSummary. Preserve known repository context and delivered issues or pull requests as canonical GitHub URLs, including both child-repository and workspace pull requests when applicable. Before ending the same turn, report needs_input, completed, or failed with a concise semantic summary and requestSummary omitted. Exact retries are idempotent; stale or mismatched turns are rejected.",
|
|
229
229
|
inputSchema: {
|
|
230
230
|
taskId: z.string().min(1),
|
|
231
231
|
threadId: z.string().min(1).nullable(),
|