/** * Work-item comments domain — one message list per work item, materialized * from every source. Local creates and platform mirrors (Slack, Linear, * GitHub) share one idempotency story: `external_source` json + derived * `source_key` under a partial unique index, a local retry token being just * another source (`local:comment:`). * * Ordering is caller-settable `occurred_at`, never insert time — ingest * backdates to the platform timestamp and retries arrive out of order. * Deletes are soft: the tombstone holds the ordering and its kept * `source_key` stops a redelivery from resurrecting the row. */ import { FactoryStorageDomain } from '@mastra/core/storage'; import type { ExternalWorkItemSource } from '../work-items/base.js'; import type { WorkItemActivityRow } from './activity.js'; import type { FactoryActorRef } from './actor.js'; export type { WorkItemActivityRow } from './activity.js'; export { WORK_ITEM_ACTIVITY_SCHEMA, WORK_ITEM_COMMENT_MENTIONS_SCHEMA, WORK_ITEM_COMMENTS_SCHEMA } from './schema.js'; export type WorkItemCommentKind = 'comment'; export interface FactoryMentionRef { kind: 'user'; id: string; } export interface WorkItemCommentReplyRef { commentId: string; quote?: string; authorId?: string; authorName?: string; } export interface WorkItemCommentRow { id: string; orgId: string; factoryProjectId: string; workItemId: string; kind: WorkItemCommentKind; body: string; bodyFormat: string; author: FactoryActorRef; replyTo: WorkItemCommentReplyRef | null; mentions: FactoryMentionRef[]; externalSource: ExternalWorkItemSource | null; sourceKey: string | null; occurredAt: Date; editedAt: Date | null; deletedAt: Date | null; deletedBy: string | null; revision: number; createdAt: Date; updatedAt: Date; } export interface CreateWorkItemCommentInput { orgId: string; factoryProjectId: string; workItemId: string; author: FactoryActorRef; body: string; bodyFormat?: string; replyTo?: WorkItemCommentReplyRef; mentions?: FactoryMentionRef[]; externalSource?: ExternalWorkItemSource; /** Local idempotent-retry token; mutually exclusive with `externalSource`. */ clientToken?: string; occurredAt?: Date; } export interface EditWorkItemCommentInput { orgId: string; commentId: string; body: string; mentions?: FactoryMentionRef[]; /** The acting user; their own handle never becomes a mention row. */ editorId?: string; /** When set, the edit only lands if the row is still at this revision. */ expectedRevision?: number; now?: Date; } export interface EditWorkItemCommentResult { comment: WorkItemCommentRow; addedMentions: FactoryMentionRef[]; removedMentions: FactoryMentionRef[]; } export interface ListWorkItemCommentsInput { orgId: string; factoryProjectId: string; workItemId: string; before?: string; limit?: number; /** * Anchor the first page on this comment: it and every comment newer than it, * so a deep link opens on the target instead of paging back to find it. * Ignored when the comment is gone, or sits further back than one page holds. */ around?: string; } /** A feed snapshot: how many comments the item had, and when it last moved. */ export interface FeedActivitySnapshot { commentCount: number; feedActivityAt: Date; } /** * The refresh reads its snapshot outside the write transaction, so two of them * can interleave and the loser can write last. This is the one rule that keeps * that from undoing a newer refresh: feed activity never moves backwards, and * on the same stamp the fuller count wins. * * Known ceiling: a soft delete sharing a millisecond with a create leaves the * count one high until the next feed mutation recounts. Closing that needs the * aggregate read inside the write transaction, which the ops layer cannot do * without checking out a second pool connection per open transaction. */ export declare function supersedesFeedActivity(next: FeedActivitySnapshot, stored: FeedActivitySnapshot): boolean; export interface WorkItemCommentPage { comments: WorkItemCommentRow[]; nextCursor?: string; } export interface WorkItemMentionRow { id: string; commentId: string; mentionedKind: 'user'; mentionedId: string; authorId: string; orgId: string; factoryProjectId: string; workItemId: string; occurredAt: Date; } export declare const MAX_COMMENT_BODY_LENGTH = 16000; export declare const MAX_COMMENT_QUOTE_LENGTH = 500; export declare const MAX_COMMENT_MENTIONS = 20; /** The one body policy: HTTP parsing and the service both reject on it. */ export declare function commentBodyError(body: string): string | undefined; export declare function clampCommentLimit(limit: number | undefined): number; export declare function encodeCommentCursor(row: WorkItemCommentRow): string; export declare function decodeCommentCursor(cursor: string): { occurredAt: Date; id: string; } | undefined; export declare function commentSourceKey(input: { externalSource?: ExternalWorkItemSource; clientToken?: string; }): string | null; /** A `clientToken` replay that resolved to a different work item or author. */ export declare class CommentTokenConflictError extends Error { constructor(); } export declare class WorkItemCommentsStorage extends FactoryStorageDomain { #private; constructor(); init(): Promise; dangerouslyClearAll(): Promise; create(input: CreateWorkItemCommentInput): Promise; listActivityForUser(args: { orgId: string; factoryProjectId: string; userId: string; before?: { occurredAt: Date; id: string; }; limit: number; }): Promise; /** * Idempotent counter refresh on the parent work item: both columns are read * back off the comments (never incremented or stamped with the wall clock — * replays and races double an increment, and a replayed create would move a * work item in the feed without adding a comment; a read-back can't drift). * Read BEFORE `updateAtomic`: its mutator runs inside an open transaction * holding a pool connection, and a query in there checks out a second one — * concurrent posts would exhaust the pool. Reading outside means two * refreshes can interleave, so the write goes through * {@link supersedesFeedActivity} rather than landing whatever it read. * Touches ONLY the counter columns: `revision`/`updated_at` are the * stage-transition concurrency token. */ refreshWorkItemFeedActivity({ orgId, factoryProjectId, workItemId, now, }: { orgId: string; factoryProjectId: string; workItemId: string; now?: Date; }): Promise; get({ orgId, commentId }: { orgId: string; commentId: string; }): Promise; listByIds({ orgId, ids }: { orgId: string; ids: string[]; }): Promise; list(input: ListWorkItemCommentsInput): Promise; /** * Newest non-deleted comments for run-context injection, newest-first (the * caller reverses for display order). */ listRecent({ orgId, factoryProjectId, workItemId, limit, }: { orgId: string; factoryProjectId: string; workItemId: string; limit: number; }): Promise; edit(input: EditWorkItemCommentInput): Promise; softDelete({ orgId, commentId, deletedBy, now, }: { orgId: string; commentId: string; deletedBy: string; now?: Date; }): Promise; /** * Provenance write-back after an outbound publish. First platform wins, and * an existing `source_key` is kept: replacing a `local:comment:` one * would let a client retry duplicate the row. So a web-born comment is never * key-deduped against its own platform echo — the host's bot-sender check is * the only echo layer for those rows (COR-1174). */ attachExternalSource({ orgId, commentId, source, }: { orgId: string; commentId: string; source: ExternalWorkItemSource; }): Promise; listMentionsForComment(commentId: string): Promise; /** Keyset inbox read for the mention attention provider, newest-first. */ listMentionsForUser({ orgId, factoryProjectId, userId, before, limit, }: { orgId: string; factoryProjectId: string; userId: string; before?: { occurredAt: Date; id: string; }; limit: number; }): Promise; countForWorkItem({ orgId, factoryProjectId, workItemId, }: { orgId: string; factoryProjectId: string; workItemId: string; }): Promise; /** Recent distinct comment authors of a project, for the roster fallback. */ listRecentAuthors({ orgId, factoryProjectId, limit, }: { orgId: string; factoryProjectId: string; limit?: number; }): Promise; } //# sourceMappingURL=base.d.ts.map