@ricsam/r5d-worker 0.0.136 → 0.0.138

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 (240) hide show
  1. package/README.md +4 -94
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/mjs/main.mjs +46 -7537
  4. package/dist/mjs/package.json +1 -1
  5. package/dist/mjs/personal/action-scheduler.mjs +31 -0
  6. package/dist/mjs/personal/client.mjs +215 -0
  7. package/dist/mjs/personal/runtime.mjs +328 -0
  8. package/dist/mjs/personal/tcp.mjs +217 -0
  9. package/dist/mjs/runtime/adapter.mjs +42 -0
  10. package/dist/mjs/runtime/client.mjs +95 -0
  11. package/dist/mjs/runtime/daemon.mjs +131 -0
  12. package/dist/mjs/runtime/executor.mjs +436 -0
  13. package/dist/mjs/runtime/index.mjs +15 -0
  14. package/dist/mjs/runtime/protocol.mjs +139 -0
  15. package/dist/mjs/runtime/pty-host.mjs +156 -0
  16. package/dist/mjs/runtime/pty.mjs +127 -0
  17. package/dist/mjs/runtime/releases/artifacts.mjs +201 -0
  18. package/dist/mjs/runtime/releases/grants.mjs +93 -0
  19. package/dist/mjs/runtime/releases/main.mjs +101 -0
  20. package/dist/mjs/runtime/releases/manager.mjs +541 -0
  21. package/dist/mjs/runtime/releases/process.mjs +88 -0
  22. package/dist/mjs/runtime/releases/retirement.mjs +18 -0
  23. package/dist/mjs/runtime/releases/rpc-client.mjs +184 -0
  24. package/dist/mjs/runtime/releases/rpc-main.mjs +213 -0
  25. package/dist/mjs/runtime/releases/rpc-protocol.mjs +103 -0
  26. package/dist/mjs/runtime/storage.mjs +156 -0
  27. package/dist/mjs/runtime/workspace/artifacts.mjs +121 -0
  28. package/dist/mjs/runtime/workspace/authority.mjs +1355 -0
  29. package/dist/mjs/runtime/workspace/contracts.mjs +60 -0
  30. package/dist/mjs/runtime/workspace/file-write.mjs +23 -0
  31. package/dist/mjs/runtime/workspace/files.mjs +308 -0
  32. package/dist/mjs/runtime/workspace/http-body.mjs +45 -0
  33. package/dist/mjs/runtime/workspace/storage-client.mjs +59 -0
  34. package/dist/mjs/runtime/workspace/storage-wire.mjs +53 -0
  35. package/dist/types/main.d.ts +0 -1167
  36. package/dist/types/personal/action-scheduler.d.ts +23 -0
  37. package/dist/types/personal/client.d.ts +14 -0
  38. package/dist/types/personal/runtime.d.ts +121 -0
  39. package/dist/types/personal/tcp.d.ts +14 -0
  40. package/dist/types/runtime/client.d.ts +17 -0
  41. package/dist/types/runtime/daemon.d.ts +8 -0
  42. package/dist/types/runtime/executor.d.ts +35 -0
  43. package/dist/types/runtime/index.d.ts +5 -0
  44. package/dist/types/runtime/protocol.d.ts +544 -0
  45. package/dist/types/runtime/pty.d.ts +30 -0
  46. package/dist/types/runtime/releases/artifacts.d.ts +51 -0
  47. package/dist/types/runtime/releases/grants.d.ts +16 -0
  48. package/dist/types/runtime/releases/manager.d.ts +145 -0
  49. package/dist/types/runtime/releases/process.d.ts +14 -0
  50. package/dist/types/runtime/releases/retirement.d.ts +15 -0
  51. package/dist/types/runtime/releases/rpc-client.d.ts +52 -0
  52. package/dist/types/runtime/releases/rpc-main.d.ts +10 -0
  53. package/dist/types/runtime/releases/rpc-protocol.d.ts +1139 -0
  54. package/dist/types/runtime/storage.d.ts +21 -0
  55. package/dist/types/runtime/test-harness.d.ts +53 -0
  56. package/dist/types/runtime/workspace/artifacts.d.ts +30 -0
  57. package/dist/types/runtime/workspace/authority.d.ts +338 -0
  58. package/dist/types/runtime/workspace/contracts.d.ts +86 -0
  59. package/dist/types/runtime/workspace/file-write.d.ts +14 -0
  60. package/dist/types/runtime/workspace/files.d.ts +21 -0
  61. package/dist/types/runtime/workspace/http-body.d.ts +9 -0
  62. package/dist/types/runtime/workspace/storage-client.d.ts +23 -0
  63. package/dist/types/runtime/workspace/storage-wire.d.ts +138 -0
  64. package/package.json +8 -6
  65. package/dist/cjs/atomic-rename.cjs +0 -303
  66. package/dist/cjs/browser-upload.cjs +0 -73
  67. package/dist/cjs/cli-update.cjs +0 -108
  68. package/dist/cjs/command-launcher.cjs +0 -423
  69. package/dist/cjs/control-command-policy.cjs +0 -177
  70. package/dist/cjs/git-blob-hash.cjs +0 -41
  71. package/dist/cjs/git-identity.cjs +0 -39
  72. package/dist/cjs/git-process-environment.cjs +0 -133
  73. package/dist/cjs/inline-output.cjs +0 -62
  74. package/dist/cjs/main.cjs +0 -7511
  75. package/dist/cjs/managed-paths.cjs +0 -215
  76. package/dist/cjs/plan-lint.cjs +0 -165
  77. package/dist/cjs/plan-parser.cjs +0 -127
  78. package/dist/cjs/plan-paths.cjs +0 -53
  79. package/dist/cjs/port-forward-client.cjs +0 -206
  80. package/dist/cjs/process-tree.cjs +0 -117
  81. package/dist/cjs/project-checkout-garbage.cjs +0 -219
  82. package/dist/cjs/project-mirror-fetch-policy.cjs +0 -82
  83. package/dist/cjs/project-mirror-refs-token.cjs +0 -85
  84. package/dist/cjs/project-workspace-state.cjs +0 -972
  85. package/dist/cjs/project-worktrees.cjs +0 -1713
  86. package/dist/cjs/pty-output-coalescer.cjs +0 -87
  87. package/dist/cjs/recovery-journal-protocol.cjs +0 -56
  88. package/dist/cjs/recovery-journal-runtime.cjs +0 -133
  89. package/dist/cjs/recovery-journal-thread.cjs +0 -9
  90. package/dist/cjs/recovery-journal.cjs +0 -742
  91. package/dist/cjs/recovery-output-window.cjs +0 -77
  92. package/dist/cjs/recovery-protocol.cjs +0 -31
  93. package/dist/cjs/recovery-store.cjs +0 -252
  94. package/dist/cjs/registry-auth.cjs +0 -310
  95. package/dist/cjs/repository-transition-policy.cjs +0 -49
  96. package/dist/cjs/runtime-version.cjs +0 -88
  97. package/dist/cjs/session-file-mutations.cjs +0 -61
  98. package/dist/cjs/supervisor.cjs +0 -147
  99. package/dist/cjs/three-way-merge.cjs +0 -346
  100. package/dist/cjs/working-tree-mirror.cjs +0 -1872
  101. package/dist/cjs/workspace-automatic-sync-policy.cjs +0 -79
  102. package/dist/cjs/workspace-branch-incarnation-policy.cjs +0 -37
  103. package/dist/cjs/workspace-command-sync-policy.cjs +0 -112
  104. package/dist/cjs/workspace-command-targets.cjs +0 -63
  105. package/dist/cjs/workspace-filesystem-executor-thread.cjs +0 -36
  106. package/dist/cjs/workspace-filesystem-executor.cjs +0 -327
  107. package/dist/cjs/workspace-filesystem-job-types.cjs +0 -144
  108. package/dist/cjs/workspace-filesystem-jobs.cjs +0 -59
  109. package/dist/cjs/workspace-git-sync.cjs +0 -3450
  110. package/dist/cjs/workspace-hydration-ledger.cjs +0 -66
  111. package/dist/cjs/workspace-hydration-merge.cjs +0 -433
  112. package/dist/cjs/workspace-hydration-recovery-state.cjs +0 -53
  113. package/dist/cjs/workspace-incident-state.cjs +0 -37
  114. package/dist/cjs/workspace-merge-projection.cjs +0 -467
  115. package/dist/cjs/workspace-mount-boundary.cjs +0 -71
  116. package/dist/cjs/workspace-mount-hold-fence.cjs +0 -120
  117. package/dist/cjs/workspace-mutation-gate.cjs +0 -127
  118. package/dist/cjs/workspace-path-move.cjs +0 -195
  119. package/dist/cjs/workspace-preserve-only-policy.cjs +0 -36
  120. package/dist/cjs/workspace-project-config-policy.cjs +0 -53
  121. package/dist/cjs/workspace-projection-ledger.cjs +0 -81
  122. package/dist/cjs/workspace-publication-evidence.cjs +0 -46
  123. package/dist/cjs/workspace-sync-coalescer.cjs +0 -58
  124. package/dist/mjs/atomic-rename.mjs +0 -261
  125. package/dist/mjs/browser-upload.mjs +0 -42
  126. package/dist/mjs/command-launcher.mjs +0 -393
  127. package/dist/mjs/control-command-policy.mjs +0 -146
  128. package/dist/mjs/git-blob-hash.mjs +0 -16
  129. package/dist/mjs/git-identity.mjs +0 -15
  130. package/dist/mjs/git-process-environment.mjs +0 -105
  131. package/dist/mjs/inline-output.mjs +0 -37
  132. package/dist/mjs/managed-paths.mjs +0 -174
  133. package/dist/mjs/plan-lint.mjs +0 -129
  134. package/dist/mjs/plan-parser.mjs +0 -94
  135. package/dist/mjs/plan-paths.mjs +0 -25
  136. package/dist/mjs/port-forward-client.mjs +0 -172
  137. package/dist/mjs/project-checkout-garbage.mjs +0 -178
  138. package/dist/mjs/project-mirror-fetch-policy.mjs +0 -56
  139. package/dist/mjs/project-mirror-refs-token.mjs +0 -53
  140. package/dist/mjs/project-workspace-state.mjs +0 -937
  141. package/dist/mjs/project-worktrees.mjs +0 -1657
  142. package/dist/mjs/pty-output-coalescer.mjs +0 -61
  143. package/dist/mjs/recovery-journal-protocol.mjs +0 -30
  144. package/dist/mjs/recovery-journal-runtime.mjs +0 -112
  145. package/dist/mjs/recovery-journal-thread.mjs +0 -8
  146. package/dist/mjs/recovery-journal.mjs +0 -694
  147. package/dist/mjs/recovery-output-window.mjs +0 -53
  148. package/dist/mjs/recovery-protocol.mjs +0 -6
  149. package/dist/mjs/recovery-store.mjs +0 -217
  150. package/dist/mjs/registry-auth.mjs +0 -269
  151. package/dist/mjs/repository-transition-policy.mjs +0 -22
  152. package/dist/mjs/runtime-version.mjs +0 -54
  153. package/dist/mjs/session-file-mutations.mjs +0 -37
  154. package/dist/mjs/supervisor.mjs +0 -114
  155. package/dist/mjs/three-way-merge.mjs +0 -318
  156. package/dist/mjs/working-tree-mirror.mjs +0 -1817
  157. package/dist/mjs/workspace-automatic-sync-policy.mjs +0 -48
  158. package/dist/mjs/workspace-branch-incarnation-policy.mjs +0 -13
  159. package/dist/mjs/workspace-command-sync-policy.mjs +0 -84
  160. package/dist/mjs/workspace-command-targets.mjs +0 -37
  161. package/dist/mjs/workspace-filesystem-executor-thread.mjs +0 -38
  162. package/dist/mjs/workspace-filesystem-executor.mjs +0 -287
  163. package/dist/mjs/workspace-filesystem-job-types.mjs +0 -116
  164. package/dist/mjs/workspace-filesystem-jobs.mjs +0 -55
  165. package/dist/mjs/workspace-git-sync.mjs +0 -3407
  166. package/dist/mjs/workspace-hydration-ledger.mjs +0 -42
  167. package/dist/mjs/workspace-hydration-merge.mjs +0 -399
  168. package/dist/mjs/workspace-hydration-recovery-state.mjs +0 -29
  169. package/dist/mjs/workspace-incident-state.mjs +0 -12
  170. package/dist/mjs/workspace-merge-projection.mjs +0 -433
  171. package/dist/mjs/workspace-mount-boundary.mjs +0 -37
  172. package/dist/mjs/workspace-mount-hold-fence.mjs +0 -95
  173. package/dist/mjs/workspace-mutation-gate.mjs +0 -103
  174. package/dist/mjs/workspace-path-move.mjs +0 -160
  175. package/dist/mjs/workspace-preserve-only-policy.mjs +0 -11
  176. package/dist/mjs/workspace-project-config-policy.mjs +0 -27
  177. package/dist/mjs/workspace-projection-ledger.mjs +0 -57
  178. package/dist/mjs/workspace-publication-evidence.mjs +0 -21
  179. package/dist/mjs/workspace-sync-coalescer.mjs +0 -34
  180. package/dist/types/atomic-rename.d.ts +0 -78
  181. package/dist/types/browser-upload.d.ts +0 -7
  182. package/dist/types/command-launcher.d.ts +0 -104
  183. package/dist/types/control-command-policy.d.ts +0 -53
  184. package/dist/types/git-blob-hash.d.ts +0 -10
  185. package/dist/types/git-identity.d.ts +0 -9
  186. package/dist/types/git-process-environment.d.ts +0 -9
  187. package/dist/types/inline-output.d.ts +0 -3
  188. package/dist/types/managed-paths.d.ts +0 -19
  189. package/dist/types/plan-lint.d.ts +0 -16
  190. package/dist/types/plan-parser.d.ts +0 -38
  191. package/dist/types/plan-paths.d.ts +0 -36
  192. package/dist/types/port-forward-client.d.ts +0 -8
  193. package/dist/types/project-checkout-garbage.d.ts +0 -86
  194. package/dist/types/project-mirror-fetch-policy.d.ts +0 -84
  195. package/dist/types/project-mirror-refs-token.d.ts +0 -53
  196. package/dist/types/project-workspace-state.d.ts +0 -188
  197. package/dist/types/project-worktrees.d.ts +0 -397
  198. package/dist/types/pty-output-coalescer.d.ts +0 -20
  199. package/dist/types/recovery-journal-protocol.d.ts +0 -35
  200. package/dist/types/recovery-journal-runtime.d.ts +0 -12
  201. package/dist/types/recovery-journal-thread.d.ts +0 -1
  202. package/dist/types/recovery-journal.d.ts +0 -248
  203. package/dist/types/recovery-output-window.d.ts +0 -19
  204. package/dist/types/recovery-protocol.d.ts +0 -59
  205. package/dist/types/recovery-store.d.ts +0 -52
  206. package/dist/types/registry-auth.d.ts +0 -47
  207. package/dist/types/repository-transition-policy.d.ts +0 -26
  208. package/dist/types/runtime-version.d.ts +0 -1
  209. package/dist/types/session-file-mutations.d.ts +0 -22
  210. package/dist/types/supervisor-daemonized-fixture.d.ts +0 -1
  211. package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +0 -1
  212. package/dist/types/supervisor-signal-fixture.d.ts +0 -1
  213. package/dist/types/supervisor.d.ts +0 -23
  214. package/dist/types/three-way-merge.d.ts +0 -77
  215. package/dist/types/working-tree-mirror.d.ts +0 -424
  216. package/dist/types/workspace-automatic-sync-policy.d.ts +0 -57
  217. package/dist/types/workspace-branch-incarnation-policy.d.ts +0 -14
  218. package/dist/types/workspace-command-sync-policy.d.ts +0 -57
  219. package/dist/types/workspace-command-targets.d.ts +0 -37
  220. package/dist/types/workspace-filesystem-executor-thread.d.ts +0 -1
  221. package/dist/types/workspace-filesystem-executor.d.ts +0 -123
  222. package/dist/types/workspace-filesystem-job-types.d.ts +0 -288
  223. package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
  224. package/dist/types/workspace-git-sync.d.ts +0 -495
  225. package/dist/types/workspace-hydration-ledger.d.ts +0 -43
  226. package/dist/types/workspace-hydration-merge.d.ts +0 -95
  227. package/dist/types/workspace-hydration-recovery-state.d.ts +0 -10
  228. package/dist/types/workspace-incident-state.d.ts +0 -19
  229. package/dist/types/workspace-merge-projection.d.ts +0 -60
  230. package/dist/types/workspace-mount-boundary.d.ts +0 -10
  231. package/dist/types/workspace-mount-hold-fence.d.ts +0 -42
  232. package/dist/types/workspace-mutation-gate.d.ts +0 -42
  233. package/dist/types/workspace-path-move.d.ts +0 -21
  234. package/dist/types/workspace-preserve-only-policy.d.ts +0 -15
  235. package/dist/types/workspace-project-config-policy.d.ts +0 -48
  236. package/dist/types/workspace-projection-ledger.d.ts +0 -59
  237. package/dist/types/workspace-publication-evidence.d.ts +0 -17
  238. package/dist/types/workspace-sync-coalescer.d.ts +0 -28
  239. /package/dist/types/{credential-authority-lock-fixture.d.ts → runtime/adapter.d.ts} +0 -0
  240. /package/dist/types/{recovery-journal-stall-fixture.d.ts → runtime/releases/main.d.ts} +0 -0
