@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,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-117: one-shot engine connect over the bridge.
|
|
3
|
+
*
|
|
4
|
+
* Much simpler than engine-login: nothing is spawned in a container. With a
|
|
5
|
+
* `secret` this runs the SAME paste paths the local UI's connect uses
|
|
6
|
+
* (`connectEngine` with pastedToken/apiKey, or the ADR-116 labeled-account
|
|
7
|
+
* sink); without one it runs OpenCode's re-detect. The secret arrives in the
|
|
8
|
+
* start frame, goes straight into `connectEngine`/`addEngineAccount`, and is
|
|
9
|
+
* never logged or echoed into any event.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
HOST_ACCOUNT_SCOPE,
|
|
14
|
+
isEngineAccountLabel,
|
|
15
|
+
isEngineAccountScope,
|
|
16
|
+
isHostOpId,
|
|
17
|
+
MAX_ENGINE_CONNECT_LINE_CHARS,
|
|
18
|
+
MAX_ENGINE_CONNECT_SECRET_CHARS,
|
|
19
|
+
type EngineConnectEventFrame,
|
|
20
|
+
type EngineConnectKind,
|
|
21
|
+
} from "../src/protocol";
|
|
22
|
+
import type { CaptureFlowWire, CaptureResultWire } from "../src/cloud-credentials";
|
|
23
|
+
import type { CaptureOutcome, EngineCaptureMaterial } from "./capture-flow";
|
|
24
|
+
import { addEngineAccount, pastedSecretMaterial } from "./engine-accounts";
|
|
25
|
+
import { connectEngine, type EngineKind } from "./engines";
|
|
26
|
+
|
|
27
|
+
const CONNECT_TIMEOUT_MS = 2 * 60_000;
|
|
28
|
+
// Connects are one store write each; labeled (workspace) connects never touch
|
|
29
|
+
// a shared slot, so they run side by side — a user is never told to wait for
|
|
30
|
+
// someone else's connect (ADR-136 sign-in box). Bounded, not serialised.
|
|
31
|
+
const MAX_CONCURRENT_CONNECTS = 16;
|
|
32
|
+
|
|
33
|
+
export type EmitEngineConnectEvent = (frame: EngineConnectEventFrame) => void;
|
|
34
|
+
|
|
35
|
+
export interface AddAccountOutcome {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
message: string;
|
|
38
|
+
id?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface EngineConnectManagerOptions {
|
|
42
|
+
/** Re-advertise capabilities after a successful mutation (main.ts). */
|
|
43
|
+
readvertise(): void;
|
|
44
|
+
/** Refresh running agents of a kind after a DEFAULT credential change. */
|
|
45
|
+
refreshEngineAccountAgents(kind: string): Promise<void>;
|
|
46
|
+
/** Best-effort post-connect image maintenance (optional engines bake in). */
|
|
47
|
+
ensureStandardImage(): Promise<unknown>;
|
|
48
|
+
/** ADR-136 sign-in box: only labeled, workspace-scoped connects are
|
|
49
|
+
* admitted — nothing here may write the host's default slot. */
|
|
50
|
+
workspaceOnly?: boolean;
|
|
51
|
+
/** The labeled-account sink. Defaults to the local `addEngineAccount`; the
|
|
52
|
+
* sign-in box supplies the store-first one and awaits its write. */
|
|
53
|
+
addLabeledAccount?: (
|
|
54
|
+
kind: EngineConnectKind,
|
|
55
|
+
label: string,
|
|
56
|
+
secret: string,
|
|
57
|
+
scope: string,
|
|
58
|
+
) => AddAccountOutcome | Promise<AddAccountOutcome>;
|
|
59
|
+
/** ADR-154: the sign-in box's sink for a capture flow. Absent elsewhere:
|
|
60
|
+
* only the box writes cloud credentials. */
|
|
61
|
+
captureAccount?: (
|
|
62
|
+
flow: CaptureFlowWire,
|
|
63
|
+
kind: EngineConnectKind,
|
|
64
|
+
material: EngineCaptureMaterial,
|
|
65
|
+
) => Promise<CaptureOutcome>;
|
|
66
|
+
timers?: {
|
|
67
|
+
setTimeout(handler: () => void, ms: number): NodeJS.Timeout;
|
|
68
|
+
clearTimeout(timer: NodeJS.Timeout): void;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface ConnectOperation {
|
|
73
|
+
opId: string;
|
|
74
|
+
engine: EngineConnectKind;
|
|
75
|
+
emit: EmitEngineConnectEvent;
|
|
76
|
+
done: boolean;
|
|
77
|
+
timer: NodeJS.Timeout | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class EngineConnectManager {
|
|
81
|
+
private readonly operations = new Map<string, ConnectOperation>();
|
|
82
|
+
/** Each connect's work, which goes on writing (a key, an account) after
|
|
83
|
+
* its operation is finished or stopped. */
|
|
84
|
+
private readonly runs = new Set<Promise<void>>();
|
|
85
|
+
|
|
86
|
+
constructor(private readonly options: EngineConnectManagerOptions) {}
|
|
87
|
+
|
|
88
|
+
start(
|
|
89
|
+
opId: string,
|
|
90
|
+
engine: EngineConnectKind,
|
|
91
|
+
secret: string | undefined,
|
|
92
|
+
label: string | undefined,
|
|
93
|
+
emit: EmitEngineConnectEvent,
|
|
94
|
+
scope: string | undefined = undefined,
|
|
95
|
+
flow: CaptureFlowWire | undefined = undefined,
|
|
96
|
+
): boolean {
|
|
97
|
+
if (!isHostOpId(opId) || this.operations.has(opId)) return false;
|
|
98
|
+
if (scope !== undefined && !isEngineAccountScope(scope)) return false;
|
|
99
|
+
if (
|
|
100
|
+
secret !== undefined &&
|
|
101
|
+
(secret.length < 1 || secret.length > MAX_ENGINE_CONNECT_SECRET_CHARS)
|
|
102
|
+
) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (label !== undefined && !isEngineAccountLabel(label)) return false;
|
|
106
|
+
if (this.operations.size >= MAX_CONCURRENT_CONNECTS) {
|
|
107
|
+
safeEmit(emit, {
|
|
108
|
+
kind: "engine.connect.event",
|
|
109
|
+
opId,
|
|
110
|
+
engine,
|
|
111
|
+
phase: "done",
|
|
112
|
+
ok: false,
|
|
113
|
+
message: "Too many engine connects are running on this station right now; try again in a minute.",
|
|
114
|
+
});
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
const operation: ConnectOperation = {
|
|
118
|
+
opId,
|
|
119
|
+
engine,
|
|
120
|
+
emit,
|
|
121
|
+
done: false,
|
|
122
|
+
timer: null,
|
|
123
|
+
};
|
|
124
|
+
this.operations.set(opId, operation);
|
|
125
|
+
const timers = this.options.timers ?? {
|
|
126
|
+
setTimeout: (handler: () => void, ms: number) => setTimeout(handler, ms),
|
|
127
|
+
clearTimeout: (timer: NodeJS.Timeout) => clearTimeout(timer),
|
|
128
|
+
};
|
|
129
|
+
operation.timer = timers.setTimeout(() => {
|
|
130
|
+
this.finish(operation, false, "Engine connect timed out.");
|
|
131
|
+
}, CONNECT_TIMEOUT_MS);
|
|
132
|
+
operation.timer.unref?.();
|
|
133
|
+
const running = this.run(operation, secret, label, scope, flow).catch((error: unknown) => {
|
|
134
|
+
this.finish(
|
|
135
|
+
operation,
|
|
136
|
+
false,
|
|
137
|
+
error instanceof Error ? error.message : "Engine connect failed.",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
this.runs.add(running);
|
|
141
|
+
void running.finally(() => this.runs.delete(running));
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Resolves once no connect's work is under way, a stopped one's included:
|
|
146
|
+
* what it writes has been written (ADR-154 §13's leaving waits for it). */
|
|
147
|
+
async idle(): Promise<void> {
|
|
148
|
+
while (this.runs.size > 0) await Promise.allSettled([...this.runs]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
stop(opId: string): void {
|
|
152
|
+
const operation = this.operations.get(opId);
|
|
153
|
+
if (operation) this.finish(operation, false, "Cancelled.");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
stopAll(): void {
|
|
157
|
+
for (const operation of [...this.operations.values()]) {
|
|
158
|
+
this.finish(operation, false, "The connection was replaced.");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async run(
|
|
163
|
+
operation: ConnectOperation,
|
|
164
|
+
secret: string | undefined,
|
|
165
|
+
label: string | undefined,
|
|
166
|
+
scope: string | undefined,
|
|
167
|
+
flow: CaptureFlowWire | undefined,
|
|
168
|
+
): Promise<void> {
|
|
169
|
+
const { engine } = operation;
|
|
170
|
+
// ADR-154: a capture flow writes one revision of a cloud credential, on
|
|
171
|
+
// the sign-in box only, from the pasted secret alone.
|
|
172
|
+
if (flow !== undefined) {
|
|
173
|
+
if (!this.options.workspaceOnly || !this.options.captureAccount) {
|
|
174
|
+
this.finish(operation, false, "Only the Moondeck Cloud sign-in box writes cloud credentials.");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (secret === undefined) {
|
|
178
|
+
this.finish(operation, false, "Paste a token or API key.");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const pasted = pastedSecretMaterial(engine, secret);
|
|
182
|
+
if (!pasted.ok) {
|
|
183
|
+
this.finish(operation, false, pasted.message);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const outcome = await this.options.captureAccount(flow, engine, pasted.material);
|
|
187
|
+
if (operation.done) return;
|
|
188
|
+
this.finish(operation, outcome.ok, outcome.message, undefined, outcome.capture);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const emitLine = (line: string): void => {
|
|
192
|
+
const bounded = line.trim().slice(0, MAX_ENGINE_CONNECT_LINE_CHARS);
|
|
193
|
+
if (!bounded || operation.done) return;
|
|
194
|
+
safeEmit(operation.emit, {
|
|
195
|
+
kind: "engine.connect.event",
|
|
196
|
+
opId: operation.opId,
|
|
197
|
+
engine,
|
|
198
|
+
phase: "line",
|
|
199
|
+
line: bounded,
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
if (secret === undefined && engine !== "opencode") {
|
|
204
|
+
this.finish(
|
|
205
|
+
operation,
|
|
206
|
+
false,
|
|
207
|
+
"This engine needs a credential here, or its sign-in run on the host.",
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (secret !== undefined && engine === "opencode") {
|
|
212
|
+
// saveApiKey has no opencode branch and would claim success without
|
|
213
|
+
// storing anything — OpenCode connects on the host, we only re-detect.
|
|
214
|
+
this.finish(
|
|
215
|
+
operation,
|
|
216
|
+
false,
|
|
217
|
+
"OpenCode signs in on the host (`opencode auth login`); use Check connection here.",
|
|
218
|
+
);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ADR-136 sign-in box: a workspace account or nothing.
|
|
223
|
+
if (
|
|
224
|
+
this.options.workspaceOnly &&
|
|
225
|
+
(label === undefined || scope === undefined || scope === HOST_ACCOUNT_SCOPE)
|
|
226
|
+
) {
|
|
227
|
+
this.finish(
|
|
228
|
+
operation,
|
|
229
|
+
false,
|
|
230
|
+
"The Moondeck Cloud sign-in box connects workspace accounts only.",
|
|
231
|
+
);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// ADR-116 labeled extra account — the sink validates kind support.
|
|
235
|
+
if (label !== undefined && secret !== undefined) {
|
|
236
|
+
const sink =
|
|
237
|
+
this.options.addLabeledAccount ??
|
|
238
|
+
((kind, accountLabel, value, accountScope) =>
|
|
239
|
+
addEngineAccount(kind, accountLabel, { token: value }, {}, accountScope));
|
|
240
|
+
const added = await sink(engine, label, secret, scope ?? HOST_ACCOUNT_SCOPE);
|
|
241
|
+
if (operation.done) return;
|
|
242
|
+
if (added.ok) this.afterMutation(engine, false);
|
|
243
|
+
this.finish(operation, added.ok, added.message, added.id);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// ADR-131 D3: everything below writes the host's DEFAULT slot or re-detects
|
|
247
|
+
// a host-side login — the owner's credential, never a workspace's. A
|
|
248
|
+
// tenant scope must not reach it.
|
|
249
|
+
if (scope !== undefined && scope !== HOST_ACCOUNT_SCOPE) {
|
|
250
|
+
this.finish(
|
|
251
|
+
operation,
|
|
252
|
+
false,
|
|
253
|
+
"Workspace accounts need a label and a pasted secret; the host's own sign-in is not available to workspaces.",
|
|
254
|
+
);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const result = await connectEngine(
|
|
259
|
+
engine as EngineKind,
|
|
260
|
+
secret === undefined
|
|
261
|
+
? {}
|
|
262
|
+
: engine === "claude"
|
|
263
|
+
? { pastedToken: secret }
|
|
264
|
+
: { apiKey: secret },
|
|
265
|
+
emitLine,
|
|
266
|
+
);
|
|
267
|
+
if (result.ok) this.afterMutation(engine, true);
|
|
268
|
+
this.finish(operation, result.ok, result.message);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Mirror the local UI's post-connect behavior (ui/server.ts). */
|
|
272
|
+
private afterMutation(engine: EngineConnectKind, defaultSlot: boolean): void {
|
|
273
|
+
this.options.readvertise();
|
|
274
|
+
void this.options.ensureStandardImage().catch(() => {});
|
|
275
|
+
if (defaultSlot) {
|
|
276
|
+
void this.options.refreshEngineAccountAgents(engine).catch(() => {});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private finish(
|
|
281
|
+
operation: ConnectOperation,
|
|
282
|
+
ok: boolean,
|
|
283
|
+
message: string,
|
|
284
|
+
accountId?: string,
|
|
285
|
+
capture?: CaptureResultWire,
|
|
286
|
+
): void {
|
|
287
|
+
if (operation.done) return;
|
|
288
|
+
operation.done = true;
|
|
289
|
+
if (operation.timer) {
|
|
290
|
+
(this.options.timers ?? { clearTimeout }).clearTimeout(operation.timer);
|
|
291
|
+
}
|
|
292
|
+
this.operations.delete(operation.opId);
|
|
293
|
+
safeEmit(operation.emit, {
|
|
294
|
+
kind: "engine.connect.event",
|
|
295
|
+
opId: operation.opId,
|
|
296
|
+
engine: operation.engine,
|
|
297
|
+
phase: "done",
|
|
298
|
+
ok,
|
|
299
|
+
message,
|
|
300
|
+
...(ok && accountId !== undefined ? { accountId } : {}),
|
|
301
|
+
...(ok && capture !== undefined ? { capture } : {}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function safeEmit(
|
|
307
|
+
emit: EmitEngineConnectEvent,
|
|
308
|
+
frame: EngineConnectEventFrame,
|
|
309
|
+
): void {
|
|
310
|
+
try {
|
|
311
|
+
emit(frame);
|
|
312
|
+
} catch {
|
|
313
|
+
// The bridge socket owns delivery failures.
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function createEngineConnectManager(
|
|
318
|
+
options: EngineConnectManagerOptions,
|
|
319
|
+
): EngineConnectManager {
|
|
320
|
+
return new EngineConnectManager(options);
|
|
321
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ENGINE_ENV_CREDENTIAL_KEYS } from "../src/cloud-credentials";
|
|
2
|
+
|
|
3
|
+
/** Every env-token engine's credential names, of any account kind. */
|
|
4
|
+
export const ENGINE_CREDENTIAL_ENV_NAMES: ReadonlySet<string> = new Set(
|
|
5
|
+
Object.values(ENGINE_ENV_CREDENTIAL_KEYS).flat(),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Whether an exec env entry carries an env-token engine's credential: a name
|
|
10
|
+
* some account kind may set (ENGINE_ENV_CREDENTIAL_KEYS is the one list).
|
|
11
|
+
* ADR-154 §6 never puts such a value on the runner's argv, which every local
|
|
12
|
+
* user can read with `ps`. The docker and Apple providers pass it by name,
|
|
13
|
+
* from the exec client's own environment (cliExecEnv in
|
|
14
|
+
* task-environment/docker.ts). The machine and self providers, whose
|
|
15
|
+
* request environment rides a command line (buildRemoteCommand in
|
|
16
|
+
* machine-exec.ts), carry a leased launch's values in its single-use
|
|
17
|
+
* admission instead (credential-consumers/engine-anchor.ts).
|
|
18
|
+
*/
|
|
19
|
+
export function isEngineCredentialEnvName(name: string): boolean {
|
|
20
|
+
return ENGINE_CREDENTIAL_ENV_NAMES.has(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An agent's task token (ADR-048: its own permissions on the task's API)
|
|
25
|
+
* and the Managed Engine's names for it (ADR-128: the gateway's bearer):
|
|
26
|
+
* bearer credentials as an engine's own are.
|
|
27
|
+
*/
|
|
28
|
+
export const TASK_SECRET_ENV_NAMES: ReadonlySet<string> = new Set([
|
|
29
|
+
"UAI_TASK_TOKEN",
|
|
30
|
+
"UAI_INFERENCE_TOKEN",
|
|
31
|
+
"OPENAI_API_KEY",
|
|
32
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
33
|
+
"UAI_CUSTOM_API_KEY",
|
|
34
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/** Every name whose value never rides a runner's command line. */
|
|
38
|
+
export const LAUNCH_SECRET_ENV_NAMES: ReadonlySet<string> = new Set([
|
|
39
|
+
...ENGINE_CREDENTIAL_ENV_NAMES,
|
|
40
|
+
...TASK_SECRET_ENV_NAMES,
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether an exec env entry carries a launch's secret: an engine's
|
|
45
|
+
* credential, or an agent's task token under any of its names. Like an
|
|
46
|
+
* engine credential, such a value is passed by name on the docker and Apple
|
|
47
|
+
* providers, and carried in a leased launch's admission on the machine and
|
|
48
|
+
* self providers (ADR-154 §6).
|
|
49
|
+
*/
|
|
50
|
+
export function isLaunchSecretEnvName(name: string): boolean {
|
|
51
|
+
return LAUNCH_SECRET_ENV_NAMES.has(name);
|
|
52
|
+
}
|