/** * @synap/hub-rest-client — Hub Protocol REST API Types * * Canonical TypeScript interfaces for all objects returned by the * Synap Hub Protocol REST API (`/api/hub/*`). * * These are the source of truth for external consumers (Raycast extension, * CLI, third-party integrations). Keep in sync with hub-protocol-rest.ts * response shapes in synap-backend. * * Zero runtime dependencies — pure TypeScript interfaces. */ interface HubEntity { id: string; title: string; profileSlug: string; workspaceId: string | null; /** JSONB property bag — keys depend on the profile schema */ properties: Record; createdAt: string; updatedAt: string; status?: string; priority?: string; dueDate?: string; content?: string; url?: string; /** Short, server-generated summary suitable for lists and agent context. */ preview?: string | null; /** Optional long-form summary returned by entity detail routes. */ description?: string | null; /** Linked versioned document, when this entity has long-form content. */ documentId?: string | null; /** System-managed fields returned by the canonical entity wire codec. */ systemData?: Record; version?: number; } interface HubDocument { id: string; title: string; content: string; workspaceId: string | null; createdAt: string; updatedAt: string; userId?: string; type?: "text" | "markdown" | "code" | "html" | "pdf" | "docx"; language?: string | null; } interface HubChannel { id: string; name: string; type: "personal" | "thread" | "sub_thread" | "feed" | "external" | "agent_collab"; workspaceId: string | null; agentType?: string; contextObjectType?: "workspace" | "entity" | "document" | "view" | "project" | "task" | "user" | "external" | null; contextObjectId?: string | null; createdAt: string; } interface HubWorkspace { id: string; name: string; role?: string; } /** GET /api/hub/workspaces — canonical Hub Protocol shape (not `data`). */ interface HubWorkspacesListResponse { workspaces: HubWorkspace[]; } /** GET /api/hub/users/me returns at least `id` (and `scopes`); email may be omitted. */ interface HubUser { id: string; email?: string; name?: string; scopes?: string[]; /** True only when the server authenticated this request as an agent credential. */ isAgent: boolean; } interface HubMemoryResult { id: string; content: string; score?: number; createdAt: string; } interface HubListResponse { data: T[]; total?: number; hasMore?: boolean; } interface HubSingleResponse { data: T; } interface CreateEntityInput { profileSlug: string; title: string; workspaceId?: string; /** Existing project to file this entity into via `belongs_to_project`. */ projectId?: string; /** Short description rendered with the entity; long-form text belongs in content. */ description?: string; properties?: Record; content?: string; url?: string; status?: string; priority?: "low" | "medium" | "high" | "urgent"; dueDate?: string; agentUserId?: string; sessionId?: string; reasoning?: string; /** Origin signal for audit/provenance. It does not grant permissions. */ source?: HubWriteSource; sourceMessageId?: string; extractedFromMessageId?: string; /** Role profiles to attach with this entity when the write applies inline. */ facets?: Array<{ /** `profileSlug` is accepted by REST; `slug` remains valid for legacy callers. */ profileSlug?: string; slug?: string; status?: string; properties?: Record; contextEntityId?: string; }>; } interface UpdateEntityInput { title?: string; properties?: Record; content?: string; url?: string; status?: string; priority?: "low" | "medium" | "high" | "urgent"; dueDate?: string; } interface CreateDocumentInput { title: string; content?: string; workspaceId?: string; entityId?: string; type?: HubDocument["type"]; reasoning?: string; agentUserId?: string; sourceMessageId?: string; sessionId?: string; } /** Full-document replacement input for PATCH /documents/:id. */ interface UpdateDocumentInput { content: string; title?: string; agentUserId?: string; sourceMessageId?: string; sessionId?: string; } interface HubDocumentChange { op: "insert" | "delete" | "replace"; position?: number; range?: [number, number]; text?: string; } /** Submit a governed edit proposal without replacing a document directly. */ interface CreateDocumentProposalInput { documentId: string; agentUserId?: string; threadId?: string; sourceMessageId?: string; sessionId?: string; proposalType?: "ai_edit" | "user_suggestion" | "review_comment"; changes: HubDocumentChange[]; proposedContent: string; originalContent?: string; } /** Proposal rows vary slightly by pod version; these stable fields are shared. */ interface HubDocumentProposalResult { id?: string; proposalId?: string; status?: string; reviewUrl?: string; [key: string]: unknown; } interface StoreMemoryInput { fact: string; context?: string; workspaceId?: string; } interface SendToChannelInput { channelId: string; content: string; /** Defaults to the authenticated user (GET /users/me). */ userId?: string; role?: "system" | "assistant" | "user"; /** When true, may queue an IS response on AI-active threads (server-side). */ autoRespond?: boolean; workspaceId?: string; } interface AgentSetupResult { hubApiKey: string; agentUserId: string; workspaceId: string; } interface PodStatus { url: string; healthy: boolean; version?: string; } /** * A single tappable chip in a structured follow-up. Mirrors the IS `structure` * output and the frontend capture-pipeline contract EXACTLY (do NOT narrow). */ interface FollowUpChip { label: string; value: string; action: "link_entity" | "set_property" | "add_relation" | "confirm" | "dismiss"; icon?: string; entityId?: string; propertyKey?: string; } /** Structured follow-up the IS may emit instead of a plain string question. */ interface StructuredFollowUp { question: string; suggestions: FollowUpChip[]; } /** One field of an AI-authored dynamic form. `type` is a free string (field kind). */ interface DynamicFormField { key: string; label: string; type: string; constraints?: { enum?: string[]; min?: number; max?: number; pattern?: string; }; required?: boolean; help?: string; } /** AI-authored guided-capture form spec (additive, null-safe). */ interface DynamicFormSpec { title?: string; note?: string; fields: DynamicFormField[]; } interface CaptureProposal { tempId: string; profileSlug: string; title: string; description?: string; /** Long-form body preserved through plan → commit as a linked document. */ content?: string; properties?: Record; /** Role profiles proposed alongside the primary kind. */ facets?: Array<{ profileSlug: string; status?: string; properties?: Record; /** Batch-local entity reference used by captureExecute. */ contextTempId?: string; }>; confidence: number; action: "create" | "link" | "dismiss"; linkedEntityId?: string; linkedEntityTitle?: string; dedupCandidates?: Array<{ entityId: string; title: string; profileSlug: string; score: number; }>; } interface CaptureRelation { sourceTempId: string; targetTempId: string; relationType: string; } interface CaptureStructureResponse { proposals: CaptureProposal[]; relations: CaptureRelation[]; /** The intelligence service was unavailable, so the pod returned a raw-note fallback. */ degraded?: boolean; followUp: string | StructuredFollowUp | null; formSpec?: DynamicFormSpec | null; targetWorkspaceId?: string | null; targetWorkspaceConfidence?: number | null; targetWorkspaceReason?: string | null; targetProjectId?: string | null; /** * Soft meta-structure suggestions (display-only chips). Never materialize. * Additive — absent when the model has nothing to suggest. */ architectureSuggestions?: Array<{ kind?: "workspace_template" | "new_workspace" | "project" | "view" | "role" | "playbook"; title: string; reason?: string; confidence?: number; payload?: Record; }>; dedupCandidates?: Record>; } interface CaptureExecuteInput { entities: Array<{ tempId: string; profileSlug: string; title: string; description?: string; properties?: Record; /** Legacy structure-output field; the execute route ignores it. */ action?: "create" | "link" | "dismiss"; linkedEntityId?: string; confidence?: number; /** Long-form body materialized as a linked document by the capture pipeline. */ content?: string; /** Reuse an existing entity instead of creating one for this batch entry. */ existingEntityId?: string; /** Role profiles to attach after the primary kind materializes. */ facets?: Array<{ profileSlug: string; status?: string; properties?: Record; contextTempId?: string; }>; }>; relations?: CaptureRelation[]; /** Cross-cutting project lens to file the created entities into. */ projectId?: string | null; /** Explicit reviewed placement override; unlike workspaceId it is never inferred. */ targetWorkspaceId?: string | null; /** Preserve the original binary source with the primary derived entity. */ keepRaw?: boolean; file?: { /** Base64 payload; server caps it at about 5MB decoded. */ content: string; mimeType: string; filename?: string; }; /** Client-stable retry namespace for this capture execution. */ idempotencyKey?: string; /** * Workspace routing (shared across all capture doors). Forward the AI's * structure hints + the caller's mode so the door auto-routes; the backend * decides the final workspace (auto/ask/locked, confidence + membership gated). */ workspaceRouting?: "auto" | "ask" | "locked"; aiWorkspaceId?: string | null; aiWorkspaceConfidence?: number | null; aiWorkspaceReason?: string | null; } /** One planned entity in the proposal-first graph capture door. */ interface CaptureGraphEntity { /** Batch-local ID used by relations and bindings. Must be unique per request. */ ref: string; profileSlug: string; title?: string; /** Short descriptive body retained on the approved entity. */ description?: string; /** Long-form body materialized through the canonical document path on approval. */ content?: string; properties?: Record; /** Link this graph node to an existing entity rather than creating it. */ existingEntityId?: string; /** Role profiles to attach after the primary kind materializes. */ facets?: Array<{ profileSlug: string; status?: string; properties?: Record; contextRef?: string; }>; } interface CaptureGraphRelation { sourceRef: string; targetRef: string; type: string; } /** Optional post-approval external-channel binding for an entity in the graph. */ interface CaptureGraphBinding { externalChannelId: string; entityRef: string; branchPurpose?: "client-comms" | "team"; title?: string; } /** * Bounded original-input context retained in proposal data for review/retry. * It is deliberately not a materialized source artifact or entity provenance. */ interface CaptureGraphRawSource { rawText?: string; sourceUrl?: string; label?: string; mimeType?: string; hash?: string; idempotencyKey?: string; } /** Input for POST /capture/graph. The server always creates one composite proposal. */ interface SubmitCaptureGraphInput { workspaceId?: string | null; /** Existing project to file every newly created graph entity into on approval. */ projectId?: string | null; /** Origin signal preserved through proposal approval and materialization. */ source?: HubWriteSource; sourceMessageId?: string; sessionId?: string; rawSource?: CaptureGraphRawSource; entities: CaptureGraphEntity[]; relations?: CaptureGraphRelation[]; bindings?: CaptureGraphBinding[]; summary?: string; } interface SubmitCaptureGraphResult { /** Composite graph writes are proposal-first, so this receipt begins pending. */ writeReceipt?: HubWriteReceipt; proposalId?: string; entityCount: number; relationCount: number; bindingCount: number; reviewUrl?: string; summary: string; } interface CaptureExecuteResponse { created: Array<{ tempId: string; entityId: string; profileSlug: string; linked: boolean; }>; relations: Array<{ sourceTempId: string; targetTempId: string; relationType: string; }>; /** Set when AUTO routing moved the capture to the AI-resolved workspace. */ movedToWorkspace?: string; /** Set in ASK mode — a suggested switch for the surface to confirm. */ pendingWorkspaceSwitch?: { suggestedWorkspaceId: string; reason: string | null; confidence: number | null; }; } /** One substrate's slice of an `ask` answer (semantic / procedural / episodic). */ interface AskAnswerBlock { substrate: string; items: Array>; status: "ok" | "error"; } /** * The provenance-tagged result of `ask` — glass-box: it says which substrates * were queried (`routedTo`), what the query's cue suggested (`intent`), and the * per-substrate answer blocks. Shape mirrors the backend `AskResult`. */ interface AskResponse { query: string; routedTo: string[]; intent: string; answers?: AskAnswerBlock[]; verdict?: string; [key: string]: unknown; } interface HubRelation { id: string; sourceEntityId: string; targetEntityId: string; type: string; label?: string; createdAt: string; } interface HubGraphNode extends HubEntity { depth: number; } interface HubGraphEdge { sourceId: string; targetId: string; type: string; label?: string; } interface HubGraphResult { nodes: HubGraphNode[]; edges: HubGraphEdge[]; } /** * A single link returned by getConnections() across the local graph: * - `"graph"` : an explicit row in the relations table * - `"property"` : an inbound or outbound `entity_id` property edge * - `"thread"` / `"context_channel"` : a channel that touched or is about this entity * - `"focus_session"` : a session anchored to this entity */ interface HubConnection { entityId: string; entity: HubEntity | null; label: string; direction: "outgoing" | "incoming" | "structural"; source: "graph" | "property" | "thread" | "context_channel" | "focus_session"; relationId?: string; relationType?: string; propertySlug?: string; propertyLabel?: string; channelId?: string; channelRelationshipType?: string; channelTitle?: string | null; channelWorkspaceId?: string | null; focusSessionId?: string; focusSessionGoal?: string; focusSessionStatus?: string; focusSessionWorkspaceId?: string | null; createdAt?: string | null; } interface HubConnectionsResult { connections: HubConnection[]; counts: { total: number; graph: number; structural: number; threads: number; contextChannels?: number; focusSessions?: number; }; } interface HubProfile { id: string; slug: string; displayName: string; description?: string; entityScope: "pod" | "workspace"; parentSlug?: string; icon?: string; color?: string; properties?: HubPropertyDef[]; /** Primary entity kind or attachable role/facet. Defaults to kind on old pods. */ profileKind?: "kind" | "role"; /** Kinds this role can be attached to; null means the pod did not constrain it. */ applicableKinds?: string[] | null; } interface HubPropertyDef { id: string; slug: string; displayName: string; type: "string" | "number" | "boolean" | "date" | "entity_id" | "array" | "object" | "secret"; required?: boolean; options?: string[]; } interface HubDiscoverProperty { slug: string; displayName: string; type: string; options?: string[]; required?: boolean; /** Default the validator applies when this property is omitted. */ defaultValue?: unknown; /** Exact validation constraints used by the property validator. */ constraints?: Record; /** Target kind for an entity_id property, when configured. */ targetProfileSlug?: string; /** Base definitions are always visible; workspace definitions require this lens. */ schemaScope?: "base" | "workspace"; workspaceId?: string | null; } interface HubDiscoverProfile { slug: string; displayName: string; scope: "pod" | "workspace"; description?: string | null; icon?: string | null; /** Omitted by the summary tier. */ properties?: HubDiscoverProperty[]; /** Omitted by the summary tier. */ createCommand?: string; profileKind?: "kind" | "role"; applicableKinds?: string[] | null; } interface HubDiscoverResult { profiles: HubDiscoverProfile[]; commands: Record; hint: string; } /** Progressive-disclosure controls for GET /discover. */ interface HubDiscoverOptions { /** * Omit to read the base/pod schema only. Supplying a workspace resolves only * that workspace's overlays; callers must never substitute a default here. */ workspaceId?: string; /** Return the digest tier without property schemas. */ summary?: boolean; /** Limit full discovery to these profile slugs when the pod supports it. */ profileSlugs?: string[]; } type HubOrientScope = "workspaces" | "projects" | "profiles"; type HubOrientDetail = "light" | "full"; interface HubOrientProfile { slug: string; name: string; profileKind: "kind" | "role"; applicableKinds?: string[] | null; /** Placement for entities of this kind; distinct from profile visibility. */ entityScope?: "pod" | "workspace" | null; } interface HubOrientWorkspace { id: string; name: string; domain: string | null; entityCount: number; onboarding?: Record; description?: string | null; profiles?: HubOrientProfile[]; } interface HubOrientProject { id: string; name: string; description: string | null; status: string | null; workspaceId: string | null; homeWorkspace: string | null; } /** * Prompt-facing team roster for a workspace (no emails). Present when the * pinned/sample workspace has human members. Treat as internal — not contacts. */ interface HubOrientTeamRoster { instructionBlock: string | null; names: string[]; members: Array<{ displayName: string; personId?: string | null; }>; } /** Canonical session bootstrap response shared by MCP, CLI, and REST surfaces. */ interface HubOrientResult { me: { userId: string; scopes: string[]; }; detail: HubOrientDetail; projects: HubOrientProject[]; projectCount: number; workspaces: HubOrientWorkspace[]; workspaceCount: number; profiles: HubOrientProfile[]; note: string; /** Internal team for the pinned/sample workspace — omitted when empty. */ teamRoster?: HubOrientTeamRoster; } interface HubOrientOptions { detail?: HubOrientDetail; scope?: HubOrientScope[]; workspaceId?: string; projectId?: string; } interface HubThread { id: string; name?: string; type: "personal" | "thread" | "sub_thread" | "feed" | "external" | "agent_collab"; workspaceId?: string; agentType?: string; contextObjectType?: "workspace" | "entity" | "document" | "view" | "project" | "task" | "user" | "external"; contextObjectId?: string; parentChannelId?: string; linkedEntityIds?: string[]; linkedDocumentIds?: string[]; createdAt: string; updatedAt: string; } interface HubMessage { id: string; content: string; role: "user" | "assistant" | "system"; userId?: string; createdAt: string; } interface HubThreadContext { thread: HubThread; messages: HubMessage[]; linkedEntities: HubEntity[]; linkedDocuments: HubDocument[]; } interface HubProposal { id: string; status: "pending" | "approved" | "rejected"; action: "create" | "update" | "delete"; subjectType: string; data: Record; reason?: string; createdAt: string; reviewedAt?: string; } interface HubView { id: string; name: string; type: "table" | "kanban" | "list" | "grid" | "gallery" | "calendar" | "timeline" | "graph" | "bento" | string; profileSlug?: string; workspaceId?: string; config?: Record; metadata?: Record; profileId?: string | null; userId?: string; createdAt: string; updatedAt: string; } interface HubSearchResult { entities: HubEntity[]; documents: HubDocument[]; total: number; } interface HubCommand { id: string; name: string; slug: string; description?: string; workspaceId?: string; } interface HubAgentUser { id: string; name: string; agentType?: string; workspaceId?: string; } interface HubUserContext { recentEntities: HubEntity[]; activeThreads: HubThread[]; workspaceSummary?: Record; } type HubWriteSource = "intelligence" | "agent" | "openwebui-pipeline" | "extension" | "cli" | "n8n" | "raycast"; /** * Truthful outcome envelope shared by direct and proposal-first write doors. * `partial` means independently-applied sub-operations failed; it never * implies an atomic rollback. */ interface HubWriteReceipt { state: "pending" | "applied" | "partial"; proposalId?: string; reviewUrl?: string; entityId?: string; proposedEntityId?: string; profileSlug?: string; effectiveWorkspaceId?: string | null; projectId?: string; source?: HubWriteSource; facets?: Array<{ slug: string; outcome: "attached" | "proposed" | "dropped" | "error" | string; facetId?: string; proposalId?: string; error?: string; }>; warnings?: string[]; } interface HubGovernanceResult { /** `created` is an inline, materialized write; `proposed` remains pending. */ status: "approved" | "created" | "proposed" | "denied"; id?: string; proposalId?: string; reason?: string; message?: string; /** * Short human-readable summary of what was proposed. Present on `proposed` * responses. Example: `Delete task "Q2 plan review"`. */ summary?: string; /** * Reasoning — echoed from the AI's rationale or the policy's explanation * of why review is needed. Present on `proposed` responses. */ reasoning?: string; /** * Pod-relative path into the app: `/open/{id}`. * Present on `proposed` responses. */ reviewPath?: string; /** * Absolute clickable link into the app: `${PUBLIC_URL}/open/{id}`. The pod * resolves the id's type server-side and bounces to the Electron app. Surface * this directly to the user so they can approve without digging through the app. */ reviewUrl?: string; /** Additive receipt for write-aware clients. Legacy clients may keep using status/id. */ writeReceipt?: HubWriteReceipt; } interface CreateThreadInput { name?: string; type?: HubThread["type"]; workspaceId?: string; agentType?: string; entityId?: string; documentId?: string; userId?: string; } interface CreateRelationInput { sourceEntityId: string; targetEntityId: string; type: string; label?: string; workspaceId?: string; userId?: string; } /** Attach an existing role-profile to a primary-kind entity. */ interface AttachFacetInput { entityId: string; profileSlug?: string; profileId?: string; workspaceId?: string | null; contextEntityId?: string | null; status?: string; properties?: Record; reasoning?: string; } /** * Result of `POST /entities/{entityId}/facets`. * * NOT a `HubGovernanceResult`: the facet door's inline-write status is * `attached` (never `created`/`approved`), and it reports the new row as * `facetId` rather than `id`. Source of truth for both branches: * `entities.attachFacet` in `packages/api/src/routers/entities.ts`. */ interface HubAttachFacetResult { status: "attached" | "proposed"; message?: string; /** Present on the inline `attached` branch. */ facetId?: string; /** The materialized facet row on `attached`; `null` on `proposed`. */ facet?: Record | null; /** Present on the `proposed` branch. */ proposalId?: string; proposalType?: string; reviewUrl?: string; } interface CreateViewInput { name: string; type: HubView["type"]; profileSlug?: string; workspaceId: string; config?: Record; metadata?: Record; userId?: string; agentUserId?: string; reasoning?: string; sourceMessageId?: string; } interface UpdateViewInput { name?: string; config?: Record; metadata?: Record; workspaceId?: string; userId?: string; agentUserId?: string; reasoning?: string; sourceMessageId?: string; } interface BentoWidgetInput { /** Canonical Hub-router field naming the cell type. */ key: string; x: number; y: number; w: number; h: number; config?: Record; props?: Record; } interface ArrangeBentoViewInput { workspaceId?: string; userId?: string; widgets: BentoWidgetInput[]; agentUserId?: string; reasoning?: string; sourceMessageId?: string; } interface HubBentoArrangementResult { status: string; viewId?: string; widgetCount?: number; message?: string; proposalId?: string; reviewUrl?: string; } interface HubCapabilityVerb { id?: string; verbId?: string; label?: string; type?: "read" | "write"; enabled?: boolean; granted?: boolean; runnable?: boolean; governance?: "auto" | "propose"; effectiveExecMode?: string; govDefault?: string; [key: string]: unknown; } /** Flat capability read-model for callers that need every granted verb. */ interface HubCapability { id?: string; name?: string; key?: string; kind?: string; description?: string | null; verbs?: HubCapabilityVerb[]; governance?: Record; approved?: boolean; [key: string]: unknown; } interface HubCapabilityCatalogConnection { required: boolean; kind: "provider" | "vault" | null; provider?: string; /** `unavailable`: this pod's Nango doesn't declare the provider — no connect action exists. */ state: "connected" | "missing" | "expired" | "unavailable"; account?: string; } interface HubCapabilityCatalogCard { id: string | null; key: string; name: string; description?: string | null; source: "installed" | "available"; status: "available" | "needs_connection" | "connected" | "draft" | "ready" | "partial" | "unavailable"; connection?: HubCapabilityCatalogConnection; verbs: Array<{ verbId: string; label: string; type: "read" | "write"; enabled: boolean; governance: "auto" | "propose"; runnable: boolean; }>; nextAction: { kind: "add" | "connect" | "enable" | "run" | "none"; hint: string; }; } interface HubCapabilityCatalogResult { capabilities: HubCapabilityCatalogCard[]; } /** One action the shared capability execute door can launch immediately. */ interface HubRunnableCapabilityAction { skillId?: string; verbId?: string; label: string; description?: string | null; tool: string | null; connection?: { required: true; state: "connected"; provider: string; }; governance: "auto"; executionMode?: string; parameters: Record; } interface HubRunnableCapabilityActionsResult { actions: HubRunnableCapabilityAction[]; } interface ExecuteCapabilityInput { verbId?: string; skillId?: string; parameters?: Record; workspaceId?: string; connectionSelector?: { connectionId?: string; contextObjectId?: string; }; } type ExecuteCapabilityResult = { status: "run" | "dry-run"; skillId: string; result?: unknown; dryRun?: boolean; } | { proposed: true; proposalId: string; reviewUrl?: string; }; interface HubAgentSkill { id: string; slug: string; name: string; description: string | null; topics: string[]; body: string | null; source: string | null; author: string | null; version: string | null; tags: string[]; teachesTools: string[]; skillGroup: string | null; alwaysOn: boolean; createdAt: string; updatedAt: string; } interface ListAgentSkillsOptions { topic?: string; query?: string; tag?: string; /** Restrict to the seeded system/* teaching catalog. */ system?: boolean; /** Include visible workspace-scoped skills for this selected lens. */ workspaceId?: string; limit?: number; offset?: number; } interface HubAgentSkillsResult { skills: HubAgentSkill[]; total: number; } interface GetCapabilityBriefsInput { tools: string[]; workspaceId?: string; door?: "chat" | "automation"; } interface HubCapabilityBriefsResult { briefs: Record; } interface ExecuteCommandInput { slug: string; workspaceId?: string; parameters?: Record; userId?: string; } type AutomationStatus = "draft" | "active" | "paused" | "error"; type AutomationTriggerType = "event" | "cron" | "webhook" | "manual"; interface HubAutomation { id: string; userId: string; workspaceId?: string | null; name: string; description?: string | null; triggerType: AutomationTriggerType; triggerConfig?: Record; flowDefinition?: { nodes: Record[]; edges: Record[]; }; status: AutomationStatus; metadata?: Record; createdAt?: string; updatedAt?: string; } interface CreateAutomationInput { name: string; triggerType: AutomationTriggerType; workspaceId?: string | null; description?: string; triggerConfig?: Record; flowDefinition?: { nodes: Record[]; edges: Record[]; }; status?: AutomationStatus; metadata?: Record; userId?: string; agentUserId?: string; } interface UpdateAutomationInput { name?: string; description?: string; triggerType?: AutomationTriggerType; triggerConfig?: Record; flowDefinition?: { nodes: Record[]; edges: Record[]; }; status?: AutomationStatus; metadata?: Record; workspaceId?: string; userId?: string; } type ReactionKind = "automation" | "ai_feed" | "ai_react" | "notify" | "webhook" | "message_out"; type ReactionLens = "all" | "internal" | "external"; /** Opaque reaction event from the Pulse feed — shape varies by kind. */ interface HubReactionEvent { id: string; eventType: string; kind?: ReactionKind; workspaceId?: string | null; userId?: string; createdAt: string; reactions?: Record[]; [key: string]: unknown; } type NotificationSourceType = "proposal" | "connector" | "agent" | "system" | "inbox_item"; interface CreateNotificationInput { userId: string; workspaceId: string; type: string; sourceType?: NotificationSourceType; sourceId?: string; workspaceUrl?: string; groupKey?: string; data?: Record; } interface HubWebhookDelivery { id: string; subscriptionId: string; status: string; responseStatus?: number; attempt: number; deliveredAt?: string; createdAt: string; } /** * HubRestClient — typed HTTP client for the Synap Hub Protocol REST API. * * Uses the native `fetch` API (Node.js >= 18, browsers, Deno, Bun). * Zero runtime dependencies. * * @example * ```ts * const client = new HubRestClient({ * podUrl: "https://my-pod.synap.live", * apiKey: "synap_hub_live_...", * }); * * const entities = await client.searchEntities("meeting notes", { profileSlug: "note" }); * ``` */ interface HubRestClientConfig { /** Pod URL, e.g. https://my-pod.synap.live */ podUrl: string; /** Hub Protocol API key (Bearer token) */ apiKey: string; /** Default workspace ID — used when not specified per call */ workspaceId?: string; /** Optional request timeout in ms (default: 30000). Fallback when read/write timeouts are unset. */ timeoutMs?: number; /** Optional read (GET) timeout in ms (default: timeoutMs ?? 30000). */ readTimeoutMs?: number; /** Optional write (non-GET) timeout in ms (default: timeoutMs ?? 30000). */ writeTimeoutMs?: number; /** * Max attempts for a request (default: 3). Retries ONLY on network failure or * 5xx — never on 4xx, never on a caller-provided abort. Set to 1 to disable. */ maxAttempts?: number; } declare class HubRestClient { private readonly base; private readonly headers; private readonly timeoutMs; private readonly readTimeoutMs; private readonly writeTimeoutMs; private readonly maxAttempts; readonly workspaceId: string | undefined; /** Cached from GET /users/me — avoids repeated identity calls. */ private resolvedUserId; constructor(config: HubRestClientConfig); /** User id for the current API key (Hub REST requires userId on several GETs). */ private resolveUserId; /** * The ONE shared request loop. Per-method timeout (GET = read, else write, or a * caller override) + up to `maxAttempts` with exponential backoff. Retries ONLY * on a network failure or 5xx; NEVER on a 4xx or a caller abort. Returns the raw * `Response` for any non-5xx (ok OR 4xx) — the typed entry points below decide * how to interpret it. This is the single source of retry/timeout truth, shared * by the CLI and the IS's `ISHubClient` (which reuses the protected entry points * rather than re-implementing fetch). */ private fetchWithRetry; /** Build a HubApiError from a non-ok Response (reads the JSON error body). */ private toHubError; /** Parse a 2xx body, tolerating an empty/204 response (returns undefined). */ private parseBody; /** * Typed JSON request — throws `HubApiError` on any non-2xx. Protected so the IS * subclass reuses it. Tolerates an empty/204 body (returns `undefined`). */ protected request(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise; /** * Like `request` but returns `null` on 404/403 (the "absent/forbidden → * empty" contract several IS reads use) instead of throwing. */ protected requestOrNull(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise; /** * The raw `Response` (same retry/timeout infra), for the few callers that read * `.text()`, branch on status themselves, or need a per-call timeout override. */ protected requestRaw(method: string, path: string, body?: unknown, signal?: AbortSignal, timeoutMsOverride?: number): Promise; getMe(): Promise; getWorkspaces(): Promise; provisionAgentWorkspace(input: { agentUserId: string; workspaceName?: string; }): Promise<{ workspaceId: string; created: boolean; }>; /** * Get full activity context for a user — recent entities, active threads, workspace summary. * Use at session start to orient the agent to the user's current state. */ getUserContext(userId: string, options?: { workspaceId?: string; }): Promise; searchEntities(query: string, options?: { profileSlug?: string; workspaceId?: string; /** * "workspace" (default) — applies the client workspaceId filter. * "all" — omits workspaceId entirely so results span all workspaces. */ scope?: "workspace" | "all"; limit?: number; }, signal?: AbortSignal): Promise; getEntity(id: string): Promise; getRecentEntities(options?: { profileSlug?: string; workspaceId?: string; limit?: number; /** "all" — omits workspaceId so results span all workspaces the user can access. */ scope?: "workspace" | "all"; }): Promise; createEntity(input: CreateEntityInput): Promise; updateEntity(id: string, input: UpdateEntityInput): Promise; /** * Unified full-text search across entities, documents, and views. * Use when you don't know the content type. For entity-only search use searchEntities(). * * Note: The backend GET /search requires userId as a query param; this method resolves * the current user automatically. */ search(query: string, options?: { collections?: Array<"entities" | "documents" | "views">; workspaceId?: string; limit?: number; }, signal?: AbortSignal): Promise; /** * Get all relations for an entity — inbound and outbound. * Use to discover connections before graph traversal. * * Note: The backend GET /relations requires both userId and workspaceId. * This method resolves userId automatically; workspaceId falls back to client default. */ getRelations(entityId: string, options?: { workspaceId?: string; }): Promise; /** * Create a typed relation between two entities. * Type is a free string — conventions: "related_to", "parent_of", "child_of", * "belongs_to", "authored_by", "depends_on", "references". * Goes through governance — may return "proposed". */ createRelation(input: CreateRelationInput): Promise; /** * Attach a role-profile through the canonical governed facet door. A result * may be `proposed`; callers must surface its review information instead of * claiming the role was applied. */ attachFacet(input: AttachFacetInput): Promise; /** * Delete a relation by ID (get from getRelations()). */ deleteRelation(relationId: string): Promise; /** * Traverse the knowledge graph from an entity using BFS. * Returns nodes and edges up to maxDepth hops away. * maxDepth: 1=direct neighbors, 2=neighborhood (recommended), 3=extended (expensive). * * @example * const graph = await client.traverseGraph(projectId, { maxDepth: 2 }); * const tasks = graph.nodes.filter(n => n.profileSlug === "task"); */ traverseGraph(entityId: string, options?: { maxDepth?: number; workspaceId?: string; }): Promise; /** * Unified view of everything connected to an entity across the local graph: * 1. Graph relations — explicit rows in the relations table (both directions) * 2. Structural links — inbound and outbound `entity_id` property edges * 3. Channel and focus-session connections around this entity * * Prefer this over `getRelations()` / `traverseGraph()` when you want the complete * picture — those only see the relations table and miss property-based links that * haven't been synced (notably custom profiles without a `relationDefId` mapping). * * Each connection carries a `source` field so callers can filter by origin. * * @example * const { connections } = await client.getConnections(entityId); * const tasks = connections.filter(c => c.entity?.profileSlug === "task"); */ getConnections(entityId: string, options?: { workspaceId?: string; limit?: number; }): Promise; /** * Canonical session bootstrap shared by the MCP, CLI, and external surfaces. * It is deliberately a lens map, not a profile/schema dump; use discover() * only for the selected profiles needed by the next action. */ orient(options?: HubOrientOptions): Promise; /** * List all entity profile types in the workspace. * Always call before creating entities to discover what types are available. * Returns system profiles (always present) + custom workspace profiles. */ listProfiles(workspaceId: string, options?: { detail?: "full"; }): Promise; /** * List property definitions for a workspace, optionally filtered by profile. */ listPropertyDefs(workspaceId: string, options?: { profileSlug?: string; }): Promise; /** * Runtime discovery — profiles with property schemas + command tree. * * Call once per session at session start. Returns ground-truth profile * schemas (including custom workspace profiles) and the canonical CLI * command map. Replaces static skill file profile descriptions. */ discover(options?: string | HubDiscoverOptions): Promise; /** * List threads (channels) accessible to a user. */ listThreads(userId: string, options?: { workspaceId?: string; type?: string; }): Promise; /** * Get the user's personal channel — their private AI conversation thread. * Use as default destination for messages and proactive posts. * * Note: The backend GET /channels/personal requires both userId and workspaceId. */ getPersonalChannel(userId: string, workspaceId?: string): Promise; /** * Create a new thread. Pass entityId to auto-link on creation. * * Note: The backend POST /threads requires workspaceId in the body. */ createThread(input: CreateThreadInput): Promise; /** * Get full thread context: messages + all linked entities and documents. * Call before sending a message to orient the AI with conversation history. */ getThreadContext(threadId: string): Promise; /** * Get messages in a thread. */ getMessages(threadId: string, _options?: { limit?: number; before?: string; }): Promise; /** * Link an entity to a thread so it appears in thread context for AI. */ linkEntityToThread(threadId: string, entityId: string): Promise; /** * Link a document to a thread. */ linkDocumentToThread(threadId: string, documentId: string): Promise; /** * Get research branches of a thread — parallel AI investigations. */ getThreadBranches(threadId: string): Promise>; storeMemory(input: StoreMemoryInput): Promise<{ id: string; }>; recallMemory(query: string, options?: { workspaceId?: string; limit?: number; }): Promise; /** * Delete a stored memory fact by ID. */ deleteMemory(memoryId: string): Promise; getChannels(options?: { workspaceId?: string; }): Promise; sendToChannel(input: SendToChannelInput): Promise<{ id: string; }>; /** * List proposals — pending AI writes awaiting human review. * Filter by status: "pending" (needs review), "approved", "rejected". */ listProposals(options?: { status?: "pending" | "approved" | "rejected"; workspaceId?: string; /** * "workspace" (default) — applies the client workspaceId filter. * "all" — omits workspaceId so results span every workspace the user can see. */ scope?: "workspace" | "all"; limit?: number; }): Promise; /** * Approve or reject a proposal. * * Note: The backend PATCH /proposals/:id is an AI-revision endpoint that updates * the proposal data/summary, not a review (approve/reject) endpoint. * Use this to update proposal data before human review. */ reviewProposal(proposalId: string, decision: "approved" | "rejected", reason?: string): Promise; /** * List data views in a workspace. */ listViews(workspaceId: string, options?: { profileSlug?: string; }): Promise; /** * Create a new view. Goes through governance. */ createView(input: CreateViewInput): Promise; /** Update a view through the existing governed Hub route. */ updateView(viewId: string, input: UpdateViewInput): Promise; /** Replace a bento view's widget arrangement through the existing Hub route. */ arrangeBentoView(viewId: string, input: ArrangeBentoViewInput): Promise; /** * Get a document by ID with full markdown content. */ getDocument(documentId: string): Promise; /** * Create a document. Use for long-form content: meeting notes, research, writeups. * Goes through governance. */ createDocument(input: CreateDocumentInput & { workspaceId?: string; }): Promise; /** Propose a full document-content replacement through the governed Hub route. */ updateDocument(documentId: string, input: UpdateDocumentInput): Promise; /** Submit a structured, reviewable document-edit proposal. */ createDocumentProposal(input: CreateDocumentProposalInput): Promise; /** Flat capability read-model. Prefer getCapabilityCatalog() for presentation. */ listCapabilities(options?: { workspaceId?: string; }): Promise; /** Status-computed, pack-grouped capability catalog for every external surface. */ getCapabilityCatalog(options?: { workspaceId?: string; extraKey?: string; }): Promise; /** List only approved, connected actions that this client can execute now. */ listRunnableCapabilityActions(options?: { workspaceId?: string; query?: string; kind?: string; limit?: number; }): Promise; /** Run one registered capability through the shared governance gate. */ executeCapability(input: ExecuteCapabilityInput): Promise; /** List the compact system teaching catalog, or search the available skill index. */ listAgentSkills(options?: ListAgentSkillsOptions): Promise; /** Load one skill body only when it is relevant to the agent's next action. */ getAgentSkillBySlug(slug: string, options?: { workspaceId?: string; }): Promise; /** Compose just-in-time teaching and governance briefs for selected tools. */ getCapabilityBriefs(input: GetCapabilityBriefsInput): Promise; /** * List available commands (automation shortcuts) in the workspace. */ listCommands(workspaceId?: string): Promise; /** * Execute a command by slug. * * Note: The backend POST /commands/execute uses a `command` field (the shell command * string) and `userId`, not a `slug`. This maps ExecuteCommandInput.slug to `command`. */ executeCommand(input: ExecuteCommandInput): Promise<{ status: string; result?: unknown; }>; /** * List agent users provisioned in the workspace. */ listAgentUsers(workspaceId?: string): Promise; /** * Post a proactive message to the user's personal channel. * For AI-initiated insights and summaries. Rate-limited: 3/hour, 10/day. * proactiveType must be one of: insight, suggestion, alert, nudge, * morning_briefing, weekly_digest, health_check. */ postProactive(userId: string, content: string, options?: { workspaceId?: string; type?: string; }): Promise<{ id: string; }>; captureStructure(input: { text: string; url?: string; workspaceId?: string; previousEntities?: CaptureProposal[]; }): Promise; captureExecute(input: CaptureExecuteInput & { workspaceId?: string; }): Promise; /** * Submit a designed entity graph as ONE reviewable composite proposal. * * This is intentionally distinct from captureExecute(): execute materializes * a prior structure result immediately, while this door keeps an autonomous * graph plan reviewable and applies entities, relations, and bindings together * only after approval. */ submitCaptureGraph(input: SubmitCaptureGraphInput): Promise; /** * `ask` — the unified recall verb. Routes a natural-language question across * all knowledge substrates (semantic entities, procedural runbooks, episodic * facts) server-side and returns ONE provenance-tagged answer. The canonical * recall door — prefer it over the fragmented searchEntities / recallMemory. * The server builds the profile catalog from the caller's workspace; the * client only sends the query (+ optional scope). */ ask(input: { query: string; workspaceId?: string; limit?: number; /** * Return just the glass-box understanding + routing (no retrieval) — for a * caller that routes a query before fetching results (e.g. a palette * completing a type word). `answers` comes back empty. */ parseOnly?: boolean; }): Promise; /** * List automations for the current user, optionally filtered by workspace and status. */ listAutomations(options?: { workspaceId?: string; status?: AutomationStatus; limit?: number; }): Promise; /** * Get a single automation by ID. */ getAutomation(automationId: string, options?: { workspaceId?: string; }): Promise; /** * Create an automation. Defaults to status=draft. * Use activateAutomation() to enable it. */ createAutomation(input: CreateAutomationInput): Promise; /** * Update an automation's definition or metadata. */ updateAutomation(automationId: string, input: UpdateAutomationInput): Promise; /** * Manually trigger an automation once with an optional payload. * Bypasses the automation's normal trigger config. */ triggerAutomation(automationId: string, options?: { payload?: Record; workspaceId?: string; }): Promise<{ status: string; runId?: string; result?: unknown; }>; /** * Activate a draft or paused automation (sets status=active). */ activateAutomation(automationId: string, options?: { workspaceId?: string; }): Promise; /** * Pause an active automation (sets status=paused). */ pauseAutomation(automationId: string, options?: { workspaceId?: string; }): Promise; /** * List the user-wide Pulse feed — the timestamp-sorted union of reactive events. * Call getSubscriptionFanout() on an individual event for its dense reactions[]. */ listSubscriptions(options?: { workspaceId?: string; kind?: ReactionKind; eventType?: string; lens?: ReactionLens; limit?: number; }): Promise; /** * Get the reaction fan-out for a single event — full reactions[] populated. */ getSubscriptionFanout(eventId: string, options?: { lens?: ReactionLens; }): Promise; /** * Persist a notification and emit notification:new to the frontend. * Use for IS-originated events (skill.triggered, agent actions, etc.). * Backend-originated notifications (vault, proposals) use NotificationService directly. */ createNotification(input: CreateNotificationInput): Promise<{ id: string; }>; /** * List delivery log for a webhook subscription. * Powers the Reactions Health tab and replay flows. */ getWebhookDeliveries(subscriptionId: string, options?: { limit?: number; }): Promise; } /** * Hub Protocol REST API error class. * Thrown when the server returns a non-2xx response. */ declare class HubApiError extends Error { readonly statusCode: number; readonly body?: unknown | undefined; constructor(message: string, statusCode: number, body?: unknown | undefined); get isUnauthorized(): boolean; get isForbidden(): boolean; get isNotFound(): boolean; get isServerError(): boolean; } /** * Pod setup and health utilities. * * These functions are the canonical implementations shared between * the Synap CLI and the Raycast extension. They use the native fetch API * and have zero Node.js-specific dependencies. * * ⚠️ `assertValidPodUrl` below is a DUPLICATE of the guard in * `@synap-core/auth-bootstrap` (`src/url.ts`). It is copied, not imported, * because this package is deliberately zero-dependency. The two copies must * be kept in sync — change one, change the other. */ /** Shared per-request knobs for the credential-bearing bootstrap calls. */ interface BootstrapRequestOptions { /** Allow `http://` pod URLs (localhost / local-mode dev only). */ allowHttp?: boolean; } /** * Check whether a Synap pod is healthy. * Hits `GET {podUrl}/health` with a 5s timeout. */ declare function checkPodHealth(podUrl: string): Promise; /** * Create an agent user + Hub Protocol API key on the pod. * * Auth: `Authorization: Bearer ` * The provisioning token is either: * - The pod's `PROVISIONING_TOKEN` env var (self-hosted path) * - A CP-signed `agent_setup` JWT (managed pod path) * * Endpoint: `POST {podUrl}/api/hub/setup/agent` */ declare function setupAgent(podUrl: string, provisioningToken: string, agentType: string, opts?: BootstrapRequestOptions): Promise; export { type AgentSetupResult, type ArrangeBentoViewInput, type AskAnswerBlock, type AskResponse, type BentoWidgetInput, type CaptureExecuteInput, type CaptureExecuteResponse, type CaptureGraphBinding, type CaptureGraphEntity, type CaptureGraphRawSource, type CaptureGraphRelation, type CaptureProposal, type CaptureRelation, type CaptureStructureResponse, type CreateDocumentInput, type CreateDocumentProposalInput, type CreateEntityInput, type CreateRelationInput, type CreateThreadInput, type CreateViewInput, type ExecuteCapabilityResult, type ExecuteCommandInput, type GetCapabilityBriefsInput, type HubAgentSkill, type HubAgentSkillsResult, type HubAgentUser, HubApiError, type HubAttachFacetResult, type HubBentoArrangementResult, type HubCapability, type HubCapabilityBriefsResult, type HubCapabilityCatalogCard, type HubCapabilityCatalogConnection, type HubCapabilityCatalogResult, type HubCapabilityVerb, type HubChannel, type HubCommand, type HubConnection, type HubConnectionsResult, type HubDiscoverOptions, type HubDiscoverProfile, type HubDiscoverProperty, type HubDiscoverResult, type HubDocument, type HubDocumentChange, type HubDocumentProposalResult, type HubEntity, type HubGovernanceResult, type HubGraphEdge, type HubGraphNode, type HubGraphResult, type HubListResponse, type HubMemoryResult, type HubMessage, type HubOrientDetail, type HubOrientOptions, type HubOrientProfile, type HubOrientProject, type HubOrientResult, type HubOrientScope, type HubOrientTeamRoster, type HubOrientWorkspace, type HubProfile, type HubPropertyDef, type HubProposal, type HubRelation, HubRestClient, type HubRestClientConfig, type HubRunnableCapabilityAction, type HubRunnableCapabilityActionsResult, type HubSearchResult, type HubSingleResponse, type HubThread, type HubThreadContext, type HubUser, type HubUserContext, type HubView, type HubWorkspace, type HubWorkspacesListResponse, type HubWriteReceipt, type HubWriteSource, type ListAgentSkillsOptions, type PodStatus, type SendToChannelInput, type StoreMemoryInput, type SubmitCaptureGraphInput, type SubmitCaptureGraphResult, type UpdateDocumentInput, type UpdateEntityInput, type UpdateViewInput, checkPodHealth, setupAgent };