@marcoscale98/pi-extensible-workflows 5.17.0-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 (128) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +442 -162
  5. package/dist/src/background-widget.d.ts +5 -1
  6. package/dist/src/background-widget.js +5 -10
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +59 -37
  9. package/dist/src/execution.d.ts +6 -2
  10. package/dist/src/execution.js +48 -7
  11. package/dist/src/host-delivery.js +5 -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 +11 -8
  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 +6 -1
  18. package/dist/src/host-view.js +46 -20
  19. package/dist/src/host.d.ts +2 -2
  20. package/dist/src/host.js +246 -118
  21. package/dist/src/index.d.ts +1 -2
  22. package/dist/src/index.js +1985 -1690
  23. package/dist/src/index.js.map +3 -3
  24. package/dist/src/paths.d.ts +8 -5
  25. package/dist/src/paths.js +24 -13
  26. package/dist/src/persistence.d.ts +1 -0
  27. package/dist/src/persistence.js +1 -0
  28. package/dist/src/pi-runtime-runner.js +4 -1
  29. package/dist/src/registry.d.ts +9 -7
  30. package/dist/src/registry.js +29 -71
  31. package/dist/src/runtime/agent-runner.d.ts +2 -0
  32. package/dist/src/script-tools.d.ts +27 -0
  33. package/dist/src/script-tools.js +87 -0
  34. package/dist/src/session-lease.d.ts +1 -1
  35. package/dist/src/session-lease.js +45 -11
  36. package/dist/src/settings.d.ts +19 -3
  37. package/dist/src/settings.js +63 -50
  38. package/dist/src/store.d.ts +3 -1
  39. package/dist/src/store.js +17 -1
  40. package/dist/src/trajectory.d.ts +12 -3
  41. package/dist/src/trajectory.js +32 -11
  42. package/dist/src/types.d.ts +129 -45
  43. package/dist/src/types.js +5 -8
  44. package/dist/src/utils.d.ts +21 -1
  45. package/dist/src/utils.js +46 -6
  46. package/dist/src/validation.d.ts +14 -3
  47. package/dist/src/validation.js +143 -87
  48. package/dist/starter/index.js +8 -11
  49. package/dist/starter/index.js.map +1 -1
  50. package/dist/starter/review-loop.d.ts +0 -13
  51. package/dist/starter/review-loop.js +8 -9
  52. package/dist/subagents/index.js +904 -956
  53. package/dist/subagents/index.js.map +3 -3
  54. package/dist/subagents/src/contracts.d.ts +48 -31
  55. package/dist/subagents/src/contracts.js +20 -9
  56. package/dist/subagents/src/index.d.ts +1 -1
  57. package/dist/subagents/src/index.js +14 -8
  58. package/dist/subagents/src/manager.js +52 -73
  59. package/dist/subagents/src/navigator.js +3 -14
  60. package/dist/subagents/src/view.d.ts +1 -1
  61. package/dist/subagents/src/view.js +15 -68
  62. package/dist/subagents/src/worktree.js +1 -1
  63. package/dist/trajectory/assets/index.html +35 -13
  64. package/dist/trajectory/index.js +576 -932
  65. package/dist/trajectory/index.js.map +3 -3
  66. package/dist/trajectory/src/assets/index.html +35 -13
  67. package/dist/trajectory/src/index.js +134 -104
  68. package/dist/trajectory/src/server.js +268 -27
  69. package/dist/trajectory/src/server.js.map +3 -3
  70. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  71. package/dist/trajectory/src/startup-mutex.js +172 -0
  72. package/examples/workflow-extension-template/README.md +4 -7
  73. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  74. package/examples/workflow-extension-template/index.js +0 -3
  75. package/package.json +10 -10
  76. package/skills/pi-extensible-workflows/SKILL.md +12 -8
  77. package/src/agent-execution.ts +412 -164
  78. package/src/background-widget.ts +5 -11
  79. package/src/decoders.ts +51 -37
  80. package/src/execution.ts +46 -9
  81. package/src/host-delivery.ts +5 -8
  82. package/src/host-navigator.ts +4 -2
  83. package/src/host-phases.ts +1 -1
  84. package/src/host-recovery.ts +23 -28
  85. package/src/host-runtime.ts +44 -6
  86. package/src/host-view.ts +41 -16
  87. package/src/host.ts +229 -142
  88. package/src/index.ts +1 -2
  89. package/src/paths.ts +18 -9
  90. package/src/persistence.ts +1 -0
  91. package/src/pi-runtime-runner.ts +3 -1
  92. package/src/registry.ts +28 -64
  93. package/src/runtime/agent-runner.ts +2 -0
  94. package/src/script-tools.ts +76 -0
  95. package/src/session-lease.ts +34 -6
  96. package/src/settings.ts +56 -50
  97. package/src/store.ts +18 -2
  98. package/src/trajectory.ts +32 -15
  99. package/src/types.ts +82 -34
  100. package/src/utils.ts +42 -5
  101. package/src/validation.ts +121 -80
  102. package/starter/index.ts +0 -1
  103. package/starter/review-loop.ts +8 -9
  104. package/starter/roles/developer.md +0 -1
  105. package/starter/roles/oracle.md +0 -1
  106. package/starter/roles/researcher.md +0 -1
  107. package/starter/roles/reviewer.md +0 -1
  108. package/starter/roles/scout.md +0 -1
  109. package/subagents/README.md +10 -16
  110. package/subagents/src/contracts.ts +21 -11
  111. package/subagents/src/index.ts +17 -8
  112. package/subagents/src/manager.ts +48 -128
  113. package/subagents/src/navigator.ts +4 -13
  114. package/subagents/src/view.ts +16 -63
  115. package/subagents/src/worktree.ts +1 -1
  116. package/trajectory/src/assets/index.html +35 -13
  117. package/trajectory/src/index.ts +112 -91
  118. package/trajectory/src/server.ts +46 -10
  119. package/trajectory/src/startup-mutex.ts +127 -0
  120. package/dist/src/roles.d.ts +0 -70
  121. package/dist/src/roles.js +0 -325
  122. package/dist/starter/roles/developer.md +0 -6
  123. package/dist/starter/roles/oracle.md +0 -18
  124. package/dist/starter/roles/researcher.md +0 -20
  125. package/dist/starter/roles/reviewer.md +0 -19
  126. package/dist/starter/roles/scout.md +0 -18
  127. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  128. package/src/roles.ts +0 -326
