@moondeck/station 0.0.1 → 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.
- 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/meta/_journal.json +223 -0
- package/db/schema.ts +850 -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 +1453 -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 +891 -0
- package/lib/env.ts +195 -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/capture-wire.ts +102 -0
- package/src/cli.ts +2583 -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/event-outbox.ts +185 -0
- package/src/index.ts +2899 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6349 -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 +5545 -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,704 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# uai-init — prepare a standard-image task container (ADR-022).
|
|
3
|
+
#
|
|
4
|
+
# Runs INSIDE the container (Linux, bash 5 — may use anything). Invoked by
|
|
5
|
+
# the host via `docker exec <app> /usr/local/bin/uai-init` after compose up.
|
|
6
|
+
# Spec: ../../../docs/runtime.md §uai-init.
|
|
7
|
+
#
|
|
8
|
+
# Workspace layout (ADR-022): /workspace/<project-slug>/ — one git worktree
|
|
9
|
+
# per selected project. A 0-project (scratchpad) task has an empty /workspace.
|
|
10
|
+
#
|
|
11
|
+
# Responsibilities:
|
|
12
|
+
# 1. For each /workspace/<slug>/ project, run a dependency-install heuristic
|
|
13
|
+
# for the folder's stack. task-up has already materialized and verified
|
|
14
|
+
# its declared runtimes or the image's root defaults before this task
|
|
15
|
+
# container starts; this script never mutates the shared asdf volume.
|
|
16
|
+
# 2. Launch code-server on 0.0.0.0:8080 (auth disabled — reached only via
|
|
17
|
+
# the authenticated tunnel), opening /workspace.
|
|
18
|
+
#
|
|
19
|
+
# Best-effort + idempotent: an ordinary failing folder logs and is skipped.
|
|
20
|
+
# Re-running when things are already up is a no-op. A host runtime-authority
|
|
21
|
+
# violation exits 78 so task-up/recovery can quarantine the task instead of
|
|
22
|
+
# starting agents against a runtime context that was never proved.
|
|
23
|
+
|
|
24
|
+
set -uo pipefail
|
|
25
|
+
|
|
26
|
+
WORKSPACE="${UAI_WORKSPACE:-/workspace}"
|
|
27
|
+
RUNTIME_PROJECTS="${UAI_RUNTIME_PROJECTS:-/run/uai/runtime-projects}"
|
|
28
|
+
|
|
29
|
+
# Compose owns runtime/cache selection. Reassert it here for rolling recovery
|
|
30
|
+
# and remove every inherited asdf/Corepack/npm authority variable before any
|
|
31
|
+
# dependency command resolves a runtime. No command in this script installs or
|
|
32
|
+
# reshims. Direct source-tree tests may prepend stubs; the packaged path never
|
|
33
|
+
# honors those test-only seams. Bash does not import environment names that
|
|
34
|
+
# contain `-`, but npm treats LOGS-DIR and LOGS_DIR as the same setting. Strip
|
|
35
|
+
# every owned spelling with GNU/BSD env before continuing, then export only
|
|
36
|
+
# Uai's exact canonical names. Unrelated registry/auth npm config survives.
|
|
37
|
+
npm_sanitize_args=()
|
|
38
|
+
while IFS= read -r -d '' environment_entry; do
|
|
39
|
+
environment_name=${environment_entry%%=*}
|
|
40
|
+
case "$environment_name" in
|
|
41
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Pp][Rr][Ee][Ff][Ii][Xx] | \
|
|
42
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Cc][Aa][Cc][Hh][Ee] | \
|
|
43
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Ll][Oo][Gg][Ss][_-][Dd][Ii][Rr] | \
|
|
44
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Uu][Mm][Aa][Ss][Kk])
|
|
45
|
+
npm_sanitize_args+=( -u "$environment_name" )
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
done < <(/usr/bin/env -0)
|
|
49
|
+
if [ "${#npm_sanitize_args[@]}" -gt 0 ]; then
|
|
50
|
+
exec /usr/bin/env "${npm_sanitize_args[@]}" \
|
|
51
|
+
/bin/bash "${BASH_SOURCE[0]}" "$@"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Parse the role only after the one-time sanitized re-exec above. Parsing it
|
|
55
|
+
# first would shift away --dependency-worker, causing the clean process to
|
|
56
|
+
# restart as the main/editor role whenever inherited npm authority was
|
|
57
|
+
# present (the normal task environment).
|
|
58
|
+
UAI_INIT_ROLE=main
|
|
59
|
+
case ${1-} in
|
|
60
|
+
--dependency-worker)
|
|
61
|
+
UAI_INIT_ROLE=dependency-worker
|
|
62
|
+
shift
|
|
63
|
+
;;
|
|
64
|
+
"") ;;
|
|
65
|
+
*)
|
|
66
|
+
printf '%s\n' 'uai-init: invalid invocation' >&2
|
|
67
|
+
exit 64
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
[ "$#" -eq 0 ] || {
|
|
71
|
+
printf '%s\n' 'uai-init: invalid invocation' >&2
|
|
72
|
+
exit 64
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for runtime_var in $(compgen -A variable 2>/dev/null); do
|
|
76
|
+
case "$runtime_var" in
|
|
77
|
+
ASDF_* | COREPACK_* | \
|
|
78
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Pp][Rr][Ee][Ff][Ii][Xx] | \
|
|
79
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Cc][Aa][Cc][Hh][Ee] | \
|
|
80
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Ll][Oo][Gg][Ss]_[Dd][Ii][Rr] | \
|
|
81
|
+
[Nn][Pp][Mm]_[Cc][Oo][Nn][Ff][Ii][Gg]_[Uu][Mm][Aa][Ss][Kk])
|
|
82
|
+
unset "$runtime_var"
|
|
83
|
+
;;
|
|
84
|
+
esac
|
|
85
|
+
done
|
|
86
|
+
unset LD_PRELOAD LD_AUDIT LD_LIBRARY_PATH NODE_OPTIONS NODE_PATH PS4 BASH_ENV ENV
|
|
87
|
+
case "${BASH_SOURCE[0]}" in
|
|
88
|
+
/usr/local/bin/uai-init | /uai-init-current) export HOME=/home/node ;;
|
|
89
|
+
esac
|
|
90
|
+
export XDG_CONFIG_HOME="$HOME/.config"
|
|
91
|
+
export XDG_DATA_HOME="$HOME/.local/share"
|
|
92
|
+
export XDG_CACHE_HOME="$HOME/.cache"
|
|
93
|
+
export ASDF_DIR=/opt/asdf
|
|
94
|
+
export ASDF_DATA_DIR=/opt/asdf-data
|
|
95
|
+
export ASDF_CONFIG_FILE=/dev/null
|
|
96
|
+
export ASDF_DEFAULT_TOOL_VERSIONS_FILENAME=.tool-versions
|
|
97
|
+
export ASDF_NODEJS_VERSION=
|
|
98
|
+
export ASDF_PYTHON_VERSION=
|
|
99
|
+
export ASDF_GOLANG_VERSION=
|
|
100
|
+
export ASDF_RUBY_VERSION=
|
|
101
|
+
export ASDF_RUST_VERSION=
|
|
102
|
+
export ASDF_SKIP_RESHIM=1
|
|
103
|
+
export COREPACK_HOME=/opt/asdf-data/corepack
|
|
104
|
+
export COREPACK_DEFAULT_TO_LATEST=0
|
|
105
|
+
export COREPACK_ENV_FILE=0
|
|
106
|
+
export COREPACK_NPM_REGISTRY=https://registry.npmjs.org
|
|
107
|
+
export COREPACK_ENABLE_NETWORK=0
|
|
108
|
+
export COREPACK_ENABLE_AUTO_PIN=0
|
|
109
|
+
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
|
110
|
+
export COREPACK_ENABLE_PROJECT_SPEC=1
|
|
111
|
+
export COREPACK_ENABLE_STRICT=1
|
|
112
|
+
export COREPACK_ENABLE_UNSAFE_CUSTOM_URLS=0
|
|
113
|
+
export COREPACK_INTEGRITY_CHECK=1
|
|
114
|
+
export NPM_CONFIG_PREFIX="$HOME/.local/share/uai-npm"
|
|
115
|
+
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
|
116
|
+
export NPM_CONFIG_CACHE="$HOME/.cache/uai-npm"
|
|
117
|
+
export npm_config_cache="$NPM_CONFIG_CACHE"
|
|
118
|
+
export NPM_CONFIG_LOGS_DIR="$NPM_CONFIG_CACHE/_logs"
|
|
119
|
+
export npm_config_logs_dir="$NPM_CONFIG_LOGS_DIR"
|
|
120
|
+
export NPM_CONFIG_UMASK=0002
|
|
121
|
+
export npm_config_umask=0002
|
|
122
|
+
export NODE_OPTIONS=
|
|
123
|
+
export NODE_PATH=
|
|
124
|
+
export PATH="/opt/asdf-data/shims:/opt/asdf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$NPM_CONFIG_PREFIX/bin"
|
|
125
|
+
# Fixed here, not from the environment: the packaged path must not let a
|
|
126
|
+
# forged mount table or lower path defeat the shared-cache backstop below.
|
|
127
|
+
UAI_PROC_MOUNTS=/proc/mounts
|
|
128
|
+
UAI_ASDF_LOWER_DIR=/run/uai/asdf-lower
|
|
129
|
+
case "${BASH_SOURCE[0]}" in
|
|
130
|
+
/usr/local/bin/uai-init | /uai-init-current) ;;
|
|
131
|
+
*)
|
|
132
|
+
if [ -n "${UAI_INIT_TEST_ASDF_DATA_DIR:-}" ]; then
|
|
133
|
+
export ASDF_DATA_DIR="$UAI_INIT_TEST_ASDF_DATA_DIR"
|
|
134
|
+
export COREPACK_HOME="$ASDF_DATA_DIR/corepack"
|
|
135
|
+
fi
|
|
136
|
+
if [ -n "${UAI_INIT_TEST_PATH:-}" ]; then
|
|
137
|
+
export PATH="$UAI_INIT_TEST_PATH:$PATH"
|
|
138
|
+
fi
|
|
139
|
+
if [ -n "${UAI_INIT_TEST_PROC_MOUNTS:-}" ]; then
|
|
140
|
+
UAI_PROC_MOUNTS="$UAI_INIT_TEST_PROC_MOUNTS"
|
|
141
|
+
fi
|
|
142
|
+
if [ -n "${UAI_INIT_TEST_ASDF_LOWER_DIR:-}" ]; then
|
|
143
|
+
UAI_ASDF_LOWER_DIR="$UAI_INIT_TEST_ASDF_LOWER_DIR"
|
|
144
|
+
fi
|
|
145
|
+
;;
|
|
146
|
+
esac
|
|
147
|
+
UAI_ENV_EXEC=/usr/local/bin/uai-env-exec
|
|
148
|
+
UAI_ENV_HOOK_FILE=/usr/local/share/uai/env-hook.sh
|
|
149
|
+
MANAGED_ENV_KEYS=/run/uai/managed-env-keys
|
|
150
|
+
UAI_INIT_SELF=/usr/local/bin/uai-init
|
|
151
|
+
case "${BASH_SOURCE[0]}" in
|
|
152
|
+
/usr/local/bin/uai-init | /uai-init-current) ;;
|
|
153
|
+
*)
|
|
154
|
+
UAI_INIT_SELF=${BASH_SOURCE[0]}
|
|
155
|
+
[ -z "${UAI_INIT_TEST_ENV_EXEC:-}" ] \
|
|
156
|
+
|| UAI_ENV_EXEC=$UAI_INIT_TEST_ENV_EXEC
|
|
157
|
+
[ -z "${UAI_INIT_TEST_ENV_HOOK:-}" ] \
|
|
158
|
+
|| UAI_ENV_HOOK_FILE=$UAI_INIT_TEST_ENV_HOOK
|
|
159
|
+
[ -z "${UAI_INIT_TEST_MANAGED_ENV_KEYS:-}" ] \
|
|
160
|
+
|| MANAGED_ENV_KEYS=$UAI_INIT_TEST_MANAGED_ENV_KEYS
|
|
161
|
+
;;
|
|
162
|
+
esac
|
|
163
|
+
ASDF_SH="$ASDF_DIR/asdf.sh"
|
|
164
|
+
|
|
165
|
+
log() { echo "uai-init: $*"; }
|
|
166
|
+
|
|
167
|
+
reject_writable_shared_runtime_cache() {
|
|
168
|
+
# The host proves the Docker mount metadata before invoking us. Keep an
|
|
169
|
+
# in-container backstop as well so a current initializer staged into a
|
|
170
|
+
# legacy/recovered container cannot become a shared-cache writer. This is a
|
|
171
|
+
# metadata/access check only: never create even a temporary shared file.
|
|
172
|
+
if [ ! -d "$ASDF_DATA_DIR" ]; then
|
|
173
|
+
log "error: shared runtime cache is missing; refusing dependency or editor startup"
|
|
174
|
+
return 1
|
|
175
|
+
fi
|
|
176
|
+
# ADR-108 (apple): /opt/asdf-data is an in-guest overlay — writable BY
|
|
177
|
+
# DESIGN, with every write landing in the task-private upper layer. That is
|
|
178
|
+
# safe exactly when the overlay is real (this mount table, not trust) and
|
|
179
|
+
# its lower is the read-only shared attachment; the shared volume then
|
|
180
|
+
# still cannot be written from this container. Any other writable shape
|
|
181
|
+
# remains the violation it always was.
|
|
182
|
+
# /usr/bin paths: present on both merged-usr Debian (in-container) and
|
|
183
|
+
# macOS (the direct source-tree test harness). Token-exact matching against
|
|
184
|
+
# comma-delimited mount options — substring matching accepted
|
|
185
|
+
# lowerdir=<lower>-evil — plus exactly ONE effective mount at the merged
|
|
186
|
+
# path (a later mount would shadow the proven one).
|
|
187
|
+
local overlay_lines="" any_lines="" overlay_opts=""
|
|
188
|
+
overlay_lines=$(/usr/bin/grep -c "^overlay ${ASDF_DATA_DIR} overlay " \
|
|
189
|
+
"$UAI_PROC_MOUNTS" 2>/dev/null) || overlay_lines=0
|
|
190
|
+
if [ "$overlay_lines" != "0" ]; then
|
|
191
|
+
if [ "$overlay_lines" != "1" ]; then
|
|
192
|
+
log "error: expected exactly one shared runtime cache overlay, found $overlay_lines; refusing dependency or editor startup"
|
|
193
|
+
return 1
|
|
194
|
+
fi
|
|
195
|
+
any_lines=$(/usr/bin/grep -c " ${ASDF_DATA_DIR} " "$UAI_PROC_MOUNTS" \
|
|
196
|
+
2>/dev/null) || any_lines=0
|
|
197
|
+
if [ "$any_lines" != "1" ]; then
|
|
198
|
+
log "error: unexpected extra mounts at the shared runtime cache; refusing dependency or editor startup"
|
|
199
|
+
return 1
|
|
200
|
+
fi
|
|
201
|
+
overlay_opts=$(/usr/bin/grep "^overlay ${ASDF_DATA_DIR} overlay " \
|
|
202
|
+
"$UAI_PROC_MOUNTS" | /usr/bin/cut -d" " -f4)
|
|
203
|
+
case ",$overlay_opts," in
|
|
204
|
+
*",lowerdir=${UAI_ASDF_LOWER_DIR},"*) ;;
|
|
205
|
+
*)
|
|
206
|
+
log "error: shared runtime cache overlay has a foreign lower; refusing dependency or editor startup"
|
|
207
|
+
return 1
|
|
208
|
+
;;
|
|
209
|
+
esac
|
|
210
|
+
case ",$overlay_opts," in
|
|
211
|
+
*",upperdir=/var/lib/uai/asdf-upper,"*) ;;
|
|
212
|
+
*)
|
|
213
|
+
log "error: shared runtime cache overlay has a foreign upper; refusing dependency or editor startup"
|
|
214
|
+
return 1
|
|
215
|
+
;;
|
|
216
|
+
esac
|
|
217
|
+
case ",$overlay_opts," in
|
|
218
|
+
*",workdir=/var/lib/uai/asdf-work,"*) ;;
|
|
219
|
+
*)
|
|
220
|
+
log "error: shared runtime cache overlay has a foreign workdir; refusing dependency or editor startup"
|
|
221
|
+
return 1
|
|
222
|
+
;;
|
|
223
|
+
esac
|
|
224
|
+
if [ ! -d "$UAI_ASDF_LOWER_DIR" ]; then
|
|
225
|
+
log "error: shared runtime cache lower attachment is missing; refusing dependency or editor startup"
|
|
226
|
+
return 1
|
|
227
|
+
fi
|
|
228
|
+
if [ -w "$UAI_ASDF_LOWER_DIR" ]; then
|
|
229
|
+
log "error: shared runtime cache lower attachment is writable; refusing dependency or editor startup"
|
|
230
|
+
return 1
|
|
231
|
+
fi
|
|
232
|
+
return 0
|
|
233
|
+
fi
|
|
234
|
+
if [ -w "$ASDF_DATA_DIR" ]; then
|
|
235
|
+
log "error: shared runtime cache is writable in the task container; refusing dependency or editor startup"
|
|
236
|
+
return 1
|
|
237
|
+
fi
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
prepare_private_npm_authority() {
|
|
241
|
+
local path prefix_probe="" cache_probe="" logs_probe=""
|
|
242
|
+
|
|
243
|
+
# These directories live in the task container layer, never on the shared
|
|
244
|
+
# asdf volume. Reject redirects before mkdir, then prove both exact write
|
|
245
|
+
# targets. The task has not started any agents yet, so there is no concurrent
|
|
246
|
+
# task process that can race these checks.
|
|
247
|
+
for path in \
|
|
248
|
+
"$HOME/.local" \
|
|
249
|
+
"$HOME/.local/share" \
|
|
250
|
+
"$NPM_CONFIG_PREFIX" \
|
|
251
|
+
"$HOME/.cache" \
|
|
252
|
+
"$NPM_CONFIG_CACHE" \
|
|
253
|
+
"$NPM_CONFIG_LOGS_DIR"
|
|
254
|
+
do
|
|
255
|
+
if [ -L "$path" ] || { [ -e "$path" ] && [ ! -d "$path" ]; }; then
|
|
256
|
+
log "error: private npm authority path is unsafe: $path"
|
|
257
|
+
return 1
|
|
258
|
+
fi
|
|
259
|
+
done
|
|
260
|
+
|
|
261
|
+
if ! /bin/mkdir -p -- \
|
|
262
|
+
"$NPM_CONFIG_PREFIX/bin" "$NPM_CONFIG_CACHE" "$NPM_CONFIG_LOGS_DIR"; then
|
|
263
|
+
log "error: could not prepare the private npm prefix/cache"
|
|
264
|
+
return 1
|
|
265
|
+
fi
|
|
266
|
+
for path in \
|
|
267
|
+
"$NPM_CONFIG_PREFIX" \
|
|
268
|
+
"$NPM_CONFIG_PREFIX/bin" \
|
|
269
|
+
"$NPM_CONFIG_CACHE" \
|
|
270
|
+
"$NPM_CONFIG_LOGS_DIR"
|
|
271
|
+
do
|
|
272
|
+
if [ -L "$path" ] || [ ! -d "$path" ]; then
|
|
273
|
+
log "error: private npm authority path changed during preparation: $path"
|
|
274
|
+
return 1
|
|
275
|
+
fi
|
|
276
|
+
done
|
|
277
|
+
|
|
278
|
+
prefix_probe=$(/usr/bin/mktemp \
|
|
279
|
+
"$NPM_CONFIG_PREFIX/.uai-write-probe.XXXXXX") || {
|
|
280
|
+
log "error: private npm prefix is not writable"
|
|
281
|
+
return 1
|
|
282
|
+
}
|
|
283
|
+
cache_probe=$(/usr/bin/mktemp \
|
|
284
|
+
"$NPM_CONFIG_CACHE/.uai-write-probe.XXXXXX") || {
|
|
285
|
+
/bin/rm -f -- "$prefix_probe"
|
|
286
|
+
log "error: private npm cache is not writable"
|
|
287
|
+
return 1
|
|
288
|
+
}
|
|
289
|
+
logs_probe=$(/usr/bin/mktemp \
|
|
290
|
+
"$NPM_CONFIG_LOGS_DIR/.uai-write-probe.XXXXXX") || {
|
|
291
|
+
/bin/rm -f -- "$prefix_probe" "$cache_probe"
|
|
292
|
+
log "error: private npm logs directory is not writable"
|
|
293
|
+
return 1
|
|
294
|
+
}
|
|
295
|
+
/bin/rm -f -- "$prefix_probe" "$cache_probe" "$logs_probe"
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
reject_workspace_runtime_shadow() {
|
|
299
|
+
local shadow="$WORKSPACE/.tool-versions"
|
|
300
|
+
# The root is never a selected project. Any entry here changes asdf lookup
|
|
301
|
+
# for no-manifest projects without being part of the pre-start proof. Check
|
|
302
|
+
# metadata only so a FIFO/socket/device cannot block initialization.
|
|
303
|
+
if [ -L "$shadow" ] || [ -e "$shadow" ]; then
|
|
304
|
+
log "error: workspace-root .tool-versions appeared after runtime proof; refusing dependency or editor startup"
|
|
305
|
+
return 1
|
|
306
|
+
fi
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# Fail before sourcing asdf or executing any project input if this is an old
|
|
310
|
+
# writable task mount, or if task-private npm state redirects somewhere else.
|
|
311
|
+
reject_writable_shared_runtime_cache || exit 78
|
|
312
|
+
prepare_private_npm_authority || exit 78
|
|
313
|
+
|
|
314
|
+
# Make asdf + its shims available to this non-interactive shell. A bare
|
|
315
|
+
# `docker exec` sources no profile, so source asdf explicitly here.
|
|
316
|
+
if [ -f "$ASDF_SH" ]; then
|
|
317
|
+
# shellcheck disable=SC1090
|
|
318
|
+
. "$ASDF_SH"
|
|
319
|
+
else
|
|
320
|
+
log "warning: asdf not found at $ASDF_SH; dependency runtime resolution may fail"
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
# ---------------------------------------------------------------------------
|
|
324
|
+
# 0. Git transport selection (ADR-027). A connected user's App token is the
|
|
325
|
+
# primary clone/fetch/push credential over HTTPS; the host writes it into
|
|
326
|
+
# gh's private config and runs `gh auth setup-git` before agents start. SSH
|
|
327
|
+
# transport is only the no-GitHub-connection fallback. The SSH key remains
|
|
328
|
+
# useful in either mode for commit/tag signing.
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
if [ "$UAI_INIT_ROLE" = main ]; then
|
|
332
|
+
if [ "${UAI_SKIP_GIT_TRANSPORT:-0}" = "1" ]; then
|
|
333
|
+
# Recovery/connect/disconnect reconciles the current credential immediately
|
|
334
|
+
# before invoking us. Do not overwrite that live decision with the static
|
|
335
|
+
# transport marker captured when Compose was first created.
|
|
336
|
+
log "keeping the reconciled GitHub transport"
|
|
337
|
+
elif [ "${UAI_GIT_TRANSPORT:-anonymous}" = "ssh" ]; then
|
|
338
|
+
# Remove a prior token-mode reverse rewrite if this container is being
|
|
339
|
+
# repaired after disconnect, then route canonical HTTPS origins over SSH.
|
|
340
|
+
/usr/bin/git config --global --unset-all url."https://github.com/".insteadOf \
|
|
341
|
+
>/dev/null 2>&1 || true
|
|
342
|
+
/usr/bin/git config --global url."git@github.com:".insteadOf "https://github.com/"
|
|
343
|
+
/usr/bin/git config --global core.sshCommand "/usr/bin/ssh -o StrictHostKeyChecking=accept-new"
|
|
344
|
+
log "using SSH as the GitHub transport fallback"
|
|
345
|
+
else
|
|
346
|
+
# Heal containers created by the old SSH-only policy. Canonical origins are
|
|
347
|
+
# HTTPS; reverse legacy SSH URLs (including submodules) through gh as well.
|
|
348
|
+
/usr/bin/git config --global --unset-all url."git@github.com:".insteadOf \
|
|
349
|
+
>/dev/null 2>&1 || true
|
|
350
|
+
/usr/bin/git config --global --unset-all core.sshCommand >/dev/null 2>&1 || true
|
|
351
|
+
/usr/bin/git config --global --replace-all url."https://github.com/".insteadOf \
|
|
352
|
+
"git@github.com:"
|
|
353
|
+
/usr/bin/git config --global --add url."https://github.com/".insteadOf \
|
|
354
|
+
"ssh://git@github.com/"
|
|
355
|
+
if [ "${UAI_GIT_TRANSPORT:-}" = "https" ]; then
|
|
356
|
+
log "using the connected GitHub credential for HTTPS Git"
|
|
357
|
+
else
|
|
358
|
+
log "using anonymous HTTPS Git for public repositories"
|
|
359
|
+
fi
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# Signed commits (policy): when the uai SSH identity is present, configure git
|
|
363
|
+
# to SSH-sign every commit + tag with it. The pubkey is registered on GitHub
|
|
364
|
+
# so commits show as Verified. Signing is independent of Git transport.
|
|
365
|
+
if [ -f "$HOME/.ssh/id_ed25519.pub" ]; then
|
|
366
|
+
log "enabling SSH commit signing with the uai identity"
|
|
367
|
+
/usr/bin/git config --global gpg.format ssh
|
|
368
|
+
/usr/bin/git config --global user.signingkey "$HOME/.ssh/id_ed25519.pub"
|
|
369
|
+
/usr/bin/git config --global commit.gpgsign true
|
|
370
|
+
/usr/bin/git config --global tag.gpgsign true
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
# Attribution policy: never co-author with the agents. Claude Code honors
|
|
374
|
+
# `includeCoAuthoredBy: false` (drops the "Co-Authored-By: Claude" commit
|
|
375
|
+
# trailer and the "Generated with Claude Code" PR footer). Seed it into the
|
|
376
|
+
# per-task Claude user settings if absent. Codex is covered by the
|
|
377
|
+
# orchestrator's system preamble.
|
|
378
|
+
cc_settings="$HOME/.claude/settings.json"
|
|
379
|
+
if [ ! -f "$cc_settings" ]; then
|
|
380
|
+
mkdir -p "$HOME/.claude"
|
|
381
|
+
printf '{\n "includeCoAuthoredBy": false\n}\n' > "$cc_settings"
|
|
382
|
+
log "seeded ~/.claude/settings.json (includeCoAuthoredBy: false)"
|
|
383
|
+
fi
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
# ---------------------------------------------------------------------------
|
|
387
|
+
# 1. Per-folder dependency install (runtimes are already materialized).
|
|
388
|
+
# ---------------------------------------------------------------------------
|
|
389
|
+
|
|
390
|
+
install_node_deps() {
|
|
391
|
+
# Pick the package manager from the packageManager field, else a lockfile,
|
|
392
|
+
# else npm. corepack provisions pnpm/yarn from the packageManager field.
|
|
393
|
+
local pm=""
|
|
394
|
+
if [ -f package.json ]; then
|
|
395
|
+
pm=$(jq -r '.packageManager // ""' package.json 2>/dev/null | sed 's/@.*//')
|
|
396
|
+
fi
|
|
397
|
+
if [ -z "$pm" ]; then
|
|
398
|
+
if [ -f pnpm-lock.yaml ]; then pm="pnpm"
|
|
399
|
+
elif [ -f yarn.lock ]; then pm="yarn"
|
|
400
|
+
elif [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then pm="npm"
|
|
401
|
+
elif [ -f bun.lockb ] || [ -f bun.lock ]; then pm="bun"
|
|
402
|
+
else pm="npm"
|
|
403
|
+
fi
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
case "$pm" in
|
|
407
|
+
pnpm) log "pnpm install"; pnpm install ;;
|
|
408
|
+
yarn) log "yarn install"; yarn install ;;
|
|
409
|
+
bun) log "bun install"; bun install ;;
|
|
410
|
+
*) log "npm install"; npm install --no-audit --no-fund ;;
|
|
411
|
+
esac
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
install_python_deps() {
|
|
415
|
+
# uv handles both requirements.txt and pyproject.toml; create a project .venv
|
|
416
|
+
# so the install is isolated to the folder. Pin uv to ASDF's Python (the
|
|
417
|
+
# version asdf just resolved from the in-scope .tool-versions) so the venv
|
|
418
|
+
# matches the project's declared runtime. uv otherwise selects via
|
|
419
|
+
# .python-version / requires-python, which can disagree with .tool-versions
|
|
420
|
+
# (or be junk, e.g. a stray pyenv-virtualenv name) — asdf is the authority.
|
|
421
|
+
local py
|
|
422
|
+
py="$(asdf which python 2>/dev/null || true)"
|
|
423
|
+
if [ -f pyproject.toml ]; then
|
|
424
|
+
log "uv sync (pyproject.toml)${py:+ — asdf python $py}"
|
|
425
|
+
if [ -n "$py" ]; then uv sync --python "$py"; else uv sync; fi
|
|
426
|
+
elif [ -f requirements.txt ]; then
|
|
427
|
+
log "uv venv + pip install -r requirements.txt${py:+ — asdf python $py}"
|
|
428
|
+
if [ -n "$py" ]; then uv venv --python "$py"; else uv venv; fi \
|
|
429
|
+
&& uv pip install -r requirements.txt
|
|
430
|
+
fi
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
install_folder_deps() {
|
|
434
|
+
local dir="$1" name
|
|
435
|
+
name=$(basename "$dir")
|
|
436
|
+
|
|
437
|
+
# Enter the folder; asdf auto-resolves either its already-materialized local
|
|
438
|
+
# declarations or the already-proven root image defaults.
|
|
439
|
+
cd "$dir" || { log "cannot cd into $name — skipping"; return; }
|
|
440
|
+
|
|
441
|
+
# Dependency-install heuristic. Run every matcher that applies (a polyglot
|
|
442
|
+
# folder may have more than one); each is independently best-effort.
|
|
443
|
+
if [ -f package.json ]; then
|
|
444
|
+
install_node_deps || log "node deps failed in $name — continuing"
|
|
445
|
+
fi
|
|
446
|
+
if [ -f pyproject.toml ] || [ -f requirements.txt ]; then
|
|
447
|
+
install_python_deps || log "python deps failed in $name — continuing"
|
|
448
|
+
fi
|
|
449
|
+
if [ -f Cargo.toml ]; then
|
|
450
|
+
log "cargo build ($name)"
|
|
451
|
+
cargo build || log "cargo build failed in $name — continuing"
|
|
452
|
+
fi
|
|
453
|
+
if [ -f go.mod ]; then
|
|
454
|
+
log "go mod download ($name)"
|
|
455
|
+
go mod download || log "go mod download failed in $name — continuing"
|
|
456
|
+
fi
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
reject_workspace_runtime_shadow || exit 78
|
|
460
|
+
|
|
461
|
+
# ADR-122: dependency installs leave the task-start critical path. The loop
|
|
462
|
+
# below runs in a DETACHED background process so uai-init (and with it
|
|
463
|
+
# task-up) returns as soon as the cheap, provable work is done — the editor
|
|
464
|
+
# and the agents start immediately, warm or cold. Progress is observable in
|
|
465
|
+
# the workspace itself:
|
|
466
|
+
#
|
|
467
|
+
# $WORKSPACE/.moondeck/init/deps-status running | done | failed | authority-violation
|
|
468
|
+
# $WORKSPACE/.moondeck/init/deps.log the full install transcript
|
|
469
|
+
#
|
|
470
|
+
# The runtime-authority posture narrows deliberately: the synchronous
|
|
471
|
+
# workspace-shadow proofs above and below still exit 78 (task-up
|
|
472
|
+
# quarantines), but a shadow that APPEARS MID-INSTALL can no longer stop the
|
|
473
|
+
# task — agents are already running by then, and an agent could author the
|
|
474
|
+
# same shadow a second later anyway. The runner still refuses to run any
|
|
475
|
+
# further dependency command under a shadowed root and records
|
|
476
|
+
# `authority-violation`; the shared asdf volume stays read-only-proven
|
|
477
|
+
# regardless.
|
|
478
|
+
run_workspace_installs() {
|
|
479
|
+
local seen_runtime_projects="|" project_slug folder git_marker
|
|
480
|
+
while IFS= read -r project_slug || [ -n "$project_slug" ]; do
|
|
481
|
+
if [[ ! "$project_slug" =~ ^[a-z0-9-]{1,64}$ ]]; then
|
|
482
|
+
log "warning: unsafe runtime-project allowlist entry — skipping"
|
|
483
|
+
continue
|
|
484
|
+
fi
|
|
485
|
+
case "$seen_runtime_projects" in
|
|
486
|
+
*"|$project_slug|"*)
|
|
487
|
+
log "warning: duplicate runtime-project entry $project_slug — skipping"
|
|
488
|
+
continue
|
|
489
|
+
;;
|
|
490
|
+
esac
|
|
491
|
+
seen_runtime_projects="${seen_runtime_projects}${project_slug}|"
|
|
492
|
+
folder="$WORKSPACE/$project_slug"
|
|
493
|
+
git_marker="$folder/.git"
|
|
494
|
+
if [ -L "$folder" ] || [ ! -d "$folder" ] || [ -L "$git_marker" ] \
|
|
495
|
+
|| { [ ! -f "$git_marker" ] && [ ! -d "$git_marker" ]; }; then
|
|
496
|
+
log "warning: allowlisted project $project_slug is not a safe Git worktree — skipping"
|
|
497
|
+
continue
|
|
498
|
+
fi
|
|
499
|
+
if ! reject_workspace_runtime_shadow; then
|
|
500
|
+
printf 'authority-violation\n' > "$deps_status_file"
|
|
501
|
+
return 78
|
|
502
|
+
fi
|
|
503
|
+
# Run in a subshell so a `cd` (or a failing command under the relaxed
|
|
504
|
+
# error mode) in one folder never leaks into the next.
|
|
505
|
+
( install_folder_deps "$folder" )
|
|
506
|
+
done < "$RUNTIME_PROJECTS"
|
|
507
|
+
return 0
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
# The detached worker is the only uai-init role that receives project env.
|
|
511
|
+
# Its caller has already used uai-env-exec to perform one authenticated
|
|
512
|
+
# refresh; this role does no editor or Git-control-plane work and exits after
|
|
513
|
+
# the installs. Paths are repeated here because a fresh process cannot inherit
|
|
514
|
+
# the parent's shell locals.
|
|
515
|
+
if [ "$UAI_INIT_ROLE" = dependency-worker ]; then
|
|
516
|
+
deps_state_dir="$WORKSPACE/.moondeck/init"
|
|
517
|
+
deps_status_file="$deps_state_dir/deps-status"
|
|
518
|
+
run_workspace_installs
|
|
519
|
+
exit $?
|
|
520
|
+
fi
|
|
521
|
+
|
|
522
|
+
if [ -d "$WORKSPACE" ] && [ -f "$RUNTIME_PROJECTS" ] \
|
|
523
|
+
&& [ ! -L "$RUNTIME_PROJECTS" ]; then
|
|
524
|
+
deps_state_dir="$WORKSPACE/.moondeck/init"
|
|
525
|
+
deps_status_file="$deps_state_dir/deps-status"
|
|
526
|
+
deps_log_file="$deps_state_dir/deps.log"
|
|
527
|
+
deps_lock_dir="$deps_state_dir/deps.lock.d"
|
|
528
|
+
mkdir -p "$deps_state_dir"
|
|
529
|
+
|
|
530
|
+
# One runner at a time. uai-init is re-runnable (task-up retry, recovery),
|
|
531
|
+
# and each re-run SHOULD refresh dependencies — but never concurrently.
|
|
532
|
+
# mkdir is the portable atomic lock; a pid that no longer answers marks a
|
|
533
|
+
# crashed runner whose lock is stale and may be stolen.
|
|
534
|
+
deps_spawn=1
|
|
535
|
+
if mkdir "$deps_lock_dir" 2>/dev/null; then
|
|
536
|
+
# Hold the lock as ourselves until the runner's pid replaces it, so a
|
|
537
|
+
# concurrent uai-init never reads an empty pid file as a stale lock.
|
|
538
|
+
printf '%s\n' "$$" > "$deps_lock_dir/pid"
|
|
539
|
+
else
|
|
540
|
+
deps_holder=$(cat "$deps_lock_dir/pid" 2>/dev/null || true)
|
|
541
|
+
if [ -n "$deps_holder" ] && kill -0 "$deps_holder" 2>/dev/null; then
|
|
542
|
+
log "dependency install already running (pid $deps_holder) — leaving it be"
|
|
543
|
+
deps_spawn=0
|
|
544
|
+
else
|
|
545
|
+
rm -rf "$deps_lock_dir"
|
|
546
|
+
if mkdir "$deps_lock_dir" 2>/dev/null; then
|
|
547
|
+
printf '%s\n' "$$" > "$deps_lock_dir/pid"
|
|
548
|
+
else
|
|
549
|
+
log "warning: could not acquire the dependency install lock — skipping installs"
|
|
550
|
+
deps_spawn=0
|
|
551
|
+
fi
|
|
552
|
+
fi
|
|
553
|
+
fi
|
|
554
|
+
if [ "$deps_spawn" -eq 1 ]; then
|
|
555
|
+
printf 'running\n' > "$deps_status_file"
|
|
556
|
+
log "installing workspace dependencies in the background (status: $deps_status_file, log: $deps_log_file)"
|
|
557
|
+
(
|
|
558
|
+
trap 'rm -rf "$deps_lock_dir"' EXIT
|
|
559
|
+
deps_rc=0
|
|
560
|
+
if [ "${UAI_ENV_MODE:-baked}" = loader ]; then
|
|
561
|
+
UAI_ENV_HOOK=1 \
|
|
562
|
+
BASH_ENV="$UAI_ENV_HOOK_FILE" \
|
|
563
|
+
SHELL=/usr/bin/zsh \
|
|
564
|
+
"$UAI_ENV_EXEC" "$UAI_INIT_SELF" --dependency-worker \
|
|
565
|
+
|| deps_rc=$?
|
|
566
|
+
else
|
|
567
|
+
run_workspace_installs || deps_rc=$?
|
|
568
|
+
fi
|
|
569
|
+
if [ "$deps_rc" -eq 0 ]; then
|
|
570
|
+
printf 'done\n' > "$deps_status_file"
|
|
571
|
+
log "workspace dependency install complete"
|
|
572
|
+
elif [ "$deps_rc" -ne 78 ]; then
|
|
573
|
+
printf 'failed\n' > "$deps_status_file"
|
|
574
|
+
log "workspace dependency install failed (exit $deps_rc)"
|
|
575
|
+
fi
|
|
576
|
+
) >>"$deps_log_file" 2>&1 </dev/null &
|
|
577
|
+
printf '%s\n' "$!" > "$deps_lock_dir/pid"
|
|
578
|
+
disown
|
|
579
|
+
fi
|
|
580
|
+
elif [ ! -d "$WORKSPACE" ]; then
|
|
581
|
+
log "workspace $WORKSPACE missing — scratchpad/no-project task, skipping installs"
|
|
582
|
+
else
|
|
583
|
+
log "warning: trusted runtime-project allowlist missing or unsafe — skipping installs"
|
|
584
|
+
fi
|
|
585
|
+
|
|
586
|
+
reject_workspace_runtime_shadow || exit 78
|
|
587
|
+
|
|
588
|
+
# ---------------------------------------------------------------------------
|
|
589
|
+
# 2. code-server — the Editor pane. Bound to 0.0.0.0:8080, auth disabled
|
|
590
|
+
# (reached only through the authenticated tunnel / editor proxy). Opens
|
|
591
|
+
# /workspace so all worktrees are visible at the top level. Idempotent:
|
|
592
|
+
# skips launch if already running.
|
|
593
|
+
# ---------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
editor_root="$WORKSPACE"
|
|
596
|
+
[ -d "$editor_root" ] || editor_root="/home/node"
|
|
597
|
+
|
|
598
|
+
# Keep code-server on Uai's managed data path even when a project declares
|
|
599
|
+
# XDG_DATA_HOME. Otherwise code-server starts with a fresh profile elsewhere,
|
|
600
|
+
# which restores the light theme and Workspace Trust prompt.
|
|
601
|
+
cs_data_dir="$HOME/.local/share/code-server"
|
|
602
|
+
cs_user_dir="$cs_data_dir/User"
|
|
603
|
+
cs_seed="/usr/local/share/uai/code-server-settings.json"
|
|
604
|
+
# The source-tree sibling keeps direct smoke tests and development runs on the
|
|
605
|
+
# same seed as the packaged image; the image always uses the root-owned path.
|
|
606
|
+
cs_source_seed="$(dirname "${BASH_SOURCE[0]}")/code-server-settings.json"
|
|
607
|
+
if [ ! -f "$cs_seed" ] && [ -f "$cs_source_seed" ]; then
|
|
608
|
+
cs_seed="$cs_source_seed"
|
|
609
|
+
fi
|
|
610
|
+
|
|
611
|
+
# ADR-091: seed the Editor pane in the palette the task was created from.
|
|
612
|
+
# UAI_EDITOR_THEME carries a VS Code theme LABEL ("Quiet Light"), already
|
|
613
|
+
# resolved cloud-side from the theme registry — the container never maps a Uai
|
|
614
|
+
# palette id and never sees a raw user string. Unset is the normal state for a
|
|
615
|
+
# task created before this shipped or outside a browser; the seed's own baked
|
|
616
|
+
# default then stands.
|
|
617
|
+
#
|
|
618
|
+
# Applied ONLY while seeding. An existing settings.json is still left entirely
|
|
619
|
+
# alone, so a user's own editor tweaks survive a restart — which is also why
|
|
620
|
+
# changing the app's palette later does not re-theme an already-created task
|
|
621
|
+
# (docs/theming.md). If jq is missing or the filter fails, fall through to the
|
|
622
|
+
# plain copy: a themed editor is worth less than an editor that starts.
|
|
623
|
+
seed_code_server_settings() {
|
|
624
|
+
local seed="$1" dest="$2" theme="${UAI_EDITOR_THEME:-}"
|
|
625
|
+
if [ -n "$theme" ] && command -v jq >/dev/null 2>&1; then
|
|
626
|
+
if jq --arg theme "$theme" '.["workbench.colorTheme"] = $theme' \
|
|
627
|
+
"$seed" >"$dest.tmp" 2>/dev/null && [ -s "$dest.tmp" ]; then
|
|
628
|
+
mv "$dest.tmp" "$dest"
|
|
629
|
+
log "seeded code-server settings (theme: $theme)"
|
|
630
|
+
return 0
|
|
631
|
+
fi
|
|
632
|
+
rm -f "$dest.tmp"
|
|
633
|
+
log "warning: could not apply UAI_EDITOR_THEME=$theme; using image default"
|
|
634
|
+
fi
|
|
635
|
+
cp "$seed" "$dest" || return 1
|
|
636
|
+
log "seeded code-server settings"
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if [ ! -f "$cs_user_dir/settings.json" ]; then
|
|
640
|
+
if [ ! -f "$cs_seed" ]; then
|
|
641
|
+
log "warning: code-server settings seed missing at $cs_seed"
|
|
642
|
+
elif mkdir -p "$cs_user_dir" &&
|
|
643
|
+
seed_code_server_settings "$cs_seed" "$cs_user_dir/settings.json"; then
|
|
644
|
+
:
|
|
645
|
+
else
|
|
646
|
+
log "warning: could not seed code-server settings at $cs_user_dir/settings.json"
|
|
647
|
+
fi
|
|
648
|
+
fi
|
|
649
|
+
|
|
650
|
+
if pgrep -f 'code-server.*--bind-addr' >/dev/null 2>&1; then
|
|
651
|
+
log "code-server already running"
|
|
652
|
+
else
|
|
653
|
+
log "launching code-server on 0.0.0.0:8080"
|
|
654
|
+
# Compose deliberately replaces the image PATH with Uai's runtime authority
|
|
655
|
+
# path, which excludes task-writable home bins. code-server is an image-owned
|
|
656
|
+
# standalone install, so invoke its exact baked path instead of relying on
|
|
657
|
+
# PATH. A source-tree test invocation may resolve its isolated stub, but the
|
|
658
|
+
# two production destinations never consult PATH for this executable.
|
|
659
|
+
code_server_bin=/home/node/.local/bin/code-server
|
|
660
|
+
case "${BASH_SOURCE[0]}" in
|
|
661
|
+
/usr/local/bin/uai-init | /uai-init-current) ;;
|
|
662
|
+
*)
|
|
663
|
+
test_code_server_bin=$(command -v code-server 2>/dev/null || true)
|
|
664
|
+
[ -z "$test_code_server_bin" ] || code_server_bin="$test_code_server_bin"
|
|
665
|
+
;;
|
|
666
|
+
esac
|
|
667
|
+
code_server_env=()
|
|
668
|
+
if [ "${UAI_ENV_MODE:-baked}" = loader ]; then
|
|
669
|
+
if [ -L "$MANAGED_ENV_KEYS" ] || [ ! -f "$MANAGED_ENV_KEYS" ]; then
|
|
670
|
+
log "error: managed env key list is unavailable; refusing editor startup"
|
|
671
|
+
exit 78
|
|
672
|
+
fi
|
|
673
|
+
code_server_env=(/usr/bin/env)
|
|
674
|
+
while IFS= read -r managed_name || [ -n "$managed_name" ]; do
|
|
675
|
+
case $managed_name in
|
|
676
|
+
''|[!A-Za-z_]*|*[!A-Za-z0-9_]*)
|
|
677
|
+
log "error: managed env key list is invalid; refusing editor startup"
|
|
678
|
+
exit 78
|
|
679
|
+
;;
|
|
680
|
+
esac
|
|
681
|
+
code_server_env+=( -u "$managed_name" )
|
|
682
|
+
done < "$MANAGED_ENV_KEYS"
|
|
683
|
+
code_server_env+=(
|
|
684
|
+
-u __UAI_ENV_KEYS
|
|
685
|
+
-u __UAI_ENV_ETAG
|
|
686
|
+
UAI_ENV_HOOK=1
|
|
687
|
+
"BASH_ENV=$UAI_ENV_HOOK_FILE"
|
|
688
|
+
SHELL=/usr/bin/zsh
|
|
689
|
+
)
|
|
690
|
+
fi
|
|
691
|
+
nohup "${code_server_env[@]}" "$code_server_bin" \
|
|
692
|
+
--user-data-dir "$cs_data_dir" \
|
|
693
|
+
--disable-workspace-trust \
|
|
694
|
+
--auth none \
|
|
695
|
+
--disable-telemetry \
|
|
696
|
+
--disable-update-check \
|
|
697
|
+
--bind-addr 0.0.0.0:8080 \
|
|
698
|
+
"$editor_root" \
|
|
699
|
+
>/tmp/code-server.log 2>&1 &
|
|
700
|
+
disown
|
|
701
|
+
fi
|
|
702
|
+
|
|
703
|
+
# Always succeed: the container is up and code-server has been (re)launched.
|
|
704
|
+
exit 0
|