@fungi.computer/oyster 0.0.0-stage → 0.1.0

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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1134 -2
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +322 -0
  5. package/dist/app-builds.d.ts +210 -0
  6. package/dist/app-builds.js +499 -0
  7. package/dist/app-sandbox.d.ts +2 -0
  8. package/dist/app-sandbox.js +2 -0
  9. package/dist/app-sources.d.ts +31 -0
  10. package/dist/app-sources.js +370 -0
  11. package/dist/app-storage.d.ts +141 -0
  12. package/dist/app-storage.js +281 -0
  13. package/dist/apps/gate.d.ts +105 -0
  14. package/dist/apps/gate.js +273 -0
  15. package/dist/apps/index.d.ts +5 -0
  16. package/dist/apps/index.js +5 -0
  17. package/dist/apps/installation.d.ts +248 -0
  18. package/dist/apps/installation.js +307 -0
  19. package/dist/apps/operations.d.ts +8 -0
  20. package/dist/apps/operations.js +98 -0
  21. package/dist/apps/tools.d.ts +33 -0
  22. package/dist/apps/tools.js +17 -0
  23. package/dist/auth.d.ts +11 -0
  24. package/dist/auth.js +93 -0
  25. package/dist/backend-request.d.ts +3 -0
  26. package/dist/backend-request.js +53 -0
  27. package/dist/build/artifact-writer.d.ts +40 -0
  28. package/dist/build/artifact-writer.js +181 -0
  29. package/dist/build/build-jobs.d.ts +128 -0
  30. package/dist/build/build-jobs.js +203 -0
  31. package/dist/build/build-policy.d.ts +10 -0
  32. package/dist/build/build-policy.js +10 -0
  33. package/dist/build/build-step.d.ts +37 -0
  34. package/dist/build/build-step.js +28 -0
  35. package/dist/build/bundled-html.d.ts +7 -0
  36. package/dist/build/bundled-html.js +64 -0
  37. package/dist/build/candidate-builder.d.ts +68 -0
  38. package/dist/build/candidate-builder.js +91 -0
  39. package/dist/build/candidate-cli.d.ts +1 -0
  40. package/dist/build/candidate-cli.js +119 -0
  41. package/dist/build/candidate-cli.mjs +28170 -0
  42. package/dist/build/content-objects.d.ts +37 -0
  43. package/dist/build/content-objects.js +53 -0
  44. package/dist/build/definition-emission.d.ts +34 -0
  45. package/dist/build/definition-emission.js +201 -0
  46. package/dist/build/effect.d.ts +249 -0
  47. package/dist/build/effect.js +436 -0
  48. package/dist/build/git-environment.d.ts +9 -0
  49. package/dist/build/git-environment.js +60 -0
  50. package/dist/build/html-recipe.d.ts +20 -0
  51. package/dist/build/html-recipe.js +51 -0
  52. package/dist/build/local-build.d.ts +67 -0
  53. package/dist/build/local-build.js +1128 -0
  54. package/dist/build/snapshot-package-store.d.ts +12 -0
  55. package/dist/build/snapshot-package-store.js +36 -0
  56. package/dist/build/snapshot-program-scripts.d.ts +6 -0
  57. package/dist/build/snapshot-program-scripts.js +2 -0
  58. package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
  59. package/dist/build/snapshot-programs/snapshot-path.js +6 -0
  60. package/dist/build/submission-contract.d.ts +828 -0
  61. package/dist/build/submission-contract.js +180 -0
  62. package/dist/build/workspace-build.d.ts +55 -0
  63. package/dist/build/workspace-build.js +200 -0
  64. package/dist/build-contract.d.ts +7 -0
  65. package/dist/build-contract.js +7 -0
  66. package/dist/build-egress.d.ts +62 -0
  67. package/dist/build-egress.js +148 -0
  68. package/dist/build-jobs.d.ts +2 -0
  69. package/dist/build-jobs.js +2 -0
  70. package/dist/build.d.ts +12 -0
  71. package/dist/build.js +14 -0
  72. package/dist/capability-descriptor.d.ts +46 -0
  73. package/dist/capability-descriptor.js +51 -0
  74. package/dist/catalog-frame.d.ts +39 -0
  75. package/dist/catalog-frame.js +72 -0
  76. package/dist/cause-class.d.ts +19 -0
  77. package/dist/cause-class.js +54 -0
  78. package/dist/cloudflare.d.ts +123 -0
  79. package/dist/cloudflare.js +391 -0
  80. package/dist/computer-terminal.d.ts +38 -0
  81. package/dist/computer-terminal.js +410 -0
  82. package/dist/computer.d.ts +60 -0
  83. package/dist/computer.js +33 -0
  84. package/dist/event-recipients.d.ts +57 -0
  85. package/dist/event-recipients.js +144 -0
  86. package/dist/facet-invocation.d.ts +4 -0
  87. package/dist/facet-invocation.js +45 -0
  88. package/dist/files.d.ts +42 -0
  89. package/dist/files.js +123 -0
  90. package/dist/frame-binding-wire-script.d.ts +2 -0
  91. package/dist/frame-binding-wire-script.js +1 -0
  92. package/dist/frame-binding-wire.d.ts +28 -0
  93. package/dist/frame-binding-wire.js +54 -0
  94. package/dist/git-admission.d.ts +52 -0
  95. package/dist/git-admission.js +188 -0
  96. package/dist/git-auth.d.ts +11 -0
  97. package/dist/git-auth.js +27 -0
  98. package/dist/git-door.d.ts +35 -0
  99. package/dist/git-door.js +108 -0
  100. package/dist/git-journal.d.ts +31 -0
  101. package/dist/git-journal.js +188 -0
  102. package/dist/git.d.ts +31 -0
  103. package/dist/git.js +64 -0
  104. package/dist/guest-policy.d.ts +9 -0
  105. package/dist/guest-policy.js +13 -0
  106. package/dist/host.d.ts +244 -0
  107. package/dist/host.js +1043 -0
  108. package/dist/index.d.ts +166 -0
  109. package/dist/index.js +320 -0
  110. package/dist/installation-consent.d.ts +79 -0
  111. package/dist/installation-consent.js +82 -0
  112. package/dist/internal/background-origins.d.ts +3 -0
  113. package/dist/internal/background-origins.js +8 -0
  114. package/dist/internal/delivery.d.ts +12 -0
  115. package/dist/internal/delivery.js +167 -0
  116. package/dist/internal/host-gate.d.ts +14 -0
  117. package/dist/internal/host-gate.js +62 -0
  118. package/dist/internal/registry-installations.d.ts +6 -0
  119. package/dist/internal/registry-installations.js +12 -0
  120. package/dist/invocation.d.ts +251 -0
  121. package/dist/invocation.js +377 -0
  122. package/dist/keys.d.ts +48 -0
  123. package/dist/keys.js +52 -0
  124. package/dist/mcp-owner.d.ts +27 -0
  125. package/dist/mcp-owner.js +155 -0
  126. package/dist/mcp.d.ts +89 -0
  127. package/dist/mcp.js +429 -0
  128. package/dist/media.d.ts +23 -0
  129. package/dist/media.js +12 -0
  130. package/dist/pack.d.ts +79 -0
  131. package/dist/pack.js +151 -0
  132. package/dist/page/capabilities.d.ts +23 -0
  133. package/dist/page/capabilities.js +256 -0
  134. package/dist/page/computer-handles.d.ts +79 -0
  135. package/dist/page/computer-handles.js +44 -0
  136. package/dist/page/gill-observation.d.ts +4 -0
  137. package/dist/page/gill-observation.js +31 -0
  138. package/dist/page/lifetime.d.ts +6 -0
  139. package/dist/page/lifetime.js +19 -0
  140. package/dist/page/mount.d.ts +163 -0
  141. package/dist/page/mount.js +390 -0
  142. package/dist/page/storage.d.ts +3 -0
  143. package/dist/page/storage.js +82 -0
  144. package/dist/page/terminal.d.ts +5 -0
  145. package/dist/page/terminal.js +7 -0
  146. package/dist/page/window-launch.d.ts +18 -0
  147. package/dist/page/window-launch.js +11 -0
  148. package/dist/page-script.d.ts +14 -0
  149. package/dist/page-script.js +5 -0
  150. package/dist/r2-files.d.ts +3 -0
  151. package/dist/r2-files.js +79 -0
  152. package/dist/release.d.ts +83 -0
  153. package/dist/release.js +167 -0
  154. package/dist/root-data.d.ts +86 -0
  155. package/dist/root-data.js +69 -0
  156. package/dist/shell/app-directory.d.ts +542 -0
  157. package/dist/shell/app-directory.js +199 -0
  158. package/dist/shell/browser-actions.d.ts +7 -0
  159. package/dist/shell/browser-actions.js +56 -0
  160. package/dist/shell/command-host.d.ts +112 -0
  161. package/dist/shell/command-host.js +502 -0
  162. package/dist/shell/commands.d.ts +25 -0
  163. package/dist/shell/commands.js +78 -0
  164. package/dist/shell/index.d.ts +8 -0
  165. package/dist/shell/index.js +8 -0
  166. package/dist/shell/keys.d.ts +41 -0
  167. package/dist/shell/keys.js +134 -0
  168. package/dist/shell/shell-module.d.ts +55 -0
  169. package/dist/shell/shell-module.js +86 -0
  170. package/dist/shell/window-manager.d.ts +124 -0
  171. package/dist/shell/window-manager.js +711 -0
  172. package/dist/shell/workbench-contract.d.ts +68 -0
  173. package/dist/shell/workbench-contract.js +1 -0
  174. package/dist/shell/workbench-directory.d.ts +332 -0
  175. package/dist/shell/workbench-directory.js +35 -0
  176. package/dist/shell/workbench-host-types.d.ts +96 -0
  177. package/dist/shell/workbench-host-types.js +1 -0
  178. package/dist/shell/workbench-host.d.ts +34 -0
  179. package/dist/shell/workbench-host.js +347 -0
  180. package/dist/shell/workbench-model.d.ts +192 -0
  181. package/dist/shell/workbench-model.js +53 -0
  182. package/dist/shell/workbench.d.ts +4 -0
  183. package/dist/shell/workbench.js +2 -0
  184. package/dist/source/fork.d.ts +27 -0
  185. package/dist/source/fork.js +46 -0
  186. package/dist/source/index.d.ts +10 -0
  187. package/dist/source/index.js +10 -0
  188. package/dist/source/seed-receipt.d.ts +53 -0
  189. package/dist/source/seed-receipt.js +18 -0
  190. package/dist/source/team-source.d.ts +32 -0
  191. package/dist/source/team-source.js +42 -0
  192. package/dist/source/template-push.d.ts +33 -0
  193. package/dist/source/template-push.js +223 -0
  194. package/dist/source/template-repository.d.ts +4 -0
  195. package/dist/source/template-repository.js +21 -0
  196. package/dist/source/template-seed.d.ts +68 -0
  197. package/dist/source/template-seed.js +80 -0
  198. package/dist/standalone/agent-directory.d.ts +4 -0
  199. package/dist/standalone/agent-directory.js +4 -0
  200. package/dist/standalone/agents.d.ts +220 -0
  201. package/dist/standalone/agents.js +795 -0
  202. package/dist/standalone/app-builds-binding.d.ts +36 -0
  203. package/dist/standalone/app-builds-binding.js +119 -0
  204. package/dist/standalone/app-set.d.ts +91 -0
  205. package/dist/standalone/app-set.js +395 -0
  206. package/dist/standalone/app-source.d.ts +41 -0
  207. package/dist/standalone/app-source.js +59 -0
  208. package/dist/standalone/builds.d.ts +18 -0
  209. package/dist/standalone/builds.js +101 -0
  210. package/dist/standalone/computer-egress.d.ts +37 -0
  211. package/dist/standalone/computer-egress.js +111 -0
  212. package/dist/standalone/computer.d.ts +122 -0
  213. package/dist/standalone/computer.js +714 -0
  214. package/dist/standalone/install-apps-cli.d.ts +2 -0
  215. package/dist/standalone/install-apps-cli.js +126 -0
  216. package/dist/standalone/instance.d.ts +35 -0
  217. package/dist/standalone/instance.js +74 -0
  218. package/dist/standalone/media.d.ts +76 -0
  219. package/dist/standalone/media.js +227 -0
  220. package/dist/standalone/preview.d.ts +51 -0
  221. package/dist/standalone/preview.js +167 -0
  222. package/dist/standalone/registry.d.ts +102 -0
  223. package/dist/standalone/registry.js +193 -0
  224. package/dist/standalone/tenancy.d.ts +42 -0
  225. package/dist/standalone/tenancy.js +241 -0
  226. package/dist/standalone/worker.d.ts +55 -0
  227. package/dist/standalone/worker.js +147 -0
  228. package/dist/standalone/workflow-authority-readers.d.ts +16 -0
  229. package/dist/standalone/workflow-authority-readers.js +35 -0
  230. package/dist/standalone/workflow-binding.d.ts +216 -0
  231. package/dist/standalone/workflow-binding.js +217 -0
  232. package/dist/standalone/workflow-events.d.ts +46 -0
  233. package/dist/standalone/workflow-events.js +39 -0
  234. package/dist/standalone/workflow-git-capture.d.ts +7 -0
  235. package/dist/standalone/workflow-git-capture.js +62 -0
  236. package/dist/standalone/workflow-host.d.ts +931 -0
  237. package/dist/standalone/workflow-host.js +751 -0
  238. package/dist/standalone/workflow-node-capture.d.ts +13 -0
  239. package/dist/standalone/workflow-node-capture.js +72 -0
  240. package/dist/standalone/workflow-root-authority.d.ts +27 -0
  241. package/dist/standalone/workflow-root-authority.js +87 -0
  242. package/dist/standalone/workflow-schedules.d.ts +52 -0
  243. package/dist/standalone/workflow-schedules.js +40 -0
  244. package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
  245. package/dist/standalone/workflow-trigger-host.js +52 -0
  246. package/dist/standalone/workflows.d.ts +657 -0
  247. package/dist/standalone/workflows.js +554 -0
  248. package/dist/storage-frame.d.ts +64 -0
  249. package/dist/storage-frame.js +28 -0
  250. package/dist/tenancy/auth0.d.ts +64 -0
  251. package/dist/tenancy/auth0.js +198 -0
  252. package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
  253. package/dist/tenancy/cloudflare/rate-limit.js +19 -0
  254. package/dist/tenancy/cloudflare/registry.d.ts +50 -0
  255. package/dist/tenancy/cloudflare/registry.js +301 -0
  256. package/dist/tenancy/cloudflare.d.ts +2 -0
  257. package/dist/tenancy/cloudflare.js +2 -0
  258. package/dist/tenancy/cold-directory.d.ts +13 -0
  259. package/dist/tenancy/cold-directory.js +43 -0
  260. package/dist/tenancy/contracts.d.ts +195 -0
  261. package/dist/tenancy/contracts.js +1 -0
  262. package/dist/tenancy/index.d.ts +19 -0
  263. package/dist/tenancy/index.js +156 -0
  264. package/dist/tenancy/owner-key.d.ts +26 -0
  265. package/dist/tenancy/owner-key.js +63 -0
  266. package/dist/tenancy/oyster.d.ts +4 -0
  267. package/dist/tenancy/oyster.js +25 -0
  268. package/dist/tenancy/registry-record.d.ts +7 -0
  269. package/dist/tenancy/registry-record.js +44 -0
  270. package/dist/tenancy/registry.d.ts +27 -0
  271. package/dist/tenancy/registry.js +138 -0
  272. package/dist/tenancy/resolve.d.ts +7 -0
  273. package/dist/tenancy/resolve.js +64 -0
  274. package/dist/tenant.d.ts +81 -0
  275. package/dist/tenant.js +71 -0
  276. package/dist/testing/capabilities.d.ts +29 -0
  277. package/dist/testing/capabilities.js +130 -0
  278. package/dist/testing/catalog.d.ts +3 -0
  279. package/dist/testing/catalog.js +53 -0
  280. package/dist/testing/index.d.ts +10 -0
  281. package/dist/testing/index.js +10 -0
  282. package/dist/testing/installation.d.ts +11 -0
  283. package/dist/testing/installation.js +25 -0
  284. package/dist/testing/native-worker.d.ts +21 -0
  285. package/dist/testing/native-worker.js +106 -0
  286. package/dist/testing/worker.d.ts +18 -0
  287. package/dist/testing/worker.js +67 -0
  288. package/dist/testing/workers-runtime.d.ts +24 -0
  289. package/dist/testing/workers-runtime.js +33 -0
  290. package/dist/webhook-delivery.d.ts +14 -0
  291. package/dist/webhook-delivery.js +17 -0
  292. package/dist/workers-git.d.ts +285 -0
  293. package/dist/workers-git.js +1174 -0
  294. package/dist/workers.d.ts +352 -0
  295. package/dist/workers.js +868 -0
  296. package/docs/api/README.md +82 -0
  297. package/docs/api/agents.md +332 -0
  298. package/docs/api/app-storage.md +209 -0
  299. package/docs/api/apps.md +1375 -0
  300. package/docs/api/auth.md +63 -0
  301. package/docs/api/build-contract.md +1304 -0
  302. package/docs/api/build.md +1712 -0
  303. package/docs/api/cloudflare.md +421 -0
  304. package/docs/api/computer.md +203 -0
  305. package/docs/api/files.md +117 -0
  306. package/docs/api/git.md +470 -0
  307. package/docs/api/host.md +1328 -0
  308. package/docs/api/index.md +430 -0
  309. package/docs/api/invocation.md +738 -0
  310. package/docs/api/keys.md +175 -0
  311. package/docs/api/mcp.md +301 -0
  312. package/docs/api/media.md +40 -0
  313. package/docs/api/modules.md +50 -0
  314. package/docs/api/pack.md +141 -0
  315. package/docs/api/page/mount.md +298 -0
  316. package/docs/api/root-data.md +111 -0
  317. package/docs/api/shell/app-directory.md +599 -0
  318. package/docs/api/shell/command-host.md +224 -0
  319. package/docs/api/shell/commands.md +197 -0
  320. package/docs/api/shell/keys.md +183 -0
  321. package/docs/api/shell/shell-module.md +204 -0
  322. package/docs/api/shell/window-manager.md +267 -0
  323. package/docs/api/shell/workbench-directory.md +119 -0
  324. package/docs/api/shell/workbench.md +205 -0
  325. package/docs/api/shell.md +1915 -0
  326. package/docs/api/tenancy/auth0.md +110 -0
  327. package/docs/api/tenancy/cloudflare.md +221 -0
  328. package/docs/api/tenancy/cold-directory.md +46 -0
  329. package/docs/api/tenancy/owner-key.md +63 -0
  330. package/docs/api/tenancy/registry.md +77 -0
  331. package/docs/api/tenancy/resolve.md +69 -0
  332. package/docs/api/tenancy.md +430 -0
  333. package/docs/api/tenant.md +194 -0
  334. package/docs/api/workers-git.md +1646 -0
  335. package/docs/api/workers.md +1691 -0
  336. package/docs/site/agent-quickstart.mdx +346 -0
  337. package/examples/standalone/worker.ts +7 -0
  338. package/examples/standalone/wrangler.jsonc +82 -0
  339. package/package.json +300 -3
