taskchef 8.3.0 → 8.3.1

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,174 +1,83 @@
1
1
  import { realpath } from "node:fs/promises";
2
2
 
3
3
  import { createDashboardServer } from "./dashboard.js";
4
- import {
5
- dashboardHandoffMetadata,
6
- dashboardOwnerMetadata,
7
- validDashboardControlSecret,
8
- writeDashboardHandoff,
9
- writeDashboardOwner,
10
- } from "./dashboard-ownership.js";
11
- import {
12
- MAX_DASHBOARD_SESSION_PIDS,
13
- createDashboardSessionLease,
14
- validSessionPid,
15
- } from "./dashboard-session.js";
4
+ import { createDashboardSessionLease, validSessionPid } from "./dashboard-session.js";
16
5
  import { DASHBOARD_SERVER_VERSION, TASKCHEF_VERSION } from "./version.js";
17
6
 
18
7
  export async function runDashboardSessionProcess({
19
8
  workspace = process.env.TASKCHEF_DASHBOARD_WORKSPACE,
20
9
  host = process.env.TASKCHEF_DASHBOARD_HOST ?? "127.0.0.1",
21
10
  port = Number(process.env.TASKCHEF_DASHBOARD_PORT ?? 3210),
22
- secret = process.env.TASKCHEF_DASHBOARD_SECRET,
23
11
  sessionPid = Number(process.env.TASKCHEF_DASHBOARD_SESSION_PID),
24
- sessionPids,
25
12
  taskchefVersion = TASKCHEF_VERSION,
26
13
  serverVersion = DASHBOARD_SERVER_VERSION,
27
14
  createServer = createDashboardServer,
28
- writeOwner = writeDashboardOwner,
29
- writeHandoff = writeDashboardHandoff,
30
15
  createLease = createDashboardSessionLease,
31
16
  processObject = process,
32
17
  signal,
33
18
  checkIntervalMs = Number(process.env.TASKCHEF_DASHBOARD_CHECK_INTERVAL_MS ?? 1_000),
34
19
  exitGraceMs = Number(process.env.TASKCHEF_DASHBOARD_EXIT_GRACE_MS ?? 15_000),
35
20
  } = {}) {
36
- if (processObject.env) delete processObject.env.TASKCHEF_DASHBOARD_SECRET;
37
- sessionPids ??= JSON.parse(process.env.TASKCHEF_DASHBOARD_SESSION_PIDS ?? "[]");
38
- if (!workspace) throw new Error("dashboard session workspace is required");
39
- if (!validDashboardControlSecret(secret)) {
40
- throw new Error("dashboard session control credential is invalid");
21
+ if (processObject.env) {
22
+ delete processObject.env.TASKCHEF_DASHBOARD_SECRET;
23
+ delete processObject.env.TASKCHEF_DASHBOARD_SESSION_PIDS;
41
24
  }
25
+ if (!workspace) throw new Error("dashboard session workspace is required");
42
26
  if (!validSessionPid(sessionPid)) throw new Error("dashboard session PID is invalid");
43
- if (!Array.isArray(sessionPids) || sessionPids.length > MAX_DASHBOARD_SESSION_PIDS
44
- || sessionPids.some((pid) => !validSessionPid(pid))
45
- || new Set([sessionPid, ...sessionPids]).size > MAX_DASHBOARD_SESSION_PIDS) {
46
- throw new Error("dashboard transferred session PIDs are invalid");
47
- }
48
27
 
49
28
  const canonicalWorkspace = await realpath(workspace);
50
29
  let server = null;
51
30
  let lease = null;
52
31
  let closePromise = null;
32
+ let closeChain = Promise.resolve();
33
+ let closedLease = null;
34
+ let closedServer = null;
53
35
  let closing = false;
54
- let publishingOwner = false;
55
- let deferredClose = null;
56
36
  const abortError = () => Object.assign(new Error("dashboard session startup was cancelled"), {
57
37
  code: "TASKCHEF_DASHBOARD_START_TIMEOUT",
58
38
  });
59
- const startupClosedError = () => Object.assign(
60
- new Error("dashboard session closed during startup"),
61
- { code: "TASKCHEF_DASHBOARD_START_EXIT" },
62
- );
63
39
  const removeListeners = () => {
64
40
  processObject.off("SIGINT", close);
65
41
  processObject.off("SIGTERM", close);
66
42
  };
67
- const performClose = async () => {
68
- let cleanupError = null;
69
- try {
70
- lease?.close();
71
- } catch (error) {
72
- cleanupError = error;
73
- }
74
- await server?.close();
75
- if (cleanupError) throw cleanupError;
76
- };
77
43
  const close = () => {
78
44
  closing = true;
79
- if (!closePromise && publishingOwner) {
80
- closePromise = new Promise((resolve, reject) => {
81
- deferredClose = { resolve, reject };
82
- }).finally(removeListeners);
83
- } else if (!closePromise) {
84
- closePromise = performClose().finally(removeListeners);
85
- }
45
+ closePromise = closeChain.then(async () => {
46
+ let failure = null;
47
+ if (lease && lease !== closedLease) {
48
+ closedLease = lease;
49
+ try { lease.close(); } catch (error) { failure = error; }
50
+ }
51
+ if (server && server !== closedServer) {
52
+ closedServer = server;
53
+ try { await server.close(); } catch (error) { failure ??= error; }
54
+ }
55
+ if (failure) throw failure;
56
+ }).finally(removeListeners);
57
+ closeChain = closePromise.catch(() => {});
86
58
  return closePromise;
87
59
  };
88
- const onAbort = () => {
89
- void close().catch(() => {});
90
- };
60
+ const onAbort = () => { void close().catch(() => {}); };
91
61
  if (signal?.aborted) throw abortError();
92
62
  signal?.addEventListener("abort", onAbort, { once: true });
93
63
  processObject.once("SIGINT", close);
94
64
  processObject.once("SIGTERM", close);
95
- const control = {
96
- secret,
97
- onShutdown: close,
98
- onSession: (pid) => {
99
- if (closing) {
100
- throw Object.assign(new Error("dashboard session is retiring"), {
101
- code: "TASKCHEF_DASHBOARD_SESSION_RETIRING",
102
- });
103
- }
104
- lease.register(pid);
105
- },
106
- onHandoff: (pid) => {
107
- if (closing) {
108
- throw Object.assign(new Error("dashboard session is retiring"), {
109
- code: "TASKCHEF_DASHBOARD_SESSION_RETIRING",
110
- });
111
- }
112
- lease.register(pid);
113
- return lease.snapshot();
114
- },
115
- onHandoffFinalized: ({ id, pids }) => writeHandoff(
116
- canonicalWorkspace,
117
- dashboardHandoffMetadata({
118
- workspace: canonicalWorkspace,
119
- host,
120
- port: server.port,
121
- taskchefVersion,
122
- serverVersion,
123
- id,
124
- pids,
125
- secret,
126
- }),
127
- ),
128
- };
129
65
 
130
66
  try {
131
- server = await createServer({
132
- workspace: canonicalWorkspace,
133
- host,
134
- port,
135
- taskchefVersion,
136
- serverVersion,
137
- launcher: "session",
138
- control,
139
- });
140
- if (closing) {
141
- closePromise = null;
142
- if (signal?.aborted) throw abortError();
143
- throw startupClosedError();
144
- }
67
+ const control = {
68
+ onShutdown: close,
69
+ onSession: (pid) => {
70
+ if (closing) throw Object.assign(new Error("dashboard session is retiring"), {
71
+ code: "TASKCHEF_DASHBOARD_SESSION_RETIRING",
72
+ });
73
+ lease.register(pid);
74
+ },
75
+ };
76
+ server = await createServer({ workspace: canonicalWorkspace, host, port,
77
+ taskchefVersion, serverVersion, launcher: "session", control });
78
+ if (closing || signal?.aborted) throw abortError();
145
79
  lease = createLease({ initialPid: sessionPid, checkIntervalMs, exitGraceMs, onExpire: close });
146
- for (const pid of sessionPids) lease.register(pid);
147
- publishingOwner = true;
148
- try {
149
- await writeOwner(canonicalWorkspace, dashboardOwnerMetadata({
150
- workspace: canonicalWorkspace,
151
- host,
152
- port: server.port,
153
- taskchefVersion,
154
- serverVersion,
155
- launcher: "session",
156
- secret,
157
- }), { signal });
158
- } finally {
159
- publishingOwner = false;
160
- if (deferredClose) {
161
- const pending = deferredClose;
162
- deferredClose = null;
163
- void performClose().then(pending.resolve, pending.reject);
164
- }
165
- }
166
- if (closing) {
167
- await closePromise;
168
- if (signal?.aborted) throw abortError();
169
- throw startupClosedError();
170
- }
171
- if (signal?.aborted) throw abortError();
80
+ if (closing || signal?.aborted) throw abortError();
172
81
  signal?.removeEventListener("abort", onAbort);
173
82
  return { server, lease, close };
174
83
  } catch (error) {
@@ -1,7 +1,6 @@
1
1
  const DEFAULT_CHECK_INTERVAL_MS = 1_000;
2
2
  const DEFAULT_EXIT_GRACE_MS = 15_000;
3
3
  export const MAX_DASHBOARD_SESSION_PIDS = 64;
4
- export const MAX_TRANSFERRED_DASHBOARD_SESSION_PIDS = MAX_DASHBOARD_SESSION_PIDS - 1;
5
4
 
6
5
  export function processIsAlive(pid, processObject = process) {
7
6
  try {
@@ -77,10 +76,6 @@ export function createDashboardSessionLease({
77
76
  pids.add(pid);
78
77
  absentSince = null;
79
78
  },
80
- snapshot() {
81
- activePids();
82
- return [...pids];
83
- },
84
79
  tick,
85
80
  close() {
86
81
  if (timer) clearInterval(timer);
package/src/dashboard.js CHANGED
@@ -31,22 +31,11 @@ import { createUsageTracker } from "./usage-tracker.js";
31
31
  import { readCcusageRuntimeInfo, readUsageStore, usageStorePath } from "./usage.js";
32
32
  import { reportedWorkSummary } from "./reported-work.js";
33
33
  import {
34
- MAX_DASHBOARD_SESSION_PIDS,
35
- MAX_TRANSFERRED_DASHBOARD_SESSION_PIDS,
36
- validSessionPid,
37
- } from "./dashboard-session.js";
38
- import {
39
- DASHBOARD_CONTROL_CHALLENGE_PATH,
40
- DASHBOARD_CONTROL_HANDOFF_COMMIT_PATH,
41
34
  DASHBOARD_CONTROL_SESSION_PATH,
42
- DASHBOARD_CONTROL_HANDOFF_PATH,
43
35
  DASHBOARD_CONTROL_SHUTDOWN_PATH,
44
- createDashboardControlNonce,
45
- dashboardControlProof,
46
- validDashboardControlNonce,
47
- validDashboardControlSecret,
48
- verifyDashboardControlProof,
49
- } from "./dashboard-ownership.js";
36
+ exactDashboardIdentity,
37
+ validDashboardControlRequest,
38
+ } from "./dashboard-control.js";
50
39
 
51
40
  const TASKS_FILE_NAME = "tasks.jsonl";
52
41
  const STATIC_ROOT = fileURLToPath(new URL("./dashboard/", import.meta.url));
@@ -57,11 +46,6 @@ const DEFAULT_MAX_FILE_BYTES = 16 * 1024 * 1024;
57
46
  const DEFAULT_MAX_TASKS = 2_000;
58
47
  const DEFAULT_MAX_EVENT_CLIENTS = 16;
59
48
  const DEFAULT_CALCULATING_USAGE_STALE_MS = 120_000;
60
- const DASHBOARD_HANDOFF_PREPARE_TTL_MS = 5_000;
61
- const DASHBOARD_HANDOFF_COMMIT_GRACE_MS = 250;
62
- const DASHBOARD_HANDOFF_COMMIT_RETRY_MS = 1_000;
63
- const DASHBOARD_CONTROL_NONCE_LIMIT = 4_096;
64
- const DASHBOARD_CONTROL_NONCE_TTL_MS = 5 * 60_000;
65
49
  const MAX_MANUAL_TRANSITION_BODY_BYTES = 4 * 1024;
66
50
  export const DASHBOARD_HEALTH_PATH = "/api/health";
67
51
  export const DASHBOARD_HEALTH_MAX_BYTES = 8 * 1024;
@@ -792,33 +776,6 @@ function publicMonitorError() {
792
776
  };
793
777
  }
794
778
 
795
- export function createDashboardControlReplayCache({
796
- maximum = DASHBOARD_CONTROL_NONCE_LIMIT,
797
- ttlMs = DASHBOARD_CONTROL_NONCE_TTL_MS,
798
- now = Date.now,
799
- } = {}) {
800
- if (!Number.isSafeInteger(maximum) || maximum <= 0) {
801
- throw new Error("dashboard control replay limit must be a positive integer");
802
- }
803
- if (!Number.isFinite(ttlMs) || ttlMs <= 0 || typeof now !== "function") {
804
- throw new Error("dashboard control replay window is invalid");
805
- }
806
- const entries = new Map();
807
- return {
808
- accept(nonce) {
809
- const timestamp = now();
810
- for (const [candidate, expiresAt] of entries) {
811
- if (expiresAt > timestamp) break;
812
- entries.delete(candidate);
813
- }
814
- if (entries.has(nonce) || entries.size >= maximum) return false;
815
- entries.set(nonce, timestamp + ttlMs);
816
- return true;
817
- },
818
- get size() { return entries.size; },
819
- };
820
- }
821
-
822
779
  export async function createDashboardServer({
823
780
  archiveEnabled = CODEX_CHAT_ARCHIVE_ENABLED,
824
781
  archiveThread = archiveThreadInCodex,
@@ -840,7 +797,6 @@ export async function createDashboardServer({
840
797
  resolveUsageProvider = readCcusageRuntimeInfo,
841
798
  updateRole = updateModelRole,
842
799
  control = null,
843
- controlReplayCache = createDashboardControlReplayCache(),
844
800
  } = {}) {
845
801
  if (!LOOPBACK_HOSTS.has(host)) {
846
802
  throw new Error("dashboard host must be a loopback address");
@@ -858,11 +814,9 @@ export async function createDashboardServer({
858
814
  throw new Error("dashboard launcher must be mcp, session, or standalone");
859
815
  }
860
816
  if (control !== null && (!new Set(["mcp", "session"]).has(launcher)
861
- || !validDashboardControlSecret(control?.secret)
862
817
  || typeof control?.onShutdown !== "function"
863
- || (launcher === "session" && (typeof control?.onSession !== "function"
864
- || typeof control?.onHandoff !== "function")))) {
865
- throw new Error("dashboard control requires a valid TaskChef ownership controller");
818
+ || (launcher === "session" && typeof control?.onSession !== "function"))) {
819
+ throw new Error("dashboard control requires TaskChef session callbacks");
866
820
  }
867
821
  let requestHandler = (_request, response) => {
868
822
  sendJson(response, 503, { message: "TaskChef dashboard is initializing." });
@@ -908,8 +862,6 @@ export async function createDashboardServer({
908
862
  const clients = new Set();
909
863
  const archiveRequests = new Set();
910
864
  let usageProviderPromise = null;
911
- let controlHandoff = null;
912
- let controlHandoffTimer = null;
913
865
 
914
866
  const broadcast = (event, value) => {
915
867
  const payload = ssePayload(event, value);
@@ -970,37 +922,29 @@ export async function createDashboardServer({
970
922
  return;
971
923
  }
972
924
 
973
- if (url.pathname === DASHBOARD_CONTROL_CHALLENGE_PATH && method === "GET") {
974
- const nonce = url.searchParams.get("nonce");
975
- if (!control || !validDashboardControlNonce(nonce)) {
976
- sendJson(response, control ? 400 : 404, { message: "Not found." });
977
- return;
978
- }
979
- sendJson(response, 200, {
980
- schemaVersion: 1,
981
- nonce,
982
- proof: dashboardControlProof(control.secret, "challenge", nonce),
983
- });
984
- return;
985
- }
986
-
987
925
  if (url.pathname === DASHBOARD_CONTROL_SHUTDOWN_PATH && method === "POST") {
988
926
  if (!control) {
989
927
  sendJson(response, 404, { message: "Not found." });
990
928
  return;
991
929
  }
930
+ if (!validDashboardControlRequest(request, {
931
+ authority: allowedAuthority, origin: allowedOrigin, action: "shutdown",
932
+ })) {
933
+ sendJson(response, 403, { message: "Dashboard control request was refused." });
934
+ return;
935
+ }
992
936
  const body = await readBoundedJsonBody(request);
993
- const nonce = body?.nonce;
994
- if (!verifyDashboardControlProof(control.secret, "shutdown", nonce, body?.proof)) {
995
- sendJson(response, 403, { message: "Dashboard control authentication failed." });
937
+ if (!body || Object.keys(body).length !== 2 || body.reason !== "version-replacement"
938
+ || !("expectedIdentity" in body)) {
939
+ sendJson(response, 400, { message: "Dashboard shutdown request was malformed." });
996
940
  return;
997
941
  }
998
- if (!controlReplayCache.accept(nonce)) {
999
- sendJson(response, 409, { message: "Dashboard control credential was already used." });
942
+ if (!exactDashboardIdentity(identity, body.expectedIdentity)) {
943
+ sendJson(response, 409, { schemaVersion: 1, accepted: false, reason: "identity-mismatch" });
1000
944
  return;
1001
945
  }
1002
946
  sendJson(response, 202, { schemaVersion: 1, accepted: true });
1003
- setImmediate(() => { void control.onShutdown().catch(() => {}); });
947
+ setImmediate(() => { void Promise.resolve(control.onShutdown()).catch(() => {}); });
1004
948
  return;
1005
949
  }
1006
950
 
@@ -1009,25 +953,21 @@ export async function createDashboardServer({
1009
953
  sendJson(response, 404, { message: "Not found." });
1010
954
  return;
1011
955
  }
1012
- const body = await readBoundedJsonBody(request);
1013
- const nonce = body?.nonce;
1014
- const pid = body?.pid;
1015
- if (!Number.isSafeInteger(pid) || pid <= 1
1016
- || !verifyDashboardControlProof(
1017
- control.secret, `session:${pid}`, nonce, body?.proof,
1018
- )) {
1019
- sendJson(response, 403, { message: "Dashboard control authentication failed." });
956
+ if (!validDashboardControlRequest(request, {
957
+ authority: allowedAuthority, origin: allowedOrigin, action: "session",
958
+ })) {
959
+ sendJson(response, 403, { message: "Dashboard control request was refused." });
1020
960
  return;
1021
961
  }
1022
- if (controlHandoff) {
1023
- sendJson(response, 409, {
1024
- message: "Dashboard handoff is already in progress.",
1025
- reason: "retiring",
1026
- });
962
+ const body = await readBoundedJsonBody(request);
963
+ const pid = body?.pid;
964
+ if (!body || Object.keys(body).length !== 2 || !Number.isSafeInteger(pid) || pid <= 1
965
+ || !("expectedIdentity" in body)) {
966
+ sendJson(response, 400, { message: "Dashboard session request was malformed." });
1027
967
  return;
1028
968
  }
1029
- if (!controlReplayCache.accept(nonce)) {
1030
- sendJson(response, 409, { message: "Dashboard control credential was already used." });
969
+ if (!exactDashboardIdentity(identity, body.expectedIdentity)) {
970
+ sendJson(response, 409, { schemaVersion: 1, accepted: false, reason: "identity-mismatch" });
1031
971
  return;
1032
972
  }
1033
973
  try {
@@ -1035,8 +975,6 @@ export async function createDashboardServer({
1035
975
  sendJson(response, 200, {
1036
976
  schemaVersion: 1,
1037
977
  accepted: true,
1038
- nonce,
1039
- proof: dashboardControlProof(control.secret, `session-accepted:${pid}`, nonce),
1040
978
  });
1041
979
  } catch (error) {
1042
980
  sendJson(response, 409, {
@@ -1049,197 +987,6 @@ export async function createDashboardServer({
1049
987
  return;
1050
988
  }
1051
989
 
1052
- if (url.pathname === DASHBOARD_CONTROL_HANDOFF_PATH && method === "POST") {
1053
- if (!control || typeof control.onHandoff !== "function") {
1054
- sendJson(response, 404, { message: "Not found." });
1055
- return;
1056
- }
1057
- const body = await readBoundedJsonBody(request);
1058
- const nonce = body?.nonce;
1059
- const pid = body?.pid;
1060
- if (!Number.isSafeInteger(pid) || pid <= 1
1061
- || !verifyDashboardControlProof(
1062
- control.secret, `handoff:${pid}`, nonce, body?.proof,
1063
- )) {
1064
- sendJson(response, 403, { message: "Dashboard control authentication failed." });
1065
- return;
1066
- }
1067
- if (!controlReplayCache.accept(nonce)) {
1068
- sendJson(response, 409, { message: "Dashboard control credential was already used." });
1069
- return;
1070
- }
1071
- if (controlHandoff) {
1072
- if (controlHandoff.pending || controlHandoff.finalizing || controlHandoff.finalized) {
1073
- sendJson(response, 409, {
1074
- message: "Dashboard handoff is already in progress.",
1075
- reason: "retiring",
1076
- });
1077
- return;
1078
- }
1079
- try {
1080
- const pids = controlHandoff.participants.has(pid)
1081
- ? controlHandoff.pids
1082
- : await control.onHandoff(pid);
1083
- if (!Array.isArray(pids) || pids.length > MAX_DASHBOARD_SESSION_PIDS
1084
- || pids.some((value) => !validSessionPid(value))
1085
- || new Set(pids).size !== pids.length
1086
- || !pids.includes(pid)) {
1087
- throw new Error("dashboard handoff returned invalid session leases");
1088
- }
1089
- controlHandoff.pids = pids;
1090
- controlHandoff.participants.add(pid);
1091
- const { id } = controlHandoff;
1092
- sendJson(response, 200, {
1093
- schemaVersion: 1,
1094
- accepted: true,
1095
- id,
1096
- pids,
1097
- nonce,
1098
- proof: dashboardControlProof(
1099
- control.secret,
1100
- `handoff-prepared:${pid}:${id}:${JSON.stringify(pids)}`,
1101
- nonce,
1102
- ),
1103
- });
1104
- } catch {
1105
- sendJson(response, 409, {
1106
- message: "Dashboard handoff was refused.",
1107
- reason: "refused",
1108
- });
1109
- }
1110
- return;
1111
- }
1112
- controlHandoff = { pid, pending: true, committed: false };
1113
- try {
1114
- const pids = await control.onHandoff(pid);
1115
- if (!Array.isArray(pids) || pids.length > MAX_DASHBOARD_SESSION_PIDS
1116
- || pids.some((value) => !validSessionPid(value))
1117
- || new Set(pids).size !== pids.length
1118
- || !pids.includes(pid)) {
1119
- throw new Error("dashboard handoff returned invalid session leases");
1120
- }
1121
- const id = createDashboardControlNonce();
1122
- controlHandoff = {
1123
- id,
1124
- pid,
1125
- pids,
1126
- participants: new Set([pid]),
1127
- pending: false,
1128
- committed: false,
1129
- };
1130
- controlHandoffTimer = setTimeout(() => {
1131
- if (controlHandoff?.id === id && !controlHandoff.committed) controlHandoff = null;
1132
- controlHandoffTimer = null;
1133
- }, DASHBOARD_HANDOFF_PREPARE_TTL_MS);
1134
- controlHandoffTimer.unref?.();
1135
- sendJson(response, 200, {
1136
- schemaVersion: 1,
1137
- accepted: true,
1138
- id,
1139
- pids,
1140
- nonce,
1141
- proof: dashboardControlProof(
1142
- control.secret,
1143
- `handoff-prepared:${pid}:${id}:${JSON.stringify(pids)}`,
1144
- nonce,
1145
- ),
1146
- });
1147
- } catch {
1148
- if (controlHandoff?.pid === pid && controlHandoff.pending) controlHandoff = null;
1149
- sendJson(response, 409, {
1150
- message: "Dashboard handoff was refused.",
1151
- reason: "refused",
1152
- });
1153
- }
1154
- return;
1155
- }
1156
-
1157
- if (url.pathname === DASHBOARD_CONTROL_HANDOFF_COMMIT_PATH && method === "POST") {
1158
- if (!control || typeof control.onHandoff !== "function") {
1159
- sendJson(response, 404, { message: "Not found." });
1160
- return;
1161
- }
1162
- const body = await readBoundedJsonBody(request);
1163
- const nonce = body?.nonce;
1164
- const id = body?.id;
1165
- if (!validDashboardControlNonce(id)
1166
- || !verifyDashboardControlProof(
1167
- control.secret, `handoff-commit:${id}`, nonce, body?.proof,
1168
- )) {
1169
- sendJson(response, 403, { message: "Dashboard control authentication failed." });
1170
- return;
1171
- }
1172
- if (!controlReplayCache.accept(nonce)) {
1173
- sendJson(response, 409, { message: "Dashboard control credential was already used." });
1174
- return;
1175
- }
1176
- if (!controlHandoff || controlHandoff.id !== id) {
1177
- sendJson(response, 409, {
1178
- message: "Dashboard handoff preparation expired or did not match.",
1179
- reason: "refused",
1180
- });
1181
- return;
1182
- }
1183
- if (!controlHandoff.commitPromise) {
1184
- controlHandoff.committed = true;
1185
- if (controlHandoffTimer) clearTimeout(controlHandoffTimer);
1186
- controlHandoffTimer = null;
1187
- controlHandoff.commitPromise = new Promise((resolve, reject) => {
1188
- const finalizationTimer = setTimeout(async () => {
1189
- try {
1190
- controlHandoff.finalizing = true;
1191
- const pids = await control.onHandoff(controlHandoff.pid);
1192
- if (!Array.isArray(pids)
1193
- || pids.length > MAX_TRANSFERRED_DASHBOARD_SESSION_PIDS
1194
- || pids.some((value) => !validSessionPid(value))
1195
- || new Set(pids).size !== pids.length
1196
- || !pids.includes(controlHandoff.pid)) {
1197
- throw new Error("dashboard handoff returned invalid final session leases");
1198
- }
1199
- controlHandoff.pids = pids;
1200
- await control.onHandoffFinalized?.({ id, pids });
1201
- controlHandoff.finalized = true;
1202
- resolve(pids);
1203
- } catch (error) {
1204
- reject(error);
1205
- }
1206
- }, DASHBOARD_HANDOFF_COMMIT_GRACE_MS);
1207
- finalizationTimer.unref?.();
1208
- });
1209
- }
1210
- let pids;
1211
- try {
1212
- pids = await controlHandoff.commitPromise;
1213
- } catch {
1214
- if (controlHandoff?.id === id) controlHandoff = null;
1215
- sendJson(response, 409, {
1216
- message: "Dashboard handoff finalization was refused.",
1217
- reason: "refused",
1218
- });
1219
- return;
1220
- }
1221
- sendJson(response, 202, {
1222
- schemaVersion: 1,
1223
- accepted: true,
1224
- id,
1225
- pids,
1226
- nonce,
1227
- proof: dashboardControlProof(
1228
- control.secret,
1229
- `handoff-committed:${id}:${JSON.stringify(pids)}`,
1230
- nonce,
1231
- ),
1232
- });
1233
- if (!controlHandoff.shutdownScheduled) {
1234
- controlHandoff.shutdownScheduled = true;
1235
- const shutdownTimer = setTimeout(() => {
1236
- void control.onShutdown().catch(() => {});
1237
- }, DASHBOARD_HANDOFF_COMMIT_RETRY_MS);
1238
- shutdownTimer.unref?.();
1239
- }
1240
- return;
1241
- }
1242
-
1243
990
  if (url.pathname === "/api/settings" && method === "GET") {
1244
991
  const profiles = [{ id: "personal", project: "Personal", ...await resolveRoles({ includeCatalog: true }) }];
1245
992
  usageProviderPromise ??= Promise.resolve(resolveUsageProvider()).catch(() => ({
package/src/mcp.js CHANGED
@@ -109,7 +109,7 @@ const preparationSchema = z.object({
109
109
 
110
110
  const dashboardSchema = z.object({
111
111
  action: z.enum(["started", "reused"]),
112
- launcher: z.literal("session"),
112
+ launcher: z.enum(["session", "standalone", "mcp"]),
113
113
  url: z.string().url(),
114
114
  workspace: z.string(),
115
115
  taskchefVersion: z.string(),
@@ -126,7 +126,7 @@ function toolResult(key, value, message) {
126
126
  function dashboardAutostartDiagnostic(error) {
127
127
  const message = typeof error?.message === "string" ? error.message : "";
128
128
  if (typeof error?.staleTaskchefVersion === "string") {
129
- return `TaskChef dashboard autostart skipped: verified older TaskChef ${error.staleTaskchefVersion} listener could not complete authenticated handoff; it was left untouched.`;
129
+ return `TaskChef dashboard autostart skipped: recognized older TaskChef ${error.staleTaskchefVersion} listener refused graceful shutdown; it was left running.`;
130
130
  }
131
131
  if (error?.code === "EADDRINUSE" || /port conflict|already in use/i.test(message)) {
132
132
  return "TaskChef dashboard autostart skipped: port 127.0.0.1:3210 is unavailable; the listener was left untouched.";
@@ -204,7 +204,7 @@ export function createTaskChefMcpServer({
204
204
  {
205
205
  title: "Ensure TaskChef dashboard",
206
206
  description:
207
- "Best-effort ensure the canonical TaskChef dashboard is available on 127.0.0.1:3210. Starts or reuses the authenticated TaskChef dashboard for this Codex session and canonical workspace; verified older TaskChef session listeners are handed off to the installed version, while standalone and unknown listeners are never terminated or replaced.",
207
+ "Best-effort ensure the canonical TaskChef dashboard is available on 127.0.0.1:3210. Starts or reuses the exact recognized TaskChef dashboard for this Codex session and canonical workspace; recognized older same-workspace listeners are gracefully restarted to the installed version, while newer and unknown listeners remain running.",
208
208
  inputSchema: {},
209
209
  outputSchema: { dashboard: dashboardSchema },
210
210
  annotations: {