import { SystemModelMessage, LanguageModelCallOptions, RequestOptions, streamText, JSONSchema7, ModelMessage, TextStreamPart, ToolSet } from 'ai'; /** * Account-owned code hook upload validation for Convex config-plane sync. * Mirrors core's account hook upload contract without requiring the Node runtime. */ declare const AGENT_HOOK_EVENT_NAMES: readonly ["agent.started", "agent.step.finished", "agent.finished", "agent.failed", "agent.approval.required", "tool.call.started", "tool.call.finished", "tool.result", "subagent.task.started", "subagent.task.finished", "channel.message.received", "channel.message.sending"]; type AgentHookEventName = (typeof AGENT_HOOK_EVENT_NAMES)[number]; /** * Every Vercel AI SDK provider that ships language models, plus `custom` * (any OpenAI-compatible endpoint) and `minimax`. Image-, speech- and * transcription-only providers are deliberately absent: they cannot back * `config.model`. */ declare const MODEL_PROVIDERS: { readonly anthropic: { readonly label: "Anthropic"; readonly modelPlaceholder: "claude-sonnet-4-5-20250929"; }; readonly azure: { readonly label: "Azure OpenAI"; readonly modelPlaceholder: "gpt-4.1-mini"; }; readonly baseten: { readonly label: "Baseten"; readonly modelPlaceholder: "deepseek-ai/DeepSeek-V3"; }; readonly bedrock: { readonly label: "Amazon Bedrock"; readonly modelPlaceholder: "anthropic.claude-sonnet-4-5-20250929-v1:0"; }; readonly cerebras: { readonly label: "Cerebras"; readonly modelPlaceholder: "llama3.1-8b"; }; readonly cohere: { readonly label: "Cohere"; readonly modelPlaceholder: "command-a-03-2025"; }; readonly custom: { readonly label: "Custom OpenAI-compatible"; readonly modelPlaceholder: "gpt-oss-120b"; }; readonly deepinfra: { readonly label: "DeepInfra"; readonly modelPlaceholder: "deepseek-ai/DeepSeek-V3"; }; readonly deepseek: { readonly label: "DeepSeek"; readonly modelPlaceholder: "deepseek-chat"; }; readonly fireworks: { readonly label: "Fireworks"; readonly modelPlaceholder: "accounts/fireworks/models/deepseek-v3"; }; readonly google: { readonly label: "Google"; readonly modelPlaceholder: "gemini-2.5-flash"; }; readonly groq: { readonly label: "Groq"; readonly modelPlaceholder: "llama-3.3-70b-versatile"; }; readonly minimax: { readonly label: "MiniMax"; readonly modelPlaceholder: "MiniMax-M2.7"; }; readonly mistral: { readonly label: "Mistral"; readonly modelPlaceholder: "mistral-large-latest"; }; readonly openai: { readonly label: "OpenAI"; readonly modelPlaceholder: "gpt-4.1-mini"; }; readonly perplexity: { readonly label: "Perplexity"; readonly modelPlaceholder: "sonar-pro"; }; readonly togetherai: { readonly label: "Together.ai"; readonly modelPlaceholder: "deepseek-ai/DeepSeek-V3"; }; readonly v0: { readonly label: "Vercel v0"; readonly modelPlaceholder: "v0-1.0-md"; }; readonly vercel: { readonly label: "Vercel AI Gateway"; readonly modelPlaceholder: "openai/gpt-4.1-mini"; }; readonly vertex: { readonly label: "Google Vertex AI"; readonly modelPlaceholder: "gemini-2.5-flash"; }; readonly xai: { readonly label: "xAI Grok"; readonly modelPlaceholder: "grok-4"; }; }; type AccountModelProviderName = keyof typeof MODEL_PROVIDERS; /** * Agent config validation, the only copy of it: Convex config HTTP checks a * config on write and core re-checks the stored config on every run. Pure * module: safe for the default Convex runtime. The public projection lives in * ./responses.ts. */ declare const AGENT_HARNESS_TYPES: readonly ["claude-code", "codex", "deepagents", "opencode", "pi"]; declare const AGENT_HARNESS_DEBUG_LEVELS: readonly ["error", "warn", "info", "debug", "trace"]; declare const AGENT_HARNESS_PERMISSION_MODES: readonly ["allow-reads", "allow-edits", "allow-all"]; declare const AGENT_LIFECYCLE_EVENT_NAMES: readonly ["agent.started", "agent.step.finished", "agent.finished", "agent.failed", "agent.approval.required", "tool.call.started", "tool.call.finished", "tool.result", "subagent.task.started", "subagent.task.finished"]; /** * Shared validation for MCP server registrations (#331). One normalizer * serves every write path (CLI sync, direct API, dashboard). A `url` makes an "http" row * core connects to over the stateless 2026-07-28 transport; a `bundle` makes * a "hosted" row served by the mcp-runner Lambda, hashed here so sha256 * always travels with the bundle. Auth header values may carry ${NAME} * account env refs; they resolve into the encrypted agent config at sync * time, never on this row, and credential-bearing headers must use one * instead of an inline secret. `oauth` follows the same rule: clientSecret * and refreshToken must be ${NAME} refs, so the row never holds a secret. */ /** * OAuth 2.0 refresh-token grant for an external row. Core mints access tokens * at connect time and sends `Authorization: Bearer `, so a server * whose tokens expire (Google's Workspace MCP endpoints) still works where a * static header cannot. Secret fields hold ${NAME} refs on the row. */ interface McpOauth { clientId: string; clientSecret: string; refreshToken: string; /** Token endpoint, https only; core defaults to https://oauth2.googleapis.com/token. */ tokenUrl?: string; } /** * Agent configuration: types for the per-agent settings object, the runtime * projection of a stored config, and encryption helpers. The validation rules * are the config plane's (`@broods/convex/model/agentRules`), so a config is * judged the same on write and on every run. * Account types and auth live in `./accounts.ts` and `../auth.ts`. */ interface AgentConfig { agent?: AgentBehaviorConfig; harness?: AgentHarnessConfig; model?: AgentModelConfig; provider?: AgentProviderConfig; sandboxes?: string[]; workspaces?: AgentWorkspaceRef[]; session?: AgentSessionConfig; hooks?: AgentHooksConfig; channels?: AgentChannelsConfig; tools?: AgentToolsConfig; /** Connected MCP servers, keyed by their config-plane row id (#331). */ mcp?: AgentMcpConfig; /** * Tool names withheld for this run, applied after the tool set is built. * Set by a channel record; a channel can take a tool away, never add one. * Names that are not present are ignored. */ denyTools?: string[]; skills?: AgentSkillsConfig; subagent?: AgentSubagentConfig; scheduler?: AgentSchedulerConfig; /** Policies that gate this agent. Each one carries its own enforcement mode. */ policies?: string[]; publicAccess?: boolean; allowRunOverrides?: boolean; [key: string]: unknown; } interface AgentBehaviorConfig { maxTurn?: number; system?: string | SystemModelMessage | SystemModelMessage[]; [key: string]: unknown; } interface AgentHarnessConfig { activeTools?: string[]; debug?: AgentHarnessDebugConfig; inactiveTools?: string[]; type: (typeof AGENT_HARNESS_TYPES)[number]; permissionMode?: (typeof AGENT_HARNESS_PERMISSION_MODES)[number]; startupTimeoutMs?: number; webSearch?: boolean; } interface AgentHarnessDebugConfig { enabled?: boolean; level?: (typeof AGENT_HARNESS_DEBUG_LEVELS)[number]; subsystems?: string[]; } type StreamTextOptions$1 = Parameters[0]; type AgentModelProviderOptions = StreamTextOptions$1["providerOptions"]; interface AgentSkillsConfig { enabled?: boolean; allowed?: string[]; [key: string]: unknown; } /** * Opt-in for the `schedule` tool. Off by default: a scheduled task starts * billable agent runs long after the turn that asked for it. */ interface AgentSchedulerConfig { enabled?: boolean; [key: string]: unknown; } interface AgentSubagentConfig { enabled?: boolean; allowed?: string[]; context?: "new" | "inherited"; mode?: "ephemeral" | "persistent"; /** * Publishes child reasoning, text, and tool stream parts to the existing * WebSocket/JetStream response path. Off by default. */ stream?: boolean; /** * Controls what the parent agent sees from a finished subagent (AI SDK * "controlling what the model sees"): `full` = the child's whole transcript, * `result` = only its final result (default), `none` = nothing. A * `subagent.task.finished` code hook overrides this for custom shaping. */ visibility?: "full" | "result" | "none"; [key: string]: unknown; } interface AgentModelConfig extends LanguageModelCallOptions, Partial> { provider?: AccountModelProviderName; modelId?: string; /** * Speech-to-text model for inbound audio, on the same provider and key as * `modelId`. Defaults to the widest-container model the provider ships. */ transcriptionModelId?: string; providerOptions?: AgentModelProviderOptions; output?: AgentModelOutputConfig; } type AgentModelOutputConfig = ({ type: "text"; } & AgentModelOutputMetadata) | ({ type: "object"; schema: JSONSchema7; } & AgentModelOutputMetadata) | ({ type: "array"; element: JSONSchema7; } & AgentModelOutputMetadata) | ({ type: "choice"; options: string[]; } & AgentModelOutputMetadata) | ({ type: "json"; } & AgentModelOutputMetadata); type AgentModelOutputMetadata = { name?: string; description?: string; [key: string]: unknown; }; type AgentProviderConfig = Partial>; interface AgentProviderSettings { apiKey?: string; /** OpenAI-compatible endpoint (`custom`). Snake form, as documented. */ base_url?: string; /** OpenAI-compatible endpoint (`custom`). AI-SDK form; the dashboard writes both. */ baseURL?: string; headers?: Record; /** Endpoint label; becomes the provider id and the pi harness env prefix. */ name?: string; organization?: string; project?: string; [key: string]: unknown; } interface AgentWorkspaceRef { name: string; workspaceId: string; sandbox?: string | null; } interface AgentSessionConfig { pruning?: AgentSessionPruningConfig; compaction?: AgentSessionCompactionConfig; [key: string]: unknown; } interface AgentSessionPruningConfig { enabled?: boolean; [key: string]: unknown; } interface AgentSessionCompactionConfig { enabled?: boolean; maxContextLength?: number; [key: string]: unknown; } interface AgentHooksConfig { /** Outbound event webhooks. An agent may register several independent endpoints. */ webhooks?: AgentWebhookHookConfig[]; /** * Uploaded code hooks. Each entry references an accountHooks bundle by id; the * bundle runs in the V8 isolate at the matching fire-points and its validated * return is folded into mutable harness state. */ code?: AgentCodeHookConfig[]; [key: string]: unknown; } interface AgentCodeHookConfig { hookId: string; /** * Optional narrowing of the events this reference reacts to. Omitted => the * bundle's own declared `events` set. Any listed event outside the bundle's * declared set is ignored at runtime. */ events?: AgentHookEventName[]; enabled?: boolean; [key: string]: unknown; } interface AgentWebhookHookConfig { enabled?: boolean; url?: string; secret?: string; events?: AgentLifecycleEventName[]; [key: string]: unknown; } type AgentLifecycleEventName = (typeof AGENT_LIFECYCLE_EVENT_NAMES)[number]; type AgentToolsConfig = Record; interface AgentToolConfig { enabled?: boolean; needsApproval?: boolean; async?: boolean; config?: Record; [key: string]: unknown; } type AgentMcpConfig = Record; interface AgentMcpEntry { enabled?: boolean; /** Applies to every tool the server exposes. */ needsApproval?: boolean; /** Extra request headers; values resolved from account env vars at sync. */ headers?: Record; /** * Overrides for the row's oauth credentials; values resolved from account * env vars at sync, so the row's ${NAME} refs never reach the token * endpoint. tokenUrl stays on the row, where registration checked it. */ oauth?: Partial>; [key: string]: unknown; } interface AgentChannelsConfig { telegram?: AgentTelegramChannelConfig; github?: AgentGitHubChannelConfig; slack?: AgentSlackChannelConfig; discord?: AgentDiscordChannelConfig; pancake?: AgentPancakeChannelConfig; zalo?: AgentZaloChannelConfig; matrix?: AgentMatrixChannelConfig; [key: string]: unknown; } /** * How an attached partitioned workspace splits its folders for runs arriving * through this door. `shared` mounts the workspace root; `conversation` mounts * a private child folder per thread, issue or chat under `alias`. */ type ChannelPartition = { by: "shared"; alias?: never; } | { by: "conversation"; alias: string; }; interface AgentTelegramChannelConfig { id?: string; apiUrl?: string; botToken?: string; webhookSecret?: string; allowedChannelIds?: string[]; allowedUserIds?: string[]; /** Bot's @username, e.g. `tracy_bot`. Set it to answer only when the agent is mentioned. */ botUsername?: string; reactionEmoji?: string; trace?: "enabled" | "disabled"; partition?: ChannelPartition; [key: string]: unknown; } interface AgentGitHubChannelConfig { id?: string; apiUrl?: string; webhookSecret?: string; appId?: string; privateKey?: string; allowedChannelIds?: string[]; allowedUserIds?: string[]; /** Bot username for @-mention detection (e.g. "my-bot" or "my-bot[bot]"). */ botUserName?: string; /** Bot's numeric GitHub user ID for self-message detection. */ botUserId?: number; /** When false, the bot does not auto-trigger on new issues (opened/edited/reopened). Defaults to true. The bot still triggers when assigned to an issue. */ triggerOnIssueOpen?: boolean; /** When false, the bot does not auto-trigger on new PRs (opened/edited/reopened). Defaults to true. The bot still triggers when assigned to a PR. */ triggerOnPROpen?: boolean; trace?: "enabled" | "disabled"; partition?: ChannelPartition; [key: string]: unknown; } interface AgentSlackChannelConfig { id?: string; apiUrl?: string; botToken?: string; signingSecret?: string; allowedChannelIds?: string[]; allowedUserIds?: string[]; reactionEmoji?: string; trace?: "enabled" | "disabled"; partition?: ChannelPartition; [key: string]: unknown; } interface AgentDiscordChannelConfig { id?: string; apiUrl?: string; botToken?: string; publicKey?: string; allowedChannelIds?: string[]; allowedUserIds?: string[]; /** Bot's Discord user id. Set it to answer only when the agent is mentioned. */ botUserId?: string; /** Role ids that count as mentioning the agent, e.g. an on-call role. */ mentionRoleIds?: string[]; trace?: "enabled" | "disabled"; partition?: ChannelPartition; [key: string]: unknown; } /** * A Matrix account the agent speaks as. Matrix has no bot concept, so this is * usually a person's own account: replies carry a per-message profile named * `botName`, and `mentionText` decides what addresses the agent, because a * mention of the account would also be a mention of its owner. */ interface AgentMatrixChannelConfig { id?: string; /** Homeserver base URL, e.g. `https://matrix.org`. */ apiUrl?: string; /** Access token of the account; `apps/matrix-forwarder` syncs with it. */ botToken?: string; /** Room ids. */ allowedChannelIds?: string[]; /** Matrix user ids, e.g. `@alice:matrix.org`. */ allowedUserIds?: string[]; /** Name replies are shown under. Unset, replies look like the account's own messages. */ botName?: string; /** * Text that addresses the agent, e.g. `@georgi-ai`. Unset, a mention of the * account does. Other messages are stored as context either way. */ mentionText?: string; trace?: "enabled" | "disabled"; partition?: ChannelPartition; [key: string]: unknown; } interface AgentPancakeChannelConfig { allowedChannelIds?: string[]; allowedUserIds?: string[]; id?: string; pageId?: string; pageAccessToken?: string; webhookSecret?: string; senderId?: string; trace?: "enabled" | "disabled"; partition?: ChannelPartition; [key: string]: unknown; } interface AgentZaloChannelConfig { allowedChannelIds?: string[]; allowedUserIds?: string[]; id?: string; botToken?: string; webhookSecret?: string; trace?: "enabled" | "disabled"; partition?: ChannelPartition; [key: string]: unknown; } /** * Cron-job records and patch-merge helpers for the runtime. Input * normalization lives in the config plane (packages/convex/model/cronRules.ts, * the single home of those rules). */ type CronStatus = "active" | "paused"; type CronLastStatus = "started" | "completed" | "failed"; /** * One-of run payload mirroring the agent direct API's AgentRunInput: provide a * single `input` string (wrapped into one user message) or a full `events` list. */ type CronRunInput = { input: string; events?: never; } | { events: ModelMessage[]; input?: never; }; type CreateCronInput = { name: string; description?: string; agentId: string; conversationKey?: string; scheduleExpression: string; timezone?: string; status?: CronStatus; } & CronRunInput; type UpdateCronInput = { name?: string; description?: string | null; agentId?: string; conversationKey?: string | null; scheduleExpression?: string; timezone?: string | null; status?: CronStatus; } & ({ input?: string; events?: never; } | { events?: ModelMessage[]; input?: never; }); /** * Sandbox-config validation for the Convex config plane. Ports core's former * storage/sandbox-config.ts normalizer so the public /v1/sandboxes contract * is unchanged. The public projection lives in ./responses.ts. */ declare const SANDBOX_PROVIDERS: readonly ["sandbox", "lambda", "e2b", "daytona", "vercel", "machine"]; declare const SANDBOX_RUNTIMES: readonly ["bash", "python", "node"]; declare const SANDBOX_PERMISSION_MODES: readonly ["edit", "ask", "bypass"]; declare const SANDBOX_NETWORK_MODES: readonly ["allow-all", "deny-all", "restricted"]; declare const SANDBOX_SIZE_NAMES: readonly ["tiny", "xsmall", "small", "medium", "large"]; type SandboxProvider = (typeof SANDBOX_PROVIDERS)[number]; type RuntimeName = (typeof SANDBOX_RUNTIMES)[number]; type PermissionMode = (typeof SANDBOX_PERMISSION_MODES)[number]; type NetworkMode = (typeof SANDBOX_NETWORK_MODES)[number]; type SandboxSize = (typeof SANDBOX_SIZE_NAMES)[number]; /** * Idle and maximum lifetime controls for a persistent sandbox. */ interface SandboxLifecycleConfig { idleTimeoutSeconds?: number; maxLifetimeSeconds?: number; } interface SandboxNetworkConfig { mode: NetworkMode; allowDomains?: string[]; allowCidrs?: string[]; } /** * Account-scoped reusable sandbox configuration referenced by agents. */ interface SandboxConfig { provider: SandboxProvider; fallbackProvider?: SandboxProvider; size?: SandboxSize; snapshot?: string; runtimes?: RuntimeName[]; network?: SandboxNetworkConfig; permissionMode?: PermissionMode; persistent?: boolean; lifecycle?: SandboxLifecycleConfig; onCreate?: string[]; onResume?: string[]; timeout?: number; memoryLimit?: number; outputLimitBytes?: number; envVars?: Record; options?: Record; } declare const WORKSPACE_STORAGE_PROVIDERS: readonly ["s3"]; type WorkspaceStorageProvider = (typeof WORKSPACE_STORAGE_PROVIDERS)[number]; type WorkspaceStorageAuth = { type: "managed"; } | { type: "assumeRole"; roleArn: string; externalId?: string; }; interface WorkspaceStorageConfig { provider: WorkspaceStorageProvider; bucket?: string; region?: string; endpoint?: string; prefix?: string; auth?: WorkspaceStorageAuth; } interface WorkspaceConfig { storage: WorkspaceStorageConfig; isolation?: boolean; harness?: { workspace?: { enabled?: boolean; }; memory?: { enabled?: boolean; }; }; } /** * Agent-policy validation for the Convex config plane. Ports core's public * CRUD normalizer so policy documents keep the account-management API * contract. The public projection lives in ./responses.ts. */ declare const AGENT_POLICY_ACTIONS: readonly ["agent.invoke", "tool.call", "workspace.read", "workspace.write", "workspace.exec", "subagent.run", "skill.load"]; /** * API action namespace for account roles: one read/write pair per config-plane * resource route. Roles carry the same PolicyDocument shape as agent policies; * each caller passes its action set to `normalizePolicyDocument`. */ declare const API_POLICY_ACTIONS: readonly ["account:read", "account:write", "agents:read", "agents:write", "channels:read", "channels:write", "crons:read", "crons:write", "env:read", "env:write", "hooks:read", "hooks:write", "mcp:read", "mcp:write", "policies:read", "policies:write", "sandboxes:read", "sandboxes:write", "skills:read", "skills:write", "tools:read", "tools:write", "workspaces:read", "workspaces:write"]; type AgentPolicyAction = (typeof AGENT_POLICY_ACTIONS)[number]; type ApiPolicyAction = (typeof API_POLICY_ACTIONS)[number]; type PolicyAction = AgentPolicyAction | ApiPolicyAction; interface PolicyCondition { attribute: string; operator: PolicyConditionOperator; value: string | number | boolean | string[] | number[] | boolean[]; } type PolicyConditionOperator = "equals" | "notEquals" | "in" | "notIn" | "prefix" | "contains"; /** * Versioned policy document accepted by account-management CRUD. */ interface PolicyDocument { version: 1; /** How hard this policy bites where it is attached. Omitted reads as `audit`. */ mode?: "enforce" | "audit"; rules: PolicyRule[]; } type PolicyEffect = "allow" | "deny"; interface PolicyResourceSelector { toolNames?: string[]; /** MCP registration ids, for scoping tool.call rules per server (#331). */ mcpIds?: string[]; workspaceIds?: string[]; workspaceNames?: string[]; filePaths?: string[]; subagentIds?: string[]; skillPaths?: string[]; /** Config-plane resource ids for API-action rules; "*" matches every id. */ resourceIds?: string[]; } interface PolicyRule { id: string; effect: PolicyEffect; actions: PolicyAction[]; resources?: PolicyResourceSelector; conditions?: PolicyCondition[]; } /** * Channel record validation for the config plane, the single home of the * normalizers core's `shared/domain/channel-record.ts` re-exports. Kept free * of Convex imports so the rules stay unit-testable; the public projection * lives in ./responses.ts. */ /** Where a reply lands: its own thread, or wherever the message came from. */ declare const CHANNEL_REPLY_TARGETS: readonly ["thread", "source"]; type ChannelReplyIn = (typeof CHANNEL_REPLY_TARGETS)[number]; /** Telegram channel adapter. */ interface TelegramSource { chatId: number; commandToken?: string; messageId: string; messageThreadId?: number; threadId: string; fromUserId?: number; fromUsername?: string; } /** * GitHub channel adapter. Event filtering and source mapping live here; auth and * API calls go through the Chat SDK. */ interface GitHubSource { owner: string; repo: string; installationId: number; threadId: string; messageId?: string; issueNumber?: number; pullNumber?: number; commentId?: number; target: "issue" | "issue_comment" | "pull_request" | "pull_request_review_comment"; } /** Slack channel adapter. */ interface SlackSource { teamId: string; channelId: string; /** Where the reply goes. Unset posts to the channel. */ threadTs?: string; /** The thread the message arrived in, unset when it arrived in the channel. */ inThreadTs?: string; messageTs?: string; responseUrl?: string; commandToken?: string; userId?: string; } /** Discord channel adapter. */ interface DiscordSource { applicationId: string; interactionToken?: string; interactionId?: string; guildId?: string; channelId?: string; threadId?: string; messageId?: string; commandToken?: string; userId?: string; } /** * Matrix channel adapter. * * Matrix delivers nothing over a webhook, so `apps/matrix-forwarder` holds a * `/sync` long-poll per account, decrypts what arrives and POSTs each room * message here. The forwarder also holds the account's end-to-end encryption * keys, which is why every event sent into a room goes back through it. * Media never does: an attachment's key rides the decrypted event, so this * module downloads, decrypts, encrypts and uploads files against the * homeserver itself. * * The wire shapes shared with the forwarder live in `matrix-wire.ts`. */ /** Where a reply goes: the room, and the thread when the message was in one. */ interface MatrixSource { encrypted: boolean; messageId: string; roomId: string; threadRootId?: string; userId: string; } /** * Pancake channel adapter. * * Per-conversation policy (e.g. skipping human-owned conversations by tag) is * not baked in: the parsed source carries `tagIds` so a user `onMessageReceived` * hook can decide to drop the message. */ interface PancakeSource { pageId: string; conversationId: string; messageId: string; messageType: "INBOX" | "COMMENT"; postId?: string; fromId?: string; fromName?: string; pageCustomerId?: string; tagIds?: string[]; } /** Zalo channel adapter, on the official Zalo Bot API. */ declare const ZALO_CHAT_TYPES: readonly ["PRIVATE", "GROUP"]; type ZaloChatType = (typeof ZALO_CHAT_TYPES)[number]; interface ZaloSource { chatId: string; chatType: ZaloChatType; messageId: string; senderId: string; senderName?: string; eventName: string; date?: number; } /** * Canonical CLI manifest wire types, the single source of truth shared by the * backend (cliSync.ts, cliHttp.ts) and the SDK/CLI (packages/broods). * * This file is intentionally type-only with no runtime imports so the SDK can * import it without pulling the Convex server module graph into its typecheck. */ type CliManifestResource = { kind: "agent" | "workspace" | "sandbox" | "cron" | "skill" | "hook" | "mcp" | "policy" | "channelRecord"; name: string; description?: string; config: unknown; }; type GeneratedIds = { agents: Record; workspaces: Record; sandboxes: Record; crons: Record; skills: Record; hooks: Record; mcp: Record; policies: Record; channelRecords: Record; }; type CliManifest = { version: 1; project: string; stage: string; resources: CliManifestResource[]; }; /** * Type contracts inherited from Convex and core domain/runtime modules. * Keep this file type-only so the public SDK does not bundle backend code. */ type Id = string & { readonly __tableName?: TableName; }; type Doc = Record & { readonly _id: Id; }; type ProjectDoc = Doc<"projects">; type StageDoc = Doc<"stages">; type AgentConfigDoc = Doc<"agentConfigs">; type WorkspaceConfigDoc = Doc<"workspaceConfigs">; type SandboxConfigDoc = Doc<"sandboxConfigs">; type CronDoc = Doc<"crons">; type StreamTextOptions = Parameters[0]; type JsonCallSettings = Partial>; type AgentRunModelOverrides = JsonCallSettings & Pick; type AgentRunOverrides = { system?: SystemModelMessage | SystemModelMessage[]; model?: AgentRunModelOverrides; }; type AgentRunEventInput = { /** Shorthand for a single user text message. */ input: string; events?: never; answers?: never; } | { /** Full-fidelity event list for multimodal content or tool responses. */ events: [ModelMessage, ...ModelMessage[]]; input?: never; answers?: never; }; /** * Resolves a run's events from either the explicit `events` list or the `input` * string shorthand, matching the core direct API's event contract. */ declare function resolveRunEvents(input: AgentRunEventInput): ModelMessage[]; /** * Shared direct-run stream contracts and SSE parsing helpers. */ type AgentStreamPart = TextStreamPart | { type: "structured-output"; output: unknown; }; /** Yield the payload of each `data:` line from an SSE response body. */ declare function readSseStream(body: ReadableStream): AsyncGenerator; /** * Shared WebSocket wire message contracts used by the SDK client and gateway. */ type IngressMode = "reject" | "followup" | "collect" | "steer"; type IngressStatus = "accepted" | "queued" | "applied" | "processing" | "awaiting_approval" | "awaiting_input" | "completed" | "failed" | "expired"; /** One open `ask_questions` prompt while a run is `awaiting_input`. */ interface PendingQuestion { /** Pass back as `answers[].statusId`. */ statusId: string; /** After this the prompt settles as no_answer. */ answerBy: string; questions: { id: string; header: string; question: string; options: { label: string; description?: string; }[]; allowFreeText?: boolean; }[]; } /** Answers one open prompt: option labels or free text, keyed by question id. */ interface QuestionAnswer { statusId: string; answers: Record; } /** Settles open prompts instead of sending events; the run resumes on this socket. */ type AgentRunAnswerInput = { answers: [QuestionAnswer, ...QuestionAnswer[]]; input?: never; events?: never; }; type WebSocketStreamMessage = AgentStreamPart | { type: "question-request"; questions: PendingQuestion[]; } | { type: string; [key: string]: unknown; }; type WebSocketServerMessage = { type: "meta"; sessionId: string; taskId: string; } | { type: "ack"; requestId: string; eventId: string; status: IngressStatus; statusUrl?: string; } | { type: "status"; requestId: string; eventId: string; status: IngressStatus | "not_found"; requestedMode?: IngressMode; appliedMode?: IngressMode; appliedToEventId?: string; statusUrl?: string; error?: string; } | { type: "attached"; requestId: string; eventId: string; status: IngressStatus; replayFromCursor?: string; replayThroughCursor?: string; statusUrl?: string; } | { type: "replay_unavailable"; requestId: string; eventId: string; status: IngressStatus | "not_found"; statusUrl?: string; } | WebSocketOutputMessage | WebSocketStreamMessage; /** * Durable-stream envelope around one stream part. The SDK unwraps `data` for * handlers and surfaces the envelope itself through `onOutput` so clients can * persist `cursor` for attach-based resume. */ type WebSocketOutputMessage = { type: "output"; eventId: string; cursor: string; replay: boolean; data: WebSocketStreamMessage; }; type WebSocketClientExecuteMessage = { type: "execute"; agentId: string; sessionId?: string; eventId?: string; /** Defaults to "steer": join the live run at its next step boundary. */ mode?: IngressMode; idempotencyKey?: string; } & (AgentRunEventInput | AgentRunAnswerInput) & AgentRunOverrides; type WebSocketClientControlMessage = { type: "control"; requestId: string; eventId: string; idempotencyKey?: string; /** Defaults to "steer": join the live run at its next step boundary. */ mode?: IngressMode; } & AgentRunEventInput; type WebSocketClientAttachMessage = { type: "attach"; requestId: string; agentId: string; conversationKey: string; eventId: string; /** The run id the accepting response returned; how core resolves the run. */ runId: string; afterCursor?: string; }; type WebSocketClientCancelMessage = { type: "cancel"; }; type WebSocketClientMessage = WebSocketClientExecuteMessage | WebSocketClientControlMessage | WebSocketClientAttachMessage | WebSocketClientCancelMessage; /** * Wire types for the public account-manage and harness APIs. These mirror * the deployed API contract (docs/api-reference/openapi.yaml is the source * of truth); they are intentionally independent of the runtime internals so * the SDK keeps working when the runtime is ported. */ interface Account { account: { accountId: string; username: string; }; secret: string; } interface Agent { accountId: string; agentId: string; name: string; } interface AsyncRequestAccepted { statusUrl: string; /** Account-unique id for this run; what the status URL names. */ runId: string; /** The eventId sent with the request, echoed back. */ eventId: string; agentId: string; status?: "accepted" | "queued" | "applied" | "processing"; requestedMode?: "reject" | "followup" | "collect" | "steer"; } interface AsyncStatus { status: "accepted" | "queued" | "applied" | "processing" | "awaiting_approval" | "awaiting_input" | "completed" | "failed" | "expired" | "not_found"; requestedMode?: "reject" | "followup" | "collect" | "steer"; appliedMode?: "reject" | "followup" | "collect" | "steer"; appliedToEventId?: string; result?: unknown; response?: unknown; error?: string; /** True when `failed` was a deliberate /stop, not a fault. */ stoppedByUser?: boolean; approvals?: ToolApprovalSummary[]; questions?: PendingQuestion[]; } interface Cron { accountId: string; cronId: string; name: string; description?: string; agentId: string; events: ModelMessage[]; conversationKey?: string; scheduleExpression: string; timezone?: string; status: CronStatus; createdAt: string; updatedAt: string; lastInvokedAt?: string; lastStatus?: CronLastStatus; lastError?: string; } interface CronRun { accountId: string; cronId: string; runId: string; eventId: string; conversationKey: string; status: CronLastStatus; result?: unknown; error?: string; startedAt: string; completedAt?: string; } interface Sandbox { sandboxId: string; name: string; } interface Skill { path: string; name: string; description: string; files?: Array<{ path: string; size?: number; }>; } /** A tool call held for user approval by an `ask`-mode sandbox. */ interface ToolApprovalSummary { approvalId: string; toolCallId: string; toolName: string; input: unknown; } interface Workspace { workspaceId: string; name: string; } /** * Shared WebSocket wire-protocol types for the observability gateway, used by * the gateway, the SDK/CLI, and the dashboard. Pure types + tiny pure helpers, * zero runtime deps. Kept separate from the agent-test websocket-contracts. */ declare const MAX_OBSERVABILITY_BACKFILL = 500; type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR"; type ObservabilityLogEntry = { ts: number; level: LogLevel; eventType: string; message: string; traceId?: string; accountId?: string; endpointId?: string; service?: string; agentId?: string; conversationKey?: string; data?: unknown; }; type ObservabilitySpanRow = { traceId: string; spanId: string; parentSpanId?: string; name: string; kind: "task" | "cron" | "subtask" | "model.step" | "tool.call" | "phase"; startTimeMs: number; endTimeMs: number; durationMs: number; status: "running" | "ok" | "error"; endpointId?: string; agentId?: string; conversationKey?: string; attributes?: Record; error?: string; }; type ObservabilitySubscribeMessage = { type: "subscribe"; stream: "logs" | "traces"; backfill?: number; liveOnly?: boolean; minLevel?: LogLevel; sandboxId?: string; }; type ObservabilityUnsubscribeMessage = { type: "unsubscribe"; stream: "logs" | "traces"; }; type ObservabilityFetchTraceMessage = { type: "fetchTrace"; traceId: string; }; type ObservabilityClientMessage = ObservabilitySubscribeMessage | ObservabilityUnsubscribeMessage | ObservabilityFetchTraceMessage; type ObservabilityReadyMessage = { type: "ready"; }; type ObservabilityBackfillMessage = { type: "backfill"; stream: "logs" | "traces"; entries: ObservabilityLogEntry[] | ObservabilitySpanRow[]; more?: boolean; error?: string; }; type ObservabilityLogMessage = { type: "log"; entry: ObservabilityLogEntry; }; type ObservabilitySpanMessage = { type: "span"; entry: ObservabilitySpanRow; }; type ObservabilityErrorMessage = { type: "error"; error: string; }; type ObservabilityServerMessage = ObservabilityReadyMessage | ObservabilityBackfillMessage | ObservabilityLogMessage | ObservabilitySpanMessage | ObservabilityErrorMessage; /** Narrow an unknown wire value to a LogLevel. */ declare function isLogLevel(value: unknown): value is LogLevel; declare function isObservabilityClientMessage(v: unknown): v is ObservabilityClientMessage; /** Whether a span is a top-level run, each of which owns its own trace. */ declare function isRootSpanKind(kind: ObservabilitySpanRow["kind"]): boolean; /** * Narrow a wire value to a sandbox log id. Strict on purpose: the gateway * interpolates it into LogQL, so only the UUID shape core mints may pass. */ declare function isSandboxLogId(value: unknown): value is string; /** Whether a value is a real trace id a log line can be followed to. */ declare function isTraceId(value: unknown): value is string; /** * Configurable client for running deployed agents over direct core SSE. * Stream chunks are the Vercel AI SDK's `TextStreamPart` parts that core emits. */ declare const DEFAULT_CORE_BASE_URL = "https://gateway.broods.app"; /** * Input for a single agent run. The core direct API is event-based (a list of * Vercel AI SDK model messages), so `events` is the full-fidelity form. Use it * for multimodal content (images/files), ephemeral system messages, or * tool-approval responses. `input` is a shorthand for a single user text message * and is wrapped into one user event. Provide exactly one of the two. */ type AgentRunInputBase = { conversationKey?: string; eventId?: string; /** Busy-conversation behavior. Defaults to `steer`: join the live run at its next step boundary (falls back to a FIFO follow-up). */ mode?: "reject" | "followup" | "collect" | "steer"; /** Stable retry identity within the conversation; defaults to eventId. */ idempotencyKey?: string; } & AgentRunOverrides; type AgentRunInput = AgentRunInputBase & AgentRunEventInput; interface AgentRunResult { text: string; events: TextStreamPart[]; } /** Input for `continue`: the conversation to re-enter. */ interface AgentContinueInput { /** The key given to `run`, or the scoped key a dashboard trace row shows. */ conversationKey: string; eventId?: string; } declare class IngressAcceptedError extends Error { readonly accepted: AsyncRequestAccepted; constructor(accepted: AsyncRequestAccepted); } interface AsyncPollOptions { intervalMs?: number; timeoutMs?: number; signal?: AbortSignal; } interface AsyncAgentRun extends AsyncRequestAccepted { conversationKey: string; poll(): Promise; wait(options?: AsyncPollOptions): Promise; } interface AgentReference { readonly kind: "agent"; readonly name: Name; readonly id: string; readonly project: string; readonly stage: string; /** * Authoritative scope of the stage's runtime key, embedded by codegen * from the deploy response. When present the client posts to the scoped URL * `/v1/projects/{projectSlug}/stages/{stageSlug}/agents/{endpointId}` (matching the * dashboard); when absent it falls back to the base URL. */ readonly endpointId?: string; readonly projectSlug?: string; readonly stageSlug?: string; } interface ChannelReference { readonly kind: "channel"; readonly type: "telegram" | "github" | "slack" | "discord" | "matrix" | "pancake" | "zalo"; readonly agentName: string; readonly agentId: string; readonly accountId: string; readonly webhookPath: string; } interface ResourceApi { readonly agents: Record; readonly channels?: Record; readonly workspaces?: Record; readonly sandboxes?: Record; readonly crons?: Record; readonly skills?: Record; readonly policies?: Record; } interface BroodsClientOptions { /** * Base URL of the core service to call directly. Use `https://gateway.broods.app` * for the hosted service. If you only have a domain, use `host` instead. */ baseUrl?: string; /** Hostname or URL of the core service. `gateway.broods.app` becomes `https://gateway.broods.app`. */ host?: string; /** API key used as the Bearer token for direct runtime calls. */ apiKey?: string; fetch?: typeof fetch; } type AgentHandle = { id: string; run: (input: AgentRunInput) => Promise; runAsync: (input: AgentRunInput) => Promise; continue: (input: AgentContinueInput) => Promise; stream: (input: AgentRunInput) => AsyncGenerator>; }; type CreateClientCronInput = CreateCronInput | (Omit & { agent: AgentReference | string; }); declare class BroodsClient { private readonly baseUrl; private readonly apiKey?; private readonly fetchImpl; constructor(options?: BroodsClientOptions); /** Return the public provider webhook URL for a generated channel reference. */ channelWebhookUrl(ref: ChannelReference): string; /** * Return the provider webhook URL for an account's channel. The URL names no * agent, since every channel of one type in an account shares it, so this is * what to paste into the provider when several agents sit behind the same app. */ accountWebhookUrl(accountId: string, channelType: ChannelReference["type"]): string; /** * Return the provider webhook URL pinned to one stage. Production keeps the * bare account URL from `accountWebhookUrl`. */ stageWebhookUrl(accountId: string, endpointId: string, channelType: ChannelReference["type"]): string; agent(ref: AgentReference): AgentHandle; agent(name: string, agentId: string): AgentHandle; /** Run an agent and accumulate the streamed text and raw parts. */ run(ref: AgentReference, input: AgentRunInput): Promise; run(input: AgentRunInput & { agentId: string; agentName?: string; }): Promise; /** Stream an agent run, yielding each AI SDK `TextStreamPart` as it arrives. */ stream(ref: AgentReference, input: AgentRunInput): AsyncGenerator>; stream(input: AgentRunInput & { agentId: string; agentName?: string; }): AsyncGenerator>; /** Start an async agent run and return the status id/URL used for polling. */ runAsync(ref: AgentReference, input: AgentRunInput): Promise; runAsync(input: AgentRunInput & { agentId: string; agentName?: string; }): Promise; /** * Re-enter a conversation whose last turn stopped short (step cap, provider * fault). Core adds one "continue" user turn on the persisted history and * runs it like a background run; a live channel session answers in its channel. */ continue(ref: AgentReference, input: AgentContinueInput): Promise; continue(input: AgentContinueInput & { agentId: string; }): Promise; /** Fetch one async status snapshot by status URL or run id. */ getAsyncStatus(status: AsyncRequestAccepted | string): Promise; /** Poll async status until it reaches completed, failed, awaiting_approval, awaiting_input, or timeout. */ waitForAsyncStatus(status: AsyncRequestAccepted | string, options?: AsyncPollOptions): Promise; createCron(input: CreateClientCronInput): Promise; listCrons(): Promise; getCron(cronId: string): Promise; listCronRuns(cronId: string, options?: { limit?: number; }): Promise; updateCron(cronId: string, patch: UpdateCronInput): Promise; deleteCron(cronId: string): Promise; /** * Scoped invoke URL for a deployed agent. When codegen embedded the runtime * key's scope, this is * `/v1/projects/{projectSlug}/stages/{stageSlug}/agents/{endpointId}` (the * same URL the dashboard shows, so core can validate the key against the * path); otherwise it falls back to the single run endpoint. */ private scopedUrl; private asyncAgentRun; /** POST a run body that must answer 202 and wrap the accepted run for polling. */ private postAcceptedRun; private openStream; private fetchCore; private apiKeyHeaders; private fetchJson; private resolveStatusUrl; } declare function normalizeHttpServiceUrl(value: string): string; /** * Account config-plane client for the broods public account REST API. * * This is the DYNAMIC counterpart to the config-first `broods dev` / `broods * deploy` flow: `broods dev` syncs the predefined resources declared in your * `broods/` folder, while `BroodsAccountClient` creates and mutates the full * account config plane at runtime: agents, sandboxes (config + lifecycle), * workspaces (config + files), tools, policies, skills, crons, and the account * itself. One caller is a multi-tenant app provisioning one agent per customer * from its own backend. * * Kept intentionally standalone (import from `broods/account`): pure fetch, * no Node built-ins, no `.env` file loading, so it runs in edge/worker * runtimes such as Convex actions, Cloudflare Workers, and the browser-less * server runtimes, as well as Node and Bun. * * Auth: every call sends `Authorization: Bearer {accountSecret}` to * `{baseUrl}/v1/...`, or a short-lived `fp_sts_` role session token from * `assumeRole()`, limited to what the role's policy allows. Secrets inside * agent configs are encrypted at rest by the platform and come back redacted * (`********`) on reads. */ interface BroodsAccountClientOptions { /** Base URL of the broods gateway. Falls back to `BROODS_BASE_URL`, then `https://gateway.broods.app`. */ baseUrl?: string; /** Account secret used as the Bearer token. Falls back to `BROODS_ACCOUNT_SECRET`. */ accountSecret?: string; /** * Short-lived `fp_sts_` role session token (from {@link BroodsAccountClient.assumeRole}) * used as the Bearer instead of the account secret. The session can only do * what its role's policy allows. Falls back to `BROODS_SESSION_TOKEN`. */ sessionToken?: string; fetch?: typeof fetch; } /** Public account record returned by `GET /v1/account`. */ interface BroodsAccount { accountId: string; username: string; status: string; [key: string]: unknown; } /** Public agent record; `config` comes back with secret values redacted. */ interface AccountAgent { accountId: string; agentId: string; name: string; description?: string; status: string; config: AgentConfig; createdAt: string; updatedAt: string; } interface CreateAgentResult { accountId: string; agentId: string; name: string; description?: string; } /** Write-only account environment variable metadata. */ interface AccountEnvVar { name: string; /** ISO 8601, like every other timestamp in the API. */ updatedAt: string; } /** Fields accepted by `PATCH /v1/agents/{id}`. `config` is deep-merged; `null` values delete keys. */ interface UpdateAgentInput { name?: string; description?: string | null; config?: unknown; } /** Public workspace record returned by the workspaces routes. */ interface AccountWorkspace { accountId: string; workspaceId: string; name: string; description?: string; config: WorkspaceConfig; createdAt: string; updatedAt: string; } /** One entry of a workspace file listing (`GET /v1/workspaces/{id}/files`). */ interface WorkspaceFileEntry { path: string; name: string; isFolder: boolean; sizeBytes?: number; updatedAt?: string; } /** Public sandbox config record; `config` comes back with secret values (e.g. `envVars`) redacted. */ interface AccountSandbox { accountId: string; sandboxId: string; name: string; description?: string; config: SandboxConfig; createdAt: string; updatedAt: string; [key: string]: unknown; } /** Public agent-policy record returned by the policies routes. */ interface AccountPolicy { accountId: string; policyId: string; name: string; description?: string; document: PolicyDocument; status: string; createdAt: string; updatedAt: string; } /** * Public account-role record returned by the roles routes. The policy uses the * API action namespace (`"agents:read"`, `"crons:write"`, ...); `projectId` and * `stageId` bound which stage runtime keys may assume the role. */ interface AccountRole { accountId: string; roleId: string; name: string; projectId?: string; stageId?: string; status: "active" | "disabled"; policy: PolicyDocument; createdAt: string; updatedAt: string; } /** Short-lived role session minted by `POST /v1/account/assume-role`. */ interface AssumeRoleResult { /** `fp_sts_` bearer token; pass it as `sessionToken` to a new client. */ token: string; /** ISO timestamp when the session stops working. */ expiresAt: string; } /** * One real place a team talks, bound to an agent: a Slack channel, a Discord * channel, a repo. The runtime reads it on the inbound webhook to decide who * answers there and with what instructions, workspaces and policies. */ interface AccountChannel { accountId: string; channelId: string; platform: string; externalId: string; workspaceRef?: string; name: string; description?: string; config: ChannelRecordConfig; status: "active" | "deleted"; createdAt: string; updatedAt: string; } /** A channel record narrows and adds; it never grants capability the agent lacks. */ interface ChannelRecordConfig { /** Appended after the agent's own system prompt. */ instructions?: string; agentBindings: Array<{ agentId: string; isDefault?: boolean; }>; workspaces?: Array<{ name: string; workspaceId: string; }>; /** Added to whatever the agent already carries. Each policy holds its own mode. */ policies?: string[]; /** * Tool names withheld in this channel, applied after the tool set is built, * so it also covers sandbox tools (`bash`, `read`, …) that `config.tools` * cannot name. Narrowing only; unknown names are ignored. */ denyTools?: string[]; /** * Where the reply lands. `source` answers wherever the message came from, and * threads only when the message already did. Slack only. No other provider * gives the runtime a second place to reply. */ replyIn?: ChannelReplyIn; partition?: ChannelPartition; /** Images the agent may stand a sandbox up from for a thread here. */ sandboxImages?: string[]; /** Named groups of people, readable from policy conditions as `userRoles`. */ tagRoles?: Array<{ roleId: string; userIds: string[]; }>; } /** The stage a resource belongs to. Same name in two stages = two resources. */ interface StageScope { project: string; stage: string; } /** * OAuth 2.0 refresh-token grant on an external MCP row. The runtime mints and * refreshes access tokens and sends `Authorization: Bearer ` itself. * `clientSecret` and `refreshToken` must be `${NAME}` account env refs; * `tokenUrl` defaults to https://oauth2.googleapis.com/token. */ interface McpOauthInput { clientId: string; clientSecret: string; refreshToken: string; tokenUrl?: string; } /** Public MCP server registration returned by the `/v1/mcp` routes (#331). */ interface AccountMcp { accountId: string; serverId: string; projectId: string; stageId: string; name: string; description?: string; transport: "http" | "hosted"; /** External servers only; a hosted row has no endpoint of its own. */ url?: string; /** Hosted servers only: content hash of the uploaded bundle. */ sha256?: string; headers?: Record; oauth?: McpOauthInput; allowedTools?: string[]; disabled: boolean; status: string; createdAt: string; updatedAt: string; deletedAt?: string; } /** * Fields accepted by `POST /v1/mcp`: `url` connects, `bundle` uploads inline * (≤10 MB); a larger bundle goes through `uploadMcpBundle` first. */ interface CreateMcpInput { name: string; description?: string; url?: string; bundle?: string; bundleStorageId?: string; sha256?: string; headers?: Record; oauth?: McpOauthInput; allowedTools?: string[]; } /** Fields accepted by `PATCH /v1/mcp/{serverId}`; every field is optional. */ interface UpdateMcpInput { name?: string; description?: string; url?: string; bundle?: string; bundleStorageId?: string; sha256?: string; headers?: Record; oauth?: McpOauthInput; allowedTools?: string[]; disabled?: boolean; } /** * Body of a skill upload (`POST /v1/skills`, `PUT /v1/skills/{skillName}`). * `json` needs `name`/`description`/`content`; `files` needs base64 `files` * including a root `SKILL.md`; `github` needs a tree `url`. */ interface SkillUploadInput { source: "json" | "files" | "github"; name?: string; description?: string; content?: string; files?: Array<{ path: string; contentBase64: string; contentType?: string; }>; url?: string; } /** Result of `POST /v1/account/rotate-secret`. The returned `secret` is shown once; the old secret stops working immediately. */ interface RotateSecretResult { account: BroodsAccount; secret: string; } /** Result of `DELETE /v1/account`: the account and all account-scoped data are removed; `cleanup` reports per-resource deletion counts. */ interface DeleteAccountResult { deleted: boolean; cleanup?: Record; } /** Result of a suspend/resume/terminate sandbox lifecycle action. */ interface SandboxLifecycleResult { status: string; } /** Result of `POST /v1/sandboxes/{id}/snapshot`. */ interface SandboxSnapshotResult { status: string; snapshotId?: string; externalImageId?: string; } /** Sealed ticket from `POST /v1/sandboxes/{id}/terminal`; hand `token` to the gateway terminal WebSocket at `websocketPath`. */ interface SandboxTerminalTicket { token: string; expiresAt: number; websocketPath: string; } /** Non-2xx response from the account API (404s on id routes return null instead). */ declare class BroodsAccountApiError extends Error { readonly status: number; readonly body: string; constructor(method: string, path: string, status: number, body: string); } /** Build a validated account env-var reference for use in an agent config. */ declare function envPlaceholder(name: string): string; /** * The credential the environment supplies, with a role session winning over * the account secret. The constructor throws through this same resolution, so * callers that can run without an account credential (`broods mcp` with only a * stored login) probe here instead of catching the constructor. */ declare function resolveEnvCredential(): string | undefined; /** * Typed client for the broods account config API. All `get`/`update`/`delete` * methods return `null`/`false` when the resource does not exist (HTTP 404) so * callers can implement upsert flows without try/catch; every other non-2xx * status throws {@link BroodsAccountApiError}. */ declare class BroodsAccountClient { private readonly baseUrl; private readonly bearerToken; private readonly fetchImpl; constructor(options?: BroodsAccountClientOptions); /** The account this secret belongs to. Its `accountId` is the first segment of channel webhook URLs. */ getAccount(): Promise; /** Update account metadata (username/description). Returns null when the account is gone. Runtime config is managed through the agent endpoints. */ updateAccount(patch: { username?: string; description?: string | null; }): Promise; /** * Exchange a role for a short-lived `fp_sts_` session token. Callable with * the account secret, a CLI login token, or a stage runtime key (the latter * only into roles scoped to the key's own project/stage). Construct a new * client with `{ sessionToken: result.token }` to act as the role. */ assumeRole(roleId: string, options?: { ttlSeconds?: number; }): Promise; /** Rotate the account secret. The returned `secret` is shown once and the current secret stops working immediately, so persist it before the process exits. */ rotateSecret(): Promise; /** Delete this account and cascade-clean every account-scoped resource. `cleanup` reports per-resource deletion counts. */ deleteAccount(): Promise; /** * Provider webhook URL for one of the account's channels. Paste this into the * provider's webhook settings (Slack Event Subscriptions, Zalo OA webhook, * Pancake page webhook). Routing is per account + channel: the credentials * that verify the request pick the receiving agent, and a channel record * binds each place to the agent that should answer there. */ webhookUrl(accountId: string, channelType: string): string; listAgents(): Promise; createAgent(input: { name: string; description?: string; config: unknown; }): Promise; getAgent(agentId: string): Promise; /** PATCH an agent. `config` deep-merges into the stored config; `null` leaves delete keys. Returns null when the agent is gone. */ updateAgent(agentId: string, patch: UpdateAgentInput): Promise; deleteAgent(agentId: string): Promise; /** List account environment variable names and update timestamps; values are never returned. */ listEnvVars(): Promise; /** Create or replace one write-only account environment variable. */ setEnvVar(name: string, value: string): Promise; /** Delete one account environment variable. Returns false when it is already absent. */ deleteEnvVar(name: string): Promise; listCrons(): Promise; createCron(input: CreateCronInput): Promise; getCron(cronId: string): Promise; updateCron(cronId: string, patch: UpdateCronInput): Promise; deleteCron(cronId: string): Promise; /** Run history for a cron, newest first. Returns [] when the cron is gone. */ listCronRuns(cronId: string, options?: { limit?: number; }): Promise; listWorkspaces(): Promise; createWorkspace(input: { name: string; description?: string; config?: unknown; }): Promise; getWorkspace(workspaceId: string): Promise; updateWorkspace(workspaceId: string, patch: { name?: string; description?: string | null; config?: unknown; }): Promise; deleteWorkspace(workspaceId: string): Promise; /** Flat listing of every file in the workspace's S3-backed filesystem. Returns [] when the workspace is gone. */ listWorkspaceFiles(workspaceId: string): Promise; /** Short-lived download URL for one workspace file. Returns null when the workspace or file is gone. */ getWorkspaceFileUrl(workspaceId: string, path: string): Promise; /** Upload or replace one workspace file from base64 content. Throws when the workspace is gone (404). */ uploadWorkspaceFile(workspaceId: string, input: { path: string; contentBase64: string; contentType?: string; }): Promise; /** Rename a workspace file or folder. Returns false when the workspace or source path is gone. */ renameWorkspaceFile(workspaceId: string, path: string, newPath: string): Promise; /** Delete a workspace file or folder. Returns false when the workspace or path is gone. */ deleteWorkspaceFile(workspaceId: string, path: string): Promise; listSandboxes(): Promise; createSandbox(input: { name: string; description?: string; config?: unknown; }): Promise; getSandbox(sandboxId: string): Promise; /** PATCH a sandbox config. `config` fully replaces the stored config. Returns null when the sandbox is gone. */ updateSandbox(sandboxId: string, patch: { name?: string; description?: string | null; config?: unknown; }): Promise; deleteSandbox(sandboxId: string): Promise; /** Suspend a persistent sandbox reservation. Throws on 404/403/409 (missing sandbox, foreign reservation, or unsupported provider). */ suspendSandbox(sandboxId: string, reservationKey: string): Promise; /** Resume a persistent sandbox reservation. Throws on 404/403/409. */ resumeSandbox(sandboxId: string, reservationKey: string): Promise; /** Terminate a persistent sandbox reservation and drop its live-instance row. Throws on 404/403/409. */ terminateSandbox(sandboxId: string, reservationKey: string): Promise; /** Snapshot a persistent sandbox reservation into a reusable image (self-hosted `sandbox` provider). Throws on 404/403/409. */ snapshotSandbox(sandboxId: string, reservationKey: string, name: string): Promise; /** Mint a short-lived sealed ticket for an interactive PTY session on a persistent sandbox (`sandbox`/`lambda` providers). Throws on 404/403/409. */ openSandboxTerminal(sandboxId: string, reservationKey: string): Promise; /** MCP servers live in one stage, so the collection routes need a scope. */ listMcp(scope: StageScope): Promise; createMcp(scope: StageScope, input: CreateMcpInput): Promise; /** * Upload a hosted MCP bundle too large for the JSON body; returns the * `bundleStorageId` + `sha256` pair `createMcp`/`updateMcp` accept. */ uploadMcpBundle(bundle: string): Promise<{ bundleStorageId: string; sha256: string; }>; getMcp(serverId: string): Promise; updateMcp(serverId: string, patch: UpdateMcpInput): Promise; deleteMcp(serverId: string): Promise; listPolicies(): Promise; createPolicy(input: { name: string; description?: string; document: PolicyDocument; }): Promise; getPolicy(policyId: string): Promise; /** PATCH a policy. `description: null` clears it. Returns null when the policy is gone. */ updatePolicy(policyId: string, patch: { name?: string; description?: string | null; document?: PolicyDocument; status?: string; }): Promise; deletePolicy(policyId: string): Promise; /** Roles are account-secret only: a session cannot list, mint, or edit roles. */ listRoles(): Promise; /** Create a role whose policy uses the API action namespace. `projectId`/`stageId` must be provided together. */ createRole(input: { name: string; policy: PolicyDocument; projectId?: string; stageId?: string; }): Promise; getRole(roleId: string): Promise; /** PATCH a role. `status: "disabled"` kills every live session of the role. Returns null when the role is gone. */ updateRole(roleId: string, patch: { name?: string; policy?: PolicyDocument; status?: "active" | "disabled"; }): Promise; deleteRole(roleId: string): Promise; listChannels(): Promise; /** Bind one real chat channel to an agent. One active record per place. */ createChannel(input: { platform: string; externalId: string; workspaceRef?: string; name: string; description?: string; config: ChannelRecordConfig; }): Promise; getChannel(channelId: string): Promise; /** PATCH a channel. `description: null` clears it. Returns null when it is gone. */ updateChannel(channelId: string, patch: { name?: string; description?: string | null; workspaceRef?: string | null; config?: ChannelRecordConfig; status?: "active" | "deleted"; }): Promise; deleteChannel(channelId: string): Promise; /** All skills for the account, each with its `/` path. */ listSkills(): Promise; /** Upload a new skill from JSON content, a base64 file bundle, or a GitHub tree URL. Every bundle must include a root `SKILL.md`. */ createSkill(input: SkillUploadInput): Promise; getSkill(skillName: string): Promise; /** Replace a skill's bundle in place (`PUT`). Throws when the skill is gone (404). */ uploadSkill(skillName: string, input: SkillUploadInput): Promise; deleteSkill(skillName: string): Promise; /** POST a sandbox lifecycle action, throwing on any non-2xx (including 404, since these are not upsert flows). */ private sandboxAction; private request; } /** * WebSocket client for deployed-agent endpoints. * Uses the gateway URL when configured, or derives one from the core service URL. */ type WebSocketRunInput = { agent?: AgentReference; agentId?: string; endpointId?: string; sessionId?: string; eventId?: string; projectSlug?: string; stageSlug?: string; signal?: AbortSignal; /** Defaults to "steer": join the live run at its next step boundary. */ mode?: "reject" | "followup" | "collect" | "steer"; idempotencyKey?: string; } & (AgentRunEventInput | AgentRunAnswerInput) & AgentRunOverrides; type WebSocketAttachInput = Omit & { endpointId?: string; agent?: AgentReference; projectSlug?: string; stageSlug?: string; signal?: AbortSignal; }; interface WebSocketHandlers { /** * Receives every server message with durable output envelopes unwrapped: * stream parts arrive as themselves (`message.type === "text-delta"`), never * nested under `data`. Use `onOutput` when the envelope cursor is needed. */ onMessage?(message: WebSocketServerMessage): void; /** Receives the raw durable output envelope (cursor, replay flag, part). */ onOutput?(output: WebSocketOutputMessage): void; onMeta?(meta: Extract): void; onDone?(): void; onError?(error: Error): void; } interface WebSocketSubscription { readonly url: string; sendControl(message: Omit): void; close(code?: number, reason?: string): void; } interface BroodsWebSocketClientOptions { /** Base URL of the core service. Use `https://...`; the client converts it to `wss://...`. */ baseUrl?: string; /** Hostname or URL of the core service. `gateway.broods.app` becomes `https://gateway.broods.app`. */ host?: string; /** API key used as the WebSocket token. Defaults to BROODS_API_KEY from the environment or local .env files. */ apiKey?: string; WebSocket?: WebSocketConstructorLike; connectTimeoutMs?: number; } interface WebSocketConstructorLike { new (url: string, protocols?: string[]): WebSocketLike; } interface WebSocketLike { readyState: number; onopen: ((event: unknown) => void) | null; onmessage: ((event: { data: unknown; }) => void) | null; onerror: ((event: unknown) => void) | null; onclose: ((event: { code: number; reason: string; }) => void) | null; send(data: string): void; close(code?: number, reason?: string): void; } declare class BroodsWebSocketClient { private readonly baseUrl; private readonly apiKey; private readonly WebSocketImpl?; private readonly connectTimeoutMs; constructor(options?: BroodsWebSocketClientOptions); subscribe(input: WebSocketRunInput, handlers?: WebSocketHandlers): WebSocketSubscription; /** Attach to a previously started event and replay from an exclusive cursor. */ attach(input: WebSocketAttachInput, handlers?: WebSocketHandlers): WebSocketSubscription; stream(input: WebSocketRunInput): AsyncGenerator; buildUrl(input: Pick): string; private resolveWebSocket; } declare function toWebSocketBaseUrl(url: string): string; /** * The credential travels as a `Sec-WebSocket-Protocol` entry rather than in * the URL, so it never lands in proxy or access logs. The gateway answers * with `broods.v1`, which is what completes a handshake that offered * subprotocols. */ declare function webSocketSubprotocols(apiKey: string): string[]; /** * Resource definition helpers for the code-first `broods/` project folder. * * Every runtime function here is synchronous. */ declare const RESOURCE_MARKER: unique symbol; declare const CONFIG_MARKER: unique symbol; declare const CONNECTION_MARKER: unique symbol; interface EnvRef { readonly __beeblastEnv: true; readonly name: Name; } /** Callable accessor for {@link env}. */ interface EnvAccessor { (name: Name): EnvRef; } type EnvRefString = T extends string ? T | EnvRef : T extends readonly (infer Item)[] ? readonly EnvRefString[] : T extends (infer Item)[] ? EnvRefString[] : T extends object ? { [Key in keyof T]: EnvRefString; } : T; interface BroodsProjectConfig { project?: string; stages?: { dev?: string; deploy?: string; [name: string]: string | undefined; }; dashboardUrl?: string; /** Convex control-plane base URL for sync/env calls; defaults to the URL discovered at login. */ baseUrl?: string; } interface BroodsConfigDefinition { readonly [CONFIG_MARKER]: true; readonly config: BroodsProjectConfig; } type ResourceKind = "agent" | "workspace" | "sandbox" | "cron" | "skill" | "mcp" | "policy" | "channelRecord"; interface ResourceDefinition { readonly [RESOURCE_MARKER]: true; readonly kind: Kind; readonly name: Name; readonly description?: string; readonly config: Config; } /** * Authoring shape for every resource helper: the resource's own `name` (plus an * optional human `description`) sits inline with that resource's config keys. */ type ResourceInput = { name: Name; description?: string; } & Config; /** * Provider-specific sandbox knobs. `reservationKey` names the reserved machine a * `persistent` sandbox reconnects to when no workspace is mounted; unset, each * agent gets its own, and pinning one string on two sandboxes shares a machine. * Keys are scoped to the account, so they cannot reach another account's machine. */ type SandboxDefinitionOptions = Record & { reservationKey?: string; }; /** * Code-first sandbox config input. Mirrors core's `SandboxConfig` but lets * `envVars` values be `env("NAME")` references (compiled to `${NAME}` placeholders * at sync time, exactly like provider `apiKey`). Add overrides here if more * sandbox fields should accept env refs. */ type SandboxDefinitionConfig = Omit & { envVars?: Record; options?: SandboxDefinitionOptions; }; type HarnessType = NonNullable["type"]; /** Harness settings. The harness runs on the agent's first sandbox. */ type HarnessDefinition = NonNullable; interface SkillDefinitionConfig { /** * Folder containing SKILL.md plus optional scripts/assets. Relative paths are * resolved from the `broods/` project directory. */ path: string; } type PolicyDefinitionConfig = Omit & { version?: PolicyDocument["version"]; }; /** * Fetch-style MCP handler for a hosted server: what * `createMcpHandler(...)` from @modelcontextprotocol/server returns: either * the request function itself or an object exposing it as `fetch`. */ type McpHandler = ((request: Request) => Response | Promise) | { fetch(request: Request): Response | Promise; }; /** * MCP server registration (#331): external (`url`), hosted (`handler`), or on * a user's computer (`sandbox`). Either way the server's tools are offered as * `__`; an external row is dialed over the stateless HTTP * transport (spec 2026-07-28) at agent registration time. The name namespaces * those tools, so it must be 1-32 lowercase letters, digits, or hyphens, * starting with a letter. */ interface McpDefinitionConfig { /** External server's MCP endpoint; http(s), no embedded credentials. */ url?: string; /** * Instead of `url` or `handler`: the machine sandbox whose daemon runs this * server, from the entry with the same name in its `--mcp` file. */ sandbox?: SandboxResource | string; /** * Hosted alternative to `url`: declare the server inline as * `handler: createMcpHandler(...)` from @modelcontextprotocol/server, * right next to the `defineMcp` call. The CLI bundles the defining module * and the mcp-runner Lambda hosts it, one invoke per batch of requests. */ handler?: McpHandler; /** * Extra request headers. Credential-bearing headers (Authorization, * X-Api-Key, ...) must reference an account env var by name inside a plain * string, e.g. `"Bearer ${TOKEN}"`. `env()` returns an object, so a template * literal around it sends `[object Object]`. Never carry an inline secret. */ headers?: Record; /** * OAuth 2.0 refresh-token grant for an external server whose access tokens * expire (Google's Workspace MCP endpoints). The runtime mints, caches and * refreshes access tokens and sends `Authorization: Bearer ` itself, * so do not also set an Authorization header. `clientSecret` and * `refreshToken` must be `env("NAME")` refs; `tokenUrl` defaults to * https://oauth2.googleapis.com/token. */ oauth?: { clientId: string | EnvRef; clientSecret: string | EnvRef; refreshToken: string | EnvRef; tokenUrl?: string; }; /** Tool names agents may use from this server; omit to allow all. */ allowedTools?: string[]; } type ChannelType = "telegram" | "github" | "slack" | "discord" | "matrix" | "pancake" | "zalo"; /** * A connection is one app install: the credentials an agent needs before a * provider can reach it at all. Channels point at a connection; a connection * never points back, which is what keeps the reference graph acyclic. */ interface ConnectionDefinition { readonly [CONNECTION_MARKER]: true; readonly kind: "connection"; readonly type: Type; readonly partition?: ChannelPartition; /** `partition` is lifted out of the authored input, so it is not in here. */ readonly config: Omit; } /** * An agent bound to a channel. Every bound agent runs when a message arrives; * `reply: false` runs it with a silenced channel, so it can work without * speaking in the room. */ type ChannelAgentInput = AgentResource | { agent: AgentResource; reply?: boolean; }; type RequiredChannelKeys = Required> & Omit; type ChannelSecret = string | EnvRef | undefined; type ConnectionIdentityInput = { /** Include the dashboard trace link in channel replies. Off by default. */ trace?: "enabled" | "disabled"; partition?: ChannelPartition; /** * Rooms this connection answers in, on top of every channel declared against * it. Use `["*"]` to answer everywhere instead, which is the only reason to * name rooms here at all: a room with rules belongs in a channel resource. */ allowedChannelIds?: readonly string[]; /** Provider user ids allowed to trigger the agent. `["*"]` or omitted is everyone. */ allowedUserIds?: readonly string[]; }; type TelegramConnectionInput = EnvRefString, "botToken" | "webhookSecret">> & ConnectionIdentityInput; type GitHubConnectionInput = EnvRefString, "webhookSecret" | "appId" | "privateKey">> & ConnectionIdentityInput; type SlackConnectionInput = EnvRefString, "botToken" | "signingSecret">> & ConnectionIdentityInput; type DiscordConnectionInput = EnvRefString, "botToken" | "publicKey">> & ConnectionIdentityInput; type MatrixConnectionInput = EnvRefString, "apiUrl" | "botToken">> & ConnectionIdentityInput; interface PancakeConnectionInput extends ConnectionIdentityInput { pageId: ChannelSecret; pageAccessToken: ChannelSecret; webhookSecret: ChannelSecret; senderId?: string | EnvRef; } interface ZaloConnectionInput extends ConnectionIdentityInput { botToken: ChannelSecret; webhookSecret: ChannelSecret; } type TelegramConnectionDefinition = ConnectionDefinition<"telegram", TelegramConnectionInput>; type GitHubConnectionDefinition = ConnectionDefinition<"github", GitHubConnectionInput>; type SlackConnectionDefinition = ConnectionDefinition<"slack", SlackConnectionInput>; type DiscordConnectionDefinition = ConnectionDefinition<"discord", DiscordConnectionInput>; type MatrixConnectionDefinition = ConnectionDefinition<"matrix", MatrixConnectionInput>; type PancakeConnectionDefinition = ConnectionDefinition<"pancake", PancakeConnectionInput>; type ZaloConnectionDefinition = ConnectionDefinition<"zalo", ZaloConnectionInput>; type AnyConnectionDefinition = TelegramConnectionDefinition | GitHubConnectionDefinition | SlackConnectionDefinition | DiscordConnectionDefinition | MatrixConnectionDefinition | PancakeConnectionDefinition | ZaloConnectionDefinition; /** * One real place a team talks: a Slack channel, a Discord channel, a repo. It * narrows and adds, and never grants what a bound agent lacks: instructions * append, policies union, tools and workspaces only narrow. It names the * connection it belongs to, so `platform` is never written by hand. */ type ChannelDefinitionConfig = { connection: AnyConnectionDefinition; /** * Provider id of the place, from the per-platform field on the input. A list * fans out to one record per id at deploy time. */ externalId: string | readonly string[]; /** Team, guild or repo owner the place sits in, when the provider has one. */ workspaceRef?: string; /** Every one of these runs. Omit and the connection's own agent answers. */ agents?: readonly ChannelAgentInput[]; /** Appended after each agent's own system prompt, never replacing it. */ instructions?: string; /** Selects from what the agent already attaches; anything else is dropped. */ workspaces?: readonly AgentWorkspaceInput[]; /** Added to whatever the agent already carries. Each policy holds its own mode. */ policies?: readonly (PolicyResource | string)[]; denyTools?: readonly string[]; /** Where the reply lands. Slack only. */ replyIn?: ChannelReplyIn; partition?: ChannelPartition; sandboxImages?: readonly string[]; tagRoles?: readonly { roleId: string; userIds: readonly string[]; }[]; }; /** Rules shared by every channel, whatever the provider calls its rooms. */ type ChannelRulesInput = Omit; type SlackChannelInput = ChannelRulesInput & { connection: SlackConnectionDefinition; /** Slack channel id, e.g. "C0123ABCD". */ channelId: string; /** Slack team id the channel sits in. */ teamId?: string; /** Where the reply lands. Slack is the only provider with a choice. */ replyIn?: ChannelReplyIn; }; type DiscordChannelInput = ChannelRulesInput & { connection: DiscordConnectionDefinition; /** Discord channel id (a snowflake). */ channelId: string; /** Guild the channel sits in. */ guildId?: string; }; type MatrixChannelInput = ChannelRulesInput & { connection: MatrixConnectionDefinition; /** Matrix room id, e.g. "!abc123:matrix.org". */ channelId: string; }; type GitHubChannelInput = ChannelRulesInput & { connection: GitHubConnectionDefinition; /** Repository full name, e.g. "beeblast/api". */ repo: string; }; type TelegramChannelInput = ChannelRulesInput & { connection: TelegramConnectionDefinition; /** Telegram chat id, e.g. "-1001234567". */ chatId: string; }; type ZaloChannelInput = ChannelRulesInput & { connection: ZaloConnectionDefinition; /** Zalo user or group chat id, or several that share one set of rules. */ chatId: string | readonly string[]; }; type PancakeChannelInput = ChannelRulesInput & { connection: PancakeConnectionDefinition; /** Pancake conversation id. */ conversationId: string; }; /** * Per-agent workspace mount with an optional sandbox override. A bare * `defineWorkspace(...)` inherits the agent's first sandbox; the object form lets * a single workspace pin its own sandbox, or set `sandbox: null` to force the * workspace read-only (no compute attached). */ interface AgentWorkspaceRefInput { workspace: WorkspaceResource | string; sandbox?: SandboxResource | string | null; } type AgentWorkspaceInput = WorkspaceResource | AgentWorkspaceRefInput; /** * `subagent` block where `allowed` may reference other `defineAgent(...)` * resources directly; the compiler rewrites them to agent names and the backend * resolves those to deploy-time agent ids. */ type AgentSubagentDefinitionConfig = Omit, "allowed"> & { allowed?: readonly (AgentResource | string)[]; }; type AgentSkillsDefinitionConfig = Omit, "allowed"> & { allowed?: readonly (SkillResource | string)[]; }; interface HookContext { fetch: typeof fetch; config: Record; /** * Mutable per-request scratchpad shared across this agent request's hooks. * Seed it in an early hook (e.g. `onStart`) and read or modify it later. * Every loop hook, `onSubagentFinish`, and the reply's `onMessageSending` * see the same state. Keep it JSON-serializable. `onMessageReceived`, * delayed background replies, and each subagent's own run get fresh state. */ state: Record; } type Handler = (ctx: HookContext, event: Event) => Result | void | Promise; /** * Channel-specific routing data attached to an inbound message. Inherited from * the core channel adapters (via contracts.ts) so an `onMessageReceived` hook * that narrows on `event.channel` always sees exactly what core emits (e.g. * Pancake `tagIds`). */ type TelegramMessageSource = TelegramSource; type GitHubMessageSource = GitHubSource; type SlackMessageSource = SlackSource; type DiscordMessageSource = DiscordSource; type MatrixMessageSource = MatrixSource; type PancakeMessageSource = PancakeSource; type ZaloMessageSource = ZaloSource; /** * Inbound channel message passed to `onMessageReceived`, discriminated on * `channel` so each variant exposes its channel's strongly-typed `source`. */ type ChannelMessageReceived = { channel: "telegram"; text: string; source: TelegramMessageSource; } | { channel: "github"; text: string; source: GitHubMessageSource; } | { channel: "slack"; text: string; source: SlackMessageSource; } | { channel: "discord"; text: string; source: DiscordMessageSource; } | { channel: "matrix"; text: string; source: MatrixMessageSource; } | { channel: "pancake"; text: string; source: PancakeMessageSource; } | { channel: "zalo"; text: string; source: ZaloMessageSource; }; /** * Inline agent hook callbacks. Handlers are serialized with `.toString()`, * bundled into one account hook, and run in a fresh V8 isolate. Keep them * self-contained: use only `ctx`, `event`, and JavaScript globals. Do not rely * on imports or closure variables. Arrow functions and function expressions are * preferred so the serialized source is valid as an object-literal value. * * Subagent runs fire hooks too: a registered subagent runs its own hooks, a * prompt-only (virtual) subagent inherits this bundle, always with fresh * `ctx.state`. `onSubagentFinish` fires on the parent with the parent's state. */ interface AgentHooks { onStart?: Handler<{ system: string; messages: unknown[]; }, { system?: string; messages?: unknown[]; }>; onStepFinish?: Handler<{ stepNumber: number; finishReason: string; toolCallCount: number; }, void>; onToolCall?: Handler<{ toolName: string; input: unknown; }, { decision?: "allow" | "deny"; args?: Record; denyReason?: string; }>; onToolResult?: Handler<{ toolName: string; output: unknown; }, { output?: unknown; }>; onFinish?: Handler<{ finishReason: string; response: unknown; }, { output?: unknown; }>; onApproval?: Handler<{ approvals: unknown; }, { approve?: boolean; }>; onError?: Handler<{ error: string; }, void>; onSubagentFinish?: Handler<{ taskId: string; result: unknown; }, { visibleResult?: unknown; }>; onMessageReceived?: Handler; onMessageSending?: Handler<{ channel: ChannelType; text: string; }, { drop?: boolean; text?: string; }>; } /** * Code-first agent config input. Built from an explicit `Pick` of `AgentConfig` * (not `Omit`) so the SDK input type does NOT inherit `AgentConfig`'s * `[key: string]: unknown` index signature, which would otherwise disable * TypeScript's excess-property checks and silently accept typos like * `workspace:` instead of `workspaces:`. Add a key here when core's `AgentConfig` * gains a new top-level field that should be code-definable. */ /** * SDK-facing model-provider constructor settings. Open by design: everything a * provider's Vercel AI SDK factory accepts is forwarded verbatim, so there is no * key list to keep in sync. Only the keys broods reads itself are named (and so * typo-checked at run time by `validateProviderConfig`); every string field also * accepts an `env("NAME")` reference. */ interface ProviderSettingsInput { apiKey?: string | EnvRef; base_url?: string | EnvRef; baseURL?: string | EnvRef; headers?: Record; [key: string]: unknown; } /** Per-provider settings; provider names stay synced with core's `AgentConfig`. */ type ProviderConfigInput = Partial, ProviderSettingsInput>>; type AgentDefinitionConfig = EnvRefString> & { provider?: ProviderConfigInput; } & { harness?: HarnessDefinition; hooks?: AgentHooks & { webhooks?: readonly EnvRefString[]; }; connections?: readonly AnyConnectionDefinition[]; /** * Sandboxes this agent runs on. The first is the default: bash with no * workspace runs there, workspaces without their own sandbox mount it, and a * harness runs on it. The model reaches the others by name. */ sandboxes?: readonly (SandboxResource | string)[]; workspaces?: readonly AgentWorkspaceInput[]; subagent?: AgentSubagentDefinitionConfig; skills?: AgentSkillsDefinitionConfig; /** Policies that gate this agent. Each one carries its own enforcement mode. */ policies?: readonly (PolicyResource | string)[]; /** * Opt the agent into the public runtime endpoint (SSE/WebSocket via the * stage runtime key). Off by default: when unset the public endpoint * refuses requests for this agent. Reach a private agent through an * internal endpoint or a channel webhook. See issue #65. */ publicAccess?: boolean; /** * Let a runtime-key caller send `system` and `model` overrides on a run. Off * by default: such a run is refused with `403 run_overrides_disabled`. */ allowRunOverrides?: boolean; }; type CronDefinitionConfig = Omit & { agent: AgentResource | string; }; type AgentResource = ResourceDefinition<"agent", Name, AgentDefinitionConfig>; /** * Code-first workspace config. Says `partitioned` where storage says * `isolation`: the flag permits a split, it does not perform one. A channel's * `partition` decides which folder a run mounts. */ type WorkspaceDefinitionConfig = Omit & { /** Allow this workspace to be split into per-conversation folders. */ partitioned?: boolean; }; type WorkspaceResource = ResourceDefinition<"workspace", Name, WorkspaceDefinitionConfig>; type SandboxResource = ResourceDefinition<"sandbox", Name, SandboxDefinitionConfig>; type SkillResource = ResourceDefinition<"skill", Name, SkillDefinitionConfig>; type McpResource = ResourceDefinition<"mcp", Name, McpDefinitionConfig>; type PolicyResource = ResourceDefinition<"policy", Name, PolicyDefinitionConfig>; type CronResource = ResourceDefinition<"cron", Name, CronDefinitionConfig>; type ChannelResource = ResourceDefinition<"channelRecord", Name, ChannelDefinitionConfig>; type AnyResource = AgentResource | WorkspaceResource | SandboxResource | CronResource | SkillResource | McpResource | PolicyResource | ChannelResource; /** * References an account/environment variable resolved on the SERVER at runtime. * Set it with `broods env set ` or in the dashboard (the Convex-style * `convex env set` model). It is a deferred reference, never read from your local * environment and never baked into the deployed config: * * apiKey: env("OPENAI_API_KEY") * * It compiles to a `${NAME}` placeholder the harness fills in at run time. This is * NOT `process.env`: agent configs are compiled locally, so `process.env.NAME` would * bake the literal local value into the deployed config instead of deferring it. * * The reference must resolve: syncing a manifest whose `env("NAME")` has no value * stored for the stage is rejected outright, so an unset or misspelled name fails * the sync instead of reaching the runtime as a literal `${NAME}`. `broods dev` * pushes matching `.env.local` values first, so a local value is enough there. */ declare const env: EnvAccessor; /** * Shared builder behind every `define*` helper below. The public helpers are * thin, per-kind typed front doors: each pins its `kind` (the discriminant the * sync/codegen pipeline switches on) and splits the flat authoring input back * into the `{ name, description?, config }` shape the manifest wire format uses. */ declare function defineAgent(input: ResourceInput): AgentResource; declare function defineBroods(config: BroodsProjectConfig): BroodsConfigDefinition; declare function defineCron(input: ResourceInput): CronResource; declare function defineHarness(definition: Definition): Definition; declare function defineMcp(input: ResourceInput): McpResource; declare function definePolicy(input: ResourceInput): PolicyResource; declare function defineSandbox(input: ResourceInput): SandboxResource; declare function defineSkill(input: ResourceInput): SkillResource; declare function defineWorkspace(input: ResourceInput): WorkspaceResource; declare function defineDiscordConnection(config: DiscordConnectionInput): DiscordConnectionDefinition; declare function defineGitHubConnection(config: GitHubConnectionInput): GitHubConnectionDefinition; declare function defineMatrixConnection(config: MatrixConnectionInput): MatrixConnectionDefinition; declare function definePancakeConnection(config: PancakeConnectionInput): PancakeConnectionDefinition; declare function defineSlackConnection(config: SlackConnectionInput): SlackConnectionDefinition; declare function defineTelegramConnection(config: TelegramConnectionInput): TelegramConnectionDefinition; declare function defineZaloConnection(config: ZaloConnectionInput): ZaloConnectionDefinition; declare function defineDiscordChannel(input: ResourceInput): ChannelResource; declare function defineGitHubChannel(input: ResourceInput): ChannelResource; declare function defineMatrixChannel(input: ResourceInput): ChannelResource; declare function definePancakeChannel(input: ResourceInput): ChannelResource; declare function defineSlackChannel(input: ResourceInput): ChannelResource; declare function defineTelegramChannel(input: ResourceInput): ChannelResource; declare function defineZaloChannel(input: ResourceInput): ChannelResource; declare function isBroodsConfig(value: unknown): value is BroodsConfigDefinition; declare function isConnectionDefinition(value: unknown): value is AnyConnectionDefinition; declare function isResource(value: unknown): value is AnyResource; export { BroodsAccountApiError, BroodsAccountClient, BroodsClient, BroodsWebSocketClient, DEFAULT_CORE_BASE_URL, IngressAcceptedError, MAX_OBSERVABILITY_BACKFILL, BroodsWebSocketClient as WebSocketClient, BroodsWebSocketClient as WebsocketClient, defineAgent, defineBroods, defineCron, defineDiscordChannel, defineDiscordConnection, defineGitHubChannel, defineGitHubConnection, defineHarness, defineMatrixChannel, defineMatrixConnection, defineMcp, definePancakeChannel, definePancakeConnection, definePolicy, defineSandbox, defineSkill, defineSlackChannel, defineSlackConnection, defineTelegramChannel, defineTelegramConnection, defineWorkspace, defineZaloChannel, defineZaloConnection, env, envPlaceholder, isBroodsConfig, isConnectionDefinition, isLogLevel, isObservabilityClientMessage, isResource, isRootSpanKind, isSandboxLogId, isTraceId, normalizeHttpServiceUrl, readSseStream, resolveEnvCredential, resolveRunEvents, toWebSocketBaseUrl, webSocketSubprotocols }; export type { Account, AccountAgent, AccountChannel, AccountEnvVar, AccountMcp, AccountPolicy, AccountRole, AccountSandbox, AccountWorkspace, Agent, AgentChannelsConfig, AgentCodeHookConfig, AgentConfig, AgentConfigDoc, AgentContinueInput, AgentDefinitionConfig, AgentDiscordChannelConfig, AgentGitHubChannelConfig, AgentHandle, AgentHookEventName, AgentHooks, AgentHooksConfig, AgentMatrixChannelConfig, AgentPancakeChannelConfig, AgentProviderSettings, AgentReference, AgentResource, AgentRunAnswerInput, AgentRunEventInput, AgentRunInput, AgentRunModelOverrides, AgentRunOverrides, AgentRunResult, AgentSkillsDefinitionConfig, AgentSlackChannelConfig, AgentStreamPart, AgentSubagentDefinitionConfig, AgentTelegramChannelConfig, AgentWebhookHookConfig, AgentWorkspaceInput, AgentWorkspaceRef, AgentWorkspaceRefInput, AgentZaloChannelConfig, AnyConnectionDefinition, AnyResource, AssumeRoleResult, AsyncAgentRun, AsyncPollOptions, AsyncRequestAccepted, AsyncStatus, BroodsAccount, BroodsAccountClientOptions, BroodsClientOptions, BroodsConfigDefinition, BroodsProjectConfig, BroodsWebSocketClientOptions, ChannelAgentInput, ChannelDefinitionConfig, ChannelMessageReceived, ChannelPartition, ChannelRecordConfig, ChannelReference, ChannelReplyIn, ChannelResource, ChannelType, CliManifest, CliManifestResource, ConnectionDefinition, CreateAgentResult, CreateClientCronInput, CreateCronInput, CreateMcpInput, Cron, CronDefinitionConfig, CronDoc, CronLastStatus, CronResource, CronRun, CronStatus, DeleteAccountResult, DiscordChannelInput, DiscordConnectionDefinition, DiscordConnectionInput, DiscordMessageSource, DiscordSource, Doc, EnvAccessor, EnvRef, EnvRefString, GeneratedIds, GitHubChannelInput, GitHubConnectionDefinition, GitHubConnectionInput, GitHubMessageSource, GitHubSource, HarnessDefinition, HarnessType, HookContext, Id, IngressMode, IngressStatus, LogLevel, MatrixChannelInput, MatrixConnectionDefinition, MatrixConnectionInput, MatrixMessageSource, MatrixSource, McpDefinitionConfig, McpHandler, McpOauthInput, McpResource, ObservabilityBackfillMessage, ObservabilityClientMessage, ObservabilityErrorMessage, ObservabilityFetchTraceMessage, ObservabilityLogEntry, ObservabilityLogMessage, ObservabilityReadyMessage, ObservabilityServerMessage, ObservabilitySpanMessage, ObservabilitySpanRow, ObservabilitySubscribeMessage, ObservabilityUnsubscribeMessage, PancakeChannelInput, PancakeConnectionDefinition, PancakeConnectionInput, PancakeMessageSource, PancakeSource, PendingQuestion, PolicyDefinitionConfig, PolicyDocument, PolicyResource, ProjectDoc, ProviderConfigInput, ProviderSettingsInput, QuestionAnswer, ResourceApi, ResourceDefinition, ResourceInput, ResourceKind, RotateSecretResult, Sandbox, SandboxConfig, SandboxConfigDoc, SandboxDefinitionConfig, SandboxDefinitionOptions, SandboxLifecycleResult, SandboxResource, SandboxSnapshotResult, SandboxTerminalTicket, Skill, SkillDefinitionConfig, SkillResource, SkillUploadInput, SlackChannelInput, SlackConnectionDefinition, SlackConnectionInput, SlackMessageSource, SlackSource, StageDoc, StageScope, TelegramChannelInput, TelegramConnectionDefinition, TelegramConnectionInput, TelegramMessageSource, TelegramSource, ToolApprovalSummary, UpdateAgentInput, UpdateCronInput, UpdateMcpInput, WebSocketAttachInput, WebSocketClientAttachMessage, WebSocketClientCancelMessage, WebSocketClientControlMessage, WebSocketClientExecuteMessage, WebSocketClientMessage, WebSocketConstructorLike, WebSocketHandlers, WebSocketLike, WebSocketOutputMessage, WebSocketRunInput, WebSocketServerMessage, WebSocketStreamMessage, WebSocketSubscription, Workspace, WorkspaceConfig, WorkspaceConfigDoc, WorkspaceDefinitionConfig, WorkspaceFileEntry, WorkspaceResource, ZaloChannelInput, ZaloConnectionDefinition, ZaloConnectionInput, ZaloMessageSource, ZaloSource };