@@ -0,0 +1,410 @@
1
+ import { AppError } from "@fungi.computer/agar";
2
+ import { computerTerminalInformationSchema, } from "@fungi.computer/agar/computer";
3
+ import { LONG, nanoid } from "@fungi.computer/cairn";
4
+ import { RpcTarget } from "@fungi.computer/capnweb";
5
+ import * as Schema from "effect/Schema";
6
+ import * as SchemaTransformation from "effect/SchemaTransformation";
7
+ const terminalControlEventSchema = Schema.Union([
8
+ Schema.Struct({
9
+ type: Schema.Literal("ready"),
10
+ information: computerTerminalInformationSchema,
11
+ }),
12
+ Schema.Struct({ type: Schema.Literals(["exit", "error"]) }),
13
+ ]);
14
+ const terminalFrameSchema = Schema.Union([
15
+ Schema.fromJsonString(terminalControlEventSchema),
16
+ Schema.instanceOf(ArrayBuffer),
17
+ Schema.instanceOf(Blob),
18
+ Schema.instanceOf(Uint8Array),
19
+ ]);
20
+ const decodeTerminalFrame = Schema.decodeUnknownSync(terminalFrameSchema);
21
+ const terminalFailureSchema = Schema.toType(Schema.Defect()).pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
22
+ decode: (failure) => String(failure),
23
+ encode: (message) => message,
24
+ })));
25
+ const decodeTerminalFailure = Schema.decodeUnknownSync(terminalFailureSchema);
26
+ const terminalSizeSchema = Schema.Struct({
27
+ cols: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isBetween({ minimum: 2, maximum: 500 }))),
28
+ rows: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isBetween({ minimum: 2, maximum: 300 }))),
29
+ });
30
+ const decodeTerminalSize = Schema.decodeUnknownSync(terminalSizeSchema);
31
+ const decodeTerminalInput = Schema.decodeUnknownSync(Schema.instanceOf(Uint8Array));
32
+ class ComputerTerminalHostImplementation extends RpcTarget {
33
+ #sessions = new Set();
34
+ #terminalUrl;
35
+ #computerId;
36
+ #connectSocket;
37
+ #hostWindow;
38
+ #ensureAccess;
39
+ #mountSignal;
40
+ #abort = () => {
41
+ this[Symbol.dispose]();
42
+ };
43
+ #disposed = false;
44
+ constructor(options) {
45
+ super();
46
+ this.#terminalUrl = options.terminalUrl;
47
+ this.#computerId = options.computerId;
48
+ this.#connectSocket = options.connect;
49
+ this.#hostWindow = options.hostWindow;
50
+ this.#ensureAccess = options.ensureAccess;
51
+ this.#mountSignal = options.mountSignal;
52
+ this.#mountSignal?.addEventListener("abort", this.#abort, { once: true });
53
+ if (this.#mountSignal?.aborted)
54
+ this.#abort();
55
+ }
56
+ /** Open an admitted PTY and retain its session across transport reconnects. */
57
+ async open(size) {
58
+ if (this.#disposed)
59
+ throw new Error("App Computer terminal is closed");
60
+ const session = new ComputerTerminalSession({
61
+ terminalUrl: this.#terminalUrl,
62
+ computerId: this.#computerId,
63
+ connect: this.#connectSocket,
64
+ hostWindow: this.#hostWindow,
65
+ ensureAccess: this.#ensureAccess,
66
+ mountSignal: this.#mountSignal,
67
+ release: (terminal) => {
68
+ this.#sessions.delete(terminal);
69
+ },
70
+ });
71
+ this.#sessions.add(session);
72
+ try {
73
+ await session.start(decodeTerminalSize(size));
74
+ return session;
75
+ }
76
+ catch (error) {
77
+ session[Symbol.dispose]();
78
+ throw error;
79
+ }
80
+ }
81
+ /** Close every terminal when the App window's host lifetime ends. */
82
+ [Symbol.dispose]() {
83
+ if (this.#disposed)
84
+ return;
85
+ this.#disposed = true;
86
+ this.#mountSignal?.removeEventListener("abort", this.#abort);
87
+ for (const session of this.#sessions)
88
+ session[Symbol.dispose]();
89
+ this.#sessions.clear();
90
+ }
91
+ }
92
+ /** Construct the shared terminal owner with host policy and transport edges. */
93
+ export const ComputerTerminalHost = ComputerTerminalHostImplementation;
94
+ class ComputerTerminalSession extends RpcTarget {
95
+ #controller = new AbortController();
96
+ #abort = () => {
97
+ this[Symbol.dispose]();
98
+ };
99
+ #release;
100
+ #hostWindow;
101
+ #computerId;
102
+ #connectSocket;
103
+ #ensureAccess;
104
+ #mountSignal;
105
+ #output;
106
+ #input;
107
+ #outputController;
108
+ #inputController;
109
+ #terminalUrl;
110
+ #sessionId = nanoid(LONG);
111
+ #size = { cols: 80, rows: 24 };
112
+ #socket;
113
+ #connectPromise;
114
+ #accessPromise;
115
+ #retryTimer;
116
+ #leaseTimer;
117
+ // A refused peer session must never extend this session's admitted lease.
118
+ #leaseExpiresAt;
119
+ #attempt = 0;
120
+ #sawOutput = false;
121
+ #deadShell = false;
122
+ #closed = false;
123
+ #outputDone = false;
124
+ #information;
125
+ #informationWait;
126
+ #resolveInformation;
127
+ #rejectInformation;
128
+ constructor(options) {
129
+ super();
130
+ this.#terminalUrl = options.terminalUrl;
131
+ this.#computerId = options.computerId;
132
+ this.#connectSocket = options.connect;
133
+ this.#hostWindow = options.hostWindow;
134
+ this.#ensureAccess = options.ensureAccess;
135
+ this.#mountSignal = options.mountSignal;
136
+ this.#release = options.release;
137
+ this.#output = new ReadableStream({
138
+ start: (controller) => {
139
+ this.#outputController = controller;
140
+ },
141
+ cancel: () => {
142
+ this.#outputDone = true;
143
+ return this.close();
144
+ },
145
+ });
146
+ this.#input = new WritableStream({
147
+ start: (controller) => {
148
+ this.#inputController = controller;
149
+ },
150
+ write: (chunk) => this.#write(chunk),
151
+ close: () => this.close(),
152
+ abort: () => this.close(),
153
+ });
154
+ this.#mountSignal?.addEventListener("abort", this.#abort, { once: true });
155
+ if (this.#mountSignal?.aborted)
156
+ this.#abort();
157
+ }
158
+ async start(size) {
159
+ this.#size = size;
160
+ await this.#connect();
161
+ }
162
+ information() {
163
+ if (this.#closed)
164
+ return Promise.reject(new Error("Computer terminal is closed"));
165
+ if (this.#information !== undefined)
166
+ return Promise.resolve(this.#information);
167
+ this.#informationWait ??= new Promise((resolve, reject) => {
168
+ this.#resolveInformation = resolve;
169
+ this.#rejectInformation = reject;
170
+ });
171
+ return this.#informationWait;
172
+ }
173
+ output() {
174
+ if (this.#closed)
175
+ return Promise.reject(new Error("Computer terminal is closed"));
176
+ return Promise.resolve(this.#output);
177
+ }
178
+ input() {
179
+ if (this.#closed)
180
+ return Promise.reject(new Error("Computer terminal is closed"));
181
+ return Promise.resolve(this.#input);
182
+ }
183
+ async resize(size) {
184
+ const nextSize = decodeTerminalSize(size);
185
+ await this.#ensureLease();
186
+ const socket = await this.#readySocket();
187
+ socket.send(JSON.stringify({ type: "resize", ...nextSize }));
188
+ this.#size = nextSize;
189
+ }
190
+ close() {
191
+ this[Symbol.dispose]();
192
+ return Promise.resolve();
193
+ }
194
+ [Symbol.dispose]() {
195
+ this.#shutdown();
196
+ }
197
+ async #write(chunk) {
198
+ const bytes = decodeTerminalInput(chunk);
199
+ await this.#ensureLease();
200
+ const socket = await this.#readySocket();
201
+ socket.send(bytes);
202
+ }
203
+ async #readySocket() {
204
+ while (!this.#closed) {
205
+ if (this.#socket?.readyState === WebSocket.OPEN)
206
+ return this.#socket;
207
+ await (this.#connectPromise ?? this.#connect());
208
+ }
209
+ throw new Error("Computer terminal is closed");
210
+ }
211
+ #connect() {
212
+ if (this.#closed)
213
+ return Promise.reject(new Error("Computer terminal is closed"));
214
+ if (this.#connectPromise !== undefined)
215
+ return this.#connectPromise;
216
+ const connecting = this.#openSocket().finally(() => {
217
+ if (this.#connectPromise === connecting)
218
+ this.#connectPromise = undefined;
219
+ });
220
+ this.#connectPromise = connecting;
221
+ return connecting;
222
+ }
223
+ async #openSocket() {
224
+ const configuration = await this.#ensureCurrent();
225
+ const terminalUrl = configuration.terminalUrl ?? this.#terminalUrl;
226
+ this.#terminalUrl = terminalUrl;
227
+ this.#attempt += 1;
228
+ this.#sawOutput = false;
229
+ this.#deadShell = false;
230
+ const socket = this.#connectSocket(terminalUrl, {
231
+ session: this.#sessionId,
232
+ ...this.#size,
233
+ });
234
+ socket.binaryType = "arraybuffer";
235
+ this.#socket = socket;
236
+ socket.addEventListener("message", (event) => {
237
+ if (this.#closed || socket !== this.#socket)
238
+ return;
239
+ try {
240
+ const frame = decodeTerminalFrame(event.data);
241
+ void this.#writeOutput(frame).catch((error) => {
242
+ this.#fail(decodeTerminalFailure(error));
243
+ });
244
+ }
245
+ catch (error) {
246
+ this.#fail(decodeTerminalFailure(error));
247
+ }
248
+ });
249
+ socket.addEventListener("close", () => {
250
+ this.#socketClosed(socket);
251
+ });
252
+ socket.addEventListener("error", () => {
253
+ /* The close event owns reconnect and teardown. */
254
+ });
255
+ await new Promise((resolve, reject) => {
256
+ const onOpen = () => {
257
+ socket.removeEventListener("open", onOpen);
258
+ socket.removeEventListener("close", onClose);
259
+ resolve();
260
+ };
261
+ const onClose = () => {
262
+ socket.removeEventListener("open", onOpen);
263
+ socket.removeEventListener("close", onClose);
264
+ reject(new Error("Computer terminal connection closed"));
265
+ };
266
+ socket.addEventListener("open", onOpen, { once: true });
267
+ socket.addEventListener("close", onClose, { once: true });
268
+ });
269
+ }
270
+ async #writeOutput(frame) {
271
+ if (Schema.is(terminalControlEventSchema)(frame)) {
272
+ switch (frame.type) {
273
+ case "ready":
274
+ this.#information = frame.information;
275
+ this.#resolveInformation?.(frame.information);
276
+ this.#resolveInformation = undefined;
277
+ this.#rejectInformation = undefined;
278
+ return;
279
+ case "error":
280
+ return;
281
+ case "exit":
282
+ this.#deadShell = true;
283
+ return;
284
+ }
285
+ }
286
+ let bytes;
287
+ if (frame instanceof ArrayBuffer)
288
+ bytes = new Uint8Array(frame);
289
+ else if (frame instanceof Blob)
290
+ bytes = new Uint8Array(await frame.arrayBuffer());
291
+ else
292
+ bytes = frame;
293
+ if (this.#closed)
294
+ return;
295
+ this.#sawOutput = true;
296
+ this.#outputController?.enqueue(bytes);
297
+ }
298
+ #socketClosed(socket) {
299
+ if (this.#closed || socket !== this.#socket)
300
+ return;
301
+ this.#socket = undefined;
302
+ if (this.#deadShell || !this.#sawOutput)
303
+ this.#sessionId = nanoid(LONG);
304
+ const delay = Math.min(30_000, 3_000 + this.#attempt * 3_000);
305
+ this.#retryTimer = this.#hostWindow.setTimeout(() => {
306
+ this.#retryTimer = undefined;
307
+ void this.#connect().catch((error) => {
308
+ this.#fail(decodeTerminalFailure(error));
309
+ });
310
+ }, delay);
311
+ }
312
+ async #ensureLease() {
313
+ if (this.#closed)
314
+ throw new Error("Computer terminal is closed");
315
+ const expiresAt = this.#leaseExpiresAt;
316
+ if (expiresAt === undefined ||
317
+ expiresAt === null ||
318
+ expiresAt <= Date.now())
319
+ await this.#ensureCurrent();
320
+ }
321
+ #ensureCurrent() {
322
+ if (this.#closed)
323
+ return Promise.reject(new Error("Computer terminal is closed"));
324
+ this.#accessPromise ??= this.#checkCurrent()
325
+ .then((selection) => {
326
+ this.#scheduleLeaseCheck();
327
+ return selection;
328
+ })
329
+ .finally(() => {
330
+ this.#accessPromise = undefined;
331
+ });
332
+ return this.#accessPromise;
333
+ }
334
+ async #checkCurrent() {
335
+ if (this.#closed)
336
+ throw new Error("Computer terminal is closed");
337
+ let configuration;
338
+ try {
339
+ configuration = await this.#ensureAccess(this.#controller.signal);
340
+ }
341
+ catch (error) {
342
+ const failure = decodeTerminalFailure(error);
343
+ this.#fail(failure);
344
+ throw new Error(failure, { cause: error });
345
+ }
346
+ this.#controller.signal.throwIfAborted();
347
+ if (this.#computerId === null ||
348
+ configuration.computerId !== this.#computerId) {
349
+ const error = new AppError("The selected Computer changed. Retry on the original Computer.", { code: "CAPABILITY_DENIED", retryable: false });
350
+ this.#shutdown(error);
351
+ throw error;
352
+ }
353
+ if (configuration.terminalUrl === null) {
354
+ const error = new AppError("Computer terminal is unavailable", {
355
+ code: "CAPABILITY_DENIED",
356
+ retryable: false,
357
+ });
358
+ this.#shutdown(error);
359
+ throw error;
360
+ }
361
+ this.#leaseExpiresAt = this.#ensureAccess.leaseExpiresAt?.();
362
+ return configuration;
363
+ }
364
+ #scheduleLeaseCheck() {
365
+ if (this.#leaseTimer !== undefined)
366
+ this.#hostWindow.clearTimeout(this.#leaseTimer);
367
+ this.#leaseTimer = undefined;
368
+ if (this.#closed)
369
+ return;
370
+ const expiresAt = this.#leaseExpiresAt;
371
+ if (expiresAt === undefined || expiresAt === null)
372
+ return;
373
+ this.#leaseTimer = this.#hostWindow.setTimeout(() => {
374
+ this.#leaseTimer = undefined;
375
+ void this.#ensureCurrent().catch((error) => {
376
+ this.#fail(decodeTerminalFailure(error));
377
+ });
378
+ }, Math.max(0, expiresAt - Date.now()));
379
+ }
380
+ #fail(message) {
381
+ this.#shutdown(new Error(message));
382
+ }
383
+ #shutdown(error) {
384
+ if (this.#closed)
385
+ return;
386
+ this.#closed = true;
387
+ this.#rejectInformation?.(error ?? new Error("Computer terminal is closed"));
388
+ this.#rejectInformation = undefined;
389
+ this.#resolveInformation = undefined;
390
+ if (error !== undefined) {
391
+ this.#outputDone = true;
392
+ this.#outputController?.error(error);
393
+ this.#inputController?.error(error);
394
+ }
395
+ else if (!this.#outputDone) {
396
+ this.#outputDone = true;
397
+ this.#outputController?.close();
398
+ }
399
+ this.#controller.abort();
400
+ this.#mountSignal?.removeEventListener("abort", this.#abort);
401
+ if (this.#retryTimer !== undefined)
402
+ this.#hostWindow.clearTimeout(this.#retryTimer);
403
+ if (this.#leaseTimer !== undefined)
404
+ this.#hostWindow.clearTimeout(this.#leaseTimer);
405
+ const socket = this.#socket;
406
+ this.#socket = undefined;
407
+ socket?.close();
408
+ this.#release(this);
409
+ }
410
+ }
@@ -0,0 +1,60 @@
1
+ export { ComputerTerminalHost, type ComputerTerminalAccess, type ComputerTerminalConnect, type ComputerTerminalHostOptions, type ComputerTerminalSelection, } from "./computer-terminal.js";
2
+ /** Computer routes supplied by the host; execution stays in the Computer runtime. */
3
+ export declare const computerSlots: {
4
+ readonly ports: {
5
+ readonly id: string;
6
+ readonly version: number;
7
+ readonly base: "/computer/";
8
+ readonly methods: {
9
+ readonly list: {
10
+ readonly kind: "json";
11
+ readonly method: "GET";
12
+ readonly path: "ports";
13
+ };
14
+ readonly expose: {
15
+ readonly kind: "json";
16
+ readonly method: "POST";
17
+ readonly path: "ports/expose";
18
+ };
19
+ readonly unexpose: {
20
+ readonly kind: "json";
21
+ readonly method: "POST";
22
+ readonly path: "ports/unexpose";
23
+ };
24
+ };
25
+ };
26
+ readonly processes: {
27
+ readonly id: string;
28
+ readonly version: number;
29
+ readonly base: "/computer/";
30
+ readonly methods: {
31
+ readonly start: {
32
+ readonly kind: "json";
33
+ readonly method: "POST";
34
+ readonly path: "processes/start";
35
+ };
36
+ readonly list: {
37
+ readonly kind: "json";
38
+ readonly method: "GET";
39
+ readonly path: "processes";
40
+ };
41
+ readonly kill: {
42
+ readonly kind: "json";
43
+ readonly method: "POST";
44
+ readonly path: "processes/kill";
45
+ };
46
+ };
47
+ };
48
+ readonly terminal: {
49
+ readonly id: string;
50
+ readonly version: number;
51
+ readonly base: "/computer/";
52
+ readonly methods: {
53
+ readonly open: {
54
+ readonly kind: "transport";
55
+ readonly method: "GET";
56
+ readonly path: "terminal";
57
+ };
58
+ };
59
+ };
60
+ };
@@ -0,0 +1,33 @@
1
+ import { computerPorts, computerProcesses, computerTerminal, } from "@fungi.computer/agar/computer";
2
+ export { ComputerTerminalHost, } from "./computer-terminal.js";
3
+ /** Computer routes supplied by the host; execution stays in the Computer runtime. */
4
+ export const computerSlots = {
5
+ ports: {
6
+ id: computerPorts.id,
7
+ version: computerPorts.version,
8
+ base: "/computer/",
9
+ methods: {
10
+ list: { kind: "json", method: "GET", path: "ports" },
11
+ expose: { kind: "json", method: "POST", path: "ports/expose" },
12
+ unexpose: { kind: "json", method: "POST", path: "ports/unexpose" },
13
+ },
14
+ },
15
+ processes: {
16
+ id: computerProcesses.id,
17
+ version: computerProcesses.version,
18
+ base: "/computer/",
19
+ methods: {
20
+ start: { kind: "json", method: "POST", path: "processes/start" },
21
+ list: { kind: "json", method: "GET", path: "processes" },
22
+ kill: { kind: "json", method: "POST", path: "processes/kill" },
23
+ },
24
+ },
25
+ terminal: {
26
+ id: computerTerminal.id,
27
+ version: computerTerminal.version,
28
+ base: "/computer/",
29
+ methods: {
30
+ open: { kind: "transport", method: "GET", path: "terminal" },
31
+ },
32
+ },
33
+ };
@@ -0,0 +1,57 @@
1
+ import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
2
+ /** The emitting installation as the host attested it on the emit's frame. */
3
+ export type HostEventSource = Readonly<{
4
+ installationId: string;
5
+ appId: string;
6
+ releaseId: string;
7
+ }>;
8
+ /** One accepted event, as a host recipient receives it. */
9
+ export type HostRecipientEvent = Readonly<{
10
+ source: HostEventSource;
11
+ type: string;
12
+ eventId: string;
13
+ payload: Readonly<{
14
+ type: string;
15
+ timestamp: string;
16
+ data: unknown;
17
+ }>;
18
+ }>;
19
+ /** One recipient's delivery record for one event. */
20
+ export type RecipientDelivery = Readonly<{
21
+ recipient: string;
22
+ eventId: string;
23
+ type: string;
24
+ state: "pending" | "delivered" | "failed";
25
+ attempts: number;
26
+ /** The last failed attempt's host-owned diagnostic, never guest text. */
27
+ lastError: string | null;
28
+ updatedAt: number;
29
+ }>;
30
+ /**
31
+ * Retained D5 baseline: a ten-second attempt bound, and a terminal failure
32
+ * after six failed attempts (the first plus one per retry delay).
33
+ */
34
+ export declare const RECIPIENT_ATTEMPT_MS = 10000;
35
+ export declare const RECIPIENT_RETRY_DELAYS_MS: readonly [10000, 60000, 300000, 1800000, 7200000];
36
+ /** The owner's SQLite records, its alarm and the one typed delivery port. */
37
+ export declare function createRecipientDeliveries(options: {
38
+ sql: WatchdogSqliteOwner["sql"];
39
+ now(): number;
40
+ /** Schedule the owner's next drain no later than `at`. */
41
+ wake(at: number): Promise<void>;
42
+ /** Deliver one event to one recipient; a rejection is one failed attempt. */
43
+ deliver(recipient: string, event: HostRecipientEvent): Promise<void>;
44
+ attemptMs?: number;
45
+ }): {
46
+ /**
47
+ * Record one pending row per recipient (idempotent per event id). The
48
+ * caller commits it with its other rows and then wakes for `now`.
49
+ */
50
+ enqueue(event: HostRecipientEvent, recipients: readonly string[]): number;
51
+ /** Attempt every due row once, record each outcome, and re-arm for the next retry. */
52
+ drain(): Promise<void>;
53
+ /** The earliest pending retry, if any. */
54
+ nextDue: () => number | null;
55
+ /** Recent deliveries to one recipient, newest first. */
56
+ deliveries(recipient: string): RecipientDelivery[];
57
+ };