taskchef 7.15.4 → 7.15.6

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.4",
3
+ "version": "7.15.6",
4
4
  "description": "Dispatch work from a data-only workspace to visible Codex project tasks.",
5
5
  "author": {
6
6
  "name": "Favo Yang",
package/docs/spec.md CHANGED
@@ -143,6 +143,25 @@ the final link, preserving the delegate skill's immediate-return contract.
143
143
  TaskChef MUST atomically close it as `interrupted` before appending the new
144
144
  working turn; the executor MUST NOT report semantic `failed` for recovery.
145
145
 
146
+ Normal executor completion MUST stop after the terminal callback and return
147
+ normally. It MUST NOT archive, hand off, close, navigate away from, or otherwise
148
+ terminate the Codex task merely because work completed. Archive is authorized
149
+ only by an explicit request in the current assignment or follow-up for that
150
+ exact Codex task; `finish`, `complete`, `done`, `ship`, and ordinary cleanup do
151
+ not imply authorization.
152
+
153
+ For an explicitly authorized archive, the executor MUST finish and verify the
154
+ work, read its exact task identity, submit the current turn's terminal
155
+ `report_state`, verify TaskChef accepted that state, and only then invoke the
156
+ native archive operation as the final state-changing action. It MUST require
157
+ native confirmation before claiming archive succeeded. The same callback-first
158
+ ordering applies to any separately authorized action that can make the executor
159
+ unavailable before reporting, including handoff or terminating or restarting
160
+ the process that owns the TaskChef MCP transport; this rule does not itself
161
+ authorize those actions. A terminal reporting failure MUST prevent the later
162
+ action and leave the executor accessible. Failure of a later action MUST NOT
163
+ change or reopen the already accepted semantic terminal state.
164
+
146
165
  Request and result summaries are the durable source for dashboard related-link
147
166
  projection; TaskChef does not scan full Codex transcripts. When known, an
148
167
  executor MUST preserve the selected repository as a canonical GitHub repository
package/docs/workflows.md CHANGED
@@ -163,6 +163,16 @@ sequenceDiagram
163
163
  A native approval prompt is not a semantic result. `needs_input` is reserved
164
164
  for a user decision or fact required to proceed.
165
165
 
166
+ Ordinary completion returns after the terminal callback; it does not archive,
167
+ hand off, close, navigate away from, or otherwise terminate the native Codex
168
+ task. An archive requires an explicit request in the current assignment or
169
+ follow-up for that exact task. `finish`, `complete`, `done`, `ship`, and cleanup
170
+ alone do not authorize it. Any explicitly requested action that could make the
171
+ executor unavailable must follow, never precede, an accepted terminal callback.
172
+ If reporting fails, the later action is forbidden and the executor remains
173
+ accessible. If the later action fails, its error does not reopen or replace the
174
+ accepted semantic state.
175
+
166
176
  ## Follow-up turns
167
177
 
168
178
  Turn refs are freshness tokens for semantic callbacks. The workspace rejects a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "7.15.4",
3
+ "version": "7.15.6",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
@@ -80,6 +80,33 @@ and unnecessary personal data. Identical lifecycle retries are safe; never
80
80
  replace a same-`turnRef` report with different content or let an older turn
81
81
  overwrite newer state.
82
82
 
83
+ ### Keep terminal reporting ahead of executor-ending actions
84
+
85
+ Normal completion ends with the semantic terminal `report_state` callback and
86
+ then returns normally. Never archive, hand off, close, navigate away from, or
87
+ otherwise terminate the Codex task merely because the work or turn completed.
88
+ An archive is authorized only when the current assignment or follow-up
89
+ explicitly requests archiving this exact Codex task. Words such as `finish`,
90
+ `complete`, `done`, `ship`, and ordinary cleanup do not authorize archive.
91
+
92
+ When archive is explicitly authorized, finish and verify the requested work,
93
+ read the exact task identity required by this protocol, submit the terminal
94
+ `report_state` callback for the current `turnRef`, and verify that TaskChef
95
+ accepted the terminal state. Only then call the native Codex archive operation.
96
+ Archive must be the final state-changing action, and native confirmation is
97
+ required before claiming it succeeded.
98
+
99
+ Apply the same terminal-callback-first rule when the user explicitly requests
100
+ another action that can make this executor unavailable before it reports, such
101
+ as handing off the task or terminating or restarting the process that owns the
102
+ TaskChef MCP transport. This ordering rule does not authorize any such action.
103
+ If terminal reporting fails, do not archive or perform the requested
104
+ executor-ending action; leave the executor accessible and report the lifecycle
105
+ failure visibly. If terminal reporting succeeds but the later action fails,
106
+ preserve the accepted `completed`, `failed`, or `needs_input` state, report only
107
+ the later action's failure, and do not reopen the lifecycle or report `working`
108
+ again.
109
+
83
110
  ### Preserve repository and delivery links
84
111
 
85
112
  Treat lifecycle summaries as the durable TaskChef timeline; the dashboard does
@@ -29,6 +29,8 @@ const state = {
29
29
  };
30
30
  let dateRefreshTimer = null;
31
31
  let detailRequestGeneration = 0;
32
+ let copyTaskIdGeneration = 0;
33
+ let copyTaskIdTimer = null;
32
34
  const relativeTimes = new RelativeTimeController();
33
35
 
34
36
  const elements = {
@@ -36,7 +38,7 @@ const elements = {
36
38
  closeDialog: document.querySelector("#close-dialog"),
37
39
  connectionDot: document.querySelector("#connection-dot"),
38
40
  connectionLabel: document.querySelector("#connection-label"),
39
- copyThreadId: document.querySelector("#copy-thread-id"),
41
+ copyTaskId: document.querySelector("#copy-task-id"),
40
42
  dashboardMessage: document.querySelector("#dashboard-message"),
41
43
  dashboardMessageText: document.querySelector("#dashboard-message-text"),
42
44
  dateFilter: document.querySelector("#date-filter"),
@@ -211,6 +213,11 @@ function configureOpenTaskControl(control, ariaLabel) {
211
213
  control.replaceChildren(codexIcon(), label);
212
214
  }
213
215
 
216
+ function setCopyTaskIdLabel(label) {
217
+ elements.copyTaskId.textContent = label;
218
+ elements.copyTaskId.setAttribute("aria-label", label);
219
+ }
220
+
214
221
  function setConnection(connected) {
215
222
  elements.connectionDot.classList.toggle("connected", connected);
216
223
  elements.connectionLabel.textContent = connected ? "Live" : "Reconnecting…";
@@ -377,6 +384,11 @@ function turnTimeline(task) {
377
384
  }
378
385
 
379
386
  function renderDialog(task) {
387
+ if (state.selectedTask?.id !== task.id) {
388
+ copyTaskIdGeneration += 1;
389
+ clearTimeout(copyTaskIdTimer);
390
+ copyTaskIdTimer = null;
391
+ }
380
392
  const preservedResults = state.selectedTask?.id === task.id
381
393
  ? state.selectedTask.results
382
394
  : null;
@@ -398,7 +410,8 @@ function renderDialog(task) {
398
410
  );
399
411
  elements.dialogResults.replaceChildren(...turnTimeline(detailedTask));
400
412
  elements.dialogInstruction.textContent = task.instruction;
401
- elements.copyThreadId.disabled = !task.threadId;
413
+ elements.copyTaskId.disabled = !task.id;
414
+ setCopyTaskIdLabel("Copy Task ID");
402
415
  elements.dialogMetadata.replaceChildren(
403
416
  ...detailRow("Current status", taskStatusLabel(task)),
404
417
  ...detailRow("Current turn ref", task.turnRef),
@@ -607,14 +620,26 @@ elements.closeDialog.addEventListener("click", () => elements.dialog.close());
607
620
  elements.dialog.addEventListener("click", (event) => {
608
621
  if (event.target === elements.dialog) elements.dialog.close();
609
622
  });
610
- elements.copyThreadId.addEventListener("click", async () => {
611
- if (!state.selectedTask?.threadId) return;
623
+ elements.copyTaskId.addEventListener("click", async () => {
624
+ const taskId = state.selectedTask?.id;
625
+ if (!taskId) return;
626
+ const copyGeneration = ++copyTaskIdGeneration;
627
+ clearTimeout(copyTaskIdTimer);
628
+ copyTaskIdTimer = null;
612
629
  try {
613
- await navigator.clipboard.writeText(state.selectedTask.threadId);
614
- elements.copyThreadId.textContent = "Copied";
615
- setTimeout(() => { elements.copyThreadId.textContent = "Copy thread ID"; }, 1_500);
630
+ await navigator.clipboard.writeText(taskId);
631
+ if (copyGeneration !== copyTaskIdGeneration || state.selectedTask?.id !== taskId) return;
632
+ setCopyTaskIdLabel("Task ID copied");
633
+ copyTaskIdTimer = setTimeout(() => {
634
+ if (copyGeneration === copyTaskIdGeneration && state.selectedTask?.id === taskId) {
635
+ setCopyTaskIdLabel("Copy Task ID");
636
+ }
637
+ copyTaskIdTimer = null;
638
+ }, 1_500);
616
639
  } catch {
617
- showMessage("Clipboard access is unavailable. Copy the thread ID from the metadata below.");
640
+ if (copyGeneration === copyTaskIdGeneration && state.selectedTask?.id === taskId) {
641
+ showMessage("Clipboard access is unavailable. Copy the Task ID from the metadata below.");
642
+ }
618
643
  }
619
644
  });
620
645
  elements.openProject.addEventListener("click", async (event) => {
@@ -112,7 +112,7 @@
112
112
  </picture>
113
113
  <span>Open task</span>
114
114
  </button>
115
- <button id="copy-thread-id" class="secondary-button" type="button">Copy thread ID</button>
115
+ <button id="copy-task-id" class="secondary-button" type="button" aria-label="Copy Task ID">Copy Task ID</button>
116
116
  </div>
117
117
  <nav id="dialog-related-links" class="github-links" aria-label="Related GitHub links" hidden></nav>
118
118
  <section>