/** * ConnectorManager — unified lifecycle & mount-target allocation. * * Manages the full lifecycle of every connector declared in `skaile.yaml`, * whether it exposes a `filesystem` face, a `tools` face, or both. * * @docLink packages/connectors/concepts#connector-manager */ import type { CatalogEntry } from "@skaile/workspaces/core"; import type { TelemetryProvider, Trace } from "@skaile/workspaces/telemetry"; import type { ConnectorStatusEvent } from "@skaile/workspaces/types"; import type { Connector, ConnectorChangeEvent, ConnectorDeclaration, ConnectorHandle, ConnectorStartupReport, FailedConnectorInfo, RuntimeSkillDescriptor, TokenMediator } from "./connector-types.js"; import type { FleetHealthVerdict } from "./fleet-utils.js"; import type { PreMintedSecretProvider, SecretProvider, SecretProviderChain } from "./secrets.js"; import type { ConnectorContent, ConnectorEntry, ListOptions, OperationDescriptor, SearchOptions, SearchResult, SyncOptions, SyncResult, WatchHandle, WatchOptions } from "./shared-types.js"; /** * Internal per-connector tracking record. */ interface ActiveConnector { connector: Connector; handle: ConnectorHandle; declaration: ConnectorDeclaration; watchHandle?: WatchHandle; } interface LifecycleResult { id: string; ok: boolean; error?: string; detail?: string; } export interface SyncConnectorResult { id: string; ok: boolean; error?: string; } /** * Options for the unified `ConnectorManager`. Pass as the second argument to the constructor. * * Note: constructor signature is `new ConnectorManager(workspaceDir, opts?)` — * `workspaceDir` comes first because mountable connectors need it. * `secrets` moves into opts (was the first arg in the old `ConnectorManager`). */ export interface ConnectorManagerOptions { /** * Secret provider (chain or bare) for resolving credential references in * `describeFields()` and for passing to `Connector.connect()`. */ secrets?: SecretProviderChain | SecretProvider; /** * Catalog entries for dynamic connector resolution (registry-first, catalog-fallback). */ catalogEntries?: CatalogEntry[]; /** * Backend token mediator — present for `auth: backend` filesystem-face connectors. * Wrapped with pre-minted secrets when `preMintedSecrets` is also set. */ tokenMediator?: TokenMediator; /** * Pre-minted credential store from `session_init`. When set, `auth: backend` * connectors use this for the initial mint without a platform round-trip. */ preMintedSecrets?: PreMintedSecretProvider; /** * Called whenever a connector's connection state changes. * * Fired from both the bulk `connectAll()` path and the hot-plug `connect()` path. * Status values: `"connecting"` → `"connected"` | `"error"`, and `"disconnected"` * after `disconnect()` / `disconnectAll()`. */ onStatusChange?: (event: ConnectorStatusEvent) => void; /** Optional telemetry provider for span instrumentation. */ telemetry?: TelemetryProvider; /** Optional active trace to attach connector spans to. */ trace?: Trace; /** Deprecated no-op option kept so older callers can pass it during migration. */ placeholderBarrier?: unknown; /** Deprecated no-op option kept so older callers can pass it during migration. */ fleetHealthPollMs?: number; /** Deprecated no-op option kept so older callers can pass it during migration. */ fleetMountRecoveryProbe?: (target: string) => Promise; } /** * `ConnectorManager` — manages the full lifecycle of all connectors declared * in `skaile.yaml`, whether they expose a filesystem face, a tool face, or both. * * For filesystem-face connectors, the manager allocates a mount-target * directory and passes it as `ctx.mountTarget`; the connector mounts its * content there during `connect()`. * * @docLink packages/connectors/concepts#connector-manager */ export declare class ConnectorManager { private readonly opts; private active; /** * Connectors whose `connect()` threw, keyed by id. A failed connector never * enters `active`, so without this map its failure would exist only in the * log store — invisible to the agent that is about to write into a mount * that will never sync. See {@link listFailedConnectors}. */ private readonly failedConnectors; private readonly mountTargets; readonly workspaceDir: string; private readonly chain?; private readonly secrets?; private readonly catalogEntries; private readonly tokenMediator?; private readonly preMintedSecrets?; private readonly mgrLog; /** Aborted on dispose; threaded into every `connect()` via `ctx.abortSignal`. */ private readonly disposeController; /** * @param workspaceDir - Absolute (or relative) working directory. Filesystem-face * connectors mount into `/.mounts//` by default. * @param opts - Optional configuration: secrets, catalogEntries, tokenMediator, * preMintedSecrets, onStatusChange, telemetry, trace. */ constructor(workspaceDir: string, opts?: ConnectorManagerOptions); /** * Wrap the user-supplied `TokenMediator` so `reason: 'initial'` calls for an * `auth: backend` connector first consult the pre-minted credentials. Falls * back to the original mediator for refresh / retry-401 paths. On that * fall-through it attaches the connector's `driver` + `providerLinkId` taken * from the `declaration` (authoritative routing — a connector cannot override * them per call) so the runner mediator can pick the platform refresh `kind` * and `id`. * * Namespace fallback: when no `connector:` pre-mint entry exists, the * lookup retries under the legacy `mount:` key. This is a defence-in-depth * companion to the platform-side pre-mint namespace alignment — see the * 2026-05-19 incident notes and `_devlog/specs/2026-05-07-unified-credential-mediation.md`. * When the fallback fires, a `warn` log signals that the platform writer is * filing credentials under the pre-unified namespace key. */ private buildWrappedMediator; /** * Resolve a connector by driver name: registry first, then catalog. */ private resolveConnector; /** * Compute the absolute mount-target directory for a filesystem-face connector. * * - `local` driver + absolute source + no explicit target → bind directly to source * - otherwise → `decl.mount.target ?? .mounts/`, resolved relative to workspaceDir */ private resolveMountTarget; private reserveMountTarget; /** * Remember a connect failure so the agent can be told about it. Called from * both the bulk (`attemptConnect`) and hot-plug (`connect`) failure paths. */ private recordFailure; /** Release every mount target owned by `id` (on disconnect). */ private releaseMountTarget; /** * Build a `ConnectContext` for a connector, resolving fields and — for * filesystem-face connectors — allocating the mount-target directory. */ private buildConnectContext; /** * Span-wrapping helper for connector lifecycle calls. */ private withSpan; /** * Connect all declared connectors. For each connector: * 1. Resolves the `Connector` from the registry (or catalog). * 2. Calls `setLogger` when available. * 3. Resolves fields via `describeFields()` + secret chain. * 4. For filesystem-face connectors: allocates mount-target dir. * 5. Calls `connector.connect(decl, ctx)`. * 6. Tracks in the active map; emits `onStatusChange`. * * After the loop, if `options.failOnReadWriteError` is set and any read-write * connector with a `mount` config failed → throws `ConnectorStartupError` * (fail-fast lifecycle semantics for mountable connectors). * * @param declarations - Connector declarations from `skaile.yaml`. * @param options.failOnReadWriteError - Throw `ConnectorStartupError` if any * read-write connector fails. Read-only failures are always tolerated. * Only the eager set is considered — deferred mounts never throw. * @param options.deferMounts - Deprecated compatibility option. Cloud * connectors now require already-live host fleet bind mounts and connect * synchronously. */ connectAll(declarations: ConnectorDeclaration[], options?: { failOnReadWriteError?: boolean; deferMounts?: boolean; }): Promise; /** * Connect a single declaration, emitting `connecting` → `connected | error`. * Never throws — failures are recorded in the returned result so `connectAll` * can keep going. */ private attemptConnect; /** * Compatibility no-op for the removed in-container fleet health gate. Host * fleet-managed cloud mounts are verified at connect time by their drivers. */ reportFleetHealth(_connectorId: string, _verdict: FleetHealthVerdict): Promise; /** * Compatibility no-op for the removed deferred rclone mount path. */ whenDeferredMountsSettled(): Promise; /** Disconnect all connectors and stop all watchers. Best-effort — ignores disconnect errors. */ disconnectAll(): Promise; /** * Connect a single connector (hot-plug). Resolves fields and allocates * mount target if the connector has a filesystem face. * * @returns The `ConnectorHandle` for subsequent operations. */ connect(declaration: ConnectorDeclaration): Promise; /** * Disconnect a single connector by ID and stop its watcher. * * A connector that only ever *failed* to connect is not in `active`, so * `get(id)` would throw for it. Removing such a declaration is still a * legitimate host action — and it is the only way to retract its * `## Mount failures` row — so that case is handled as a plain forget rather * than an error. * * @throws {Error} if the connector is neither connected nor recorded as failed */ disconnect(id: string): Promise; /** * Start watchers for all connected connectors that expose a `watch()` method. * For filesystem-face connectors, nested-mount-path exclusion logic is applied. */ watchAll(callback: (connectorId: string, event: ConnectorChangeEvent) => void, options?: WatchOptions): void; /** Stop all active watchers. */ unwatchAll(): Promise; hibernateAll(): Promise; closeAll(): Promise; private runLifecycleHook; /** * Return the internal `ActiveConnector` entry. * * @throws {Error} if the connector is not found */ get(id: string): ActiveConnector; has(id: string): boolean; /** * Return summary info for all currently connected connectors. */ listConnectors(): Array<{ id: string; driver: string; label?: string; access: "read-only" | "read-write"; mountPath?: string; status: ConnectorHandle["status"]; }>; /** * Return every connector whose `connect()` threw and that has not since * connected. * * A failed connector is deliberately **absent** from `listConnectors()` and * `get()` — it never entered the active map, and those surfaces describe what * the agent can actually use. That is exactly why this second list exists: * without it a failed mount is invisible to the prompt builder, and the agent * keeps treating a dead directory as a live, synced workspace. * * Entries are cleared by a later successful connect of the same id, by * `disconnect(id)`, and wholesale by `disconnectAll()`. */ listFailedConnectors(): FailedConnectorInfo[]; /** * Lists git-backed connectors, surfacing the fields prompt-builders and the * runner's wake-mid-401 refresh handler need. */ listGitConnectors(): Array<{ id: string; source: string; exposeAccessToken: boolean; auth?: string; }>; /** * Pull the latest remote state for a single filesystem-face connector. * * @throws {Error} if connector is not found or has no filesystem face. */ sync(id: string, options?: SyncOptions): Promise; /** * Pull the latest remote state for all connected filesystem-face connectors. * Errors from individual connectors are captured and returned rather than thrown. */ syncAll(options?: SyncOptions): Promise; /** * Throw a consistent error when the tools face is absent. */ private requireToolFace; read(id: string, path: string): Promise; write(id: string, path: string, content: ConnectorContent): Promise; delete(id: string, path: string): Promise; list(id: string, path?: string, options?: ListOptions): Promise; search(id: string, query: string, options?: SearchOptions): Promise; /** * Execute a custom operation on a connector's tool face. * * @throws {Error} if connector has no tool face, operation is unknown, or * a write operation is called on a read-only connector. */ executeOp(id: string, operation: string, args: Record): Promise; /** * Return the list of operations that a connector's tool face exposes. * Returns an empty array when the connector has no tool face. */ getOperations(id: string): OperationDescriptor[]; /** * Return `RuntimeSkillDescriptor` for every connected connector. * Uses `tools.describeSkill()` when available; falls back to `buildDefaultSkillDescriptor`. */ listSkills(): RuntimeSkillDescriptor[]; /** * Return the rendered skill body for a single connector. * * @param id - Connector ID. * @param shellAccess - When true (default), CLI-style commands are used; * false renders SDK tool-call style. * @throws {Error} if the connector is not found. */ loadSkill(id: string, shellAccess?: boolean): string; } export {}; //# sourceMappingURL=connector-manager.d.ts.map