@marcoscale98/pi-extensible-workflows 5.19.1-fork.1 → 6.2.1-fork.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +365 -127
  5. package/dist/src/background-widget.d.ts +0 -1
  6. package/dist/src/background-widget.js +4 -9
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +33 -37
  9. package/dist/src/execution.d.ts +2 -0
  10. package/dist/src/execution.js +5 -5
  11. package/dist/src/host-delivery.js +4 -8
  12. package/dist/src/host-navigator.js +5 -2
  13. package/dist/src/host-phases.d.ts +1 -0
  14. package/dist/src/host-recovery.d.ts +6 -6
  15. package/dist/src/host-recovery.js +14 -22
  16. package/dist/src/host-runtime.js +56 -5
  17. package/dist/src/host-view.d.ts +1 -1
  18. package/dist/src/host-view.js +17 -15
  19. package/dist/src/host.js +62 -87
  20. package/dist/src/index.d.ts +1 -2
  21. package/dist/src/index.js +1523 -1630
  22. package/dist/src/index.js.map +3 -3
  23. package/dist/src/paths.d.ts +7 -6
  24. package/dist/src/paths.js +24 -15
  25. package/dist/src/persistence.d.ts +1 -0
  26. package/dist/src/persistence.js +1 -0
  27. package/dist/src/pi-runtime-runner.js +4 -1
  28. package/dist/src/registry.d.ts +9 -7
  29. package/dist/src/registry.js +28 -70
  30. package/dist/src/runtime/agent-runner.d.ts +2 -0
  31. package/dist/src/session-lease.d.ts +1 -1
  32. package/dist/src/session-lease.js +45 -11
  33. package/dist/src/settings.d.ts +6 -3
  34. package/dist/src/settings.js +36 -47
  35. package/dist/src/store.d.ts +3 -1
  36. package/dist/src/store.js +17 -1
  37. package/dist/src/trajectory.d.ts +0 -1
  38. package/dist/src/trajectory.js +2 -3
  39. package/dist/src/types.d.ts +100 -44
  40. package/dist/src/types.js +4 -7
  41. package/dist/src/utils.d.ts +3 -2
  42. package/dist/src/utils.js +20 -10
  43. package/dist/src/validation.d.ts +8 -2
  44. package/dist/src/validation.js +122 -86
  45. package/dist/starter/index.js +8 -9
  46. package/dist/starter/index.js.map +1 -1
  47. package/dist/starter/review-loop.js +8 -7
  48. package/dist/subagents/index.js +654 -814
  49. package/dist/subagents/index.js.map +3 -3
  50. package/dist/subagents/src/contracts.d.ts +4 -3
  51. package/dist/subagents/src/contracts.js +2 -2
  52. package/dist/subagents/src/index.d.ts +1 -1
  53. package/dist/subagents/src/index.js +1 -2
  54. package/dist/subagents/src/manager.js +49 -70
  55. package/dist/subagents/src/navigator.js +3 -14
  56. package/dist/subagents/src/view.d.ts +0 -1
  57. package/dist/subagents/src/view.js +2 -16
  58. package/dist/subagents/src/worktree.js +1 -1
  59. package/dist/trajectory/assets/index.html +8 -8
  60. package/dist/trajectory/index.js +487 -891
  61. package/dist/trajectory/index.js.map +3 -3
  62. package/dist/trajectory/src/assets/index.html +8 -8
  63. package/dist/trajectory/src/index.js +126 -86
  64. package/dist/trajectory/src/server.js +264 -25
  65. package/dist/trajectory/src/server.js.map +3 -3
  66. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  67. package/dist/trajectory/src/startup-mutex.js +172 -0
  68. package/examples/workflow-extension-template/README.md +4 -7
  69. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  70. package/examples/workflow-extension-template/index.js +0 -3
  71. package/package.json +5 -5
  72. package/skills/pi-extensible-workflows/SKILL.md +10 -8
  73. package/src/agent-execution.ts +339 -127
  74. package/src/background-widget.ts +4 -10
  75. package/src/decoders.ts +31 -37
  76. package/src/execution.ts +5 -5
  77. package/src/host-delivery.ts +4 -8
  78. package/src/host-navigator.ts +4 -2
  79. package/src/host-phases.ts +1 -1
  80. package/src/host-recovery.ts +20 -26
  81. package/src/host-runtime.ts +44 -6
  82. package/src/host-view.ts +18 -11
  83. package/src/host.ts +62 -113
  84. package/src/index.ts +1 -2
  85. package/src/paths.ts +18 -12
  86. package/src/persistence.ts +1 -0
  87. package/src/pi-runtime-runner.ts +3 -1
  88. package/src/registry.ts +27 -63
  89. package/src/runtime/agent-runner.ts +2 -0
  90. package/src/session-lease.ts +34 -6
  91. package/src/settings.ts +35 -47
  92. package/src/store.ts +18 -2
  93. package/src/trajectory.ts +2 -4
  94. package/src/types.ts +71 -30
  95. package/src/utils.ts +18 -9
  96. package/src/validation.ts +102 -79
  97. package/starter/index.ts +0 -1
  98. package/starter/review-loop.ts +8 -7
  99. package/starter/roles/developer.md +0 -1
  100. package/starter/roles/oracle.md +0 -1
  101. package/starter/roles/researcher.md +0 -1
  102. package/starter/roles/reviewer.md +0 -1
  103. package/starter/roles/scout.md +0 -1
  104. package/subagents/README.md +8 -14
  105. package/subagents/src/contracts.ts +3 -4
  106. package/subagents/src/index.ts +2 -3
  107. package/subagents/src/manager.ts +45 -125
  108. package/subagents/src/navigator.ts +4 -13
  109. package/subagents/src/view.ts +4 -14
  110. package/subagents/src/worktree.ts +1 -1
  111. package/trajectory/src/assets/index.html +8 -8
  112. package/trajectory/src/index.ts +106 -82
  113. package/trajectory/src/server.ts +44 -8
  114. package/trajectory/src/startup-mutex.ts +127 -0
  115. package/dist/src/roles.d.ts +0 -70
  116. package/dist/src/roles.js +0 -326
  117. package/dist/starter/roles/developer.md +0 -6
  118. package/dist/starter/roles/oracle.md +0 -18
  119. package/dist/starter/roles/researcher.md +0 -20
  120. package/dist/starter/roles/reviewer.md +0 -19
  121. package/dist/starter/roles/scout.md +0 -18
  122. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  123. package/src/roles.ts +0 -327
