taskchef 7.17.0 → 7.18.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 +2 -2
- package/README.md +41 -10
- package/assets/taskchef-dispatcher-instructions.md +2 -4
- package/docs/firstmate-taskchef-comparison.md +2 -2
- package/docs/spec.md +31 -2
- package/docs/workflows.md +27 -3
- package/index.js +2 -1
- package/package.json +2 -1
- package/skills/taskchef-bootstrap/SKILL.md +5 -2
- package/skills/taskchef-dashboard/SKILL.md +20 -0
- package/skills/taskchef-dashboard/agents/openai.yaml +4 -0
- package/src/dashboard/app.js +20 -10
- package/src/dashboard/github-links.js +21 -0
- package/src/dashboard/styles.css +2 -1
- package/src/mcp.js +49 -0
- package/src/workspace.js +31 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taskchef",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.18.0",
|
|
4
4
|
"description": "Dispatch work from a data-only workspace to visible Codex project tasks.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Favo Yang",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"defaultPrompt": [
|
|
32
32
|
"$taskchef-bootstrap Set up TaskChef and index my local Codex projects.",
|
|
33
33
|
"$taskchef-delegate Do X in Y project.",
|
|
34
|
-
"
|
|
34
|
+
"$taskchef-dashboard Ensure and open the TaskChef dashboard.",
|
|
35
35
|
"$taskchef-copilot Summarize recent delegated work and tell me what needs attention."
|
|
36
36
|
],
|
|
37
37
|
"brandColor": "#D97706",
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ codex plugin add taskchef@favoyang-plugins
|
|
|
31
31
|
npm install --global taskchef
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The plugin provides
|
|
34
|
+
The plugin provides five skills and a local MCP server. The npm installation
|
|
35
35
|
puts the `taskchef` CLI on `PATH`. TaskChef installs no hooks, schedules,
|
|
36
36
|
daemons, login items, system services, or background identity search and needs
|
|
37
37
|
no elevated permissions.
|
|
@@ -48,7 +48,7 @@ The canonical workspace is `~/.agents/taskchef`. TaskChef owns only:
|
|
|
48
48
|
|
|
49
49
|
```text
|
|
50
50
|
AGENTS.md managed dispatcher instructions plus user additions
|
|
51
|
-
taskchef.json schema-2 Codex project index and delegation metadata
|
|
51
|
+
taskchef.json schema-2 Codex project index, dashboard preference, and delegation metadata
|
|
52
52
|
tasks.jsonl one task snapshot per line (schema 9; schema 4-8 migration supported)
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -129,9 +129,10 @@ correlation marker. That skill reads the executor's own `CODEX_THREAD_ID`, self-
|
|
|
129
129
|
reports lifecycle state. Independent outcomes may become separate executors;
|
|
130
130
|
dependent work should stay together.
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
Activating the TaskChef MCP process best-effort ensures the dashboard by
|
|
133
|
+
default. The managed workspace instructions also retry it at the start of each
|
|
134
|
+
dispatcher turn. A startup failure never blocks MCP tools, an answer, report,
|
|
135
|
+
or delegation. Every dispatcher response ends with
|
|
135
136
|
the stable [TaskChef Dashboard](http://127.0.0.1:3210/) link; a created-task
|
|
136
137
|
directive remains on the preceding line so dispatch still returns immediately.
|
|
137
138
|
|
|
@@ -219,13 +220,14 @@ eight-character prefix.
|
|
|
219
220
|
|
|
220
221
|
## Dashboard
|
|
221
222
|
|
|
222
|
-
|
|
223
|
+
Use the packaged recovery skill to make the dashboard available and open it:
|
|
223
224
|
|
|
224
225
|
```text
|
|
225
|
-
|
|
226
|
+
$taskchef-dashboard Ensure and open the TaskChef dashboard.
|
|
226
227
|
```
|
|
227
228
|
|
|
228
|
-
|
|
229
|
+
MCP activation and `$taskchef-dashboard` call the input-free `ensure_dashboard`
|
|
230
|
+
MCP tool. It starts at
|
|
229
231
|
most one dashboard inside the existing TaskChef MCP process on
|
|
230
232
|
`127.0.0.1:3210`, or reuses a listener only when its bounded `/api/health`
|
|
231
233
|
identity proves the exact TaskChef/dashboard-server version and the same
|
|
@@ -233,8 +235,22 @@ canonical workspace. The response says `started` or `reused` and includes the
|
|
|
233
235
|
stable URL, canonical workspace, and versions.
|
|
234
236
|
|
|
235
237
|
The in-process dashboard closes with the MCP process. Closing Codex or reloading
|
|
236
|
-
the plugin may therefore stop the dashboard; the next
|
|
237
|
-
it. TaskChef adds no OS-persistent component.
|
|
238
|
+
the plugin may therefore stop the dashboard; activating the next TaskChef MCP
|
|
239
|
+
process normally restores it. TaskChef adds no OS-persistent component.
|
|
240
|
+
|
|
241
|
+
Autostart is enabled when `dashboard` is absent and in new workspaces. To opt
|
|
242
|
+
out, add this optional exact object to `taskchef.json` while retaining its other
|
|
243
|
+
fields:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
"dashboard": { "autostart": false }
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The manual `$taskchef-dashboard` recovery skill remains available when
|
|
250
|
+
autostart is disabled or fails. It reports whether the compatible dashboard was
|
|
251
|
+
started or reused and always returns its canonical clickable URL. It may open
|
|
252
|
+
the returned localhost URL in an available in-app browser, but browser failure
|
|
253
|
+
does not make dashboard recovery fail.
|
|
238
254
|
|
|
239
255
|
For manual development, run the foreground CLI:
|
|
240
256
|
|
|
@@ -322,6 +338,21 @@ codex plugin marketplace upgrade favoyang-plugins
|
|
|
322
338
|
codex plugin add taskchef@favoyang-plugins
|
|
323
339
|
```
|
|
324
340
|
|
|
341
|
+
Replacing or installing plugin files cannot execute autostart by itself. After
|
|
342
|
+
installation, activate or reload the new TaskChef MCP process; installation
|
|
343
|
+
does not necessarily reload Codex. Then run `$taskchef-dashboard` (or call
|
|
344
|
+
`ensure_dashboard`) and verify the returned dashboard identity has all of:
|
|
345
|
+
|
|
346
|
+
- the expected released TaskChef version;
|
|
347
|
+
- the expected dashboard protocol `serverVersion`;
|
|
348
|
+
- the canonical TaskChef workspace path;
|
|
349
|
+
- the canonical `http://127.0.0.1:3210/` URL.
|
|
350
|
+
|
|
351
|
+
The release-install sequence is therefore: install plugin, activate or reload
|
|
352
|
+
the new MCP process, ensure the dashboard, then verify TaskChef version,
|
|
353
|
+
protocol `serverVersion`, canonical workspace, and URL. Exact-compatible
|
|
354
|
+
servers may be reused; unknown listeners remain untouched.
|
|
355
|
+
|
|
325
356
|
## Development
|
|
326
357
|
|
|
327
358
|
```sh
|
|
@@ -6,10 +6,8 @@ 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
|
|
10
|
-
not delegated project work.
|
|
11
|
-
its returned URL when an in-app browser capability is available. Otherwise,
|
|
12
|
-
return the URL as a clickable link.
|
|
9
|
+
- A request to start, recover, or open the TaskChef dashboard is TaskChef
|
|
10
|
+
maintenance, not delegated project work. Use `$taskchef-dashboard`.
|
|
13
11
|
- Use `$taskchef-bootstrap` when initializing or refreshing this workspace,
|
|
14
12
|
changing or listing its configured projects, running TaskChef doctor,
|
|
15
13
|
or repairing its managed instructions.
|
|
@@ -70,7 +70,7 @@ responsibility until an outcome is landed, transferred, or safely preserved.
|
|
|
70
70
|
| Dimension | TaskChef 7 | FirstMate at `038d0f7` |
|
|
71
71
|
| --- | --- | --- |
|
|
72
72
|
| Primary goal | Put multi-project work into the right visible Codex task and make it findable later. | Let one person direct a supervised crew across projects through one liaison. |
|
|
73
|
-
| Product form | Codex plugin:
|
|
73
|
+
| Product form | Codex plugin: five skills, local MCP server, data CLI, and loopback dashboard. | Cloneable agent distribution: instructions, internal skills, scripts, policies, and private on-disk state. |
|
|
74
74
|
| Runtime | Native Codex desktop tasks are the executors and live source of truth. | Harness-driven workers in tmux by default, with documented alternative session backends; Codex can be a harness, but Codex desktop is not a runtime backend. |
|
|
75
75
|
| Coordinator lifetime | Dispatcher returns immediately and does not supervise. | First mate remains active and uses watcher/guard mechanisms to supervise meaningful events. |
|
|
76
76
|
| Project isolation | Delegates to the configured Codex project; isolation follows native Codex/project behavior. | Ship and scout workers require separate Treehouse- or backend-managed worktrees. |
|
|
@@ -219,7 +219,7 @@ harness adapter.
|
|
|
219
219
|
| --- | --- | --- |
|
|
220
220
|
| Codex | Built around native Codex projects, tasks, thread reads, and desktop deep links. | Codex is one verified harness; desktop tasks are not its worker runtime. |
|
|
221
221
|
| GitHub | Configured repository URLs aid routing; delivery remains the executor project's concern. | GitHub CLI, PR state, and configured delivery modes are part of supervised shipping workflows. |
|
|
222
|
-
| Skills |
|
|
222
|
+
| Skills | Five plugin skills with narrow bootstrap, dashboard recovery, delegate, executor, and conversational coordination responsibilities. | Internal firstmate-only skills plus standalone public skills; AGENTS.md routes conditional procedures. |
|
|
223
223
|
| MCP | Four primary local TaskChef tools plus one deprecated compatibility alias own deterministic identity and state writes. | FirstMate describes itself as an agent distribution rather than an MCP product; deterministic behavior lives largely in scripts. |
|
|
224
224
|
| Runtime extensions | Extend plugin skills/MCP/CLI/dashboard or compose native Codex capabilities. | Add or verify harness adapters, session backends, dispatch profiles, operational scripts, and optional integrations. |
|
|
225
225
|
|
package/docs/spec.md
CHANGED
|
@@ -29,7 +29,7 @@ is dated research, not contract.
|
|
|
29
29
|
| **Turn reference** | Required lifecycle identity for one executor prompt. It is the native Codex turn ID when available, otherwise a retained client-generated UUID. |
|
|
30
30
|
| **Current turn ID** | Optional Codex metadata for the reported prompt; null when native turn reading is unavailable. |
|
|
31
31
|
| **Dashboard** | The loopback, read-only UI derived from validated workspace snapshots and bounded native actions. |
|
|
32
|
-
| **Skill** | One packaged agent procedure: `taskchef-bootstrap`, `taskchef-delegate`, `taskchef-executor`, or `taskchef-copilot`. |
|
|
32
|
+
| **Skill** | One packaged agent procedure: `taskchef-bootstrap`, `taskchef-dashboard`, `taskchef-delegate`, `taskchef-executor`, or `taskchef-copilot`. |
|
|
33
33
|
|
|
34
34
|
## Components and ownership
|
|
35
35
|
|
|
@@ -43,6 +43,9 @@ is dated research, not contract.
|
|
|
43
43
|
- `taskchef-copilot` MUST own conversational outcome explanation, attention,
|
|
44
44
|
and next-action recommendations. It MUST use cached normalized briefs by
|
|
45
45
|
default and MUST NOT poll or persist inferred state.
|
|
46
|
+
- `taskchef-dashboard` MUST own manual dashboard ensure and recovery. It MUST
|
|
47
|
+
NOT dispatch work or inspect task outcomes, and browser navigation failure
|
|
48
|
+
MUST NOT suppress the returned dashboard URL.
|
|
46
49
|
- The MCP server MUST expose `ensure_dashboard`, four primary lifecycle tools,
|
|
47
50
|
and the deprecated `report_result` compatibility alias specified below.
|
|
48
51
|
- The CLI MAY administer and inspect the workspace, but MUST NOT provide a
|
|
@@ -56,7 +59,8 @@ is dated research, not contract.
|
|
|
56
59
|
TaskChef MUST manage only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl`
|
|
57
60
|
inside the dispatcher workspace. It MUST preserve unrelated paths.
|
|
58
61
|
|
|
59
|
-
`taskchef.json` MUST have schema version 2 and
|
|
62
|
+
`taskchef.json` MUST have schema version 2, the following required fields, and
|
|
63
|
+
an optional exact `dashboard` object:
|
|
60
64
|
|
|
61
65
|
```json
|
|
62
66
|
{
|
|
@@ -71,6 +75,11 @@ Names and paths MUST be unique. Git projects MUST be exact Git roots.
|
|
|
71
75
|
Repository URLs MUST canonicalize to `https://github.com/<owner>/<repository>`
|
|
72
76
|
and be case-insensitively deduplicated.
|
|
73
77
|
|
|
78
|
+
When present, `dashboard` MUST contain exactly boolean `autostart`. Its absence
|
|
79
|
+
is backward-compatible and means `true`; new workspaces SHOULD write
|
|
80
|
+
`{"autostart": true}`. `false` disables MCP-lifecycle autostart but MUST NOT
|
|
81
|
+
disable the explicit `ensure_dashboard` tool.
|
|
82
|
+
|
|
74
83
|
`tasks.jsonl` MUST contain zero or more newline-terminated schema-4 through
|
|
75
84
|
schema-9 records, one per line. Schemas 4 through 8 are supported
|
|
76
85
|
migration/read formats; every new record and state mutation MUST write schema 9. Other schemas
|
|
@@ -112,6 +121,14 @@ fields MUST NOT change after recording.
|
|
|
112
121
|
|
|
113
122
|
## Required lifecycle
|
|
114
123
|
|
|
124
|
+
When the canonical TaskChef MCP server finishes connecting, it MUST invoke the
|
|
125
|
+
same serialized dashboard ensure path once by default. It MUST read the
|
|
126
|
+
canonical configuration and skip this only for explicit
|
|
127
|
+
`dashboard.autostart: false`. Initialization failures, invalid workspace state,
|
|
128
|
+
port conflicts, and dashboard errors MUST NOT prevent tool registration or MCP
|
|
129
|
+
availability. They MUST emit only a bounded non-sensitive diagnostic through
|
|
130
|
+
the MCP process logging channel. MCP initialization MUST NOT open a browser.
|
|
131
|
+
|
|
115
132
|
At the start of every dispatcher turn, the dispatcher SHOULD call
|
|
116
133
|
`ensure_dashboard` best-effort. Failure MUST NOT block direct TaskChef answers,
|
|
117
134
|
reporting, or delegation. Every dispatcher final response MUST end with the
|
|
@@ -233,6 +250,11 @@ failure MUST leave no owned listener. The MCP server MUST close its owned
|
|
|
233
250
|
dashboard when its transport or process shuts down; it MUST NOT close a reused
|
|
234
251
|
external foreground server.
|
|
235
252
|
|
|
253
|
+
The packaged `$taskchef-dashboard` skill MUST call this tool, report `started`
|
|
254
|
+
or `reused`, and return the canonical clickable URL. It MAY use an available
|
|
255
|
+
in-app browser when permitted; absent or blocked browser navigation MUST fall
|
|
256
|
+
back to the link without failing. It MUST NOT inspect task outcomes or dispatch.
|
|
257
|
+
|
|
236
258
|
**Annotations:** `readOnlyHint: false`, `destructiveHint: false`,
|
|
237
259
|
`openWorldHint: false`.
|
|
238
260
|
|
|
@@ -473,3 +495,10 @@ system services, cron jobs, hooks, privileged components, or elevated/system
|
|
|
473
495
|
permissions for dashboard availability. Availability is best-effort while the
|
|
474
496
|
owning Codex/plugin MCP process is alive and is not guaranteed while Codex is
|
|
475
497
|
closed.
|
|
498
|
+
|
|
499
|
+
Installing or replacing plugin files MUST NOT be described as activating the
|
|
500
|
+
new MCP code. Release verification MUST install the plugin, activate or reload
|
|
501
|
+
the new MCP process, ensure the dashboard, and verify the expected TaskChef
|
|
502
|
+
version, dashboard protocol `serverVersion`, canonical workspace, and canonical
|
|
503
|
+
URL. Exact-compatible listener reuse remains valid; installation MUST NOT be
|
|
504
|
+
claimed to reload Codex automatically.
|
package/docs/workflows.md
CHANGED
|
@@ -13,6 +13,7 @@ research.
|
|
|
13
13
|
| `skills/taskchef-delegate/SKILL.md` | Split, route, record-before-create, create, return. |
|
|
14
14
|
| `skills/taskchef-executor/SKILL.md` | Own, self-link, execute, and report every executor turn. |
|
|
15
15
|
| `skills/taskchef-bootstrap/SKILL.md` | Initialize the workspace and maintain the Codex project index. |
|
|
16
|
+
| `skills/taskchef-dashboard/SKILL.md` | Ensure or recover the canonical dashboard and return its URL. |
|
|
16
17
|
| `skills/taskchef-copilot/SKILL.md` | Explain normalized cached briefs and coordinate safe next actions. |
|
|
17
18
|
| `src/mcp.js` | Dashboard ensure, four primary lifecycle tools, one deprecated alias, shutdown ownership, and MCP annotations. |
|
|
18
19
|
| `src/delegation.js` | UUID marker, concise executor-skill invocation shape, and creation-failure handling. |
|
|
@@ -27,9 +28,9 @@ per-user default.
|
|
|
27
28
|
|
|
28
29
|
## Dispatcher dashboard lifecycle
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
centralized instead of duplicating it across
|
|
31
|
+
MCP activation makes dashboard maintenance a best-effort lifecycle action. The
|
|
32
|
+
generated managed `AGENTS.md` block also retries it before dispatcher turns and
|
|
33
|
+
keeps response ordering centralized instead of duplicating it across skills.
|
|
33
34
|
|
|
34
35
|
```mermaid
|
|
35
36
|
sequenceDiagram
|
|
@@ -38,6 +39,8 @@ sequenceDiagram
|
|
|
38
39
|
participant M as TaskChef MCP
|
|
39
40
|
participant H as Loopback health
|
|
40
41
|
participant S as Dashboard server
|
|
42
|
+
M->>M: Read dashboard.autostart (absent means true)
|
|
43
|
+
M->>M: Best-effort ensure after MCP connect
|
|
41
44
|
D->>M: ensure_dashboard()
|
|
42
45
|
M->>M: Serialize concurrent ensure calls
|
|
43
46
|
M->>H: GET 127.0.0.1:3210/api/health
|
|
@@ -63,6 +66,27 @@ started. A compatible foreground `taskchef dashboard` listener may be reused
|
|
|
63
66
|
but remains owned by that CLI process. No TaskChef path terminates an unknown
|
|
64
67
|
listener or installs OS persistence.
|
|
65
68
|
|
|
69
|
+
Autostart and explicit ensures share the same manager promise, so concurrent
|
|
70
|
+
initialization and recovery calls produce at most one owned listener. An
|
|
71
|
+
explicit `dashboard.autostart: false` skips only activation-time ensure. Any
|
|
72
|
+
failure is reduced to a fixed stderr diagnostic; tool registration and MCP
|
|
73
|
+
availability continue. Activation never opens a browser.
|
|
74
|
+
|
|
75
|
+
## Release-install verification
|
|
76
|
+
|
|
77
|
+
The practical release handoff ends in this order:
|
|
78
|
+
|
|
79
|
+
1. Install the released plugin.
|
|
80
|
+
2. Activate or reload its new TaskChef MCP process.
|
|
81
|
+
3. Run `$taskchef-dashboard` or call `ensure_dashboard`.
|
|
82
|
+
4. Verify the expected TaskChef version, protocol `serverVersion`, canonical
|
|
83
|
+
workspace, and canonical URL returned by the dashboard identity.
|
|
84
|
+
|
|
85
|
+
Replacing plugin files alone cannot execute autostart because old code remains
|
|
86
|
+
in the already-running MCP process. Installation does not necessarily reload
|
|
87
|
+
Codex. An exact-compatible dashboard may be reused; an unknown listener is
|
|
88
|
+
never terminated or replaced.
|
|
89
|
+
|
|
66
90
|
## Normal delegation and self-linking
|
|
67
91
|
|
|
68
92
|
The dispatcher uses native Codex project discovery for routing and MCP for
|
package/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export {
|
|
|
5
5
|
canonicalDirectory,
|
|
6
6
|
canonicalGitRoot,
|
|
7
7
|
doctorWorkspace,
|
|
8
|
+
dashboardAutostartEnabled,
|
|
8
9
|
ensureWorkspaceInstructions,
|
|
9
10
|
filterTasks,
|
|
10
11
|
importProjects,
|
|
@@ -74,4 +75,4 @@ export {
|
|
|
74
75
|
|
|
75
76
|
export { DASHBOARD_SERVER_VERSION, TASKCHEF_VERSION } from "./src/version.js";
|
|
76
77
|
|
|
77
|
-
export { createTaskChefMcpServer } from "./src/mcp.js";
|
|
78
|
+
export { createDashboardAutostart, createTaskChefMcpServer } from "./src/mcp.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taskchef",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.18.0",
|
|
4
4
|
"description": "A non-blocking interactive dispatcher for visible Codex tasks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Favo Yang",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"scripts/e2e-benchmark.js",
|
|
37
37
|
"src",
|
|
38
38
|
"skills/taskchef-bootstrap",
|
|
39
|
+
"skills/taskchef-dashboard",
|
|
39
40
|
"skills/taskchef-delegate",
|
|
40
41
|
"skills/taskchef-executor",
|
|
41
42
|
"skills/taskchef-copilot"
|
|
@@ -160,5 +160,8 @@ Example managed-workspace import entry:
|
|
|
160
160
|
}
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
TaskChef accepts configuration schema version 2 only.
|
|
164
|
-
|
|
163
|
+
TaskChef accepts configuration schema version 2 only. Its optional exact
|
|
164
|
+
`dashboard` object contains boolean `autostart`; absence means enabled and
|
|
165
|
+
`false` opts out of MCP-lifecycle dashboard startup without disabling manual
|
|
166
|
+
`$taskchef-dashboard` recovery. `githubRepos` is always an array; unsupported
|
|
167
|
+
configuration is rejected without being rewritten.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: taskchef-dashboard
|
|
3
|
+
description: "Ensure and recover the canonical TaskChef dashboard. Use when the user asks to start, restore, open, or get the link for the TaskChef dashboard. Do not use to dispatch work or inspect task outcomes."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TaskChef Dashboard
|
|
7
|
+
|
|
8
|
+
Maintain only the canonical TaskChef dashboard.
|
|
9
|
+
|
|
10
|
+
1. Call the TaskChef `ensure_dashboard` MCP tool with its empty input. Do not
|
|
11
|
+
supply a workspace, host, or port.
|
|
12
|
+
2. Report whether the returned `action` is `started` or `reused` and provide
|
|
13
|
+
the returned canonical URL as a clickable link.
|
|
14
|
+
3. If the user asked to open the dashboard and an in-app browser navigation
|
|
15
|
+
capability is available and permits the returned localhost URL, open or
|
|
16
|
+
navigate to it. Browser absence, refusal, or blocked localhost navigation is
|
|
17
|
+
non-fatal: still return the clickable URL and the ensure result.
|
|
18
|
+
|
|
19
|
+
Do not dispatch project work, read task briefs, inspect executor outcomes, poll,
|
|
20
|
+
start a separate foreground CLI server, or modify TaskChef workspace files.
|
package/src/dashboard/app.js
CHANGED
|
@@ -19,6 +19,7 @@ import { openTaskFromControl } from "./actions.js";
|
|
|
19
19
|
import {
|
|
20
20
|
githubReferenceAccessibleLabel,
|
|
21
21
|
githubReferenceDisplayLabels,
|
|
22
|
+
groupRelatedGitHubLinks,
|
|
22
23
|
referenceSegments,
|
|
23
24
|
} from "./github-links.js";
|
|
24
25
|
import { formatRelativeTime, RelativeTimeController, parsedTimestamp } from "./time.js";
|
|
@@ -116,26 +117,35 @@ function appendLinkedText(container, text, task) {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
function relatedGitHubLinks(task, { compact = false } = {}) {
|
|
119
|
-
const
|
|
120
|
-
const links =
|
|
121
|
-
if (link.type === "repository" || seen.has(link.label)) return false;
|
|
122
|
-
seen.add(link.label);
|
|
123
|
-
return true;
|
|
124
|
-
});
|
|
120
|
+
const groups = groupRelatedGitHubLinks(task.relatedGitHubLinks);
|
|
121
|
+
const links = groups.flat();
|
|
125
122
|
const container = document.createElement("nav");
|
|
126
123
|
container.className = `github-links${compact ? " github-links-compact" : ""}`;
|
|
127
124
|
container.setAttribute("aria-label", `Related GitHub links for ${task.title}`);
|
|
128
125
|
const displayLabels = githubReferenceDisplayLabels(links, { related: true });
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
let linkIndex = 0;
|
|
127
|
+
const children = groups.map((group) => {
|
|
128
|
+
const line = document.createElement("div");
|
|
129
|
+
line.className = "github-links-group";
|
|
130
|
+
line.replaceChildren(...group.map((link) => {
|
|
131
|
+
const anchor = referenceLink(link, {
|
|
132
|
+
compact,
|
|
133
|
+
displayLabel: displayLabels[linkIndex],
|
|
134
|
+
});
|
|
135
|
+
linkIndex += 1;
|
|
136
|
+
return anchor;
|
|
137
|
+
}));
|
|
138
|
+
return line;
|
|
139
|
+
});
|
|
132
140
|
if (task.relatedGitHubLinksTruncated) {
|
|
133
141
|
const more = document.createElement("span");
|
|
134
142
|
more.className = "github-links-more";
|
|
135
143
|
more.textContent = "+ more";
|
|
136
144
|
more.title = "Additional related GitHub references are omitted from this bounded dashboard view.";
|
|
137
145
|
more.setAttribute("aria-label", more.title);
|
|
138
|
-
children.
|
|
146
|
+
const finalGroup = children.at(-1);
|
|
147
|
+
if (finalGroup) finalGroup.append(more);
|
|
148
|
+
else children.push(more);
|
|
139
149
|
}
|
|
140
150
|
container.hidden = children.length === 0;
|
|
141
151
|
container.replaceChildren(...children);
|
|
@@ -52,6 +52,27 @@ export function githubReferenceDisplayLabels(links, { related = false } = {}) {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function compareReferenceNumbers(left, right) {
|
|
56
|
+
const leftNumber = String(left.number ?? "");
|
|
57
|
+
const rightNumber = String(right.number ?? "");
|
|
58
|
+
return leftNumber.length - rightNumber.length || leftNumber.localeCompare(rightNumber);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function groupRelatedGitHubLinks(links) {
|
|
62
|
+
const groups = new Map();
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (const link of links ?? []) {
|
|
65
|
+
if (link.type === "repository" || seen.has(link.label)) continue;
|
|
66
|
+
seen.add(link.label);
|
|
67
|
+
const repository = repositoryIdentity(link);
|
|
68
|
+
if (!repository) continue;
|
|
69
|
+
const group = groups.get(repository) ?? [];
|
|
70
|
+
group.push(link);
|
|
71
|
+
groups.set(repository, group);
|
|
72
|
+
}
|
|
73
|
+
return [...groups.values()].map((group) => [...group].sort(compareReferenceNumbers));
|
|
74
|
+
}
|
|
75
|
+
|
|
55
76
|
export function githubReferenceAccessibleLabel(link) {
|
|
56
77
|
const repository = repositoryIdentity(link);
|
|
57
78
|
if (!repository) return link.label ?? link.text;
|
package/src/dashboard/styles.css
CHANGED
|
@@ -97,9 +97,10 @@ 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:
|
|
100
|
+
.github-links { display: grid; gap: 7px; margin: 0 0 18px; }
|
|
101
101
|
.github-links[hidden] { display: none; }
|
|
102
102
|
.github-links-compact { margin: -2px 0 12px; gap: 5px; }
|
|
103
|
+
.github-links-group { display: flex; min-width: 0; flex-wrap: wrap; gap: inherit; }
|
|
103
104
|
.github-link { color: var(--accent); font-weight: 700; text-decoration-thickness: 1px; text-underline-offset: 3px; overflow-wrap: anywhere; }
|
|
104
105
|
.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
106
|
.github-link:hover { text-decoration-thickness: 2px; }
|
package/src/mcp.js
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
recordTask,
|
|
7
7
|
reportTaskState,
|
|
8
8
|
reportTaskResult,
|
|
9
|
+
dashboardAutostartEnabled,
|
|
10
|
+
readConfig,
|
|
9
11
|
} from "./workspace.js";
|
|
10
12
|
import { parseTaskChefMarker } from "./delegation.js";
|
|
11
13
|
import { createDashboardManager } from "./dashboard-manager.js";
|
|
@@ -99,6 +101,39 @@ function toolResult(key, value, message) {
|
|
|
99
101
|
};
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
function dashboardAutostartDiagnostic(error) {
|
|
105
|
+
const message = typeof error?.message === "string" ? error.message : "";
|
|
106
|
+
if (error?.code === "EADDRINUSE" || /port conflict|already in use/i.test(message)) {
|
|
107
|
+
return "TaskChef dashboard autostart skipped: port 127.0.0.1:3210 is unavailable; the listener was left untouched.";
|
|
108
|
+
}
|
|
109
|
+
if (/configuration|workspace|task log/i.test(message)) {
|
|
110
|
+
return "TaskChef dashboard autostart skipped: the canonical workspace is not ready.";
|
|
111
|
+
}
|
|
112
|
+
return "TaskChef dashboard autostart skipped: the dashboard could not be started.";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createDashboardAutostart({
|
|
116
|
+
workspace,
|
|
117
|
+
dashboardManager,
|
|
118
|
+
readConfiguration = readConfig,
|
|
119
|
+
log = (message) => process.stderr.write(`${message}\n`),
|
|
120
|
+
} = {}) {
|
|
121
|
+
let startPromise = null;
|
|
122
|
+
return async () => {
|
|
123
|
+
startPromise ??= (async () => {
|
|
124
|
+
try {
|
|
125
|
+
const config = await readConfiguration(workspace, { checkPaths: false });
|
|
126
|
+
if (!dashboardAutostartEnabled(config)) return { action: "disabled" };
|
|
127
|
+
return await dashboardManager.ensure();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
try { log(dashboardAutostartDiagnostic(error)); } catch {}
|
|
130
|
+
return { action: "failed" };
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
133
|
+
return startPromise;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
102
137
|
export function createTaskChefMcpServer({
|
|
103
138
|
workspace = resolveWorkspacePath().workspace,
|
|
104
139
|
prepare = prepareDispatch,
|
|
@@ -107,6 +142,8 @@ export function createTaskChefMcpServer({
|
|
|
107
142
|
reportState = reportTaskState,
|
|
108
143
|
link = linkTask,
|
|
109
144
|
dashboardManager = createDashboardManager({ workspace }),
|
|
145
|
+
readConfiguration = readConfig,
|
|
146
|
+
logDashboardDiagnostic,
|
|
110
147
|
} = {}) {
|
|
111
148
|
const server = new McpServer(
|
|
112
149
|
{ name: "taskchef", version: TASKCHEF_VERSION },
|
|
@@ -280,5 +317,17 @@ export function createTaskChefMcpServer({
|
|
|
280
317
|
},
|
|
281
318
|
);
|
|
282
319
|
|
|
320
|
+
const originalConnect = server.connect.bind(server);
|
|
321
|
+
const autostartDashboard = createDashboardAutostart({
|
|
322
|
+
workspace,
|
|
323
|
+
dashboardManager,
|
|
324
|
+
readConfiguration,
|
|
325
|
+
...(logDashboardDiagnostic ? { log: logDashboardDiagnostic } : {}),
|
|
326
|
+
});
|
|
327
|
+
server.connect = async (...args) => {
|
|
328
|
+
await originalConnect(...args);
|
|
329
|
+
void autostartDashboard();
|
|
330
|
+
};
|
|
331
|
+
|
|
283
332
|
return server;
|
|
284
333
|
}
|
package/src/workspace.js
CHANGED
|
@@ -43,7 +43,8 @@ const PREVIOUS_TASK_SCHEMA_VERSION = 8;
|
|
|
43
43
|
const FIRST_TURN_HISTORY_TASK_SCHEMA_VERSION = 7;
|
|
44
44
|
const LEGACY_RESULTS_TASK_SCHEMA_VERSION = 6;
|
|
45
45
|
const FIRST_SELF_LINKING_TASK_SCHEMA_VERSION = 4;
|
|
46
|
-
const CONFIG_FIELDS = new Set(["schemaVersion", "projects"]);
|
|
46
|
+
const CONFIG_FIELDS = new Set(["schemaVersion", "projects", "dashboard"]);
|
|
47
|
+
const DASHBOARD_CONFIG_FIELDS = new Set(["autostart"]);
|
|
47
48
|
const PROJECT_FIELDS = new Set([
|
|
48
49
|
"name",
|
|
49
50
|
"path",
|
|
@@ -533,16 +534,36 @@ async function inspectProject(input, index = 0) {
|
|
|
533
534
|
}
|
|
534
535
|
|
|
535
536
|
export async function validateConfig(config, { checkPaths = true } = {}) {
|
|
536
|
-
|
|
537
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
538
|
+
throw new Error("taskchef.json must be an object");
|
|
539
|
+
}
|
|
540
|
+
const unexpected = Object.keys(config).find((key) => !CONFIG_FIELDS.has(key));
|
|
541
|
+
if (unexpected) throw new Error(`taskchef.json has unsupported field: ${unexpected}`);
|
|
542
|
+
for (const required of ["schemaVersion", "projects"]) {
|
|
543
|
+
if (!(required in config)) throw new Error(`taskchef.json is missing field: ${required}`);
|
|
544
|
+
}
|
|
537
545
|
if (config.schemaVersion !== CURRENT_CONFIG_SCHEMA_VERSION) {
|
|
538
546
|
throw new Error("unsupported configuration schemaVersion");
|
|
539
547
|
}
|
|
548
|
+
let dashboard;
|
|
549
|
+
if ("dashboard" in config) {
|
|
550
|
+
requireExactFields(config.dashboard, DASHBOARD_CONFIG_FIELDS, "taskchef.json.dashboard");
|
|
551
|
+
if (typeof config.dashboard.autostart !== "boolean") {
|
|
552
|
+
throw new Error("taskchef.json.dashboard.autostart must be a boolean");
|
|
553
|
+
}
|
|
554
|
+
dashboard = { autostart: config.dashboard.autostart };
|
|
555
|
+
}
|
|
540
556
|
return {
|
|
541
557
|
schemaVersion: CURRENT_CONFIG_SCHEMA_VERSION,
|
|
542
558
|
projects: await normalizeProjects(config.projects, { checkPaths }),
|
|
559
|
+
...(dashboard ? { dashboard } : {}),
|
|
543
560
|
};
|
|
544
561
|
}
|
|
545
562
|
|
|
563
|
+
export function dashboardAutostartEnabled(config) {
|
|
564
|
+
return config?.dashboard?.autostart !== false;
|
|
565
|
+
}
|
|
566
|
+
|
|
546
567
|
async function ensureDispatchFile(workspaceRoot) {
|
|
547
568
|
const filePath = path.join(workspaceRoot, DISPATCH_FILE_NAME);
|
|
548
569
|
const exists = await managedRegularFileExists(filePath);
|
|
@@ -569,7 +590,11 @@ export async function initializeWorkspace(workspaceRoot) {
|
|
|
569
590
|
const configExists = await managedRegularFileExists(configPath);
|
|
570
591
|
const config = configExists
|
|
571
592
|
? await readConfig(root, { checkPaths: false })
|
|
572
|
-
: {
|
|
593
|
+
: {
|
|
594
|
+
schemaVersion: CURRENT_CONFIG_SCHEMA_VERSION,
|
|
595
|
+
projects: [],
|
|
596
|
+
dashboard: { autostart: true },
|
|
597
|
+
};
|
|
573
598
|
const dispatchPath = path.join(root, DISPATCH_FILE_NAME);
|
|
574
599
|
if (configExists && (await managedRegularFileExists(dispatchPath))) {
|
|
575
600
|
await readDispatchesUnlocked(root);
|
|
@@ -638,7 +663,7 @@ export async function addProject(workspaceRoot, input) {
|
|
|
638
663
|
const project = await inspectProject(input);
|
|
639
664
|
assertWorkspaceOutsideProject(root, project.path);
|
|
640
665
|
const updated = await validateConfig({
|
|
641
|
-
|
|
666
|
+
...config,
|
|
642
667
|
projects: [...config.projects, project],
|
|
643
668
|
});
|
|
644
669
|
await writeJsonAtomic(path.join(root, "taskchef.json"), updated);
|
|
@@ -676,7 +701,7 @@ export async function importProjects(workspaceRoot, inputs, { replace = false }
|
|
|
676
701
|
else projects[index] = project;
|
|
677
702
|
}
|
|
678
703
|
const config = await validateConfig({
|
|
679
|
-
|
|
704
|
+
...current,
|
|
680
705
|
projects,
|
|
681
706
|
});
|
|
682
707
|
await writeJsonAtomic(path.join(root, "taskchef.json"), config);
|
|
@@ -700,7 +725,7 @@ export async function removeProject(workspaceRoot, name) {
|
|
|
700
725
|
const [project] = config.projects.slice(index, index + 1);
|
|
701
726
|
const projects = config.projects.filter((_, projectIndex) => projectIndex !== index);
|
|
702
727
|
await writeJsonAtomic(path.join(root, "taskchef.json"), {
|
|
703
|
-
|
|
728
|
+
...config,
|
|
704
729
|
projects,
|
|
705
730
|
});
|
|
706
731
|
return { project };
|