@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.
- package/LICENSE +21 -0
- package/README.md +149 -2
- package/bin/uai-host.mjs +130 -0
- package/db/migrations/0000_host_tasks.sql +12 -0
- package/db/migrations/0001_host_ui.sql +11 -0
- package/db/migrations/0002_host_github_tokens.sql +8 -0
- package/db/migrations/0003_host_ssh_keys.sql +8 -0
- package/db/migrations/0004_host_owner_name.sql +1 -0
- package/db/migrations/0005_host_env_vars.sql +5 -0
- package/db/migrations/0006_host_project_env.sql +9 -0
- package/db/migrations/0007_host_github_token_kind.sql +7 -0
- package/db/migrations/0008_host_mcp_connections.sql +18 -0
- package/db/migrations/0009_host_agent_sessions.sql +15 -0
- package/db/migrations/0010_host_engine_accounts.sql +13 -0
- package/db/migrations/0011_codex_thread_id.sql +5 -0
- package/db/migrations/0012_agent_session_attach_key.sql +4 -0
- package/db/migrations/0013_github_credential_generations.sql +11 -0
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/0017_engine_account_scope.sql +7 -0
- package/db/migrations/0018_engine_account_position.sql +3 -0
- package/db/migrations/0019_engine_account_store_ref.sql +3 -0
- package/db/migrations/0020_user_credential_store_ref.sql +6 -0
- package/db/migrations/0021_project_env_store_ref.sql +3 -0
- package/db/migrations/0022_task_project_ids.sql +5 -0
- package/db/migrations/0023_project_env_org.sql +4 -0
- package/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
- package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
- package/db/migrations/0029_task_shared_files_mode.sql +5 -0
- package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
- package/db/migrations/0031_first_turn_receipts.sql +9 -0
- package/db/migrations/meta/_journal.json +230 -0
- package/db/schema.ts +871 -0
- package/images/standard/Dockerfile +347 -0
- package/images/standard/README.md +167 -0
- package/images/standard/container/code-server-settings.json +63 -0
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +704 -0
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/images/standard/tool-versions +2 -0
- package/lib/__fixtures__/skill-index/SKILL.md +10 -0
- package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
- package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
- package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
- package/lib/agent-cli.ts +1507 -0
- package/lib/agent-errors.ts +22 -0
- package/lib/agent.ts +672 -0
- package/lib/agents/attach-compatibility.ts +51 -0
- package/lib/agents/claude.ts +918 -0
- package/lib/agents/codex-token-usage.fixture.json +130 -0
- package/lib/agents/codex.ts +1392 -0
- package/lib/agents/cursor-period-usage.ts +253 -0
- package/lib/agents/cursor.ts +562 -0
- package/lib/agents/dispatch.ts +78 -0
- package/lib/agents/durable-proc.ts +334 -0
- package/lib/agents/factory.ts +57 -0
- package/lib/agents/grok.ts +381 -0
- package/lib/agents/kimi.ts +329 -0
- package/lib/agents/machine-durable.ts +604 -0
- package/lib/agents/mock.ts +133 -0
- package/lib/agents/mode.ts +64 -0
- package/lib/agents/opencode.ts +508 -0
- package/lib/agents/output-source.ts +3 -0
- package/lib/agents/proc.ts +221 -0
- package/lib/agents/rate-limit.ts +337 -0
- package/lib/agents/registry.ts +182 -0
- package/lib/agents/session-state.ts +197 -0
- package/lib/agents/skill-index-preamble.fixture.ts +26 -0
- package/lib/agents/transport.ts +741 -0
- package/lib/agents/types.ts +254 -0
- package/lib/agents/uai.ts +106 -0
- package/lib/agents/usage.ts +44 -0
- package/lib/apple-one-off.ts +277 -0
- package/lib/apple-runtime-recycle.ts +237 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/assignment-evidence.ts +94 -0
- package/lib/attachments.ts +48 -0
- package/lib/browser-testing.ts +1537 -0
- package/lib/capture-flow.ts +332 -0
- package/lib/cloud-credential-cleanup-adapter.ts +123 -0
- package/lib/cloud-credential-host.ts +512 -0
- package/lib/cloud-credential-leases.ts +2587 -0
- package/lib/cloud-credential-runtime.ts +1357 -0
- package/lib/cloud-state.ts +56 -0
- package/lib/codex-auth.ts +456 -0
- package/lib/command-db.ts +310 -0
- package/lib/compose-network-recovery.ts +283 -0
- package/lib/connection-secret-redaction.ts +25 -0
- package/lib/container-runtime.ts +1470 -0
- package/lib/credential-cleanup-inventory.ts +490 -0
- package/lib/credential-consumers/engine-anchor.ts +1880 -0
- package/lib/credential-consumers/engines.ts +797 -0
- package/lib/credential-consumers/files.ts +934 -0
- package/lib/credential-consumers/github.ts +702 -0
- package/lib/credential-consumers/mcp.ts +389 -0
- package/lib/credential-consumers/task-files.ts +127 -0
- package/lib/credential-consumers/task-secrets.ts +10 -0
- package/lib/credential-store-pickup.ts +149 -0
- package/lib/credential-store-scope.ts +112 -0
- package/lib/credential-store.ts +932 -0
- package/lib/credentials-activation-authority.ts +709 -0
- package/lib/credentials-purge.ts +1006 -0
- package/lib/custom-connection-capture.ts +395 -0
- package/lib/custom-connection-launch.ts +384 -0
- package/lib/custom-connection-runtime.ts +141 -0
- package/lib/db.ts +80 -0
- package/lib/disk-pressure.ts +42 -0
- package/lib/docker-exec.ts +156 -0
- package/lib/engine-accounts.ts +1970 -0
- package/lib/engine-connect.ts +321 -0
- package/lib/engine-credential-env.ts +52 -0
- package/lib/engine-login.ts +2933 -0
- package/lib/engines.ts +1342 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +246 -0
- package/lib/env-schema.ts +937 -0
- package/lib/env.ts +195 -0
- package/lib/first-turn-receipts.ts +62 -0
- package/lib/fs-aliases.ts +49 -0
- package/lib/git-clone-credentials.ts +334 -0
- package/lib/git-diff.ts +530 -0
- package/lib/git-identity.ts +715 -0
- package/lib/github-git-auth.ts +207 -0
- package/lib/github-origin.ts +28 -0
- package/lib/github-tokens.ts +2439 -0
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-env.ts +424 -0
- package/lib/host-logs.ts +965 -0
- package/lib/internal-connections.ts +5 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/launch-mode.ts +25 -0
- package/lib/lease.ts +239 -0
- package/lib/legacy-host.ts +769 -0
- package/lib/legacy-runtime-retirement.ts +424 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/machine-exec.ts +238 -0
- package/lib/machine-gateway-tunnel.ts +344 -0
- package/lib/machine-keys.ts +65 -0
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/machine-provider-aws.ts +923 -0
- package/lib/machine-provider-local.ts +338 -0
- package/lib/machine-provider.ts +90 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +57 -0
- package/lib/managed-runtime.ts +4296 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-capture.ts +865 -0
- package/lib/mcp-config-lock.ts +2 -0
- package/lib/mcp-connections.ts +959 -0
- package/lib/mcp-gateway.ts +2594 -0
- package/lib/obs.ts +512 -0
- package/lib/openrouter-budget.ts +247 -0
- package/lib/orchestrator.ts +11409 -0
- package/lib/person-capture.ts +327 -0
- package/lib/preview-ports.ts +85 -0
- package/lib/preview-sidecar.ts +331 -0
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-legacy-tags.ts +130 -0
- package/lib/project-env-loader.ts +242 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/project-env-task-replica.ts +1752 -0
- package/lib/release-feed.ts +61 -0
- package/lib/release-manifest.ts +710 -0
- package/lib/release-trust.ts +46 -0
- package/lib/repo-clone.ts +38 -0
- package/lib/routing/shadow.ts +261 -0
- package/lib/runner-boot.ts +10 -0
- package/lib/runtime-activation-tail.ts +279 -0
- package/lib/runtime-archive.ts +1216 -0
- package/lib/runtime-authority.ts +166 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-reconnect.ts +86 -0
- package/lib/runtime-state.ts +1033 -0
- package/lib/safe-http.ts +364 -0
- package/lib/secrets-proxy/address.ts +95 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +57 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +150 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +605 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/secrets.ts +71 -0
- package/lib/shared-files-s3.ts +976 -0
- package/lib/shared-files.ts +156 -0
- package/lib/sidecar-assign.ts +49 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +178 -0
- package/lib/sidecar.ts +69 -0
- package/lib/signin-box-credentials.ts +465 -0
- package/lib/signin-box.ts +114 -0
- package/lib/skill-index-example.fixture.ts +56 -0
- package/lib/skill-layout.ts +417 -0
- package/lib/skills.ts +489 -0
- package/lib/ssh.ts +401 -0
- package/lib/standard-image-cleanup.ts +117 -0
- package/lib/standard-image-pin.ts +207 -0
- package/lib/standard-image.ts +1697 -0
- package/lib/stopped-task-status-queue.ts +120 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-credential-cleanup.ts +752 -0
- package/lib/task-diff-sandbox.ts +464 -0
- package/lib/task-diff.ts +91 -0
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +249 -0
- package/lib/task-environment/anchor.ts +31 -0
- package/lib/task-environment/apple-container.ts +860 -0
- package/lib/task-environment/docker.ts +1344 -0
- package/lib/task-environment/index.ts +466 -0
- package/lib/task-environment/legacy-adoption.ts +467 -0
- package/lib/task-environment/machine-environment.ts +61 -0
- package/lib/task-environment/machine-task-up.ts +1227 -0
- package/lib/task-environment/machine.ts +974 -0
- package/lib/task-environment/registry.ts +59 -0
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +566 -0
- package/lib/task-environment/types.ts +642 -0
- package/lib/task-environment/workspace-files.ts +685 -0
- package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
- package/lib/task-files-machine-retirement-outbox.ts +592 -0
- package/lib/task-files-machine-retirement.ts +116 -0
- package/lib/task-files-readiness.ts +127 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +47 -0
- package/lib/task-gateway-state.ts +512 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +682 -0
- package/lib/task-machine-credentials-activation.ts +95 -0
- package/lib/task-machine-swap-safety.ts +729 -0
- package/lib/task-person-credentials.ts +76 -0
- package/lib/task-project-ops-host.ts +147 -0
- package/lib/task-project-ops.ts +1062 -0
- package/lib/task-secret-staging.ts +16 -0
- package/lib/task-status.ts +46 -0
- package/lib/task-token.ts +46 -0
- package/lib/token-broker-adopt.ts +344 -0
- package/lib/token-broker-lambda.ts +105 -0
- package/lib/token-broker-local.ts +392 -0
- package/lib/token-broker-revision.ts +310 -0
- package/lib/token-broker-ssm.ts +525 -0
- package/lib/token-broker.ts +218 -0
- package/lib/token-lanes/openai-oauth.ts +123 -0
- package/lib/token-lanes/registry.ts +45 -0
- package/lib/token-lanes/types.ts +163 -0
- package/lib/token-lanes/xai-oauth.ts +165 -0
- package/lib/transcript.ts +128 -0
- package/lib/tunnel-registry.ts +220 -0
- package/lib/tunnel-runtime.ts +235 -0
- package/lib/ulid.ts +16 -0
- package/lib/user-bus-env.ts +116 -0
- package/lib/user-credentials-store.ts +435 -0
- package/lib/vnode-pressure.ts +142 -0
- package/package.json +115 -8
- package/runner/runner.mjs +442 -0
- package/scripts/agent/_common.sh +633 -0
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/moondeck-cli.sh +5 -0
- package/scripts/agent/task-down.sh +291 -0
- package/scripts/agent/task-status.sh +70 -0
- package/scripts/agent/task-up.sh +3761 -0
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/scripts/install/darwin.ts +1048 -0
- package/scripts/install/linux.ts +958 -0
- package/scripts/install/types.ts +78 -0
- package/scripts/install/util.ts +254 -0
- package/scripts/install/win.ts +143 -0
- package/src/activation-respawn.ts +155 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/bridge-diagnostics.ts +39 -0
- package/src/capture-wire.ts +102 -0
- package/src/cli.ts +2585 -0
- package/src/cloud-credentials.ts +2144 -0
- package/src/cloud-origins.ts +23 -0
- package/src/credential-location.ts +248 -0
- package/src/credential-wire-validation.ts +25 -0
- package/src/custom-connections.ts +237 -0
- package/src/engine-account-paths.ts +11 -0
- package/src/engine-models.ts +39 -0
- package/src/engine-usage.ts +104 -0
- package/src/enrollment-next-steps.ts +24 -0
- package/src/event-outbox.ts +185 -0
- package/src/index.ts +2907 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6363 -0
- package/src/never-proxied-domains.ts +31 -0
- package/src/outbound-frame.ts +31 -0
- package/src/paths.ts +81 -0
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +396 -0
- package/src/project-env-wording.ts +53 -0
- package/src/protocol.ts +5550 -0
- package/src/runtime-bootstrap.ts +165 -0
- package/src/station-identity.ts +96 -0
- package/src/task-env-names.ts +28 -0
- package/src/task-secrets.ts +75 -0
- package/src/tunnel-dial.ts +102 -0
- package/src/ui/server.ts +852 -0
- package/src/ui/types.ts +173 -0
- package/src/workspace-path-policy.ts +41 -0
- package/ui/app.js +1102 -0
- package/ui/index.html +90 -0
- package/ui/style.css +685 -0
- package/ui/uai-favicon.svg +21 -0
- package/ui/uai-logo-black.svg +9 -0
- package/ui/uai-wheel.svg +9 -0
- 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
|
+
}
|