@ricsam/r5d-worker 0.0.137 → 0.0.139

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 (236) hide show
  1. package/README.md +4 -94
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/mjs/main.mjs +46 -7543
  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 +359 -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 +3 -1
  41. package/dist/types/runtime/executor.d.ts +6 -1
  42. package/dist/types/runtime/protocol.d.ts +176 -4
  43. package/dist/types/runtime/pty.d.ts +1 -0
  44. package/dist/types/runtime/releases/artifacts.d.ts +51 -0
  45. package/dist/types/runtime/releases/grants.d.ts +16 -0
  46. package/dist/types/runtime/releases/manager.d.ts +145 -0
  47. package/dist/types/runtime/releases/process.d.ts +14 -0
  48. package/dist/types/runtime/releases/retirement.d.ts +15 -0
  49. package/dist/types/runtime/releases/rpc-client.d.ts +52 -0
  50. package/dist/types/runtime/releases/rpc-main.d.ts +10 -0
  51. package/dist/types/runtime/releases/rpc-protocol.d.ts +1139 -0
  52. package/dist/types/runtime/workspace/artifacts.d.ts +30 -0
  53. package/dist/types/runtime/workspace/authority.d.ts +338 -0
  54. package/dist/types/runtime/workspace/contracts.d.ts +86 -0
  55. package/dist/types/runtime/workspace/file-write.d.ts +14 -0
  56. package/dist/types/runtime/workspace/files.d.ts +21 -0
  57. package/dist/types/runtime/workspace/http-body.d.ts +9 -0
  58. package/dist/types/runtime/workspace/storage-client.d.ts +23 -0
  59. package/dist/types/runtime/workspace/storage-wire.d.ts +138 -0
  60. package/package.json +8 -6
  61. package/dist/cjs/atomic-rename.cjs +0 -303
  62. package/dist/cjs/browser-upload.cjs +0 -73
  63. package/dist/cjs/cli-update.cjs +0 -108
  64. package/dist/cjs/command-launcher.cjs +0 -423
  65. package/dist/cjs/control-command-policy.cjs +0 -177
  66. package/dist/cjs/git-blob-hash.cjs +0 -41
  67. package/dist/cjs/git-identity.cjs +0 -39
  68. package/dist/cjs/git-process-environment.cjs +0 -133
  69. package/dist/cjs/inline-output.cjs +0 -62
  70. package/dist/cjs/main.cjs +0 -7517
  71. package/dist/cjs/managed-paths.cjs +0 -215
  72. package/dist/cjs/plan-lint.cjs +0 -165
  73. package/dist/cjs/plan-parser.cjs +0 -127
  74. package/dist/cjs/plan-paths.cjs +0 -53
  75. package/dist/cjs/port-forward-client.cjs +0 -206
  76. package/dist/cjs/process-tree.cjs +0 -117
  77. package/dist/cjs/project-checkout-garbage.cjs +0 -219
  78. package/dist/cjs/project-mirror-fetch-policy.cjs +0 -82
  79. package/dist/cjs/project-mirror-refs-token.cjs +0 -85
  80. package/dist/cjs/project-workspace-state.cjs +0 -972
  81. package/dist/cjs/project-worktrees.cjs +0 -1713
  82. package/dist/cjs/pty-output-coalescer.cjs +0 -87
  83. package/dist/cjs/recovery-journal-protocol.cjs +0 -56
  84. package/dist/cjs/recovery-journal-runtime.cjs +0 -133
  85. package/dist/cjs/recovery-journal-thread.cjs +0 -9
  86. package/dist/cjs/recovery-journal.cjs +0 -742
  87. package/dist/cjs/recovery-output-window.cjs +0 -77
  88. package/dist/cjs/recovery-protocol.cjs +0 -31
  89. package/dist/cjs/recovery-store.cjs +0 -252
  90. package/dist/cjs/registry-auth.cjs +0 -310
  91. package/dist/cjs/repository-transition-policy.cjs +0 -49
  92. package/dist/cjs/runtime-version.cjs +0 -88
  93. package/dist/cjs/session-file-mutations.cjs +0 -61
  94. package/dist/cjs/supervisor.cjs +0 -147
  95. package/dist/cjs/three-way-merge.cjs +0 -346
  96. package/dist/cjs/working-tree-mirror.cjs +0 -1872
  97. package/dist/cjs/workspace-automatic-sync-policy.cjs +0 -79
  98. package/dist/cjs/workspace-branch-incarnation-policy.cjs +0 -37
  99. package/dist/cjs/workspace-command-sync-policy.cjs +0 -112
  100. package/dist/cjs/workspace-command-targets.cjs +0 -63
  101. package/dist/cjs/workspace-filesystem-executor-thread.cjs +0 -36
  102. package/dist/cjs/workspace-filesystem-executor.cjs +0 -327
  103. package/dist/cjs/workspace-filesystem-job-types.cjs +0 -144
  104. package/dist/cjs/workspace-filesystem-jobs.cjs +0 -59
  105. package/dist/cjs/workspace-git-sync.cjs +0 -3595
  106. package/dist/cjs/workspace-hydration-ledger.cjs +0 -83
  107. package/dist/cjs/workspace-hydration-merge.cjs +0 -448
  108. package/dist/cjs/workspace-hydration-recovery-state.cjs +0 -53
  109. package/dist/cjs/workspace-incident-state.cjs +0 -37
  110. package/dist/cjs/workspace-merge-projection.cjs +0 -628
  111. package/dist/cjs/workspace-mount-boundary.cjs +0 -71
  112. package/dist/cjs/workspace-mount-hold-fence.cjs +0 -120
  113. package/dist/cjs/workspace-mutation-gate.cjs +0 -127
  114. package/dist/cjs/workspace-path-move.cjs +0 -195
  115. package/dist/cjs/workspace-preserve-only-policy.cjs +0 -36
  116. package/dist/cjs/workspace-project-config-policy.cjs +0 -53
  117. package/dist/cjs/workspace-projection-ledger.cjs +0 -81
  118. package/dist/cjs/workspace-publication-evidence.cjs +0 -46
  119. package/dist/cjs/workspace-sync-coalescer.cjs +0 -58
  120. package/dist/mjs/atomic-rename.mjs +0 -261
  121. package/dist/mjs/browser-upload.mjs +0 -42
  122. package/dist/mjs/command-launcher.mjs +0 -393
  123. package/dist/mjs/control-command-policy.mjs +0 -146
  124. package/dist/mjs/git-blob-hash.mjs +0 -16
  125. package/dist/mjs/git-identity.mjs +0 -15
  126. package/dist/mjs/git-process-environment.mjs +0 -105
  127. package/dist/mjs/inline-output.mjs +0 -37
  128. package/dist/mjs/managed-paths.mjs +0 -174
  129. package/dist/mjs/plan-lint.mjs +0 -129
  130. package/dist/mjs/plan-parser.mjs +0 -94
  131. package/dist/mjs/plan-paths.mjs +0 -25
  132. package/dist/mjs/port-forward-client.mjs +0 -172
  133. package/dist/mjs/project-checkout-garbage.mjs +0 -178
  134. package/dist/mjs/project-mirror-fetch-policy.mjs +0 -56
  135. package/dist/mjs/project-mirror-refs-token.mjs +0 -53
  136. package/dist/mjs/project-workspace-state.mjs +0 -937
  137. package/dist/mjs/project-worktrees.mjs +0 -1657
  138. package/dist/mjs/pty-output-coalescer.mjs +0 -61
  139. package/dist/mjs/recovery-journal-protocol.mjs +0 -30
  140. package/dist/mjs/recovery-journal-runtime.mjs +0 -112
  141. package/dist/mjs/recovery-journal-thread.mjs +0 -8
  142. package/dist/mjs/recovery-journal.mjs +0 -694
  143. package/dist/mjs/recovery-output-window.mjs +0 -53
  144. package/dist/mjs/recovery-protocol.mjs +0 -6
  145. package/dist/mjs/recovery-store.mjs +0 -217
  146. package/dist/mjs/registry-auth.mjs +0 -269
  147. package/dist/mjs/repository-transition-policy.mjs +0 -22
  148. package/dist/mjs/runtime-version.mjs +0 -54
  149. package/dist/mjs/session-file-mutations.mjs +0 -37
  150. package/dist/mjs/supervisor.mjs +0 -114
  151. package/dist/mjs/three-way-merge.mjs +0 -318
  152. package/dist/mjs/working-tree-mirror.mjs +0 -1817
  153. package/dist/mjs/workspace-automatic-sync-policy.mjs +0 -48
  154. package/dist/mjs/workspace-branch-incarnation-policy.mjs +0 -13
  155. package/dist/mjs/workspace-command-sync-policy.mjs +0 -84
  156. package/dist/mjs/workspace-command-targets.mjs +0 -37
  157. package/dist/mjs/workspace-filesystem-executor-thread.mjs +0 -38
  158. package/dist/mjs/workspace-filesystem-executor.mjs +0 -287
  159. package/dist/mjs/workspace-filesystem-job-types.mjs +0 -116
  160. package/dist/mjs/workspace-filesystem-jobs.mjs +0 -55
  161. package/dist/mjs/workspace-git-sync.mjs +0 -3553
  162. package/dist/mjs/workspace-hydration-ledger.mjs +0 -58
  163. package/dist/mjs/workspace-hydration-merge.mjs +0 -414
  164. package/dist/mjs/workspace-hydration-recovery-state.mjs +0 -29
  165. package/dist/mjs/workspace-incident-state.mjs +0 -12
  166. package/dist/mjs/workspace-merge-projection.mjs +0 -594
  167. package/dist/mjs/workspace-mount-boundary.mjs +0 -37
  168. package/dist/mjs/workspace-mount-hold-fence.mjs +0 -95
  169. package/dist/mjs/workspace-mutation-gate.mjs +0 -103
  170. package/dist/mjs/workspace-path-move.mjs +0 -160
  171. package/dist/mjs/workspace-preserve-only-policy.mjs +0 -11
  172. package/dist/mjs/workspace-project-config-policy.mjs +0 -27
  173. package/dist/mjs/workspace-projection-ledger.mjs +0 -57
  174. package/dist/mjs/workspace-publication-evidence.mjs +0 -21
  175. package/dist/mjs/workspace-sync-coalescer.mjs +0 -34
  176. package/dist/types/atomic-rename.d.ts +0 -78
  177. package/dist/types/browser-upload.d.ts +0 -7
  178. package/dist/types/command-launcher.d.ts +0 -104
  179. package/dist/types/control-command-policy.d.ts +0 -53
  180. package/dist/types/git-blob-hash.d.ts +0 -10
  181. package/dist/types/git-identity.d.ts +0 -9
  182. package/dist/types/git-process-environment.d.ts +0 -9
  183. package/dist/types/inline-output.d.ts +0 -3
  184. package/dist/types/managed-paths.d.ts +0 -19
  185. package/dist/types/plan-lint.d.ts +0 -16
  186. package/dist/types/plan-parser.d.ts +0 -38
  187. package/dist/types/plan-paths.d.ts +0 -36
  188. package/dist/types/port-forward-client.d.ts +0 -8
  189. package/dist/types/project-checkout-garbage.d.ts +0 -86
  190. package/dist/types/project-mirror-fetch-policy.d.ts +0 -84
  191. package/dist/types/project-mirror-refs-token.d.ts +0 -53
  192. package/dist/types/project-workspace-state.d.ts +0 -188
  193. package/dist/types/project-worktrees.d.ts +0 -397
  194. package/dist/types/pty-output-coalescer.d.ts +0 -20
  195. package/dist/types/recovery-journal-protocol.d.ts +0 -35
  196. package/dist/types/recovery-journal-runtime.d.ts +0 -12
  197. package/dist/types/recovery-journal-stall-fixture.d.ts +0 -1
  198. package/dist/types/recovery-journal-thread.d.ts +0 -1
  199. package/dist/types/recovery-journal.d.ts +0 -248
  200. package/dist/types/recovery-output-window.d.ts +0 -19
  201. package/dist/types/recovery-protocol.d.ts +0 -59
  202. package/dist/types/recovery-store.d.ts +0 -52
  203. package/dist/types/registry-auth.d.ts +0 -47
  204. package/dist/types/repository-transition-policy.d.ts +0 -26
  205. package/dist/types/runtime-version.d.ts +0 -1
  206. package/dist/types/session-file-mutations.d.ts +0 -22
  207. package/dist/types/supervisor-daemonized-fixture.d.ts +0 -1
  208. package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +0 -1
  209. package/dist/types/supervisor-signal-fixture.d.ts +0 -1
  210. package/dist/types/supervisor.d.ts +0 -23
  211. package/dist/types/three-way-merge.d.ts +0 -77
  212. package/dist/types/working-tree-mirror.d.ts +0 -424
  213. package/dist/types/workspace-automatic-sync-policy.d.ts +0 -57
  214. package/dist/types/workspace-branch-incarnation-policy.d.ts +0 -14
  215. package/dist/types/workspace-command-sync-policy.d.ts +0 -57
  216. package/dist/types/workspace-command-targets.d.ts +0 -37
  217. package/dist/types/workspace-filesystem-executor-thread.d.ts +0 -1
  218. package/dist/types/workspace-filesystem-executor.d.ts +0 -123
  219. package/dist/types/workspace-filesystem-job-types.d.ts +0 -302
  220. package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
  221. package/dist/types/workspace-git-sync.d.ts +0 -514
  222. package/dist/types/workspace-hydration-ledger.d.ts +0 -76
  223. package/dist/types/workspace-hydration-merge.d.ts +0 -103
  224. package/dist/types/workspace-hydration-recovery-state.d.ts +0 -10
  225. package/dist/types/workspace-incident-state.d.ts +0 -19
  226. package/dist/types/workspace-merge-projection.d.ts +0 -69
  227. package/dist/types/workspace-mount-boundary.d.ts +0 -10
  228. package/dist/types/workspace-mount-hold-fence.d.ts +0 -42
  229. package/dist/types/workspace-mutation-gate.d.ts +0 -42
  230. package/dist/types/workspace-path-move.d.ts +0 -21
  231. package/dist/types/workspace-preserve-only-policy.d.ts +0 -15
  232. package/dist/types/workspace-project-config-policy.d.ts +0 -48
  233. package/dist/types/workspace-projection-ledger.d.ts +0 -59
  234. package/dist/types/workspace-publication-evidence.d.ts +0 -17
  235. package/dist/types/workspace-sync-coalescer.d.ts +0 -28
  236. /package/dist/types/{credential-authority-lock-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
+ };