import { type WorkItemAttachmentHandle } from './comment-attachments.js'; import type { WriteOrigin } from './origin.js'; /** * Work-item comments — the mutable human/agent discussion layer of the Todos * ledger (Todos v2 slice 2). `work_item_events` stays pure machine audit; every * comment mutation here records its audit event in the same transaction. * * Semantics (design decisions, locked): * - Threading is single-level: a comment is top-level or replies to a TOP-LEVEL * comment. Replying to a reply re-parents to the thread root (Slack model). * - Delete = tombstone: body is cleared and `deleted_at` stamped; the row and * the thread shape survive. Tombstones cannot be edited; replying to one is * fine. * - Authority: anyone identified may comment; edit/tombstone is author-only, * plus the operator surface for any comment. * - `comment_added` bumps the Todo version (activity resorts lists); * `comment_edited`/`comment_deleted` are audit-only. */ export interface WorkItemComment { id: string; workItemId: string; parentCommentId: string | null; authorKind: 'operator' | 'employee' | 'system'; author: string; /** '' when tombstoned. */ body: string; createdAt: string; editedAt: string | null; deletedAt: string | null; } export interface AddCommentInput { workItemId: string; body: string; author: string; authorKind: WorkItemComment['authorKind']; parentCommentId?: string | null; /** The surface the write was issued from, when the request declared one. */ origin?: WriteOrigin; /** Stable machine-operation identity. When present, an exact retry returns * the first committed comment and changed input fails closed. */ idempotencyKey?: string; } /** Who is attempting an edit/tombstone: the derived author identity (string AND * kind — the kind discriminates the operator/system sentinel namespace from * employee slugs, so a slug literally named "operator" never matches the * operator's comments), plus whether the call comes from the authenticated * operator surface. */ export interface CommentEditor { author: string; authorKind: WorkItemComment['authorKind']; operator: boolean; } export type TodoCommentListener = (comment: WorkItemComment) => void | Promise; export declare function setTodoCommentListener(listener: TodoCommentListener | null): void; export declare function notifyTodoComment(comment: WorkItemComment): void; export type WorkItemCommentErrorCode = 'comment-not-found' | 'comment-forbidden' | 'comment-deleted'; export declare class WorkItemCommentError extends Error { readonly code: WorkItemCommentErrorCode; constructor(code: WorkItemCommentErrorCode, message: string); } /** Comments returned per page: default when the caller passes no limit, and the * hard per-page ceiling. */ export declare const COMMENT_PAGE_DEFAULT_LIMIT = 50; export declare const COMMENT_PAGE_MAX_LIMIT = 500; /** Comments included in the Todo detail payload's tail. */ export declare const COMMENT_TAIL_DEFAULT = 10; export declare function getComment(id: string): WorkItemComment | undefined; export { addComment } from './comment-add.js'; /** The earliest live operator comment on `todoId` written inside the window * `(after, until)` — how a parked `todo-comment` Wait node learns the operator * replied while it was still listening. * * Identity is the (kind, author) PAIR, never the string alone: an employee * whose slug is literally "operator" writes `author = 'operator'` too, and the * `comment_added` audit row keeps only that string. Reading the comments table * is what keeps such a slug from resuming a run the operator never answered. * * Both bounds are strict. A comment written in the same millisecond as the park * is not an answer to a question it could not have seen, and the deadline * instant belongs to the timeout — that is where the node stops waiting, so a * reply from there on cannot be one the run was still open to. Without the * upper bound a restart hours late would answer an expired park. */ /** The operator's reply as a parked Wait consumes it: the words, plus handles * for whatever was attached to that comment. A screenshot is half the answer. */ export interface OperatorReply extends Pick { attachments: WorkItemAttachmentHandle[]; } export declare function firstOperatorCommentAfter(workItemId: string, after: string, until: string): OperatorReply | undefined; /** Edit a comment's body. Author-or-operator; tombstones cannot be edited. */ export declare function editComment(id: string, body: string, editor: CommentEditor): WorkItemComment; /** Tombstone a comment: body cleared, `deleted_at` stamped, row and thread shape * retained. Author-or-operator. Idempotent — deleting a tombstone is a no-op. * * `origin` is the surface the deletion was issued from, same audit colour as * `addComment`'s: taking a spoken comment back is itself a talk write, and an * audit that labels the add but not its reversal tells half the story. */ export declare function tombstoneComment(id: string, editor: CommentEditor, origin?: WriteOrigin): WorkItemComment; export interface CommentPage { comments: WorkItemComment[]; /** Exact per-item comment count, before LIMIT/OFFSET. */ total: number; } /** List a Todo's comments chronologically (oldest first) with LIMIT/OFFSET * paging. An unknown Todo simply reads as empty — existence 404s belong to the * route layer. */ export declare function listComments(workItemId: string, opts?: { limit?: number; offset?: number; }): CommentPage; /** The last `n` comments (default 10) in chronological order, with the exact * total — the capped tail the Todo detail payload embeds. */ export declare function commentsTail(workItemId: string, n?: number): CommentPage; //# sourceMappingURL=comments.d.ts.map