import type { ReasoningLevel } from "./agent-reasoning"; import type { ObjectiveStatus } from "./objective-validation"; import type { ResourceInput } from "./types"; import type { IssueStatus } from "./issue-constants"; import type { SelfHostingHelpContext } from "./self-hosting-help-context"; // ── Numo (AI assistant) shared types ───────────────────────────────── export type ConversationStatus = "idle" | "generating" | "error"; export type NumoTurnStatus = | "queued" | "running" | "waiting_input" | "stopping" | "stopped" | "retryable" | "reconciling" | "completed" | "failed"; export interface NumoTurnActivity { id: string; seq: number; type: string; payload: Record; created_at: string; } export interface Conversation { id: string; project_id: string | null; user_id: string; title: string | null; status: ConversationStatus; error_message: string | null; created_at: string; updated_at: string; /** Joined project name, present on list responses in global mode. */ project?: { name: string } | null; } export interface NumoConversation extends Omit { user_id: string | null; source: "assistant"; /** The assistant conversation row behind the common Numo identity. */ legacy_id: string; archived_at: string | null; pinned_at: string | null; last_read_at: string | null; /** Time of Numo's latest answer; null while the conversation has none. */ last_answer_at: string | null; /** Explicit conversation choice; null preserves the account default. */ /** Provider of the conversation (MIN-826); null follows Numo's default provider. */ provider?: string | null; model?: string | null; /** Explicit conversation choice; null preserves the compatible legacy default. */ reasoning_level?: ReasoningLevel | null; } export interface NumoMessage extends AssistantMessage { source: "assistant"; kind: "message" | "action"; turn_id: string | null; } export interface NumoConversationDetail { conversation: NumoConversation; messages: NumoMessage[]; actions: NumoMessage[]; turns: Array<{ id: string; conversation_id: string; run_id: string; status: NumoTurnStatus; model: string | null; reasoning_level: string | null; initiated_by: string | null; cost_usd: number; outcome: string | null; error_message: string | null; started_at: string | null; completed_at: string | null; created_at: string; updated_at: string; }>; /** Trigger provenance when this conversation is a routine occurrence. */ routine_occurrence?: { id: string; routine_id: string; origin: "scheduled" | "manual" | "event" | "database"; event_id?: number | null; subject_id?: string | null; subject_type?: "issue" | "page" | "feedback" | null; scheduled_for: string | null; created_at: string; } | null; } export interface NumoConversationPatch { title?: string | null; pinned?: boolean; archived?: boolean; read?: boolean; /** Empty or null follows the active assistant provider default. */ /** Provider of the conversation (MIN-826); null follows Numo's default provider. */ provider?: string | null; model?: string | null; /** Null follows the instance assistant reasoning default. */ reasoningLevel?: ReasoningLevel | null; } export interface AssistantToolCall { id: string; type: "function"; function: { name: string; arguments: string; }; /** * Provider data echoed back with the call on the next round: Gemini 3 puts * its `thought_signature` here and rejects a later round without it. */ extra_content?: unknown; } export interface AssistantMessage { id: string; conversation_id: string; role: "user" | "assistant" | "system" | "tool"; content: string | null; tool_calls: AssistantToolCall[] | null; tool_call_id: string | null; tool_name: string | null; /** On user messages, `metadata.attachments` carries the files sent with the message (AttachmentInput[] shape) — chat files have no DB row. */ metadata: Record; /** * The page context the message was sent with (open issue, board onglet…), * persisted so the chat can render a context badge above the user bubble. * Only set on user messages sent from a contextual surface; null otherwise. */ context?: AssistantPageContext | null; created_at: string; } /** * A hand-chosen context element (@ button on the composer): a ticket, * a project or a team member. The label comes from the client — it's only there * so Numo can name the thing without re-resolving its id. */ export interface AssistantPinnedContext { /** Validated project provenance of the referenced resource. */ projectId?: string; kind: "issue" | "project" | "member" | "objective" | "page"; id: string; /** Examples such as “MIN-42”, “minddy”, and “Clément Guérin” — what the pill displays. */ label: string; /** Secondary detail: ticket title, member email. */ detail?: string; /** Members: the portrait seed (public.user_avatars), which is NOT still the user_id — inferring it would show another face. */ avatarSeed?: string; /** Objectives: their color — this is what their target wears, here like everywhere else in the application. */ color?: string | null; /** Objectives: their status, shown instead of the target (MIN-785). */ objectiveStatus?: ObjectiveStatus; /** Wiki pages: their emoji, when they have one. */ icon?: string | null; } /** * A “/” command chosen from the composer slash menu. The id is canonical * (the label is localized - "/create issue" / "/create ticket"): * it is he who travels in the request and persists on `metadata.command` * of the user message, where the server unfolds it into instructions for Numo. */ export type AssistantCommandId = "create-issue"; /** * An "@" mention written IN the message (team member, project, ticket * or goal), resolved at the time of typing. Persisted on * `metadata.mentions` of the user message: it is used to return the pill * to the bubble, and to tell Numo who/what this name designates exactly. */ export interface AssistantMention { projectId?: string; type: "member" | "project" | "issue" | "objective" | "page"; id: string; /** The text written after the “@” in the message. */ label: string; /** Zero-based occurrence of this exact token label in a persisted prompt. */ occurrence?: number; /** Members: the portrait seed — see AssistantPinnedContext.avatarSeed. */ avatarSeed?: string; /** Objectives: their color; see AssistantPinnedContext.color. */ color?: string | null; /** Wiki pages: their emoji (MIN-273). */ icon?: string | null; /** Issues: workflow state captured for immediate rendering; live sources win. Objectives: the issue state their status borrows (MIN-785). */ status?: IssueStatus; } /** A repository skill explicitly attached to one Numo message. */ export interface AssistantSkillSelection { projectId?: string; /** Repository-relative SKILL.md entrypoint; it is the stable selection id. */ path: string; name: string; description: string; } /** * Structured "what the user is currently looking at" context, attached to a * chat request so Numo can resolve deictic references ("this ticket", "this * view") to a concrete issue/board without guessing. Derived ambiently from * the page the user is on. Plain opens with no surface leave it undefined. * Client-set, server-validated. */ export interface AssistantPageContext { /** Public guide context; this surface only permits documentation help. */ documentation?: { articleId: string | null; locale: string; selfHosting?: SelfHostingHelpContext }; projectId?: string; /** The account-level inbox is the current ambient surface. */ inbox?: true; /** The settings surface the assistant was opened from. */ settings?: "account" | "project"; /** The admin dashboard tab the assistant was opened from (MIN-829). */ admin?: "accounts" | "costs" | "models" | "instance"; /** * Context PINED by hand from the composer (@ button), as opposed to * from the rest of this object, inferred from the page. It survives navigation: it's * the user who chose it, not the page that published it. */ pinned?: AssistantPinnedContext[]; /** Legacy (pre views-v2): the board tab the message was sent from. No longer populated — kept so old persisted messages still render their badge. */ onglet?: "my" | "all"; /** The issue open in the side panel (or selected in triage), when any. */ issueId?: string; /** Issues selected for a bulk assistant request. */ issueIds?: string[]; /** Project provenance aligned with issueIds, resolved by the server. */ issueProjectIds?: string[]; issueIdentifiers?: string[]; issueTitles?: string[]; /** Human identifier ("MIND-42") — used for the context badge. */ issueIdentifier?: string; issueTitle?: string; /** The objective whose filtered board is displayed, when any. */ objectiveId?: string; objectiveName?: string; /** Its color — what its target wears on the context pill. */ objectiveColor?: string | null; /** Its status, whose glyph the context pill shows (MIN-785). */ objectiveStatus?: ObjectiveStatus; /** The feedback post open in the team dashboard, when any (MIN-52). */ feedbackId?: string; feedbackTitle?: string; /** The routine open in the Agents page's Routines tab, when any (MIN-185). */ routineId?: string; routineTitle?: string; /** Canonical Minddy pull-request row. Unlike `prRunId`, this also exists for human pull requests and survives worker changes. */ pullRequestId?: string; prNumber?: number; prState?: string; /** Selected head/base refs retained with the voluntary request. */ prHeadRef?: string; prBaseRef?: string; /** Canonical agent run id backing the PR — what read_pull_request resolves. */ prRunId?: string; /** Historical worker explicitly selected for continuation through Numo. */ codeWorkerRunId?: string; /** The saved kanban view currently selected on the board, when any. */ viewId?: string; viewName?: string; /** The cycle displayed in cycle mode (MIN-32), when any. */ cycleId?: string; /** Human date-range label ("6–19 juil") — used for the context badge. */ cycleLabel?: string; /** * The opened wiki PAGE (MIN-273). The title travels with the id so that the * pill says it without rereading the page, and so that Numo can name it before * its first call of tool. */ pageId?: string; pageTitle?: string; pageIcon?: string | null; /** * Client-only hint that the open page is a database, so the empty composer * offers database suggestions. The chat route never forwards it: the server * derives `pageIsDatabase` on its own. */ pageDatabase?: true; /** * Set by the server, never read from the request: the open page is a * database, so "a view" means one of its saved views, not a board view. */ pageIsDatabase?: boolean; /** * A passage the user selected in an editor and handed to Numo ("Ask Numo" * in the selection toolbar). It belongs to the message being written: the * composer drops it once that message is sent. Normalized and capped by * `normalizeSelectionText` on both sides (lib/assistant-selection.ts). */ selection?: AssistantSelectionContext; /** * Records the user selected together on a list surface (MIN-873), all of * one kind. Tickets keep their own `issueIds` lists. */ selectedItems?: AssistantSelectedItem[]; } /** * One record of a group selected on a list surface other than an issue board * (MIN-873): feedback posts, routines, objectives or wiki pages. The label is * the record's title, so Numo can name it before its first tool call. */ export interface AssistantSelectedItem { kind: AssistantSelectedItemKind; id: string; label: string; } export type AssistantSelectedItemKind = "feedback" | "routine" | "objective" | "page"; /** The quoted passage of `AssistantPageContext.selection`. */ export interface AssistantSelectionContext { text: string; } /** Where a voluntary request entered Numo. This is product context, not an * execution choice: repository work is still delegated by Numo itself. */ export type NumoIntentSource = | "home" | "issue" | "objective" | "feedback" | "page" | "scratchpad" | "code_worker" | "pull_request" | "routine" | "bulk" /** Background Numo (MIN-929): a wake on workspace events, never a client gesture. */ | "background" /** A reminder the user asked Numo for (MIN-923) whose condition was met. */ | "reminder"; /** The distinction the originating action promised to preserve. */ export type NumoIntentAction = | "discuss" | "implement" | "plan" | "verify" | "custom" | "review" | "fix" | "promote"; /** * The single client-side entry contract for a voluntary Numo request. * * Every source supplies the original wording plus structured source context. * Model selection, Minddy tools and code-worker delegation remain decisions of * the common conversation runtime rather than fields on this payload. */ export interface NumoIntent { source: NumoIntentSource; action: NumoIntentAction; projectId: string | null; /** Omit when the gesture only opens the shared composer with source context. */ prompt?: string; pageContext?: AssistantPageContext; mentions?: AssistantMention[]; command?: AssistantCommandId; attachments?: ResourceInput[]; } // Request body for chat endpoint export interface AssistantChatRequest { /** Idempotency key for one user intent. Reuse it when the same POST is retried. */ requestId?: string; /** Identity reserved by the browser before the first response headers arrive. */ newConversationId?: string; conversationId?: string; projectId?: string; message: string; /** Voluntary entry provenance, persisted with the user message. */ intent?: Pick; /** * The version of Numo's Home summary this first message answers (MIN-933): * the new conversation opens with it as Numo's first message. */ homeBriefId?: string; /** Exact pending delegated-worker question answered by this card submission. */ workerInput?: { parentTurnId: string; runId: string; questionId: string; }; /** Model selected for this conversation; omitted/null follows its default. */ /** Conversation provider (MIN-826): `minddy` or a credential provider; null follows the Numo default. */ provider?: string | null; model?: string | null; /** Reasoning selected for this conversation; omitted/null follows its default. */ reasoningLevel?: ReasoningLevel | null; /** * What the user is currently viewing (open issue, board onglet, objective). * Injected into the system prompt so Numo can resolve "ce ticket" precisely. * Carried on the messages of the originating session. */ pageContext?: AssistantPageContext; /** * Files already uploaded to the `attachments` bucket under `chat/{uid}/…` * (AttachmentInput[] shape) — validated server-side against that prefix and * persisted on the user message's metadata. */ attachments?: Array<{ storage_path: string; file_name: string; mime_type: string; size_bytes: number; }>; /** * The "@" written in the message (members, projects), resolved on the client side. * Persisted on the message metadata and given to Numo in the form of a * resolution line name → id. */ mentions?: AssistantMention[]; /** * The “/” command placed at the top of the message, when there is one. Validated * server, persisted on the message metadata and unfolded as a block * of instructions attached to this message (same mechanics as the mentions). */ command?: AssistantCommandId; /** Repository entrypoints selected through the slash or add menu. */ skillPaths?: string[]; skills?: AssistantSkillSelection[]; /** * The browser's IANA zone (MIN-185). Without it, "creates a routine every * Mondays at 1 p.m." would go into UTC without anyone knowing — and would find out weeks later, when the routine runs. This is * data that only the client knows: the server cannot guess it. */ timezone?: string; }