@moondeck/station 0.0.0-stage → 1.0.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 (325) 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/meta/_journal.json +223 -0
  36. package/db/schema.ts +850 -0
  37. package/images/standard/Dockerfile +347 -0
  38. package/images/standard/README.md +167 -0
  39. package/images/standard/container/code-server-settings.json +63 -0
  40. package/images/standard/container/corepack-version +1 -0
  41. package/images/standard/container/uai-init +704 -0
  42. package/images/standard/container/uai-materialize-runtimes +1527 -0
  43. package/images/standard/container/uai-task-forwarder.mjs +303 -0
  44. package/images/standard/tool-versions +2 -0
  45. package/lib/__fixtures__/skill-index/SKILL.md +10 -0
  46. package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
  47. package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
  48. package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
  49. package/lib/agent-cli.ts +1507 -0
  50. package/lib/agent-errors.ts +22 -0
  51. package/lib/agent.ts +672 -0
  52. package/lib/agents/attach-compatibility.ts +51 -0
  53. package/lib/agents/claude.ts +918 -0
  54. package/lib/agents/codex-token-usage.fixture.json +130 -0
  55. package/lib/agents/codex.ts +1392 -0
  56. package/lib/agents/cursor-period-usage.ts +253 -0
  57. package/lib/agents/cursor.ts +562 -0
  58. package/lib/agents/dispatch.ts +78 -0
  59. package/lib/agents/durable-proc.ts +334 -0
  60. package/lib/agents/factory.ts +57 -0
  61. package/lib/agents/grok.ts +381 -0
  62. package/lib/agents/kimi.ts +329 -0
  63. package/lib/agents/machine-durable.ts +604 -0
  64. package/lib/agents/mock.ts +133 -0
  65. package/lib/agents/mode.ts +64 -0
  66. package/lib/agents/opencode.ts +508 -0
  67. package/lib/agents/output-source.ts +3 -0
  68. package/lib/agents/proc.ts +221 -0
  69. package/lib/agents/rate-limit.ts +337 -0
  70. package/lib/agents/registry.ts +182 -0
  71. package/lib/agents/session-state.ts +197 -0
  72. package/lib/agents/skill-index-preamble.fixture.ts +26 -0
  73. package/lib/agents/transport.ts +741 -0
  74. package/lib/agents/types.ts +254 -0
  75. package/lib/agents/uai.ts +106 -0
  76. package/lib/agents/usage.ts +44 -0
  77. package/lib/apple-one-off.ts +277 -0
  78. package/lib/apple-runtime-recycle.ts +237 -0
  79. package/lib/apple-uninstall-teardown.ts +224 -0
  80. package/lib/assignment-evidence.ts +94 -0
  81. package/lib/attachments.ts +48 -0
  82. package/lib/browser-testing.ts +1537 -0
  83. package/lib/capture-flow.ts +332 -0
  84. package/lib/cloud-credential-cleanup-adapter.ts +123 -0
  85. package/lib/cloud-credential-host.ts +512 -0
  86. package/lib/cloud-credential-leases.ts +2587 -0
  87. package/lib/cloud-credential-runtime.ts +1357 -0
  88. package/lib/cloud-state.ts +56 -0
  89. package/lib/codex-auth.ts +456 -0
  90. package/lib/command-db.ts +310 -0
  91. package/lib/compose-network-recovery.ts +283 -0
  92. package/lib/connection-secret-redaction.ts +25 -0
  93. package/lib/container-runtime.ts +1453 -0
  94. package/lib/credential-cleanup-inventory.ts +490 -0
  95. package/lib/credential-consumers/engine-anchor.ts +1880 -0
  96. package/lib/credential-consumers/engines.ts +797 -0
  97. package/lib/credential-consumers/files.ts +934 -0
  98. package/lib/credential-consumers/github.ts +702 -0
  99. package/lib/credential-consumers/mcp.ts +389 -0
  100. package/lib/credential-consumers/task-files.ts +127 -0
  101. package/lib/credential-consumers/task-secrets.ts +10 -0
  102. package/lib/credential-store-pickup.ts +149 -0
  103. package/lib/credential-store-scope.ts +112 -0
  104. package/lib/credential-store.ts +932 -0
  105. package/lib/credentials-activation-authority.ts +709 -0
  106. package/lib/credentials-purge.ts +1006 -0
  107. package/lib/custom-connection-capture.ts +395 -0
  108. package/lib/custom-connection-launch.ts +384 -0
  109. package/lib/custom-connection-runtime.ts +141 -0
  110. package/lib/db.ts +80 -0
  111. package/lib/disk-pressure.ts +42 -0
  112. package/lib/docker-exec.ts +156 -0
  113. package/lib/engine-accounts.ts +1970 -0
  114. package/lib/engine-connect.ts +321 -0
  115. package/lib/engine-credential-env.ts +52 -0
  116. package/lib/engine-login.ts +2933 -0
  117. package/lib/engines.ts +1342 -0
  118. package/lib/enrollment-state.ts +251 -0
  119. package/lib/env-file.ts +246 -0
  120. package/lib/env-schema.ts +891 -0
  121. package/lib/env.ts +195 -0
  122. package/lib/fs-aliases.ts +49 -0
  123. package/lib/git-clone-credentials.ts +334 -0
  124. package/lib/git-diff.ts +530 -0
  125. package/lib/git-identity.ts +715 -0
  126. package/lib/github-git-auth.ts +207 -0
  127. package/lib/github-origin.ts +28 -0
  128. package/lib/github-tokens.ts +2439 -0
  129. package/lib/host-cloud-url.ts +62 -0
  130. package/lib/host-config.ts +279 -0
  131. package/lib/host-env.ts +424 -0
  132. package/lib/host-logs.ts +965 -0
  133. package/lib/internal-connections.ts +5 -0
  134. package/lib/keyed-promise-tail.ts +23 -0
  135. package/lib/launch-mode.ts +25 -0
  136. package/lib/lease.ts +239 -0
  137. package/lib/legacy-host.ts +769 -0
  138. package/lib/legacy-runtime-retirement.ts +424 -0
  139. package/lib/legacy-runtime-v1.fixture.ts +627 -0
  140. package/lib/machine-exec.ts +238 -0
  141. package/lib/machine-gateway-tunnel.ts +344 -0
  142. package/lib/machine-keys.ts +65 -0
  143. package/lib/machine-project-env-loader.ts +152 -0
  144. package/lib/machine-provider-aws.ts +923 -0
  145. package/lib/machine-provider-local.ts +338 -0
  146. package/lib/machine-provider.ts +90 -0
  147. package/lib/managed-activation-watcher.ts +72 -0
  148. package/lib/managed-install-owner-watcher.ts +55 -0
  149. package/lib/managed-operation-drain.ts +57 -0
  150. package/lib/managed-runtime.ts +4296 -0
  151. package/lib/managed-update-scheduler.ts +125 -0
  152. package/lib/mcp-capture.ts +865 -0
  153. package/lib/mcp-config-lock.ts +2 -0
  154. package/lib/mcp-connections.ts +959 -0
  155. package/lib/mcp-gateway.ts +2594 -0
  156. package/lib/obs.ts +512 -0
  157. package/lib/openrouter-budget.ts +247 -0
  158. package/lib/orchestrator.ts +11409 -0
  159. package/lib/person-capture.ts +327 -0
  160. package/lib/preview-ports.ts +85 -0
  161. package/lib/preview-sidecar.ts +331 -0
  162. package/lib/project-env-authority.ts +1319 -0
  163. package/lib/project-env-feature.ts +74 -0
  164. package/lib/project-env-legacy-tags.ts +130 -0
  165. package/lib/project-env-loader.ts +242 -0
  166. package/lib/project-env-replica.ts +2094 -0
  167. package/lib/project-env-schema-capture.ts +342 -0
  168. package/lib/project-env-staging.ts +148 -0
  169. package/lib/project-env-task-policy.ts +176 -0
  170. package/lib/project-env-task-replica.ts +1752 -0
  171. package/lib/release-feed.ts +61 -0
  172. package/lib/release-manifest.ts +710 -0
  173. package/lib/release-trust.ts +46 -0
  174. package/lib/repo-clone.ts +38 -0
  175. package/lib/routing/shadow.ts +261 -0
  176. package/lib/runner-boot.ts +10 -0
  177. package/lib/runtime-activation-tail.ts +279 -0
  178. package/lib/runtime-archive.ts +1216 -0
  179. package/lib/runtime-authority.ts +166 -0
  180. package/lib/runtime-guard.ts +36 -0
  181. package/lib/runtime-provider-state.ts +169 -0
  182. package/lib/runtime-reconnect.ts +86 -0
  183. package/lib/runtime-state.ts +1033 -0
  184. package/lib/safe-http.ts +364 -0
  185. package/lib/secrets-proxy/address.ts +95 -0
  186. package/lib/secrets-proxy/audit.ts +30 -0
  187. package/lib/secrets-proxy/ca-store.ts +200 -0
  188. package/lib/secrets-proxy/ca.ts +470 -0
  189. package/lib/secrets-proxy/container-steps.ts +138 -0
  190. package/lib/secrets-proxy/der.ts +273 -0
  191. package/lib/secrets-proxy/engine.ts +1633 -0
  192. package/lib/secrets-proxy/host.ts +93 -0
  193. package/lib/secrets-proxy/hostname.ts +57 -0
  194. package/lib/secrets-proxy/placeholders.ts +196 -0
  195. package/lib/secrets-proxy/policy.ts +150 -0
  196. package/lib/secrets-proxy/readiness.ts +148 -0
  197. package/lib/secrets-proxy/runtime.ts +582 -0
  198. package/lib/secrets-proxy/scrub.ts +559 -0
  199. package/lib/secrets-proxy/sidecar-view.ts +139 -0
  200. package/lib/secrets-proxy/task-scripts.ts +262 -0
  201. package/lib/secrets-proxy/task-trust.ts +605 -0
  202. package/lib/secrets-proxy/trust-watch.ts +80 -0
  203. package/lib/secrets.ts +71 -0
  204. package/lib/shared-files-s3.ts +976 -0
  205. package/lib/shared-files.ts +156 -0
  206. package/lib/sidecar-assign.ts +49 -0
  207. package/lib/sidecar-guard.ts +58 -0
  208. package/lib/sidecar-resume.ts +178 -0
  209. package/lib/sidecar.ts +69 -0
  210. package/lib/signin-box-credentials.ts +465 -0
  211. package/lib/signin-box.ts +114 -0
  212. package/lib/skill-index-example.fixture.ts +56 -0
  213. package/lib/skill-layout.ts +417 -0
  214. package/lib/skills.ts +489 -0
  215. package/lib/ssh.ts +401 -0
  216. package/lib/standard-image-cleanup.ts +117 -0
  217. package/lib/standard-image-pin.ts +207 -0
  218. package/lib/standard-image.ts +1697 -0
  219. package/lib/stopped-task-status-queue.ts +120 -0
  220. package/lib/task-container-cli.ts +269 -0
  221. package/lib/task-credential-cleanup.ts +752 -0
  222. package/lib/task-diff-sandbox.ts +464 -0
  223. package/lib/task-diff.ts +91 -0
  224. package/lib/task-env-forwarder.ts +228 -0
  225. package/lib/task-env-source.ts +249 -0
  226. package/lib/task-environment/anchor.ts +31 -0
  227. package/lib/task-environment/apple-container.ts +860 -0
  228. package/lib/task-environment/docker.ts +1344 -0
  229. package/lib/task-environment/index.ts +466 -0
  230. package/lib/task-environment/legacy-adoption.ts +467 -0
  231. package/lib/task-environment/machine-environment.ts +61 -0
  232. package/lib/task-environment/machine-task-up.ts +1227 -0
  233. package/lib/task-environment/machine.ts +974 -0
  234. package/lib/task-environment/registry.ts +59 -0
  235. package/lib/task-environment/self-identity.ts +20 -0
  236. package/lib/task-environment/self.ts +566 -0
  237. package/lib/task-environment/types.ts +642 -0
  238. package/lib/task-environment/workspace-files.ts +685 -0
  239. package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
  240. package/lib/task-files-machine-retirement-outbox.ts +592 -0
  241. package/lib/task-files-machine-retirement.ts +116 -0
  242. package/lib/task-files-readiness.ts +127 -0
  243. package/lib/task-gateway-port.ts +22 -0
  244. package/lib/task-gateway-protocol.ts +190 -0
  245. package/lib/task-gateway-recovery.ts +47 -0
  246. package/lib/task-gateway-state.ts +512 -0
  247. package/lib/task-identity.ts +19 -0
  248. package/lib/task-inventory.ts +682 -0
  249. package/lib/task-machine-credentials-activation.ts +95 -0
  250. package/lib/task-machine-swap-safety.ts +729 -0
  251. package/lib/task-person-credentials.ts +76 -0
  252. package/lib/task-project-ops-host.ts +147 -0
  253. package/lib/task-project-ops.ts +1062 -0
  254. package/lib/task-secret-staging.ts +16 -0
  255. package/lib/task-status.ts +46 -0
  256. package/lib/task-token.ts +46 -0
  257. package/lib/token-broker-adopt.ts +344 -0
  258. package/lib/token-broker-lambda.ts +105 -0
  259. package/lib/token-broker-local.ts +392 -0
  260. package/lib/token-broker-revision.ts +310 -0
  261. package/lib/token-broker-ssm.ts +525 -0
  262. package/lib/token-broker.ts +218 -0
  263. package/lib/token-lanes/openai-oauth.ts +123 -0
  264. package/lib/token-lanes/registry.ts +45 -0
  265. package/lib/token-lanes/types.ts +163 -0
  266. package/lib/token-lanes/xai-oauth.ts +165 -0
  267. package/lib/transcript.ts +128 -0
  268. package/lib/tunnel-registry.ts +220 -0
  269. package/lib/tunnel-runtime.ts +235 -0
  270. package/lib/ulid.ts +16 -0
  271. package/lib/user-bus-env.ts +116 -0
  272. package/lib/user-credentials-store.ts +435 -0
  273. package/lib/vnode-pressure.ts +142 -0
  274. package/package.json +131 -4
  275. package/runner/runner.mjs +442 -0
  276. package/scripts/agent/_common.sh +633 -0
  277. package/scripts/agent/env-hook.sh +626 -0
  278. package/scripts/agent/moondeck-cli.sh +5 -0
  279. package/scripts/agent/task-down.sh +291 -0
  280. package/scripts/agent/task-status.sh +70 -0
  281. package/scripts/agent/task-up.sh +3761 -0
  282. package/scripts/agent/uai-env-exec.sh +26 -0
  283. package/scripts/install/darwin.ts +1048 -0
  284. package/scripts/install/linux.ts +958 -0
  285. package/scripts/install/types.ts +78 -0
  286. package/scripts/install/util.ts +254 -0
  287. package/scripts/install/win.ts +143 -0
  288. package/src/activation-respawn.ts +155 -0
  289. package/src/apple-tunnel-route.ts +104 -0
  290. package/src/capture-wire.ts +102 -0
  291. package/src/cli.ts +2583 -0
  292. package/src/cloud-credentials.ts +2144 -0
  293. package/src/cloud-origins.ts +23 -0
  294. package/src/credential-location.ts +248 -0
  295. package/src/credential-wire-validation.ts +25 -0
  296. package/src/custom-connections.ts +237 -0
  297. package/src/engine-account-paths.ts +11 -0
  298. package/src/engine-models.ts +39 -0
  299. package/src/engine-usage.ts +104 -0
  300. package/src/event-outbox.ts +185 -0
  301. package/src/index.ts +2899 -0
  302. package/src/load-env.ts +97 -0
  303. package/src/machine-tunnel-route.ts +156 -0
  304. package/src/main.ts +6349 -0
  305. package/src/never-proxied-domains.ts +31 -0
  306. package/src/outbound-frame.ts +31 -0
  307. package/src/paths.ts +81 -0
  308. package/src/project-env-latest.ts +130 -0
  309. package/src/project-env-policy.ts +396 -0
  310. package/src/project-env-wording.ts +53 -0
  311. package/src/protocol.ts +5545 -0
  312. package/src/runtime-bootstrap.ts +165 -0
  313. package/src/station-identity.ts +96 -0
  314. package/src/task-env-names.ts +28 -0
  315. package/src/task-secrets.ts +75 -0
  316. package/src/tunnel-dial.ts +102 -0
  317. package/src/ui/server.ts +852 -0
  318. package/src/ui/types.ts +173 -0
  319. package/src/workspace-path-policy.ts +41 -0
  320. package/ui/app.js +1102 -0
  321. package/ui/index.html +90 -0
  322. package/ui/style.css +685 -0
  323. package/ui/uai-favicon.svg +21 -0
  324. package/ui/uai-logo-black.svg +9 -0
  325. package/ui/uai-wheel.svg +9 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Agent adapter registry (ADR-021).
