taskchef 7.15.6 → 7.17.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "7.15.6",
3
+ "version": "7.17.0",
4
4
  "description": "Dispatch work from a data-only workspace to visible Codex project tasks.",
5
5
  "author": {
6
6
  "name": "Favo Yang",
@@ -29,10 +29,10 @@
29
29
  ],
30
30
  "websiteURL": "https://github.com/favoyang/taskchef",
31
31
  "defaultPrompt": [
32
- "$taskchef-bootstrap Set up TaskChef and index my Codex projects.",
33
- "$taskchef-delegate Dispatch this request to the right project.",
34
- "$taskchef-executor Execute this delegated TaskChef assignment.",
35
- "$taskchef-copilot Explain TaskChef outcomes and recommend the next action."
32
+ "$taskchef-bootstrap Set up TaskChef and index my local Codex projects.",
33
+ "$taskchef-delegate Do X in Y project.",
34
+ "Start and open the TaskChef dashboard.",
35
+ "$taskchef-copilot Summarize recent delegated work and tell me what needs attention."
36
36
  ],
37
37
  "brandColor": "#D97706",
38
38
  "composerIcon": "./assets/taskchef.svg",
package/README.md CHANGED
@@ -41,7 +41,7 @@ no elevated permissions.
41
41
  Ask the bootstrap skill to create the per-user dispatcher:
42
42
 
43
43
  ```text
44
- $taskchef-bootstrap Set up TaskChef.
44
+ $taskchef-bootstrap Set up TaskChef and index my local Codex projects.
45
45
  ```
46
46
 
47
47
  The canonical workspace is `~/.agents/taskchef`. TaskChef owns only:
@@ -181,7 +181,7 @@ The dashboard is the primary monitoring and browsing UI. Ask copilot when you
181
181
  want a concise explanation or recommendation:
182
182
 
183
183
  ```text
184
- $taskchef-copilot What finished, what needs attention, and what should I do next?
184
+ $taskchef-copilot Summarize recent delegated work and tell me what needs attention.
185
185
  ```
186
186
 
187
187
  Copilot starts from TaskChef's normalized cached brief. It uses live Codex
@@ -219,6 +219,12 @@ eight-character prefix.
219
219
 
220
220
  ## Dashboard
221
221
 
222
+ Ask the dispatcher to make the dashboard available and open it:
223
+
224
+ ```text
225
+ Start and open the TaskChef dashboard.
226
+ ```
227
+
222
228
  Dispatcher turns call the input-free `ensure_dashboard` MCP tool. It starts at
223
229
  most one dashboard inside the existing TaskChef MCP process on
224
230
  `127.0.0.1:3210`, or reuses a listener only when its bounded `/api/health`
@@ -6,6 +6,10 @@ This folder is the canonical per-user TaskChef dispatcher workspace.
6
6
  - Early in every dispatcher turn, best-effort call the TaskChef
7
7
  `ensure_dashboard` MCP tool. Dashboard startup failure must not block direct
8
8
  TaskChef answers, reporting, or delegation.
9
+ - A request to start or open the TaskChef dashboard is TaskChef maintenance,
10
+ not delegated project work. Call `ensure_dashboard`, then open or navigate to
11
+ its returned URL when an in-app browser capability is available. Otherwise,
12
+ return the URL as a clickable link.
9
13
  - Use `$taskchef-bootstrap` when initializing or refreshing this workspace,
10
14
  changing or listing its configured projects, running TaskChef doctor,
11
15
  or repairing its managed instructions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "7.15.6",
3
+ "version": "7.17.0",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
@@ -16,7 +16,11 @@ import {
16
16
  turnPresentation,
17
17
  } from "./state.js";
18
18
  import { openTaskFromControl } from "./actions.js";
19
- import { referenceSegments } from "./github-links.js";
19
+ import {
20
+ githubReferenceAccessibleLabel,
21
+ githubReferenceDisplayLabels,
22
+ referenceSegments,
23
+ } from "./github-links.js";
20
24
  import { formatRelativeTime, RelativeTimeController, parsedTimestamp } from "./time.js";
21
25
 