@@ -0,0 +1,156 @@
1
+ // node-pty's Unix FD integration needs Node, not Bun. This helper belongs to the
2
+ // keeper lifetime, never the adapter. Its IPC is inherited anonymous pipes only.
3
+ import process from "node:process";
4
+ import { Buffer } from "node:buffer";
5
+ import { write, constants, openSync, closeSync, lstatSync, fstatSync, fchmodSync, readSync, realpathSync } from "node:fs";
6
+ import { createRequire } from "node:module";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { setTimeout as delay } from "node:timers/promises";
10
+ import pty from "node-pty";
11
+ import readline from "node:readline";
12
+
13
+ /** node-pty 1.1.0 ships Darwin's spawn-helper without executable permission. */
14
+ export function ensureDarwinSpawnHelperPermissions({ platform = process.platform, arch = process.arch, currentUid = process.getuid?.(), requirePty = createRequire(import.meta.url) } = {}) {
15
+ if (platform !== "darwin") return false;
16
+ if (!["x64", "arm64"].includes(arch) || !Number.isSafeInteger(currentUid)) throw new Error("pty_native_helper_unavailable");
17
+ const manifest = requirePty("node-pty/package.json");
18
+ if (manifest.name !== "node-pty" || manifest.version !== "1.1.0") throw new Error("pty_native_helper_version");
19
+ const packageRoot = path.dirname(requirePty.resolve("node-pty/package.json"));
20
+ const library = path.dirname(requirePty.resolve("node-pty/lib/unixTerminal.js"));
21
+ const selected = requirePty("node-pty/lib/utils.js").loadNativeModule("pty");
22
+ const directory = path.resolve(library, selected.dir);
23
+ const allowed = ["build/Release", "build/Debug", `prebuilds/darwin-${arch}`].flatMap(relative => [path.join(packageRoot, relative), path.join(packageRoot, "lib", relative)]);
24
+ if (!allowed.includes(directory)) throw new Error("pty_native_helper_path");
25
+ const filename = path.join(directory, "spawn-helper");
26
+ let ancestor = path.parse(filename).root;
27
+ for (const segment of path.dirname(filename).slice(ancestor.length).split(path.sep).filter(Boolean)) {
28
+ ancestor = path.join(ancestor, segment);
29
+ const info = lstatSync(ancestor);
30
+ if (!info.isDirectory() || info.isSymbolicLink() || ![0, currentUid].includes(info.uid) || ((info.mode & 0o022) && !(info.mode & 0o1000))) throw new Error("pty_native_helper_unsafe");
31
+ }
32
+ const fd = openSync(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
33
+ try {
34
+ const before = fstatSync(fd);
35
+ if (!before.isFile() || before.nlink !== 1 || ![0, currentUid].includes(before.uid) || (before.mode & 0o7022) || before.size < 1 || before.size > 1024 * 1024) throw new Error("pty_native_helper_unsafe");
36
+ const target = before.mode | ((before.mode & 0o444) >> 2);
37
+ if (!(target & 0o111)) throw new Error("pty_native_helper_not_executable");
38
+ if (target === before.mode) return false;
39
+ if (before.uid !== currentUid) throw new Error("pty_native_helper_not_owned");
40
+ const bytes = Buffer.alloc(before.size);
41
+ if (readSync(fd, bytes, 0, bytes.length, 0) !== bytes.length) throw new Error("pty_native_helper_changed");
42
+ const named = lstatSync(filename);
43
+ if (named.dev !== before.dev || named.ino !== before.ino || named.isSymbolicLink()) throw new Error("pty_native_helper_changed");
44
+ fchmodSync(fd, target & 0o777);
45
+ const after = fstatSync(fd), sameName = lstatSync(filename), sameBytes = Buffer.alloc(before.size);
46
+ if (after.dev !== before.dev || after.ino !== before.ino || after.uid !== before.uid || after.nlink !== 1 || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.mode !== target || sameName.dev !== before.dev || sameName.ino !== before.ino || readSync(fd, sameBytes, 0, sameBytes.length, 0) !== sameBytes.length || !bytes.equals(sameBytes)) throw new Error("pty_native_helper_changed");
47
+ return true;
48
+ } finally { closeSync(fd); }
49
+ }
50
+
51
+ function startPtyHost() {
52
+ try { ensureDarwinSpawnHelperPermissions(); }
53
+ catch {
54
+ process.stderr.write("pty_native_helper_unavailable: use a supported, user-owned node-pty 1.1.0 installation with executable spawn-helper\n");
55
+ process.exit(78);
56
+ }
57
+ if (process.argv[2] === "--preflight") { process.stdout.write("pty-preflight-ok\n"); return; }
58
+
59
+ let terminal;
60
+ let initialized = false;
61
+ let terminalExited = false;
62
+ const send = (message) => process.stdout.write(JSON.stringify(message) + "\n");
63
+ const input = readline.createInterface({ input: process.stdin });
64
+ let serial = Promise.resolve();
65
+ input.on("line", (line) => {
66
+ serial = serial.then(async () => {
67
+ try {
68
+ const message = JSON.parse(line);
69
+ if (!initialized) {
70
+ if (message.method !== "start") throw new Error("Expected initialization");
71
+ initialized = true;
72
+ terminal = pty.spawn(message.argv[0], message.argv.slice(1), {
73
+ cwd: message.cwd,
74
+ env: message.env,
75
+ name: "xterm-256color",
76
+ cols: message.cols,
77
+ rows: message.rows,
78
+ encoding: null,
79
+ });
80
+ // node-pty 1.1.x UnixTerminal exposes fd. Fail closed if this pinned native
81
+ // contract changes; its public write() only enqueues and cannot ACK delivery.
82
+ if (!Number.isSafeInteger(terminal.fd) || terminal.fd < 0) throw new Error("PTY descriptor unavailable");
83
+ send({ event: "ready", pid: terminal.pid });
84
+ terminal.onData((data) => {
85
+ if (!Buffer.isBuffer(data)) {
86
+ shutdown();
87
+ return;
88
+ }
89
+ if (!send({ event: "data", data: data.toString("base64") })) terminal.pause();
90
+ });
91
+ process.stdout.on("drain", () => {
92
+ if (!terminalExited) terminal.resume();
93
+ });
94
+ terminal.onExit(({ exitCode, signal }) => {
95
+ terminalExited = true;
96
+ input.close();
97
+ process.stdin.destroy();
98
+ // Flush all queued output and the final event before process exit.
99
+ process.stdout.end(JSON.stringify({ event: "exit", exitCode, signal }) + "\n", () => process.exit(0));
100
+ });
101
+ return;
102
+ }
103
+ if (terminalExited) return;
104
+ try {
105
+ if (message.method === "write") await writeTerminal(message.data);
106
+ else if (message.method === "resize") terminal.resize(message.cols, message.rows);
107
+ else throw new Error("Unknown method");
108
+ if (!terminalExited) send({ event: "ack", id: message.id });
109
+ } catch {
110
+ if (!terminalExited) send({ event: "action-error", id: message.id });
111
+ }
112
+ } catch {
113
+ shutdown();
114
+ }
115
+ });
116
+ });
117
+ async function writeTerminal(data) {
118
+ const buffer = Buffer.from(data, "utf8");
119
+ let offset = 0;
120
+ const deadline = Date.now() + 1500;
121
+ while (offset < buffer.length) {
122
+ if (terminalExited || Date.now() > deadline) throw new Error("Unknown input outcome");
123
+ try {
124
+ offset += await new Promise((resolve, reject) =>
125
+ write(terminal.fd, buffer, offset, buffer.length - offset, (error, count) => (error ? reject(error) : resolve(count))),
126
+ );
127
+ } catch (error) {
128
+ if (error.code !== "EAGAIN" && error.code !== "EINTR") throw error;
129
+ await delay(5);
130
+ }
131
+ }
132
+ }
133
+ function shutdown() {
134
+ if (terminal && !terminalExited) {
135
+ try {
136
+ process.kill(-terminal.pid, "SIGKILL");
137
+ } catch {
138
+ /* Already gone. */
139
+ }
140
+ try {
141
+ terminal.kill("SIGKILL");
142
+ } catch {
143
+ /* Already gone. */
144
+ }
145
+ }
146
+ process.exit(1);
147
+ }
148
+ input.on("close", () => {
149
+ if (!terminalExited) shutdown();
150
+ });
151
+ process.on("SIGTERM", shutdown);
152
+ process.on("SIGINT", shutdown);
153
+ process.stdout.on("error", shutdown);
154
+ }
155
+
156
+ if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) startPtyHost();
@@ -0,0 +1,127 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import { ExecutorError } from "./protocol.mjs";
4
+ const nodeExecutable = typeof R5D_PACKAGED_NODE !== "undefined" && R5D_PACKAGED_NODE ? fileURLToPath(new URL("./bin/node", import.meta.url)) : Bun.which("node") ?? "node";
5
+ function preflightNativePty() {
6
+ if (process.platform !== "darwin") return;
7
+ const result = spawnSync(nodeExecutable, [fileURLToPath(new URL("./pty-host.mjs", import.meta.url)), "--preflight"], { encoding: "utf8", timeout: 5e3, env: { PATH: "/usr/local/bin:/usr/bin:/bin" } });
8
+ if (result.status !== 0 || typeof result.stdout !== "string" || result.stdout.trim() !== "pty-preflight-ok") throw new ExecutorError("pty_native_helper_unavailable", "Native PTY helper is unavailable; use a supported, user-owned node-pty 1.1.0 installation with executable spawn-helper");
9
+ }
10
+ class PtySession {
11
+ pid = 0;
12
+ sequence = 0;
13
+ pending = /* @__PURE__ */ new Map();
14
+ child;
15
+ onDataCallback;
16
+ onExitCallback;
17
+ buffered = [];
18
+ exit;
19
+ ready;
20
+ constructor(input) {
21
+ this.child = spawn(nodeExecutable, [fileURLToPath(new URL("./pty-host.mjs", import.meta.url))], {
22
+ env: { PATH: "/usr/local/bin:/usr/bin:/bin", HOME: input.cwd },
23
+ stdio: "pipe"
24
+ });
25
+ this.ready = new Promise((resolve, reject) => {
26
+ const timer = setTimeout(() => {
27
+ this.child.kill("SIGKILL");
28
+ reject(new ExecutorError("pty_start_timeout"));
29
+ }, 5e3);
30
+ let buffer = "";
31
+ const fail = () => {
32
+ clearTimeout(timer);
33
+ reject(new ExecutorError("pty_helper_failed"));
34
+ for (const pending of this.pending.values()) pending.reject(new ExecutorError("pty_helper_failed"));
35
+ this.pending.clear();
36
+ this.deliverExit({ exitCode: -1, signal: 0 });
37
+ };
38
+ this.child.on("error", fail);
39
+ this.child.stdin.on("error", fail);
40
+ this.child.stdout.setEncoding("utf8");
41
+ this.child.stdout.on("data", (data) => {
42
+ buffer += data;
43
+ if (buffer.length > 1024 * 1024) {
44
+ this.child.kill("SIGKILL");
45
+ fail();
46
+ return;
47
+ }
48
+ while (buffer.includes("\n")) {
49
+ const n = buffer.indexOf("\n");
50
+ const event = JSON.parse(buffer.slice(0, n));
51
+ buffer = buffer.slice(n + 1);
52
+ if (event.event === "ready") {
53
+ this.pid = event.pid;
54
+ clearTimeout(timer);
55
+ resolve();
56
+ } else if (event.event === "data") {
57
+ const bytes = Buffer.from(event.data, "base64");
58
+ if (this.onDataCallback) this.onDataCallback(bytes);
59
+ else this.buffered.push(bytes);
60
+ } else if (event.event === "exit") this.deliverExit({ exitCode: event.exitCode, signal: event.signal ?? 0 });
61
+ else if (event.event === "ack") {
62
+ this.pending.get(event.id)?.resolve();
63
+ this.pending.delete(event.id);
64
+ } else if (event.event === "action-error") {
65
+ this.pending.get(event.id)?.reject(new ExecutorError("pty_write_unknown"));
66
+ this.pending.delete(event.id);
67
+ } else fail();
68
+ }
69
+ });
70
+ this.child.once("close", () => {
71
+ if (!this.exit) fail();
72
+ });
73
+ this.child.stdin.write(JSON.stringify({ method: "start", ...input }) + "\n");
74
+ });
75
+ }
76
+ deliverExit(event) {
77
+ if (this.exit) return;
78
+ this.exit = event;
79
+ this.onExitCallback?.(event);
80
+ }
81
+ onData(callback) {
82
+ this.onDataCallback = callback;
83
+ for (const data of this.buffered.splice(0)) callback(data);
84
+ }
85
+ onExit(callback) {
86
+ this.onExitCallback = callback;
87
+ if (this.exit) callback(this.exit);
88
+ }
89
+ request(message) {
90
+ if (this.exit) return Promise.reject(new ExecutorError("pty_exited"));
91
+ const id = ++this.sequence;
92
+ return new Promise((resolve, reject) => {
93
+ const timer = setTimeout(() => {
94
+ this.pending.delete(id);
95
+ reject(new ExecutorError("pty_write_unknown"));
96
+ }, 2e3);
97
+ this.pending.set(id, {
98
+ resolve: () => {
99
+ clearTimeout(timer);
100
+ resolve();
101
+ },
102
+ reject: (error) => {
103
+ clearTimeout(timer);
104
+ reject(error);
105
+ }
106
+ });
107
+ this.child.stdin.write(JSON.stringify({ id, ...message }) + "\n");
108
+ });
109
+ }
110
+ write(data) {
111
+ return this.request({ method: "write", data });
112
+ }
113
+ resize(cols, rows) {
114
+ return this.request({ method: "resize", cols, rows });
115
+ }
116
+ kill(signal = "SIGTERM") {
117
+ if (!Number.isSafeInteger(this.pid) || this.pid <= 1 || this.exit) return;
118
+ try {
119
+ process.kill(-this.pid, signal);
120
+ } catch {
121
+ }
122
+ }
123
+ }
124
+ export {
125
+ PtySession,
126
+ preflightNativePty
127
+ };
@@ -0,0 +1,201 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ chmodSync,
4
+ closeSync,
5
+ constants,
6
+ existsSync,
7
+ fstatSync,
8
+ fsyncSync,
9
+ lstatSync,
10
+ mkdirSync,
11
+ openSync,
12
+ readFileSync,
13
+ readdirSync,
14
+ realpathSync,
15
+ renameSync,
16
+ rmSync,
17
+ writeFileSync
18
+ } from "node:fs";
19
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
20
+ import { z } from "zod";
21
+ import { RuntimeHello, RuntimeId } from "@ricsam/r5d-api/runtime-protocol";
22
+ import { ExecutorError } from "../protocol.mjs";
23
+ import { privateDirectory } from "../storage.mjs";
24
+ import { digest } from "./grants.mjs";
25
+ const sha256 = z.string().regex(/^[a-f0-9]{64}$/);
26
+ const memberPath = z.string().min(1).max(512).refine(
27
+ (path) => path.split("/").every((part) => /^[a-zA-Z0-9_.-]+$/.test(part) && part !== "." && part !== ".."),
28
+ "Unsafe artifact member"
29
+ );
30
+ const AdapterManifest = z.object({
31
+ version: z.literal(1),
32
+ installationId: RuntimeId,
33
+ workerId: RuntimeId,
34
+ hello: RuntimeHello,
35
+ entrypoint: memberPath,
36
+ files: z.array(
37
+ z.object({
38
+ path: memberPath,
39
+ sha256,
40
+ size: z.number().int().min(0).max(64 * 1024 * 1024),
41
+ executable: z.boolean()
42
+ }).strict()
43
+ ).min(1).max(4096)
44
+ }).strict().superRefine((manifest, ctx) => {
45
+ if (manifest.hello.installationId !== manifest.installationId || manifest.hello.role !== "worker-adapter" || new Set(manifest.files.map((file) => file.path)).size !== manifest.files.length || !manifest.files.some((file) => file.path === manifest.entrypoint) || manifest.files.reduce((total, file) => total + file.size, 0) > 256 * 1024 * 1024)
46
+ ctx.addIssue({ code: "custom", message: "Invalid adapter inventory or identity" });
47
+ });
48
+ const bytesHash = (bytes) => createHash("sha256").update(bytes).digest("hex");
49
+ function within(root, path) {
50
+ const rel = relative(root, path);
51
+ return rel === "" || !isAbsolute(rel) && rel !== ".." && !rel.startsWith("../");
52
+ }
53
+ function canonicalDirectory(path) {
54
+ if (!isAbsolute(path) || resolve(path) !== path) throw new ExecutorError("unsafe_release_path");
55
+ let cursor = path;
56
+ while (true) {
57
+ const stat = lstatSync(cursor);
58
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 18) !== 0 && !(stat.mode & 512))
59
+ throw new ExecutorError("unsafe_release_path");
60
+ if (cursor === dirname(cursor)) break;
61
+ cursor = dirname(cursor);
62
+ }
63
+ if (realpathSync(path) !== path) throw new ExecutorError("unsafe_release_path");
64
+ return path;
65
+ }
66
+ function outsideWorkspaces(path, workspaceRoots) {
67
+ canonicalDirectory(path);
68
+ for (const root of workspaceRoots) {
69
+ const workspace = realpathSync(root);
70
+ if (within(workspace, path) || within(path, workspace)) throw new ExecutorError("release_inside_workspace");
71
+ }
72
+ }
73
+ function syncDirectory(path) {
74
+ const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
75
+ try {
76
+ fsyncSync(fd);
77
+ } finally {
78
+ closeSync(fd);
79
+ }
80
+ }
81
+ function readMember(path, immutable) {
82
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
83
+ try {
84
+ const stat = fstatSync(fd);
85
+ if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || stat.size > 64 * 1024 * 1024 || stat.mode & (immutable ? 191 : 18))
86
+ throw new ExecutorError("unsafe_artifact_member");
87
+ return readFileSync(fd);
88
+ } finally {
89
+ closeSync(fd);
90
+ }
91
+ }
92
+ function inventory(root, immutable) {
93
+ const files = [];
94
+ let entries = 0;
95
+ function walk(dir) {
96
+ const directory = lstatSync(dir);
97
+ if (immutable && (directory.uid !== process.getuid?.() || (directory.mode & 511) !== 320))
98
+ throw new ExecutorError("artifact_mode_mismatch");
99
+ if (++entries > 8192) throw new ExecutorError("artifact_inventory_mismatch");
100
+ if (files.length > 4096) throw new ExecutorError("artifact_inventory_mismatch");
101
+ for (const name of readdirSync(dir)) {
102
+ const path = join(dir, name), stat = lstatSync(path);
103
+ if (stat.isSymbolicLink() || !stat.isDirectory() && !stat.isFile()) throw new ExecutorError("unsafe_artifact_member");
104
+ if (stat.isDirectory()) {
105
+ canonicalDirectory(path);
106
+ walk(path);
107
+ } else files.push(relative(root, path));
108
+ }
109
+ }
110
+ walk(root);
111
+ return files.sort();
112
+ }
113
+ function verifyTree(root, manifest, immutable) {
114
+ canonicalDirectory(root);
115
+ if (JSON.stringify(inventory(root, immutable)) !== JSON.stringify(manifest.files.map((file) => file.path).sort()))
116
+ throw new ExecutorError("artifact_inventory_mismatch");
117
+ for (const file of manifest.files) {
118
+ const bytes = readMember(join(root, file.path), immutable);
119
+ if (bytes.length !== file.size || bytesHash(bytes) !== file.sha256) throw new ExecutorError("artifact_digest_mismatch");
120
+ if (immutable && (lstatSync(join(root, file.path)).mode & 511) !== (file.executable ? 320 : 256))
121
+ throw new ExecutorError("artifact_mode_mismatch");
122
+ }
123
+ }
124
+ class ImmutableAdapterArtifacts {
125
+ constructor(root, installationId, workerId, workspaceRoots) {
126
+ this.root = root;
127
+ this.installationId = installationId;
128
+ this.workerId = workerId;
129
+ this.workspaceRoots = workspaceRoots;
130
+ privateDirectory(root);
131
+ outsideWorkspaces(root, workspaceRoots);
132
+ }
133
+ root;
134
+ installationId;
135
+ workerId;
136
+ workspaceRoots;
137
+ approved(input, approvalDigest) {
138
+ const manifest = AdapterManifest.parse(input);
139
+ if (manifest.installationId !== this.installationId || manifest.workerId !== this.workerId) throw new ExecutorError("wrong_resource");
140
+ if (digest(manifest) !== sha256.parse(approvalDigest)) throw new ExecutorError("unapproved_artifact");
141
+ return manifest;
142
+ }
143
+ stage(source, input, approvalDigest) {
144
+ const manifest = this.approved(input, approvalDigest);
145
+ outsideWorkspaces(source, this.workspaceRoots);
146
+ verifyTree(source, manifest, false);
147
+ const destination = join(this.root, approvalDigest);
148
+ if (existsSync(destination)) return this.verify(manifest, approvalDigest);
149
+ const temporary = join(this.root, `.staging-${randomUUID()}`);
150
+ privateDirectory(temporary);
151
+ try {
152
+ for (const file of manifest.files) {
153
+ const target = join(temporary, file.path);
154
+ mkdirSync(dirname(target), { recursive: true, mode: 448 });
155
+ const bytes = readMember(join(source, file.path), false);
156
+ if (bytes.length !== file.size || bytesHash(bytes) !== file.sha256) throw new ExecutorError("artifact_digest_mismatch");
157
+ const fd = openSync(target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
158
+ try {
159
+ writeFileSync(fd, bytes);
160
+ fsyncSync(fd);
161
+ } finally {
162
+ closeSync(fd);
163
+ }
164
+ chmodSync(target, file.executable ? 320 : 256);
165
+ }
166
+ const seal = (dir) => {
167
+ for (const name of readdirSync(dir)) if (lstatSync(join(dir, name)).isDirectory()) seal(join(dir, name));
168
+ chmodSync(dir, 320);
169
+ syncDirectory(dir);
170
+ };
171
+ seal(temporary);
172
+ verifyTree(temporary, manifest, true);
173
+ renameSync(temporary, destination);
174
+ syncDirectory(this.root);
175
+ return destination;
176
+ } catch (error) {
177
+ if (existsSync(temporary)) {
178
+ const unseal = (dir) => {
179
+ chmodSync(dir, 448);
180
+ for (const name of readdirSync(dir)) if (lstatSync(join(dir, name)).isDirectory()) unseal(join(dir, name));
181
+ };
182
+ unseal(temporary);
183
+ rmSync(temporary, { recursive: true });
184
+ }
185
+ throw error;
186
+ }
187
+ }
188
+ verify(input, approvalDigest) {
189
+ const manifest = this.approved(input, approvalDigest), path = join(this.root, approvalDigest);
190
+ verifyTree(path, manifest, true);
191
+ return path;
192
+ }
193
+ }
194
+ export {
195
+ AdapterManifest,
196
+ ImmutableAdapterArtifacts,
197
+ bytesHash,
198
+ canonicalDirectory,
199
+ outsideWorkspaces,
200
+ within
201
+ };
@@ -0,0 +1,93 @@
1
+ import { createHash } from "node:crypto";
2
+ import { realpathSync } from "node:fs";
3
+ import { isAbsolute, relative } from "node:path";
4
+ import { canonicalJson, negotiateProtocol } from "@ricsam/r5d-api/runtime-protocol";
5
+ import {
6
+ AdapterGrant,
7
+ ExecutorError,
8
+ EXECUTOR_CAPABILITY
9
+ } from "../protocol.mjs";
10
+ const digest = (value) => createHash("sha256").update(canonicalJson(value)).digest("hex");
11
+ class AdapterGrantRegistry {
12
+ constructor(db, config) {
13
+ this.db = db;
14
+ this.config = config;
15
+ db.exec(`CREATE TABLE IF NOT EXISTS adapter_grants (
16
+ id TEXT PRIMARY KEY, owner TEXT NOT NULL UNIQUE, release TEXT NOT NULL UNIQUE,
17
+ grant_json TEXT NOT NULL, digest TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('registered','retired')))`);
18
+ for (const row of this.rows()) {
19
+ const grant = AdapterGrant.parse(JSON.parse(row.grant_json));
20
+ if (digest(grant) !== row.digest || grant.installationId !== config.installationId || grant.workerId !== config.workerId || config.principals.some((p) => p.id === grant.principal.id || p.ownerId === grant.principal.ownerId))
21
+ throw new ExecutorError("grant_store_conflict");
22
+ }
23
+ }
24
+ db;
25
+ config;
26
+ rows() {
27
+ return this.db.query("SELECT * FROM adapter_grants").all();
28
+ }
29
+ find(id) {
30
+ const row = this.rows().find((row2) => row2.id === id && row2.state === "registered");
31
+ return row ? AdapterGrant.parse(JSON.parse(row.grant_json)) : void 0;
32
+ }
33
+ hasOwner(owner) {
34
+ return this.rows().some(
35
+ (row) => row.state === "registered" && AdapterGrant.parse(JSON.parse(row.grant_json)).principal.ownerId === owner
36
+ );
37
+ }
38
+ list() {
39
+ return this.rows().map((row) => {
40
+ const grant = AdapterGrant.parse(JSON.parse(row.grant_json));
41
+ return {
42
+ principalId: row.id,
43
+ ownerId: grant.principal.ownerId,
44
+ releaseId: grant.hello.releaseId,
45
+ artifactDigest: grant.artifactDigest,
46
+ grantDigest: row.digest,
47
+ state: row.state
48
+ };
49
+ });
50
+ }
51
+ register(controller, input, keeperHello) {
52
+ if (controller.role !== "controller") throw new ExecutorError("forbidden");
53
+ const grant = AdapterGrant.parse(input), p = grant.principal;
54
+ if (grant.installationId !== this.config.installationId || grant.workerId !== this.config.workerId)
55
+ throw new ExecutorError("wrong_resource");
56
+ negotiateProtocol(keeperHello, grant.hello, [EXECUTOR_CAPABILITY]);
57
+ if (grant.hello.role !== "worker-adapter" || grant.hello.instanceId !== p.ownerId) throw new ExecutorError("wrong_principal");
58
+ if (p.userIds.some((id) => !controller.userIds.includes(id)) || p.lanes.some((lane) => !controller.lanes.includes(lane)) || p.credentialIds.some((id) => !controller.credentialIds.includes(id)) || p.cwdRoots.some(
59
+ (root) => !controller.cwdRoots.some((allowed) => {
60
+ const rel = relative(realpathSync(allowed), realpathSync(root));
61
+ return rel === "" || !isAbsolute(rel) && rel !== ".." && !rel.startsWith("../");
62
+ })
63
+ ))
64
+ throw new ExecutorError("grant_scope_exceeded");
65
+ const hash = digest(grant), rows = this.rows(), old = rows.find((row) => row.id === p.id);
66
+ if (old) {
67
+ if (old.digest !== hash) throw new ExecutorError("grant_identity_conflict");
68
+ if (old.state === "retired") throw new ExecutorError("adapter_retired");
69
+ return this.list().find((r) => r.principalId === p.id);
70
+ }
71
+ if (this.config.principals.some((s) => s.id === p.id || s.ownerId === p.ownerId) || rows.some((row) => {
72
+ const prior = AdapterGrant.parse(JSON.parse(row.grant_json));
73
+ return prior.principal.ownerId === p.ownerId || prior.hello.releaseId === grant.hello.releaseId;
74
+ }))
75
+ throw new ExecutorError("grant_identity_conflict");
76
+ if (rows.filter((row) => row.state === "registered").length + this.config.principals.filter((p2) => p2.role === "adapter").length >= 2)
77
+ throw new ExecutorError("adapter_retention_blocked");
78
+ this.db.query("INSERT INTO adapter_grants VALUES (?,?,?,?,?,'registered')").run(p.id, p.ownerId, grant.hello.releaseId, canonicalJson(grant), hash);
79
+ return this.list().find((r) => r.principalId === p.id);
80
+ }
81
+ retire(controller, id, authoritativeOwner) {
82
+ if (controller.role !== "controller") throw new ExecutorError("forbidden");
83
+ const row = this.list().find((row2) => row2.principalId === id);
84
+ if (!row) throw new ExecutorError("adapter_not_found");
85
+ if (row.ownerId === authoritativeOwner) throw new ExecutorError("adapter_is_authority");
86
+ this.db.query("UPDATE adapter_grants SET state='retired' WHERE id=?").run(id);
87
+ return { ...row, state: "retired" };
88
+ }
89
+ }
90
+ export {
91
+ AdapterGrantRegistry,
92
+ digest
93
+ };
@@ -0,0 +1,101 @@
1
+ import { z } from "zod";
2
+ import { OwnershipFence, RuntimeId } from "@ricsam/r5d-api/runtime-protocol";
3
+ import { AdapterConfig, AdapterGrant, ExecutorCommand, ExecutorError, MAX_FRAME_BYTES } from "../protocol.mjs";
4
+ import { readPrivateJson } from "../storage.mjs";
5
+ import { AdapterManifest } from "./artifacts.mjs";
6
+ import { ReleaseManagerConfig, WorkerAdapterReleases } from "./manager.mjs";
7
+ const Command = z.discriminatedUnion("method", [
8
+ z.object({ method: z.literal("status") }).strict(),
9
+ z.object({
10
+ method: z.literal("stage"),
11
+ source: z.string(),
12
+ manifest: AdapterManifest,
13
+ approvalDigest: z.string(),
14
+ grant: AdapterGrant,
15
+ adapter: AdapterConfig
16
+ }).strict(),
17
+ z.object({ method: z.literal("launch"), principalId: RuntimeId }).strict(),
18
+ z.object({ method: z.literal("stop-adapter"), principalId: RuntimeId }).strict(),
19
+ z.object({ method: z.literal("promote"), principalId: RuntimeId, fence: OwnershipFence, validUntil: z.number().int().positive() }).strict(),
20
+ z.object({ method: z.literal("retire"), principalId: RuntimeId }).strict(),
21
+ z.object({ method: z.literal("request"), callId: RuntimeId, principalId: RuntimeId, command: ExecutorCommand }).strict(),
22
+ z.object({ method: z.literal("reconcile-call"), callId: RuntimeId, principalId: RuntimeId, command: ExecutorCommand }).strict(),
23
+ z.object({ method: z.literal("acknowledge"), callId: RuntimeId }).strict(),
24
+ z.object({ method: z.literal("reconcile") }).strict()
25
+ ]);
26
+ if (import.meta.main) {
27
+ const path = process.argv[2];
28
+ if (!path || process.argv.length !== 3) throw new Error("Usage: bun releases/main.ts /absolute/private/release-manager.json");
29
+ const manager = new WorkerAdapterReleases(ReleaseManagerConfig.parse(readPrivateJson(path)));
30
+ let buffer = Buffer.alloc(0), tail = Promise.resolve(), stopping = false;
31
+ async function handle(line) {
32
+ let id;
33
+ try {
34
+ const message = z.object({ id: RuntimeId, command: Command }).strict().parse(JSON.parse(line.toString("utf8")));
35
+ id = message.id;
36
+ const command = message.command;
37
+ let result;
38
+ switch (command.method) {
39
+ case "status":
40
+ result = { releases: manager.list(), calls: manager.calls(), transitions: manager.transitions() };
41
+ break;
42
+ case "stage":
43
+ result = await manager.stage(command);
44
+ break;
45
+ case "launch":
46
+ result = await manager.launch(command.principalId);
47
+ break;
48
+ case "stop-adapter":
49
+ result = await manager.stopAdapter(command.principalId);
50
+ break;
51
+ case "promote":
52
+ result = await manager.promote(command.principalId, command.fence, command.validUntil);
53
+ break;
54
+ case "retire":
55
+ result = await manager.retire(command.principalId);
56
+ break;
57
+ case "request":
58
+ result = await manager.request(command.callId, command.principalId, command.command);
59
+ break;
60
+ case "reconcile-call":
61
+ result = await manager.reconcileCall(command.callId, command.principalId, command.command);
62
+ break;
63
+ case "acknowledge":
64
+ result = await manager.acknowledge(command.callId);
65
+ break;
66
+ case "reconcile":
67
+ result = await manager.reconcile();
68
+ break;
69
+ }
70
+ process.stdout.write(JSON.stringify({ id, ok: true, result: result ?? null }) + "\n");
71
+ } catch (error) {
72
+ process.stdout.write(
73
+ JSON.stringify({ id, ok: false, error: { code: error instanceof ExecutorError ? error.code : "invalid_request" } }) + "\n"
74
+ );
75
+ }
76
+ }
77
+ const stop = () => {
78
+ if (stopping) return;
79
+ stopping = true;
80
+ void tail.then(() => manager.close()).then(
81
+ () => process.exit(0),
82
+ () => process.exit(1)
83
+ );
84
+ };
85
+ process.stdin.on("data", (chunk) => {
86
+ if (stopping) return;
87
+ buffer = Buffer.concat([buffer, chunk]);
88
+ if (buffer.length > MAX_FRAME_BYTES) {
89
+ stop();
90
+ return;
91
+ }
92
+ while (buffer.includes(10)) {
93
+ const end = buffer.indexOf(10), line = buffer.subarray(0, end);
94
+ buffer = buffer.subarray(end + 1);
95
+ tail = tail.then(() => handle(line));
96
+ }
97
+ });
98
+ process.stdin.once("end", stop);
99
+ process.on("SIGTERM", stop);
100
+ process.on("SIGINT", stop);
101
+ }