@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,165 @@
1
+ /** ADR-106 boot wiring kept side-effect free and directly testable. */
2
+
3
+ import {
4
+ appleContainerRuntimeBinding,
5
+ containerRuntimeMachineIdentity,
6
+ rejectContainerRuntimeSelection,
7
+ reprobeContainerRuntimeMachineIdentity,
8
+ setAppleContainerRuntime,
9
+ setAppleContainerSelectionGuard,
10
+ type AppleRuntimeBinding,
11
+ } from "../lib/container-runtime";
12
+ import {
13
+ resolveBundledRuntimeFromEnvironment,
14
+ type ResolvedBundledRuntime,
15
+ } from "../lib/managed-runtime";
16
+ import {
17
+ adoptLegacyTaskEnvironments,
18
+ hostTaskEnvironmentsAllowMachineSelection,
19
+ hostTaskEnvironmentsMatchMachine,
20
+ setAppleContainerBinding,
21
+ type HostMachineIdentity,
22
+ type LegacyTaskEnvironmentAdoptionVerdict,
23
+ type TaskEnvironmentBackendVerdict,
24
+ } from "../lib/task-environment";
25
+
26
+ export interface RuntimeBootstrapDeps {
27
+ readonly resolveRuntime?: () => Promise<ResolvedBundledRuntime | null>;
28
+ readonly registerRuntime?: (binding: AppleRuntimeBinding | null) => void;
29
+ readonly registerSelectionGuard?: (
30
+ guard: ((endpoint: string) => boolean | Promise<boolean>) | null,
31
+ ) => void;
32
+ readonly allowMachineSelection?: (machine: {
33
+ backend: "apple-container";
34
+ endpoint: string;
35
+ }) => TaskEnvironmentBackendVerdict;
36
+ }
37
+
38
+ /**
39
+ * Register the bundled Apple container toolchain from the active signed
40
+ * generation, if one exists (ADR-106). Registration is fail-closed: seams are
41
+ * cleared first so a thrown validation cannot leave a stale binding, and the
42
+ * selection guard keeps `auto` off the bundled runtime while durable task
43
+ * rows still belong to another backend. An unmanaged checkout, Linux archive,
44
+ * or rolling v1 generation registers nothing; there is deliberately no PATH
45
+ * or inferred-layout fallback.
46
+ */
47
+ export async function configureBundledContainerRuntime(
48
+ deps: RuntimeBootstrapDeps = {},
49
+ ): Promise<ResolvedBundledRuntime | null> {
50
+ const registerRuntime = deps.registerRuntime ?? setAppleContainerRuntime;
51
+ const registerGuard =
52
+ deps.registerSelectionGuard ?? setAppleContainerSelectionGuard;
53
+ registerRuntime(null);
54
+ registerGuard(null);
55
+
56
+ const bundled = await (
57
+ deps.resolveRuntime ?? resolveBundledRuntimeFromEnvironment
58
+ )();
59
+ if (bundled === null) return null;
60
+
61
+ const allowSelection =
62
+ deps.allowMachineSelection ?? hostTaskEnvironmentsAllowMachineSelection;
63
+ registerGuard(
64
+ (endpoint) =>
65
+ allowSelection({ backend: "apple-container", endpoint }).ok,
66
+ );
67
+ registerRuntime({
68
+ containerCliPath: bundled.containerCliPath,
69
+ kernelPath: bundled.kernelPath,
70
+ });
71
+ return bundled;
72
+ }
73
+
74
+ export interface RuntimeCompatibilityDeps {
75
+ readonly machineIdentity?: () => HostMachineIdentity | null;
76
+ readonly matchMachine?: (
77
+ machine: HostMachineIdentity,
78
+ ) => TaskEnvironmentBackendVerdict;
79
+ readonly adoptLegacy?: (
80
+ machine: HostMachineIdentity,
81
+ ) => Promise<LegacyTaskEnvironmentAdoptionVerdict>;
82
+ readonly reprobeRuntime?: () => Promise<HostMachineIdentity | null>;
83
+ readonly rejectSelection?: (message: string, action?: string) => void;
84
+ }
85
+
86
+ /**
87
+ * Final database-backed fence before maintenance/recovery touches a selected
88
+ * daemon. A missing exact endpoint and every malformed/newer/mismatched task
89
+ * locator are quarantines, never retry hints against the current machine.
90
+ */
91
+ export async function selectedRuntimeMatchesPersistedTaskEnvironments(
92
+ deps: RuntimeCompatibilityDeps = {},
93
+ ): Promise<boolean> {
94
+ const reject = deps.rejectSelection ?? rejectContainerRuntimeSelection;
95
+ const machine = (
96
+ deps.machineIdentity ?? containerRuntimeMachineIdentity
97
+ )();
98
+ if (machine === null) {
99
+ reject(
100
+ "The selected container runtime has no durable local machine identity.",
101
+ );
102
+ return false;
103
+ }
104
+ const reprobe =
105
+ deps.reprobeRuntime ?? reprobeContainerRuntimeMachineIdentity;
106
+ const liveMachine = await reprobe();
107
+ if (
108
+ liveMachine === null ||
109
+ liveMachine.backend !== machine.backend ||
110
+ liveMachine.endpoint !== machine.endpoint ||
111
+ liveMachine.engineId !== machine.engineId
112
+ ) {
113
+ reject(
114
+ "The selected container runtime daemon identity changed during validation.",
115
+ );
116
+ return false;
117
+ }
118
+ // Legacy compose rows can only ever be adopted by a Docker daemon; any
119
+ // other active backend makes the candidate check inside adoption fail
120
+ // closed with its repair guidance, so the machine is narrowed lazily.
121
+ // Bind (or unbind) the Apple provider to the proven identity before any
122
+ // maintenance can reconstruct a task environment. The binding carries the
123
+ // exact machine the fence just re-probed, so provider reconstruction and
124
+ // this admission decision can never disagree about identity.
125
+ if (deps.machineIdentity === undefined) {
126
+ const binding = appleContainerRuntimeBinding();
127
+ if (machine.backend === "apple-container" && binding !== null) {
128
+ const appleMachine = {
129
+ backend: "apple-container" as const,
130
+ endpoint: machine.endpoint,
131
+ engineId: machine.engineId,
132
+ };
133
+ setAppleContainerBinding({
134
+ containerCliPath: () => binding.containerCliPath,
135
+ machineIdentity: () => appleMachine,
136
+ });
137
+ } else {
138
+ setAppleContainerBinding(null);
139
+ }
140
+ }
141
+
142
+ const adoption = await (
143
+ deps.adoptLegacy ??
144
+ ((selected: HostMachineIdentity) =>
145
+ adoptLegacyTaskEnvironments(selected, {
146
+ reprobeMachine: reprobe,
147
+ }))
148
+ )(machine);
149
+ if (!adoption.ok) {
150
+ reject(
151
+ "Moondeck Station could not safely adopt persisted legacy task resources" +
152
+ (adoption.detail ? `: ${adoption.detail}` : "."),
153
+ );
154
+ return false;
155
+ }
156
+ const verdict = (
157
+ deps.matchMachine ?? hostTaskEnvironmentsMatchMachine
158
+ )(machine);
159
+ if (verdict.ok) return true;
160
+ reject(
161
+ "The selected container runtime does not match persisted task environments" +
162
+ (verdict.detail ? `: ${verdict.detail}` : "."),
163
+ );
164
+ return false;
165
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Every name a Moondeck Station puts on a machine, and the names the frozen
3
+ * `@runuai/host` used for the same things (Moondeck migration plan §4).
4
+ *
5
+ * A station is a fresh install beside, then instead of, the old host: it
6
+ * never reads, writes or stops anything under a legacy name, except in
7
+ * `moondeck-station uninstall-legacy` and the refuse-if-old-running check,
8
+ * which use LEGACY_HOST and nothing else does. Two hosts sharing one of these
9
+ * names would stop each other's service or delete each other's runtime.
10
+ *
11
+ * Not renamed, on purpose: `UAI_*` environment names (plan §2), the token
12
+ * prefixes, the wire protocol, and the runtime archive's internal layout
13
+ * (`current/bin/uai-host`, `agent/bin/uai-host.mjs`), which the archive
14
+ * verifiers pin and which only ever sit under the station's own runtime root.
15
+ */
16
+
17
+ export interface HostIdentity {
18
+ /** npm package name. */
19
+ readonly packageName: string;
20
+ /** The command on PATH (npm bin and the managed shim in ~/.local/bin). */
21
+ readonly command: string;
22
+ /** The managed runtime's recovery command, also shimmed into ~/.local/bin. */
23
+ readonly recoveryCommand: string;
24
+ /** `~/<this>`: `.env.local`, the data dir and task workspaces. */
25
+ readonly homeDirName: string;
26
+ /** `~/<this>`: the managed runtime root, and the prefix of its lock files. */
27
+ readonly runtimeDirName: string;
28
+ /** launchd LaunchAgent label (macOS). */
29
+ readonly launchdLabel: string;
30
+ /** launchd LaunchDaemon label of the root-owned `tune` helper (macOS). */
31
+ readonly tuneDaemonLabel: string;
32
+ /** systemd user unit (Linux). */
33
+ readonly systemdUnit: string;
34
+ /** Folder under ~/Library/Logs (macOS) and %LOCALAPPDATA% (Windows). */
35
+ readonly logDirName: string;
36
+ /** Service log file name inside it. */
37
+ readonly logFileName: string;
38
+ /** Human name in service descriptions. */
39
+ readonly displayName: string;
40
+ }
41
+
42
+ export const STATION: HostIdentity = Object.freeze({
43
+ packageName: "@moondeck/station",
44
+ command: "moondeck-station",
45
+ recoveryCommand: "moondeck-station-recover",
46
+ homeDirName: ".moondeck",
47
+ runtimeDirName: ".moondeck-station",
48
+ launchdLabel: "io.moondeck.station",
49
+ tuneDaemonLabel: "io.moondeck.tune",
50
+ systemdUnit: "moondeck-station.service",
51
+ logDirName: "Moondeck",
52
+ logFileName: "station.log",
53
+ displayName: "Moondeck Station",
54
+ });
55
+
56
+ /** The frozen `@runuai/host` (0.9.x). Read only by uninstall-legacy and the old-host check. */
57
+ export const LEGACY_HOST: HostIdentity = Object.freeze({
58
+ packageName: "@runuai/host",
59
+ command: "uai-host",
60
+ recoveryCommand: "uai-host-recover",
61
+ homeDirName: ".uai",
62
+ runtimeDirName: ".uai-host",
63
+ launchdLabel: "com.runuai.host",
64
+ tuneDaemonLabel: "com.runuai.tune",
65
+ systemdUnit: "uai-host.service",
66
+ logDirName: "Uai",
67
+ logFileName: "host.log",
68
+ displayName: "Uai host",
69
+ });
70
+
71
+ /**
72
+ * Where a station connects and calls when nothing else is configured. Both
73
+ * hosts must serve and be verified before station 1.0 ships (plan §3.1).
74
+ */
75
+ export const STATION_DEFAULT_CLOUD_URL = "wss://app.moondeck.io/host";
76
+ export const STATION_DEFAULT_GATEWAY_URL = "https://inference.moondeck.io";
77
+ export const STATION_INSTALLER_URL = "https://app.moondeck.io/install";
78
+
79
+ /**
80
+ * What a station's task containers have (Moondeck plan §5 row 7), and so what
81
+ * the cloud tells their agents once the station advertises
82
+ * AGENT_CLI_MOONDECK_PROTOCOL_FEATURE. Must equal the cloud's
83
+ * STATION_AGENT_CLI (lib/agent-cli-profile.ts, #708). The frozen host has
84
+ * `.uai/`, `uai.json` and the `uai-ask` fence; a station has none of them.
85
+ * `UAI_*` environment names and the `uai/<taskId>/<name>` branch namespace
86
+ * stay on both.
87
+ */
88
+ export const STATION_AGENT_CLI = Object.freeze({
89
+ command: "moondeck",
90
+ stateDir: ".moondeck",
91
+ cliPath: "/workspace/.moondeck/cli.mjs",
92
+ configFile: "moondeck.json",
93
+ askFence: "moondeck-ask",
94
+ productName: "Moondeck",
95
+ });
96
+
@@ -0,0 +1,28 @@
1
+ import { ENGINE_ENV_CREDENTIAL_KEYS } from "./cloud-credentials";
2
+
3
+ const RESERVED_TASK_ENV_EXACT = new Set([
4
+ "GH_TOKEN", "GITHUB_TOKEN", "GH_CONFIG_DIR", "GH_HOST",
5
+ "GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_OBJECT_DIRECTORY",
6
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES", "GIT_INDEX_FILE", "GIT_EXEC_PATH",
7
+ "GIT_ASKPASS", "SSH_ASKPASS", "GIT_SSH", "GIT_SSH_COMMAND",
8
+ "HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME",
9
+ "PATH", "LD_PRELOAD", "LD_AUDIT", "LD_LIBRARY_PATH", "NODE_OPTIONS",
10
+ "NODE_PATH", "SHELL", "SHELLOPTS", "BASHOPTS", "PS4", "BASH_ENV", "ENV",
11
+ "NODE_EXTRA_CA_CERTS", "SSL_CERT_FILE", "REQUESTS_CA_BUNDLE",
12
+ "__CURSOR_SANDBOX_ENV_RESTORE", "GROK_LOGIN_ENV",
13
+ // Every name an env-token engine's account may set (ADR-154's contract):
14
+ // a project value never stands in for an engine's credential.
15
+ ...Object.values(ENGINE_ENV_CREDENTIAL_KEYS).flat(),
16
+ "PLAYWRIGHT_BROWSERS_PATH",
17
+ ]);
18
+ const RESERVED_TASK_ENV_NPM = /^npm_config_(prefix|cache|logs[_-]dir|umask)$/i;
19
+ /** Toolchain prefixes, in any case, as task-up.sh matches them. */
20
+ const RESERVED_TASK_ENV_TOOLCHAIN = /^(asdf|corepack)_/i;
21
+
22
+ export function isReservedTaskEnvName(name: string): boolean {
23
+ if (RESERVED_TASK_ENV_EXACT.has(name)) return true;
24
+ if (name.startsWith("GIT_CONFIG") || RESERVED_TASK_ENV_TOOLCHAIN.test(name)) return true;
25
+ if (name.startsWith("UAI_")) return true;
26
+ if (name.startsWith("__UAI_ENV_")) return true;
27
+ return RESERVED_TASK_ENV_NPM.test(name);
28
+ }
@@ -0,0 +1,75 @@
1
+ /** ADR-161 §5: value-free task-secret wire contract. No I/O. */
2
+ export const TASK_SECRETS_FEATURE = "task-secrets-v1";
3
+ export const MAX_TASK_SECRETS = 20;
4
+ export const TASK_SECRET_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
5
+ export const TASK_SECRET_STAGING_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
6
+ export interface TaskSecretManifestEntry {
7
+ credentialId: string;
8
+ name: string;
9
+ stagingId: string;
10
+ generation: number;
11
+ parameter: string;
12
+ version: number;
13
+ mode: "proxied" | "delivered";
14
+ domains: string[];
15
+ placeholder: { prefix: string; totalLength: number } | null;
16
+ sessionId: string;
17
+ }
18
+ export interface TaskSecretStagingPut {
19
+ kind: "task.secret.staging.put";
20
+ opId: string;
21
+ orgId: string;
22
+ taskId: string;
23
+ name: string;
24
+ stagingId: string;
25
+ value: string;
26
+ }
27
+ export interface TaskSecretStagingAck {
28
+ kind: "task.secret.staging.ack";
29
+ opId: string;
30
+ stagingId: string;
31
+ ok: boolean;
32
+ }
33
+ const ID = /^[A-Za-z0-9_-]{1,128}$/;
34
+ export function isTaskSecretStagingPut(value: unknown): value is TaskSecretStagingPut {
35
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
36
+ const o = value as Record<string, unknown>;
37
+ return Object.keys(o).length === 7 && o.kind === "task.secret.staging.put" &&
38
+ [o.opId, o.orgId, o.taskId].every(v => typeof v === "string" && ID.test(v)) &&
39
+ typeof o.name === "string" && TASK_SECRET_NAME.test(o.name) &&
40
+ typeof o.stagingId === "string" && TASK_SECRET_STAGING_ID.test(o.stagingId) &&
41
+ typeof o.value === "string" && o.value.length > 0 && !o.value.includes("\0") &&
42
+ new TextEncoder().encode(JSON.stringify({ v: 1, kind: "task-secret", orgId: o.orgId, taskId: o.taskId, name: o.name, stagingId: o.stagingId, value: o.value })).byteLength <= 4096;
43
+ }
44
+ export function isTaskSecretStagingAck(value: unknown): value is TaskSecretStagingAck {
45
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
46
+ const o = value as Record<string, unknown>;
47
+ return Object.keys(o).length === 4 && o.kind === "task.secret.staging.ack" && typeof o.opId === "string" && ID.test(o.opId) &&
48
+ typeof o.stagingId === "string" && TASK_SECRET_STAGING_ID.test(o.stagingId) && typeof o.ok === "boolean";
49
+ }
50
+ export interface TaskSecretMaterial {
51
+ class: "task-secret";
52
+ location: { orgId: string; taskId: string; name: string; stagingId: string };
53
+ value: string;
54
+ }
55
+ /** Compare every envelope identity; no provider text is included in errors. */
56
+ export function parseTaskSecretMaterial(raw: string, expected: TaskSecretMaterial["location"]): TaskSecretMaterial | null {
57
+ try {
58
+ if (new TextEncoder().encode(raw).byteLength > 4096) return null;
59
+ const o = JSON.parse(raw) as Record<string, unknown>;
60
+ if (!o || Object.keys(o).length !== 7 || o.v !== 1 || o.kind !== "task-secret" ||
61
+ o.orgId !== expected.orgId || o.taskId !== expected.taskId || o.name !== expected.name || o.stagingId !== expected.stagingId ||
62
+ typeof o.value !== "string" || !o.value.length || o.value.includes("\0")) return null;
63
+ return { class: "task-secret", location: { ...expected }, value: o.value };
64
+ } catch { return null; }
65
+ }
66
+
67
+ /** Sent only after the lease's consumers have opened on the exact new view. */
68
+ export interface TaskSecretApplied { kind: "task.secret.applied"; taskId: string; runEpoch: number; manifestRevision: number; manifestDigest: string }
69
+ export function isTaskSecretApplied(value: unknown): value is TaskSecretApplied {
70
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
71
+ const o = value as Record<string, unknown>;
72
+ return Object.keys(o).length === 5 && o.kind === "task.secret.applied" && typeof o.taskId === "string" && ID.test(o.taskId) &&
73
+ typeof o.runEpoch === "number" && Number.isSafeInteger(o.runEpoch) && o.runEpoch > 0 &&
74
+ typeof o.manifestRevision === "number" && Number.isSafeInteger(o.manifestRevision) && o.manifestRevision > 0 && typeof o.manifestDigest === "string" && /^[0-9a-f]{64}$/.test(o.manifestDigest);
75
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Connect-phase timeout for tunnel upstream dials to a memoised address.
3
+ *
4
+ * An ADR-121 machine task's `{host, port}` comes from a 45 s memo
5
+ * (`machine-tunnel-route.ts`) that is dropped when a dial fails. A machine
6
+ * stopped or terminated while its address is memoised does not refuse the
7
+ * dial — it black-holes it: no SYN answer, so neither `connect` nor `error`
8
+ * fires until the kernel gives up on retransmits (~75 s macOS, ~127 s Linux).
9
+ * The memo's failure-driven invalidation never runs in that window and the
10
+ * pane or editor hangs on every open. An in-region machine answers a SYN in
11
+ * milliseconds, the cloud waits 30 s for the ack (`TUNNEL_ACK_TIMEOUT_MS`)
12
+ * and the forward hop 45 s for the head, so a 10 s connect bound is generous
13
+ * to a live machine and well inside what the caller will still accept.
14
+ *
15
+ * Only the connect phase is timed: the deadline is armed while the socket is
16
+ * `connecting` and disarmed by `connect` (or by the dial failing on its own).
17
+ * It is NOT an idle timeout — a quiet VNC/SSE/code-server stream after the
18
+ * handshake must live forever (see the tunnel rules in docs/agent-notes.md).
19
+ * A timeout destroys the socket with an error carrying `code = "ETIMEDOUT"`
20
+ * and `syscall = "connect"`, so the dial path's existing `'error'` handler
21
+ * runs unchanged: it acks the cloud with the same 502 shape as any other
22
+ * dial failure and invalidates the machine route memo.
23
+ */
24
+
25
+ export interface DialTarget {
26
+ readonly host: string;
27
+ readonly port: number;
28
+ }
29
+
30
+ /** The slice of `net.Socket` the timeout needs; tests pass a fake. */
31
+ export interface DialSocket {
32
+ /** True from `connect()` until the handshake completes. False on a socket
33
+ * handed over already connected (an agent's reused keep-alive socket, an
34
+ * ADR-106 pre-established apple route) — those are never armed. */
35
+ readonly connecting: boolean;
36
+ once(event: "connect" | "error" | "close", listener: () => void): unknown;
37
+ destroy(error?: Error): unknown;
38
+ }
39
+
40
+ export interface DialTimers {
41
+ setTimeout(callback: () => void, delayMs: number): NodeJS.Timeout;
42
+ clearTimeout(timer: NodeJS.Timeout): void;
43
+ }
44
+
45
+ export const CONNECT_TIMEOUT_ERROR_CODE = "ETIMEDOUT" as const;
46
+
47
+ export interface ConnectTimeoutError extends Error {
48
+ readonly code: typeof CONNECT_TIMEOUT_ERROR_CODE;
49
+ readonly syscall: "connect";
50
+ readonly address: string;
51
+ readonly port: number;
52
+ }
53
+
54
+ export function connectTimeoutError(
55
+ target: DialTarget,
56
+ timeoutMs: number,
57
+ ): ConnectTimeoutError {
58
+ return Object.assign(
59
+ new Error(
60
+ `connect ETIMEDOUT ${target.host}:${target.port} (no answer within ${timeoutMs} ms)`,
61
+ ),
62
+ {
63
+ code: CONNECT_TIMEOUT_ERROR_CODE,
64
+ syscall: "connect" as const,
65
+ address: target.host,
66
+ port: target.port,
67
+ },
68
+ );
69
+ }
70
+
71
+ export function isConnectTimeoutError(error: unknown): error is ConnectTimeoutError {
72
+ return (
73
+ error instanceof Error &&
74
+ (error as Partial<ConnectTimeoutError>).code === CONNECT_TIMEOUT_ERROR_CODE &&
75
+ (error as Partial<ConnectTimeoutError>).syscall === "connect"
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Arm a connect deadline on a socket that has started (or is about to be
81
+ * handed a started) connect. Returns whether a deadline was armed — false for
82
+ * a socket that is already connected, which has nothing to time.
83
+ */
84
+ export function armConnectTimeout(
85
+ socket: DialSocket,
86
+ target: DialTarget,
87
+ timeoutMs: number,
88
+ timers: DialTimers = { setTimeout, clearTimeout },
89
+ ): boolean {
90
+ if (!socket.connecting) return false;
91
+ const deadline = timers.setTimeout(() => {
92
+ socket.destroy(connectTimeoutError(target, timeoutMs));
93
+ }, timeoutMs);
94
+ deadline.unref?.();
95
+ const disarm = (): void => {
96
+ timers.clearTimeout(deadline);
97
+ };
98
+ socket.once("connect", disarm);
99
+ socket.once("error", disarm);
100
+ socket.once("close", disarm);
101
+ return true;
102
+ }