taskchef 7.20.0 → 7.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +26 -11
- package/docs/spec.md +57 -15
- package/docs/workflows.md +11 -9
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/dashboard/actions.js +81 -0
- package/src/dashboard/app.js +220 -13
- package/src/dashboard/index.html +13 -4
- package/src/dashboard/state.js +60 -3
- package/src/dashboard/styles.css +17 -4
- package/src/dashboard.js +116 -0
- package/src/mcp.js +19 -1
- package/src/usage-tracker.js +68 -13
- package/src/workspace.js +368 -20
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ The canonical workspace is `~/.agents/taskchef`. TaskChef owns only:
|
|
|
55
55
|
```text
|
|
56
56
|
AGENTS.md managed dispatcher instructions plus user additions
|
|
57
57
|
taskchef.json schema-2 Codex project index, dashboard preference, and delegation metadata
|
|
58
|
-
tasks.jsonl one task snapshot per line (schema
|
|
58
|
+
tasks.jsonl one task snapshot per line (schema 10; schema 4-9 migration supported)
|
|
59
59
|
.taskchef-usage.json optional mode-0600 ccusage snapshot and turn-boundary cache
|
|
60
60
|
```
|
|
61
61
|
|
|
@@ -270,7 +270,21 @@ The loopback dashboard watches `tasks.jsonl`, groups current states, and opens
|
|
|
270
270
|
linked Codex tasks. List snapshots and SSE events carry only the latest
|
|
271
271
|
request/result pair; opening task details fetches the full newest-first activity
|
|
272
272
|
timeline, including clearly labeled interrupted turns.
|
|
273
|
-
Task details
|
|
273
|
+
Task details offer infrequent administrative actions without cluttering task
|
|
274
|
+
cards. Selecting **More task actions** (`…`) reveals the action list immediately
|
|
275
|
+
beside the disclosure and changes it to a back/hide control. The list contains
|
|
276
|
+
**Copy Task ID**, direct **Mark completed** and **Mark failed** actions for a
|
|
277
|
+
`working` or `needs_input` task, and **Archive chat** whenever archival is
|
|
278
|
+
eligible. The menu disclosure is the deliberate first step; choosing a terminal
|
|
279
|
+
outcome submits it immediately without a second confirmation. There is no
|
|
280
|
+
free-form reason: the audit turn records a fixed summary, timestamp, dashboard
|
|
281
|
+
provenance, optimistic preconditions, and a unique action ID while preserving
|
|
282
|
+
every executor turn. Terminal tasks cannot be rewritten. Stale or concurrent
|
|
283
|
+
changes are rejected and the dialog refreshes to the current task. A stalled
|
|
284
|
+
local request is aborted after a bounded wait so the dialog cannot remain
|
|
285
|
+
permanently locked; retry keeps the same idempotency identity.
|
|
286
|
+
|
|
287
|
+
The More actions list offers **Archive chat** for every linked task whose current
|
|
274
288
|
TaskChef state is not `working`. After confirmation, TaskChef invokes only the
|
|
275
289
|
Codex CLI at the canonical ChatGPT or Codex desktop app location under
|
|
276
290
|
`/Applications`, to archive the exact thread UUID. The Codex chat leaves active chat lists while the TaskChef record
|
|
@@ -303,12 +317,13 @@ revision, so reconnects and non-semantic rewrites do not replay a notice and a
|
|
|
303
317
|
later task state cannot rewrite an older notice. A notice remains readable if
|
|
304
318
|
its task disappears; selecting it then explains that current details are no
|
|
305
319
|
longer available.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
MCP port. If a
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
listener or choose
|
|
320
|
+
Apart from an explicit manual state selection from the task-detail menu, it does not mutate
|
|
321
|
+
TaskChef data and prints its local URL. A foreground dashboard identifies
|
|
322
|
+
itself as standalone and is never reused on the canonical MCP port. If a
|
|
323
|
+
standalone, unknown, different-workspace, or stale-version process owns port
|
|
324
|
+
3210, TaskChef reports a concise conflict and never kills or replaces that
|
|
325
|
+
process. The foreground CLI similarly asks you to stop the listener or choose
|
|
326
|
+
another `--port`.
|
|
312
327
|
|
|
313
328
|
The health endpoint contains only a fixed service marker, health schema,
|
|
314
329
|
TaskChef version, dashboard-server version, canonical workspace, and launcher.
|
|
@@ -338,9 +353,9 @@ taskchef doctor
|
|
|
338
353
|
|
|
339
354
|
`doctor` is read-only. `workspace init` creates missing files and refreshes
|
|
340
355
|
managed instructions. `workspace migrate` explicitly upgrades supported schema
|
|
341
|
-
4-
|
|
356
|
+
4-9 task lines to schema 10 under the workspace lock. It validates task and turn
|
|
342
357
|
counts plus the complete source and converted log before writing, creates an
|
|
343
|
-
exclusive `tasks.jsonl.pre-
|
|
358
|
+
exclusive `tasks.jsonl.pre-v10-*.bak`
|
|
344
359
|
backup, atomically replaces the log, validates the result, and becomes an
|
|
345
360
|
idempotent no-op after migration. If replacement fails, the original remains
|
|
346
361
|
or the reported backup can be restored; unsupported or invalid input is rejected
|
|
@@ -351,7 +366,7 @@ executor so its first action can retry `link_task`. Do not guess an identity
|
|
|
351
366
|
or edit `tasks.jsonl`. If native task creation failed, the record is retained
|
|
352
367
|
as `failed` with a retained fallback `turnRef` and null thread and Codex turn IDs.
|
|
353
368
|
|
|
354
|
-
Schemas other than 4, 5, 6, 7, 8, and
|
|
369
|
+
Schemas other than 4, 5, 6, 7, 8, 9, and 10 remain unsupported. Retain such a workspace
|
|
355
370
|
unchanged and create a current workspace; the migration command deliberately
|
|
356
371
|
does not guess how to convert unknown formats.
|
|
357
372
|
|
package/docs/spec.md
CHANGED
|
@@ -28,7 +28,7 @@ is dated research, not contract.
|
|
|
28
28
|
| **Last semantic result** | The final result-history entry, exposed through the derived `lastResult` compatibility alias. |
|
|
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
|
-
| **Dashboard** | The loopback
|
|
31
|
+
| **Dashboard** | The loopback UI derived from validated workspace snapshots, with optional local usage projections, bounded native actions, and an explicit audited manual-outcome mutation. |
|
|
32
32
|
| **Skill** | One packaged agent procedure: `taskchef-bootstrap`, `taskchef-dashboard`, `taskchef-delegate`, `taskchef-executor`, or `taskchef-copilot`. |
|
|
33
33
|
|
|
34
34
|
## Components and ownership
|
|
@@ -50,7 +50,8 @@ is dated research, not contract.
|
|
|
50
50
|
and the deprecated `report_result` compatibility alias specified below.
|
|
51
51
|
- The CLI MAY administer and inspect the workspace, but MUST NOT provide a
|
|
52
52
|
second agent lifecycle protocol.
|
|
53
|
-
- The dashboard MUST be read-only with respect to dispatcher files
|
|
53
|
+
- The dashboard MUST be read-only with respect to dispatcher files except for
|
|
54
|
+
the explicit audited manual-transition operation defined below.
|
|
54
55
|
- `docs/spec.md` is the single normative behavior source. Other documents
|
|
55
56
|
MUST link here rather than redefine the contract.
|
|
56
57
|
|
|
@@ -82,8 +83,8 @@ is backward-compatible and means `true`; new workspaces SHOULD write
|
|
|
82
83
|
disable the explicit `ensure_dashboard` tool.
|
|
83
84
|
|
|
84
85
|
`tasks.jsonl` MUST contain zero or more newline-terminated schema-4 through
|
|
85
|
-
schema-
|
|
86
|
-
migration/read formats; every new record and state mutation MUST write schema
|
|
86
|
+
schema-10 records, one per line. Schemas 4 through 9 are supported
|
|
87
|
+
migration/read formats; every new record and state mutation MUST write schema 10. Other schemas
|
|
87
88
|
or unsupported fields MUST be rejected without conversion.
|
|
88
89
|
Reads and writes MUST reject symlinked managed files. Mutations
|
|
89
90
|
MUST hold the shared workspace lock and replace state atomically; read-only
|
|
@@ -95,7 +96,7 @@ Every record MUST contain exactly these fields:
|
|
|
95
96
|
|
|
96
97
|
| Field | Contract |
|
|
97
98
|
| --- | --- |
|
|
98
|
-
| `schemaVersion` | Integer `
|
|
99
|
+
| `schemaVersion` | Integer `10`; schema-4/5/6/7/8/9 records remain readable until explicit migration or their next mutation. |
|
|
99
100
|
| `id` | Unique safe TaskChef ID; delegation uses a lowercase full UUID. |
|
|
100
101
|
| `project` | Immutable configured-project snapshot. |
|
|
101
102
|
| `title` | Non-empty display title. |
|
|
@@ -107,14 +108,14 @@ Every record MUST contain exactly these fields:
|
|
|
107
108
|
| `turnRef` | Null before reporting begins; otherwise the required lifecycle identity of the current turn. Self-linking journeys use a native or fallback UUID. Migrated low-level `report_result` compatibility records may retain an opaque ref equal to their opaque `turnId`. |
|
|
108
109
|
| `turnId` | Optional Codex metadata. When non-null it equals `turnRef`; null indicates fallback identity. |
|
|
109
110
|
| `updatedAt` | ISO 8601 timestamp not earlier than `createdAt` or the prior `updatedAt`; clock rollback cannot backdate a transition. |
|
|
110
|
-
| `updatedBy` | `dispatcher` or `
|
|
111
|
-
| `turns` | Ordered oldest-first array of `{turnRef, turnId, requestSummary, startedAt, result}`. Every `turnRef` is required. `requestSummary` is null only for migrated/compatibility turns. `result` is null only for the latest working turn, a semantic `{status, summary, updatedAt}` result, or the fixed TaskChef `interrupted` outcome. New self-linking turn refs are unique. A migrated low-level opaque record may retain one final reused native-derived ref for its legacy ambiguity. |
|
|
111
|
+
| `updatedBy` | `dispatcher`, `mcp`, or `dashboard`. `dashboard` is valid only when the latest turn is a manual dashboard transition. |
|
|
112
|
+
| `turns` | Ordered oldest-first array of `{turnRef, turnId, requestSummary, startedAt, result, provenance}`. Every `turnRef` is required. `requestSummary` is null only for migrated/compatibility turns. `result` is null only for the latest working turn, a semantic `{status, summary, updatedAt}` result, or the fixed TaskChef `interrupted` outcome. `provenance` is `{kind: legacy}` or `{kind: mcp}` for ordinary turns, or the audited manual-transition record defined below. New self-linking and manual turn refs are unique. A migrated low-level opaque record may retain one final reused native-derived ref for its legacy ambiguity. |
|
|
112
113
|
|
|
113
114
|
Returned Task objects MUST additionally expose `latestTurn` as null for an empty
|
|
114
115
|
timeline or the final `turns` entry. They MUST derive `results` only from
|
|
115
116
|
semantic `needs_input`, `completed`, and `failed` turn results and `lastResult`
|
|
116
117
|
from the final derived semantic result. Interrupted outcomes MUST be excluded.
|
|
117
|
-
These projections MUST NOT be persisted in schema
|
|
118
|
+
These projections MUST NOT be persisted in schema 10 and remain compatibility
|
|
118
119
|
aliases for existing callers.
|
|
119
120
|
|
|
120
121
|
## Optional usage projection
|
|
@@ -125,9 +126,15 @@ store, or serve raw Codex rollout files, prompts, responses, transcripts, or
|
|
|
125
126
|
reasoning. Analyzer absence, timeout, malformed output, unknown pricing, or an
|
|
126
127
|
unresolved thread MUST NOT block lifecycle tools or dashboard loading.
|
|
127
128
|
|
|
129
|
+
Dashboard-manual turns are administrative events, not Codex executions. They
|
|
130
|
+
MUST immediately report per-turn usage as unavailable, MUST NOT schedule or
|
|
131
|
+
store a cumulative usage boundary, and MUST NOT receive a token or cost delta.
|
|
132
|
+
A later executor turn MUST use the nearest preceding reliable executor
|
|
133
|
+
boundary, skipping any intervening dashboard-manual turns.
|
|
134
|
+
|
|
128
135
|
The mode-0600 `.taskchef-usage.json` cache stores only normalized cumulative
|
|
129
136
|
token boundaries, per-turn deltas, model names, estimated cost, source version,
|
|
130
|
-
and freshness. It is independent of
|
|
137
|
+
and freshness. It is independent of task-log schema versions so legacy logs remain
|
|
131
138
|
readable. Writes MUST use the workspace lock and atomic replacement. Symlinked
|
|
132
139
|
or unsupported cache files MUST be rejected. Writes MUST compact the derived
|
|
133
140
|
cache to recent task projections, recent per-turn results, and the latest
|
|
@@ -350,7 +357,7 @@ new preparation values, though it writes no state.
|
|
|
350
357
|
|
|
351
358
|
**Structured output:** `{ task: Task }`.
|
|
352
359
|
|
|
353
|
-
The returned task has schema
|
|
360
|
+
The returned task has schema 10, `working`, null summary/turn/thread/latestTurn/lastResult,
|
|
354
361
|
empty `turns` and derived `results` arrays,
|
|
355
362
|
`updatedBy: dispatcher`, and equal creation/update timestamps. Duplicate IDs,
|
|
356
363
|
unknown projects, malformed markers, and invalid input fail. Repeating a
|
|
@@ -427,8 +434,8 @@ MUST contain no crash output, transcript, user text, or inferred failure cause.
|
|
|
427
434
|
temporary compatibility alias. It implicitly accepts a fresh supplied turn and
|
|
428
435
|
stores its semantic result in a request-unknown turn, including for supported schema-4/5/6 records and
|
|
429
436
|
low-level opaque direct records. It does not accept `working`. New executor
|
|
430
|
-
instructions MUST use `report_state`. Successful mutation upgrades schema 4-
|
|
431
|
-
to schema
|
|
437
|
+
instructions MUST use `report_state`. Successful mutation upgrades schema 4-9
|
|
438
|
+
to schema 10; unsupported schemas remain rejected. Legacy callers that omit
|
|
432
439
|
`turnRef` remain compatible when `turnId` is non-null.
|
|
433
440
|
|
|
434
441
|
## Copilot and dashboard
|
|
@@ -476,6 +483,41 @@ startup safely. Direct thread navigation
|
|
|
476
483
|
MUST require a canonical Codex UUIDv7. Otherwise it MAY open the revalidated
|
|
477
484
|
configured project. Project paths from task history MUST be matched against
|
|
478
485
|
current configuration before use.
|
|
486
|
+
The task-detail dashboard MAY offer manual terminal outcomes only for current
|
|
487
|
+
`working` or `needs_input` tasks. It MUST permit exactly `completed` and
|
|
488
|
+
`failed`, MUST NOT rewrite a terminal task, and MUST keep this infrequent
|
|
489
|
+
administrative action out of list cards. A keyboard-accessible **More task
|
|
490
|
+
actions** disclosure MUST reveal its action list immediately beside it and
|
|
491
|
+
change from an ellipsis to an accessible back/hide control while expanded. The
|
|
492
|
+
list MUST group **Copy Task ID**, **Mark completed**, and **Mark failed** for
|
|
493
|
+
eligible tasks, plus **Archive chat** when archival is eligible. Opening the
|
|
494
|
+
list is the deliberate disclosure step; choosing a terminal outcome submits it
|
|
495
|
+
immediately without a second confirmation. Escape MUST close the idle list,
|
|
496
|
+
pending controls MUST be
|
|
497
|
+
disabled, and failure feedback MUST remain in the dialog. Pending state MUST
|
|
498
|
+
focus and announce a stable status inside the dialog. Failed MUST have
|
|
499
|
+
destructive styling. The client MUST bound and abort a stalled manual-transition
|
|
500
|
+
request, restore the dialog controls, and preserve the action ID so a retry can
|
|
501
|
+
resolve idempotently if the server committed before the timeout.
|
|
502
|
+
|
|
503
|
+
`POST /api/tasks/:id/manual-transition` MUST require the exact loopback origin,
|
|
504
|
+
`application/json`, a bounded body, and exactly this versioned shape:
|
|
505
|
+
`{schemaVersion: 1, actionId, expected: {status, turnRef, threadId, updatedAt},
|
|
506
|
+
targetStatus}`. The server MUST compare every expected field while holding the
|
|
507
|
+
workspace lock. A stale request, invalid transition, or reused action ID with a
|
|
508
|
+
different operation MUST return a conflict without mutation. Replaying the
|
|
509
|
+
same committed action ID and operation MUST return the current task as an
|
|
510
|
+
idempotent success. The operation MUST atomically append one new manual turn;
|
|
511
|
+
it MUST NOT overwrite executor history. An active unfinished turn MUST first
|
|
512
|
+
receive the standard interrupted timeline outcome. The manual turn MUST have a
|
|
513
|
+
new server-generated `turnRef`, null `turnId`, one monotonic timestamp for its
|
|
514
|
+
start, result, and task update, `updatedBy: dashboard`, and deterministic
|
|
515
|
+
request/result summaries. It MUST record provenance
|
|
516
|
+
`{kind: dashboard_manual, actionId, fromStatus, toStatus, expectedTurnRef,
|
|
517
|
+
expectedThreadId, expectedUpdatedAt}`. No free-form reason is collected or
|
|
518
|
+
persisted. A committed write remains successful if the subsequent best-effort
|
|
519
|
+
monitor refresh or notification delivery fails.
|
|
520
|
+
|
|
479
521
|
The task-detail dashboard MAY offer Codex chat archival only when the stored
|
|
480
522
|
thread ID is a canonical Codex UUID and the current TaskChef state is not
|
|
481
523
|
`working`. It MUST revalidate both conditions for the POST request, require the
|
|
@@ -521,15 +563,15 @@ its displayed summary when present, event time, and missing-task state.
|
|
|
521
563
|
|
|
522
564
|
## Task-log migration
|
|
523
565
|
|
|
524
|
-
`workspace migrate` MUST explicitly convert every supported schema-4/5/6/7/8 record
|
|
566
|
+
`workspace migrate` MUST explicitly convert every supported schema-4/5/6/7/8/9 record
|
|
525
567
|
under the shared lock. Each legacy semantic result becomes a request-unknown
|
|
526
568
|
completed turn; a newer working state becomes a final unfinished turn, and a
|
|
527
569
|
schema-7/8 timeline is preserved. Each non-null legacy `turnId` becomes the
|
|
528
570
|
same `turnRef`; each null legacy `turnId` receives one durably persisted UUID.
|
|
529
571
|
Migration MUST validate the complete source, record/turn counts, turn-ref
|
|
530
|
-
invariants, and complete schema-
|
|
572
|
+
invariants, and complete schema-10 candidate before changing the task log,
|
|
531
573
|
create and read back an exclusive recovery backup, atomically replace the log,
|
|
532
|
-
and validate the installed result. A fully schema-
|
|
574
|
+
and validate the installed result. A fully schema-10 log MUST be an idempotent
|
|
533
575
|
no-op without another backup. Invalid/unsupported input MUST remain untouched;
|
|
534
576
|
failures after backup creation MUST report the backup path and MUST never
|
|
535
577
|
partially rewrite individual lines.
|
package/docs/workflows.md
CHANGED
|
@@ -116,7 +116,7 @@ sequenceDiagram
|
|
|
116
116
|
D->>D: Choose one configured and native project
|
|
117
117
|
D->>M: record_task(id, project, title, instruction, null)
|
|
118
118
|
M->>W: recordTask()
|
|
119
|
-
W->>W: Lock, validate, append schema-
|
|
119
|
+
W->>W: Lock, validate, append schema-10 snapshot
|
|
120
120
|
W-->>M: working link-pending task
|
|
121
121
|
M-->>D: task
|
|
122
122
|
D->>C: Create executor with marked instruction
|
|
@@ -260,7 +260,7 @@ sequenceDiagram
|
|
|
260
260
|
W->>W: Acquire workspace lock and validate turnRefB is new
|
|
261
261
|
W->>W: Close unfinished turnA as interrupted
|
|
262
262
|
W->>W: Append turnB with requestB and null result
|
|
263
|
-
W->>F: One atomic schema-
|
|
263
|
+
W->>F: One atomic schema-10 replacement
|
|
264
264
|
W-->>M: working task projected from turnB
|
|
265
265
|
M-->>E: Idempotent recovery success
|
|
266
266
|
E->>M: late semantic result for turnA
|
|
@@ -396,16 +396,17 @@ all, and ordinary rerendering do not re-announce retained history. Toast action
|
|
|
396
396
|
labels remain concise while `aria-describedby` connects the visible summary,
|
|
397
397
|
event time, and missing-task explanation for assistive technology.
|
|
398
398
|
|
|
399
|
-
## Schema 4-
|
|
399
|
+
## Schema 4-9 migration
|
|
400
400
|
|
|
401
401
|
`taskchef workspace migrate` acquires the same workspace lock as lifecycle
|
|
402
402
|
writers, validates the complete legacy log, converts schema-4/5/6 results into
|
|
403
403
|
request-unknown completed turns and preserves a newer working turn, then validates the
|
|
404
404
|
complete candidate. Before replacement it writes and reads back an exclusive
|
|
405
|
-
`tasks.jsonl.pre-
|
|
405
|
+
`tasks.jsonl.pre-v10-*.bak` file. Schema-7/8 timelines gain a durable `turnRef`:
|
|
406
406
|
non-null `turnId` values are copied exactly and null IDs receive persisted UUIDs.
|
|
407
407
|
Task and turn counts plus all refs are validated before and after the atomic
|
|
408
|
-
replacement.
|
|
408
|
+
replacement. Schema-9 turns gain explicit legacy or MCP provenance. A second
|
|
409
|
+
run sees only schema 10 and returns unchanged without
|
|
409
410
|
another backup. Unsupported or malformed input fails before backup/rewrite;
|
|
410
411
|
after a later filesystem failure, the reported backup is the recovery source.
|
|
411
412
|
|
|
@@ -422,9 +423,10 @@ summary is cryptographically authenticated; this is a local single-user trust
|
|
|
422
423
|
model. Managed files, instructions, project snapshots, MCP inputs, and dashboard
|
|
423
424
|
requests are validated at every action boundary.
|
|
424
425
|
|
|
425
|
-
Configuration schema 2 and task schemas 4 through
|
|
426
|
-
4-
|
|
427
|
-
mutation upgrades each record to schema
|
|
426
|
+
Configuration schema 2 and task schemas 4 through 10 are accepted. Schemas
|
|
427
|
+
4-9 are read/migration compatibility until an explicit migration or lifecycle
|
|
428
|
+
mutation upgrades each record to schema 10. Schema 10 persists `turns`, including
|
|
428
429
|
timeline-only interrupted outcomes, and derives semantic-only `results` and
|
|
429
|
-
`lastResult` plus `latestTurn` for compact compatibility.
|
|
430
|
+
`lastResult` plus `latestTurn` for compact compatibility. It also records turn
|
|
431
|
+
provenance and the optimistic preconditions for manual dashboard outcomes. Other schemas
|
|
430
432
|
are rejected without rewrite.
|
package/index.js
CHANGED
package/package.json
CHANGED
package/src/dashboard/actions.js
CHANGED
|
@@ -53,3 +53,84 @@ export async function archiveTaskFromControl(event, task, {
|
|
|
53
53
|
if (!archived) control.disabled = false;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
export async function manuallyTransitionTaskFromControl(event, task, targetStatus, actionId, {
|
|
58
|
+
clearTimer = globalThis.clearTimeout,
|
|
59
|
+
fetchAction = globalThis.fetch,
|
|
60
|
+
setTimer = globalThis.setTimeout,
|
|
61
|
+
timeoutMs = 10_000,
|
|
62
|
+
} = {}) {
|
|
63
|
+
event?.stopPropagation?.();
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeout = setTimer(() => controller.abort(), timeoutMs);
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetchAction(
|
|
68
|
+
`/api/tasks/${encodeURIComponent(task.id)}/manual-transition`,
|
|
69
|
+
{
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { "Content-Type": "application/json" },
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
schemaVersion: 1,
|
|
74
|
+
actionId,
|
|
75
|
+
expected: {
|
|
76
|
+
status: task.status,
|
|
77
|
+
turnRef: task.turnRef,
|
|
78
|
+
threadId: task.threadId,
|
|
79
|
+
updatedAt: task.updatedAt,
|
|
80
|
+
},
|
|
81
|
+
targetStatus,
|
|
82
|
+
}),
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
const result = await response.json();
|
|
87
|
+
return response.ok
|
|
88
|
+
? { ok: true, ...result }
|
|
89
|
+
: {
|
|
90
|
+
ok: false,
|
|
91
|
+
code: result.code ?? "dashboard_error",
|
|
92
|
+
message: result.message ?? "Task state could not be changed.",
|
|
93
|
+
task: result.task ?? null,
|
|
94
|
+
};
|
|
95
|
+
} catch {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
code: controller.signal.aborted ? "request_timeout" : "network_error",
|
|
99
|
+
message: controller.signal.aborted
|
|
100
|
+
? "Task state change timed out. Try again."
|
|
101
|
+
: "Task state changes are temporarily unavailable. Try again.",
|
|
102
|
+
task: null,
|
|
103
|
+
};
|
|
104
|
+
} finally {
|
|
105
|
+
clearTimer(timeout);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function handleManualTransitionEscape(event, {
|
|
110
|
+
active,
|
|
111
|
+
pending,
|
|
112
|
+
cancel,
|
|
113
|
+
}) {
|
|
114
|
+
if (event.key !== "Escape" || !active) return false;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
event.stopPropagation();
|
|
117
|
+
if (!pending) cancel();
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function focusManualTransitionStatus(panel) {
|
|
122
|
+
const status = panel.querySelector('[data-manual-focus="pending"]');
|
|
123
|
+
status?.focus();
|
|
124
|
+
return status !== null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function restoreTaskActionMenuFocus(panel, activeElement, fallback) {
|
|
128
|
+
if (!activeElement || !panel.contains(activeElement)) return null;
|
|
129
|
+
if (!activeElement.hidden && !activeElement.disabled) return activeElement;
|
|
130
|
+
const copyTaskId = panel.querySelector('[data-manual-focus="copy"]');
|
|
131
|
+
const destination = copyTaskId && !copyTaskId.hidden && !copyTaskId.disabled
|
|
132
|
+
? copyTaskId
|
|
133
|
+
: fallback;
|
|
134
|
+
destination?.focus();
|
|
135
|
+
return destination ?? null;
|
|
136
|
+
}
|