@@ -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,15 +1983,148 @@ 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
+
2117
+ // src/tool-timing.ts
2118
+ var TOOL_TIMING_ENTRY_TYPE = "pi-workflows:tool-timing";
2119
+
1917
2120
  // trajectory/src/server.ts
1918
2121
  var TRAJECTORY_IDLE_EXIT_MS = 5 * 60 * 1e3;
1919
2122
  var MAX_FRAME_BYTES = 32 * 1024 * 1024;
1920
2123
  var MAX_PENDING_REQUESTS = 128;
1921
2124
  var TRANSCRIPT_REQUEST_TIMEOUT_MS = 1e4;
1922
2125
  var ACTION_REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
1923
- var TIMING_ENTRY_TYPE = "pi-workflows:tool-timing";
1924
2126
  function isTimingEntry(value) {
1925
- return Boolean(value && typeof value === "object" && !Array.isArray(value) && value.type === "custom" && value.customType === TIMING_ENTRY_TYPE);
2127
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && value.type === "custom" && value.customType === TOOL_TIMING_ENTRY_TYPE);
1926
2128
  }
1927
2129
  function compactTranscript(value) {
1928
2130
  if (Array.isArray(value)) return value.filter(isTimingEntry);
@@ -2231,7 +2433,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2231
2433
  return requests;
2232
2434
  };
2233
2435
  const hasPendingRequest = (requests, requestId) => [...requests.values()].some((request) => request.requestId === requestId);
2234
- 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;
2235
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 };
2236
2438
  const clearPending = (client, error) => {
2237
2439
  const own = pending.get(client);
@@ -2260,21 +2462,45 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2260
2462
  if (!requests.size) pending.delete(browser);
2261
2463
  emit(browser, errorResponse(requestId, request, "failed", error));
2262
2464
  };
2465
+ const busy = () => closed || [...publishers.values()].some(({ value }) => value.connected === true);
2263
2466
  const scheduleIdleExit = () => {
2264
- if (closed || [...publishers.values()].some(({ value }) => value.connected === true) || idleTimer !== void 0) return;
2467
+ if (busy() || idleTimer !== void 0) return;
2265
2468
  idleTimer = setTimeout(() => {
2266
- server.closeAllConnections();
2267
- for (const client of clients) client.socket.destroy();
2268
- server.close(() => {
2269
- void rm(lockPath, { force: true }).then(() => {
2270
- process.exit(0);
2271
- }).catch(() => {
2272
- 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
+ });
2273
2487
  });
2488
+ }).catch(() => {
2489
+ process.exit(1);
2274
2490
  });
2275
2491
  }, TRAJECTORY_IDLE_EXIT_MS);
2276
2492
  idleTimer.unref();