3
+ *
4
+ * A small module-level registry so adding an agent kind is mechanical and
5
+ * needs no uai-core change: an adapter module calls `register()` at load,
6
+ * declaring its `kind`, display `label`, the models it can drive, an
7
+ * optional `defaultModel`, and a `create()` that builds an `AgentSession`.
8
+ *
9
+ * The host derives two things from the registry:
10
+ * - `factoryFor(kind)` — used by the real agent factory to construct a
11
+ * session for a roster entry by its `kind`.
12
+ * - `capabilities()` — the `agentKinds` slice of the `host.capabilities`
13
+ * frame, computed straight from the registered adapters.
14
+ *
15
+ * Registration is side-effect-on-import: importing `./claude` and `./codex`
16
+ * runs their `register()` calls (see `factory.ts`, which imports them).
17
+ */
18
+
19
+ import type { AgentSessionFactory } from "./types";
20
+
21
+ /** A registered agent adapter — metadata plus the session factory. */
22
+ export interface RegisteredAdapter {
23
+ /** Host-advertised agent kind ("claude" | "codex" | "gemini" | …). */
24
+ kind: string;
25
+ /** Display name for the cloud picker. */
26
+ label: string;
27
+ /** Models this adapter can drive (CLI `--model` / `-c model=` values). */
28
+ supportedModels(): string[];
29
+ /** Preferred model when the user doesn't pick one. */
30
+ defaultModel?: string;
31
+ /** Reasoning levels this adapter can drive (CLI effort values). */
32
+ supportedEfforts(): string[];
33
+ /** Preferred effort when the user doesn't pick one. */
34
+ defaultEffort?: string;
35
+ /** Restricted profiles this adapter can enforce by construction. */
36
+ executionProfiles?: Array<{
37
+ id: string;
38
+ mechanism: string;
39
+ defaultModel?: string;
40
+ defaultEffort?: string;
41
+ }>;
42
+ /**
43
+ * Whether this kind is usable on THIS host right now — i.e. its credentials
44
+ * are present (ADR-044 P2). Gates advertisement: an unavailable kind is left
45
+ * out of `capabilities()` so the cloud's task picker doesn't offer an LLM the
46
+ * host can't actually run. Omit (or return true) to always advertise; the
47
+ * session factory stays permissive regardless.
48
+ */
49
+ available?: () => boolean;
50
+ /** Builds an AgentSession for a roster entry of this kind. */
51
+ create: AgentSessionFactory["create"];
52
+ }
53
+
54
+ /** One entry of the `host.capabilities` frame's `agentKinds`. */
55
+ export interface AgentKindCapability {
56
+ kind: string;
57
+ label: string;
58
+ supportedModels: string[];
59
+ defaultModel?: string;
60
+ supportedEfforts: string[];
61
+ defaultEffort?: string;
62
+ executionProfiles?: Array<{
63
+ id: string;
64
+ mechanism: string;
65
+ defaultModel?: string;
66
+ defaultEffort?: string;
67
+ }>;
68
+ }
69
+
70
+ const adapters = new Map<string, RegisteredAdapter>();
71
+
72
+ /** Listeners fired after any registry mutation (manual re-advertise hook). */
73
+ const changeListeners = new Set<() => void>();
74
+
75
+ /**
76
+ * ADR-131 D3: a second source of "usable on this host" beyond the adapter's
77
+ * own `available()`, which only sees the host's default credential. The host
78
+ * agent installs the engine-account store here so a kind with a stored
79
+ * (workspace- or host-scoped) account is advertised even when the owner never
80
+ * signed in to it. Installed by main.ts; absent in unit tests.
81
+ */
82
+ let advertisementFallback: (kind: string) => boolean = () => false;
83
+
84
+ export function setAdvertisementFallback(
85
+ fallback: ((kind: string) => boolean) | null,
86
+ ): void {
87
+ advertisementFallback = fallback ?? (() => false);
88
+ }
89
+
90
+ /**
91
+ * ADR-154 §10: on cloud credentials the runner holds no engine credential of
92
+ * its own, so availability and the fallback above say nothing: the kinds a
93
+ * leased task can run are advertised, and only those (`CLOUD_CREDENTIAL_KINDS`:
94
+ * the leased lanes and the Managed Engine). Null while the runner runs its
95
+ * tasks on its own credentials. Installed by main.ts.
96
+ */
97
+ let leasedKinds: () => ReadonlySet<string> | null = () => null;
98
+
99
+ export function setLeasedAdvertisement(
100
+ source: (() => ReadonlySet<string> | null) | null,
101
+ ): void {
102
+ leasedKinds = source ?? (() => null);
103
+ }
104
+
105
+ /**
106
+ * Register (or replace) an adapter for a kind. Called at module load by the
107
+ * adapter modules. Replacing is allowed so a later import wins deterministically.
108
+ */
109
+ export function register(adapter: RegisteredAdapter): void {
110
+ adapters.set(adapter.kind, adapter);
111
+ for (const listener of changeListeners) listener();
112
+ }
113
+
114
+ /** Every registered adapter, in insertion order. */
115
+ export function list(): RegisteredAdapter[] {
116
+ return [...adapters.values()];
117
+ }
118
+
119
+ /** The session factory for a kind, or undefined if no adapter is registered. */
120
+ export function factoryFor(kind: string): AgentSessionFactory | undefined {
121
+ const adapter = adapters.get(kind);
122
+ return adapter ? { create: adapter.create } : undefined;
123
+ }
124
+
125
+ /** Whether the adapter declares an engine-enforced execution profile. */
126
+ export function supportsExecutionProfile(kind: string, profileId: string): boolean {
127
+ return Boolean(
128
+ adapters
129
+ .get(kind)
130
+ ?.executionProfiles?.some((profile) => profile.id === profileId),
131
+ );
132
+ }
133
+
134
+ /**
135
+ * The `agentKinds` capability slice, derived from the registered adapters.
136
+ * Each adapter's `supportedModels()` / `supportedEfforts()` is evaluated here.
137
+ */
138
+ export function capabilities(): AgentKindCapability[] {
139
+ const leased = leasedKinds();
140
+ return list()
141
+ .filter((adapter) =>
142
+ leased !== null
143
+ ? leased.has(adapter.kind)
144
+ : (adapter.available?.() ?? true) || advertisementFallback(adapter.kind),
145
+ )
146
+ .map((adapter) => {
147
+ const out: AgentKindCapability = {
148
+ kind: adapter.kind,
149
+ label: adapter.label,
150
+ supportedModels: adapter.supportedModels(),
151
+ supportedEfforts: adapter.supportedEfforts(),
152
+ };
153
+ if (adapter.defaultModel !== undefined) {
154
+ out.defaultModel = adapter.defaultModel;
155
+ }
156
+ if (adapter.defaultEffort !== undefined) {
157
+ out.defaultEffort = adapter.defaultEffort;
158
+ }
159
+ if (adapter.executionProfiles && adapter.executionProfiles.length > 0) {
160
+ out.executionProfiles = adapter.executionProfiles.map((profile) => ({
161
+ ...profile,
162
+ }));
163
+ }
164
+ return out;
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Subscribe to registry changes (adapter added/replaced). Returns an
170
+ * unsubscribe. Used to re-send the capability frame on change.
171
+ */
172
+ export function onChange(listener: () => void): () => void {
173
+ changeListeners.add(listener);
174
+ return () => changeListeners.delete(listener);
175
+ }
176
+
177
+ /** Test/maintenance hook: drop all adapters and listeners. */
178
+ export function reset(): void {
179
+ adapters.clear();
180
+ changeListeners.clear();
181
+ advertisementFallback = () => false;
182
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Durable agent-session state used by live roster reconciliation.
3
+ *
4
+ * The orchestrator normally owns a live AgentSession and closes it through the
5
+ * adapter. After a host restart, however, a removed agent can exist only as a
6
+ * host_agent_sessions row. These helpers stop that persisted runner
7
+ * without deleting its row or generation directory, and clear Codex's durable
8
+ * thread id when launch settings change.
9
+ */
10
+
11
+
12
+ import { isOwnedInternalConnectionRuntime } from "../internal-connections";
13
+ import { spawn } from "node:child_process";
14
+ import { and, eq } from "drizzle-orm";
15
+
16
+ import { getDb, schema } from "../db";
17
+ import { taskWorkspaceDir } from "../env";
18
+ import {
19
+ appendContainedFile,
20
+ hostPathParts,
21
+ WorkspaceDirectoryMissingError,
22
+ } from "../task-environment/workspace-files";
23
+ import type { TaskEnvironmentHandle } from "../task-environment/types";
24
+ import { isEnvironmentTransportProvider } from "../task-environment/types";
25
+ import {
26
+ stopMachinePredecessorStrict,
27
+ type MachineSessionEnvironment,
28
+ } from "./machine-durable";
29
+
30
+ /** Durable-session ids persisted for one task, including rows marked closed.
31
+ * A crash can land after markClosed but before its stop reaches the runner. */
32
+ export function persistedAgentIds(taskId: string): string[] {
33
+ return getDb()
34
+ .select({ agentId: schema.hostAgentSessions.agentId })
35
+ .from(schema.hostAgentSessions)
36
+ .where(eq(schema.hostAgentSessions.taskId, taskId))
37
+ .all()
38
+ .map((row) => row.agentId);
39
+ }
40
+
41
+ /**
42
+ * A changed model/engine/effort starts a fresh Codex thread. The channel
43
+ * transcript is the durable memory; resuming a thread created under the old
44
+ * launch settings can make the replacement keep using the old model.
45
+ */
46
+ export function clearPersistedCodexThread(
47
+ taskId: string,
48
+ agentId: string,
49
+ ): void {
50
+ getDb()
51
+ .update(schema.hostAgentSessions)
52
+ .set({ codexThreadId: null, updatedAt: Date.now() })
53
+ .where(
54
+ and(
55
+ eq(schema.hostAgentSessions.taskId, taskId),
56
+ eq(schema.hostAgentSessions.agentId, agentId),
57
+ ),
58
+ )
59
+ .run();
60
+ }
61
+
62
+ /**
63
+ * Stop a persisted runner that has no in-memory AgentSession (boot recovery).
64
+ * The session directory and DB row deliberately remain for diagnostics.
65
+ */
66
+ export async function stopPersistedAgentSession(
67
+ taskId: string,
68
+ agentId: string,
69
+ environment: TaskEnvironmentHandle<unknown>,
70
+ ): Promise<string | null> {
71
+ const db = getDb();
72
+ const row = db
73
+ .select()
74
+ .from(schema.hostAgentSessions)
75
+ .where(
76
+ and(
77
+ eq(schema.hostAgentSessions.taskId, taskId),
78
+ eq(schema.hostAgentSessions.agentId, agentId),
79
+ ),
80
+ )
81
+ .get();
82
+ if (!row) return null;
83
+
84
+ if (isEnvironmentTransportProvider(environment.descriptor.locator.provider)) {
85
+ await stopMachinePredecessorStrict(
86
+ environment as MachineSessionEnvironment,
87
+ row.sessionDir,
88
+ );
89
+ } else {
90
+ // Never through a link the task put in the session's place (#cb3j0z).
91
+ const root = taskWorkspaceDir(taskId);
92
+ let parts: string[] | null;
93
+ try {
94
+ parts = hostPathParts(root, row.sessionDir);
95
+ } catch {
96
+ // Not below the task's workspace: no runner of this task reads it.
97
+ parts = null;
98
+ }
99
+ if (parts !== null) {
100
+ try {
101
+ await appendContainedFile(root, [...parts, "inbox.jsonl"], Buffer.from('{"__uai":"stop"}\n'));
102
+ } catch (err) {
103
+ // A removed generation directory proves there is no runner left to
104
+ // read it. Permission, I/O, and other failures are not proof: retry
105
+ // them.
106
+ if (!(err instanceof WorkspaceDirectoryMissingError)) throw err;
107
+ }
108
+ }
109
+ }
110
+
111
+ db.update(schema.hostAgentSessions)
112
+ .set({ status: "closed", updatedAt: Date.now() })
113
+ .where(
114
+ and(
115
+ eq(schema.hostAgentSessions.taskId, taskId),
116
+ eq(schema.hostAgentSessions.agentId, agentId),
117
+ eq(schema.hostAgentSessions.sessionDir, row.sessionDir),
118
+ ),
119
+ )
120
+ .run();
121
+ return row.sessionDir;
122
+ }
123
+
124
+ /** Remove internal runner generations only after the caller has proven every
125
+ * covered anchored process stopped. The persisted compatibility key survives
126
+ * restart and binds this deletion to the run and credential being cleaned. */
127
+ export async function purgeInternalAgentSessionState(
128
+ request: { taskId: string; runEpoch: number; credentialIds?: readonly string[] },
129
+ environments: readonly TaskEnvironmentHandle<unknown>[],
130
+ removeDirectory: (root: string, path: string) => Promise<void> = removeInternalSessionDirectory,
131
+ ): Promise<void> {
132
+ const db = getDb();
133
+ const rows = db.select().from(schema.hostAgentSessions).where(eq(schema.hostAgentSessions.taskId, request.taskId)).all();
134
+ for (const row of rows) {
135
+ let key: unknown;
136
+ try { key = JSON.parse(row.attachCompatibilityKey ?? "null"); } catch { continue; }
137
+ if (!Array.isArray(key) || key[1] !== "lease-v1" || !Number.isSafeInteger(key[2]) || key[2] > request.runEpoch || key.at(-1) !== "custom-internal" ||
138
+ (request.credentialIds && !request.credentialIds.includes(key[3] as string))) continue;
139
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(row.agentId)) throw new Error("unsupported");
140
+ if (!environments.length) throw new Error("unsupported");
141
+ // Owned-runtime workspaces are host mounts; they persist even after the
142
+ // container disappears. Delete through host directory FDs, not exec into
143
+ // a container which may already be stopped/absent.
144
+ if (environments.some(e => e.descriptor.taskId !== request.taskId || !isOwnedInternalConnectionRuntime(e.descriptor.locator.provider))) throw new Error("unsupported");
145
+ const root = taskWorkspaceDir(request.taskId);
146
+ await removeDirectory(root, `${root}/.moondeck/sessions/${row.agentId}`);
147
+ db.delete(schema.hostAgentSessions).where(and(eq(schema.hostAgentSessions.taskId, row.taskId), eq(schema.hostAgentSessions.agentId, row.agentId), eq(schema.hostAgentSessions.sessionDir, row.sessionDir))).run();
148
+ }
149
+ }
150
+
151
+ /** FD-relative tree removal. A task can
152
+ * replace entries concurrently; links are never followed, and a race fails
153
+ * closed for the cleanup coordinator to retry. No provider text is read. */
154
+ export const INTERNAL_SESSION_UNLINK_HELPER = String.raw`
155
+ import os, stat, sys
156
+ root, absolute = sys.argv[1:3]
157
+ prefix = root.rstrip("/") + "/"
158
+ if not absolute.startswith(prefix): raise RuntimeError()
159
+ parts = absolute[len(prefix):].split("/")
160
+ if not parts or any(p in ("", ".", "..") for p in parts): raise RuntimeError()
161
+ flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
162
+ fds = []
163
+ def remove(parent, name):
164
+ try: info = os.stat(name, dir_fd=parent, follow_symlinks=False)
165
+ except FileNotFoundError: return
166
+ if not stat.S_ISDIR(info.st_mode): raise RuntimeError()
167
+ child = os.open(name, flags, dir_fd=parent)
168
+ try:
169
+ for entry in os.listdir(child):
170
+ info = os.stat(entry, dir_fd=child, follow_symlinks=False)
171
+ if stat.S_ISDIR(info.st_mode): remove(child, entry)
172
+ else: os.unlink(entry, dir_fd=child)
173
+ finally: os.close(child)
174
+ os.rmdir(name, dir_fd=parent)
175
+ try:
176
+ current = os.open("/", flags); fds.append(current)
177
+ for p in root.split("/") + parts[:-1]:
178
+ if not p: continue
179
+ current = os.open(p, flags, dir_fd=current); fds.append(current)
180
+ remove(current, parts[-1])
181
+ try: os.stat(parts[-1], dir_fd=current, follow_symlinks=False)
182
+ except FileNotFoundError: sys.exit(0)
183
+ raise RuntimeError()
184
+ except FileNotFoundError: sys.exit(0)
185
+ finally:
186
+ for fd in reversed(fds): os.close(fd)
187
+ `;
188
+ function removeInternalSessionDirectory(root: string, path: string): Promise<void> {
189
+ return new Promise((resolve, reject) => {
190
+ const proc = spawn("/usr/bin/python3", ["-I", "-", root, path], { stdio: ["pipe", "ignore", "ignore"], env: { PATH: "/usr/bin:/bin" } });
191
+ const timer = setTimeout(() => { proc.kill("SIGKILL"); reject(new Error("unsupported")); }, 10_000);
192
+ proc.on("error", () => { clearTimeout(timer); reject(new Error("unsupported")); });
193
+ proc.on("exit", code => { clearTimeout(timer); if (code === 0) resolve(); else reject(new Error("unsupported")); });
194
+ proc.stdin.on("error", () => {});
195
+ proc.stdin.end(INTERNAL_SESSION_UNLINK_HELPER);
196
+ });
197
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ADR-166: the preamble an indexed agent's engine receives, from the real
3
+ * builder, for the per-engine adapter tests (claude, codex, grok).
4
+ */
5
+ import { buildSystemPreamble } from "../orchestrator";
6
+ import type { RosterAgent } from "./types";
7
+
8
+ export const INDEXED_SKILLS: NonNullable<RosterAgent["skills"]> = [
9
+ { name: "Brand voice", type: "document", value: "Plain and warm." },
10
+ { name: "Design tokens", type: "document", value: "The scales." },
11
+ ];
12
+
13
+ /** The skills pointer every engine must carry for an indexed agent. */
14
+ export const INDEX_POINTER = /Your skills are indexed at `\/workspace\/\.moondeck\/agents\/[\w-]+\/SKILL\.md`: "Brand voice" and "Design tokens"\. Read the index at the start\. Before work a skill covers, or an action the index names, read that file in full with `cat`/;
15
+
16
+ export function indexedPreamble(agent: RosterAgent): string {
17
+ const withSkills = { ...agent, skills: INDEXED_SKILLS };
18
+ return buildSystemPreamble(
19
+ [withSkills],
20
+ withSkills,
21
+ [{ slug: "repo", defaultPrompt: "" } as never],
22
+ undefined,
23
+ "/workspace",
24
+ "task/x",
25
+ );
26
+ }