@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,166 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ProjectEnvMode,
|
|
3
|
+
ProjectEnvProfile,
|
|
4
|
+
} from "./task-environment/types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Host-owned runtime/cache authority for every command launched in a task
|
|
8
|
+
* container. Admission and recovery separately prove the container's fixed
|
|
9
|
+
* Config.Env; these values are reasserted for each managed exec so a caller
|
|
10
|
+
* cannot accidentally omit the policy or inherit project-controlled values.
|
|
11
|
+
*/
|
|
12
|
+
export const RUNTIME_AUTHORITY_ENV: Readonly<Record<string, string>> =
|
|
13
|
+
Object.freeze({
|
|
14
|
+
// Shared, host-maintained shims stay ahead of task-installed commands so
|
|
15
|
+
// `npm install -g claude/codex/node` cannot replace Uai's proved tools.
|
|
16
|
+
// Packages with new command names remain available from the private
|
|
17
|
+
// container-layer prefix at the final component, after every trusted
|
|
18
|
+
// control directory.
|
|
19
|
+
PATH: "/opt/asdf-data/shims:/opt/asdf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/node/.local/share/uai-npm/bin",
|
|
20
|
+
HOME: "/home/node",
|
|
21
|
+
XDG_CONFIG_HOME: "/home/node/.config",
|
|
22
|
+
XDG_DATA_HOME: "/home/node/.local/share",
|
|
23
|
+
XDG_CACHE_HOME: "/home/node/.cache",
|
|
24
|
+
ASDF_DIR: "/opt/asdf",
|
|
25
|
+
ASDF_DATA_DIR: "/opt/asdf-data",
|
|
26
|
+
ASDF_CONFIG_FILE: "/dev/null",
|
|
27
|
+
ASDF_DEFAULT_TOOL_VERSIONS_FILENAME: ".tool-versions",
|
|
28
|
+
ASDF_NODEJS_VERSION: "",
|
|
29
|
+
ASDF_PYTHON_VERSION: "",
|
|
30
|
+
ASDF_GOLANG_VERSION: "",
|
|
31
|
+
ASDF_RUBY_VERSION: "",
|
|
32
|
+
ASDF_RUST_VERSION: "",
|
|
33
|
+
// asdf-nodejs wraps global npm installs with an automatic reshim. Task
|
|
34
|
+
// mounts are intentionally read-only and private bins are already on PATH;
|
|
35
|
+
// only host maintenance may mutate shared shims.
|
|
36
|
+
ASDF_SKIP_RESHIM: "1",
|
|
37
|
+
// Corepack selections are completely prepared by host maintenance. Task
|
|
38
|
+
// contexts see this path only through the read-only shared volume.
|
|
39
|
+
COREPACK_HOME: "/opt/asdf-data/corepack",
|
|
40
|
+
COREPACK_DEFAULT_TO_LATEST: "0",
|
|
41
|
+
COREPACK_ENV_FILE: "0",
|
|
42
|
+
COREPACK_NPM_REGISTRY: "https://registry.npmjs.org",
|
|
43
|
+
COREPACK_ENABLE_NETWORK: "0",
|
|
44
|
+
COREPACK_ENABLE_AUTO_PIN: "0",
|
|
45
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
|
46
|
+
COREPACK_ENABLE_PROJECT_SPEC: "1",
|
|
47
|
+
COREPACK_ENABLE_STRICT: "1",
|
|
48
|
+
COREPACK_ENABLE_UNSAFE_CUSTOM_URLS: "0",
|
|
49
|
+
COREPACK_INTEGRITY_CHECK: "1",
|
|
50
|
+
// npm treats npm_config_* names case-insensitively and lifecycle scripts
|
|
51
|
+
// often receive the lowercase spelling. Pin both to task-private storage
|
|
52
|
+
// so image/project environment cannot turn an ordinary global install
|
|
53
|
+
// into a shared-cache writer.
|
|
54
|
+
NPM_CONFIG_PREFIX: "/home/node/.local/share/uai-npm",
|
|
55
|
+
npm_config_prefix: "/home/node/.local/share/uai-npm",
|
|
56
|
+
NPM_CONFIG_CACHE: "/home/node/.cache/uai-npm",
|
|
57
|
+
npm_config_cache: "/home/node/.cache/uai-npm",
|
|
58
|
+
NPM_CONFIG_LOGS_DIR: "/home/node/.cache/uai-npm/_logs",
|
|
59
|
+
npm_config_logs_dir: "/home/node/.cache/uai-npm/_logs",
|
|
60
|
+
NPM_CONFIG_UMASK: "0002",
|
|
61
|
+
npm_config_umask: "0002",
|
|
62
|
+
LD_PRELOAD: "",
|
|
63
|
+
LD_AUDIT: "",
|
|
64
|
+
LD_LIBRARY_PATH: "",
|
|
65
|
+
NODE_OPTIONS: "",
|
|
66
|
+
NODE_PATH: "",
|
|
67
|
+
// SHELLOPTS/BASHOPTS deliberately absent: present-in-env (even empty)
|
|
68
|
+
// makes bash export its live options into children — the Editor-terminal
|
|
69
|
+
// nounset poisoning of 2026-08-16.
|
|
70
|
+
PS4: "",
|
|
71
|
+
BASH_ENV: "",
|
|
72
|
+
ENV: "",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ADR-121: the MACHINE flavor of the authority env. A machine is a
|
|
77
|
+
* single-task trust domain — the container constants above defend SHARED
|
|
78
|
+
* host resources (the read-only asdf/corepack volume, uai-npm prefixes)
|
|
79
|
+
* that do not exist on a machine, and carrying them anyway pinned corepack
|
|
80
|
+
* to an unwritable /opt path with the network off, so `pnpm install`
|
|
81
|
+
* failed out of the box (live 2026-08-27, first EC2 task — the agent had
|
|
82
|
+
* to hand-repair its own environment). Keep the hygiene (loader/env
|
|
83
|
+
* clearing, prompt-free corepack), drop the container-topology pins.
|
|
84
|
+
*/
|
|
85
|
+
export const MACHINE_RUNTIME_AUTHORITY_ENV: Readonly<Record<string, string>> =
|
|
86
|
+
Object.freeze({
|
|
87
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/node/.local/bin",
|
|
88
|
+
HOME: "/home/node",
|
|
89
|
+
XDG_CONFIG_HOME: "/home/node/.config",
|
|
90
|
+
XDG_DATA_HOME: "/home/node/.local/share",
|
|
91
|
+
XDG_CACHE_HOME: "/home/node/.cache",
|
|
92
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
|
93
|
+
LD_PRELOAD: "",
|
|
94
|
+
LD_AUDIT: "",
|
|
95
|
+
LD_LIBRARY_PATH: "",
|
|
96
|
+
NODE_OPTIONS: "",
|
|
97
|
+
NODE_PATH: "",
|
|
98
|
+
PS4: "",
|
|
99
|
+
BASH_ENV: "",
|
|
100
|
+
ENV: "",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/** Root-owned loader paths installed in every PR2+ task environment. */
|
|
104
|
+
export const PROJECT_ENV_HOOK_PATH = "/usr/local/share/uai/env-hook.sh";
|
|
105
|
+
export const PROJECT_ENV_EXEC_PATH = "/usr/local/bin/uai-env-exec";
|
|
106
|
+
|
|
107
|
+
/** Names that must reach a provider-added outer shell as well as the payload. */
|
|
108
|
+
export const PROJECT_ENV_PROFILE_ENV_NAMES = Object.freeze([
|
|
109
|
+
"BASH_ENV",
|
|
110
|
+
"SHELL",
|
|
111
|
+
"UAI_ENV_HOOK",
|
|
112
|
+
] as const);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Apply the caller's classified project-env policy at the final provider
|
|
116
|
+
* boundary. The overlay is deliberately last, so inherited/caller env cannot
|
|
117
|
+
* opt a maintenance command into the hook or replace the task login shell.
|
|
118
|
+
* Legacy baked environments are a rollout no-op: their image has no loader.
|
|
119
|
+
*/
|
|
120
|
+
export function applyProjectEnvProfile<
|
|
121
|
+
T extends {
|
|
122
|
+
projectEnvProfile: ProjectEnvProfile;
|
|
123
|
+
env?: Readonly<Record<string, string>>;
|
|
124
|
+
},
|
|
125
|
+
>(
|
|
126
|
+
request: T,
|
|
127
|
+
envMode: ProjectEnvMode,
|
|
128
|
+
taskShell: "/usr/bin/zsh" | "/bin/bash",
|
|
129
|
+
): T {
|
|
130
|
+
if (envMode === "baked") return request;
|
|
131
|
+
const profileEnv =
|
|
132
|
+
request.projectEnvProfile === "maintenance"
|
|
133
|
+
? { BASH_ENV: "", UAI_ENV_HOOK: "0" }
|
|
134
|
+
: {
|
|
135
|
+
BASH_ENV: PROJECT_ENV_HOOK_PATH,
|
|
136
|
+
SHELL: taskShell,
|
|
137
|
+
UAI_ENV_HOOK: "1",
|
|
138
|
+
};
|
|
139
|
+
return { ...request, env: { ...request.env, ...profileEnv } };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Rewrite a session env assembled for a CONTAINER into its machine form:
|
|
143
|
+
* every container-authority key is dropped, then the machine authority is
|
|
144
|
+
* overlaid. Non-authority keys (engine credentials, task tokens, preview
|
|
145
|
+
* env) pass through untouched. */
|
|
146
|
+
export function machineSessionAuthorityEnv(
|
|
147
|
+
env: Readonly<Record<string, string>> | undefined,
|
|
148
|
+
): Record<string, string> {
|
|
149
|
+
const out: Record<string, string> = {};
|
|
150
|
+
for (const [key, value] of Object.entries(env ?? {})) {
|
|
151
|
+
if (key in RUNTIME_AUTHORITY_ENV) continue;
|
|
152
|
+
out[key] = value;
|
|
153
|
+
}
|
|
154
|
+
return { ...out, ...MACHINE_RUNTIME_AUTHORITY_ENV };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Docker `exec` options for the fixed authority environment. Callers append
|
|
158
|
+
* this after every less-trusted `-e` option and immediately before the
|
|
159
|
+
* container name. Container admission additionally rejects noncanonical
|
|
160
|
+
* case/underscore/hyphen aliases for npm's host-owned path settings. */
|
|
161
|
+
export function runtimeAuthorityDockerExecArgs(): string[] {
|
|
162
|
+
return Object.entries(RUNTIME_AUTHORITY_ENV).flatMap(([key, value]) => [
|
|
163
|
+
"-e",
|
|
164
|
+
`${key}=${value}`,
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide, dependency-light admission seam for background work that can
|
|
3
|
+
* reach Docker without entering through HostCommands. It defaults open so
|
|
4
|
+
* isolated library tests and non-daemon embeddings preserve their historical
|
|
5
|
+
* behavior; main.ts installs the authoritative ADR-101 readiness predicate.
|
|
6
|
+
*/
|
|
7
|
+
let containersOperational = (): boolean => true;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A typed failure for work that reached its last Docker boundary after the
|
|
11
|
+
* process-wide runtime verdict was invalidated. Callers that otherwise use a
|
|
12
|
+
* boolean for an ordinary domain result (notably GitHub's disconnected state)
|
|
13
|
+
* must be able to keep infrastructure unavailability distinct.
|
|
14
|
+
*/
|
|
15
|
+
export class ContainerRuntimeUnavailableError extends Error {
|
|
16
|
+
constructor(message = "container runtime is not operational") {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "ContainerRuntimeUnavailableError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function setContainerRuntimeOperationalCheck(
|
|
23
|
+
check: () => boolean,
|
|
24
|
+
): void {
|
|
25
|
+
containersOperational = check;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function backgroundContainerWorkAllowed(): boolean {
|
|
29
|
+
return containersOperational();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function requireContainerRuntimeOperational(): void {
|
|
33
|
+
if (!backgroundContainerWorkAllowed()) {
|
|
34
|
+
throw new ContainerRuntimeUnavailableError();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash-safe identity for a container backend that may hold task state.
|
|
3
|
+
*
|
|
4
|
+
* The marker is deliberately monotonic. Once a bundled-runtime task can create
|
|
5
|
+
* a container, image, or volume, `auto` must not silently move the host back
|
|
6
|
+
* to an ambient Docker Desktop/OrbStack daemon on the next service restart. An
|
|
7
|
+
* explicit `UAI_RUNTIME=docker` remains the operator's switch.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomBytes } from "node:crypto";
|
|
11
|
+
import {
|
|
12
|
+
chmodSync,
|
|
13
|
+
closeSync,
|
|
14
|
+
existsSync,
|
|
15
|
+
fsyncSync,
|
|
16
|
+
linkSync,
|
|
17
|
+
lstatSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
openSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { basename, dirname, join } from "node:path";
|
|
25
|
+
|
|
26
|
+
export type StatefulRuntimeProvider = "apple-container";
|
|
27
|
+
|
|
28
|
+
export interface RuntimeProviderState {
|
|
29
|
+
readonly schemaVersion: 1;
|
|
30
|
+
readonly statefulProvider: StatefulRuntimeProvider;
|
|
31
|
+
readonly claimedAt: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RuntimeProviderStateStore {
|
|
35
|
+
read(): RuntimeProviderState | null | Promise<RuntimeProviderState | null>;
|
|
36
|
+
claim(
|
|
37
|
+
provider: StatefulRuntimeProvider,
|
|
38
|
+
): RuntimeProviderState | Promise<RuntimeProviderState>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function runtimeProviderStatePath(dataDir: string): string {
|
|
42
|
+
return join(dataDir, "container-runtime-provider.json");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function fileRuntimeProviderStateStore(
|
|
46
|
+
file: string,
|
|
47
|
+
): RuntimeProviderStateStore {
|
|
48
|
+
return {
|
|
49
|
+
read: () => readRuntimeProviderState(file),
|
|
50
|
+
claim: (provider) => claimRuntimeProviderState(file, provider),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readRuntimeProviderState(
|
|
55
|
+
file: string,
|
|
56
|
+
): RuntimeProviderState | null {
|
|
57
|
+
if (!existsSync(file)) return null;
|
|
58
|
+
const stat = lstatSync(file);
|
|
59
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
60
|
+
throw new Error("container runtime provider state must be a regular file");
|
|
61
|
+
}
|
|
62
|
+
let value: unknown;
|
|
63
|
+
try {
|
|
64
|
+
value = JSON.parse(readFileSync(file, "utf8"));
|
|
65
|
+
} catch {
|
|
66
|
+
throw new Error("container runtime provider state is malformed");
|
|
67
|
+
}
|
|
68
|
+
return validateRuntimeProviderState(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Durably claim the first stateful fallback provider without an overwrite
|
|
73
|
+
* race. The marker is written before task provisioning can mutate that
|
|
74
|
+
* provider. A false-positive after an interrupted task-up is safe (and only
|
|
75
|
+
* makes `auto` conservative); a missing marker after VM state was created is
|
|
76
|
+
* not.
|
|
77
|
+
*/
|
|
78
|
+
export function claimRuntimeProviderState(
|
|
79
|
+
file: string,
|
|
80
|
+
provider: StatefulRuntimeProvider,
|
|
81
|
+
): RuntimeProviderState {
|
|
82
|
+
const existing = readRuntimeProviderState(file);
|
|
83
|
+
if (existing) {
|
|
84
|
+
if (existing.statefulProvider !== provider) {
|
|
85
|
+
throw new Error("container runtime provider is already claimed");
|
|
86
|
+
}
|
|
87
|
+
return existing;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const state: RuntimeProviderState = {
|
|
91
|
+
schemaVersion: 1,
|
|
92
|
+
statefulProvider: provider,
|
|
93
|
+
claimedAt: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
const parent = dirname(file);
|
|
96
|
+
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
97
|
+
const temporary = join(
|
|
98
|
+
parent,
|
|
99
|
+
`.${basename(file)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`,
|
|
100
|
+
);
|
|
101
|
+
try {
|
|
102
|
+
writeDurableFile(temporary, `${JSON.stringify(state, null, 2)}\n`);
|
|
103
|
+
try {
|
|
104
|
+
linkSync(temporary, file);
|
|
105
|
+
fsyncDirectory(parent);
|
|
106
|
+
return state;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
109
|
+
const winner = readRuntimeProviderState(file);
|
|
110
|
+
if (!winner) throw new Error("container runtime provider claim disappeared");
|
|
111
|
+
if (winner.statefulProvider !== provider) {
|
|
112
|
+
throw new Error("container runtime provider was claimed concurrently");
|
|
113
|
+
}
|
|
114
|
+
return winner;
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
try {
|
|
118
|
+
unlinkSync(temporary);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateRuntimeProviderState(value: unknown): RuntimeProviderState {
|
|
126
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
127
|
+
throw new Error("container runtime provider state is malformed");
|
|
128
|
+
}
|
|
129
|
+
const record = value as Record<string, unknown>;
|
|
130
|
+
if (
|
|
131
|
+
Object.keys(record).length !== 3 ||
|
|
132
|
+
record.schemaVersion !== 1 ||
|
|
133
|
+
record.statefulProvider !== "apple-container" ||
|
|
134
|
+
typeof record.claimedAt !== "number" ||
|
|
135
|
+
!Number.isSafeInteger(record.claimedAt) ||
|
|
136
|
+
record.claimedAt <= 0
|
|
137
|
+
) {
|
|
138
|
+
throw new Error("container runtime provider state is malformed");
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
schemaVersion: 1,
|
|
142
|
+
statefulProvider: "apple-container",
|
|
143
|
+
claimedAt: record.claimedAt,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function writeDurableFile(file: string, body: string): void {
|
|
148
|
+
const descriptor = openSync(file, "wx", 0o600);
|
|
149
|
+
try {
|
|
150
|
+
writeFileSync(descriptor, body, "utf8");
|
|
151
|
+
fsyncSync(descriptor);
|
|
152
|
+
} finally {
|
|
153
|
+
closeSync(descriptor);
|
|
154
|
+
}
|
|
155
|
+
chmodSync(file, 0o600);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function fsyncDirectory(directory: string): void {
|
|
159
|
+
let descriptor: number | null = null;
|
|
160
|
+
try {
|
|
161
|
+
descriptor = openSync(directory, "r");
|
|
162
|
+
fsyncSync(descriptor);
|
|
163
|
+
} catch {
|
|
164
|
+
// Some filesystems do not expose directory fsync. The marker itself was
|
|
165
|
+
// still fsync'd before the atomic hard-link publication.
|
|
166
|
+
} finally {
|
|
167
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A known-ready container runtime revalidated when the bridge reconnects: a
|
|
3
|
+
* healthy daemon can still have restarted while the bridge was offline. One
|
|
4
|
+
* label scan keeps ordinary cloud/network flaps cheap; only a missing,
|
|
5
|
+
* exited or unknowable active app takes the full per-task recovery.
|
|
6
|
+
*
|
|
7
|
+
* ADR-154: it runs on the credentials it began on, as an activation does
|
|
8
|
+
* (credentialsActivationFence). Off the runner's own before this process's
|
|
9
|
+
* credential sweep has succeeded, or once leaving them begins, it neither
|
|
10
|
+
* recovers nor publishes: it hands the runtime to the credentials-aware
|
|
11
|
+
* activation, which sweeps first. Off them after the sweep, its recovery
|
|
12
|
+
* goes through the credentials wrapper (the sweep already done).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
cloudCredentialsSwept,
|
|
17
|
+
credentialsActivationFence,
|
|
18
|
+
onOwnCredentials,
|
|
19
|
+
recoverRuntimeOnCredentials,
|
|
20
|
+
} from "./cloud-credential-host";
|
|
21
|
+
|
|
22
|
+
export interface RuntimeReconnectDeps {
|
|
23
|
+
/** Suspends a ready runtime for revalidation; its epoch, or null. */
|
|
24
|
+
suspend(): number | null;
|
|
25
|
+
/** Probes the daemon again: ready or not. */
|
|
26
|
+
probe(): Promise<boolean>;
|
|
27
|
+
/** The readiness epoch is still this one, and the daemon ready. */
|
|
28
|
+
epochIsCurrent(epoch: number): boolean;
|
|
29
|
+
/** The epoch is current and the runner is not stopping. */
|
|
30
|
+
activationIsCurrent(epoch: number): boolean;
|
|
31
|
+
persistedEnvironmentsMatch(): Promise<boolean>;
|
|
32
|
+
recoveryVerdict(): Promise<"clean" | "needed" | "unknown">;
|
|
33
|
+
/** The orchestrator's per-task recovery. */
|
|
34
|
+
recover(isCurrent: () => boolean): Promise<boolean>;
|
|
35
|
+
/** A bounded activation retry for the epoch. */
|
|
36
|
+
scheduleRetry(epoch: number): void;
|
|
37
|
+
/** A full, credentials-aware activation pass for the epoch. */
|
|
38
|
+
requestActivation(epoch: number): void;
|
|
39
|
+
/** Marks the runtime operational for the epoch, resumes deferred agent
|
|
40
|
+
* work and re-advertises; false when it could not be marked. */
|
|
41
|
+
publish(epoch: number): boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function revalidateRuntimeOnConnect(deps: RuntimeReconnectDeps): Promise<void> {
|
|
45
|
+
const epoch = deps.suspend();
|
|
46
|
+
if (epoch === null) return;
|
|
47
|
+
const fence = credentialsActivationFence(() => deps.activationIsCurrent(epoch));
|
|
48
|
+
const handOver = (): void => {
|
|
49
|
+
if (deps.activationIsCurrent(epoch)) deps.requestActivation(epoch);
|
|
50
|
+
};
|
|
51
|
+
/** Not current any more: leaving the runner's own credentials hands the
|
|
52
|
+
* runtime to the activation; a stale epoch ends here. */
|
|
53
|
+
const stopped = (): boolean => {
|
|
54
|
+
if (fence.isCurrent()) return false;
|
|
55
|
+
if (fence.leftLegacy()) handOver();
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
if (!(await deps.probe()) || !deps.epochIsCurrent(epoch)) return;
|
|
60
|
+
if (!onOwnCredentials() && !cloudCredentialsSwept()) {
|
|
61
|
+
handOver();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!(await deps.persistedEnvironmentsMatch())) return;
|
|
65
|
+
if (stopped()) return;
|
|
66
|
+
let recovered = (await deps.recoveryVerdict()) === "clean";
|
|
67
|
+
if (!recovered) {
|
|
68
|
+
recovered = await recoverRuntimeOnCredentials(deps.recover, () => fence.isCurrent());
|
|
69
|
+
}
|
|
70
|
+
if (stopped()) return;
|
|
71
|
+
// Prove the daemon once more after recovery so a mid-sweep loss cannot
|
|
72
|
+
// reopen commands and tunnels on an unresolved backend.
|
|
73
|
+
if (!(await deps.probe()) || !deps.epochIsCurrent(epoch)) return;
|
|
74
|
+
if (stopped()) return;
|
|
75
|
+
if (!recovered) {
|
|
76
|
+
deps.scheduleRetry(epoch);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
deps.publish(epoch);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`[host-agent] container runtime reconnect validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
83
|
+
);
|
|
84
|
+
if (deps.activationIsCurrent(epoch)) deps.scheduleRetry(epoch);
|
|
85
|
+
}
|
|
86
|
+
}
|