@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
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.137",
3
+ "version": "0.0.139",
4
4
  "type": "module"
5
5
  }
@@ -0,0 +1,31 @@
1
+ class PersonalActionScheduler {
2
+ constructor(execute, failed, limit = 8) {
3
+ this.execute = execute;
4
+ this.failed = failed;
5
+ this.limit = limit;
6
+ }
7
+ execute;
8
+ failed;
9
+ limit;
10
+ active = /* @__PURE__ */ new Map();
11
+ accept(actions) {
12
+ for (const action of actions) {
13
+ const previous = this.active.get(action.id);
14
+ if (previous) {
15
+ if (previous.hash !== action.requestHash) throw new Error("Personal action identity changed while active");
16
+ continue;
17
+ }
18
+ const request = action.request;
19
+ const lane = request.kind === "tcp" && typeof request.command?.streamId === "string" ? `tcp:${request.sessionId}:${request.command.streamId}` : request.kind === "inspect" && ["processes", "status"].includes(request.command?.method ?? "") ? `inspect:${request.sessionId}` : "mutation";
20
+ if (this.active.size >= this.limit || [...this.active.values()].some((value) => value.lane === lane)) continue;
21
+ const task = Promise.resolve().then(() => this.execute(action)).catch(this.failed).finally(() => this.active.delete(action.id));
22
+ this.active.set(action.id, { hash: action.requestHash, lane, task });
23
+ }
24
+ }
25
+ async drain() {
26
+ await Promise.all([...this.active.values()].map((value) => value.task));
27
+ }
28
+ }
29
+ export {
30
+ PersonalActionScheduler
31
+ };
@@ -0,0 +1,215 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import os from "node:os";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { promises as fs, openSync, closeSync } from "node:fs";
6
+ import { Database } from "bun:sqlite";
7
+ import { canonicalJson } from "@ricsam/r5d-api/runtime-protocol";
8
+ import { privateDirectory, readPrivateJson } from "../runtime/storage.mjs";
9
+ import { openPersonalWorkerRuntime, PersonalWorkerGrant } from "./runtime.mjs";
10
+ import { installCliUpdate, readInstalledCliVersion } from "../cli-update.mjs";
11
+ import { WorkspaceError } from "../runtime/workspace/contracts.mjs";
12
+ import { PersonalActionScheduler } from "./action-scheduler.mjs";
13
+ class PersonalResponseError extends Error {
14
+ constructor(code, status, rejectedBeforeAdmission) {
15
+ super(`Personal worker request failed (${status})`);
16
+ this.code = code;
17
+ this.status = status;
18
+ this.rejectedBeforeAdmission = rejectedBeforeAdmission;
19
+ }
20
+ code;
21
+ status;
22
+ rejectedBeforeAdmission;
23
+ }
24
+ const storageErrorCodes = /* @__PURE__ */ new Set(["invalid_input", "conflict", "not_found", "busy", "stale_fence", "unknown", "unsafe_path", "git_failed"]);
25
+ function parsePersonalWorkerOptions(args, environment = process.env) {
26
+ const values = {};
27
+ const allowed = /* @__PURE__ */ new Set(["label", "base-url", "token", "api-key", "config", "root"]);
28
+ for (let index = 0; index < args.length; index++) {
29
+ const arg = args[index];
30
+ const match = /^--([^=]+)(?:=(.*))?$/.exec(arg);
31
+ if (!match || !allowed.has(match[1])) throw new Error("Unknown worker option");
32
+ const key = match[1];
33
+ if (key in values) throw new Error("Duplicate worker option");
34
+ const value = match[2] ?? args[++index];
35
+ if (!value || value.startsWith("--")) throw new Error(`Missing value for --${key}`);
36
+ values[key] = value;
37
+ }
38
+ const configPath = values.config ?? path.join(os.homedir(), ".config/r5d/r5dctl/config.json");
39
+ let saved = {};
40
+ try {
41
+ saved = readPrivateJson(configPath);
42
+ } catch (error) {
43
+ if (error.code !== "ENOENT") throw error;
44
+ }
45
+ const string = (value) => typeof value === "string" ? value : void 0;
46
+ const label = values.label;
47
+ if (!label || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/.test(label))
48
+ throw new Error("A worker --label is required (letters, numbers, dots, underscores or hyphens)");
49
+ const origin = new URL(values["base-url"] ?? environment.R5D_BASE_URL ?? string(saved.baseUrl) ?? "https://r5d.dev");
50
+ if (origin.username || origin.password || origin.search || origin.hash || origin.pathname !== "/" || origin.protocol !== "https:" && !(origin.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(origin.hostname)))
51
+ throw new Error("Worker base URL must use HTTPS or local loopback");
52
+ const credential = values.token ?? values["api-key"] ?? environment.R5D_TOKEN ?? environment.R5D_API_KEY ?? string(saved.token) ?? string(saved.apiKey);
53
+ if (!credential) throw new Error(`Authenticate this worker with: r5dctl auth login --worker-label ${label}`);
54
+ return {
55
+ label,
56
+ baseUrl: origin.origin,
57
+ credential,
58
+ root: path.resolve(values.root ?? environment.R5D_ROOT ?? path.join(os.homedir(), ".r5d"))
59
+ };
60
+ }
61
+ function safeError(error) {
62
+ const value = error.code;
63
+ return typeof value === "string" && /^[a-zA-Z0-9_-]{1,100}$/.test(value) ? value : "personal_operation_unknown";
64
+ }
65
+ async function startPersonalWorker(options, version) {
66
+ const key = createHash("sha256").update(canonicalJson([options.baseUrl, options.label])).digest("hex").slice(0, 32), root = path.join(options.root, "personal", key);
67
+ privateDirectory(root);
68
+ const identityFile = path.join(root, "identity.json");
69
+ let identity;
70
+ try {
71
+ identity = readPrivateJson(identityFile);
72
+ } catch (error) {
73
+ if (error.code !== "ENOENT") throw error;
74
+ identity = { resourceId: `worker-${randomUUID()}`, instanceId: `host-${randomUUID()}` };
75
+ const handle = await fs.open(identityFile, "wx", 384);
76
+ try {
77
+ await handle.writeFile(canonicalJson(identity));
78
+ await handle.sync();
79
+ } finally {
80
+ await handle.close();
81
+ }
82
+ }
83
+ const endpoint = `/api/personal/resources/worker/${encodeURIComponent(identity.resourceId)}`;
84
+ async function request(pathname, body) {
85
+ const response = await fetch(options.baseUrl + pathname, {
86
+ method: "POST",
87
+ headers: { authorization: `Bearer ${options.credential}`, "content-type": "application/json" },
88
+ redirect: "error",
89
+ body: JSON.stringify(body),
90
+ signal: AbortSignal.timeout(3e4)
91
+ });
92
+ const data = await response.json();
93
+ if (!response.ok) {
94
+ throw new PersonalResponseError(typeof data.error === "string" ? data.error : "personal_transport_rejected", response.status, data.rejectedBeforeAdmission === true);
95
+ }
96
+ return data;
97
+ }
98
+ const metadata = () => ({
99
+ version,
100
+ r5dctlVersion: readInstalledCliVersion("r5dctl"),
101
+ platform: process.platform,
102
+ arch: process.arch,
103
+ hostname: os.hostname(),
104
+ capabilities: { updateClis: true, durableWorkspace: true }
105
+ });
106
+ const grant = PersonalWorkerGrant.parse(
107
+ await request("/api/personal/resources/register", { kind: "worker", ...identity, label: options.label, metadata: metadata() })
108
+ );
109
+ let cliEntrypoint = options.cliEntrypoint ?? Bun.which("r5dctl");
110
+ if (!cliEntrypoint) {
111
+ try {
112
+ const module = fileURLToPath(import.meta.resolve("@ricsam/r5dctl/cli"));
113
+ cliEntrypoint = path.join(path.dirname(module), path.extname(module) === ".cjs" ? "main.cjs" : "main.mjs");
114
+ } catch {
115
+ }
116
+ }
117
+ if (!cliEntrypoint) throw new Error("Install @ricsam/r5dctl before starting this worker");
118
+ const runtime = await openPersonalWorkerRuntime({
119
+ root,
120
+ cliEntrypoint,
121
+ grant,
122
+ storage: (sessionId) => async (storageRequest) => {
123
+ try {
124
+ const response = await request(`${endpoint}/storage`, {
125
+ instanceId: identity.instanceId,
126
+ request: { sessionId, envelope: { protocol: 1, installationId: grant.installationId, request: storageRequest } }
127
+ });
128
+ if (!response || !("result" in response)) throw new Error("Invalid scoped storage response");
129
+ return response.result;
130
+ } catch (error) {
131
+ if (error instanceof PersonalResponseError && storageErrorCodes.has(error.code))
132
+ throw new WorkspaceError(error.code, "Scoped storage rejected the request; retain its original operation identity", error.rejectedBeforeAdmission);
133
+ throw error;
134
+ }
135
+ }
136
+ });
137
+ const ledgerFile = path.join(root, "relay.sqlite");
138
+ try {
139
+ closeSync(openSync(ledgerFile, "wx", 384));
140
+ } catch (error) {
141
+ if (error.code !== "EEXIST") throw error;
142
+ }
143
+ const ledger = new Database(ledgerFile);
144
+ ledger.exec(
145
+ "PRAGMA journal_mode=WAL;PRAGMA synchronous=FULL;CREATE TABLE IF NOT EXISTS actions(id TEXT PRIMARY KEY,hash TEXT NOT NULL,state TEXT NOT NULL,result TEXT)"
146
+ );
147
+ let stopped = false, metadataAt = 0;
148
+ async function action(input) {
149
+ if (createHash("sha256").update(canonicalJson(input.request)).digest("hex") !== input.requestHash)
150
+ throw new Error("Personal action digest mismatch");
151
+ const previous = ledger.query("SELECT * FROM actions WHERE id=?").get(input.id);
152
+ if (previous && previous.hash !== input.requestHash) throw new Error("Personal action identity changed");
153
+ let state = previous?.state, result = previous?.result ? JSON.parse(previous.result) : void 0;
154
+ if (!previous || state === "pending") {
155
+ if (!previous) ledger.query("INSERT INTO actions VALUES(?,?,'pending',NULL)").run(input.id, input.requestHash);
156
+ try {
157
+ if (input.request.kind === "update-clis") {
158
+ const command = input.request.command;
159
+ if (input.request.userId !== grant.userId || !/^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?$/.test(command.version))
160
+ throw new Error("Invalid CLI update");
161
+ if (previous) throw new Error("Inspect interrupted CLI installation before retrying");
162
+ result = await installCliUpdate({
163
+ workerPackageSpec: `@ricsam/r5d-worker@${command.version}`,
164
+ r5dctlPackageSpec: `@ricsam/r5dctl@${command.version}`,
165
+ targetWorkerVersion: command.version,
166
+ targetR5dctlVersion: command.version
167
+ });
168
+ } else result = await runtime.dispatch(input.request);
169
+ state = "completed";
170
+ } catch (error) {
171
+ state = error.rejectedBeforeAdmission === true ? "rejected" : "unknown";
172
+ result = { error: safeError(error), ...error.rejectedBeforeAdmission === true ? { rejectedBeforeAdmission: true } : {} };
173
+ }
174
+ ledger.query("UPDATE actions SET state=?,result=? WHERE id=?").run(state, canonicalJson(result), input.id);
175
+ }
176
+ await request(`${endpoint}/ack`, { instanceId: identity.instanceId, id: input.id, requestHash: input.requestHash, state, result });
177
+ }
178
+ const scheduler = new PersonalActionScheduler(action, (error) => {
179
+ process.stderr.write(`[r5d-worker] ${safeError(error)}; retaining original action for reconnect
180
+ `);
181
+ });
182
+ const loop = (async () => {
183
+ while (!stopped) {
184
+ try {
185
+ const sendMetadata = Date.now() - metadataAt > 6e4;
186
+ const response = await request(`${endpoint}/poll`, {
187
+ instanceId: identity.instanceId,
188
+ ...sendMetadata ? { metadata: metadata() } : {}
189
+ });
190
+ if (sendMetadata) metadataAt = Date.now();
191
+ await runtime.renew({ ...grant, ...response });
192
+ scheduler.accept(response.actions ?? []);
193
+ } catch (error) {
194
+ process.stderr.write(`[r5d-worker] ${safeError(error)}; reconnecting without replacing the local keeper
195
+ `);
196
+ }
197
+ await Bun.sleep(250);
198
+ }
199
+ })();
200
+ return {
201
+ resourceId: identity.resourceId,
202
+ root,
203
+ async close() {
204
+ stopped = true;
205
+ await loop;
206
+ await scheduler.drain();
207
+ await runtime.close();
208
+ ledger.close();
209
+ }
210
+ };
211
+ }
212
+ export {
213
+ parsePersonalWorkerOptions,
214
+ startPersonalWorker
215
+ };
@@ -0,0 +1,359 @@
1
+ import path from "node:path";
2
+ import { PersonalTcpManager } from "./tcp.mjs";
3
+ import { createHash, randomBytes } from "node:crypto";
4
+ import { promises as fs } from "node:fs";
5
+ import { z } from "zod";
6
+ import { RuntimeHello, RuntimeId, OwnershipFence, OperationEnvelope, canonicalJson } from "@ricsam/r5d-api/runtime-protocol";
7
+ import { startHostExecutor } from "../runtime/daemon.mjs";
8
+ import { HostExecutorClient } from "../runtime/client.mjs";
9
+ import { tokenHash } from "../runtime/executor.mjs";
10
+ import { EXECUTOR_CAPABILITY, ExecutorCredential } from "../runtime/protocol.mjs";
11
+ import { privateDirectory, readPrivateJson } from "../runtime/storage.mjs";
12
+ import { WorkspaceAuthority } from "../runtime/workspace/authority.mjs";
13
+ import { ApprovedWorkbench, WorkspaceError } from "../runtime/workspace/contracts.mjs";
14
+ import { WorkspaceStorageClient } from "../runtime/workspace/storage-client.mjs";
15
+ import { BranchName, StorageId } from "../runtime/workspace/storage-wire.mjs";
16
+ const PersonalWorkerGrant = z.object({
17
+ installationId: RuntimeId,
18
+ userId: RuntimeId,
19
+ resourceId: RuntimeId,
20
+ instanceId: RuntimeId,
21
+ label: z.string().optional(),
22
+ workerFence: OwnershipFence,
23
+ workerHello: RuntimeHello,
24
+ leaseExpiresAt: z.coerce.number().int().positive()
25
+ }).passthrough();
26
+ const Workbench = z.object({ id: StorageId, repositoryId: StorageId, branch: BranchName, sessionId: RuntimeId, sharedSessionId: RuntimeId.optional() }).strict();
27
+ const PersonalWorkspaceRequest = z.object({
28
+ protocol: z.literal(1),
29
+ userId: RuntimeId,
30
+ sessionId: RuntimeId,
31
+ workbench: Workbench,
32
+ sourceWorkbench: Workbench.optional(),
33
+ sharedWorkbenches: z.array(Workbench).max(32).optional(),
34
+ kind: z.enum(["inspect", "terminal", "product", "agent", "tcp"]),
35
+ command: z.record(z.string(), z.unknown()),
36
+ credentials: z.array(ExecutorCredential).max(32).optional()
37
+ }).strict();
38
+ async function personalExecutorToken(root, grant) {
39
+ const identity = { installationId: grant.installationId, userId: grant.userId, resourceId: grant.resourceId, instanceId: grant.instanceId, workerId: grant.workerFence.resourceId };
40
+ const file = path.join(root, "executor-identity.json");
41
+ try {
42
+ const handle = await fs.open(file, "wx", 384);
43
+ try {
44
+ await handle.writeFile(canonicalJson({ version: 1, ...identity, token: randomBytes(32).toString("hex") }));
45
+ await handle.sync();
46
+ } finally {
47
+ await handle.close();
48
+ }
49
+ const directory = await fs.open(root, "r");
50
+ try {
51
+ await directory.sync();
52
+ } finally {
53
+ await directory.close();
54
+ }
55
+ } catch (error) {
56
+ if (error.code !== "EEXIST") throw error;
57
+ }
58
+ const stat = await fs.lstat(file);
59
+ if (stat.nlink !== 1) throw new Error("Personal executor identity must be a private single-link file");
60
+ const stored = z.object({ version: z.literal(1), installationId: RuntimeId, userId: RuntimeId, resourceId: RuntimeId, instanceId: RuntimeId, workerId: RuntimeId, token: z.string().regex(/^[a-f0-9]{64}$/) }).strict().parse(readPrivateJson(file));
61
+ const { token, version: _, ...bound } = stored;
62
+ if (canonicalJson(bound) !== canonicalJson(identity)) throw new Error("Personal executor identity changed; retain the original keeper");
63
+ return token;
64
+ }
65
+ async function openPersonalWorkerRuntime(options) {
66
+ const grant = PersonalWorkerGrant.parse(options.grant), root = path.resolve(options.root);
67
+ privateDirectory(root);
68
+ if (grant.workerFence.installationId !== grant.installationId || grant.workerHello.installationId !== grant.installationId || grant.workerHello.instanceId !== grant.workerFence.ownerId || grant.workerHello.role !== "worker-adapter")
69
+ throw new Error("Invalid personal worker grant");
70
+ const credentialsRoot = path.join(root, "credentials"), workspaceRoot = path.join(root, "workspace", grant.installationId);
71
+ privateDirectory(credentialsRoot);
72
+ privateDirectory(workspaceRoot);
73
+ const token = await personalExecutorToken(root, grant), controllerId = `controller-${createHash("sha256").update(grant.instanceId).digest("hex").slice(0, 24)}`;
74
+ const daemon = await startHostExecutor({
75
+ installationId: grant.installationId,
76
+ workerId: grant.workerFence.resourceId,
77
+ instanceId: `keeper-${grant.resourceId}`,
78
+ releaseId: "personal-keeper-v1",
79
+ stateDir: path.join(root, "executor"),
80
+ productCredentialDirectory: credentialsRoot,
81
+ capacity: { general: 8, utility: 8, control: 4 },
82
+ principals: [
83
+ {
84
+ id: controllerId,
85
+ role: "controller",
86
+ ownerId: controllerId,
87
+ tokenHash: tokenHash(token),
88
+ userIds: [grant.userId],
89
+ cwdRoots: [workspaceRoot],
90
+ lanes: ["general", "utility", "control"],
91
+ credentialIds: []
92
+ }
93
+ ]
94
+ });
95
+ try {
96
+ let cliDirectory;
97
+ if (options.cliEntrypoint) {
98
+ const cli = await fs.realpath(options.cliEntrypoint), executable = await fs.realpath(process.execPath), stat = await fs.lstat(cli);
99
+ if (!stat.isFile() || stat.mode & 18 || ![0, process.getuid?.()].includes(stat.uid))
100
+ throw new Error("Untrusted installed r5dctl entrypoint");
101
+ cliDirectory = path.join(root, "bin");
102
+ privateDirectory(cliDirectory);
103
+ const quote = (value) => "'" + value.replace(/'/g, "'\\''") + "'";
104
+ const wrapper = `#!/bin/sh
105
+ exec ${quote(executable)} ${quote(cli)} "$@"
106
+ `;
107
+ const file = path.join(cliDirectory, "r5dctl"), temporary = `${file}.${randomBytes(8).toString("hex")}.next`;
108
+ await fs.writeFile(temporary, wrapper, { mode: 448, flag: "wx" });
109
+ await fs.rename(temporary, file);
110
+ }
111
+ const controller = new HostExecutorClient({
112
+ socketPath: daemon.socketPath,
113
+ principalId: controllerId,
114
+ token,
115
+ hello: { ...grant.workerHello, instanceId: controllerId, role: "scheduler", capabilities: [EXECUTOR_CAPABILITY] },
116
+ timeoutMs: 1e4
117
+ });
118
+ let client = new HostExecutorClient({
119
+ socketPath: daemon.socketPath,
120
+ principalId: grant.workerHello.instanceId,
121
+ token,
122
+ hello: grant.workerHello,
123
+ timeoutMs: 1e4
124
+ });
125
+ let current = grant, installed = false, retirementPending = false;
126
+ const manifests = /* @__PURE__ */ new Map();
127
+ const manifestFile = path.join(root, "workbenches.json");
128
+ try {
129
+ const rows = z.array(ApprovedWorkbench).parse(readPrivateJson(manifestFile));
130
+ for (const row of rows) {
131
+ if (row.userId !== grant.userId || !row.cwd.startsWith(workspaceRoot + path.sep)) throw new Error("Invalid retained workbench");
132
+ manifests.set(row.sessionId, row);
133
+ }
134
+ } catch (error) {
135
+ if (error.code !== "ENOENT") throw error;
136
+ }
137
+ const artifactDigest = createHash("sha256").update(await fs.readFile(new URL(import.meta.url))).digest("hex");
138
+ async function retireObsoleteAdapters(retain) {
139
+ const status = await controller.status();
140
+ for (const adapter of status.adapters) {
141
+ if (adapter.state === "registered" && adapter.ownerId !== status.authority?.fence.ownerId && !retain.includes(adapter.ownerId))
142
+ await controller.retireAdapter(adapter.principalId);
143
+ }
144
+ }
145
+ async function renew(value) {
146
+ const next = PersonalWorkerGrant.parse(value);
147
+ if (next.instanceId !== grant.instanceId || next.installationId !== grant.installationId || next.resourceId !== grant.resourceId || next.userId !== grant.userId || next.workerFence.installationId !== grant.installationId || next.workerHello.installationId !== grant.installationId || next.workerHello.role !== "worker-adapter" || next.workerFence.resourceId !== grant.workerFence.resourceId || next.workerHello.instanceId !== next.workerFence.ownerId)
148
+ throw new Error("Personal worker resource identity changed; inspect retained keeper before rebinding");
149
+ let successor = client;
150
+ if (!installed || canonicalJson(next.workerHello) !== canonicalJson(current.workerHello)) {
151
+ await retireObsoleteAdapters([next.workerHello.instanceId]);
152
+ await controller.registerAdapter({
153
+ installationId: grant.installationId,
154
+ workerId: grant.workerFence.resourceId,
155
+ hello: next.workerHello,
156
+ artifactDigest,
157
+ principal: {
158
+ id: next.workerHello.instanceId,
159
+ role: "adapter",
160
+ ownerId: next.workerHello.instanceId,
161
+ tokenHash: tokenHash(token),
162
+ userIds: [grant.userId],
163
+ cwdRoots: [workspaceRoot],
164
+ lanes: ["general", "utility", "control"],
165
+ credentialIds: []
166
+ }
167
+ });
168
+ successor = new HostExecutorClient({
169
+ socketPath: daemon.socketPath,
170
+ principalId: next.workerHello.instanceId,
171
+ token,
172
+ hello: next.workerHello,
173
+ timeoutMs: 1e4
174
+ });
175
+ }
176
+ await controller.setAuthority(next.workerFence, next.leaseExpiresAt);
177
+ if (!installed || canonicalJson(next.workerHello) !== canonicalJson(current.workerHello)) retirementPending = true;
178
+ client = successor;
179
+ current = next;
180
+ installed = true;
181
+ if (retirementPending) {
182
+ await retireObsoleteAdapters([next.workerHello.instanceId]);
183
+ retirementPending = false;
184
+ }
185
+ }
186
+ await renew(grant);
187
+ const authority = await WorkspaceAuthority.open({
188
+ config: { installationId: grant.installationId, root: workspaceRoot, dynamicWorkbenches: true, workbenches: [] },
189
+ executor: async () => ({ client, workerFence: current.workerFence }),
190
+ resolveWorkbench: async (identity) => identity.userId === grant.userId ? manifests.get(identity.sessionId) ?? null : null,
191
+ storage: async (identity) => new WorkspaceStorageClient(grant.installationId, options.storage(identity.sessionId), async () => current.workerFence)
192
+ });
193
+ async function approve(input) {
194
+ const row = ApprovedWorkbench.parse({
195
+ ...input,
196
+ userId: grant.userId,
197
+ cwd: path.join(workspaceRoot, "workbenches", input.id),
198
+ nonmutatingArgv: []
199
+ });
200
+ const previous = manifests.get(input.sessionId);
201
+ if (previous && canonicalJson(previous) !== canonicalJson(row)) throw new Error("Personal workbench identity changed");
202
+ if (!previous) {
203
+ manifests.set(input.sessionId, row);
204
+ const tmp = `${manifestFile}.${randomBytes(8).toString("hex")}.next`;
205
+ const handle = await fs.open(tmp, "wx", 384);
206
+ try {
207
+ await handle.writeFile(canonicalJson([...manifests.values()]));
208
+ await handle.sync();
209
+ } finally {
210
+ await handle.close();
211
+ }
212
+ await fs.rename(tmp, manifestFile);
213
+ }
214
+ }
215
+ async function installCredentials(values) {
216
+ for (const source of values) {
217
+ const credential = cliDirectory ? { ...source, env: { ...source.env, PATH: `${cliDirectory}:/usr/local/bin:/usr/bin:/bin` } } : source;
218
+ if (credential.installationId !== grant.installationId || credential.userId !== grant.userId || !/^product-[a-f0-9]{40}$/.test(credential.id))
219
+ throw new Error("Invalid personal credential scope");
220
+ const destination = path.join(credentialsRoot, `${credential.id}-${credential.generation}.json`), data = canonicalJson(credential);
221
+ try {
222
+ const handle = await fs.open(destination, "wx", 384);
223
+ try {
224
+ await handle.writeFile(data);
225
+ await handle.sync();
226
+ } finally {
227
+ await handle.close();
228
+ }
229
+ } catch (error) {
230
+ if (error.code !== "EEXIST" || canonicalJson(readPrivateJson(destination)) !== data) throw error;
231
+ }
232
+ }
233
+ }
234
+ const tcp = new PersonalTcpManager(path.join(root, "tcp"));
235
+ async function dispatch(raw) {
236
+ const request = PersonalWorkspaceRequest.parse(raw);
237
+ if (request.userId !== grant.userId || request.workbench.sessionId !== request.sessionId)
238
+ throw new Error("Wrong personal worker principal");
239
+ if (request.kind === "tcp") return tcp.dispatch(request.sessionId, request.command);
240
+ for (const parent of request.sharedWorkbenches ?? []) await approve(parent);
241
+ await approve(request.workbench);
242
+ if (request.sourceWorkbench) await approve(request.sourceWorkbench);
243
+ await installCredentials(request.credentials ?? []);
244
+ const identity = { userId: grant.userId, sessionId: request.sessionId }, command = request.command, run = (operationId) => ({ ...identity, operationId: RuntimeId.parse(operationId), fence: current.workerFence });
245
+ if (request.kind === "inspect") {
246
+ if (command.method === "fileWriteResult") return authority.fileWriteResult(identity, { id: command.id });
247
+ if (command.method === "processes") return authority.processes(identity);
248
+ if (command.method === "status") return { ...await authority.status(identity), workerLabel: grant.label ?? grant.resourceId };
249
+ await authority.ensureHydrated(identity);
250
+ if (command.method === "file") return authority.readFile(identity, command.path);
251
+ if (command.method === "git") return authority.inspectGit(identity, { ...command, method: command.action });
252
+ if (command.method === "fileHistoryDates") return authority.fileHistoryDates(identity, command.path);
253
+ if (command.method === "directory" || command.method === "raw") return authority.inspectFiles(identity, command);
254
+ }
255
+ if (request.kind === "terminal") {
256
+ await authority.ensureHydrated(identity);
257
+ if (command.method === "open" || command.method === "start") {
258
+ const argv = command.method === "open" ? ["/bin/bash", "--noprofile", "--norc", ...command.command ? ["-c", command.command] : []] : ["/bin/sh", "-c", command.command];
259
+ return {
260
+ ...await authority.start(
261
+ identity,
262
+ OperationEnvelope.parse({
263
+ protocol: 1,
264
+ installationId: grant.installationId,
265
+ operationId: `terminal:${command.id}`,
266
+ ...identity,
267
+ kind: "host.shell",
268
+ fence: current.workerFence,
269
+ payload: {
270
+ argv,
271
+ cwd: manifests.get(identity.sessionId).cwd,
272
+ ...command.method === "open" ? { interactive: true, pty: { cols: command.cols, rows: command.rows } } : {},
273
+ credentials: (request.credentials ?? []).map(({ id, generation }) => ({ id, generation }))
274
+ }
275
+ })
276
+ ),
277
+ workerLabel: grant.label ?? grant.resourceId
278
+ };
279
+ }
280
+ const ref = run(command.operationId ?? `terminal:${command.runId}`);
281
+ if (command.method === "poll" || command.method === "processPoll")
282
+ return authority.poll(identity, ref, command.stdoutOffset ?? 0, command.stderrOffset ?? 0, command.maxBytes ?? 32768);
283
+ if (command.method === "write") return authority.write(identity, ref, command.id, command.data, command.eof ?? false);
284
+ if (command.method === "resize") return authority.resize(identity, ref, command.id, command.cols, command.rows);
285
+ if (command.method === "cancel" || command.method === "processCancel") return authority.cancel(identity, ref, command.id);
286
+ }
287
+ if (request.kind === "product") {
288
+ if (command.method === "fileWrite") {
289
+ await authority.ensureHydrated(identity);
290
+ return authority.fileWrite(identity, command);
291
+ }
292
+ if (command.method === "materializeArtifact") return authority.materializeArtifact(identity, command);
293
+ if (command.method === "seed") return authority.seedReadme(identity, command.readme);
294
+ if (command.method === "hydrate") return authority.hydrate(identity, command.expectedBase);
295
+ if (command.method === "import") return authority.importRepository(identity, command);
296
+ if (command.method === "clone")
297
+ return authority.cloneBranch(identity, { userId: grant.userId, sessionId: command.sourceSessionId }, command);
298
+ if (command.method === "archive") return authority.archiveBranch(identity, command);
299
+ if (command.method === "commit") return authority.commit(identity, command);
300
+ if (command.method === "push") return authority.push(identity, command);
301
+ if (command.method === "reset") return authority.reset(identity, command);
302
+ if (command.method === "publish") return authority.publish(identity);
303
+ if (command.method === "reconcilePublication") return authority.reconcilePublication(identity);
304
+ }
305
+ if (request.kind === "agent") {
306
+ if (command.method === "status") return { ...await authority.status(identity), workerLabel: grant.label ?? grant.resourceId };
307
+ if (command.method === "ensureHydrated") {
308
+ await authority.ensureHydrated(identity);
309
+ return { ready: true };
310
+ }
311
+ if (command.method === "hydrate") return authority.hydrate(identity, command.expectedBase);
312
+ if (command.method === "seedReadme") return authority.seedReadme(identity, command.readme);
313
+ if (command.method === "publish") return authority.publish(identity);
314
+ if (command.method === "reconcilePublication") return authority.reconcilePublication(identity);
315
+ if (command.method === "start") {
316
+ const operation = OperationEnvelope.parse(command.operation);
317
+ if (operation.userId !== grant.userId || operation.sessionId !== identity.sessionId)
318
+ throw new Error("Wrong personal shell principal");
319
+ return authority.start(identity, {
320
+ ...operation,
321
+ fence: current.workerFence,
322
+ payload: {
323
+ ...operation.payload,
324
+ cwd: manifests.get(identity.sessionId).cwd,
325
+ credentials: (request.credentials ?? []).map(({ id, generation }) => ({ id, generation }))
326
+ }
327
+ });
328
+ }
329
+ if (command.method === "poll")
330
+ return authority.poll(identity, run(command.run.operationId), command.stdoutOffset, command.stderrOffset, command.maxBytes);
331
+ if (command.method === "write")
332
+ return authority.write(identity, run(command.run.operationId), command.actionId, command.data, command.eof);
333
+ if (command.method === "resize")
334
+ return authority.resize(identity, run(command.run.operationId), command.actionId, command.cols, command.rows);
335
+ if (command.method === "cancel") return authority.cancel(identity, run(command.run.operationId), command.actionId);
336
+ }
337
+ throw new WorkspaceError("invalid_request", "Unsupported personal workspace operation");
338
+ }
339
+ return {
340
+ grant,
341
+ renew,
342
+ dispatch,
343
+ authority,
344
+ async close() {
345
+ await tcp.close();
346
+ await authority.close();
347
+ await daemon.close();
348
+ }
349
+ };
350
+ } catch (error) {
351
+ await daemon.close();
352
+ throw error;
353
+ }
354
+ }
355
+ export {
356
+ PersonalWorkerGrant,
357
+ PersonalWorkspaceRequest,
358
+ openPersonalWorkerRuntime
359
+ };