import type { EntityMetrics } from "@lobu/connector-sdk"; import type { AgentSettings } from "@lobu/core"; import type { InstallConnectorInput } from "@lobu/core/contracts/tools/manage-connections"; import type { InferenceCapabilityBlock, InferenceModality } from "../../../config/define.js"; import { ApiError } from "../../memory/_lib/errors.js"; import type { DeploymentSummary } from "./deployment.js"; import type { DesiredAgentMetadata, DesiredEntityType, DesiredRelationshipType } from "./desired-state.js"; import { type AutomationSource, type EntityBacking, type RelationshipRule } from "./shared.js"; export interface RemoteAgent { agentId: string; name: string; description?: string; } export interface RemoteDeployment { id: number; applyId: string; createdAt: string; title: string | null; status: string | null; gitSha: string | null; gitDirty: boolean | null; manifestHash: string | null; rollbackOf: string | null; /** Stored snapshot ({version, state, connector_versions}); null on legacy deployments. */ manifest: { version: number; state: Record; connector_versions: Record; /** Attribution baseline (effective remote after apply); absent on legacy. */ attribution?: { entityTypes: unknown[]; relationshipTypes: unknown[]; automations: unknown[]; }; /** Kind-qualified incarnation identities this config applied. */ owned?: string[]; } | null; /** Blocking-drift candidates + confirm token, present on `blocked` runs. */ candidates?: { token?: string; items?: Array>; } | null; } export interface DeploymentPauseState { paused: boolean; pausedAt?: string; applyId?: string | null; rollbackOf?: string | null; pausedBy?: string | null; } export interface RemoteEntityType { /** Persistent incarnation id (`entity_types.id`) — the `owned` identity for deletes. */ id?: number; slug: string; name?: string; description?: string; required?: string[]; properties?: Record; /** Event kinds keyed by semantic_type (mirrors {@link DesiredEntityType.eventKinds}); hoisted from the row's `event_kinds`. */ eventKinds?: Record; /** * Current default view template (mirrors {@link DesiredEntityType.viewTemplate}). * NOT returned by the entity-type list (kept off that hot path); apply-cmd * fetches it per relevant type via {@link ApplyClient.getEntityTypeViewTemplate} * and attaches it before diffing. */ viewTemplate?: Record; /** Present only for derived types (mirrors {@link DesiredEntityType.backing}). */ backing?: EntityBacking; /** Declared metrics (mirrors {@link DesiredEntityType.metrics}); hoisted from the row's `metrics_config`. */ metrics?: EntityMetrics; /** * Top-level `metadata_schema` extension keys not hoisted into dedicated remote * fields. This includes `x-lobu-resolution`: apply compares it when config * declares a policy and carries it forward untouched when config omits it. * * `upsertEntityType` REBUILDS `metadata_schema` from the config's flat * `properties`/`required` and the server stores what it is sent verbatim, so * without carrying these forward every apply would silently erase them. Set * only when the stored schema has at least one such key, so a plain type * stays `undefined`. */ schemaExtras?: Record; /** * Write rules as stored, hoisted from the row's `rules_source`. The compiled * artifact is never fetched — it is a build output, so diffing it would churn * on a compiler change rather than on a rule change. */ rulesSource?: string | null; /** * Owning org id. The list endpoint also returns *public* types from OTHER * orgs (`o.visibility = 'public'`), so prune must compare this against the * target org and never delete a type this org doesn't own. */ organization_id?: string; } export interface RemoteRelationshipType { /** Persistent incarnation id (`entity_relationship_types.id`) — the `owned` identity for deletes. */ id?: number; slug: string; name?: string; description?: string; rules?: RelationshipRule[]; /** Owning org id — see RemoteEntityType.organization_id (public-type guard). */ organization_id?: string; } interface RemoteOrg { id: string; slug: string; name?: string; } /** One org-owned inference provider as returned by `GET /inference-providers`. */ export interface RemoteInferenceProvider { id: number; slug: string; kind: string; displayName: string | null; capabilities: Record>; hasCustomUpstream: boolean; status: string; createdAt: string; } export interface RemoteAutomation { slug: string; name?: string; automation_id?: string; managed_agent_id?: string | null; triggers?: import("@lobu/core/contracts/tools/manage-automations").AutomationTrigger[]; device_worker_id?: string | null; goal_id?: number | null; agent_kind?: string | null; execution_config?: Record | null; min_cooldown_seconds?: number | null; tags?: string[] | null; sources?: AutomationSource[] | null; description?: string | null; prompt?: string | null; /** * Pinned skill snapshots on the current version. NULL/absent on Automations * created before the column existed, which the diff treats as "no skills" — * so the first re-apply of a config that references skills pins them. */ skills?: Array<{ name: string; content: string; }> | null; classifiers?: unknown[] | null; outputs?: Record | null; reactions_guidance?: string | null; } interface UpsertEntityTypeResult { created?: boolean; updated?: boolean; noop?: boolean; } export interface RemoteConnectorDefinition { /** Persistent incarnation id (`connector_definitions.id`) — the `owned` identity for deletes. */ id?: number; key: string; name?: string; version?: string; options_schema?: Record | null; feeds_schema?: Record | null; auth_schema?: Record | null; installed?: boolean; installable?: boolean; catalog_origin?: string; /** `file://` URI of the bundled source on the server host (catalog entries). */ source_uri?: string | null; /** Non-secret remote MCP transport metadata exposed by the connector catalog. */ mcp_config?: Record | null; /** Trusted in-memory connector artifact synthesized from a managed Cloud catalog. */ managed_mcp_source?: string; } export interface RemoteAuthProfile { id?: number; slug: string; display_name?: string; connector_key: string; profile_kind: string; status: string; } export interface RemoteConnection { id: number; slug: string; connector_key: string; display_name?: string; status: string; auth_profile_slug?: string | null; app_auth_profile_slug?: string | null; config?: Record | null; device_worker_id?: string | null; agent_id?: string | null; credential_mode?: "managed" | "byo" | null; effective_credential_mode?: "managed" | "byo" | null; } export interface RemoteFeed { id: number; connection_id: number; feed_key: string; display_name?: string; status: string; schedule?: string | null; config?: Record | null; } /** * The mutable fields of a connection update. Every key is optional and a key * that is ABSENT is not written — the server leaves that column alone. This is * what makes "undeclared means unmanaged" expressible on the wire, so the * builders in `diff.ts` construct these payloads a key at a time from the * diff's changed-field list rather than listing every field unconditionally. */ export interface UpdateConnectionPayload { name?: string; authProfileSlug?: string | null; appAuthProfileSlug?: string | null; config?: Record; deviceWorkerId?: string | null; } /** The mutable fields of a feed update — same absent-means-unwritten rule. */ export interface UpdateFeedPayload { name?: string; /** Cron string, or null to clear (manual-only). */ schedule?: string | null; config?: Record; } interface InstallConnectorResult { connectorKey: string; updated: boolean; version?: string; } type CliInstallConnectorPayload = { connectorId?: InstallConnectorInput["connector_id"]; sourceCode?: InstallConnectorInput["source_code"]; sourceUrl?: InstallConnectorInput["source_url"]; sourceUri?: InstallConnectorInput["source_uri"]; compiled?: InstallConnectorInput["compiled"]; }; /** * Result of ensuring an auth profile exists. For interactive kinds * (`oauth_account` / `browser_session`) `connectUrl` carries the URL the * operator must open to complete auth; `status` is the state the server * reports (`pending_auth` until auth completes). */ interface EnsureAuthProfileResult { created: boolean; updated: boolean; status?: string; connectUrl?: string; } interface ApplyClientConfig { apiBaseUrl: string; orgSlug: string; token: string; /** Sent as `x-lobu-apply-id` on every request so the server can group this run's config-audit events into one deployment. */ applyId?: string; /** * The deployment this run is restoring, sent as `x-lobu-rollback-of`. Set by * `lobu rollback` only. The server refuses mutating apply-run requests while * promotions are paused; rollbacks are exempt, because rolling back FURTHER is * the main thing an operator does while paused. A rollback sets the pause * BEFORE its first mutation, so without this it would be blocked by its own * pause. The server verifies the named deployment is a restorable snapshot in * the org rather than trusting the header. */ rollbackOf?: string; } /** * Typed wrappers for the existing server endpoints `lobu apply` calls. * * The class is open over an injectable `fetchImpl` so tests can stub the * network without monkey-patching globals. Real callers leave `fetchImpl` * unset and pick up `globalThis.fetch`. */ export declare class ApplyClient { private readonly orgSlug; private readonly http; constructor(cfg: ApplyClientConfig, fetchImpl?: typeof fetch); /** * Delegates the fetch/parse/non-ok-error pipeline to the shared * {@link ApiClient}, then layers on the apply-specific shape: * - the body is coerced to a record (`undefined`→`{}`, non-record→`{value}`) * - a body-level `error` string is treated as a failure even on a 2xx * * Apply endpoints only ever return the listed `okStatuses` (200/201/204) on * success or a 4xx/5xx on failure, so `ApiClient`'s status gate produces the * same outcome the local pipeline did. `Content-Type: application/json` is * sent on every request (no `Accept`) to mirror the previous wire shape. */ private request; /** * Orgs the authenticated user belongs to, read from the OAuth userinfo * endpoint — the same source `lobu org list` uses. Used to check whether the * `[memory].org` slug already resolves to one of the operator's orgs. Does * not depend on `this.orgSlug`. (`lobu apply` can't create an org headlessly * — that needs a logged-in browser session — so there is no `createOrg`.) */ listOrgs(): Promise; listAgents(): Promise; /** * Idempotent create: PR-2 makes `POST /` return 200 with the existing * payload when an agent of the same ID already exists in the same org. * Cross-org collision still surfaces as 409 with a clear `error.code` — * we re-throw verbatim so `lobu apply` can show the operator the link * to the org-scoped IDs issue. */ upsertAgent(agent: DesiredAgentMetadata): Promise; patchAgentMetadata(agentId: string, agent: { name?: string; description?: string; }): Promise; getAgentSettings(agentId: string): Promise; patchAgentSettings(agentId: string, settings: Partial): Promise; /** * Record this apply run as a deployment (`POST /api//deployments`). * The server dedupes on apply_id, so a retried post is safe. Callers treat * failure as a warning — the apply itself already succeeded (or already * failed) independently of the audit record. */ postDeploymentSummary(summary: DeploymentSummary): Promise; /** Fetch one deployment's record, including its stored manifest snapshot. */ getDeployment(applyId: string): Promise; /** Latest `succeeded` deployment — the attribution baseline (null when none). */ getLatestDeployment(): Promise; /** Promotions-pause state (set by `lobu rollback`). */ getDeploymentPause(): Promise; setDeploymentPause(params: { applyId: string; rollbackOf: string; }): Promise; clearDeploymentPause(): Promise; /** List the org's inference providers (never returns the api key). */ listInferenceProviders(): Promise; /** Create an org inference provider. 409 (surfaced as ApiError) on slug conflict. */ createInferenceProvider(body: { slug: string; kind: string; displayName?: string; apiKey: string; capabilities?: Partial>; }): Promise; /** Upsert one modality's capability block (`{ base_url?, model?, models_endpoint? }`). */ updateInferenceProviderCapabilities(slug: string, modality: InferenceModality, block: InferenceCapabilityBlock): Promise; /** * Rotate an org provider's API key. Idempotent — the current key can't be read * back, so apply re-pushes the declared value on every run; a matching value * is a harmless no-op server-side, a changed one rotates. */ rotateInferenceProviderKey(slug: string, value: string): Promise; /** Soft-delete an org inference provider. */ deleteInferenceProvider(slug: string): Promise; listEntityTypes(): Promise; /** * The `manage_entity_schema` admin tool exposes separate `create` / `update` * actions and surfaces duplicates as a coded 409 (`[entity_type_exists]` / * `[relationship_type_exists]`). Probe with `create`; on that explicit * duplicate signal retry with `update`. Any other error (e.g. a 422 * `[invalid_schema]` validation failure) propagates verbatim — retrying it * as an update used to mask the real message behind "Entity type not * found" (issue #1177). */ private upsertSchemaResource; upsertEntityType(entity: Omit, /** * The live type's `metadata_schema` extension keys * ({@link RemoteEntityType.schemaExtras}), from the remote snapshot this * apply already fetched. Hoisted core keys are stripped before merging; a * declared resolution policy overrides its live extension, while every * undeclared extension survives the rebuild. */ schemaExtras?: Record, /** * The live type's hoisted schema core (`properties`/`required`) when the * config does not declare them itself. Required so a type that declares * ONLY an extension (e.g. `resolutionPolicy`) does not wipe the server's * complete metadata_schema by sending `properties: {}` — the config's * declared value wins when present, the remote core round-trips otherwise. */ remoteSchemaCore?: { properties?: Record; required?: string[]; }, /** * Facet names the apply diff flagged for CLEARING (prune removal of * out-of-band eventKinds, derived→stored backing revert, metric removal). * Facets are declared-only otherwise: an update fired for an unrelated * field must never wipe live values the config does not own, and the server * clears a facet only when its key is present in the payload. */ clearFacets?: ReadonlySet, /** * When true (prune off), remote-only property keys the config never * declared are merged into the write so an unrelated config update cannot * silently erase a UI-added property. When false (prune on), declared * properties alone are the full schema. */ preserveRemoteOnlyProperties?: boolean): Promise; /** * Fetch an entity type's current default view template (`null` if none). * Apply uses this to diff ONLY the types it needs (declared templates, plus * every config type under prune) — the template is deliberately NOT returned * by the entity-type list, which the UI/bootstrap also calls. */ getEntityTypeViewTemplate(slug: string): Promise | null>; /** * Set the entity type's default view template. A separate, version-appending * tool from the schema upsert, so apply calls this ONLY on create or a changed * template (see apply-cmd) — never every run, which would churn the history. */ setEntityTypeViewTemplate(slug: string, jsonTemplate: Record): Promise; /** * Clear the entity type's default view template (prune-gated removal). Nulls * the current-version pointer server-side; history rows stay for rollback. */ clearEntityTypeViewTemplate(slug: string): Promise; listRelationshipTypes(): Promise; /** * Fetch a relationship type's rules (the `list` action omits them, so the * apply diff can't otherwise see remote rules and would churn a perpetual * "rules changed" update). Maps the server's `*_entity_type_slug` columns to * `{ source, target }`; `id` is carried for reconcile (remove_rule by id). */ listRelationshipTypeRules(slug: string): Promise>; upsertRelationshipType(rel: Omit): Promise; /** * Delete an entity type (code-managed prune). The server soft-deletes and * REFUSES if instances of the type still exist — the data is exempt from * prune, so that surfaces as a clear error rather than cascading. */ deleteEntityType(slug: string): Promise; /** Delete a relationship type (code-managed prune). */ deleteRelationshipType(slug: string): Promise; /** * Fetch a single Automation's full payload, including the reaction script * (not in the list response). Used by `lobu init --from-org` to round-trip * reaction scripts back to sibling `.ts` files. */ getAutomationDetail(automationId: string): Promise<{ reaction_script?: string | null; description?: string | null; } | null>; listAutomations(): Promise; /** * Create an Automation owned by `agentId`. Duplicate-slug surfaces as a * structured error the caller swallows for idempotency. */ createAutomation(payload: { slug: string; agentId: string; name?: string; description?: string; prompt: string; skills?: Array<{ name: string; content: string; }>; reaction_script?: string; triggers?: import("@lobu/core/contracts/tools/manage-automations").AutomationTrigger[]; sources?: AutomationSource[]; reactions_guidance?: string; device_worker_id?: string; min_cooldown_seconds?: number; tags?: string[]; agent_kind?: string; execution_config?: Record | null; outputs?: Record | null; classifiers?: unknown[]; }): Promise<{ automation_id?: string; }>; /** * Update the **scalar** fields on the `automations` row — these don't require * a new version. Version-bound fields (prompt / sources / reactions_guidance / * outputs / classifiers) require `createAutomationVersion` * instead. * * `null` clears nullable fields (device_worker_id, agent_kind) per the * server contract. */ updateAutomation(payload: { automation_id: string; triggers?: import("@lobu/core/contracts/tools/manage-automations").AutomationTrigger[]; managed_agent_id?: string; device_worker_id?: string | null; min_cooldown_seconds?: number; tags?: string[]; agent_kind?: string | null; execution_config?: Record | null; }): Promise; /** * Create a new automation_versions row carrying the version-bound fields, then * upgrade the automation's `current_version_id` to that new version. Server * inherits unset fields from the previous version row. * name/description/prompt/sources are version-owned (update rejects them). */ createAutomationVersion(payload: { automation_id: string; name?: string; description?: string | null; prompt?: string; skills?: Array<{ name: string; content: string; }>; sources?: AutomationSource[]; outputs?: Record | null; classifiers?: unknown[]; reactions_guidance?: string; change_notes?: string; /** When set, written atomically with the new version (set_as_current). */ triggers?: import("@lobu/core/contracts/tools/manage-automations").AutomationTrigger[]; }): Promise<{ version?: number; }>; /** * Attach (or clear) a reaction script. Pass an empty string to remove it — * matches the admin tool contract. */ setReactionScript(automationId: string, reactionScript: string): Promise; /** * Delete an Automation by its numeric `automation_id` (code-managed prune). The * admin tool takes an array; we delete one slug's Automation at a time so a * failure is attributable. */ deleteAutomation(automationId: string): Promise; private connectionsTool; private feedsTool; private authProfilesTool; private catalogTool; /** Installed org connectors + (with `includeInstallable`) the bundled catalog. */ listConnectors(includeInstallable?: boolean): Promise; /** * Idempotent connector install. The CLI can enable a reviewed catalog * connector by id or pass connector source; server returns the resolved * connectorKey plus updated. */ installConnector(payload: CliInstallConnectorPayload): Promise; uninstallConnector(connectorKey: string): Promise; /** * Re-activate a retained connector version (org-local pointer flip — the * bytes already live in the org's `connector_versions` rows). The engine * behind `lobu rollback`'s connector pins. */ rollbackConnectorVersion(connectorKey: string, version: string): Promise; listAuthProfiles(): Promise; getAuthProfileBySlug(slug: string): Promise; createAuthProfile(payload: { slug: string; connector: string; kind: string; name?: string; credentials?: Record; }): Promise; updateAuthProfile(payload: { slug: string; name?: string; credentials?: Record; }): Promise; /** Re-issue a connect token for an existing interactive-auth profile. */ reconnectAuthProfile(slug: string): Promise; listConnections(): Promise; /** * Apply a BYO chat connection (a chat connector with a credential in `config`) * through the secret-aware `apply_chat_connection` path. Keyed by the * declared connection `slug` as the stable id (server stores it as * `agentconn-`); no owning agent — chat routing is an Automation created * when a channel is linked. The server compares resolved credentials under a * PG advisory lock, so an unchanged declaration is a true no-op. */ applyChatConnection(payload: { slug: string; connector: string; /** Omitted means preserve the server-derived/stored display name. */ name?: string; config: Record; }): Promise<{ id: number; created: boolean; changed: boolean; }>; createConnection(payload: { slug: string; connector: string; name?: string; authProfileSlug?: string; appAuthProfileSlug?: string; config?: Record; deviceWorkerId?: string; }): Promise; updateConnection(connectionId: number, payload: UpdateConnectionPayload): Promise; listFeeds(connectionId: number): Promise; createFeed(payload: { connectionId: number; feedKey: string; name?: string; /** Cron string, or null for manual-only. */ schedule?: string | null; config?: Record; }): Promise; updateFeed(feedId: number, payload: UpdateFeedPayload): Promise; } /** * Recognise duplicate-name errors from the admin tools without substring * matching the user-facing message. The server stamps a bracketed code into * the error message of every duplicate path `apply` upserts through * (`manage_entity_schema` create / add_rule → `[entity_type_exists]`, * `[relationship_type_exists]`, `[already_exists]`, all httpStatus 409). * * Anything else is NOT a duplicate. In particular a 422 schema-validation * error (`[invalid_schema]`, e.g. ">4 x-table-column fields") must surface * verbatim — the old status-only fallback treated any 4xx as "already * exists", retried with `action: update`, and buried the real message under * a misleading "Entity type not found" (issue #1177). The 409 fallback stays * as a belt-and-braces signal for duplicate paths that predate the codes. */ export declare function isDuplicateError(err: ApiError): boolean; interface ResolvedClient { client: ApplyClient; apiBaseUrl: string; orgSlug: string; } export declare function resolveApplyClient(opts: { url?: string; org?: string; applyId?: string; /** Set by `lobu rollback` — exempts the run from the promotions pause. */ rollbackOf?: string; fetchImpl?: typeof fetch; }): Promise; export {}; //# sourceMappingURL=client.d.ts.map