/** * The closure `createVendo` used to be. * * 09-vendo §2 composes eleven blocks around one guard choke point, and it did * that inside a single 2,000-line function whose bindings all reached each * other by scope. This interface is that closure, named: every phase module * below takes the composition, reads the fields it needs, and returns its own * slice of it. `createComposition` wires the phases in the SAME order the one * function ran them, because the order is load-bearing (a boot warning, a * refusal, and three registry `add`s all have to land where they landed). * * A handful of fields are filled LATER than the phase that declares them — * the app-tool risk resolver, the learned loopback origin, the MCP posture. * They were `let` bindings read from closures that only run inside a request; * they are mutable fields here, read the same way, at the same time. * * Internal — not exported from the package root. */ import type { ActionsRegistry, Connector, ExtractedTool, OverridesFile, ServerActionHandler } from "./actions/index.js"; import type { AgentComposition } from "./turn/index.js"; import type { AppsRuntime, SeedBaseline } from "./apps/index.js"; import type { AutomationsEngine } from "./automations/index.js"; import type { ActAs, AgentRunner, CreateAutomation, FilesAdapter, Harness, Principal, ConfigSurfaceName, RiskLabel, RunContext, SecretsProvider, StoreOps, ToolCall, ToolRegistry } from "./core/index.js"; import type { BriefingPack, VendoTheme } from "./core/apps/index.js"; import type { VendoGuard, RiskResolver } from "./guard/index.js"; import type { CapabilityMissConfig } from "./harnesses/index.js"; import type { VendoToolSearchConfig } from "./harnesses/vendo/index.js"; import type { McpDoor, TurnCredentials } from "./mcp/index.js"; import type { VendoStore } from "./store/index.js"; import type { createByoApprovals } from "./byo-approvals.js"; import type { McpBundle } from "./cloud-mcp.js"; import type { CapabilitySurfaceSnapshot } from "./capability-misses.js"; import type { MergedCapability } from "./capability/index.js"; import type { mergeRuntimeCatalog } from "./catalog.js"; import type { CloudDirectory } from "./cloud-directory.js"; import { type Limiter } from "./limits.js"; import type { ResolvedSweep } from "./compose-config.js"; import type { ChannelDoor, ChannelsService } from "./channels.js"; import type { ConnectionsService } from "./connections.js"; import type { HarnessTurns } from "./harness-turn.js"; import type { resolveModels } from "./models-config.js"; import type { TenantConnectors } from "./tenant-connectors.js"; import type { AppsOptions, CreateVendoConfig } from "./types.js"; import type { resolveVendoUrls } from "./urls.js"; import type { WireDeps } from "./wire/shared.js"; import type { createConnectGate, mergedHostSemantics } from "./actions/index.js"; import type { selectSandbox } from "./apps/index.js"; import type { appAccess } from "./store/index.js"; import type { HostAuthPreset } from "./auth-presets/index.js"; /** The actions registry config object, named because composition MUTATES two of * its fields after `createActions` has read the rest (`invokeTool` after the * guard binding, `baseUrl` when the wire learns its own origin). */ export interface VendoActionsConfig { dir: string; tools?: ExtractedTool[]; /** The in-memory doc (profile.overrides); otherwise the registry reads * `.vendo/overrides.json` off `dir` itself. */ overrides?: OverridesFile | (() => Promise); connectors?: Connector[]; actAs?: ActAs; serverActions?: Record; baseUrl?: string; baseUrlTrusted?: boolean; fetch?: typeof fetch; onPresentCredentialsNotForwarded: (event: { ctx: RunContext; tool: import("./core/index.js").ToolDescriptor; reason: "untrusted-host-origin" | "cross-origin-binding"; }) => Promise; untrustedOriginPolicy?: "warn" | "fail"; invokeTool?: ToolRegistry["execute"]; } export interface VendoComposition { /** Whether app generation mounts (`apps: false` folds away to no options). */ appsMounted: boolean; /** Whether the automations engine mounts. */ automationsMounted: boolean; /** The host's config with `apps: false` folded away. */ config: Omit & { apps?: AppsOptions; }; /** What `agent()` composed, when the host adopted one. */ composed: AgentComposition | undefined; resolvePrincipal: (req: Request) => Promise; actAsSeam: ActAs | undefined; oauthSeam: HostAuthPreset["oauth"]; /** Build contract §9.1 — the host org query the wire, the harness, the * automations engine and the MCP door all resolve the SAME answer through. */ membershipsSeam: HostAuthPreset["memberships"]; /** The hosted tenant directory, when VENDO_API_KEY filled a `memberships` * seam the host left unset — `undefined` whenever the host asserted its own. * Read a second time by composeLimits, off the SAME cache. */ directory: CloudDirectory | undefined; userFactsSeam: HostAuthPreset["facts"]; userPoolsSeam: HostAuthPreset["pools"]; sweepConfig: ResolvedSweep; sweepNow: () => number; /** The host's `limits` policy, bound to the store's meter — `undefined` when * the host set no policy, which is what every choke point checks. */ limiter: Limiter | undefined; store: VendoStore; /** THE files adapter for this deployment (build contract §3.4). */ files: FilesAdapter; /** The 42-op StoreOps surface for this deployment — the store's own when it * carries one, the local backend over its SQL handle otherwise, and absent * when the store offers neither (`backendOf`'s third answer). */ ops: StoreOps | undefined; sandbox: ReturnType; secrets: SecretsProvider; inference: ReturnType; /** One resolution cycle happened: re-hash the five resolved surfaces and * report them if they moved (config-report.ts). No-op without a key. */ reportConfig: () => void; surfaceRoot: string | undefined; readSurfaceFile: (name: ConfigSurfaceName) => string | undefined; memoizeOnce: (resolve: () => T | undefined) => () => T | undefined; /** Armed by the ready() latch, never at construction (Workers forbids timers * in global scope). Filled by compose-sweep.ts. */ startBackgroundSweep: () => void; /** A DEVELOPMENT process drives its own scheduler tick — the production tick * is an external caller's job (POST /tick, or Cloud for hosted deploys) and * no laptop has one. Same ready()-latch arming as the sweep. Filled by * compose-automations.ts; a no-op outside development. */ startDevAutomationsTicker: () => void; /** The other half: a DEPLOYED process is woken by Cloud's heartbeat, which can * only knock on a door it has been told about. Same ready()-latch firing; * never rejects, and shouts if it could not enrol. Filled by * compose-automations.ts. */ enrolForCloudTicks: () => Promise; /** The boot-once latch every handler/emit touch awaits. */ ready: () => Promise; /** Filled by compose-apps.ts, read by `resolveRisk` inside a later check. */ resolveAppToolRisk?: AppsRuntime["agentToolRisk"]; guard: VendoGuard; /** The app-then-broker risk chain the guard AND the automations engine take. */ resolveRisk: RiskResolver; warnPresentCredentialsNotForwarded: VendoActionsConfig["onPresentCredentialsNotForwarded"]; /** The policy file this deployment expects and does not have, judged at compose so the boot block can read it as a fact (boot-summary.ts). */ policyFileMissing: string | undefined; configuredBaseUrl: string | undefined; urls: ReturnType; isDevelopmentEnv: boolean; /** The connected-account services this deployment named, or `undefined` when * neither `connectedAccounts` nor a legacy string in `connectors` named any. */ connectorToolkits: readonly string[] | undefined; resolvedConnectors: Connector[]; actionsConfig: VendoActionsConfig; actions: ActionsRegistry; doctor: WireDeps["doctor"]; connectGate: ReturnType; /** The ONE guard-bound registry chat, apps, automations and the door ride. */ boundTools: ToolRegistry; byoApprovals: ReturnType; parkedCallTtlMs: number; /** The dev-side per-org connector registry (tenant-connectors.ts). The * overlay it selects from is composition-private; only this handle is public. */ tenantConnectors: TenantConnectors; theme: VendoTheme | undefined; themeProvider: () => VendoTheme | undefined; designRules: string | (() => string | undefined); /** THE briefing pack — assembled once (compose-surfaces.ts), read by both * generation rungs. */ briefing: (ctx: RunContext) => Promise; seedBaselines: SeedBaseline[]; hostSemanticsProvider: () => ReturnType; capability: MergedCapability; catalog: ReturnType; /** Build contract §9.3 — ONE `can()` the apps runtime and the engine share. */ access: ReturnType; apps: AppsRuntime; /** The same runtime, as the LATE slot the capability thunk resolves through: * the app tools are contributed before the runtime they act through exists. */ appsRuntime?: AppsRuntime; toolOutputCap: number; catalogConnectors: Connector[]; serviceCatalog: boolean; knowledgeIndex: ReturnType | undefined; missSurface: () => Promise; missCapture: ReturnType; system: Parameters[2]; capabilityMiss: CapabilityMissConfig; toolSearch: VendoToolSearchConfig; harness: Harness; mcpOptions: Exclude | undefined; internalDoorOnly: boolean; /** Fixed by the first loopback request the wire validates (compose-wire.ts). */ learnedLoopbackOrigin?: string; doorBase: () => string | undefined; harnessTurns: HarnessTurns; /** The screen agent's workspace door, filled with the harness turns composed * after the apps runtime that reads it (assembly only happens in a request). */ harnessTurnsForScreens?: HarnessTurns; /** THE harness door — one object, served to the host and to the wire alike. */ harnessDoor: HarnessTurns; delegateRunner: AgentRunner; connectedToolkitsCache: Map; agentMenu: () => Promise | undefined>; subjectHasToolkit: (toolkit: string, ctx: RunContext) => Promise; connectedToolkitsFor: (ctx: RunContext) => Promise; serviceToolOwner: (slug: string) => Promise<{ connector: Connector; risk: RiskLabel; } | undefined>; serviceToolRisk: (call: ToolCall) => Promise; /** What the adapter rule chose, handed back on `vendo.connections` UNTOUCHED. */ selectedConnections: ConnectionsService; /** The same adapter, wrapped so a disconnect invalidates the toolkit cache. */ connections: ConnectionsService; /** What the adapter rule chose for the text channel (`selectChannels`). */ channels: ChannelsService; /** The composed door: link/status/unlink for the host and the wire, and the * inbound runner the machine door drives. */ channelDoor: ChannelDoor; /** The bearer Vendo Cloud presents on an inbound delivery, derived from * VENDO_API_KEY; undefined when this deployment has no Cloud key. */ channelInboundSecret: () => Promise; runSweep: () => Promise; sweepEnabled: boolean; hostedStoreComposed: boolean; automations: AutomationsEngine; /** THE one create-automation operation, as the LATE authoring seam the apps * runtime holds: automations is constructed after apps, and every call * happens inside a request. Never public — `vendo.automations` has no * `create`; the four authoring doors reach it through here. */ createAutomation?: CreateAutomation; /** `.on()` declarations → records, run once on the ready() latch (after * ensureSchema, before the first request). Filled by compose-automations.ts. */ bootReconcile: () => Promise; turnCredentials: TurnCredentials; door: McpDoor | undefined; /** The /status posture: false while the door is closed, "local" when it * serves its own OAuth surface, "broker" when one fronts it. */ mcpPosture: "local" | "broker" | false; doorWellKnown: ReadonlySet; /** The Cloud tenant's MCP bundle, when Vendo Cloud filled the brokerage seam: * the door and `vendo.tokenFor` share this one lazy provisioning. */ mcpBundle: (() => Promise) | undefined; } /** * 09-vendo §2 — every live block, composed around the guard choke point, in the * order the one function composed them. * * The phases share ONE object rather than a chain of arguments because the * composition is genuinely cyclic: the guard's risk resolver reaches the apps * runtime, the connect gate reaches the connections adapter, the harness * reaches the MCP door's credential registry — and every one of those reads * happens inside a request, long after this function has returned. */ export declare const createComposition: (input: CreateVendoConfig) => VendoComposition;