@@ -1,7 +1,7 @@
1
1
  // trajectory/src/server.ts
2
- import { createHash, randomUUID } from "node:crypto";
2
+ import { createHash, randomUUID as randomUUID3 } from "node:crypto";
3
3
  import { createServer } from "node:http";
4
- import { readFile, rm, writeFile } from "node:fs/promises";
4
+ import { readFile as readFile3, rm as rm3 } from "node:fs/promises";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { URL } from "node:url";
7
7
 
@@ -18,9 +18,12 @@ function trajectoryActionError(action, target) {
18
18
  return target.kind === "subagent" && (action === "checkpoint-approve" || action === "checkpoint-reject" || action === "pause" || action === "resume" || action === "share") ? `Trajectory action ${action} is not supported for subagent targets` : target.kind === "run" && action === "steer" ? "Trajectory action steer is not supported for run targets" : void 0;
19
19
  }
20
20
 
21
- // src/paths.ts
22
- import { realpathSync } from "node:fs";
23
- import { basename, join, resolve } from "node:path";
21
+ // src/io.ts
22
+ import { execFile } from "node:child_process";
23
+ import { randomUUID } from "node:crypto";
24
+ import { renameSync, rmSync, writeFileSync } from "node:fs";
25
+ import { readFile, rename, rm, writeFile } from "node:fs/promises";
26
+ import { promisify } from "node:util";
24
27
 
25
28
  // src/types.ts
26
29
  var WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1e3;
@@ -1896,12 +1899,78 @@ minimatch.escape = escape;
1896
1899
  minimatch.unescape = unescape;