2277
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
+ };
2278
2504
  const cancelIdleExit = () => {
2279
2505
  if (idleTimer !== void 0) {
2280
2506
  clearTimeout(idleTimer);
@@ -2421,7 +2647,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2421
2647
  const runRequest = validId(message.runId) && validId(message.agentId) && message.subagentId === void 0;
2422
2648
  const subagentRequest = validId(message.subagentId) && message.runId === void 0 && message.agentId === void 0;
2423
2649
  if (!runRequest && !subagentRequest) return;
2424
- const requestId2 = validId(message.requestId) ? message.requestId : randomUUID();
2650
+ const requestId2 = validId(message.requestId) ? message.requestId : randomUUID3();
2425
2651
  const revision = message.revision === void 0 ? void 0 : typeof message.revision === "number" && Number.isSafeInteger(message.revision) && message.revision >= 0 ? message.revision : void 0;
2426
2652
  const target2 = publishers.get(message.publisherId);
2427
2653
  if (!target2 || !target2.value.connected) {
@@ -2469,7 +2695,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2469
2695
  return;
2470
2696
  }
2471
2697
  if (client.kind !== "browser" || message.type !== "ui:action" || typeof message.publisherId !== "string") return;
2472
- 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();
2473
2699
  if (!isTrajectoryAction(message.action)) {
2474
2700
  emit(client, { type: "action-result", requestId, ok: false, error: "Unsupported Trajectory action" });
2475
2701
  return;
@@ -2523,7 +2749,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2523
2749
  return;
2524
2750
  }
2525
2751
  if (request.method === "GET" && (path2 === "/" || path2 === "/index.html")) {
2526
- 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) => {
2527
2753
  response.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": html.byteLength, "cache-control": "no-store" });
2528
2754
  response.end(html);
2529
2755
  }).catch(() => {
@@ -2532,7 +2758,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2532
2758
  return;
2533
2759
  }
2534
2760
  if (request.method === "GET" && path2 === "/marked.min.js") {
2535
- 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) => {
2536
2762
  response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "content-length": script.byteLength, "cache-control": "no-store" });
2537
2763
  response.end(script);
2538
2764
  }).catch(() => {
@@ -2541,7 +2767,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2541
2767
  return;
2542
2768
  }
2543
2769
  if (request.method === "GET" && path2 === "/morphdom.min.js") {
2544
- 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) => {
2545
2771
  response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "content-length": script.byteLength, "cache-control": "no-store" });
2546
2772
  response.end(script);
2547
2773
  }).catch(() => {
@@ -2550,7 +2776,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2550
2776
  return;
2551
2777
  }
2552
2778
  if (request.method === "GET" && path2 === "/prism.min.js") {
2553
- 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) => {
2554
2780
  response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "content-length": script.byteLength, "cache-control": "no-store" });
2555
2781
  response.end(script);
2556
2782
  }).catch(() => {
@@ -2559,7 +2785,7 @@ function createTrajectoryServer(port, lockPath, options = {}) {
2559
2785
  return;
2560
2786
  }
2561
2787
  if (request.method === "GET" && (path2 === "/favicon.png" || path2 === "/favicon.ico")) {
2562
- 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) => {
2563
2789
  response.writeHead(200, { "content-type": "image/png", "content-length": icon.byteLength, "cache-control": "no-store" });
2564
2790
  response.end(icon);
2565
2791
  }).catch(() => {
@@ -2619,10 +2845,14 @@ Sec-WebSocket-Accept: ${accept}\r
2619
2845
  });
2620
2846
  server.once("listening", () => {
2621
2847
  startedAt = Date.now();
2622
- void writeFile(lockPath, `${JSON.stringify({ pid: process.pid, port, fingerprint: serverFingerprint, startedAt })}
2623
- `, { mode: 384 }).catch(() => {
2624
- process.exitCode = 1;
2625
- });
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
+ }
2626
2856
  scheduleIdleExit();
2627
2857
  });
2628
2858
  server.on("close", () => {
@@ -2634,13 +2864,24 @@ Sec-WebSocket-Accept: ${accept}\r
2634
2864
  });
2635
2865
  return server;
2636
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
+ }
2637
2876
  async function main() {
2638
2877
  const args = /* @__PURE__ */ new Map();
2639
2878
  for (let index = 2; index + 1 < process.argv.length; index += 2) args.set(process.argv[index] ?? "", process.argv[index + 1] ?? "");
2640
2879
  const port = Number(args.get("--port"));
2641
2880
  const lockPath = args.get("--lock");
2642
2881
  const fingerprint = args.get("--fingerprint");
2882
+ const startupToken = args.get("--startup");
2643
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);
2644
2885
  const server = createTrajectoryServer(port, lockPath, { fingerprint });
2645
2886
  await new Promise((resolve2, reject) => {
2646
2887
  server.once("error", reject);