@moondeck/station 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +131 -4
- 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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent adapter registry (ADR-021).
|
|
3
|
+
*
|
|
4
|
+
* A small module-level registry so adding an agent kind is mechanical and
|
|
5
|
+
* needs no uai-core change: an adapter module calls `register()` at load,
|
|
6
|
+
* declaring its `kind`, display `label`, the models it can drive, an
|
|
7
|
+
* optional `defaultModel`, and a `create()` that builds an `AgentSession`.
|
|
8
|
+
*
|
|
9
|
+
* The host derives two things from the registry:
|
|
10
|
+
* - `factoryFor(kind)` — used by the real agent factory to construct a
|
|
11
|
+
* session for a roster entry by its `kind`.
|
|
12
|
+
* - `capabilities()` — the `agentKinds` slice of the `host.capabilities`
|
|
13
|
+
* frame, computed straight from the registered adapters.
|
|
14
|
+
*
|
|
15
|
+
* Registration is side-effect-on-import: importing `./claude` and `./codex`
|
|
16
|
+
* runs their `register()` calls (see `factory.ts`, which imports them).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { AgentSessionFactory } from "./types";
|
|
20
|
+
|
|
21
|
+
/** A registered agent adapter — metadata plus the session factory. */
|
|
22
|
+
export interface RegisteredAdapter {
|
|
23
|
+
/** Host-advertised agent kind ("claude" | "codex" | "gemini" | …). */
|
|
24
|
+
kind: string;
|
|
25
|
+
/** Display name for the cloud picker. */
|
|
26
|
+
label: string;
|
|
27
|
+
/** Models this adapter can drive (CLI `--model` / `-c model=` values). */
|
|
28
|
+
supportedModels(): string[];
|
|
29
|
+
/** Preferred model when the user doesn't pick one. */
|
|
30
|
+
defaultModel?: string;
|
|
31
|
+
/** Reasoning levels this adapter can drive (CLI effort values). */
|
|
32
|
+
supportedEfforts(): string[];
|
|
33
|
+
/** Preferred effort when the user doesn't pick one. */
|
|
34
|
+
defaultEffort?: string;
|
|
35
|
+
/** Restricted profiles this adapter can enforce by construction. */
|
|
36
|
+
executionProfiles?: Array<{
|
|
37
|
+
id: string;
|
|
38
|
+
mechanism: string;
|
|
39
|
+
defaultModel?: string;
|
|
40
|
+
defaultEffort?: string;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Whether this kind is usable on THIS host right now — i.e. its credentials
|
|
44
|
+
* are present (ADR-044 P2). Gates advertisement: an unavailable kind is left
|
|
45
|
+
* out of `capabilities()` so the cloud's task picker doesn't offer an LLM the
|
|
46
|
+
* host can't actually run. Omit (or return true) to always advertise; the
|
|
47
|
+
* session factory stays permissive regardless.
|
|
48
|
+
*/
|
|
49
|
+
available?: () => boolean;
|
|
50
|
+
/** Builds an AgentSession for a roster entry of this kind. */
|
|
51
|
+
create: AgentSessionFactory["create"];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** One entry of the `host.capabilities` frame's `agentKinds`. */
|
|
55
|
+
export interface AgentKindCapability {
|
|
56
|
+
kind: string;
|
|
57
|
+
label: string;
|
|
58
|
+
supportedModels: string[];
|
|
59
|
+
defaultModel?: string;
|
|
60
|
+
supportedEfforts: string[];
|
|
61
|
+
defaultEffort?: string;
|
|
62
|
+
executionProfiles?: Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
mechanism: string;
|
|
65
|
+
defaultModel?: string;
|
|
66
|
+
defaultEffort?: string;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const adapters = new Map<string, RegisteredAdapter>();
|
|
71
|
+
|
|
72
|
+
/** Listeners fired after any registry mutation (manual re-advertise hook). */
|
|
73
|
+
const changeListeners = new Set<() => void>();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ADR-131 D3: a second source of "usable on this host" beyond the adapter's
|
|
77
|
+
* own `available()`, which only sees the host's default credential. The host
|
|
78
|
+
* agent installs the engine-account store here so a kind with a stored
|
|
79
|
+
* (workspace- or host-scoped) account is advertised even when the owner never
|
|
80
|
+
* signed in to it. Installed by main.ts; absent in unit tests.
|
|
81
|
+
*/
|
|
82
|
+
let advertisementFallback: (kind: string) => boolean = () => false;
|
|
83
|
+
|
|
84
|
+
export function setAdvertisementFallback(
|
|
85
|
+
fallback: ((kind: string) => boolean) | null,
|
|
86
|
+
): void {
|
|
87
|
+
advertisementFallback = fallback ?? (() => false);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* ADR-154 §10: on cloud credentials the runner holds no engine credential of
|
|
92
|
+
* its own, so availability and the fallback above say nothing: the kinds a
|
|
93
|
+
* leased task can run are advertised, and only those (`CLOUD_CREDENTIAL_KINDS`:
|
|
94
|
+
* the leased lanes and the Managed Engine). Null while the runner runs its
|
|
95
|
+
* tasks on its own credentials. Installed by main.ts.
|
|
96
|
+
*/
|
|
97
|
+
let leasedKinds: () => ReadonlySet<string> | null = () => null;
|
|
98
|
+
|
|
99
|
+
export function setLeasedAdvertisement(
|
|
100
|
+
source: (() => ReadonlySet<string> | null) | null,
|
|
101
|
+
): void {
|
|
102
|
+
leasedKinds = source ?? (() => null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Register (or replace) an adapter for a kind. Called at module load by the
|
|
107
|
+
* adapter modules. Replacing is allowed so a later import wins deterministically.
|
|
108
|
+
*/
|
|
109
|
+
export function register(adapter: RegisteredAdapter): void {
|
|
110
|
+
adapters.set(adapter.kind, adapter);
|
|
111
|
+
for (const listener of changeListeners) listener();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Every registered adapter, in insertion order. */
|
|
115
|
+
export function list(): RegisteredAdapter[] {
|
|
116
|
+
return [...adapters.values()];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The session factory for a kind, or undefined if no adapter is registered. */
|
|
120
|
+
export function factoryFor(kind: string): AgentSessionFactory | undefined {
|
|
121
|
+
const adapter = adapters.get(kind);
|
|
122
|
+
return adapter ? { create: adapter.create } : undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Whether the adapter declares an engine-enforced execution profile. */
|
|
126
|
+
export function supportsExecutionProfile(kind: string, profileId: string): boolean {
|
|
127
|
+
return Boolean(
|
|
128
|
+
adapters
|
|
129
|
+
.get(kind)
|
|
130
|
+
?.executionProfiles?.some((profile) => profile.id === profileId),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The `agentKinds` capability slice, derived from the registered adapters.
|
|
136
|
+
* Each adapter's `supportedModels()` / `supportedEfforts()` is evaluated here.
|
|
137
|
+
*/
|
|
138
|
+
export function capabilities(): AgentKindCapability[] {
|
|
139
|
+
const leased = leasedKinds();
|
|
140
|
+
return list()
|
|
141
|
+
.filter((adapter) =>
|
|
142
|
+
leased !== null
|
|
143
|
+
? leased.has(adapter.kind)
|
|
144
|
+
: (adapter.available?.() ?? true) || advertisementFallback(adapter.kind),
|
|
145
|
+
)
|
|
146
|
+
.map((adapter) => {
|
|
147
|
+
const out: AgentKindCapability = {
|
|
148
|
+
kind: adapter.kind,
|
|
149
|
+
label: adapter.label,
|
|
150
|
+
supportedModels: adapter.supportedModels(),
|
|
151
|
+
supportedEfforts: adapter.supportedEfforts(),
|
|
152
|
+
};
|
|
153
|
+
if (adapter.defaultModel !== undefined) {
|
|
154
|
+
out.defaultModel = adapter.defaultModel;
|
|
155
|
+
}
|
|
156
|
+
if (adapter.defaultEffort !== undefined) {
|
|
157
|
+
out.defaultEffort = adapter.defaultEffort;
|
|
158
|
+
}
|
|
159
|
+
if (adapter.executionProfiles && adapter.executionProfiles.length > 0) {
|
|
160
|
+
out.executionProfiles = adapter.executionProfiles.map((profile) => ({
|
|
161
|
+
...profile,
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Subscribe to registry changes (adapter added/replaced). Returns an
|
|
170
|
+
* unsubscribe. Used to re-send the capability frame on change.
|
|
171
|
+
*/
|
|
172
|
+
export function onChange(listener: () => void): () => void {
|
|
173
|
+
changeListeners.add(listener);
|
|
174
|
+
return () => changeListeners.delete(listener);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Test/maintenance hook: drop all adapters and listeners. */
|
|
178
|
+
export function reset(): void {
|
|
179
|
+
adapters.clear();
|
|
180
|
+
changeListeners.clear();
|
|
181
|
+
advertisementFallback = () => false;
|
|
182
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable agent-session state used by live roster reconciliation.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator normally owns a live AgentSession and closes it through the
|
|
5
|
+
* adapter. After a host restart, however, a removed agent can exist only as a
|
|
6
|
+
* host_agent_sessions row. These helpers stop that persisted runner
|
|
7
|
+
* without deleting its row or generation directory, and clear Codex's durable
|
|
8
|
+
* thread id when launch settings change.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import { isOwnedInternalConnectionRuntime } from "../internal-connections";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { and, eq } from "drizzle-orm";
|
|
15
|
+
|
|
16
|
+
import { getDb, schema } from "../db";
|
|
17
|
+
import { taskWorkspaceDir } from "../env";
|
|
18
|
+
import {
|
|
19
|
+
appendContainedFile,
|
|
20
|
+
hostPathParts,
|
|
21
|
+
WorkspaceDirectoryMissingError,
|
|
22
|
+
} from "../task-environment/workspace-files";
|
|
23
|
+
import type { TaskEnvironmentHandle } from "../task-environment/types";
|
|
24
|
+
import { isEnvironmentTransportProvider } from "../task-environment/types";
|
|
25
|
+
import {
|
|
26
|
+
stopMachinePredecessorStrict,
|
|
27
|
+
type MachineSessionEnvironment,
|
|
28
|
+
} from "./machine-durable";
|
|
29
|
+
|
|
30
|
+
/** Durable-session ids persisted for one task, including rows marked closed.
|
|
31
|
+
* A crash can land after markClosed but before its stop reaches the runner. */
|
|
32
|
+
export function persistedAgentIds(taskId: string): string[] {
|
|
33
|
+
return getDb()
|
|
34
|
+
.select({ agentId: schema.hostAgentSessions.agentId })
|
|
35
|
+
.from(schema.hostAgentSessions)
|
|
36
|
+
.where(eq(schema.hostAgentSessions.taskId, taskId))
|
|
37
|
+
.all()
|
|
38
|
+
.map((row) => row.agentId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A changed model/engine/effort starts a fresh Codex thread. The channel
|
|
43
|
+
* transcript is the durable memory; resuming a thread created under the old
|
|
44
|
+
* launch settings can make the replacement keep using the old model.
|
|
45
|
+
*/
|
|
46
|
+
export function clearPersistedCodexThread(
|
|
47
|
+
taskId: string,
|
|
48
|
+
agentId: string,
|
|
49
|
+
): void {
|
|
50
|
+
getDb()
|
|
51
|
+
.update(schema.hostAgentSessions)
|
|
52
|
+
.set({ codexThreadId: null, updatedAt: Date.now() })
|
|
53
|
+
.where(
|
|
54
|
+
and(
|
|
55
|
+
eq(schema.hostAgentSessions.taskId, taskId),
|
|
56
|
+
eq(schema.hostAgentSessions.agentId, agentId),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
.run();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Stop a persisted runner that has no in-memory AgentSession (boot recovery).
|
|
64
|
+
* The session directory and DB row deliberately remain for diagnostics.
|
|
65
|
+
*/
|
|
66
|
+
export async function stopPersistedAgentSession(
|
|
67
|
+
taskId: string,
|
|
68
|
+
agentId: string,
|
|
69
|
+
environment: TaskEnvironmentHandle<unknown>,
|
|
70
|
+
): Promise<string | null> {
|
|
71
|
+
const db = getDb();
|
|
72
|
+
const row = db
|
|
73
|
+
.select()
|
|
74
|
+
.from(schema.hostAgentSessions)
|
|
75
|
+
.where(
|
|
76
|
+
and(
|
|
77
|
+
eq(schema.hostAgentSessions.taskId, taskId),
|
|
78
|
+
eq(schema.hostAgentSessions.agentId, agentId),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
.get();
|
|
82
|
+
if (!row) return null;
|
|
83
|
+
|
|
84
|
+
if (isEnvironmentTransportProvider(environment.descriptor.locator.provider)) {
|
|
85
|
+
await stopMachinePredecessorStrict(
|
|
86
|
+
environment as MachineSessionEnvironment,
|
|
87
|
+
row.sessionDir,
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
// Never through a link the task put in the session's place (#cb3j0z).
|
|
91
|
+
const root = taskWorkspaceDir(taskId);
|
|
92
|
+
let parts: string[] | null;
|
|
93
|
+
try {
|
|
94
|
+
parts = hostPathParts(root, row.sessionDir);
|
|
95
|
+
} catch {
|
|
96
|
+
// Not below the task's workspace: no runner of this task reads it.
|
|
97
|
+
parts = null;
|
|
98
|
+
}
|
|
99
|
+
if (parts !== null) {
|
|
100
|
+
try {
|
|
101
|
+
await appendContainedFile(root, [...parts, "inbox.jsonl"], Buffer.from('{"__uai":"stop"}\n'));
|
|
102
|
+
} catch (err) {
|
|
103
|
+
// A removed generation directory proves there is no runner left to
|
|
104
|
+
// read it. Permission, I/O, and other failures are not proof: retry
|
|
105
|
+
// them.
|
|
106
|
+
if (!(err instanceof WorkspaceDirectoryMissingError)) throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
db.update(schema.hostAgentSessions)
|
|
112
|
+
.set({ status: "closed", updatedAt: Date.now() })
|
|
113
|
+
.where(
|
|
114
|
+
and(
|
|
115
|
+
eq(schema.hostAgentSessions.taskId, taskId),
|
|
116
|
+
eq(schema.hostAgentSessions.agentId, agentId),
|
|
117
|
+
eq(schema.hostAgentSessions.sessionDir, row.sessionDir),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
.run();
|
|
121
|
+
return row.sessionDir;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Remove internal runner generations only after the caller has proven every
|
|
125
|
+
* covered anchored process stopped. The persisted compatibility key survives
|
|
126
|
+
* restart and binds this deletion to the run and credential being cleaned. */
|
|
127
|
+
export async function purgeInternalAgentSessionState(
|
|
128
|
+
request: { taskId: string; runEpoch: number; credentialIds?: readonly string[] },
|
|
129
|
+
environments: readonly TaskEnvironmentHandle<unknown>[],
|
|
130
|
+
removeDirectory: (root: string, path: string) => Promise<void> = removeInternalSessionDirectory,
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const db = getDb();
|
|
133
|
+
const rows = db.select().from(schema.hostAgentSessions).where(eq(schema.hostAgentSessions.taskId, request.taskId)).all();
|
|
134
|
+
for (const row of rows) {
|
|
135
|
+
let key: unknown;
|
|
136
|
+
try { key = JSON.parse(row.attachCompatibilityKey ?? "null"); } catch { continue; }
|
|
137
|
+
if (!Array.isArray(key) || key[1] !== "lease-v1" || !Number.isSafeInteger(key[2]) || key[2] > request.runEpoch || key.at(-1) !== "custom-internal" ||
|
|
138
|
+
(request.credentialIds && !request.credentialIds.includes(key[3] as string))) continue;
|
|
139
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(row.agentId)) throw new Error("unsupported");
|
|
140
|
+
if (!environments.length) throw new Error("unsupported");
|
|
141
|
+
// Owned-runtime workspaces are host mounts; they persist even after the
|
|
142
|
+
// container disappears. Delete through host directory FDs, not exec into
|
|
143
|
+
// a container which may already be stopped/absent.
|
|
144
|
+
if (environments.some(e => e.descriptor.taskId !== request.taskId || !isOwnedInternalConnectionRuntime(e.descriptor.locator.provider))) throw new Error("unsupported");
|
|
145
|
+
const root = taskWorkspaceDir(request.taskId);
|
|
146
|
+
await removeDirectory(root, `${root}/.moondeck/sessions/${row.agentId}`);
|
|
147
|
+
db.delete(schema.hostAgentSessions).where(and(eq(schema.hostAgentSessions.taskId, row.taskId), eq(schema.hostAgentSessions.agentId, row.agentId), eq(schema.hostAgentSessions.sessionDir, row.sessionDir))).run();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** FD-relative tree removal. A task can
|
|
152
|
+
* replace entries concurrently; links are never followed, and a race fails
|
|
153
|
+
* closed for the cleanup coordinator to retry. No provider text is read. */
|
|
154
|
+
export const INTERNAL_SESSION_UNLINK_HELPER = String.raw`
|
|
155
|
+
import os, stat, sys
|
|
156
|
+
root, absolute = sys.argv[1:3]
|
|
157
|
+
prefix = root.rstrip("/") + "/"
|
|
158
|
+
if not absolute.startswith(prefix): raise RuntimeError()
|
|
159
|
+
parts = absolute[len(prefix):].split("/")
|
|
160
|
+
if not parts or any(p in ("", ".", "..") for p in parts): raise RuntimeError()
|
|
161
|
+
flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
|
|
162
|
+
fds = []
|
|
163
|
+
def remove(parent, name):
|
|
164
|
+
try: info = os.stat(name, dir_fd=parent, follow_symlinks=False)
|
|
165
|
+
except FileNotFoundError: return
|
|
166
|
+
if not stat.S_ISDIR(info.st_mode): raise RuntimeError()
|
|
167
|
+
child = os.open(name, flags, dir_fd=parent)
|
|
168
|
+
try:
|
|
169
|
+
for entry in os.listdir(child):
|
|
170
|
+
info = os.stat(entry, dir_fd=child, follow_symlinks=False)
|
|
171
|
+
if stat.S_ISDIR(info.st_mode): remove(child, entry)
|
|
172
|
+
else: os.unlink(entry, dir_fd=child)
|
|
173
|
+
finally: os.close(child)
|
|
174
|
+
os.rmdir(name, dir_fd=parent)
|
|
175
|
+
try:
|
|
176
|
+
current = os.open("/", flags); fds.append(current)
|
|
177
|
+
for p in root.split("/") + parts[:-1]:
|
|
178
|
+
if not p: continue
|
|
179
|
+
current = os.open(p, flags, dir_fd=current); fds.append(current)
|
|
180
|
+
remove(current, parts[-1])
|
|
181
|
+
try: os.stat(parts[-1], dir_fd=current, follow_symlinks=False)
|
|
182
|
+
except FileNotFoundError: sys.exit(0)
|
|
183
|
+
raise RuntimeError()
|
|
184
|
+
except FileNotFoundError: sys.exit(0)
|
|
185
|
+
finally:
|
|
186
|
+
for fd in reversed(fds): os.close(fd)
|
|
187
|
+
`;
|
|
188
|
+
function removeInternalSessionDirectory(root: string, path: string): Promise<void> {
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
const proc = spawn("/usr/bin/python3", ["-I", "-", root, path], { stdio: ["pipe", "ignore", "ignore"], env: { PATH: "/usr/bin:/bin" } });
|
|
191
|
+
const timer = setTimeout(() => { proc.kill("SIGKILL"); reject(new Error("unsupported")); }, 10_000);
|
|
192
|
+
proc.on("error", () => { clearTimeout(timer); reject(new Error("unsupported")); });
|
|
193
|
+
proc.on("exit", code => { clearTimeout(timer); if (code === 0) resolve(); else reject(new Error("unsupported")); });
|
|
194
|
+
proc.stdin.on("error", () => {});
|
|
195
|
+
proc.stdin.end(INTERNAL_SESSION_UNLINK_HELPER);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-166: the preamble an indexed agent's engine receives, from the real
|
|
3
|
+
* builder, for the per-engine adapter tests (claude, codex, grok).
|
|
4
|
+
*/
|
|
5
|
+
import { buildSystemPreamble } from "../orchestrator";
|
|
6
|
+
import type { RosterAgent } from "./types";
|
|
7
|
+
|
|
8
|
+
export const INDEXED_SKILLS: NonNullable<RosterAgent["skills"]> = [
|
|
9
|
+
{ name: "Brand voice", type: "document", value: "Plain and warm." },
|
|
10
|
+
{ name: "Design tokens", type: "document", value: "The scales." },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/** The skills pointer every engine must carry for an indexed agent. */
|
|
14
|
+
export const INDEX_POINTER = /Your skills are indexed at `\/workspace\/\.moondeck\/agents\/[\w-]+\/SKILL\.md`: "Brand voice" and "Design tokens"\. Read the index at the start\. Before work a skill covers, or an action the index names, read that file in full with `cat`/;
|
|
15
|
+
|
|
16
|
+
export function indexedPreamble(agent: RosterAgent): string {
|
|
17
|
+
const withSkills = { ...agent, skills: INDEXED_SKILLS };
|
|
18
|
+
return buildSystemPreamble(
|
|
19
|
+
[withSkills],
|
|
20
|
+
withSkills,
|
|
21
|
+
[{ slug: "repo", defaultPrompt: "" } as never],
|
|
22
|
+
undefined,
|
|
23
|
+
"/workspace",
|
|
24
|
+
"task/x",
|
|
25
|
+
);
|
|
26
|
+
}
|