1897
1900
 
1898
1901
  // src/utils.ts
1902
+ function object(value) {
1903
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1904
+ }
1899
1905
  var ESCAPE_CHARACTER = String.fromCharCode(27);
1900
1906
  var BELL_CHARACTER = String.fromCharCode(7);
1901
1907
  var ANSI_ESCAPE_SEQUENCE = new RegExp(`${ESCAPE_CHARACTER}(?:\\[[0-?]*[ -/]*[@-~]|\\][^${BELL_CHARACTER}]*(?:${BELL_CHARACTER}|${ESCAPE_CHARACTER}\\\\))`, "g");
1902
1908
  var TERMINAL_CONTROL_CHARACTER = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}-${String.fromCharCode(159)}]`, "g");
1909
+ function positiveInteger(value) {
1910
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
1911
+ }
1912
+ function isNodeError(error, code) {
1913
+ return object(error) && error.code === code;
1914
+ }
1915
+
1916
+ // src/io.ts
1917
+ var execute = promisify(execFile);
1918
+ var RENAME_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 400, 800];
1919
+ var syncSleepBuffer = new Int32Array(new SharedArrayBuffer(4));
1920
+ function retryableRenameError(error) {
1921
+ return isNodeError(error, "EPERM") || isNodeError(error, "EBUSY") || isNodeError(error, "EACCES");
1922
+ }
1923
+ async function renameWithRetry(from, to) {
1924
+ for (let attempt = 0; ; attempt += 1) {
1925
+ try {
1926
+ await rename(from, to);
1927
+ return;
1928
+ } catch (error) {
1929
+ const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
1930
+ if (!retryableRenameError(error) || delayMs === void 0) throw error;
1931
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
1932
+ }
1933
+ }
1934
+ }
1935
+ function renameSyncWithRetry(from, to) {
1936
+ for (let attempt = 0; ; attempt += 1) {
1937
+ try {
1938
+ renameSync(from, to);
1939
+ return;
1940
+ } catch (error) {
1941
+ const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
1942
+ if (!retryableRenameError(error) || delayMs === void 0) throw error;
1943
+ Atomics.wait(syncSleepBuffer, 0, 0, delayMs);
1944
+ }
1945
+ }
1946
+ }
1947
+ function atomicWriteFile(path2, content, sync = false) {
1948
+ const temporary = `${path2}.${String(process.pid)}.${randomUUID()}.tmp`;
1949
+ if (sync) {
1950
+ try {
1951
+ writeFileSync(temporary, content, { encoding: "utf8", mode: 384 });
1952
+ renameSyncWithRetry(temporary, path2);
1953
+ } catch (error) {
1954
+ try {
1955
+ rmSync(temporary, { force: true });
1956
+ } catch {
1957
+ }
1958
+ throw error;
1959
+ }
1960
+ return;
1961
+ }
1962
+ return writeFile(temporary, content, { encoding: "utf8", mode: 384 }).then(() => renameWithRetry(temporary, path2)).catch(async (error) => {
1963
+ try {
1964
+ await rm(temporary, { force: true });
1965
+ } catch {
1966
+ }
1967
+ throw error;
1968
+ });
1969
+ }
1903
1970
 
1904
1971
  // src/paths.ts
1972
+ import { realpathSync } from "node:fs";
1973
+ import { basename, isAbsolute, join, relative, resolve, sep as sep2 } from "node:path";
1905
1974
  function canonicalPath(path2) {
1906
1975
  const absolute = resolve(path2);
1907
1976
  try {
@@ -1914,6 +1983,137 @@ function sameFilesystemPath(left, right) {
1914
1983
  return canonicalPath(left) === canonicalPath(right);
1915
1984
  }
1916
1985
 
1986
+ // trajectory/src/startup-mutex.ts
1987
+ import { randomUUID as randomUUID2 } from "node:crypto";
1988
+ import { readFileSync } from "node:fs";
1989
+ import { mkdir, readdir, readFile as readFile2, rm as rm2 } from "node:fs/promises";
1990
+ import { join as join2 } from "node:path";
1991
+ var bootId;
1992
+ function processStart(pid) {
1993
+ if (process.platform !== "linux") return void 0;
1994
+ try {
1995
+ bootId ??= readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
1996
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
1997
+ const startTicks = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19];
1998
+ return startTicks === void 0 || !/^\d+$/.test(startTicks) ? void 0 : `${bootId}:${startTicks}`;
1999
+ } catch {
2000
+ return void 0;
2001
+ }
2002
+ }
2003
+ function processLive(identity) {
2004
+ try {
2005
+ process.kill(identity.pid, 0);
2006
+ } catch (error) {
2007
+ if (isNodeError(error, "ESRCH")) return false;
2008
+ }
2009
+ if (identity.start === void 0) return true;
2010
+ const current = processStart(identity.pid);
2011
+ return current === void 0 || current === identity.start;
2012
+ }
2013
+ function processConfirmed(identity) {
2014
+ return identity.start !== void 0 && processStart(identity.pid) === identity.start;
2015
+ }
2016
+ function holderDirectory(lockPath) {
2017
+ return `${lockPath}.startup`;
2018
+ }
2019
+ function holderPath(lockPath, token) {
2020
+ return join2(holderDirectory(lockPath), `${token}.json`);
2021
+ }
2022
+ function identityOf(pid) {
2023
+ const start = processStart(pid);
2024
+ return start === void 0 ? { pid } : { pid, start };
2025
+ }
2026
+ function processIdentity(value) {
2027
+ if (!object(value) || !positiveInteger(value.pid)) return void 0;
2028
+ return typeof value.start === "string" ? { pid: value.pid, start: value.start } : { pid: value.pid };
2029
+ }
2030
+ function decodeHolder(text) {
2031
+ try {
2032
+ const parsed = JSON.parse(text);
2033
+ const holder = processIdentity(parsed);
2034
+ if (holder === void 0 || !object(parsed) || typeof parsed.token !== "string") return void 0;
2035
+ const server = processIdentity(parsed.server);
2036
+ return { ...holder, token: parsed.token, ...server === void 0 ? {} : { server } };
2037
+ } catch {
2038
+ return void 0;
2039
+ }
2040
+ }
2041
+ function readStartupHolder(lockPath, token) {
2042
+ try {
2043
+ return decodeHolder(readFileSync(holderPath(lockPath, token), "utf8"));
2044
+ } catch (error) {
2045
+ if (isNodeError(error, "ENOENT")) return void 0;
2046
+ throw error;
2047
+ }
2048
+ }
2049
+ async function serverPending(lockPath, server) {
2050
+ if (!processLive(server)) return false;
2051
+ let text;
2052
+ try {
2053
+ text = await readFile2(lockPath, "utf8");
2054
+ } catch (error) {
2055
+ if (isNodeError(error, "ENOENT")) return true;
2056
+ throw error;
2057
+ }
2058
+ try {
2059
+ const lock = JSON.parse(text);
2060
+ return !(object(lock) && lock.pid === server.pid);
2061
+ } catch {
2062
+ return true;
2063
+ }
2064
+ }
2065
+ async function holderLive(lockPath, path2, read) {
2066
+ if (processLive(read)) return true;
2067
+ let holder;
2068
+ try {
2069
+ holder = decodeHolder(await readFile2(path2, "utf8"));
2070
+ } catch (error) {
2071
+ if (isNodeError(error, "ENOENT")) return false;
2072
+ throw error;
2073
+ }
2074
+ if (holder?.server === void 0 || !await serverPending(lockPath, holder.server)) return false;
2075
+ if (!processConfirmed(holder.server)) return true;
2076
+ try {
2077
+ process.kill(holder.server.pid, "SIGKILL");
2078
+ } catch (error) {
2079
+ if (!isNodeError(error, "ESRCH")) throw error;
2080
+ }
2081
+ return true;
2082
+ }
2083
+ async function tryAcquireStartupMutex(lockPath) {
2084
+ const directory = holderDirectory(lockPath);
2085
+ await mkdir(directory, { recursive: true, mode: 448 });
2086
+ const token = randomUUID2();
2087
+ const own = `${token}.json`;
2088
+ await atomicWriteFile(join2(directory, own), `${JSON.stringify({ ...identityOf(process.pid), token })}
2089
+ `);
2090
+ let contended = false;
2091
+ try {
2092
+ for (const name of await readdir(directory)) {
2093
+ if (name === own || !name.endsWith(".json")) continue;
2094
+ const path2 = join2(directory, name);
2095
+ let holder;
2096
+ try {
2097
+ holder = decodeHolder(await readFile2(path2, "utf8"));
2098
+ } catch (error) {
2099
+ if (isNodeError(error, "ENOENT")) continue;
2100
+ throw error;
2101
+ }
2102
+ if (holder !== void 0 && await holderLive(lockPath, path2, holder)) contended = true;
2103
+ else await rm2(path2, { force: true });
2104
+ }
2105
+ } catch (error) {
2106
+ await rm2(join2(directory, own), { force: true });
2107
+ throw error;
2108
+ }
2109
+ if (!contended) return token;
2110
+ await rm2(join2(directory, own), { force: true });
2111
+ return void 0;
2112
+ }
2113
+ async function releaseStartupMutex(lockPath, token) {
2114
+ await rm2(holderPath(lockPath, token), { force: true });
2115
+ }
2116
+
1917
2117
  // src/tool-timing.ts
1918
2118
  var TOOL_TIMING_ENTRY_TYPE = "pi-workflows:tool-timing";
1919
2119
 
@@ -2233,7 +2433,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2233
2433
  return requests;
2234
2434
  };
2235
2435
  const hasPendingRequest = (requests, requestId) => [...requests.values()].some((request) => request.requestId === requestId);
2236
- const publisherRequestIdFor = (requestId) => [...pending.values()].some((requests) => requests.has(requestId)) ? randomUUID() : requestId;
2436
+ const publisherRequestIdFor = (requestId) => [...pending.values()].some((requests) => requests.has(requestId)) ? randomUUID3() : requestId;
2237
2437
  const errorResponse = (_publisherRequestId, request, status, error) => request.kind === "action" ? { type: "action-result", requestId: request.requestId, ok: false, error } : { type: "transcript", requestId: request.requestId, publisherId: request.publisherId, ...request.target.runId === void 0 ? {} : { runId: request.target.runId }, ...request.target.agentId === void 0 ? {} : { agentId: request.target.agentId }, ...request.target.subagentId === void 0 ? {} : { subagentId: request.target.subagentId }, ok: false, status, revision: request.target.revision ?? 0, error };
2238
2438
  const clearPending = (client, error) => {
2239
2439
  const own = pending.get(client);
@@ -2262,21 +2462,45 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2262
2462
  if (!requests.size) pending.delete(browser);
2263
2463
  emit(browser, errorResponse(requestId, request, "failed", error));
2264
2464
  };
2465
+ const busy = () => closed || [...publishers.values()].some(({ value }) => value.connected === true);
2265
2466
  const scheduleIdleExit = () => {
2266
- if (closed || [...publishers.values()].some(({ value }) => value.connected === true) || idleTimer !== void 0) return;
2467
+ if (busy() || idleTimer !== void 0) return;
2267
2468
  idleTimer = setTimeout(() => {
2268
- server.closeAllConnections();
2269
- for (const client of clients) client.socket.destroy();
2270
- server.close(() => {
2271
- void rm(lockPath, { force: true }).then(() => {
2272
- process.exit(0);
2273
- }).catch(() => {
2274
- process.exit(1);
2469
+ idleTimer = void 0;
2470
+ void tryAcquireStartupMutex(lockPath).then(async (token) => {
2471
+ if (token === void 0) {
2472
+ scheduleIdleExit();
2473
+ return;
2474
+ }
2475
+ if (busy() || idleTimer !== void 0) {
2476
+ await releaseStartupMutex(lockPath, token);
2477
+ return;
2478
+ }
2479
+ server.closeAllConnections();
2480
+ for (const client of clients) client.socket.destroy();
2481
+ server.close(() => {
2482
+ void removeOwnLock().then(() => releaseStartupMutex(lockPath, token)).then(() => {
2483
+ process.exit(0);
2484
+ }).catch(() => {
2485
+ process.exit(1);
2486
+ });
2275
2487
  });
2488
+ }).catch(() => {
2489
+ process.exit(1);
2276
2490
  });
2277
2491
  }, TRAJECTORY_IDLE_EXIT_MS);
2278
2492
  idleTimer.unref();
2279
2493
  };
2494
+ const removeOwnLock = async () => {
2495
+ let current;
2496
+ try {
2497
+ current = JSON.parse(await readFile3(lockPath, "utf8"));
2498
+ } catch (error) {
2499
+ if (isNodeError(error, "ENOENT")) return;
2500
+ throw error;
2501
+ }
2502
+ if (object(current) && current.pid === process.pid && current.startedAt === startedAt) await rm3(lockPath, { force: true });
2503
+ };
2280
2504
  const cancelIdleExit = () => {
2281
2505
  if (idleTimer !== void 0) {
2282
2506
  clearTimeout(idleTimer);
@@ -2423,7 +2647,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2423
2647
  const runRequest = validId(message.runId) && validId(message.agentId) && message.subagentId === void 0;
2424
2648
  const subagentRequest = validId(message.subagentId) && message.runId === void 0 && message.agentId === void 0;
2425
2649
  if (!runRequest && !subagentRequest) return;
2426
- const requestId2 = validId(message.requestId) ? message.requestId : randomUUID();
2650
+ const requestId2 = validId(message.requestId) ? message.requestId : randomUUID3();
2427
2651
  const revision = message.revision === void 0 ? void 0 : typeof message.revision === "number" && Number.isSafeInteger(message.revision) && message.revision >= 0 ? message.revision : void 0;
2428
2652
  const target2 = publishers.get(message.publisherId);
2429
2653
  if (!target2 || !target2.value.connected) {
@@ -2471,7 +2695,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2471
2695
  return;
2472
2696
  }
2473
2697
  if (client.kind !== "browser" || message.type !== "ui:action" || typeof message.publisherId !== "string") return;
2474
- const requestId = typeof message.requestId === "string" && message.requestId.length >= 1 && message.requestId.length <= 200 ? message.requestId : randomUUID();
2698
+ const requestId = typeof message.requestId === "string" && message.requestId.length >= 1 && message.requestId.length <= 200 ? message.requestId : randomUUID3();
2475
2699
  if (!isTrajectoryAction(message.action)) {
2476
2700
  emit(client, { type: "action-result", requestId, ok: false, error: "Unsupported Trajectory action" });
2477
2701
  return;
@@ -2525,7 +2749,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2525
2749
  return;
2526
2750
  }
2527
2751
  if (request.method === "GET" && (path2 === "/" || path2 === "/index.html")) {
2528
- void readFile(new URL("./assets/index.html", import.meta.url)).then((html) => {
2752
+ void readFile3(new URL("./assets/index.html", import.meta.url)).then((html) => {
2529
2753
  response.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": html.byteLength, "cache-control": "no-store" });
2530
2754
  response.end(html);
2531
2755
  }).catch(() => {
@@ -2534,7 +2758,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2534
2758
  return;
2535
2759
  }
2536
2760
  if (request.method === "GET" && path2 === "/marked.min.js") {
2537
- void readFile(new URL("./assets/marked.min.js", import.meta.url)).then((script) => {
2761
+ void readFile3(new URL("./assets/marked.min.js", import.meta.url)).then((script) => {
2538
2762
  response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "content-length": script.byteLength, "cache-control": "no-store" });
2539
2763
  response.end(script);
2540
2764
  }).catch(() => {
@@ -2543,7 +2767,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2543
2767
  return;
2544
2768
  }
2545
2769
  if (request.method === "GET" && path2 === "/morphdom.min.js") {
2546
- void readFile(new URL("./assets/morphdom.min.js", import.meta.url)).then((script) => {
2770
+ void readFile3(new URL("./assets/morphdom.min.js", import.meta.url)).then((script) => {
2547
2771
  response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "content-length": script.byteLength, "cache-control": "no-store" });
2548
2772
  response.end(script);
2549
2773
  }).catch(() => {
@@ -2552,7 +2776,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2552
2776
  return;
2553
2777
  }
2554
2778
  if (request.method === "GET" && path2 === "/prism.min.js") {
2555
- void readFile(new URL("./assets/prism.min.js", import.meta.url)).then((script) => {
2779
+ void readFile3(new URL("./assets/prism.min.js", import.meta.url)).then((script) => {
2556
2780
  response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "content-length": script.byteLength, "cache-control": "no-store" });
2557
2781
  response.end(script);
2558
2782
  }).catch(() => {
@@ -2561,7 +2785,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2561
2785
  return;
2562
2786
  }
2563
2787
  if (request.method === "GET" && (path2 === "/favicon.png" || path2 === "/favicon.ico")) {
2564
- void readFile(new URL("./assets/favicon.png", import.meta.url)).then((icon) => {
2788
+ void readFile3(new URL("./assets/favicon.png", import.meta.url)).then((icon) => {
2565
2789
  response.writeHead(200, { "content-type": "image/png", "content-length": icon.byteLength, "cache-control": "no-store" });
2566
2790
  response.end(icon);
2567
2791
  }).catch(() => {
@@ -2621,10 +2845,14 @@ Sec-WebSocket-Accept: ${accept}\r
2621
2845
  });
2622
2846
  server.once("listening", () => {
2623
2847
  startedAt = Date.now();
2624
- void writeFile(lockPath, `${JSON.stringify({ pid: process.pid, port, fingerprint: serverFingerprint, startedAt })}
2625
- `, { mode: 384 }).catch(() => {
2626
- process.exitCode = 1;
2627
- });
2848
+ try {
2849
+ atomicWriteFile(lockPath, `${JSON.stringify({ pid: process.pid, port, fingerprint: serverFingerprint, startedAt, start: processStart(process.pid) })}
2850
+ `, true);
2851
+ } catch (error) {
2852
+ server.close();
2853
+ server.emit("error", error);
2854
+ return;
2855
+ }
2628
2856
  scheduleIdleExit();
2629
2857
  });
2630
2858
  server.on("close", () => {
@@ -2636,13 +2864,24 @@ Sec-WebSocket-Accept: ${accept}\r
2636
2864
  });
2637
2865
  return server;
2638
2866
  }
2867
+ async function awaitStartupRecord(lockPath, token) {
2868
+ for (let attempt = 0; attempt < 60; attempt += 1) {
2869
+ const holder = readStartupHolder(lockPath, token);
2870
+ if (holder === void 0) break;
2871
+ if (holder.server?.pid === process.pid) return;
2872
+ await new Promise((resolve2) => setTimeout(resolve2, 50));
2873
+ }
2874
+ throw new Error("Trajectory startup was abandoned before the server listened");
2875
+ }
2639
2876
  async function main() {
2640
2877
  const args = /* @__PURE__ */ new Map();
2641
2878
  for (let index = 2; index + 1 < process.argv.length; index += 2) args.set(process.argv[index] ?? "", process.argv[index + 1] ?? "");
2642
2879
  const port = Number(args.get("--port"));
2643
2880
  const lockPath = args.get("--lock");
2644
2881
  const fingerprint = args.get("--fingerprint");
2882
+ const startupToken = args.get("--startup");
2645
2883
  if (!Number.isSafeInteger(port) || port < 1 || port > 65535 || !lockPath || !fingerprint) throw new Error("Invalid Trajectory server arguments");
2884
+ if (startupToken !== void 0) await awaitStartupRecord(lockPath, startupToken);
2646
2885
  const server = createTrajectoryServer(port, lockPath, { fingerprint });
2647
2886
  await new Promise((resolve2, reject) => {
2648
2887
  server.once("error", reject);