| import type { |
| AskUserQuestionsAnswer, |
| Approval, |
| CreateIssueTreeHold, |
| DocumentRevision, |
| FeedbackTargetType, |
| FeedbackTrace, |
| FeedbackVote, |
| Issue, |
| IssueAttachment, |
| IssueComment, |
| IssueDocument, |
| IssueLabel, |
| IssueThreadInteraction, |
| IssueTreeControlPreview, |
| IssueTreeHold, |
| IssueWorkProduct, |
| PreviewIssueTreeControl, |
| ReleaseIssueTreeHold, |
| UpsertIssueDocument, |
| } from "@penclipai/shared"; |
| import { api } from "./client"; |
|
|
| export type IssueUpdateResponse = Issue & { |
| comment?: IssueComment | null; |
| }; |
|
|
| export const issuesApi = { |
| list: ( |
| companyId: string, |
| filters?: { |
| status?: string; |
| projectId?: string; |
| parentId?: string; |
| assigneeAgentId?: string; |
| participantAgentId?: string; |
| assigneeUserId?: string; |
| touchedByUserId?: string; |
| inboxArchivedByUserId?: string; |
| unreadForUserId?: string; |
| labelId?: string; |
| workspaceId?: string; |
| executionWorkspaceId?: string; |
| originKind?: string; |
| originId?: string; |
| descendantOf?: string; |
| includeRoutineExecutions?: boolean; |
| q?: string; |
| limit?: number; |
| }, |
| ) => { |
| const params = new URLSearchParams(); |
| if (filters?.status) params.set("status", filters.status); |
| if (filters?.projectId) params.set("projectId", filters.projectId); |
| if (filters?.parentId) params.set("parentId", filters.parentId); |
| if (filters?.assigneeAgentId) params.set("assigneeAgentId", filters.assigneeAgentId); |
| if (filters?.participantAgentId) params.set("participantAgentId", filters.participantAgentId); |
| if (filters?.assigneeUserId) params.set("assigneeUserId", filters.assigneeUserId); |
| if (filters?.touchedByUserId) params.set("touchedByUserId", filters.touchedByUserId); |
| if (filters?.inboxArchivedByUserId) params.set("inboxArchivedByUserId", filters.inboxArchivedByUserId); |
| if (filters?.unreadForUserId) params.set("unreadForUserId", filters.unreadForUserId); |
| if (filters?.labelId) params.set("labelId", filters.labelId); |
| if (filters?.workspaceId) params.set("workspaceId", filters.workspaceId); |
| if (filters?.executionWorkspaceId) params.set("executionWorkspaceId", filters.executionWorkspaceId); |
| if (filters?.originKind) params.set("originKind", filters.originKind); |
| if (filters?.originId) params.set("originId", filters.originId); |
| if (filters?.descendantOf) params.set("descendantOf", filters.descendantOf); |
| if (filters?.includeRoutineExecutions) params.set("includeRoutineExecutions", "true"); |
| if (filters?.q) params.set("q", filters.q); |
| if (filters?.limit) params.set("limit", String(filters.limit)); |
| const qs = params.toString(); |
| return api.get<Issue[]>(`/companies/${companyId}/issues${qs ? `?${qs}` : ""}`); |
| }, |
| listLabels: (companyId: string) => api.get<IssueLabel[]>(`/companies/${companyId}/labels`), |
| createLabel: (companyId: string, data: { name: string; color: string }) => |
| api.post<IssueLabel>(`/companies/${companyId}/labels`, data), |
| deleteLabel: (id: string) => api.delete<IssueLabel>(`/labels/${id}`), |
| get: (id: string) => api.get<Issue>(`/issues/${id}`), |
| markRead: (id: string) => api.post<{ id: string; lastReadAt: Date }>(`/issues/${id}/read`, {}), |
| markUnread: (id: string) => api.delete<{ id: string; removed: boolean }>(`/issues/${id}/read`), |
| archiveFromInbox: (id: string) => |
| api.post<{ id: string; archivedAt: Date }>(`/issues/${id}/inbox-archive`, {}), |
| unarchiveFromInbox: (id: string) => |
| api.delete<{ id: string; archivedAt: Date } | { ok: true }>(`/issues/${id}/inbox-archive`), |
| create: (companyId: string, data: Record<string, unknown>) => |
| api.post<Issue>(`/companies/${companyId}/issues`, data), |
| update: (id: string, data: Record<string, unknown>) => |
| api.patch<IssueUpdateResponse>(`/issues/${id}`, data), |
| previewTreeControl: (id: string, data: PreviewIssueTreeControl) => |
| api.post<IssueTreeControlPreview>(`/issues/${id}/tree-control/preview`, data), |
| createTreeHold: (id: string, data: CreateIssueTreeHold) => |
| api.post<{ hold: IssueTreeHold; preview: IssueTreeControlPreview }>(`/issues/${id}/tree-holds`, data), |
| getTreeHold: (id: string, holdId: string) => |
| api.get<IssueTreeHold>(`/issues/${id}/tree-holds/${holdId}`), |
| listTreeHolds: ( |
| id: string, |
| filters?: { |
| status?: "active" | "released"; |
| mode?: "pause" | "resume" | "cancel" | "restore"; |
| includeMembers?: boolean; |
| }, |
| ) => { |
| const params = new URLSearchParams(); |
| if (filters?.status) params.set("status", filters.status); |
| if (filters?.mode) params.set("mode", filters.mode); |
| if (filters?.includeMembers) params.set("includeMembers", "true"); |
| const qs = params.toString(); |
| return api.get<IssueTreeHold[]>(`/issues/${id}/tree-holds${qs ? `?${qs}` : ""}`); |
| }, |
| getTreeControlState: (id: string) => |
| api.get<{ |
| activePauseHold: { |
| holdId: string; |
| rootIssueId: string; |
| issueId: string; |
| isRoot: boolean; |
| mode: "pause"; |
| reason: string | null; |
| releasePolicy: { strategy: "manual" | "after_active_runs_finish"; note?: string | null } | null; |
| } | null; |
| }>(`/issues/${id}/tree-control/state`), |
| releaseTreeHold: (id: string, holdId: string, data: ReleaseIssueTreeHold) => |
| api.post<IssueTreeHold>(`/issues/${id}/tree-holds/${holdId}/release`, data), |
| remove: (id: string) => api.delete<Issue>(`/issues/${id}`), |
| checkout: (id: string, agentId: string) => |
| api.post<Issue>(`/issues/${id}/checkout`, { |
| agentId, |
| expectedStatuses: ["todo", "backlog", "blocked", "in_review"], |
| }), |
| release: (id: string) => api.post<Issue>(`/issues/${id}/release`, {}), |
| listComments: ( |
| id: string, |
| filters?: { |
| after?: string; |
| order?: "asc" | "desc"; |
| limit?: number; |
| }, |
| ) => { |
| const params = new URLSearchParams(); |
| if (filters?.after) params.set("after", filters.after); |
| if (filters?.order) params.set("order", filters.order); |
| if (filters?.limit) params.set("limit", String(filters.limit)); |
| const qs = params.toString(); |
| return api.get<IssueComment[]>(`/issues/${id}/comments${qs ? `?${qs}` : ""}`); |
| }, |
| listInteractions: (id: string) => |
| api.get<IssueThreadInteraction[]>(`/issues/${id}/interactions`), |
| createInteraction: (id: string, data: Record<string, unknown>) => |
| api.post<IssueThreadInteraction>(`/issues/${id}/interactions`, data), |
| acceptInteraction: ( |
| id: string, |
| interactionId: string, |
| data?: { selectedClientKeys?: string[] }, |
| ) => |
| api.post<IssueThreadInteraction>(`/issues/${id}/interactions/${interactionId}/accept`, data ?? {}), |
| rejectInteraction: (id: string, interactionId: string, reason?: string) => |
| api.post<IssueThreadInteraction>(`/issues/${id}/interactions/${interactionId}/reject`, reason ? { reason } : {}), |
| respondToInteraction: ( |
| id: string, |
| interactionId: string, |
| data: { answers: AskUserQuestionsAnswer[]; summaryMarkdown?: string | null }, |
| ) => |
| api.post<IssueThreadInteraction>(`/issues/${id}/interactions/${interactionId}/respond`, data), |
| getComment: (id: string, commentId: string) => |
| api.get<IssueComment>(`/issues/${id}/comments/${commentId}`), |
| listFeedbackVotes: (id: string) => api.get<FeedbackVote[]>(`/issues/${id}/feedback-votes`), |
| listFeedbackTraces: (id: string, filters?: Record<string, string | boolean | undefined>) => { |
| const params = new URLSearchParams(); |
| for (const [key, value] of Object.entries(filters ?? {})) { |
| if (value === undefined) continue; |
| params.set(key, String(value)); |
| } |
| const qs = params.toString(); |
| return api.get<FeedbackTrace[]>(`/issues/${id}/feedback-traces${qs ? `?${qs}` : ""}`); |
| }, |
| upsertFeedbackVote: ( |
| id: string, |
| data: { |
| targetType: FeedbackTargetType; |
| targetId: string; |
| vote: "up" | "down"; |
| reason?: string; |
| allowSharing?: boolean; |
| }, |
| ) => api.post<FeedbackVote>(`/issues/${id}/feedback-votes`, data), |
| addComment: (id: string, body: string, reopen?: boolean, interrupt?: boolean) => |
| api.post<IssueComment>( |
| `/issues/${id}/comments`, |
| { |
| body, |
| ...(reopen === undefined ? {} : { reopen }), |
| ...(interrupt === undefined ? {} : { interrupt }), |
| }, |
| ), |
| cancelComment: (id: string, commentId: string) => |
| api.delete<IssueComment>(`/issues/${id}/comments/${commentId}`), |
| listDocuments: (id: string, options?: { includeSystem?: boolean }) => |
| api.get<IssueDocument[]>( |
| `/issues/${id}/documents${options?.includeSystem ? "?includeSystem=true" : ""}`, |
| ), |
| getDocument: (id: string, key: string) => api.get<IssueDocument>(`/issues/${id}/documents/${encodeURIComponent(key)}`), |
| upsertDocument: (id: string, key: string, data: UpsertIssueDocument) => |
| api.put<IssueDocument>(`/issues/${id}/documents/${encodeURIComponent(key)}`, data), |
| listDocumentRevisions: (id: string, key: string) => |
| api.get<DocumentRevision[]>(`/issues/${id}/documents/${encodeURIComponent(key)}/revisions`), |
| restoreDocumentRevision: (id: string, key: string, revisionId: string) => |
| api.post<IssueDocument>(`/issues/${id}/documents/${encodeURIComponent(key)}/revisions/${revisionId}/restore`, {}), |
| deleteDocument: (id: string, key: string) => |
| api.delete<{ ok: true }>(`/issues/${id}/documents/${encodeURIComponent(key)}`), |
| listAttachments: (id: string) => api.get<IssueAttachment[]>(`/issues/${id}/attachments`), |
| uploadAttachment: ( |
| companyId: string, |
| issueId: string, |
| file: File, |
| issueCommentId?: string | null, |
| ) => { |
| const form = new FormData(); |
| form.append("file", file); |
| if (issueCommentId) { |
| form.append("issueCommentId", issueCommentId); |
| } |
| return api.postForm<IssueAttachment>(`/companies/${companyId}/issues/${issueId}/attachments`, form); |
| }, |
| deleteAttachment: (id: string) => api.delete<{ ok: true }>(`/attachments/${id}`), |
| listApprovals: (id: string) => api.get<Approval[]>(`/issues/${id}/approvals`), |
| linkApproval: (id: string, approvalId: string) => |
| api.post<Approval[]>(`/issues/${id}/approvals`, { approvalId }), |
| unlinkApproval: (id: string, approvalId: string) => |
| api.delete<{ ok: true }>(`/issues/${id}/approvals/${approvalId}`), |
| listWorkProducts: (id: string) => api.get<IssueWorkProduct[]>(`/issues/${id}/work-products`), |
| createWorkProduct: (id: string, data: Record<string, unknown>) => |
| api.post<IssueWorkProduct>(`/issues/${id}/work-products`, data), |
| updateWorkProduct: (id: string, data: Record<string, unknown>) => |
| api.patch<IssueWorkProduct>(`/work-products/${id}`, data), |
| deleteWorkProduct: (id: string) => api.delete<IssueWorkProduct>(`/work-products/${id}`), |
| }; |
|
|