22
26
  const state = {
@@ -63,23 +67,15 @@ const elements = {
63
67
  };
64
68
  let notificationDescriptionSerial = 0;
65
69
 
66
- function typedReferenceLabel(link, label, typePrefix) {
67
- if (!typePrefix) return label;
68
- if (link.type === "pull") return `PR ${label}`;
69
- if (link.type === "issue") return `Issue ${label}`;
70
- return label;
71
- }
72
-
73
- function referenceLink(link, { compact = false, displayLabel, typePrefix = false } = {}) {
70
+ function referenceLink(link, { compact = false, displayLabel } = {}) {
74
71
  const anchor = document.createElement("a");
75
72
  anchor.className = compact ? "github-link github-link-compact" : "github-link";
76
73
  anchor.href = link.url;
77
74
  anchor.target = "_blank";
78
75
  anchor.rel = "noopener noreferrer";
79
76
  const label = link.label ?? link.text;
80
- const visibleLabel = typedReferenceLabel(link, displayLabel ?? label, typePrefix);
81
- const accessibleLabel = typedReferenceLabel(link, label, typePrefix);
82
- anchor.textContent = visibleLabel;
77
+ anchor.textContent = displayLabel ?? label;
78
+ const accessibleLabel = githubReferenceAccessibleLabel(link);
83
79
  const githubKind = link.type === "issue"
84
80
  ? ", GitHub issue"
85
81
  : link.type === "pull"
@@ -94,12 +90,20 @@ function referenceLink(link, { compact = false, displayLabel, typePrefix = false
94
90
  }
95
91
 
96
92
  function appendLinkedText(container, text, task) {
97
- const children = referenceSegments(text, {
93
+ const segments = referenceSegments(text, {
98
94
  projectRepositories: task.project?.githubRepos,
99
95
  taskRepository: task.relatedGitHubRepository,
100
- }).map((segment) => {
96
+ });
97
+ const links = segments.filter((segment) => segment.kind === "link");
98
+ const displayLabels = githubReferenceDisplayLabels(links);
99
+ let linkIndex = 0;
100
+ const children = segments.map((segment) => {
101
101
  if (segment.kind === "text") return document.createTextNode(segment.text);
102
- if (segment.kind === "link") return referenceLink(segment, { typePrefix: true });
102
+ if (segment.kind === "link") {
103
+ const displayLabel = displayLabels[linkIndex];
104
+ linkIndex += 1;
105
+ return referenceLink(segment, { displayLabel });
106
+ }
103
107
  const ambiguous = document.createElement("span");
104
108
  ambiguous.className = "github-reference-ambiguous";
105
109
  ambiguous.textContent = segment.text;
@@ -121,18 +125,10 @@ function relatedGitHubLinks(task, { compact = false } = {}) {
121
125
  const container = document.createElement("nav");
122
126
  container.className = `github-links${compact ? " github-links-compact" : ""}`;
123
127
  container.setAttribute("aria-label", `Related GitHub links for ${task.title}`);
124
- const seenRepositories = new Set();
125
- const children = links.map((link) => {
126
- const repository = link.owner && link.repository
127
- ? `${link.owner}/${link.repository}`
128
- : null;
129
- const repeatedRepository = repository && seenRepositories.has(repository);
130
- if (repository) seenRepositories.add(repository);
131
- const displayLabel = repeatedRepository && link.number
132
- ? `#${link.number}`
133
- : link.label;
134
- return referenceLink(link, { compact, displayLabel, typePrefix: true });
135
- });
128
+ const displayLabels = githubReferenceDisplayLabels(links, { related: true });
129
+ const children = links.map((link, index) => (
130
+ referenceLink(link, { compact, displayLabel: displayLabels[index] })
131
+ ));
136
132
  if (task.relatedGitHubLinksTruncated) {
137
133
  const more = document.createElement("span");
138
134
  more.className = "github-links-more";
@@ -160,7 +156,7 @@ function renderProject(container, task) {
160
156
  repository: repositoryName,
161
157
  type: "repository",
162
158
  url: `https://github.com/${repository}`,
163
- }),
159
+ }, { displayLabel: repositoryName }),
164
160
  );
165
161
  }
166
162
  container.replaceChildren(...children);
@@ -3,6 +3,64 @@ const REFERENCE_PATTERN = /https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:[A-Z
3
3
  const ABSOLUTE_HTTP_PATTERN = /https?:\/\/[^\s<>"'`]+/gi;
4
4
  export const MAX_RELATED_GITHUB_LINKS = 20;
5
5
 
6
+ function repositoryIdentity(link) {
7
+ if (!link?.owner || !link?.repository) return null;
8
+ return `${link.owner.toLowerCase()}/${link.repository.toLowerCase()}`;
9
+ }
10
+
11
+ function collidingRepositoryBasenames(links) {
12
+ const repositoriesByBasename = new Map();
13
+ for (const link of links) {
14
+ const identity = repositoryIdentity(link);
15
+ if (!identity) continue;
16
+ const basename = link.repository.toLowerCase();
17
+ const repositories = repositoriesByBasename.get(basename) ?? new Set();
18
+ repositories.add(identity);
19
+ repositoriesByBasename.set(basename, repositories);
20
+ }
21
+ return new Set(
22
+ [...repositoriesByBasename]
23
+ .filter(([, repositories]) => repositories.size > 1)
24
+ .map(([basename]) => basename),
25
+ );
26
+ }
27
+
28
+ function visibleRepositoryLabel(link, collisions) {
29
+ return collisions.has(link.repository.toLowerCase())
30
+ ? `${link.owner}/${link.repository}`
31
+ : link.repository;
32
+ }
33
+
34
+ export function githubReferenceDisplayLabels(links, { related = false } = {}) {
35
+ const collisions = collidingRepositoryBasenames(links);
36
+ let previousRepository = null;
37
+ return links.map((link) => {
38
+ if (link.provider !== "github" && !link.owner) return link.label ?? link.text;
39
+ const repository = repositoryIdentity(link);
40
+ const repositoryLabel = visibleRepositoryLabel(link, collisions);
41
+ if (link.type === "repository") return repositoryLabel;
42
+ if (related) {
43
+ const label = repository === previousRepository
44
+ ? `#${link.number}`
45
+ : `${repositoryLabel} #${link.number}`;
46
+ previousRepository = repository;
47
+ return label;
48
+ }
49
+ if (link.type === "pull") return `${repositoryLabel} PR #${link.number}`;
50
+ if (link.type === "issue") return `${repositoryLabel} Issue #${link.number}`;
51
+ return `${repositoryLabel} #${link.number}`;
52
+ });
53
+ }
54
+
55
+ export function githubReferenceAccessibleLabel(link) {
56
+ const repository = repositoryIdentity(link);
57
+ if (!repository) return link.label ?? link.text;
58
+ if (link.type === "repository") return repository;
59
+ if (link.type === "pull") return `${repository} PR #${link.number}`;
60
+ if (link.type === "issue") return `${repository} Issue #${link.number}`;
61
+ return `${repository} #${link.number}`;
62
+ }
63
+
6
64
  function canonicalRepository(owner, repository) {
7
65
  return `${owner.toLowerCase()}/${repository.toLowerCase()}`;
8
66
  }