/** * Upload + release (design §6b; build-plan slice 3.4) — the CLI side of the * marketplace-brokered intake. One public surface: `POST /v1/builder/hosted-agents` * registers (or re-binds) the workspace's hosted agent, `POST /v1/builder/releases` * ships the bundle. The agent id is remembered per workspace in ~/.clustly/config; * a stale binding self-heals (release 404 → re-bind once → retry) so a deploy * after a server-side reset still succeeds without the builder doing anything. */ import { type CliApiDeps } from "./api-client"; import type { ClustlyManifest } from "./manifest"; import type { PackResult } from "./pack"; /** This domain speaks to the API through the shared client (R5). */ export type UploadDeps = CliApiDeps; export interface UploadInput { workspacePath: string; manifest: ClustlyManifest; bundle: PackResult; /** Detected `openclaw --version` — required for the openclaw framework (vetted pin upstream). */ openclawVersion?: string; } export interface UploadResult { agentId: string; releaseId: string; status: string; duplicate: boolean; } /** * The workspace's hosted agent id — remembered, else RECOVERED by name from the server, else * registered (+ bound) on first use. The name step is what makes a clobbered ~/.clustly/config * harmless: before this, a lost `hostedAgents` entry meant a second agent with the same name * (and the listing, secrets and reputation stranded on the first). Exposed on its own for the * wizard's secrets preflight (slice 3.5), which needs the agent to exist BEFORE the release * ships; the later deployRelease reuses the memory. */ export declare function ensureHostedAgent(deps: UploadDeps, workspacePath: string, displayName: string, policy: AdoptPolicy): Promise; /** * How a deploy may take over a live agent found BY NAME (PR #157 review, H2 + re-review). * Recovery by name exists for ONE case — this folder's own mapping was lost — and must never * quietly rebind a DIFFERENT folder onto an agent another folder still owns: that would ship * folder B's bundle as agent A, over A's listing, secrets and reputation. * * Two signals, because each is blind somewhere. The local config sees a collision on THIS * machine; a fresh machine or CI runner has an empty config and cannot. So: * · interactive — adopting an agent found by name ALWAYS takes an explicit yes (Enter = no); * the question names the other folder when the local config knows one. * · --ci — a fresh runner is the expected case, so an agent nothing local maps to is adopted * silently; a local collision is a refusal, never a prompt. */ export interface AdoptPolicy { ci: boolean; /** Interactive: an explicit yes (Enter = no) before the takeover. */ confirmAdopt: (question: string) => Promise; } export declare function deployRelease(deps: UploadDeps, input: UploadInput, policy: AdoptPolicy): Promise;