kanban.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import type { JsonValue } from '@shared/types'
  2. import { apiRequest, apiUrl, unwrapData } from './client'
  3. const BASE = '/api/app/kanban'
  4. export interface KanbanStatus {
  5. available: boolean
  6. version?: string
  7. reason?: string
  8. }
  9. export interface KanbanBoardMeta {
  10. slug: string
  11. name?: string | null
  12. description?: string | null
  13. icon?: string | null
  14. color?: string | null
  15. total?: number
  16. counts?: Record<string, number>
  17. is_current?: boolean
  18. default_workdir?: string | null
  19. default_workspace_kind?: string | null
  20. project_id?: string | null
  21. project_name?: string | null
  22. }
  23. export interface KanbanBoardsResponse {
  24. boards: KanbanBoardMeta[]
  25. current: string
  26. }
  27. export interface KanbanTask {
  28. id: string
  29. title: string
  30. body?: string | null
  31. result?: string | null
  32. status: string
  33. assignee?: string | null
  34. tenant?: string | null
  35. priority?: number
  36. created_by?: string | null
  37. created_at?: number
  38. started_at?: number | null
  39. completed_at?: number | null
  40. latest_summary?: string | null
  41. comment_count?: number
  42. model_override?: string | null
  43. provider_override?: string | null
  44. reasoning_effort?: string | null
  45. workspace_kind?: string | null
  46. workspace_path?: string | null
  47. branch_name?: string | null
  48. link_counts?: { parents: number; children: number }
  49. progress?: { done: number; total: number } | null
  50. warnings?: { count: number; highest_severity?: string | null } | null
  51. diagnostics?: KanbanDiagnostic[]
  52. }
  53. export interface KanbanColumn {
  54. name: string
  55. tasks: KanbanTask[]
  56. }
  57. export interface KanbanBoardSnapshot {
  58. columns: KanbanColumn[]
  59. tenants: string[]
  60. assignees: string[]
  61. latest_event_id: number
  62. now: number
  63. }
  64. export interface KanbanProfile {
  65. name: string
  66. is_default: boolean
  67. description?: string
  68. description_auto?: boolean
  69. model?: string
  70. provider?: string
  71. }
  72. export interface KanbanComment {
  73. id: string | number
  74. task_id?: string
  75. author: string
  76. body: string
  77. created_at: number
  78. }
  79. export interface KanbanEvent {
  80. id: number
  81. task_id?: string
  82. run_id?: string | number | null
  83. kind: string
  84. payload?: JsonValue | string | null
  85. created_at: number
  86. }
  87. export interface KanbanRun {
  88. id: string | number
  89. task_id?: string
  90. profile?: string | null
  91. status: string
  92. outcome?: string | null
  93. summary?: string | null
  94. error?: string | null
  95. metadata?: JsonValue | string | null
  96. worker_pid?: number | null
  97. started_at?: number | null
  98. ended_at?: number | null
  99. last_heartbeat_at?: number | null
  100. }
  101. export interface KanbanDiagnostic {
  102. kind: string
  103. severity: 'warning' | 'error' | 'critical' | string
  104. title: string
  105. detail: string
  106. count?: number
  107. last_seen_at?: number
  108. }
  109. export interface KanbanTaskDetail {
  110. task: KanbanTask
  111. comments: KanbanComment[]
  112. events: KanbanEvent[]
  113. links: { parents: string[]; children: string[] }
  114. child_results?: Array<{ id: string; title: string; status: string; latest_summary?: string | null; result?: string | null }>
  115. runs: KanbanRun[]
  116. }
  117. export interface CreateKanbanBoardInput {
  118. slug: string
  119. name?: string
  120. description?: string
  121. }
  122. export interface CreateKanbanTaskInput {
  123. title: string
  124. body?: string
  125. assignee?: string
  126. tenant?: string
  127. priority?: number
  128. workspace_kind?: 'scratch' | 'worktree' | 'dir'
  129. workspace_path?: string
  130. parents?: string[]
  131. triage?: boolean
  132. }
  133. export type UpdateKanbanTaskInput = Partial<Pick<KanbanTask,
  134. 'title' | 'body' | 'assignee' | 'priority' | 'result' | 'model_override' | 'provider_override' | 'reasoning_effort'>> & {
  135. status?: string
  136. block_reason?: string
  137. summary?: string
  138. clear_model_override?: boolean
  139. clear_reasoning_effort?: boolean
  140. }
  141. function boardPath(path: string, board: string, query: Record<string, string | number | boolean | undefined> = {}): string {
  142. return apiUrl(`${BASE}${path}`, { ...query, board })
  143. }
  144. export async function getKanbanStatus(): Promise<KanbanStatus> {
  145. return unwrapData(await apiRequest<KanbanStatus>(`${BASE}/status`))
  146. }
  147. export async function listKanbanBoards(): Promise<KanbanBoardsResponse> {
  148. return unwrapData(await apiRequest<KanbanBoardsResponse>(`${BASE}/boards`))
  149. }
  150. export async function createKanbanBoard(input: CreateKanbanBoardInput): Promise<{ board: KanbanBoardMeta; current: string }> {
  151. return unwrapData(await apiRequest(`${BASE}/boards`, {
  152. method: 'POST',
  153. body: { ...input, switch: false },
  154. }))
  155. }
  156. export async function getKanbanBoard(board: string, includeArchived = false): Promise<KanbanBoardSnapshot> {
  157. return unwrapData(await apiRequest<KanbanBoardSnapshot>(boardPath('/board', board, {
  158. include_archived: includeArchived,
  159. })))
  160. }
  161. export async function listKanbanProfiles(): Promise<KanbanProfile[]> {
  162. const payload = unwrapData(await apiRequest<{ profiles: KanbanProfile[] }>(`${BASE}/profiles`))
  163. return Array.isArray(payload.profiles) ? payload.profiles : []
  164. }
  165. export async function getKanbanTask(board: string, id: string): Promise<KanbanTaskDetail> {
  166. return unwrapData(await apiRequest<KanbanTaskDetail>(boardPath(`/tasks/${encodeURIComponent(id)}`, board)))
  167. }
  168. export async function createKanbanTask(board: string, input: CreateKanbanTaskInput): Promise<{ task: KanbanTask | null; warning?: string }> {
  169. return unwrapData(await apiRequest(boardPath('/tasks', board), {
  170. method: 'POST', body: input as unknown as JsonValue,
  171. }))
  172. }
  173. export async function updateKanbanTask(board: string, id: string, input: UpdateKanbanTaskInput): Promise<{ task: KanbanTask | null }> {
  174. return unwrapData(await apiRequest(boardPath(`/tasks/${encodeURIComponent(id)}`, board), {
  175. method: 'PATCH', body: input as unknown as JsonValue,
  176. }))
  177. }
  178. export async function deleteKanbanTask(board: string, id: string): Promise<void> {
  179. await apiRequest(boardPath(`/tasks/${encodeURIComponent(id)}`, board), { method: 'DELETE', body: {} })
  180. }
  181. export async function addKanbanComment(board: string, id: string, comment: { body: string }): Promise<void> {
  182. await apiRequest(boardPath(`/tasks/${encodeURIComponent(id)}/comments`, board), {
  183. method: 'POST', body: comment,
  184. })
  185. }
  186. export async function dispatchKanban(board: string): Promise<unknown> {
  187. return unwrapData(await apiRequest(boardPath('/dispatch', board), { method: 'POST', body: {} }))
  188. }