@engramind/client 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/client.ts","../src/server/routes.ts","../src/server/proxy.ts","../src/server/webhook.ts","../src/server/bus.ts","../src/server/sse.ts","../src/server/context.ts","../src/server/mcp.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\"\nimport { proxyMaskedError, toEngramindError, unreachableError, type EngramindErrorBody } from \"../core/errors\"\nimport type { CategoryInput, ChannelQuery, MemoryQuery } from \"./client-types\"\nimport type { Category, ConsumerSettings, ContextView, EngramindAgent, EngramindApp, EngramindChannel, EngramindHint, EngramindMemory, EngramindSubscription, EngramindToken, ImportResult, IssuedToken, MemoryRevision, MemoryWrite, SearchResults, WhoAmI } from \"../core/types\"\n\n/**\n * Cliente HTTP do Engramind — SOMENTE servidor. Bearer com o token do app (ou de um agente),\n * `Idempotency-Key` em toda mutação, `If-None-Match` no contexto (`304` → `null`) e erros do envelope\n * `{ error: { code, message, details } }` traduzidos para `EngramindError` (pt-BR), inclusive o 5xx\n * mascarado por um proxy na frente do Engramind (Cloudflare).\n */\n\nexport type EngramindMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\"\nexport type Query = Record<string, string | number | boolean | null | undefined>\n\nexport interface EngramindClientOptions {\n /** URL base do Engramind (ex.: `https://engramind.exemplo.com`). */\n baseUrl: string\n /** Token do app (`kind: app`), de um agente ou, no gestor, admin. Nunca vai ao navegador. */\n token: string\n userAgent?: string\n timeoutMs?: number\n fetch?: typeof fetch\n /** Depuração: método, URL (com `focus` omitido), status e duração. Nunca recebe o corpo nem o token. */\n onDebug?: (entry: { method: string; url: string; status: number | null; durationMs: number; error?: string }) => void\n}\n\nexport interface CallOptions {\n query?: Query\n body?: unknown\n headers?: Record<string, string>\n /** Chave de idempotência fixa (importação); padrão: uma nova por mutação. */\n idempotencyKey?: string\n signal?: AbortSignal\n timeoutMs?: number\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000\nconst USER_TEXT_QUERY = [\"focus\"]\nconst enc = encodeURIComponent\n\nexport function buildUrl(baseUrl: string, path: string, query: Query = {}): string {\n const url = new URL(`${baseUrl.replace(/\\/+$/, \"\")}${path}`)\n for (const [key, value] of Object.entries(query)) if (value !== undefined && value !== null && value !== \"\") url.searchParams.set(key, String(value))\n return url.toString()\n}\n\n/** URL para a depuração: o texto do usuário (`focus`) vira o tamanho. */\nexport function debugUrl(url: string): string {\n const masked = new URL(url)\n for (const key of USER_TEXT_QUERY) {\n const value = masked.searchParams.get(key)\n if (value !== null) masked.searchParams.set(key, `[omitido: ${value.length} caracteres]`)\n }\n return masked.toString()\n}\n\nexport function createEngramindClient(options: EngramindClientOptions) {\n const doFetch = options.fetch ?? fetch\n const userAgent = options.userAgent ?? \"engramind-client\"\n\n async function call<T = unknown>(method: EngramindMethod, path: string, request: CallOptions = {}): Promise<T> {\n const headers: Record<string, string> = { ...request.headers, Authorization: `Bearer ${options.token}`, Accept: \"application/json\", \"User-Agent\": userAgent }\n if (method !== \"GET\") headers[\"Idempotency-Key\"] = request.idempotencyKey ?? `engramind-client-${randomUUID()}`\n const hasBody = request.body !== undefined && method !== \"GET\"\n if (hasBody) headers[\"Content-Type\"] = \"application/json\"\n const timeout = AbortSignal.timeout(request.timeoutMs ?? options.timeoutMs ?? DEFAULT_TIMEOUT_MS)\n const signal = request.signal ? AbortSignal.any([request.signal, timeout]) : timeout\n const url = buildUrl(options.baseUrl, path, request.query)\n const started = Date.now()\n let res: Response\n try {\n res = await doFetch(url, { method, headers, body: hasBody ? JSON.stringify(request.body) : undefined, cache: \"no-store\", signal })\n } catch (error) {\n const reason = error instanceof Error ? (error.name === \"TimeoutError\" || error.name === \"AbortError\" ? \"tempo limite\" : ((error as { cause?: { code?: string } }).cause?.code ?? error.message)) : String(error)\n options.onDebug?.({ method, url: debugUrl(url), status: null, durationMs: Date.now() - started, error: reason })\n throw unreachableError(reason)\n }\n options.onDebug?.({ method, url: debugUrl(url), status: res.status, durationMs: Date.now() - started })\n if (res.status === 304) return null as T\n const text = await res.text()\n let json: unknown = null\n if (text) {\n try {\n json = JSON.parse(text)\n } catch {\n json = null\n }\n }\n if (!res.ok) {\n const body = (json as { error?: EngramindErrorBody } | null)?.error ?? null\n throw toEngramindError(res.status, body && typeof body === \"object\" ? body : null, res.headers.get(\"retry-after\"), body ? null : proxyMaskedError(text, json))\n }\n return json as T\n }\n\n /** `GET /health/ready` (público): `true` quando o serviço recebe tráfego. */\n async function ready(signal?: AbortSignal): Promise<{ ready: boolean; detail: string }> {\n try {\n const res = await doFetch(`${options.baseUrl.replace(/\\/+$/, \"\")}/health/ready`, { headers: { Accept: \"application/json\", \"User-Agent\": userAgent }, cache: \"no-store\", signal: signal ?? AbortSignal.timeout(10_000) })\n const body = (await res.json().catch(() => null)) as { status?: string } | null\n return { ready: res.ok && body?.status === \"ready\", detail: body?.status ?? `HTTP ${res.status}` }\n } catch (error) {\n return { ready: false, detail: error instanceof Error ? error.message : String(error) }\n }\n }\n\n return {\n call,\n ready,\n whoami: () => call<WhoAmI>(\"GET\", \"/v1/whoami\"),\n consumerSettings: () => call<ConsumerSettings>(\"GET\", \"/v1/consumer-settings\"),\n categories: {\n list: () => call<{ categories: Category[] }>(\"GET\", \"/v1/categories\"),\n create: (input: CategoryInput) => call<Category>(\"POST\", \"/v1/categories\", { body: input }),\n },\n agents: {\n list: (query: Query = {}) => call<{ agents: EngramindAgent[]; next_cursor: string | null }>(\"GET\", \"/v1/agents\", { query }),\n get: (agent: string) => call<EngramindAgent>(\"GET\", `/v1/agents/${enc(agent)}`),\n create: (input: { app?: string; slug: string; name: string; description?: string | null }) => call<EngramindAgent & { created?: boolean }>(\"POST\", \"/v1/agents\", { body: input }),\n update: (agent: string, input: { name?: string; description?: string | null; disabled?: boolean }) => call<EngramindAgent>(\"PATCH\", `/v1/agents/${enc(agent)}`, { body: input }),\n delete: (agent: string) => call<null>(\"DELETE\", `/v1/agents/${enc(agent)}`),\n },\n subscriptions: {\n list: (query: Query = {}) => call<{ subscriptions: EngramindSubscription[]; next_cursor: string | null }>(\"GET\", \"/v1/subscriptions\", { query }),\n create: (input: { agent: string; target: string; mode: \"inject\" | \"search\" | \"manage\"; access?: string[] | null; priority?: number | null }) => call<EngramindSubscription & { created?: boolean }>(\"POST\", \"/v1/subscriptions\", { body: input }),\n update: (subscription: string, input: { mode?: \"inject\" | \"search\" | \"manage\"; access?: string[] | null; priority?: number | null }) => call<EngramindSubscription>(\"PATCH\", `/v1/subscriptions/${enc(subscription)}`, { body: input }),\n delete: (subscription: string) => call<null>(\"DELETE\", `/v1/subscriptions/${enc(subscription)}`),\n },\n channels: {\n list: (query: ChannelQuery = {}) => call<{ channels: EngramindChannel[]; next_cursor: string | null }>(\"GET\", \"/v1/channels\", { query: query as Query }),\n get: (channel: string) => call<EngramindChannel>(\"GET\", `/v1/channels/${enc(channel)}`),\n archive: (channel: string) => call<EngramindChannel>(\"POST\", `/v1/channels/${enc(channel)}/archive`, { body: {} }),\n restore: (channel: string) => call<EngramindChannel>(\"POST\", `/v1/channels/${enc(channel)}/restore`, { body: {} }),\n },\n memories: {\n list: (query: MemoryQuery = {}) => call<{ memories: EngramindMemory[]; next_cursor: string | null }>(\"GET\", \"/v1/memories\", { query: query as Query }),\n get: (memory: string) => call<EngramindMemory>(\"GET\", `/v1/memories/${enc(memory)}`),\n create: (input: { channel: string; text: string; category?: string; key?: string; tags?: string[] }) => call<MemoryWrite>(\"POST\", \"/v1/memories\", { body: input }),\n update: (memory: string, input: { text?: string; category?: string; expected_version?: number; tags?: string[] }) => call<MemoryWrite>(\"PATCH\", `/v1/memories/${enc(memory)}`, { body: input }),\n delete: (memory: string) => call<null>(\"DELETE\", `/v1/memories/${enc(memory)}`),\n archive: (memory: string) => call<EngramindMemory>(\"POST\", `/v1/memories/${enc(memory)}/archive`, { body: {} }),\n restore: (memory: string) => call<EngramindMemory>(\"POST\", `/v1/memories/${enc(memory)}/restore`, { body: {} }),\n pin: (memory: string) => call<EngramindMemory>(\"POST\", `/v1/memories/${enc(memory)}/pin`, { body: {} }),\n unpin: (memory: string) => call<EngramindMemory>(\"POST\", `/v1/memories/${enc(memory)}/unpin`, { body: {} }),\n move: (memory: string, channel: string) => call<EngramindMemory>(\"POST\", `/v1/memories/${enc(memory)}/move`, { body: { channel } }),\n history: (memory: string, query: Query = {}) => call<{ revisions: MemoryRevision[]; next_cursor: string | null }>(\"GET\", `/v1/memories/${enc(memory)}/history`, { query }),\n revert: (memory: string, version: number) => call<EngramindMemory>(\"POST\", `/v1/memories/${enc(memory)}/revert`, { body: { version } }),\n import: (input: { channel: string; memories: unknown[] }, idempotencyKey?: string) => call<ImportResult>(\"POST\", \"/v1/memories/import\", { body: input, idempotencyKey }),\n search: (input: { query: string; channels?: string[]; categories?: string[]; limit?: number; include_archived?: boolean; exclude_context?: boolean }) => call<SearchResults & { context_version?: string }>(\"POST\", \"/v1/memories/search\", { body: input }),\n },\n hints: (input: { text: string; channels?: string[]; limit?: number }) => call<{ hints: EngramindHint[] }>(\"POST\", \"/v1/hints\", { body: input }),\n context: (input: { channels?: string[]; format?: string; focus?: string; ifNoneMatch?: string | null }, request: Pick<CallOptions, \"signal\" | \"timeoutMs\"> = {}) =>\n call<ContextView | null>(\"GET\", \"/v1/context\", { query: { channels: input.channels?.join(\",\"), format: input.format, focus: input.focus }, headers: input.ifNoneMatch ? { \"If-None-Match\": `\"${input.ifNoneMatch}\"` } : undefined, ...request }),\n tokens: {\n list: (query: Query = {}) => call<{ tokens: EngramindToken[]; next_cursor: string | null }>(\"GET\", \"/v1/tokens\", { query }),\n issue: (input: { kind: \"agent\" | \"app\" | \"admin\"; agent?: string; app?: string; scopes: string[]; label?: string; expires_at?: string }) => call<IssuedToken>(\"POST\", \"/v1/tokens\", { body: input }),\n update: (token: string, input: { label?: string | null; scopes?: string[]; expires_at?: string | null }) => call<EngramindToken>(\"PATCH\", `/v1/tokens/${enc(token)}`, { body: input }),\n revoke: (token: string) => call<EngramindToken>(\"POST\", `/v1/tokens/${enc(token)}/revoke`, { body: {} }),\n rotate: (token: string) => call<IssuedToken>(\"POST\", `/v1/tokens/${enc(token)}/rotate`, { body: {} }),\n },\n apps: {\n get: (app: string) => call<EngramindApp>(\"GET\", `/v1/apps/${enc(app)}`),\n /** `update_app_webhook`: grava ou remove (com `null`) a URL e o segredo do webhook do app. */\n updateWebhook: (app: string, input: { url: string | null; secret?: string | null }) => call<EngramindApp>(\"PUT\", `/v1/apps/${enc(app)}/webhook`, { body: input }),\n },\n }\n}\n\nexport type EngramindClient = ReturnType<typeof createEngramindClient>\n","import type { EngramindMethod } from \"./client\"\n\n/**\n * Rotas do proxy da lib: o caminho do REST do Engramind sem o `/v1` (`channels/eng%2Fglobal/archive`).\n * `app` é o que o token de app pode fazer (matriz do guia `tokens-e-escopos`); `manager` acrescenta o que\n * o gestor da rede usa nas telas (apps, criar/editar/excluir canais, configurações por canal).\n */\nexport type ProxyMode = \"app\" | \"manager\"\n\nexport interface ProxyRoute {\n method: EngramindMethod\n /** Segmentos; `:nome` casa um segmento qualquer. */\n pattern: string\n modes: readonly ProxyMode[]\n /** Operação do Engramind (auditoria do app e testes). */\n operation: string\n}\n\nconst BOTH = [\"app\", \"manager\"] as const\nconst MANAGER = [\"manager\"] as const\nconst r = (method: EngramindMethod, pattern: string, operation: string, modes: readonly ProxyMode[] = BOTH): ProxyRoute => ({ method, pattern, operation, modes })\n\nexport const PROXY_ROUTES: readonly ProxyRoute[] = [\n r(\"GET\", \"whoami\", \"whoami\"),\n r(\"GET\", \"consumer-settings\", \"get_consumer_settings\"),\n r(\"GET\", \"categories\", \"list_categories\"),\n r(\"POST\", \"categories\", \"create_category\"),\n r(\"GET\", \"agents\", \"list_agents\"),\n r(\"POST\", \"agents\", \"create_agent\"),\n r(\"GET\", \"agents/:agent\", \"get_agent\"),\n r(\"PATCH\", \"agents/:agent\", \"update_agent\"),\n r(\"DELETE\", \"agents/:agent\", \"delete_agent\"),\n r(\"GET\", \"subscriptions\", \"list_subscriptions\"),\n r(\"POST\", \"subscriptions\", \"subscribe\"),\n r(\"PATCH\", \"subscriptions/:subscription\", \"update_subscription\"),\n r(\"DELETE\", \"subscriptions/:subscription\", \"unsubscribe\"),\n r(\"GET\", \"channels\", \"list_channels\"),\n r(\"GET\", \"channels/:channel\", \"get_channel\"),\n r(\"GET\", \"channels/:channel/stats\", \"channel_stats\"),\n r(\"POST\", \"channels/:channel/archive\", \"archive_channel\"),\n r(\"POST\", \"channels/:channel/restore\", \"restore_channel\"),\n r(\"GET\", \"memories\", \"list_memories\"),\n r(\"POST\", \"memories\", \"create_memory\"),\n r(\"POST\", \"memories/import\", \"import_memories\"),\n r(\"POST\", \"memories/search\", \"search_memories\"),\n r(\"POST\", \"hints\", \"get_hints\"),\n r(\"GET\", \"memories/:memory\", \"get_memory\"),\n r(\"PATCH\", \"memories/:memory\", \"update_memory\"),\n r(\"DELETE\", \"memories/:memory\", \"delete_memory\"),\n r(\"GET\", \"memories/:memory/history\", \"memory_history\"),\n r(\"POST\", \"memories/:memory/archive\", \"archive_memory\"),\n r(\"POST\", \"memories/:memory/restore\", \"restore_memory\"),\n r(\"POST\", \"memories/:memory/pin\", \"pin_memory\"),\n r(\"POST\", \"memories/:memory/unpin\", \"unpin_memory\"),\n r(\"POST\", \"memories/:memory/move\", \"move_memory\"),\n r(\"POST\", \"memories/:memory/revert\", \"revert_memory\"),\n r(\"POST\", \"memories/:memory/verify\", \"mark_verified\"),\n r(\"GET\", \"tokens\", \"list_tokens\"),\n r(\"POST\", \"tokens\", \"issue_token\"),\n r(\"PATCH\", \"tokens/:token\", \"update_token\"),\n r(\"POST\", \"tokens/:token/revoke\", \"revoke_token\"),\n r(\"POST\", \"tokens/:token/rotate\", \"rotate_token\"),\n r(\"GET\", \"apps/:app\", \"get_app\"),\n r(\"PUT\", \"apps/:app/webhook\", \"update_app_webhook\"),\n r(\"GET\", \"context\", \"get_context\"),\n // Só o gestor (token admin).\n r(\"GET\", \"apps\", \"list_apps\", MANAGER),\n r(\"POST\", \"channels\", \"create_channel\", MANAGER),\n r(\"PATCH\", \"channels/:channel\", \"update_channel\", MANAGER),\n r(\"DELETE\", \"channels/:channel\", \"delete_channel\", MANAGER),\n r(\"GET\", \"channels/:channel/settings\", \"get_channel_settings\", MANAGER),\n r(\"PUT\", \"channels/:channel/settings\", \"update_channel_settings\", MANAGER),\n r(\"GET\", \"settings\", \"get_settings\", MANAGER),\n]\n\nexport interface RouteMatch {\n route: ProxyRoute\n params: Record<string, string>\n /** Caminho do Engramind (`/v1/...`) com os segmentos recodificados. */\n path: string\n}\n\n/** Casa método + segmentos (já decodificados) com uma rota permitida no modo. */\nexport function matchProxyRoute(method: string, segments: readonly string[], mode: ProxyMode): RouteMatch | null {\n const upper = method.toUpperCase()\n for (const route of PROXY_ROUTES) {\n if (route.method !== upper || !route.modes.includes(mode)) continue\n const parts = route.pattern.split(\"/\")\n if (parts.length !== segments.length) continue\n const params: Record<string, string> = {}\n let ok = true\n for (let index = 0; index < parts.length; index += 1) {\n const part = parts[index]!\n const segment = segments[index]!\n if (part.startsWith(\":\")) {\n if (!segment) ok = false\n else params[part.slice(1)] = segment\n } else if (part !== segment) ok = false\n if (!ok) break\n }\n if (ok) return { route, params, path: `/v1/${segments.map((segment) => encodeURIComponent(segment)).join(\"/\")}` }\n }\n return null\n}\n","import { EngramindError, notConfiguredError } from \"../core/errors\"\nimport type { EngramindClient, EngramindMethod } from \"./client\"\nimport { matchProxyRoute, type ProxyMode, type ProxyRoute } from \"./routes\"\n\n/**\n * Proxy HTTP pronto para os componentes da lib: o app monta numa rota (ex.: catch-all do Next.js\n * `app/api/engramind/[...path]/route.ts`) **atrás do próprio login** — a lib não verifica usuário. Repassa\n * só as rotas do modo (`app`: as do token de app; `manager`: também as do gestor), com o token do\n * servidor; a resposta é `{ success: true, data }` ou `{ success: false, error, code, details }` e nunca\n * traz o token configurado. No modo app, `issue_token` só emite `kind: agent`.\n */\n\nexport interface ProxyMutation {\n operation: string\n method: EngramindMethod\n params: Record<string, string>\n /** Corpo enviado (o app decide o que audita; segredos de token emitido não passam por aqui). */\n body: unknown\n result: unknown\n}\n\nexport interface EngramindProxyOptions {\n /** Cliente do Engramind (ou uma função que o monta a cada requisição, ex.: lendo a configuração). */\n client: EngramindClient | (() => EngramindClient | null | Promise<EngramindClient | null>)\n mode?: ProxyMode\n /** Prefixo da rota no app (ex.: `/api/engramind`); o resto do caminho é a rota do Engramind. */\n basePath: string\n /** Depois de cada mutação bem-sucedida (auditoria do app). */\n onMutation?: (mutation: ProxyMutation) => void | Promise<void>\n /**\n * Token de agente emitido ou rotacionado: o segredo aparece só nesta resposta. O app pode gravá-lo onde\n * quiser (ex.: cofre do servidor); o diálogo da lib também o mostra uma única vez.\n */\n onTokenIssued?: (secret: string, token: { id: string; agent: string | null; app: string | null; kind: string; operation: \"issue_token\" | \"rotate_token\" }) => void | Promise<void>\n onError?: (error: unknown, route: ProxyRoute | null) => void\n}\n\nconst json = (status: number, body: unknown, headers: Record<string, string> = {}) => new Response(JSON.stringify(body), { status, headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", ...headers } })\n\nexport function errorResponse(error: unknown): Response {\n if (error instanceof EngramindError) {\n const headers: Record<string, string> = error.retryAfter !== null ? { \"Retry-After\": String(error.retryAfter) } : {}\n return json(error.status || 500, { success: false, error: error.message, code: error.code, ...(error.details !== undefined ? { details: error.details } : {}) }, headers)\n }\n return json(500, { success: false, error: \"Falha inesperada ao falar com o Engramind.\", code: \"ENGRAMIND_PROXY_ERROR\" })\n}\n\n/** Segmentos depois do `basePath`, decodificados (`channels/eng%2Fglobal` → `[\"channels\", \"eng/global\"]`). */\nexport function proxySegments(pathname: string, basePath: string): string[] | null {\n const base = basePath.replace(/\\/+$/, \"\")\n if (!pathname.startsWith(`${base}/`) && pathname !== base) return null\n const rest = pathname.slice(base.length).replace(/^\\/+/, \"\")\n if (!rest) return []\n try {\n return rest.split(\"/\").map((segment) => decodeURIComponent(segment))\n } catch {\n return null\n }\n}\n\nasync function readBody(request: Request): Promise<unknown> {\n const text = await request.text()\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n throw new EngramindError(400, \"Corpo da requisição inválido (JSON).\", \"ENGRAMIND_VALIDATION_ERROR\")\n }\n}\n\nexport function createEngramindProxy(options: EngramindProxyOptions) {\n const mode: ProxyMode = options.mode ?? \"app\"\n return async function handle(request: Request): Promise<Response> {\n const url = new URL(request.url)\n const segments = proxySegments(url.pathname, options.basePath)\n const match = segments ? matchProxyRoute(request.method, segments, mode) : null\n if (!match) return json(404, { success: false, error: \"Operação do Engramind não disponível neste app.\", code: \"ENGRAMIND_ROUTE_NOT_ALLOWED\" })\n try {\n const client = typeof options.client === \"function\" ? await options.client() : options.client\n if (!client) throw notConfiguredError()\n const method = match.route.method\n let body = method === \"GET\" || method === \"DELETE\" ? undefined : await readBody(request)\n if (match.route.operation === \"issue_token\" && mode === \"app\") {\n const input = (body && typeof body === \"object\" ? body : {}) as Record<string, unknown>\n if (input.kind !== undefined && input.kind !== \"agent\") throw new EngramindError(403, \"O app só emite tokens de agente.\", \"ENGRAMIND_SCOPE_REQUIRED\")\n body = { ...input, kind: \"agent\" }\n }\n const query = Object.fromEntries(url.searchParams.entries())\n const result = await client.call(method, match.path, { query, body: body ?? (method === \"GET\" || method === \"DELETE\" ? undefined : {}), headers: method === \"GET\" && request.headers.get(\"if-none-match\") ? { \"If-None-Match\": request.headers.get(\"if-none-match\")! } : undefined })\n if (method !== \"GET\") {\n const issued = (match.route.operation === \"issue_token\" || match.route.operation === \"rotate_token\") && result && typeof result === \"object\" ? (result as { secret?: unknown; token?: { id: string; kind: string; app?: { slug: string } | null; agent?: { slug: string } | null } }) : null\n if (issued && typeof issued.secret === \"string\" && issued.token) {\n const token = issued.token\n await options.onTokenIssued?.(issued.secret, { id: token.id, kind: token.kind, app: token.app?.slug ?? null, agent: token.agent && token.app ? `${token.app.slug}/${token.agent.slug}` : (token.agent?.slug ?? null), operation: match.route.operation as \"issue_token\" | \"rotate_token\" })\n }\n try {\n await options.onMutation?.({ operation: match.route.operation, method, params: match.params, body: redactBody(match.route.operation, body), result: issued ? { ...issued, secret: undefined } : result })\n } catch (error) {\n options.onError?.(error, match.route)\n }\n }\n return json(200, { success: true, data: result })\n } catch (error) {\n options.onError?.(error, match.route)\n return errorResponse(error)\n }\n }\n}\n\n/** O segredo do webhook nunca vai para a auditoria do app. */\nfunction redactBody(operation: string, body: unknown): unknown {\n if (operation !== \"update_app_webhook\" || !body || typeof body !== \"object\") return body\n const copy = { ...(body as Record<string, unknown>) }\n if (copy.secret) copy.secret = \"[segredo omitido]\"\n return copy\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\"\nimport { toLiveEvent, type WebhookEvent } from \"../core/events\"\nimport type { EventBus } from \"./bus\"\nimport type { ContextCache } from \"./context\"\n\n/**\n * Webhook do Engramind (`POST <webhook_url>`): valida `Engramind-Signature` (`t=<unix>,v1=<hex>`,\n * HMAC-SHA256 do segredo sobre `t + \".\" + corpo bruto`, janela de 300 s, comparação em tempo constante),\n * descarta o `id` repetido (as retentativas repetem o corpo), marca o contexto do canal como sujo (menos\n * `memory.consumed`) e republica no SSE só ids, slugs e tipos. Assinatura inválida: `401` sem corpo.\n */\n\nexport const WEBHOOK_TOLERANCE_SECONDS = 300\n\nexport function parseSignatureHeader(header: string | null): { t: number; v1: string[] } | null {\n if (!header) return null\n let t: number | null = null\n const v1: string[] = []\n for (const part of header.split(\",\")) {\n const [key, ...rest] = part.trim().split(\"=\")\n const value = rest.join(\"=\")\n if (key === \"t\") t = Number(value)\n if (key === \"v1\" && value) v1.push(value)\n }\n if (t === null || !Number.isFinite(t) || !v1.length) return null\n return { t, v1 }\n}\n\nexport function signWebhook(secret: string, timestamp: number, body: string): string {\n return createHmac(\"sha256\", secret).update(`${timestamp}.${body}`).digest(\"hex\")\n}\n\nexport function verifyWebhookSignature(header: string | null, body: string, secret: string, options: { now?: number; toleranceSeconds?: number } = {}): boolean {\n const parsed = parseSignatureHeader(header)\n if (!parsed || !secret) return false\n const now = Math.floor((options.now ?? Date.now()) / 1000)\n if (Math.abs(now - parsed.t) > (options.toleranceSeconds ?? WEBHOOK_TOLERANCE_SECONDS)) return false\n const expected = Buffer.from(signWebhook(secret, parsed.t, body), \"hex\")\n return parsed.v1.some((candidate) => {\n const given = Buffer.from(candidate, \"hex\")\n return given.length === expected.length && timingSafeEqual(given, expected)\n })\n}\n\n/** Registro dos `id` já processados (retentativas do Engramind). */\nexport interface DedupeStore {\n /** `true` se o `id` já foi visto (e o marca como visto). */\n seen(id: string): boolean | Promise<boolean>\n}\n\n/** Padrão em memória: lembra os últimos `max` ids por `ttlMs` (24 h). */\nexport function createMemoryDedupe(options: { ttlMs?: number; max?: number } = {}): DedupeStore {\n const ttl = options.ttlMs ?? 24 * 60 * 60_000\n const max = options.max ?? 10_000\n const ids = new Map<string, number>()\n return {\n seen(id) {\n const now = Date.now()\n for (const [key, at] of ids) {\n if (now - at <= ttl && ids.size <= max) break\n ids.delete(key)\n }\n if (ids.has(id)) return true\n ids.set(id, now)\n return false\n },\n }\n}\n\nexport interface WebhookHandlerOptions {\n /** Segredo do webhook do app (o mesmo gravado no Engramind por `update_app_webhook`). */\n secret: string | (() => string | null | Promise<string | null>)\n bus?: EventBus\n dedupe?: DedupeStore\n /** Contexto em cache dos agentes do app: marca os canais sujos. */\n context?: ContextCache\n /** Depois de validar (ex.: auditoria, outro tempo real do app). Uma falha não muda a resposta. */\n onEvent?: (event: WebhookEvent) => void | Promise<void>\n onError?: (error: unknown) => void\n toleranceSeconds?: number\n}\n\nexport function createWebhookHandler(options: WebhookHandlerOptions) {\n const dedupe = options.dedupe ?? createMemoryDedupe()\n return async function handle(request: Request): Promise<Response> {\n const body = await request.text()\n const secret = typeof options.secret === \"function\" ? await options.secret() : options.secret\n if (!secret || !verifyWebhookSignature(request.headers.get(\"engramind-signature\"), body, secret, { toleranceSeconds: options.toleranceSeconds })) return new Response(null, { status: 401 })\n let event: WebhookEvent\n try {\n event = JSON.parse(body) as WebhookEvent\n } catch {\n return new Response(null, { status: 400 })\n }\n if (!event || typeof event.id !== \"string\" || typeof event.type !== \"string\") return new Response(null, { status: 400 })\n if (await dedupe.seen(event.id)) return Response.json({ ok: true, duplicate: true })\n try {\n options.context?.handleEvent(event)\n await options.bus?.publish(toLiveEvent(event))\n await options.onEvent?.(event)\n } catch (error) {\n options.onError?.(error)\n }\n return Response.json({ ok: true })\n }\n}\n","import type { LiveEvent } from \"../core/events\"\n\n/**\n * Barramento dos eventos ao vivo (webhook → SSE). O padrão vive **no processo**: só os navegadores\n * ligados à instância que recebeu o webhook são avisados. Com várias instâncias, passe um `Broadcaster`\n * (ex.: Redis pub/sub, Postgres `NOTIFY`, Pusher) que entregue o evento a todas.\n */\nexport type LiveListener = (event: LiveEvent) => void\n\nexport interface Broadcaster {\n publish(event: LiveEvent): void | Promise<void>\n subscribe(listener: LiveListener): () => void\n}\n\nexport interface EventBus {\n publish(event: LiveEvent): Promise<void>\n subscribe(listener: LiveListener): () => void\n /** Navegadores ligados nesta instância. */\n readonly size: number\n}\n\nexport function createEventBus(options: { broadcaster?: Broadcaster; onError?: (error: unknown) => void } = {}): EventBus {\n const listeners = new Set<LiveListener>()\n const deliver = (event: LiveEvent) => {\n for (const listener of listeners) {\n try {\n listener(event)\n } catch (error) {\n options.onError?.(error)\n }\n }\n }\n const remote = options.broadcaster?.subscribe(deliver)\n void remote\n return {\n async publish(event) {\n if (options.broadcaster) await options.broadcaster.publish(event)\n else deliver(event)\n },\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n get size() {\n return listeners.size\n },\n }\n}\n","import type { LiveEvent } from \"../core/events\"\nimport type { EventBus } from \"./bus\"\n\n/**\n * Endpoint SSE dos componentes (`EventSource`): repassa os eventos do barramento como\n * `event: engramind` com só ids, slugs e tipos, e um comentário de vida a cada `heartbeatMs`. O app monta\n * **atrás do próprio login**; ao cair, o navegador reconecta e relê as telas.\n */\nexport interface SseOptions {\n bus: EventBus\n heartbeatMs?: number\n /** Filtra o que cada navegador recebe (ex.: por app ou canal). */\n filter?: (event: LiveEvent, request: Request) => boolean\n}\n\nconst encoder = new TextEncoder()\nexport const formatSse = (event: string, data: unknown) => `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`\n\nexport function createSseHandler(options: SseOptions) {\n const heartbeatMs = options.heartbeatMs ?? 25_000\n return function handle(request: Request): Response {\n let cleanup: (() => void) | null = null\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n const send = (chunk: string) => {\n try {\n controller.enqueue(encoder.encode(chunk))\n } catch {\n cleanup?.()\n }\n }\n send(`retry: 3000\\n${formatSse(\"ready\", { at: new Date().toISOString() })}`)\n const unsubscribe = options.bus.subscribe((event) => {\n if (options.filter && !options.filter(event, request)) return\n send(formatSse(\"engramind\", event))\n })\n const timer = setInterval(() => send(\": ping\\n\\n\"), heartbeatMs)\n cleanup = () => {\n if (!cleanup) return\n cleanup = null\n clearInterval(timer)\n unsubscribe()\n try {\n controller.close()\n } catch {\n // Já fechado.\n }\n }\n request.signal.addEventListener(\"abort\", () => cleanup?.(), { once: true })\n },\n cancel() {\n cleanup?.()\n },\n })\n return new Response(stream, { headers: { \"Content-Type\": \"text/event-stream; charset=utf-8\", \"Cache-Control\": \"no-cache, no-transform\", Connection: \"keep-alive\", \"X-Accel-Buffering\": \"no\" } })\n }\n}\n","import { isConsumedEvent, type WebhookEvent } from \"../core/events\"\nimport type { ContextView } from \"../core/types\"\nimport type { EngramindClient } from \"./client\"\n\n/**\n * Contexto das memórias por agente (`get_context`) com cache por `version` (`If-None-Match` → `304`).\n * Os eventos `memory.*`, `channel.updated` e `subscription.updated` marcam os canais como sujos;\n * **`memory.consumed` não** (só avisa que memórias foram entregues). Se o Engramind cair, devolve o\n * último bloco (`stale: true`). Guarde o bloco na parte estável do prompt.\n */\n\nexport interface ContextEntry {\n content: string\n version: string\n counts: ContextView[\"counts\"]\n format: string\n fetchedAt: number\n dirty: boolean\n}\n\n/** Onde o bloco fica (padrão: memória do processo). Troque por Redis/banco para várias instâncias. */\nexport interface ContextStore {\n get(key: string): ContextEntry | null | Promise<ContextEntry | null>\n set(key: string, entry: ContextEntry): void | Promise<void>\n /** Chaves guardadas (para marcar os canais sujos). */\n keys(): string[] | Promise<string[]>\n}\n\nexport function createMemoryContextStore(): ContextStore {\n const map = new Map<string, ContextEntry>()\n return { get: (key) => map.get(key) ?? null, set: (key, entry) => void map.set(key, entry), keys: () => [...map.keys()] }\n}\n\nexport interface ContextResult {\n block: string\n version: string | null\n /** `live` (lido agora ou `304`), `cache` (Engramind indisponível: último bloco) ou `none`. */\n source: \"live\" | \"cache\" | \"none\"\n stale: boolean\n counts: ContextView[\"counts\"] | null\n error: string | null\n}\n\nexport interface ContextCacheOptions {\n /** Cliente com o token **do agente** (o contexto é o do agente). */\n client: (agent: string) => EngramindClient | null | Promise<EngramindClient | null>\n store?: ContextStore\n format?: string\n /**\n * `always` (padrão): toda chamada revalida com `If-None-Match` (o `304` custa poucos bytes).\n * `on-dirty`: só chama o Engramind quando um evento sujou um canal do bloco ou depois de `maxAgeMs`.\n */\n revalidate?: \"always\" | \"on-dirty\"\n maxAgeMs?: number\n onError?: (error: unknown, agent: string) => void\n}\n\nconst SEP = \"\\u0000\"\nexport const contextKey = (agent: string, channels: readonly string[], format: string) => [agent, [...channels].sort().join(\",\"), format].join(SEP)\nconst channelsOfKey = (key: string) => (key.split(SEP)[1] ?? \"\").split(\",\").filter(Boolean)\n\n/** O evento muda o contexto do canal? (`memory.consumed` não muda.) */\nexport function invalidatesContext(event: Pick<WebhookEvent, \"type\">): boolean {\n if (isConsumedEvent(event.type)) return false\n return event.type.startsWith(\"memory.\") || event.type === \"channel.updated\" || event.type === \"subscription.updated\" || event.type === \"settings.updated\"\n}\n\nexport function createContextCache(options: ContextCacheOptions) {\n const store = options.store ?? createMemoryContextStore()\n const format = options.format ?? \"tagged\"\n const maxAge = options.maxAgeMs ?? 5 * 60_000\n /** Canais sujos sem bloco guardado por inteiro (prefixos, `settings.updated` global): suja tudo. */\n let allDirty = false\n\n async function markChannels(channels: readonly (string | null)[]) {\n const wanted = channels.filter((channel): channel is string => Boolean(channel))\n for (const key of await store.keys()) {\n const keyChannels = channelsOfKey(key)\n const hit = !wanted.length || !keyChannels.length || wanted.some((channel) => keyChannels.includes(channel))\n if (!hit) continue\n const entry = await store.get(key)\n if (entry && !entry.dirty) await store.set(key, { ...entry, dirty: true })\n }\n }\n\n return {\n /** Webhook recebido: suja o contexto dos canais afetados (nunca por `memory.consumed`). */\n handleEvent(event: Pick<WebhookEvent, \"type\" | \"channel\">) {\n if (!invalidatesContext(event)) return\n if (!event.channel) allDirty = true\n void markChannels(event.channel ? [event.channel] : [])\n },\n async getContext(agent: string, input: { channels?: readonly string[]; focus?: string } = {}): Promise<ContextResult> {\n const channels = input.channels ?? []\n const key = contextKey(agent, channels, format)\n const cached = await store.get(key)\n const usable = cached && !input.focus\n if (usable && options.revalidate === \"on-dirty\" && !cached.dirty && !allDirty && Date.now() - cached.fetchedAt < maxAge) {\n return { block: cached.content, version: cached.version, source: \"live\", stale: false, counts: cached.counts, error: null }\n }\n try {\n const client = await options.client(agent)\n if (!client) throw new Error(`token do agente ${agent} indisponível`)\n const view = await client.context({ channels: channels.length ? [...channels] : undefined, format, focus: input.focus, ifNoneMatch: usable ? cached.version : null })\n allDirty = false\n if (view === null && cached) {\n await store.set(key, { ...cached, fetchedAt: Date.now(), dirty: false })\n return { block: cached.content, version: cached.version, source: \"live\", stale: false, counts: cached.counts, error: null }\n }\n if (!view) throw new Error(\"o Engramind respondeu sem o contexto\")\n const entry: ContextEntry = { content: typeof view.content === \"string\" ? view.content : \"\", version: view.version ?? \"\", counts: view.counts, format, fetchedAt: Date.now(), dirty: false }\n // Bloco com foco tem outra `version`: não substitui o cache do bloco estável.\n if (!input.focus) await store.set(key, entry)\n return { block: entry.content, version: entry.version, source: \"live\", stale: false, counts: entry.counts, error: null }\n } catch (error) {\n options.onError?.(error, agent)\n const reason = error instanceof Error ? error.message : String(error)\n if (cached) return { block: cached.content, version: cached.version, source: \"cache\", stale: true, counts: cached.counts, error: reason }\n return { block: \"\", version: null, source: \"none\", stale: true, counts: null, error: reason }\n }\n },\n /** Bloco guardado (sem chamar o Engramind). */\n peek: async (agent: string, channels: readonly string[] = []) => store.get(contextKey(agent, channels, format)),\n }\n}\n\nexport type ContextCache = ReturnType<typeof createContextCache>\n","/**\n * Configuração de MCP pronta para registrar o Engramind num agente: `POST /mcp` com o token **do agente**\n * e o cabeçalho `Engramind-Channels` (canais do projeto). `tools/list` já mostra só o que o token pode\n * usar; repasse ao agente só as ferramentas que a sua política permite.\n */\nexport interface EngramindMcpConfig {\n type: \"http\"\n url: string\n headers: Record<string, string>\n}\n\nexport function engramindMcpConfig(input: { baseUrl: string; agentToken: string; channels?: readonly string[] }): EngramindMcpConfig {\n const headers: Record<string, string> = { Authorization: `Bearer ${input.agentToken}` }\n if (input.channels?.length) headers[\"Engramind-Channels\"] = input.channels.join(\",\")\n return { type: \"http\", url: `${input.baseUrl.replace(/\\/+$/, \"\")}/mcp`, headers }\n}\n\n/** Ferramentas de leitura e de escrita do MCP do Engramind (para filtrar o `tools/list`). */\nexport const MCP_READ_TOOLS = [\"get_context\", \"search_memories\", \"get_memory\", \"get_hints\", \"list_memories\", \"list_categories\"] as const\nexport const MCP_WRITE_TOOLS = [\"create_memory\", \"update_memory\"] as const\n"],"mappings":"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","names":["json"]}
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Formatos devolvidos pela API do Engramind (contrato 0.12.0, `docs/openapi.json` do repositório
3
+ * `nandobfer/engramind`). Só os campos que a lib usa; o resto passa adiante sem tipagem.
4
+ */
5
+ interface Ref {
6
+ id: string;
7
+ slug: string;
8
+ }
9
+ type EngramindTokenKind = "admin" | "agent" | "app";
10
+ interface WhoAmI {
11
+ token_id: string;
12
+ kind: EngramindTokenKind;
13
+ scopes: string[];
14
+ app: Ref;
15
+ agent: Ref | null;
16
+ operations: string[];
17
+ }
18
+ interface EngramindApp {
19
+ id: string;
20
+ slug: string;
21
+ name: string;
22
+ description: string | null;
23
+ created_at: string;
24
+ disabled_at: string | null;
25
+ webhook: {
26
+ url: string;
27
+ secret: {
28
+ configured: boolean;
29
+ updated_at: string | null;
30
+ };
31
+ } | null;
32
+ }
33
+ interface EngramindAgent {
34
+ id: string;
35
+ slug: string;
36
+ qualified_slug: string;
37
+ name: string;
38
+ description: string | null;
39
+ app: Ref;
40
+ created_at: string;
41
+ disabled_at: string | null;
42
+ }
43
+ interface EngramindToken {
44
+ id: string;
45
+ prefix: string;
46
+ kind: EngramindTokenKind;
47
+ scopes: string[];
48
+ app: Ref;
49
+ agent: Ref | null;
50
+ label: string | null;
51
+ status: "active" | "expired" | "revoked";
52
+ created_at: string;
53
+ expires_at: string | null;
54
+ revoked_at: string | null;
55
+ last_used_at: string | null;
56
+ }
57
+ interface IssuedToken {
58
+ token: EngramindToken;
59
+ secret?: string | null;
60
+ }
61
+ interface SettingMeta {
62
+ key: string;
63
+ value: unknown;
64
+ default: unknown;
65
+ type: string;
66
+ description: string;
67
+ channel_override: boolean;
68
+ choices?: string[] | null;
69
+ min?: number | null;
70
+ max?: number | null;
71
+ secret?: boolean;
72
+ configured?: boolean | null;
73
+ updated_at?: string | null;
74
+ updated_by_app?: string | null;
75
+ }
76
+ interface ChannelSettingMeta {
77
+ key: string;
78
+ type: string;
79
+ description: string;
80
+ value: unknown;
81
+ global: unknown;
82
+ override: unknown;
83
+ min?: number | null;
84
+ max?: number | null;
85
+ }
86
+ interface ChannelSettingsView {
87
+ channel: string;
88
+ channel_id: string;
89
+ settings: ChannelSettingMeta[];
90
+ }
91
+ interface Category {
92
+ key: string;
93
+ label: string;
94
+ priority: number;
95
+ }
96
+ interface EngramindChannel {
97
+ id: string;
98
+ slug: string;
99
+ name: string;
100
+ description: string | null;
101
+ priority: number;
102
+ version: number;
103
+ created_at: string;
104
+ archived_at: string | null;
105
+ }
106
+ interface ChannelStats {
107
+ channel: string;
108
+ channel_id: string;
109
+ memories: {
110
+ active: number;
111
+ archived: number;
112
+ permanent: number;
113
+ total: number;
114
+ };
115
+ by_category: Record<string, number> | {
116
+ category: string;
117
+ count: number;
118
+ }[];
119
+ pending: {
120
+ embedding: number;
121
+ title: number;
122
+ };
123
+ retrievals: number;
124
+ last_memory_at: string | null;
125
+ version: number;
126
+ }
127
+ interface EngramindSubscription {
128
+ id: string;
129
+ agent: Ref & {
130
+ qualified_slug?: string;
131
+ };
132
+ target: string;
133
+ is_prefix: boolean;
134
+ mode: "inject" | "search" | "manage";
135
+ access: string[] | null;
136
+ priority: number | null;
137
+ reaches: string[];
138
+ created_at: string;
139
+ }
140
+ interface Author {
141
+ app: string | null;
142
+ app_id: string | null;
143
+ agent: string | null;
144
+ agent_id: string | null;
145
+ }
146
+ interface EngramindMemory {
147
+ id: string;
148
+ channel: string;
149
+ channel_id: string;
150
+ title: string | null;
151
+ title_state: "pending" | "ready" | "fallback";
152
+ text: string;
153
+ category: string;
154
+ status: "active" | "permanent" | "archived";
155
+ version: number;
156
+ author: Author;
157
+ origin?: {
158
+ source: string;
159
+ label?: string | null;
160
+ } | null;
161
+ embedding_state: "pending" | "ready" | "failed" | "off";
162
+ retrieval_count: number;
163
+ /** Entradas no contexto (gravado em lote; pode atrasar alguns segundos). */
164
+ injected_count?: number;
165
+ last_retrieved_at?: string | null;
166
+ verified_at?: string | null;
167
+ /** Sem uso há mais de `consolidation.stale_after_days` (candidata a esquecimento). */
168
+ stale_candidate?: boolean;
169
+ importance?: number | null;
170
+ created_at: string;
171
+ updated_at: string;
172
+ archived_at: string | null;
173
+ }
174
+ interface MemoryRevision {
175
+ version: number;
176
+ text: string;
177
+ title: string | null;
178
+ category: string;
179
+ status: string;
180
+ reason: string | null;
181
+ author: Author;
182
+ created_at: string;
183
+ }
184
+ interface SearchResults {
185
+ mode: "hybrid" | "text";
186
+ results: {
187
+ memory: EngramindMemory;
188
+ rank: number;
189
+ score: number;
190
+ }[];
191
+ }
192
+ /** Resposta de `create_memory`/`update_memory`. */
193
+ interface MemoryWrite {
194
+ memory: EngramindMemory;
195
+ duplicate: boolean;
196
+ similar: {
197
+ id: string;
198
+ text?: string;
199
+ title?: string | null;
200
+ score?: number;
201
+ }[];
202
+ }
203
+ /** `GET /v1/context` (o `content` é texto em `markdown`/`tagged`). */
204
+ interface ContextView {
205
+ content: string;
206
+ version: string;
207
+ counts: {
208
+ full: number;
209
+ titles: number;
210
+ overflow: number;
211
+ };
212
+ rendered_tokens: number;
213
+ format: string;
214
+ /** Só com `focus` (0.11.0): como o foco foi aplicado. `text`/`none` = degradado (não entra em cache). */
215
+ focus_mode?: "hybrid" | "text" | "none" | null;
216
+ }
217
+ /** `POST /v1/hints` (0.11.0): só título, canal e nota; nunca o texto. */
218
+ interface EngramindHint {
219
+ id: string;
220
+ title: string;
221
+ channel: string;
222
+ score: number;
223
+ method: "vector" | "text";
224
+ duplicate: boolean;
225
+ }
226
+ /** `POST /v1/consolidation/run`: o plano (`dry_run`) ou os jobs enfileirados. */
227
+ interface ConsolidationPlan {
228
+ channel: string;
229
+ skipped: Record<string, string>;
230
+ duplicates: {
231
+ keep: string;
232
+ archive: string;
233
+ similarity?: number;
234
+ }[];
235
+ classify: string[];
236
+ stale: {
237
+ flag: string[];
238
+ clear: string[];
239
+ archive: string[];
240
+ };
241
+ }
242
+ interface ConsolidationRun {
243
+ dry_run: boolean;
244
+ channels: ConsolidationPlan[];
245
+ jobs: {
246
+ channel: string;
247
+ job_id: number;
248
+ already_queued: boolean;
249
+ }[];
250
+ }
251
+ interface ImportResult {
252
+ created: {
253
+ index: number;
254
+ id: string;
255
+ }[];
256
+ duplicates: {
257
+ index: number;
258
+ existing_id: string;
259
+ }[];
260
+ }
261
+ interface EngramindJob {
262
+ id: number | string;
263
+ kind: string;
264
+ state: string;
265
+ attempts: number;
266
+ outcome: string | null;
267
+ last_error: string | null;
268
+ payload: unknown;
269
+ created_at: string;
270
+ run_after: string | null;
271
+ finished_at: string | null;
272
+ }
273
+ interface LlmCall {
274
+ id: number | string;
275
+ at: string;
276
+ purpose: string;
277
+ provider: string;
278
+ model: string;
279
+ outcome: string;
280
+ input_tokens: number | null;
281
+ output_tokens: number | null;
282
+ cost_usd: number | null;
283
+ duration_ms: number | null;
284
+ error: string | null;
285
+ }
286
+ interface UsageReport {
287
+ group_by: string;
288
+ since: string;
289
+ until: string;
290
+ total: {
291
+ requests: number;
292
+ memories: number;
293
+ rendered_tokens: number;
294
+ };
295
+ rows: {
296
+ key: string;
297
+ label: string | null;
298
+ requests: number;
299
+ memories: number;
300
+ rendered_tokens: number;
301
+ }[];
302
+ }
303
+ interface AuditEvent {
304
+ id: number | string;
305
+ at: string;
306
+ action: string;
307
+ entity: string;
308
+ entity_id: string | null;
309
+ app_id: string | null;
310
+ agent_id: string | null;
311
+ token_id: string | null;
312
+ before: unknown;
313
+ after: unknown;
314
+ }
315
+ /** Estado do modelo local de embedding (download e carga fora da requisição, Engramind 0.10.0). */
316
+ interface LocalModelStatus {
317
+ model: string;
318
+ state: "idle" | "downloading" | "loading" | "ready" | "failed";
319
+ started_at: string | null;
320
+ /** Tempo decorrido; em `ready` e `failed`, quanto levou. */
321
+ elapsed_ms: number | null;
322
+ /** Motivo da falha (`failed`); nova tentativa 30 s depois, na próxima chamada. */
323
+ error: string | null;
324
+ }
325
+ interface EngramindStatus {
326
+ version: string;
327
+ uptime_seconds: number;
328
+ database: {
329
+ status: string;
330
+ size_bytes: number;
331
+ migrations?: unknown;
332
+ };
333
+ backup: {
334
+ enabled: boolean;
335
+ status: "ok" | "fail" | "skip";
336
+ detail: string | null;
337
+ replica_age_seconds: number | null;
338
+ last_replica_at: string | null;
339
+ checked_at: string | null;
340
+ };
341
+ counts: Record<string, number>;
342
+ queue: {
343
+ pending: number;
344
+ failed: number;
345
+ by_kind: {
346
+ kind: string;
347
+ state: string;
348
+ count: number;
349
+ }[];
350
+ };
351
+ redator: {
352
+ enabled: boolean;
353
+ configured: boolean;
354
+ missing: string[];
355
+ provider: string | null;
356
+ model: string | null;
357
+ last_24h?: {
358
+ calls: number;
359
+ failures: number;
360
+ cost_usd: number | null;
361
+ };
362
+ };
363
+ embedding: {
364
+ enabled?: boolean;
365
+ configured?: boolean;
366
+ provider: string | null;
367
+ model: string | null;
368
+ model_id: string | null;
369
+ dimensions: number | null;
370
+ state: "off" | "ready" | "reindexing" | "failed";
371
+ total: number | null;
372
+ done: number | null;
373
+ missing?: string[];
374
+ reindex_started_at: string | null;
375
+ local?: LocalModelStatus | null;
376
+ };
377
+ webhooks: {
378
+ enabled: boolean;
379
+ apps: number;
380
+ pending: number;
381
+ delivered_24h: number;
382
+ failed_24h: number;
383
+ };
384
+ retention: {
385
+ last_run_at: string;
386
+ removed?: Record<string, number>;
387
+ } | null;
388
+ consolidation?: {
389
+ enabled_channels: number;
390
+ schedule: string;
391
+ queued: number;
392
+ running: number;
393
+ last_scheduled_at?: string | null;
394
+ next_run_at?: string | null;
395
+ last_24h?: Record<string, number>;
396
+ } | null;
397
+ }
398
+ /** Página de uma lista paginada por cursor (`next_cursor = null` na última). */
399
+ interface CursorPage<T> {
400
+ items: T[];
401
+ nextCursor: string | null;
402
+ }
403
+ /** `get_consumer_settings`: o que a interface do consumidor precisa sem token admin. */
404
+ interface ConsumerSettings {
405
+ memory: {
406
+ text_min: number;
407
+ text_max: number;
408
+ };
409
+ limits: {
410
+ max_batch: number;
411
+ };
412
+ context: {
413
+ budget_tokens: number;
414
+ titles_budget_tokens: number;
415
+ default_format: string;
416
+ text_max_chars: number;
417
+ };
418
+ channels: {
419
+ channel: string;
420
+ memory_text_max: number;
421
+ budget_tokens: number;
422
+ category_priorities: Record<string, number>;
423
+ text_max_chars: number;
424
+ }[];
425
+ /**
426
+ * Prazo de desuso e pesos da busca: pedidos ao Engramind para o modo app (ver
427
+ * `docs/engramind-consumer-settings.md`). Até lá a lib usa os padrões (90 dias, 1/1/60).
428
+ */
429
+ consolidation?: {
430
+ stale_after_days?: number;
431
+ };
432
+ search?: {
433
+ vector_weight?: number;
434
+ text_weight?: number;
435
+ rrf_k?: number;
436
+ };
437
+ }
438
+
439
+ export type { AuditEvent as A, Category as C, EngramindAgent as E, ImportResult as I, LlmCall as L, MemoryRevision as M, Ref as R, SearchResults as S, UsageReport as U, WhoAmI as W, Author as a, ChannelSettingMeta as b, ChannelSettingsView as c, ChannelStats as d, ConsolidationPlan as e, ConsolidationRun as f, ConsumerSettings as g, ContextView as h, CursorPage as i, EngramindApp as j, EngramindChannel as k, EngramindHint as l, EngramindJob as m, EngramindMemory as n, EngramindStatus as o, EngramindSubscription as p, EngramindToken as q, EngramindTokenKind as r, IssuedToken as s, LocalModelStatus as t, MemoryWrite as u, SettingMeta as v };