import type { ConnectorDefinition, EntityMetrics } from "@lobu/connector-sdk"; import type { AgentSettings } from "@lobu/core"; import type { AutomationEventTrigger, AutomationScheduleTrigger, AutomationWorkspaceEventTrigger } from "@lobu/core/contracts/tools/manage-automations"; import type { InferenceCapabilityBlock, InferenceModality, Project } from "../../../config/index.js"; import { type AutomationSource, type EntityBacking, type RelationshipRule } from "./shared.js"; export interface DesiredAgentMetadata { agentId: string; name: string; description?: string; } export interface DesiredEntityType { slug: string; name?: string; description?: string; required?: string[]; properties?: Record; /** * Event kinds (semantic types) for events linked to this type, keyed by * semantic_type — `{ description?, metadataSchema?, jsonTemplate? }`. Present * only when the type declares them; absent ⇒ never churns the diff. Persisted * to `entity_types.event_kinds` via manage_entity_schema. */ eventKinds?: Record; /** * The `x-lobu-resolution` metadata_schema key, lowered from the config's * `resolutionPolicy`. Present only when declared; folded into the upserted * metadata_schema (config wins over any out-of-band value) and diffed against * the remote `schemaExtras`. */ resolutionPolicy?: Record; /** * Write rules as raw source, read from the `rulesFromFile(path)` marker. * Present only when declared, so a type without rules never churns the diff. * The server compiles it; the CLI never ships a compiled artifact. */ rulesSource?: { sourcePath: string; sourceCode: string; }; /** * Default view template (render-DSL root node) for this type's detail page. * Present only when declared. Applied via manage_view_templates set/clear and * diffed against the remote current default (which apply-cmd fetches per * relevant type — NOT streamed in the entity-type list). Prune-aware: under * prune an absent template clears the remote one; otherwise it is left alone. */ viewTemplate?: Record; metadata?: Record; /** * Present only for derived (SQL-view-backed) entity types; absent ⇒ stored * (the default). Normalized so a stored type compares equal on both sides * (desired + remote both omit it) and never churns the diff — see * {@link EntityBacking}. */ backing?: EntityBacking; /** * Declared metric contract (eventSets/measures/dimensions/segments). Present * only when the type declares metrics; absent ⇒ not in the metric catalog. * Normalized so a type with no metrics compares equal on both sides and never * churns the diff (mirrors `backing`). */ metrics?: EntityMetrics; } export interface DesiredRelationshipType { slug: string; name?: string; description?: string; rules?: RelationshipRule[]; metadata?: Record; } export interface DesiredAutomation { slug: string; /** Owning agent id. Every automation belongs to exactly one agent. */ agent: string; executor?: { kind: "script"; source: string; params?: Record; } | null; name?: string; description?: string; triggers?: DesiredAutomationTrigger[]; /** * The Automation's task statement, authored via `defineAutomation({ prompt })`. * Stored as the version's frozen instruction text (the internal `prompt` * column). Empty when the Automation's whole job is its skills, or when an * event-turn Automation relies on the server-side default at run time. */ prompt: string; /** * Ordered skill names as written in config — the authoring input, resolved * into {@link DesiredAutomation.skillSnapshots} at load time. */ skills?: string[]; /** * The resolved `{name, content}` pairs actually sent to the server and pinned * onto the version. Separate from `skills` because the diff must compare * BODIES, not names: editing a skill's text changes nothing about the name * list, and a name-only diff would let a re-apply silently skip the update. */ skillSnapshots?: Array<{ name: string; content: string; }>; /** Optional SQL data sources; server applies a default when omitted. */ sources?: AutomationSource[]; /** * Reaction script — TypeScript source compiled + executed in an isolate at * automation-firing time. Authored as a sibling `.ts` file referenced by * `defineAutomation({ reaction: reactionFromFile("./reactions/foo.reaction.ts") })`; * the CLI reads it and pushes raw source via `set_reaction_script`. * Omitted preserves a previously installed reaction; explicit `null` * (from `defineAutomation({ reaction: null })`) removes it. */ reactionScript?: { sourcePath: string; sourceCode: string; } | null; /** LLM guidance for the automation's downstream reaction agent. */ reactionsGuidance?: string; /** UUID of a device worker to pin this automation's runs to (see `device_workers.id`). */ deviceWorkerId?: string; /** Execution model override; omitted preserves remote, null clears it. */ model?: string | null; /** Minimum seconds between two firings of this automation (0 = no cooldown). */ minCooldownSeconds?: number; /** Free-form tags for filtering. */ tags?: string[]; /** Optional agent-kind override (e.g. "background", "notifier"). */ agentKind?: string; /** Named durable outputs; explicit null clears existing declarations. */ outputs?: Record | null; /** Classifier definitions for extraction (server-side feature). */ classifiers?: unknown[]; } /** Apply-internal trigger form; `connectionSlug` is resolved before mutation. */ export type DesiredAutomationEventTrigger = AutomationEventTrigger & { connectionSlug?: string; }; export type DesiredAutomationTrigger = DesiredAutomationEventTrigger | AutomationWorkspaceEventTrigger | AutomationScheduleTrigger; export interface DesiredFeed { /** Feed key from the connector definition (`FeedDefinition.key`). */ feedKey: string; name?: string; /** * Cron for automatic sync, tri-state after map-config: a string sets the * cadence, `null` clears it (manual-only), and omitted means the config does * not manage it — an update leaves the feed's remote cadence alone, and a * create gets no cron (the platform never invents a default). */ schedule?: string | null; config?: Record; } export interface DesiredConnection { /** Stable public identifier — diff key. */ slug: string; /** Connector key (e.g. `github`, `hackernews`). */ connector: string; name?: string; /** Slug of the runtime/account auth profile (`auth:` in the manifest). */ authProfileSlug?: string; /** Slug of the OAuth-app auth profile (`app_auth:` in the manifest). */ appAuthProfileSlug?: string; config?: Record; /** * Set only for an explicitly declared BYO chat connection. Chat connections * apply through the secret-aware `apply_chat_connection` path so the server * persists a non-null `credential_mode` (the gateway only treats non-null * rows as chat) and resolves the token. Absent for data connectors and hosted * chat (which is filtered out of apply entirely). */ credentialMode?: "byo"; /** * Optional UUID pinning the connection's syncs/actions to a specific device * worker (`device_workers.id`). Required for connectors that declare a * `required_capability`; omit it for serverless-on-Lobu runs. */ deviceWorkerId?: string; feeds: DesiredFeed[]; /** Source label for error messages (the config the connection came from). */ sourceFile: string; } export type DesiredAuthProfileKind = "env" | "oauth_app" | "oauth_account" | "browser_session"; export interface DesiredAuthProfile { /** Stable slug — diff key. */ slug: string; connector: string; kind: DesiredAuthProfileKind; name?: string; /** * key→value credentials. Values may be `$ENV` references (collected into * `requiredSecrets`). Only meaningful for `kind: env | oauth_app`; must be * absent/empty for `oauth_account | browser_session`. */ credentials?: Record; sourceFile: string; } export interface DesiredConnectorDefinition { /** Connector key — diff key (`null` until the server compiles a `.ts`). */ key: string | null; /** * Best-effort static `definition.key` parsed from `sourceCode` WITHOUT a * compile. Used only to widen the pre-diff schema-skip set so re-applying a * local connector whose feed set changed doesn't validate the connection * against the stale server schema. NOT used for diffing/pruning — `key` * stays the authoritative (server-compiled) value there. */ declaredKeyHint?: string; /** Local `.ts` path (absolute) — mutually exclusive with `sourceUrl`. */ sourcePath?: string; /** Remote URL — mutually exclusive with `sourcePath`. */ sourceUrl?: string; /** * Raw TypeScript source read from `sourcePath`, pushed verbatim to the * server (which compiles, extracts metadata, and returns the real `key`). * Absent when `sourceUrl` is used. */ sourceCode?: string; /** For error messages — the `.connector.ts` file or `type: connector` doc. */ sourceFile: string; } export interface DesiredAgent { metadata: DesiredAgentMetadata; /** * Settings payload destined for `PATCH /:agentId/config`. Built by the mapper * + agent-dir loader: networkConfig, skillsConfig, * preApprovedTools, guardrails, toolsConfig, nixConfig, * models (ordered `/` refs), * identityMd/soulMd/userMd. */ settings: Partial; /** * Provider API keys resolved from `secret()` / `$VAR` provider keys, pushed * into `agent_secrets` after the settings PATCH. Empty when no provider * declared a `key` (or all are unset). The value lives only in process * memory; never serialized. */ providerKeys: { providerId: string; value: string; }[]; } /** * An org-owned inference provider resolved from `defineConfig({ providers })`. * The `apiKey` is resolved from its `secret()` / `$VAR` ref at map time (lives * only in process memory, never serialized into a plan row) — like an agent * provider key. Reconciled by apply against the `/inference-providers` API. */ export interface DesiredOrgProvider { slug: string; kind: string; displayName?: string; /** Resolved API key value (never the `$VAR` placeholder). */ apiKey: string; /** Per-modality upstream overrides; empty ⇒ static semantics. */ capabilities: Partial>; } export interface DesiredState { agents: DesiredAgent[]; /** * When true (`defineConfig({ prune: true })`), `lobu apply` deletes org-owned * definitions (entity/relationship types, automations, connector definitions) * absent from this config — including ones created in the UI. Data, * connections, auth profiles, and agents are never pruned. Default false. */ prune: boolean; /** * Org metadata from `defineConfig` — the org slug `lobu apply` defaults to, * the `organizationId` it matches against, and the name/description shown * when telling the operator to create the org. */ memory?: { org?: string; organizationId?: string; name?: string; description?: string; }; memorySchema: { entityTypes: DesiredEntityType[]; relationshipTypes: DesiredRelationshipType[]; }; /** Automations declared via `defineAutomation`. */ automations: DesiredAutomation[]; /** * Connectors: local `*.connector.ts` definitions (declared via * `connectorFromFile`), `defineConnection`s, and `defineAuthProfile`s. */ connectors: { definitions: DesiredConnectorDefinition[]; authProfiles: DesiredAuthProfile[]; connections: DesiredConnection[]; }; /** * Names of env vars referenced via `secret()` / `$VAR` (provider keys, * auth-profile + mcp credentials). The CLI surfaces these before mutating * remote state so missing secrets fail loud instead of expanding to empty. */ requiredSecrets: string[]; /** * Org-owned inference providers declared via `defineConfig({ providers })`. * Reconciled against the `/inference-providers` API — created if missing, * capability blocks updated per-modality, key rotated on every apply * (idempotent; the key can't be read back). Absent providers are reported as * drift, never auto-deleted. */ providers: DesiredOrgProvider[]; } export interface ResolvedConnectorSchemas { /** Connector key → `optionsSchema` (JSON Schema), if declared. */ optionsSchema?: Record; /** Every feed key declared by the connector (`connector.feeds` keys). */ feedKeys: Set; /** Feed key → `configSchema` (JSON Schema), for keys that declare one. */ feedConfigSchemas: Map>; /** Allowed auth-profile kinds for the connector (from `authSchema.methods`). */ authKinds: Set; } /** * Build per-connector validation schemas from a connector definition. Accepts * either a typed `ConnectorDefinition` (from `@lobu/connector-sdk`) or the * snake_cased shape from `manage_catalog` connector entries * (`options_schema`, `feeds_schema`, `auth_schema`). */ export declare function resolveConnectorSchemas(def: ConnectorDefinition | { options_schema?: Record | null; feeds_schema?: Record | null; auth_schema?: Record | null; }): ResolvedConnectorSchemas; /** * Validate a single connection (+ its feeds) and its referenced auth-profile * kinds against a resolved connector schema. Pass `null` to skip schema * checks (e.g. a connector that only exists as a local `.ts` not yet * compiled by the server) — structural checks have already run at load time. */ export declare function validateConnectionAgainstConnector(connection: DesiredConnection, authProfiles: ReadonlyMap, schemas: ResolvedConnectorSchemas | null): void; /** Keys the connector treats as feed-scoped (declared in any feed's `configSchema`). */ export declare function feedScopedKeys(schemas: ResolvedConnectorSchemas): Set; /** * The server stores feed-scoped settings on feeds, not the connection, and * REJECTS a connection whose config carries any feed-scoped key (see * `splitConfigByFeedScope` in packages/server). `lobu apply` mirrors that split * here: any feed-scoped key found in a connection's `config` is demoted to a * per-feed default (an explicit feed value wins) and removed from the * connection config. `managedBy` is Lobu metadata, never a connector option, so * it stays on the connection. Returns the demoted key names so the caller can * warn. Mutates `connection` in place so the normalized shape flows into the * diff + create/update payloads. */ export declare function normalizeConnectionConfigScope(connection: DesiredConnection, schemas: ResolvedConnectorSchemas | null): string[]; export declare function validateAuthProfileAgainstConnector(profile: DesiredAuthProfile, schemas: ResolvedConnectorSchemas | null): void; interface LoadDesiredStateOptions { /** Project root (directory containing `lobu.config.ts`). */ cwd: string; /** Env to resolve `$VAR` refs against; defaults to `process.env`. */ env?: NodeJS.ProcessEnv; /** * When set, only the named resource family is loaded — `"agents"` and * `"memory"` both skip the `connectors/` dir (and its `$VAR` credential * expansion), so `--only agents` doesn't require connector secrets. */ only?: "agents" | "memory"; } /** * Resolve the project's explicit `connectors: [connectorFromFile(...)]` list * into connector definitions to compile + ship. Replaces directory * auto-discovery: only listed connectors are uploaded. Paths are relative to * the config dir and guarded (no absolute, `..`, or backslash escapes), * mirroring `resolveReactionScript`. * * Each source ships with `key: null`; `apply-cmd` compiles each `sourcePath` on * the CLI (where the project's node_modules is available) and the server * resolves the real key. We intentionally do NOT compile/instantiate here to * resolve the key eagerly — that would force a full esbuild + module load on * every load (including `--dry-run`) for no benefit, since the server is the * source of truth for the compiled key. A connection that references a * connector by a bare *string* key relies on that string matching the file's * compiled `definition.key`; reference it by its `defineConnector` class * (`connector: myConnector`) to make that match exact. */ /** * Best-effort extraction of a connector's static `definition.key` from its * source WITHOUT compiling it. Connector keys are always plain string literals * (the platform indexes on them statically), declared as the first `key:` in * the `definition` object — e.g. `readonly definition: ConnectorDefinition = { * key: "linkedin", ...`. Returns `null` when the shape can't be matched with * confidence, preserving the prior null-key semantics (the server remains the * authoritative source of the compiled key; this hint only affects which * connectors the pre-diff pass treats as locally-declared). */ export declare function extractDeclaredConnectorKey(sourceCode: string): string | null; /** * Import a `lobu.config.ts` and return its `defineConfig` default export (the * SDK {@link Project}). Shared by {@link loadDesiredStateFromConfig} (apply) and * the commands that read the authored config directly (`lobu run` preview * registration, `lobu doctor`, `lobu chat`, `lobu validate`, `lobu memory seed`). * * Uses jiti — the same runtime TypeScript loader Next.js/Nuxt use for their * `*.config.ts` — which transpiles on import and resolves the config's imports * (`@lobu/cli/config`, `@lobu/connector-sdk`, relative reaction/connector files) * from the project. No bundling, no temp file. The dynamic `import("jiti")` is * lazy + allow-listed (AGENTS.md). */ export declare function loadProjectConfig(cwd: string): Promise<{ project: Project; configPath: string; }>; /** * Load desired state from a TypeScript entrypoint (`lobu.config.ts`): import the * `defineConfig()` project, map it to `DesiredState`, then attach the * file-based artifacts (agent-dir markdown + skills, Automation executor and * reaction scripts, local connector source). */ export declare function loadDesiredStateFromConfig(opts: LoadDesiredStateOptions): Promise<{ state: DesiredState; configPath: string; warnings: string[]; }>; export {}; //# sourceMappingURL=desired-state.d.ts.map