@moondeck/station 0.0.1 → 1.0.1

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 (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -2
  3. package/bin/uai-host.mjs +130 -0
  4. package/db/migrations/0000_host_tasks.sql +12 -0
  5. package/db/migrations/0001_host_ui.sql +11 -0
  6. package/db/migrations/0002_host_github_tokens.sql +8 -0
  7. package/db/migrations/0003_host_ssh_keys.sql +8 -0
  8. package/db/migrations/0004_host_owner_name.sql +1 -0
  9. package/db/migrations/0005_host_env_vars.sql +5 -0
  10. package/db/migrations/0006_host_project_env.sql +9 -0
  11. package/db/migrations/0007_host_github_token_kind.sql +7 -0
  12. package/db/migrations/0008_host_mcp_connections.sql +18 -0
  13. package/db/migrations/0009_host_agent_sessions.sql +15 -0
  14. package/db/migrations/0010_host_engine_accounts.sql +13 -0
  15. package/db/migrations/0011_codex_thread_id.sql +5 -0
  16. package/db/migrations/0012_agent_session_attach_key.sql +4 -0
  17. package/db/migrations/0013_github_credential_generations.sql +11 -0
  18. package/db/migrations/0014_host_inventory_event_index.sql +1 -0
  19. package/db/migrations/0015_host_settings.sql +9 -0
  20. package/db/migrations/0016_task_environment.sql +2 -0
  21. package/db/migrations/0017_engine_account_scope.sql +7 -0
  22. package/db/migrations/0018_engine_account_position.sql +3 -0
  23. package/db/migrations/0019_engine_account_store_ref.sql +3 -0
  24. package/db/migrations/0020_user_credential_store_ref.sql +6 -0
  25. package/db/migrations/0021_project_env_store_ref.sql +3 -0
  26. package/db/migrations/0022_task_project_ids.sql +5 -0
  27. package/db/migrations/0023_project_env_org.sql +4 -0
  28. package/db/migrations/0024_task_env_mode.sql +9 -0
  29. package/db/migrations/0025_project_env_authority.sql +147 -0
  30. package/db/migrations/0026_project_env_replica.sql +150 -0
  31. package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
  32. package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
  33. package/db/migrations/0029_task_shared_files_mode.sql +5 -0
  34. package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
  35. package/db/migrations/0031_first_turn_receipts.sql +9 -0
  36. package/db/migrations/meta/_journal.json +230 -0
  37. package/db/schema.ts +871 -0
  38. package/images/standard/Dockerfile +347 -0
  39. package/images/standard/README.md +167 -0
  40. package/images/standard/container/code-server-settings.json +63 -0
  41. package/images/standard/container/corepack-version +1 -0
  42. package/images/standard/container/uai-init +704 -0
  43. package/images/standard/container/uai-materialize-runtimes +1527 -0
  44. package/images/standard/container/uai-task-forwarder.mjs +303 -0
  45. package/images/standard/tool-versions +2 -0
  46. package/lib/__fixtures__/skill-index/SKILL.md +10 -0
  47. package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
  48. package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
  49. package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
  50. package/lib/agent-cli.ts +1507 -0
  51. package/lib/agent-errors.ts +22 -0
  52. package/lib/agent.ts +672 -0
  53. package/lib/agents/attach-compatibility.ts +51 -0
  54. package/lib/agents/claude.ts +918 -0
  55. package/lib/agents/codex-token-usage.fixture.json +130 -0
  56. package/lib/agents/codex.ts +1392 -0
  57. package/lib/agents/cursor-period-usage.ts +253 -0
  58. package/lib/agents/cursor.ts +562 -0
  59. package/lib/agents/dispatch.ts +78 -0
  60. package/lib/agents/durable-proc.ts +334 -0
  61. package/lib/agents/factory.ts +57 -0
  62. package/lib/agents/grok.ts +381 -0
  63. package/lib/agents/kimi.ts +329 -0
  64. package/lib/agents/machine-durable.ts +604 -0
  65. package/lib/agents/mock.ts +133 -0
  66. package/lib/agents/mode.ts +64 -0
  67. package/lib/agents/opencode.ts +508 -0
  68. package/lib/agents/output-source.ts +3 -0
  69. package/lib/agents/proc.ts +221 -0
  70. package/lib/agents/rate-limit.ts +337 -0
  71. package/lib/agents/registry.ts +182 -0
  72. package/lib/agents/session-state.ts +197 -0
  73. package/lib/agents/skill-index-preamble.fixture.ts +26 -0
  74. package/lib/agents/transport.ts +741 -0
  75. package/lib/agents/types.ts +254 -0
  76. package/lib/agents/uai.ts +106 -0
  77. package/lib/agents/usage.ts +44 -0
  78. package/lib/apple-one-off.ts +277 -0
  79. package/lib/apple-runtime-recycle.ts +237 -0
  80. package/lib/apple-uninstall-teardown.ts +224 -0
  81. package/lib/assignment-evidence.ts +94 -0
  82. package/lib/attachments.ts +48 -0
  83. package/lib/browser-testing.ts +1537 -0
  84. package/lib/capture-flow.ts +332 -0
  85. package/lib/cloud-credential-cleanup-adapter.ts +123 -0
  86. package/lib/cloud-credential-host.ts +512 -0
  87. package/lib/cloud-credential-leases.ts +2587 -0
  88. package/lib/cloud-credential-runtime.ts +1357 -0
  89. package/lib/cloud-state.ts +56 -0
  90. package/lib/codex-auth.ts +456 -0
  91. package/lib/command-db.ts +310 -0
  92. package/lib/compose-network-recovery.ts +283 -0
  93. package/lib/connection-secret-redaction.ts +25 -0
  94. package/lib/container-runtime.ts +1470 -0
  95. package/lib/credential-cleanup-inventory.ts +490 -0
  96. package/lib/credential-consumers/engine-anchor.ts +1880 -0
  97. package/lib/credential-consumers/engines.ts +797 -0
  98. package/lib/credential-consumers/files.ts +934 -0
  99. package/lib/credential-consumers/github.ts +702 -0
  100. package/lib/credential-consumers/mcp.ts +389 -0
  101. package/lib/credential-consumers/task-files.ts +127 -0
  102. package/lib/credential-consumers/task-secrets.ts +10 -0
  103. package/lib/credential-store-pickup.ts +149 -0
  104. package/lib/credential-store-scope.ts +112 -0
  105. package/lib/credential-store.ts +932 -0
  106. package/lib/credentials-activation-authority.ts +709 -0
  107. package/lib/credentials-purge.ts +1006 -0
  108. package/lib/custom-connection-capture.ts +395 -0
  109. package/lib/custom-connection-launch.ts +384 -0
  110. package/lib/custom-connection-runtime.ts +141 -0
  111. package/lib/db.ts +80 -0
  112. package/lib/disk-pressure.ts +42 -0
  113. package/lib/docker-exec.ts +156 -0
  114. package/lib/engine-accounts.ts +1970 -0
  115. package/lib/engine-connect.ts +321 -0
  116. package/lib/engine-credential-env.ts +52 -0
  117. package/lib/engine-login.ts +2933 -0
  118. package/lib/engines.ts +1342 -0
  119. package/lib/enrollment-state.ts +251 -0
  120. package/lib/env-file.ts +246 -0
  121. package/lib/env-schema.ts +937 -0
  122. package/lib/env.ts +195 -0
  123. package/lib/first-turn-receipts.ts +62 -0
  124. package/lib/fs-aliases.ts +49 -0
  125. package/lib/git-clone-credentials.ts +334 -0
  126. package/lib/git-diff.ts +530 -0
  127. package/lib/git-identity.ts +715 -0
  128. package/lib/github-git-auth.ts +207 -0
  129. package/lib/github-origin.ts +28 -0
  130. package/lib/github-tokens.ts +2439 -0
  131. package/lib/host-cloud-url.ts +62 -0
  132. package/lib/host-config.ts +279 -0
  133. package/lib/host-env.ts +424 -0
  134. package/lib/host-logs.ts +965 -0
  135. package/lib/internal-connections.ts +5 -0
  136. package/lib/keyed-promise-tail.ts +23 -0
  137. package/lib/launch-mode.ts +25 -0
  138. package/lib/lease.ts +239 -0
  139. package/lib/legacy-host.ts +769 -0
  140. package/lib/legacy-runtime-retirement.ts +424 -0
  141. package/lib/legacy-runtime-v1.fixture.ts +627 -0
  142. package/lib/machine-exec.ts +238 -0
  143. package/lib/machine-gateway-tunnel.ts +344 -0
  144. package/lib/machine-keys.ts +65 -0
  145. package/lib/machine-project-env-loader.ts +152 -0
  146. package/lib/machine-provider-aws.ts +923 -0
  147. package/lib/machine-provider-local.ts +338 -0
  148. package/lib/machine-provider.ts +90 -0
  149. package/lib/managed-activation-watcher.ts +72 -0
  150. package/lib/managed-install-owner-watcher.ts +55 -0
  151. package/lib/managed-operation-drain.ts +57 -0
  152. package/lib/managed-runtime.ts +4296 -0
  153. package/lib/managed-update-scheduler.ts +125 -0
  154. package/lib/mcp-capture.ts +865 -0
  155. package/lib/mcp-config-lock.ts +2 -0
  156. package/lib/mcp-connections.ts +959 -0
  157. package/lib/mcp-gateway.ts +2594 -0
  158. package/lib/obs.ts +512 -0
  159. package/lib/openrouter-budget.ts +247 -0
  160. package/lib/orchestrator.ts +11409 -0
  161. package/lib/person-capture.ts +327 -0
  162. package/lib/preview-ports.ts +85 -0
  163. package/lib/preview-sidecar.ts +331 -0
  164. package/lib/project-env-authority.ts +1319 -0
  165. package/lib/project-env-feature.ts +74 -0
  166. package/lib/project-env-legacy-tags.ts +130 -0
  167. package/lib/project-env-loader.ts +242 -0
  168. package/lib/project-env-replica.ts +2094 -0
  169. package/lib/project-env-schema-capture.ts +342 -0
  170. package/lib/project-env-staging.ts +148 -0
  171. package/lib/project-env-task-policy.ts +176 -0
  172. package/lib/project-env-task-replica.ts +1752 -0
  173. package/lib/release-feed.ts +61 -0
  174. package/lib/release-manifest.ts +710 -0
  175. package/lib/release-trust.ts +46 -0
  176. package/lib/repo-clone.ts +38 -0
  177. package/lib/routing/shadow.ts +261 -0
  178. package/lib/runner-boot.ts +10 -0
  179. package/lib/runtime-activation-tail.ts +279 -0
  180. package/lib/runtime-archive.ts +1216 -0
  181. package/lib/runtime-authority.ts +166 -0
  182. package/lib/runtime-guard.ts +36 -0
  183. package/lib/runtime-provider-state.ts +169 -0
  184. package/lib/runtime-reconnect.ts +86 -0
  185. package/lib/runtime-state.ts +1033 -0
  186. package/lib/safe-http.ts +364 -0
  187. package/lib/secrets-proxy/address.ts +95 -0
  188. package/lib/secrets-proxy/audit.ts +30 -0
  189. package/lib/secrets-proxy/ca-store.ts +200 -0
  190. package/lib/secrets-proxy/ca.ts +470 -0
  191. package/lib/secrets-proxy/container-steps.ts +138 -0
  192. package/lib/secrets-proxy/der.ts +273 -0
  193. package/lib/secrets-proxy/engine.ts +1633 -0
  194. package/lib/secrets-proxy/host.ts +93 -0
  195. package/lib/secrets-proxy/hostname.ts +57 -0
  196. package/lib/secrets-proxy/placeholders.ts +196 -0
  197. package/lib/secrets-proxy/policy.ts +150 -0
  198. package/lib/secrets-proxy/readiness.ts +148 -0
  199. package/lib/secrets-proxy/runtime.ts +582 -0
  200. package/lib/secrets-proxy/scrub.ts +559 -0
  201. package/lib/secrets-proxy/sidecar-view.ts +139 -0
  202. package/lib/secrets-proxy/task-scripts.ts +262 -0
  203. package/lib/secrets-proxy/task-trust.ts +605 -0
  204. package/lib/secrets-proxy/trust-watch.ts +80 -0
  205. package/lib/secrets.ts +71 -0
  206. package/lib/shared-files-s3.ts +976 -0
  207. package/lib/shared-files.ts +156 -0
  208. package/lib/sidecar-assign.ts +49 -0
  209. package/lib/sidecar-guard.ts +58 -0
  210. package/lib/sidecar-resume.ts +178 -0
  211. package/lib/sidecar.ts +69 -0
  212. package/lib/signin-box-credentials.ts +465 -0
  213. package/lib/signin-box.ts +114 -0
  214. package/lib/skill-index-example.fixture.ts +56 -0
  215. package/lib/skill-layout.ts +417 -0
  216. package/lib/skills.ts +489 -0
  217. package/lib/ssh.ts +401 -0
  218. package/lib/standard-image-cleanup.ts +117 -0
  219. package/lib/standard-image-pin.ts +207 -0
  220. package/lib/standard-image.ts +1697 -0
  221. package/lib/stopped-task-status-queue.ts +120 -0
  222. package/lib/task-container-cli.ts +269 -0
  223. package/lib/task-credential-cleanup.ts +752 -0
  224. package/lib/task-diff-sandbox.ts +464 -0
  225. package/lib/task-diff.ts +91 -0
  226. package/lib/task-env-forwarder.ts +228 -0
  227. package/lib/task-env-source.ts +249 -0
  228. package/lib/task-environment/anchor.ts +31 -0
  229. package/lib/task-environment/apple-container.ts +860 -0
  230. package/lib/task-environment/docker.ts +1344 -0
  231. package/lib/task-environment/index.ts +466 -0
  232. package/lib/task-environment/legacy-adoption.ts +467 -0
  233. package/lib/task-environment/machine-environment.ts +61 -0
  234. package/lib/task-environment/machine-task-up.ts +1227 -0
  235. package/lib/task-environment/machine.ts +974 -0
  236. package/lib/task-environment/registry.ts +59 -0
  237. package/lib/task-environment/self-identity.ts +20 -0
  238. package/lib/task-environment/self.ts +566 -0
  239. package/lib/task-environment/types.ts +642 -0
  240. package/lib/task-environment/workspace-files.ts +685 -0
  241. package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
  242. package/lib/task-files-machine-retirement-outbox.ts +592 -0
  243. package/lib/task-files-machine-retirement.ts +116 -0
  244. package/lib/task-files-readiness.ts +127 -0
  245. package/lib/task-gateway-port.ts +22 -0
  246. package/lib/task-gateway-protocol.ts +190 -0
  247. package/lib/task-gateway-recovery.ts +47 -0
  248. package/lib/task-gateway-state.ts +512 -0
  249. package/lib/task-identity.ts +19 -0
  250. package/lib/task-inventory.ts +682 -0
  251. package/lib/task-machine-credentials-activation.ts +95 -0
  252. package/lib/task-machine-swap-safety.ts +729 -0
  253. package/lib/task-person-credentials.ts +76 -0
  254. package/lib/task-project-ops-host.ts +147 -0
  255. package/lib/task-project-ops.ts +1062 -0
  256. package/lib/task-secret-staging.ts +16 -0
  257. package/lib/task-status.ts +46 -0
  258. package/lib/task-token.ts +46 -0
  259. package/lib/token-broker-adopt.ts +344 -0
  260. package/lib/token-broker-lambda.ts +105 -0
  261. package/lib/token-broker-local.ts +392 -0
  262. package/lib/token-broker-revision.ts +310 -0
  263. package/lib/token-broker-ssm.ts +525 -0
  264. package/lib/token-broker.ts +218 -0
  265. package/lib/token-lanes/openai-oauth.ts +123 -0
  266. package/lib/token-lanes/registry.ts +45 -0
  267. package/lib/token-lanes/types.ts +163 -0
  268. package/lib/token-lanes/xai-oauth.ts +165 -0
  269. package/lib/transcript.ts +128 -0
  270. package/lib/tunnel-registry.ts +220 -0
  271. package/lib/tunnel-runtime.ts +235 -0
  272. package/lib/ulid.ts +16 -0
  273. package/lib/user-bus-env.ts +116 -0
  274. package/lib/user-credentials-store.ts +435 -0
  275. package/lib/vnode-pressure.ts +142 -0
  276. package/package.json +115 -8
  277. package/runner/runner.mjs +442 -0
  278. package/scripts/agent/_common.sh +633 -0
  279. package/scripts/agent/env-hook.sh +626 -0
  280. package/scripts/agent/moondeck-cli.sh +5 -0
  281. package/scripts/agent/task-down.sh +291 -0
  282. package/scripts/agent/task-status.sh +70 -0
  283. package/scripts/agent/task-up.sh +3761 -0
  284. package/scripts/agent/uai-env-exec.sh +26 -0
  285. package/scripts/install/darwin.ts +1048 -0
  286. package/scripts/install/linux.ts +958 -0
  287. package/scripts/install/types.ts +78 -0
  288. package/scripts/install/util.ts +254 -0
  289. package/scripts/install/win.ts +143 -0
  290. package/src/activation-respawn.ts +155 -0
  291. package/src/apple-tunnel-route.ts +104 -0
  292. package/src/bridge-diagnostics.ts +39 -0
  293. package/src/capture-wire.ts +102 -0
  294. package/src/cli.ts +2585 -0
  295. package/src/cloud-credentials.ts +2144 -0
  296. package/src/cloud-origins.ts +23 -0
  297. package/src/credential-location.ts +248 -0
  298. package/src/credential-wire-validation.ts +25 -0
  299. package/src/custom-connections.ts +237 -0
  300. package/src/engine-account-paths.ts +11 -0
  301. package/src/engine-models.ts +39 -0
  302. package/src/engine-usage.ts +104 -0
  303. package/src/enrollment-next-steps.ts +24 -0
  304. package/src/event-outbox.ts +185 -0
  305. package/src/index.ts +2907 -0
  306. package/src/load-env.ts +97 -0
  307. package/src/machine-tunnel-route.ts +156 -0
  308. package/src/main.ts +6363 -0
  309. package/src/never-proxied-domains.ts +31 -0
  310. package/src/outbound-frame.ts +31 -0
  311. package/src/paths.ts +81 -0
  312. package/src/project-env-latest.ts +130 -0
  313. package/src/project-env-policy.ts +396 -0
  314. package/src/project-env-wording.ts +53 -0
  315. package/src/protocol.ts +5550 -0
  316. package/src/runtime-bootstrap.ts +165 -0
  317. package/src/station-identity.ts +96 -0
  318. package/src/task-env-names.ts +28 -0
  319. package/src/task-secrets.ts +75 -0
  320. package/src/tunnel-dial.ts +102 -0
  321. package/src/ui/server.ts +852 -0
  322. package/src/ui/types.ts +173 -0
  323. package/src/workspace-path-policy.ts +41 -0
  324. package/ui/app.js +1102 -0
  325. package/ui/index.html +90 -0
  326. package/ui/style.css +685 -0
  327. package/ui/uai-favicon.svg +21 -0
  328. package/ui/uai-logo-black.svg +9 -0
  329. package/ui/uai-wheel.svg +9 -0
  330. package/bin/moondeck-station.mjs +0 -3
@@ -0,0 +1,104 @@
1
+ /**
2
+ * ADR-106 Apple tunnel routing, extracted from main.ts so its two safety
3
+ * properties are directly testable: the identity proof and the TCP connect
4
+ * both happen inside the task's lifecycle slot (a concurrent
5
+ * taskDown/recreate cannot free and reuse the address between proof and
6
+ * connection), and a readiness-epoch change during the awaited connect
7
+ * destroys the socket rather than carrying a dead generation's proof out.
8
+ */
9
+
10
+ export interface AppleRouteSocket {
11
+ connect(port: number, host: string): void;
12
+ once(event: "connect" | "error", listener: () => void): void;
13
+ destroy(): void;
14
+ }
15
+
16
+ export interface AppleTunnelRouteDeps {
17
+ getTask(taskId: string): { statusMirror: string | null } | undefined;
18
+ reconstruct(task: unknown): Promise<{
19
+ inspectRoute?: () => Promise<
20
+ | { kind: "running"; ipv4Address: string }
21
+ | { kind: "unavailable" }
22
+ | { kind: "unknown"; detail: string }
23
+ >;
24
+ } | null>;
25
+ runTaskLifecycle<T>(taskId: string, work: () => Promise<T>): Promise<T>;
26
+ runtimeProblem(): unknown;
27
+ readinessEpoch(): number;
28
+ epochIsCurrent(epoch: number): boolean;
29
+ poison(detail: string): void;
30
+ createSocket(): AppleRouteSocket;
31
+ connectTimeoutMs?: number;
32
+ }
33
+
34
+ export interface AppleTunnelRouteRequest {
35
+ taskId: string;
36
+ target: string;
37
+ containerPort?: number;
38
+ }
39
+
40
+ export async function resolveAppleTunnelRoute(
41
+ request: AppleTunnelRouteRequest,
42
+ deps: AppleTunnelRouteDeps,
43
+ ): Promise<{ host: string; port: number; socket: AppleRouteSocket } | null> {
44
+ const applePort =
45
+ request.target === "editor" ? 8080 : request.containerPort;
46
+ if (!applePort) return null;
47
+ if (deps.getTask(request.taskId)?.statusMirror !== "running") return null;
48
+ return deps.runTaskLifecycle(request.taskId, async () => {
49
+ const current = deps.getTask(request.taskId);
50
+ if (!current || current.statusMirror !== "running") return null;
51
+ let route:
52
+ | { kind: "running"; ipv4Address: string }
53
+ | { kind: "unavailable" }
54
+ | { kind: "unknown"; detail: string }
55
+ | null = null;
56
+ try {
57
+ const handle = await deps.reconstruct(current);
58
+ if (handle === null || handle.inspectRoute === undefined) return null;
59
+ route = await handle.inspectRoute();
60
+ } catch {
61
+ return null;
62
+ }
63
+ if (route === null) return null;
64
+ if (route.kind === "unknown") {
65
+ // The CLI answered with neither presence nor absence — the same signal
66
+ // class as a Docker daemon that stopped responding mid-tunnel.
67
+ deps.poison(route.detail);
68
+ return null;
69
+ }
70
+ if (route.kind !== "running") return null;
71
+ if (deps.runtimeProblem()) return null;
72
+ const routeEpoch = deps.readinessEpoch();
73
+ const upstream = deps.createSocket();
74
+ const connected = await new Promise<boolean>((resolveConnect) => {
75
+ let settled = false;
76
+ const settle = (ok: boolean): void => {
77
+ if (settled) return;
78
+ settled = true;
79
+ clearTimeout(deadline);
80
+ resolveConnect(ok);
81
+ };
82
+ const deadline = setTimeout(() => {
83
+ upstream.destroy();
84
+ settle(false);
85
+ }, deps.connectTimeoutMs ?? 10_000);
86
+ deadline.unref?.();
87
+ upstream.once("connect", () => settle(true));
88
+ upstream.once("error", () => settle(false));
89
+ upstream.connect(applePort, route.ipv4Address);
90
+ });
91
+ if (
92
+ !connected ||
93
+ // The awaited connect is a window: a forced recheck can invalidate the
94
+ // runtime mid-wait, and a proof from a dead generation must not carry
95
+ // a live socket out of the slot.
96
+ !deps.epochIsCurrent(routeEpoch) ||
97
+ deps.runtimeProblem()
98
+ ) {
99
+ upstream.destroy();
100
+ return null;
101
+ }
102
+ return { host: route.ipv4Address, port: applePort, socket: upstream };
103
+ });
104
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * What station.log says about a lost cloud connection, so its cause can be
3
+ * read off the log. The previous MacBook Air station showed 599 reconnects
4
+ * over about 18 hours as bare "disconnected; reconnecting in ~1s" lines:
5
+ * neither who closed the socket nor why was logged, and nothing showed that
6
+ * the Mac had been asleep (2026-11-03).
7
+ */
8
+
9
+ /** The close code and reason in words: who closed it, and why if known. */
10
+ export function describeClose(code: number, reason: string): string {
11
+ const why = reason.trim();
12
+ const known: Record<number, string> = {
13
+ 1000: "closed normally",
14
+ 1001: "the cloud went away (restart or deploy)",
15
+ 1006: "the connection dropped without a close frame (network, sleep or a proxy)",
16
+ 1011: "the cloud hit an internal error",
17
+ 1012: "the cloud is restarting",
18
+ 1013: "the cloud asked to try again later",
19
+ 4000: "closed by the cloud or this station",
20
+ 4001: "authentication refused",
21
+ };
22
+ const label = known[code] ?? "closed";
23
+ return `code ${code}, ${label}${why ? `: ${why}` : ""}`;
24
+ }
25
+
26
+ /** How far past its due time a periodic timer may fire before the gap counts
27
+ * as a suspension (system sleep, a stopped process) rather than load. */
28
+ export const SUSPEND_SLACK_MS = 20_000;
29
+
30
+ /**
31
+ * The time this process was suspended, when a timer due every `intervalMs`
32
+ * fires `now - previousTick` later; null when it fired on time. A sleeping
33
+ * Mac stops every timer; on wake the gap shows, the cloud has long dropped
34
+ * the silent socket (it closes one silent for 45 s), and a reconnect follows.
35
+ */
36
+ export function suspendedFor(previousTick: number, now: number, intervalMs: number): number | null {
37
+ const gap = now - previousTick;
38
+ return gap > intervalMs + SUSPEND_SLACK_MS ? gap : null;
39
+ }
@@ -0,0 +1,102 @@
1
+ /** Pure capture contract, independent of leases and custom connections. */
2
+ import { parseLeaseParameterName, parseBrokerParameterName, sameLocation, isIdValue, isTagValue, type CredentialLocation } from "./credential-location";
3
+ import { exactObject, isPositiveInteger, isEpochMs } from "./credential-wire-validation";
4
+
5
+ /**
6
+ * What the cloud tells the sign-in box to write for one capture: the
7
+ * revision's parameters (the box never chooses a name) and the ids it tags
8
+ * them with. The box checks every field against the others before it writes.
9
+ */
10
+ export interface CaptureFlowWire {
11
+ credentialId: string;
12
+ revision: string;
13
+ leaseParameter: string;
14
+ /** Null for a static credential. */
15
+ brokerParameter: string | null;
16
+ /** The workspace tag: required except for GitHub and SSH. */
17
+ orgId: string | null;
18
+ /** The person, for a personal credential. */
19
+ userId: string | null;
20
+ }
21
+
22
+ /** What the box answers once both parts are written: never material. */
23
+ export interface CaptureResultWire {
24
+ credentialId: string;
25
+ revision: string;
26
+ /** The lease-readable parameter's Parameter Store version. */
27
+ leaseVersion: number;
28
+ /** The access token's expiry when the box could read it. */
29
+ expiresAt: number | null;
30
+ }
31
+
32
+ /**
33
+ * A capture flow, rebuilt and checked, with the location its parameters name,
34
+ * or null. The two parameters must name the same credential and revision under
35
+ * their own roots, and the tags must be the parameter's owner: a workspace
36
+ * credential's `orgId` is its owner and it has no `userId`; a personal AI
37
+ * account or MCP connection names both its person and its workspace; GitHub
38
+ * and SSH name their person alone.
39
+ */
40
+ export function parseCaptureFlowWire(
41
+ value: unknown,
42
+ ): (CaptureFlowWire & { location: CredentialLocation }) | null {
43
+ const o = exactObject(value, [
44
+ "credentialId",
45
+ "revision",
46
+ "leaseParameter",
47
+ "brokerParameter",
48
+ "orgId",
49
+ "userId",
50
+ ]);
51
+ if (
52
+ !o ||
53
+ !isIdValue(o.credentialId) ||
54
+ !isIdValue(o.revision) ||
55
+ typeof o.leaseParameter !== "string" ||
56
+ !(o.brokerParameter === null || typeof o.brokerParameter === "string") ||
57
+ !(o.orgId === null || (isIdValue(o.orgId) && isTagValue(o.orgId))) ||
58
+ !(o.userId === null || (isIdValue(o.userId) && isTagValue(o.userId)))
59
+ ) {
60
+ return null;
61
+ }
62
+ const location = parseLeaseParameterName(o.leaseParameter);
63
+ if (!location || location.revision !== o.revision) return null;
64
+ const isPerson = location.class === "github" || location.class === "ssh";
65
+ if (!isPerson && location.credentialId !== o.credentialId) return null;
66
+ if (o.brokerParameter !== null) {
67
+ const broker = parseBrokerParameterName(o.brokerParameter);
68
+ if (!broker || !sameLocation(broker, location)) return null;
69
+ }
70
+ if (isPerson && o.brokerParameter !== null) return null;
71
+ const ownerOk =
72
+ location.scope === "org"
73
+ ? o.orgId === location.ownerId && o.userId === null
74
+ : o.userId === location.ownerId && (isPerson ? o.orgId === null : o.orgId !== null);
75
+ if (!ownerOk) return null;
76
+ return {
77
+ credentialId: o.credentialId,
78
+ revision: o.revision,
79
+ leaseParameter: o.leaseParameter,
80
+ brokerParameter: o.brokerParameter,
81
+ orgId: o.orgId,
82
+ userId: o.userId,
83
+ location,
84
+ };
85
+ }
86
+
87
+ export function parseCaptureResultWire(value: unknown): CaptureResultWire | null {
88
+ const o = exactObject(value, ["credentialId", "revision", "leaseVersion", "expiresAt"]);
89
+ return o &&
90
+ isIdValue(o.credentialId) &&
91
+ isIdValue(o.revision) &&
92
+ isPositiveInteger(o.leaseVersion) &&
93
+ (o.expiresAt === null || isEpochMs(o.expiresAt))
94
+ ? {
95
+ credentialId: o.credentialId,
96
+ revision: o.revision,
97
+ leaseVersion: o.leaseVersion,
98
+ expiresAt: o.expiresAt,
99
+ }
100
+ : null;
101
+ }
102
+