import { type FileResourceRef, type WorkspaceSlackReactionSummonSettings } from "@opengeni/contracts"; import { type SlackInstallationRoute, type SlackInteraction, type SlackInteractionInboxEntry, type SlackInteractionTriggerKind } from "@opengeni/db"; import { type ApiRouteDeps } from "@opengeni/core"; import type { Hono } from "hono"; import { type OpenGeniSlackBotClient, type SlackMessageBlock } from "./slack-bot.js"; import { type ImportedSlackReactionImage } from "../slack-reaction-files.js"; export declare const SLACK_INTERACTION_MAX_BODY_BYTES: number; export declare const SLACK_SIGNATURE_REPLAY_WINDOW_SECONDS = 300; export declare const SLACK_DELIVERY_EVENT_TYPES: readonly ["agent.message.completed", "session.humanInput.requested", "session.requiresAction", "turn.completed", "turn.failed", "turn.cancelled", "session.status.changed", "system.update.pending", "goal.paused"]; /** * Slack delivery restrictions are durable session-level authority, not * attacker-adjacent user-message context. Migration 0240 backfills this exact * policy onto every pre-cutover session reserved by a Slack interaction. */ export declare const SLACK_SESSION_INSTRUCTIONS: string; /** * Slack-originated tasks may retrieve the workspace bot's bounded read surface * on demand. Connector tools are explicit-only, so freeze that narrow context * selection at session creation while keeping Slack mutations out of the model * surface; interaction delivery remains owned by the durable delivery pump. */ export declare const SLACK_TASK_FIRST_PARTY_MCP_TOOLS: ("artifacts_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "artifacts_rollback" | "atlassian_get" | "atlassian_search" | "atlassian_sources_list" | "browser_act" | "browser_auth" | "browser_clipboard" | "browser_debug" | "browser_identity" | "browser_lifecycle" | "browser_observe" | "browser_open" | "browser_publish" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_remove" | "editable_artifact_apply" | "editable_artifact_create" | "editable_artifact_export" | "editable_artifact_export_status" | "editable_artifact_get" | "editable_artifact_import" | "editable_artifact_inspect" | "editable_artifact_list" | "environment_list" | "environment_set_variable" | "fiken_bank_accounts_list" | "fiken_companies_list" | "fiken_contact_create" | "fiken_contacts_list" | "fiken_invoice_draft_create" | "fiken_invoice_get" | "fiken_invoices_list" | "fiken_products_list" | "fiken_purchases_list" | "fiken_sales_list" | "github_connect_link" | "github_repositories_list" | "goal_complete" | "goal_pause" | "goal_progress" | "goal_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "remember" | "remember_confirm" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "sandbox_file_publish" | "sandbox_provision" | "sandbox_swap" | "sandboxes_list" | "scheduled_task_runs_list" | "scheduled_tasks_create" | "scheduled_tasks_delete" | "scheduled_tasks_get" | "scheduled_tasks_list" | "scheduled_tasks_pause" | "scheduled_tasks_resume" | "scheduled_tasks_trigger" | "scheduled_tasks_update" | "session_create" | "session_events" | "session_get" | "session_human_input_respond" | "session_pause" | "session_resume" | "session_send_message" | "session_set_project" | "session_steer" | "session_wait" | "sessions_list" | "set_other_session_title" | "set_session_title" | "slack_bot_channel_history" | "slack_bot_delete_message" | "slack_bot_file_content" | "slack_bot_file_info" | "slack_bot_list_channels" | "slack_bot_list_files" | "slack_bot_list_users" | "slack_bot_post_message" | "slack_bot_search" | "slack_bot_thread_replies" | "social_connections_list" | "social_daily_analysis_context" | "social_mentions_live" | "social_post_reply" | "social_posts_recent" | "social_posts_sync" | "social_search_live" | "social_thread_fetch" | "task_note_archive" | "task_note_promote_instruction_policy" | "task_note_promote_knowledge" | "task_note_promote_preference" | "task_note_replace" | "task_note_save" | "task_notes_list" | "variable_set_get_variable" | "variable_set_list" | "variable_set_set_variable" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[]; export type NormalizedSlackInteraction = { providerEventId: string; providerMessageId: string; slackTeamId: string; slackUserId: string; slackChannelId: string; slackMessageTs: string; slackThreadTs: string | null; triggerKind: SlackInteractionTriggerKind; text: string; hasFiles: boolean; }; export declare function slackInteractionRoutePolicy(entry: Pick, options?: { privateHandoff?: boolean; }): { directMessageShortcut: boolean; requiresChannelAccess: boolean; visibility: "private" | "workspace"; initialRouteKey: string; }; export declare function verifySlackRequestSignature(input: { timestamp: string | null; signature: string | null; rawBody: string; }, signingSecret: string, nowMs?: number): boolean; export declare function slackEventInboxEntry(payload: unknown, bot: Pick): NormalizedSlackInteraction | null; export declare function slackReactionInboxEntry(payload: unknown, bot: Pick, settings: WorkspaceSlackReactionSummonSettings): NormalizedSlackInteraction | null; export declare function registerSlackInteractionRoutes(app: Hono, deps: ApiRouteDeps): void; export declare function drainSlackInteractionsOnce(deps: ApiRouteDeps): Promise; export declare function startSlackInteractionPump(deps: ApiRouteDeps, options?: { intervalMs?: number; maxPerTick?: number; }): () => void; /** * Say where the work went, quietly. * * The line exists to stop work landing somewhere surprising, so it appears only * when the routing decision was a genuine choice: a null label means routing is * off, or the person had exactly one workspace anyway, and a constant footer on * every message would be noise rather than information. * * The label is frozen on the interaction rather than looked up here, because a * workspace rename between the original post and a reconciliation would make the * post ledger's byte comparison raise. */ export declare function withWorkspaceLine(label: string | null, text: string, blocks?: SlackMessageBlock[]): { text: string; blocks?: SlackMessageBlock[]; }; /** * The post-ledger seed for one interaction's messages. * * The ledger binds one operation id to a digest over the rendered bytes, so * adding the workspace line under an unchanged id would wedge every interaction * with a claimed-but-unposted row. But bumping the id for interactions whose * bytes did NOT change is not free either: their ledger rows become unreachable, * so a repair or a cross-deploy retry posts a duplicate instead of finding the * completed original, and an action handle still points at the old operation. * * Only a labelled interaction renders different bytes, and the label is written * once at insert and never updated, so it is a stable discriminator. Version the * id on the label rather than on the release: a labelled interaction can only * have been created by an image that already renders the line, and every * pre-existing and unlabelled interaction keeps its exact ledger identity. */ export declare function slackPostSeed(interaction: Pick, seed: string): string; export type SlackInvocationMessageContext = { messages: Awaited>["messages"]; nextCursor: string | null; kind: "thread" | "channel"; }; export declare function slackInvocationModelContext(invocationTimestamp: string, context: SlackInvocationMessageContext): string; type SlackReactionMessageContext = Awaited>; type PreparedSlackReactionTask = Readonly<{ resources: FileResourceRef[]; attachments: ImportedSlackReactionImage[]; omissionCodes: string[]; omittedCount: number; }>; export declare function slackReactionTaskText(context: SlackReactionMessageContext, prepared?: PreparedSlackReactionTask): string; export declare function slackDeliveryTextsCoalesce(left: string, right: string): boolean; export declare function normalizedBlockActionInteraction(payload: Record): NormalizedSlackInteraction; export declare function isSlackInfoCommand(text: string): boolean; type SlackUserLinkToken = { workspaceId: string; connectionId: string; slackTeamId: string; slackUserId: string; expiresAt: number; }; export declare function createSlackUserLinkToken(signingSecret: string, entry: Pick, nowMs?: number): string; export declare function verifySlackUserLinkToken(signingSecret: string, token: string, nowMs?: number): SlackUserLinkToken | null; export {};