@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,217 @@
1
+ import net from "node:net";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { closeSync, constants, lstatSync, openSync } from "node:fs";
5
+ import { Database } from "bun:sqlite";
6
+ import { z } from "zod";
7
+ import { RuntimeId } from "@ricsam/r5d-api/runtime-protocol";
8
+ import { privateDirectory } from "../runtime/storage.mjs";
9
+ const Cursor = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
10
+ const Command = z.discriminatedUnion("method", [
11
+ z.object({ method: z.literal("connect"), streamId: RuntimeId, id: RuntimeId.optional(), port: z.number().int().min(1).max(65535) }).strict(),
12
+ z.object({
13
+ method: z.literal("write"),
14
+ streamId: RuntimeId,
15
+ id: RuntimeId.optional(),
16
+ offset: Cursor,
17
+ base64: z.string().max(44e3),
18
+ end: z.boolean().optional()
19
+ }).strict(),
20
+ z.object({ method: z.literal("read"), streamId: RuntimeId, id: RuntimeId.optional(), after: Cursor }).strict(),
21
+ z.object({ method: z.literal("close"), streamId: RuntimeId, id: RuntimeId.optional() }).strict()
22
+ ]);
23
+ class PersonalTcpError extends Error {
24
+ constructor(code) {
25
+ super(code);
26
+ this.code = code;
27
+ }
28
+ code;
29
+ }
30
+ class PersonalTcpManager {
31
+ constructor(directory, authorizeConnect) {
32
+ this.authorizeConnect = authorizeConnect;
33
+ privateDirectory(directory);
34
+ const file = path.join(directory, "streams.sqlite");
35
+ try {
36
+ closeSync(openSync(file, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 384));
37
+ } catch (error) {
38
+ if (error.code !== "EEXIST") throw error;
39
+ }
40
+ for (const candidate of [file, `${file}-wal`, `${file}-shm`]) {
41
+ try {
42
+ const stat = lstatSync(candidate);
43
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || stat.mode & 63)
44
+ throw new PersonalTcpError("insecure_tcp_store");
45
+ } catch (error) {
46
+ if (error.code !== "ENOENT") throw error;
47
+ }
48
+ }
49
+ this.db = new Database(file);
50
+ this.db.exec(`PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;
51
+ CREATE TABLE IF NOT EXISTS streams(id TEXT PRIMARY KEY,session TEXT NOT NULL,port INTEGER NOT NULL,state TEXT NOT NULL,write_offset INTEGER NOT NULL DEFAULT 0,read_offset INTEGER NOT NULL DEFAULT 0,ack_offset INTEGER NOT NULL DEFAULT 0,eof INTEGER NOT NULL DEFAULT 0);
52
+ CREATE TABLE IF NOT EXISTS writes(stream TEXT NOT NULL,offset INTEGER NOT NULL,hash TEXT NOT NULL,next_offset INTEGER NOT NULL,state TEXT NOT NULL,PRIMARY KEY(stream,offset));
53
+ CREATE TABLE IF NOT EXISTS chunks(stream TEXT NOT NULL,offset INTEGER NOT NULL,data BLOB NOT NULL,PRIMARY KEY(stream,offset));
54
+ CREATE TABLE IF NOT EXISTS reads(id TEXT PRIMARY KEY,stream TEXT NOT NULL,after_offset INTEGER NOT NULL,result TEXT NOT NULL);
55
+ UPDATE streams SET state='unknown' WHERE state IN ('connecting','open');
56
+ UPDATE writes SET state='unknown' WHERE state='pending';`);
57
+ }
58
+ authorizeConnect;
59
+ db;
60
+ sockets = /* @__PURE__ */ new Map();
61
+ stream(sessionId, id) {
62
+ const row = this.db.query("SELECT * FROM streams WHERE id=?").get(id);
63
+ if (!row || row.session !== sessionId) throw new PersonalTcpError("tcp_stream_not_found");
64
+ return row;
65
+ }
66
+ async dispatch(sessionId, raw) {
67
+ RuntimeId.parse(sessionId);
68
+ const command = Command.parse(raw), id = command.streamId;
69
+ if (command.method === "connect") {
70
+ const previous2 = this.db.query("SELECT * FROM streams WHERE id=?").get(id);
71
+ if (previous2) {
72
+ if (previous2.session !== sessionId || previous2.port !== command.port) throw new PersonalTcpError("tcp_stream_identity_changed");
73
+ if (previous2.state !== "open" || !this.sockets.has(id)) throw new PersonalTcpError("tcp_stream_unknown_or_closed");
74
+ return { streamId: id, connected: true };
75
+ }
76
+ if (this.sockets.size >= 16) throw new PersonalTcpError("tcp_stream_capacity");
77
+ await this.authorizeConnect?.(sessionId, command.port);
78
+ if (this.db.query("SELECT id FROM streams WHERE id=?").get(id)) return this.dispatch(sessionId, raw);
79
+ this.db.query("INSERT INTO streams(id,session,port,state) VALUES(?,?,?,'connecting')").run(id, sessionId, command.port);
80
+ const socket = new net.Socket({ allowHalfOpen: true });
81
+ socket.setTimeout(5 * 60 * 1e3, () => socket.destroy(new PersonalTcpError("tcp_stream_idle")));
82
+ this.sockets.set(id, socket);
83
+ socket.on("data", (data) => {
84
+ const row2 = this.stream(sessionId, id);
85
+ if (row2.read_offset - row2.ack_offset + data.length > 8 * 1024 * 1024) {
86
+ this.db.query("UPDATE streams SET state='unknown' WHERE id=?").run(id);
87
+ socket.destroy();
88
+ return;
89
+ }
90
+ this.db.transaction(() => {
91
+ this.db.query("INSERT INTO chunks VALUES(?,?,?)").run(id, row2.read_offset, data);
92
+ this.db.query("UPDATE streams SET read_offset=read_offset+? WHERE id=?").run(data.length, id);
93
+ })();
94
+ });
95
+ socket.on("end", () => this.db.query("UPDATE streams SET eof=1 WHERE id=?").run(id));
96
+ socket.on("error", () => this.db.query("UPDATE streams SET state='unknown' WHERE id=?").run(id));
97
+ socket.on("close", () => {
98
+ this.sockets.delete(id);
99
+ this.db.query("UPDATE streams SET state=CASE WHEN state='unknown' THEN state ELSE 'closed' END,eof=1 WHERE id=?").run(id);
100
+ });
101
+ try {
102
+ await new Promise((resolve, reject) => {
103
+ const timer = setTimeout(() => {
104
+ socket.destroy();
105
+ reject(new PersonalTcpError("tcp_connect_timeout"));
106
+ }, 1e4);
107
+ socket.once("connect", () => {
108
+ clearTimeout(timer);
109
+ resolve();
110
+ });
111
+ socket.once("error", () => {
112
+ clearTimeout(timer);
113
+ reject(new PersonalTcpError("tcp_connect_failed"));
114
+ });
115
+ socket.connect({
116
+ host: "localhost",
117
+ port: command.port,
118
+ autoSelectFamily: true,
119
+ lookup: (_host, _options, callback) => callback(null, [
120
+ { address: "::1", family: 6 },
121
+ { address: "127.0.0.1", family: 4 }
122
+ ])
123
+ });
124
+ });
125
+ this.db.query("UPDATE streams SET state='open' WHERE id=?").run(id);
126
+ return { streamId: id, connected: true };
127
+ } catch (error) {
128
+ this.db.query("UPDATE streams SET state='unknown' WHERE id=?").run(id);
129
+ throw error;
130
+ }
131
+ }
132
+ const row = this.stream(sessionId, id);
133
+ if (command.method === "close") {
134
+ this.db.query("UPDATE streams SET state='closed',eof=1 WHERE id=?").run(id);
135
+ this.sockets.get(id)?.destroy();
136
+ return { closed: true };
137
+ }
138
+ if (command.method === "read") {
139
+ if (command.id) {
140
+ const previous2 = this.db.query("SELECT stream,after_offset,result FROM reads WHERE id=?").get(command.id);
141
+ if (previous2) {
142
+ if (previous2.stream !== id || previous2.after_offset !== command.after) throw new PersonalTcpError("tcp_read_identity_changed");
143
+ return JSON.parse(previous2.result);
144
+ }
145
+ }
146
+ if (command.after < row.ack_offset || command.after > row.read_offset) throw new PersonalTcpError("tcp_read_cursor_invalid");
147
+ const chunks = this.db.query("SELECT offset,data FROM chunks WHERE stream=? AND offset+length(data)>? ORDER BY offset").all(id, command.after);
148
+ let remaining = 32768;
149
+ const selected = [];
150
+ for (const chunk of chunks) {
151
+ const value = Buffer.from(chunk.data).subarray(
152
+ Math.max(0, command.after - chunk.offset),
153
+ Math.max(0, command.after - chunk.offset) + remaining
154
+ );
155
+ selected.push(value);
156
+ remaining -= value.length;
157
+ if (!remaining) break;
158
+ }
159
+ this.db.transaction(() => {
160
+ this.db.query("UPDATE streams SET ack_offset=? WHERE id=?").run(command.after, id);
161
+ this.db.query("DELETE FROM chunks WHERE stream=? AND offset+length(data)<=?").run(id, command.after);
162
+ })();
163
+ const data = Buffer.concat(selected), nextOffset2 = command.after + data.length;
164
+ if (row.state === "unknown" && !data.length) throw new PersonalTcpError("tcp_stream_unknown");
165
+ const result = { base64: data.toString("base64"), nextOffset: nextOffset2, eof: !!row.eof && nextOffset2 === row.read_offset };
166
+ if (command.id) this.db.query("INSERT INTO reads VALUES(?,?,?,?)").run(command.id, id, command.after, JSON.stringify(result));
167
+ return result;
168
+ }
169
+ const bytes = Buffer.from(command.base64, "base64");
170
+ if (bytes.length > 32768 || bytes.toString("base64") !== command.base64 || !bytes.length && !command.end)
171
+ throw new PersonalTcpError("tcp_invalid_bytes");
172
+ const hash = createHash("sha256").update(bytes).update(command.end ? ":end" : ":more").digest("hex");
173
+ const previous = this.db.query("SELECT * FROM writes WHERE stream=? AND offset=?").get(id, command.offset);
174
+ if (previous) {
175
+ if (previous.hash !== hash) throw new PersonalTcpError("tcp_write_identity_changed");
176
+ if (previous.state !== "completed") throw new PersonalTcpError("tcp_write_unknown");
177
+ return { nextOffset: previous.next_offset };
178
+ }
179
+ if (row.state !== "open" || !this.sockets.has(id)) throw new PersonalTcpError("tcp_stream_unknown_or_closed");
180
+ if (command.offset !== row.write_offset) throw new PersonalTcpError("tcp_write_cursor_invalid");
181
+ const nextOffset = command.offset + bytes.length;
182
+ this.db.query("INSERT INTO writes VALUES(?,?,?,?,'pending')").run(id, command.offset, hash, nextOffset);
183
+ try {
184
+ const socket = this.sockets.get(id);
185
+ await new Promise((resolve, reject) => {
186
+ socket.write(bytes, (error) => {
187
+ if (error) reject(new PersonalTcpError("tcp_write_unknown"));
188
+ else resolve();
189
+ });
190
+ });
191
+ if (command.end) socket.end();
192
+ this.db.transaction(() => {
193
+ this.db.query("UPDATE writes SET state='completed' WHERE stream=? AND offset=?").run(id, command.offset);
194
+ this.db.query("UPDATE streams SET write_offset=? WHERE id=?").run(nextOffset, id);
195
+ })();
196
+ return { nextOffset };
197
+ } catch (error) {
198
+ this.db.query("UPDATE writes SET state='unknown' WHERE stream=? AND offset=?").run(id, command.offset);
199
+ throw error;
200
+ }
201
+ }
202
+ async close() {
203
+ await Promise.all(
204
+ [...this.sockets.values()].map(
205
+ (socket) => new Promise((resolve) => {
206
+ socket.once("close", resolve);
207
+ socket.destroy();
208
+ })
209
+ )
210
+ );
211
+ this.db.close();
212
+ }
213
+ }
214
+ export {
215
+ PersonalTcpError,
216
+ PersonalTcpManager
217
+ };
@@ -0,0 +1,42 @@
1
+ import { HostExecutorClient } from "./client.mjs";
2
+ import { AdapterConfig, ExecutorCommand, ExecutorError, MAX_FRAME_BYTES } from "./protocol.mjs";
3
+ import { readPrivateJson } from "./storage.mjs";
4
+ if (import.meta.main) {
5
+ const path = process.argv[2];
6
+ if (!path || process.argv.length !== 3) throw new Error("Usage: bun adapter.ts /absolute/private/adapter-config.json");
7
+ const client = new HostExecutorClient(AdapterConfig.parse(readPrivateJson(path)));
8
+ let buffer = Buffer.alloc(0);
9
+ process.stdin.on("data", (chunk) => {
10
+ buffer = Buffer.concat([buffer, chunk]);
11
+ if (buffer.length > MAX_FRAME_BYTES) process.exit(1);
12
+ while (buffer.includes(10)) {
13
+ const newline = buffer.indexOf(10);
14
+ const line = buffer.subarray(0, newline);
15
+ buffer = buffer.subarray(newline + 1);
16
+ void (async () => {
17
+ let id;
18
+ let method;
19
+ try {
20
+ const message = JSON.parse(line.toString("utf8"));
21
+ if (typeof message.id !== "string" || message.id.length > 200) throw new ExecutorError("invalid_request");
22
+ id = message.id;
23
+ const command = ExecutorCommand.parse(message.command);
24
+ method = command.method;
25
+ const result = await client.request(command);
26
+ process.stdout.write(JSON.stringify({ id, ok: true, result }) + "\n");
27
+ } catch (error) {
28
+ process.stdout.write(
29
+ JSON.stringify({
30
+ id,
31
+ ok: false,
32
+ error: {
33
+ code: error instanceof ExecutorError ? error.code : "invalid_request",
34
+ ...method === "start" && error instanceof ExecutorError && error.rejectedBeforeAdmission && (error.code === "stale_fence" || error.code === "capacity_busy") ? { rejectedBeforeAdmission: true } : {}
35
+ }
36
+ }) + "\n"
37
+ );
38
+ }
39
+ })();
40
+ }
41
+ });
42
+ }
@@ -0,0 +1,95 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstatSync } from "node:fs";
3
+ import { createConnection } from "node:net";
4
+ import {
5
+ AdapterConfig,
6
+ ExecutorError,
7
+ isStartNonAdmissionCode,
8
+ MAX_FRAME_BYTES
9
+ } from "./protocol.mjs";
10
+ class HostExecutorClient {
11
+ config;
12
+ constructor(config) {
13
+ this.config = AdapterConfig.parse(config);
14
+ }
15
+ request(command) {
16
+ const stat = lstatSync(this.config.socketPath);
17
+ if (!stat.isSocket() || stat.uid !== process.getuid?.() || stat.mode & 63) throw new ExecutorError("insecure_socket");
18
+ const request = JSON.stringify({
19
+ protocol: 1,
20
+ requestId: randomUUID(),
21
+ auth: { principalId: this.config.principalId, token: this.config.token },
22
+ hello: this.config.hello,
23
+ command
24
+ }) + "\n";
25
+ if (Buffer.byteLength(request) > MAX_FRAME_BYTES) throw new ExecutorError("request_too_large");
26
+ return new Promise((resolve, reject) => {
27
+ const socket = createConnection(this.config.socketPath);
28
+ let buffer = Buffer.alloc(0);
29
+ let settled = false;
30
+ const finish = (error, value) => {
31
+ if (settled) return;
32
+ settled = true;
33
+ socket.destroy();
34
+ if (error) reject(error);
35
+ else resolve(value);
36
+ };
37
+ socket.setTimeout(this.config.timeoutMs, () => finish(new ExecutorError("transport_outcome_unknown")));
38
+ socket.once("connect", () => socket.write(request));
39
+ socket.on("error", () => finish(new ExecutorError("transport_outcome_unknown")));
40
+ socket.once("close", () => finish(new ExecutorError("transport_outcome_unknown")));
41
+ socket.on("data", (chunk) => {
42
+ buffer = Buffer.concat([buffer, chunk]);
43
+ if (buffer.length > MAX_FRAME_BYTES) {
44
+ finish(new ExecutorError("invalid_response"));
45
+ return;
46
+ }
47
+ if (!buffer.includes(10)) return;
48
+ try {
49
+ const response = JSON.parse(buffer.toString("utf8"));
50
+ if (response.ok === true) finish(void 0, response.result);
51
+ else
52
+ finish(
53
+ new ExecutorError(
54
+ response.error?.code ?? "invalid_response",
55
+ void 0,
56
+ response.ok === false && command.method === "start" && isStartNonAdmissionCode(response.error?.code) && response.error?.rejectedBeforeAdmission === true
57
+ )
58
+ );
59
+ } catch {
60
+ finish(new ExecutorError("invalid_response"));
61
+ }
62
+ });
63
+ });
64
+ }
65
+ status() {
66
+ return this.request({ method: "status" });
67
+ }
68
+ registerAdapter(grant) {
69
+ return this.request({ method: "adapter.register", grant });
70
+ }
71
+ retireAdapter(principalId) {
72
+ return this.request({ method: "adapter.retire", principalId });
73
+ }
74
+ setAuthority(fence, validUntil) {
75
+ return this.request({ method: "authority", fence, validUntil });
76
+ }
77
+ start(operation) {
78
+ return this.request({ method: "start", operation });
79
+ }
80
+ poll(run, stdoutOffset = 0, stderrOffset = 0, maxBytes = 32768) {
81
+ return this.request({ method: "poll", run, stdoutOffset, stderrOffset, maxBytes });
82
+ }
83
+ write(run, actionId, data, eof = false) {
84
+ return this.request({ method: "write", run, actionId, data, eof });
85
+ }
86
+ resize(run, actionId, cols, rows) {
87
+ return this.request({ method: "resize", run, actionId, cols, rows });
88
+ }
89
+ cancel(run, actionId) {
90
+ return this.request({ method: "cancel", run, actionId });
91
+ }
92
+ }
93
+ export {
94
+ HostExecutorClient
95
+ };
@@ -0,0 +1,131 @@
1
+ import { chmodSync, lstatSync, unlinkSync } from "node:fs";
2
+ import { createServer } from "node:net";
3
+ import { join } from "node:path";
4
+ import { HostExecutor } from "./executor.mjs";
5
+ import { ExecutorConfig, ExecutorError, MAX_FRAME_BYTES } from "./protocol.mjs";
6
+ import { readPrivateJson } from "./storage.mjs";
7
+ import { preflightNativePty } from "./pty.mjs";
8
+ async function startHostExecutor(input) {
9
+ if (process.platform === "win32") throw new ExecutorError("unsupported_platform");
10
+ const config = ExecutorConfig.parse(input);
11
+ let pty;
12
+ try {
13
+ pty = await import("node-pty");
14
+ } catch {
15
+ }
16
+ if (pty) preflightNativePty();
17
+ const executor = new HostExecutor(config, pty);
18
+ const socketPath = join(config.stateDir, "executor.sock");
19
+ const sockets = /* @__PURE__ */ new Set();
20
+ let closing;
21
+ let pending = 0;
22
+ const server = createServer((socket) => {
23
+ if (sockets.size >= 72) {
24
+ socket.destroy();
25
+ return;
26
+ }
27
+ sockets.add(socket);
28
+ socket.on("close", () => sockets.delete(socket));
29
+ socket.on("error", () => {
30
+ });
31
+ socket.setTimeout(1e4, () => socket.destroy());
32
+ let bytes = Buffer.alloc(0);
33
+ let received = false;
34
+ socket.on("data", (chunk) => {
35
+ if (received) {
36
+ socket.destroy();
37
+ return;
38
+ }
39
+ bytes = Buffer.concat([bytes, chunk]);
40
+ if (bytes.length > MAX_FRAME_BYTES) {
41
+ socket.destroy();
42
+ return;
43
+ }
44
+ const newline = bytes.indexOf(10);
45
+ if (newline < 0) return;
46
+ received = true;
47
+ if (newline !== bytes.length - 1) {
48
+ socket.destroy();
49
+ return;
50
+ }
51
+ let request;
52
+ try {
53
+ request = JSON.parse(bytes.subarray(0, newline).toString("utf8"));
54
+ } catch {
55
+ socket.end(JSON.stringify({ ok: false, error: { code: "invalid_request" } }) + "\n");
56
+ return;
57
+ }
58
+ const authorityHint = request?.command?.method === "authority";
59
+ if (pending >= 72 || pending >= 64 && !authorityHint) {
60
+ socket.end(JSON.stringify({ ok: false, error: { code: "ipc_busy" } }) + "\n");
61
+ return;
62
+ }
63
+ pending++;
64
+ void executor.handle(request).then(
65
+ (result) => socket.end(JSON.stringify({ ok: true, result }) + "\n"),
66
+ (error) => socket.end(
67
+ JSON.stringify({
68
+ ok: false,
69
+ error: {
70
+ code: error instanceof ExecutorError ? error.code : "invalid_request",
71
+ ...request?.command?.method === "start" && error instanceof ExecutorError && error.rejectedBeforeAdmission && (error.code === "stale_fence" || error.code === "capacity_busy") ? { rejectedBeforeAdmission: true } : {}
72
+ }
73
+ }) + "\n"
74
+ )
75
+ ).finally(() => {
76
+ pending--;
77
+ });
78
+ });
79
+ });
80
+ try {
81
+ try {
82
+ const stat = lstatSync(socketPath);
83
+ if (!stat.isSocket() || stat.uid !== process.getuid?.()) throw new ExecutorError("unsafe_socket_path");
84
+ unlinkSync(socketPath);
85
+ } catch (error) {
86
+ if (error.code !== "ENOENT") throw error;
87
+ }
88
+ await new Promise((resolve, reject) => {
89
+ server.once("error", reject);
90
+ server.listen(socketPath, () => {
91
+ server.removeListener("error", reject);
92
+ resolve();
93
+ });
94
+ });
95
+ chmodSync(socketPath, 384);
96
+ } catch (error) {
97
+ await executor.close();
98
+ throw error;
99
+ }
100
+ return {
101
+ executor,
102
+ socketPath,
103
+ close() {
104
+ return closing ??= (async () => {
105
+ for (const socket of sockets) socket.destroy();
106
+ await new Promise((resolve) => server.close(() => resolve()));
107
+ await executor.close();
108
+ })();
109
+ }
110
+ };
111
+ }
112
+ if (import.meta.main) {
113
+ const path = process.argv[2];
114
+ if (!path || process.argv.length !== 3) throw new Error("Usage: bun daemon.ts /absolute/private/executor-config.json");
115
+ const daemon = await startHostExecutor(ExecutorConfig.parse(readPrivateJson(path)));
116
+ process.stdout.write(JSON.stringify({ ready: true, pid: process.pid, protocol: 1 }) + "\n");
117
+ let stopping = false;
118
+ const stop = () => {
119
+ if (stopping) return;
120
+ stopping = true;
121
+ void daemon.close().then(
122
+ () => process.exit(0),
123
+ () => process.exit(1)
124
+ );
125
+ };
126
+ process.on("SIGTERM", stop);
127
+ process.on("SIGINT", stop);
128
+ }
129
+ export {
130
+ startHostExecutor
131
+ };