@cat-factory/app 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/app/app.config.ts +8 -0
  4. package/app/app.vue +11 -0
  5. package/app/assets/css/main.css +100 -0
  6. package/app/components/auth/AuthGate.vue +24 -0
  7. package/app/components/auth/LoginScreen.vue +143 -0
  8. package/app/components/auth/UserMenu.vue +39 -0
  9. package/app/components/board/AddTaskModal.vue +444 -0
  10. package/app/components/board/AgentFailureCard.vue +97 -0
  11. package/app/components/board/AgentStopButton.vue +61 -0
  12. package/app/components/board/BoardCanvas.vue +183 -0
  13. package/app/components/board/ContextPicker.vue +367 -0
  14. package/app/components/board/RecurringPipelineModal.vue +219 -0
  15. package/app/components/board/TaskDependencyEdges.vue +132 -0
  16. package/app/components/board/nodes/AgentChip.vue +59 -0
  17. package/app/components/board/nodes/BlockNode.vue +433 -0
  18. package/app/components/board/nodes/DecisionBadge.vue +27 -0
  19. package/app/components/board/nodes/DraggableTask.vue +48 -0
  20. package/app/components/board/nodes/ModuleFrame.vue +97 -0
  21. package/app/components/board/nodes/TaskCard.vue +359 -0
  22. package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
  23. package/app/components/bootstrap/BootstrapModal.vue +665 -0
  24. package/app/components/clarity/ClarityReviewWindow.vue +611 -0
  25. package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
  26. package/app/components/documents/DocumentImportModal.vue +161 -0
  27. package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
  28. package/app/components/documents/SpawnPreviewModal.vue +161 -0
  29. package/app/components/documents/TaskContextDocs.vue +83 -0
  30. package/app/components/focus/BlockFocusView.vue +171 -0
  31. package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
  32. package/app/components/gates/GateResultView.vue +282 -0
  33. package/app/components/github/AddServiceFromRepoModal.vue +354 -0
  34. package/app/components/github/GitHubConnect.vue +183 -0
  35. package/app/components/github/GitHubOnboarding.vue +45 -0
  36. package/app/components/github/GitHubPanel.vue +584 -0
  37. package/app/components/github/RepoTreeBrowser.vue +171 -0
  38. package/app/components/layout/AccountTeamSettings.vue +237 -0
  39. package/app/components/layout/BoardSwitcher.vue +280 -0
  40. package/app/components/layout/BoardToolbar.vue +156 -0
  41. package/app/components/layout/CommandBar.vue +336 -0
  42. package/app/components/layout/GitHubPatBanner.vue +73 -0
  43. package/app/components/layout/NotificationsInbox.vue +175 -0
  44. package/app/components/layout/SideBar.vue +314 -0
  45. package/app/components/layout/SpendWarningBanner.vue +107 -0
  46. package/app/components/observability/StepMetricsBar.vue +102 -0
  47. package/app/components/palettes/AgentPalette.vue +86 -0
  48. package/app/components/panels/AgentStepDetail.vue +737 -0
  49. package/app/components/panels/DecisionModal.vue +71 -0
  50. package/app/components/panels/InspectorPanel.vue +465 -0
  51. package/app/components/panels/ObservabilityPanel.vue +351 -0
  52. package/app/components/panels/StepMetadataCard.vue +253 -0
  53. package/app/components/panels/StepRestartControl.vue +90 -0
  54. package/app/components/panels/StepResultViewHost.vue +40 -0
  55. package/app/components/panels/StepTestReport.vue +84 -0
  56. package/app/components/panels/inspector/ContainerSummary.vue +74 -0
  57. package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
  58. package/app/components/panels/inspector/ServiceFragments.vue +82 -0
  59. package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
  60. package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
  61. package/app/components/panels/inspector/TaskDependencies.vue +70 -0
  62. package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
  63. package/app/components/panels/inspector/TaskExecution.vue +364 -0
  64. package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
  65. package/app/components/panels/inspector/TaskStructure.vue +96 -0
  66. package/app/components/pipeline/AgentKindIcon.vue +30 -0
  67. package/app/components/pipeline/IterationCapPrompt.vue +70 -0
  68. package/app/components/pipeline/PipelineBuilder.vue +817 -0
  69. package/app/components/pipeline/PipelineProgress.vue +484 -0
  70. package/app/components/providers/ApiKeysSection.vue +273 -0
  71. package/app/components/providers/PersonalCredentialModal.vue +128 -0
  72. package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
  73. package/app/components/providers/VendorCredentialsModal.vue +197 -0
  74. package/app/components/recurring/RecurrenceEditor.vue +124 -0
  75. package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
  76. package/app/components/settings/DatadogPanel.vue +213 -0
  77. package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
  78. package/app/components/settings/MergeThresholdsPanel.vue +378 -0
  79. package/app/components/settings/ModelDefaultsPanel.vue +250 -0
  80. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
  81. package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
  82. package/app/components/slack/SlackPanel.vue +299 -0
  83. package/app/components/tasks/TaskContextIssues.vue +88 -0
  84. package/app/components/tasks/TaskImportModal.vue +207 -0
  85. package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
  86. package/app/components/testing/TestReportWindow.vue +404 -0
  87. package/app/composables/api/accounts.ts +81 -0
  88. package/app/composables/api/auth.ts +45 -0
  89. package/app/composables/api/board.ts +101 -0
  90. package/app/composables/api/bootstrap.ts +62 -0
  91. package/app/composables/api/context.ts +25 -0
  92. package/app/composables/api/documents.ts +74 -0
  93. package/app/composables/api/execution.ts +127 -0
  94. package/app/composables/api/fragments.ts +71 -0
  95. package/app/composables/api/github.ts +131 -0
  96. package/app/composables/api/models.ts +127 -0
  97. package/app/composables/api/notifications.ts +23 -0
  98. package/app/composables/api/presets.ts +29 -0
  99. package/app/composables/api/recurring.ts +68 -0
  100. package/app/composables/api/releaseHealth.ts +43 -0
  101. package/app/composables/api/reviews.ts +146 -0
  102. package/app/composables/api/slack.ts +54 -0
  103. package/app/composables/api/tasks.ts +72 -0
  104. package/app/composables/api/workspaces.ts +36 -0
  105. package/app/composables/useApi.ts +89 -0
  106. package/app/composables/useBlockDrag.ts +90 -0
  107. package/app/composables/useBlockQueries.ts +154 -0
  108. package/app/composables/useBoardFlow.ts +11 -0
  109. package/app/composables/useContextLinking.ts +65 -0
  110. package/app/composables/useDepLabels.ts +26 -0
  111. package/app/composables/useFrameResize.ts +54 -0
  112. package/app/composables/useResultView.ts +48 -0
  113. package/app/composables/useReviewStage.ts +40 -0
  114. package/app/composables/useSemanticZoom.ts +31 -0
  115. package/app/composables/useStepApproval.ts +233 -0
  116. package/app/composables/useStepProse.ts +78 -0
  117. package/app/composables/useStepTimer.ts +63 -0
  118. package/app/composables/useTaskExpansion.ts +92 -0
  119. package/app/composables/useWorkspaceStream.ts +155 -0
  120. package/app/docs/architecture.md +31 -0
  121. package/app/pages/index.vue +141 -0
  122. package/app/stores/accounts.ts +152 -0
  123. package/app/stores/agentConfig.ts +35 -0
  124. package/app/stores/agentRuns.ts +122 -0
  125. package/app/stores/agents.ts +40 -0
  126. package/app/stores/apiKeys.ts +108 -0
  127. package/app/stores/auth.ts +166 -0
  128. package/app/stores/board.spec.ts +205 -0
  129. package/app/stores/board.ts +286 -0
  130. package/app/stores/bootstrap.ts +97 -0
  131. package/app/stores/clarity.ts +196 -0
  132. package/app/stores/consensus.ts +60 -0
  133. package/app/stores/documents.ts +176 -0
  134. package/app/stores/execution.ts +273 -0
  135. package/app/stores/fragmentLibrary.ts +147 -0
  136. package/app/stores/fragments.ts +40 -0
  137. package/app/stores/github.ts +305 -0
  138. package/app/stores/localModels.ts +51 -0
  139. package/app/stores/mergePresets.ts +58 -0
  140. package/app/stores/modelDefaults.ts +76 -0
  141. package/app/stores/models.ts +134 -0
  142. package/app/stores/notifications.ts +70 -0
  143. package/app/stores/observability.ts +144 -0
  144. package/app/stores/personalSubscriptions.ts +215 -0
  145. package/app/stores/pipelines.ts +327 -0
  146. package/app/stores/recurringPipelines.ts +112 -0
  147. package/app/stores/releaseHealth.ts +75 -0
  148. package/app/stores/requirements.spec.ts +94 -0
  149. package/app/stores/requirements.ts +208 -0
  150. package/app/stores/serviceFragmentDefaults.ts +29 -0
  151. package/app/stores/services.ts +87 -0
  152. package/app/stores/slack.ts +142 -0
  153. package/app/stores/taskExpansion.ts +36 -0
  154. package/app/stores/tasks.spec.ts +71 -0
  155. package/app/stores/tasks.ts +176 -0
  156. package/app/stores/tracker.ts +27 -0
  157. package/app/stores/ui.ts +434 -0
  158. package/app/stores/vendorCredentials.ts +54 -0
  159. package/app/stores/workspace.ts +215 -0
  160. package/app/stores/workspaceSettings.ts +36 -0
  161. package/app/types/accounts.ts +77 -0
  162. package/app/types/bootstrap.ts +83 -0
  163. package/app/types/clarity.ts +59 -0
  164. package/app/types/consensus.ts +91 -0
  165. package/app/types/documents.ts +104 -0
  166. package/app/types/domain.ts +495 -0
  167. package/app/types/execution.ts +383 -0
  168. package/app/types/fragments.ts +72 -0
  169. package/app/types/github.ts +173 -0
  170. package/app/types/localModels.ts +73 -0
  171. package/app/types/merge.ts +71 -0
  172. package/app/types/models.ts +157 -0
  173. package/app/types/notifications.ts +74 -0
  174. package/app/types/recurring.ts +69 -0
  175. package/app/types/releaseHealth.ts +31 -0
  176. package/app/types/requirements.ts +61 -0
  177. package/app/types/services.ts +27 -0
  178. package/app/types/slack.ts +57 -0
  179. package/app/types/tasks.ts +82 -0
  180. package/app/types/tracker.ts +18 -0
  181. package/app/utils/agentOutput.spec.ts +128 -0
  182. package/app/utils/agentOutput.ts +173 -0
  183. package/app/utils/catalog.spec.ts +112 -0
  184. package/app/utils/catalog.ts +455 -0
  185. package/app/utils/dnd.ts +29 -0
  186. package/app/utils/observability.ts +52 -0
  187. package/app/utils/pipelineRender.ts +151 -0
  188. package/nuxt.config.ts +55 -0
  189. package/package.json +45 -0
@@ -0,0 +1,62 @@
1
+ import type {
2
+ AgentRunKind,
3
+ BootstrapJob,
4
+ BootstrapRepoInput,
5
+ CreateReferenceArchitectureInput,
6
+ ExecutionInstance,
7
+ ReferenceArchitecture,
8
+ UpdateReferenceArchitectureInput,
9
+ } from '~/types/domain'
10
+ import type { ApiContext } from './context'
11
+
12
+ /**
13
+ * Repo bootstrap (reference architectures + bootstrap jobs) and the unified
14
+ * agent-run failure/retry/stop surface shared by bootstrap + execution runs.
15
+ */
16
+ export function bootstrapApi({ http, ws, pwHeaders }: ApiContext) {
17
+ return {
18
+ // ---- repo bootstrap ---------------------------------------------------
19
+ listReferenceArchitectures: (workspaceId: string) =>
20
+ http<ReferenceArchitecture[]>(`${ws(workspaceId)}/bootstrap/reference-architectures`),
21
+
22
+ createReferenceArchitecture: (workspaceId: string, body: CreateReferenceArchitectureInput) =>
23
+ http<ReferenceArchitecture>(`${ws(workspaceId)}/bootstrap/reference-architectures`, {
24
+ method: 'POST',
25
+ body,
26
+ }),
27
+
28
+ updateReferenceArchitecture: (
29
+ workspaceId: string,
30
+ id: string,
31
+ body: UpdateReferenceArchitectureInput,
32
+ ) =>
33
+ http<ReferenceArchitecture>(`${ws(workspaceId)}/bootstrap/reference-architectures/${id}`, {
34
+ method: 'PATCH',
35
+ body,
36
+ }),
37
+
38
+ deleteReferenceArchitecture: (workspaceId: string, id: string) =>
39
+ http(`${ws(workspaceId)}/bootstrap/reference-architectures/${id}`, { method: 'DELETE' }),
40
+
41
+ bootstrapRepo: (workspaceId: string, body: BootstrapRepoInput) =>
42
+ http<BootstrapJob>(`${ws(workspaceId)}/bootstrap/jobs`, { method: 'POST', body }),
43
+
44
+ // ---- agent runs (unified failure + retry) -----------------------------
45
+ // Retry any failed run (bootstrap or execution); the backend resolves the
46
+ // kind from the unified `agent_runs` table and re-drives the right flow.
47
+ retryAgentRun: (workspaceId: string, runId: string, password?: string) =>
48
+ http<{ kind: AgentRunKind; run: ExecutionInstance | BootstrapJob }>(
49
+ `${ws(workspaceId)}/agent-runs/${encodeURIComponent(runId)}/retry`,
50
+ { method: 'POST', headers: pwHeaders(password) },
51
+ ),
52
+
53
+ // Explicitly stop a running run (bootstrap or execution): the backend kills the
54
+ // per-run container and tears down the durable driver, then marks the run
55
+ // terminally cancelled so the board stops showing it as running.
56
+ stopAgentRun: (workspaceId: string, runId: string) =>
57
+ http<{ kind: AgentRunKind; run: ExecutionInstance | BootstrapJob }>(
58
+ `${ws(workspaceId)}/agent-runs/${encodeURIComponent(runId)}/stop`,
59
+ { method: 'POST' },
60
+ ),
61
+ }
62
+ }
@@ -0,0 +1,25 @@
1
+ import type { FragmentOwnerKind } from '~/types/domain'
2
+
3
+ /** The authed `$fetch` instance type — Nuxt's augmented client, as returned by `$fetch.create`. */
4
+ export type ApiHttp = ReturnType<typeof $fetch.create>
5
+
6
+ /**
7
+ * Shared plumbing handed to every grouped API module. `useApi()` builds one of
8
+ * these (the authed `$fetch` client + the path/header helpers) and passes it to
9
+ * each `*Api(ctx)` factory; the factories return the endpoint methods that
10
+ * `useApi()` spreads into its single flat client object. Splitting the client
11
+ * this way keeps call sites unchanged (`useApi().someMethod(...)`) while the
12
+ * ~100 endpoints live in cohesive per-domain files.
13
+ */
14
+ export interface ApiContext {
15
+ /** The authed `$fetch` instance (bearer token + 401 handling pre-wired). */
16
+ http: ApiHttp
17
+ /** `/workspaces/:id` path prefix (id encoded). */
18
+ ws: (workspaceId: string) => string
19
+ /** Prompt-fragment library prefix, resolved from the owner scope (ADR 0006 §8). */
20
+ scope: (kind: FragmentOwnerKind, id: string) => string
21
+ /** The ambient personal-unlock password header (individual-usage vendors). */
22
+ pwHeaders: (password?: string) => Record<string, string> | undefined
23
+ }
24
+
25
+ export type Position = { x: number; y: number }
@@ -0,0 +1,74 @@
1
+ import type {
2
+ DocumentBoardPlan,
3
+ DocumentConnection,
4
+ DocumentSearchResult,
5
+ DocumentSourceDescriptor,
6
+ DocumentSourceKind,
7
+ SourceDocument,
8
+ SpawnResult,
9
+ } from '~/types/domain'
10
+ import type { ApiContext } from './context'
11
+
12
+ /** Document sources (Confluence, Notion, …): connect, import, search, board-spawn. */
13
+ export function documentsApi({ http, ws }: ApiContext) {
14
+ return {
15
+ // ---- document sources (Confluence, Notion, …) -------------------------
16
+ // The configured sources + their connect/import metadata. A 503 means the
17
+ // integration is off (the store hides its UI on any error here).
18
+ listDocumentSources: (workspaceId: string) =>
19
+ http<{ sources: DocumentSourceDescriptor[] }>(`${ws(workspaceId)}/document-sources`),
20
+
21
+ listDocumentConnections: (workspaceId: string) =>
22
+ http<{ connections: DocumentConnection[] }>(
23
+ `${ws(workspaceId)}/document-sources/connections`,
24
+ ),
25
+
26
+ connectDocumentSource: (
27
+ workspaceId: string,
28
+ source: DocumentSourceKind,
29
+ credentials: Record<string, string>,
30
+ ) =>
31
+ http<DocumentConnection>(`${ws(workspaceId)}/document-sources/${source}/connect`, {
32
+ method: 'POST',
33
+ body: { credentials },
34
+ }),
35
+
36
+ disconnectDocumentSource: (workspaceId: string, source: DocumentSourceKind) =>
37
+ http(`${ws(workspaceId)}/document-sources/${source}/connection`, { method: 'DELETE' }),
38
+
39
+ listDocuments: (workspaceId: string) => http<SourceDocument[]>(`${ws(workspaceId)}/documents`),
40
+
41
+ importDocument: (workspaceId: string, source: DocumentSourceKind, body: { ref: string }) =>
42
+ http<SourceDocument>(`${ws(workspaceId)}/document-sources/${source}/import`, {
43
+ method: 'POST',
44
+ body,
45
+ }),
46
+
47
+ searchDocumentSource: (workspaceId: string, source: DocumentSourceKind, query: string) =>
48
+ http<{ results: DocumentSearchResult[] }>(
49
+ `${ws(workspaceId)}/document-sources/${source}/search`,
50
+ { method: 'POST', body: { query } },
51
+ ),
52
+
53
+ planDocument: (workspaceId: string, source: DocumentSourceKind, externalId: string) =>
54
+ http<DocumentBoardPlan>(`${ws(workspaceId)}/document-sources/${source}/plan`, {
55
+ method: 'POST',
56
+ body: { externalId },
57
+ }),
58
+
59
+ spawnDocument: (
60
+ workspaceId: string,
61
+ source: DocumentSourceKind,
62
+ body: { externalId: string; frameId?: string },
63
+ ) =>
64
+ http<{ plan: DocumentBoardPlan; result: SpawnResult }>(
65
+ `${ws(workspaceId)}/document-sources/${source}/spawn`,
66
+ { method: 'POST', body },
67
+ ),
68
+
69
+ linkDocument: (
70
+ workspaceId: string,
71
+ body: { source: DocumentSourceKind; externalId: string; blockId: string },
72
+ ) => http<SourceDocument>(`${ws(workspaceId)}/documents/link`, { method: 'POST', body }),
73
+ }
74
+ }
@@ -0,0 +1,127 @@
1
+ import type { Block, ExecutionInstance } from '~/types/domain'
2
+ import type {
3
+ IterationCapChoice,
4
+ LlmCallMetric,
5
+ LlmMetricsExport,
6
+ ReviewComment,
7
+ } from '~/types/execution'
8
+ import type { ApiContext } from './context'
9
+
10
+ /** Run lifecycle (start/cancel/decisions/approvals/restart) + LLM metrics + spend. */
11
+ export function executionApi({ http, ws, pwHeaders }: ApiContext) {
12
+ return {
13
+ // ---- executions -------------------------------------------------------
14
+ startExecution: (
15
+ workspaceId: string,
16
+ blockId: string,
17
+ body: { pipelineId: string },
18
+ password?: string,
19
+ ) =>
20
+ http<ExecutionInstance>(`${ws(workspaceId)}/blocks/${blockId}/executions`, {
21
+ method: 'POST',
22
+ body,
23
+ headers: pwHeaders(password),
24
+ }),
25
+
26
+ cancelExecution: (workspaceId: string, blockId: string) =>
27
+ http<Block>(`${ws(workspaceId)}/blocks/${blockId}/executions`, { method: 'DELETE' }),
28
+
29
+ mergeBlock: (workspaceId: string, blockId: string) =>
30
+ http<Block>(`${ws(workspaceId)}/blocks/${blockId}/merge`, { method: 'POST' }),
31
+
32
+ resolveDecision: (
33
+ workspaceId: string,
34
+ executionId: string,
35
+ decisionId: string,
36
+ body: { choice: string },
37
+ password?: string,
38
+ ) =>
39
+ http<ExecutionInstance>(
40
+ `${ws(workspaceId)}/executions/${executionId}/decisions/${decisionId}`,
41
+ { method: 'POST', body, headers: pwHeaders(password) },
42
+ ),
43
+
44
+ approveStep: (
45
+ workspaceId: string,
46
+ executionId: string,
47
+ approvalId: string,
48
+ body: { proposal?: string },
49
+ password?: string,
50
+ ) =>
51
+ http<ExecutionInstance>(
52
+ `${ws(workspaceId)}/executions/${executionId}/steps/${approvalId}/approve`,
53
+ { method: 'POST', body, headers: pwHeaders(password) },
54
+ ),
55
+
56
+ requestStepChanges: (
57
+ workspaceId: string,
58
+ executionId: string,
59
+ approvalId: string,
60
+ body: { feedback?: string; comments?: ReviewComment[] },
61
+ password?: string,
62
+ ) =>
63
+ http<ExecutionInstance>(
64
+ `${ws(workspaceId)}/executions/${executionId}/steps/${approvalId}/request-changes`,
65
+ { method: 'POST', body, headers: pwHeaders(password) },
66
+ ),
67
+
68
+ rejectStep: (
69
+ workspaceId: string,
70
+ executionId: string,
71
+ approvalId: string,
72
+ body: { reason?: string },
73
+ ) =>
74
+ http<ExecutionInstance>(
75
+ `${ws(workspaceId)}/executions/${executionId}/steps/${approvalId}/reject`,
76
+ { method: 'POST', body },
77
+ ),
78
+
79
+ // Resolve a companion step parked at its rework cap: one more round / proceed /
80
+ // stop & reset (the companion analogue of resolveRequirementsExceeded).
81
+ resolveCompanionExceeded: (
82
+ workspaceId: string,
83
+ executionId: string,
84
+ approvalId: string,
85
+ body: { choice: IterationCapChoice },
86
+ password?: string,
87
+ ) =>
88
+ http<ExecutionInstance>(
89
+ `${ws(workspaceId)}/executions/${executionId}/steps/${approvalId}/resolve-exceeded`,
90
+ { method: 'POST', body, headers: pwHeaders(password) },
91
+ ),
92
+
93
+ // Restart a run from a chosen step: re-run from `fromStepIndex` onward (resetting
94
+ // that step + later steps' iteration counters) while keeping the earlier steps'
95
+ // outputs as handoff context. Like retry it may need the initiator's personal
96
+ // password for an individual-usage (Claude) block, prompted + retried on a 428.
97
+ restartFromStep: (
98
+ workspaceId: string,
99
+ executionId: string,
100
+ fromStepIndex: number,
101
+ password?: string,
102
+ ) =>
103
+ http<ExecutionInstance>(`${ws(workspaceId)}/executions/${executionId}/restart`, {
104
+ method: 'POST',
105
+ body: { fromStepIndex },
106
+ headers: pwHeaders(password),
107
+ }),
108
+
109
+ // ---- LLM observability (per-run model-call metrics) -------------------
110
+ // The full per-call detail behind the board's step rollups. Empty when the
111
+ // observability sink is not wired.
112
+ getLlmMetrics: (workspaceId: string, executionId: string) =>
113
+ http<{ executionId: string; calls: LlmCallMetric[] }>(
114
+ `${ws(workspaceId)}/executions/${encodeURIComponent(executionId)}/llm-metrics`,
115
+ ),
116
+
117
+ // The LLM-friendly export bundle (totals + per-agent insights + every call).
118
+ exportLlmMetrics: (workspaceId: string, executionId: string) =>
119
+ http<LlmMetricsExport>(
120
+ `${ws(workspaceId)}/executions/${encodeURIComponent(executionId)}/llm-metrics/export`,
121
+ ),
122
+
123
+ // ---- spend safeguard --------------------------------------------------
124
+ resumeSpend: (workspaceId: string) =>
125
+ http<ExecutionInstance[]>(`${ws(workspaceId)}/spend/resume`, { method: 'POST' }),
126
+ }
127
+ }
@@ -0,0 +1,71 @@
1
+ import type {
2
+ CreatePromptFragmentInput,
3
+ FragmentOwnerKind,
4
+ FragmentSource,
5
+ FragmentSourceStatus,
6
+ FragmentSyncResult,
7
+ LinkFragmentSourceInput,
8
+ PromptFragment,
9
+ ResolvedFragment,
10
+ UpdatePromptFragmentInput,
11
+ } from '~/types/domain'
12
+ import type { ApiContext } from './context'
13
+
14
+ /** Best-practice prompt-fragment catalog + the managed, tenant-scoped library. */
15
+ export function fragmentsApi({ http, ws, scope }: ApiContext) {
16
+ return {
17
+ // ---- prompt fragments (best-practice catalog) -------------------------
18
+ getPromptFragments: () => http<PromptFragment[]>('/prompt-fragments'),
19
+
20
+ // ---- prompt-fragment library (managed, tenant-scoped; ADR 0006) -------
21
+ // The merged catalog an agent actually sees for a board (builtin∪account∪ws).
22
+ getResolvedFragments: (workspaceId: string) =>
23
+ http<ResolvedFragment[]>(`${ws(workspaceId)}/prompt-fragments/resolved`),
24
+
25
+ // Per-tier management (scope = account or workspace).
26
+ listFragments: (kind: FragmentOwnerKind, id: string) =>
27
+ http<PromptFragment[]>(`${scope(kind, id)}/prompt-fragments`),
28
+
29
+ createFragment: (kind: FragmentOwnerKind, id: string, body: CreatePromptFragmentInput) =>
30
+ http<PromptFragment>(`${scope(kind, id)}/prompt-fragments`, { method: 'POST', body }),
31
+
32
+ updateFragment: (
33
+ kind: FragmentOwnerKind,
34
+ id: string,
35
+ fragmentId: string,
36
+ body: UpdatePromptFragmentInput,
37
+ ) =>
38
+ http<PromptFragment>(
39
+ `${scope(kind, id)}/prompt-fragments/${encodeURIComponent(fragmentId)}`,
40
+ { method: 'PATCH', body },
41
+ ),
42
+
43
+ deleteFragment: (kind: FragmentOwnerKind, id: string, fragmentId: string) =>
44
+ http(`${scope(kind, id)}/prompt-fragments/${encodeURIComponent(fragmentId)}`, {
45
+ method: 'DELETE',
46
+ }),
47
+
48
+ // Repo sources of guideline Markdown.
49
+ listFragmentSources: (kind: FragmentOwnerKind, id: string) =>
50
+ http<FragmentSource[]>(`${scope(kind, id)}/fragment-sources`),
51
+
52
+ linkFragmentSource: (kind: FragmentOwnerKind, id: string, body: LinkFragmentSourceInput) =>
53
+ http<FragmentSource>(`${scope(kind, id)}/fragment-sources`, { method: 'POST', body }),
54
+
55
+ unlinkFragmentSource: (kind: FragmentOwnerKind, id: string, sourceId: string) =>
56
+ http(`${scope(kind, id)}/fragment-sources/${encodeURIComponent(sourceId)}`, {
57
+ method: 'DELETE',
58
+ }),
59
+
60
+ fragmentSourceStatus: (kind: FragmentOwnerKind, id: string, sourceId: string) =>
61
+ http<FragmentSourceStatus>(
62
+ `${scope(kind, id)}/fragment-sources/${encodeURIComponent(sourceId)}/status`,
63
+ ),
64
+
65
+ syncFragmentSource: (kind: FragmentOwnerKind, id: string, sourceId: string) =>
66
+ http<FragmentSyncResult>(
67
+ `${scope(kind, id)}/fragment-sources/${encodeURIComponent(sourceId)}/sync`,
68
+ { method: 'POST' },
69
+ ),
70
+ }
71
+ }
@@ -0,0 +1,131 @@
1
+ import type {
2
+ CommitFilesInput,
3
+ CreateBranchInput,
4
+ CreatedRepo,
5
+ CreateRepoRequest,
6
+ GitHubAvailableRepo,
7
+ GitHubBranch,
8
+ GitHubConnection,
9
+ GitHubInstallationOption,
10
+ GitHubIssue,
11
+ GitHubPullRequest,
12
+ GitHubRepo,
13
+ MergePullRequestInput,
14
+ OpenPullRequestInput,
15
+ RepoTreeEntry,
16
+ ResyncRequest,
17
+ } from '~/types/domain'
18
+ import type { ApiContext } from './context'
19
+
20
+ /**
21
+ * GitHub integration: connection management, the D1-served projection reads
22
+ * (fast, rate-limit-free) and the repo writes (branches/commits/PRs/merges).
23
+ */
24
+ export function githubApi({ http, ws }: ApiContext) {
25
+ return {
26
+ // ---- github integration ----------------------------------------------
27
+ // Connection management, projection reads (served from D1 — fast and
28
+ // rate-limit-free) and repo writes. A 503 from `getGitHubConnection` means
29
+ // the integration is off (the store hides its UI on any error there).
30
+ getGitHubInstallUrl: (workspaceId: string) =>
31
+ http<{ url: string }>(`${ws(workspaceId)}/github/install-url`),
32
+
33
+ getGitHubConnection: (workspaceId: string) =>
34
+ http<{ connection: GitHubConnection | null }>(`${ws(workspaceId)}/github/connection`),
35
+
36
+ listGitHubInstallations: (workspaceId: string) =>
37
+ http<{ installations: GitHubInstallationOption[] }>(
38
+ `${ws(workspaceId)}/github/installations`,
39
+ ),
40
+
41
+ connectGitHub: (workspaceId: string, installationId: number) =>
42
+ http<GitHubConnection>(`${ws(workspaceId)}/github/connect`, {
43
+ method: 'POST',
44
+ body: { installationId },
45
+ }),
46
+
47
+ disconnectGitHub: (workspaceId: string) =>
48
+ http(`${ws(workspaceId)}/github/connection`, { method: 'DELETE' }),
49
+
50
+ resyncGitHub: (workspaceId: string, body: ResyncRequest = {}) =>
51
+ http<{ status: string }>(`${ws(workspaceId)}/github/resync`, { method: 'POST', body }),
52
+
53
+ listGitHubRepos: (workspaceId: string) => http<GitHubRepo[]>(`${ws(workspaceId)}/github/repos`),
54
+
55
+ // Programmatic repo creation (privileged App tier). Only called when the
56
+ // connection reports `canCreateRepos`; otherwise the UI opens GitHub directly.
57
+ createGitHubRepo: (workspaceId: string, body: CreateRepoRequest) =>
58
+ http<CreatedRepo>(`${ws(workspaceId)}/github/repos`, { method: 'POST', body }),
59
+
60
+ // Repos the connected installation can access, annotated with whether this
61
+ // workspace links each (drives the per-workspace repo picker).
62
+ listGitHubAvailableRepos: (workspaceId: string) =>
63
+ http<GitHubAvailableRepo[]>(`${ws(workspaceId)}/github/available-repos`),
64
+
65
+ // Set the exact set of repos this workspace links.
66
+ setGitHubLinkedRepos: (workspaceId: string, repoGithubIds: number[]) =>
67
+ http<GitHubRepo[]>(`${ws(workspaceId)}/github/repos`, {
68
+ method: 'PUT',
69
+ body: { repoGithubIds },
70
+ }),
71
+
72
+ // Browse one level of a (monorepo) repo's tree to pin a service to a subdirectory.
73
+ listGitHubRepoTree: (workspaceId: string, repoGithubId: number, path = '') =>
74
+ http<RepoTreeEntry[]>(`${ws(workspaceId)}/github/repos/${repoGithubId}/tree`, {
75
+ query: { path },
76
+ }),
77
+
78
+ listGitHubBranches: (workspaceId: string, repoGithubId: number) =>
79
+ http<GitHubBranch[]>(`${ws(workspaceId)}/github/repos/${repoGithubId}/branches`),
80
+
81
+ listGitHubPullRequests: (workspaceId: string) =>
82
+ http<GitHubPullRequest[]>(`${ws(workspaceId)}/github/pulls`),
83
+
84
+ listGitHubIssues: (workspaceId: string) =>
85
+ http<GitHubIssue[]>(`${ws(workspaceId)}/github/issues`),
86
+
87
+ createGitHubBranch: (workspaceId: string, repoGithubId: number, body: CreateBranchInput) =>
88
+ http<GitHubBranch>(`${ws(workspaceId)}/github/repos/${repoGithubId}/branches`, {
89
+ method: 'POST',
90
+ body,
91
+ }),
92
+
93
+ commitGitHubFiles: (workspaceId: string, repoGithubId: number, body: CommitFilesInput) =>
94
+ http<{ sha: string }>(`${ws(workspaceId)}/github/repos/${repoGithubId}/commits`, {
95
+ method: 'POST',
96
+ body,
97
+ }),
98
+
99
+ openGitHubPullRequest: (
100
+ workspaceId: string,
101
+ repoGithubId: number,
102
+ body: OpenPullRequestInput,
103
+ ) =>
104
+ http<GitHubPullRequest>(`${ws(workspaceId)}/github/repos/${repoGithubId}/pulls`, {
105
+ method: 'POST',
106
+ body,
107
+ }),
108
+
109
+ mergeGitHubPullRequest: (
110
+ workspaceId: string,
111
+ repoGithubId: number,
112
+ number: number,
113
+ body: MergePullRequestInput = {},
114
+ ) =>
115
+ http(`${ws(workspaceId)}/github/repos/${repoGithubId}/pulls/${number}/merge`, {
116
+ method: 'PUT',
117
+ body,
118
+ }),
119
+
120
+ commentGitHubIssue: (
121
+ workspaceId: string,
122
+ repoGithubId: number,
123
+ number: number,
124
+ bodyText: string,
125
+ ) =>
126
+ http(`${ws(workspaceId)}/github/repos/${repoGithubId}/issues/${number}/comments`, {
127
+ method: 'POST',
128
+ body: { body: bodyText },
129
+ }),
130
+ }
131
+ }
@@ -0,0 +1,127 @@
1
+ import type {
2
+ AddApiKeyInput,
3
+ ApiKey,
4
+ ModelDefaults,
5
+ ModelOption,
6
+ PersonalSubscriptionStatus,
7
+ ServiceFragmentDefaults,
8
+ StorePersonalSubscriptionInput,
9
+ SubscriptionVendor,
10
+ VendorCredential,
11
+ } from '~/types/domain'
12
+ import type {
13
+ LocalModelEndpoint,
14
+ LocalModelEndpointTestResult,
15
+ LocalRunner,
16
+ TestLocalModelEndpointInput,
17
+ UpsertLocalModelEndpointInput,
18
+ } from '~/types/localModels'
19
+ import type { ApiContext } from './context'
20
+
21
+ /**
22
+ * Model catalog + the credential pools that gate selectability (direct-provider
23
+ * API keys, vendor subscription tokens, per-user personal subscriptions + local
24
+ * runners) + the per-workspace routing/selection defaults.
25
+ */
26
+ export function modelsApi({ http, ws }: ApiContext) {
27
+ return {
28
+ // ---- model picker catalog (effective per-deployment flavours) ---------
29
+ getModels: () => http<ModelOption[]>('/models'),
30
+ // Per-workspace catalog: selectability reflects the workspace's (+ account's +
31
+ // caller's) configured API keys and subscription tokens (`available` flag).
32
+ getWorkspaceModels: (workspaceId: string) => http<ModelOption[]>(`${ws(workspaceId)}/models`),
33
+
34
+ // ---- direct-provider API keys (the DB-backed pool) --------------------
35
+ // Onboarded via UI, stored encrypted, pooled + rotated. Scoped to a workspace,
36
+ // its owning account, or the signed-in user. Keys are write-only (never returned).
37
+ listWorkspaceApiKeys: (workspaceId: string) =>
38
+ http<{ keys: ApiKey[] }>(`${ws(workspaceId)}/api-keys`),
39
+ addWorkspaceApiKey: (workspaceId: string, body: AddApiKeyInput) =>
40
+ http<ApiKey>(`${ws(workspaceId)}/api-keys`, { method: 'POST', body }),
41
+ removeWorkspaceApiKey: (workspaceId: string, id: string) =>
42
+ http(`${ws(workspaceId)}/api-keys/${encodeURIComponent(id)}`, { method: 'DELETE' }),
43
+ listMyApiKeys: () => http<{ keys: ApiKey[] }>('/me/api-keys'),
44
+ addMyApiKey: (body: AddApiKeyInput) => http<ApiKey>('/me/api-keys', { method: 'POST', body }),
45
+ removeMyApiKey: (id: string) =>
46
+ http(`/me/api-keys/${encodeURIComponent(id)}`, { method: 'DELETE' }),
47
+ // Account-scoped keys (shared by every workspace in the account); admin-only.
48
+ listAccountApiKeys: (accountId: string) =>
49
+ http<{ keys: ApiKey[] }>(`/accounts/${encodeURIComponent(accountId)}/api-keys`),
50
+ addAccountApiKey: (accountId: string, body: AddApiKeyInput) =>
51
+ http<ApiKey>(`/accounts/${encodeURIComponent(accountId)}/api-keys`, { method: 'POST', body }),
52
+ removeAccountApiKey: (accountId: string, id: string) =>
53
+ http(`/accounts/${encodeURIComponent(accountId)}/api-keys/${encodeURIComponent(id)}`, {
54
+ method: 'DELETE',
55
+ }),
56
+
57
+ // ---- LLM vendor subscription credentials (the token pool) -------------
58
+ listVendorCredentials: (workspaceId: string) =>
59
+ http<{ credentials: VendorCredential[] }>(`${ws(workspaceId)}/vendor-credentials`),
60
+ addVendorCredential: (
61
+ workspaceId: string,
62
+ body: { vendor: SubscriptionVendor; label: string; token: string },
63
+ ) => http<VendorCredential>(`${ws(workspaceId)}/vendor-credentials`, { method: 'POST', body }),
64
+ removeVendorCredential: (workspaceId: string, id: string) =>
65
+ http(`${ws(workspaceId)}/vendor-credentials/${encodeURIComponent(id)}`, { method: 'DELETE' }),
66
+
67
+ // ---- personal (individual-usage) subscriptions (per-user, e.g. Claude) ----
68
+ // Stored per signed-in user, double-encrypted under their personal password.
69
+ // Metadata only is returned (never the token). User-scoped (no workspace).
70
+ listPersonalSubscriptions: () =>
71
+ http<{ subscriptions: PersonalSubscriptionStatus[] }>('/personal-subscriptions'),
72
+
73
+ storePersonalSubscription: (body: StorePersonalSubscriptionInput) =>
74
+ http<PersonalSubscriptionStatus>('/personal-subscriptions', { method: 'POST', body }),
75
+
76
+ removePersonalSubscription: (vendor: SubscriptionVendor) =>
77
+ http(`/personal-subscriptions/${encodeURIComponent(vendor)}`, { method: 'DELETE' }),
78
+
79
+ // ---- local model runners (per-user, e.g. Ollama / LM Studio) ----------
80
+ // A developer's own-machine LLM endpoints, stored per signed-in user (the API
81
+ // key is write-only, never returned). User-scoped (no workspace). The enabled
82
+ // models then surface automatically in the per-workspace `/models` catalog.
83
+ listLocalModelEndpoints: () =>
84
+ http<{ endpoints: LocalModelEndpoint[] }>('/local-model-endpoints'),
85
+
86
+ upsertLocalModelEndpoint: (provider: LocalRunner, body: UpsertLocalModelEndpointInput) =>
87
+ http<LocalModelEndpoint>(`/local-model-endpoints/${encodeURIComponent(provider)}`, {
88
+ method: 'PUT',
89
+ body,
90
+ }),
91
+
92
+ deleteLocalModelEndpoint: (provider: LocalRunner) =>
93
+ http(`/local-model-endpoints/${encodeURIComponent(provider)}`, { method: 'DELETE' }),
94
+
95
+ // Probe a runner endpoint for reachability + the models it currently serves
96
+ // (no persistence — drives the "Test connection" model multi-select).
97
+ testLocalModelEndpoint: (body: TestLocalModelEndpointInput) =>
98
+ http<LocalModelEndpointTestResult>('/local-model-endpoints/test', {
99
+ method: 'POST',
100
+ body,
101
+ }),
102
+
103
+ // ---- per-agent-kind default models (workspace routing overrides) ------
104
+ // The workspace's map of agentKind → model id; a kind absent from the map
105
+ // falls back to the deployment's env routing. `setModelDefaults` replaces the
106
+ // whole map (the settings panel sends the full set on every change).
107
+ getModelDefaults: (workspaceId: string) =>
108
+ http<ModelDefaults>(`${ws(workspaceId)}/model-defaults`),
109
+
110
+ setModelDefaults: (workspaceId: string, defaults: Record<string, string>) =>
111
+ http<ModelDefaults>(`${ws(workspaceId)}/model-defaults`, {
112
+ method: 'PUT',
113
+ body: { defaults },
114
+ }),
115
+
116
+ // The workspace's default service-fragment selection (the fragment ids new
117
+ // services inherit). `setServiceFragmentDefaults` replaces the whole list.
118
+ getServiceFragmentDefaults: (workspaceId: string) =>
119
+ http<ServiceFragmentDefaults>(`${ws(workspaceId)}/service-fragment-defaults`),
120
+
121
+ setServiceFragmentDefaults: (workspaceId: string, fragmentIds: string[]) =>
122
+ http<ServiceFragmentDefaults>(`${ws(workspaceId)}/service-fragment-defaults`, {
123
+ method: 'PUT',
124
+ body: { fragmentIds },
125
+ }),
126
+ }
127
+ }
@@ -0,0 +1,23 @@
1
+ import type { Notification } from '~/types/notifications'
2
+ import type { ApiContext } from './context'
3
+
4
+ /** The human-actionable notification inbox (act / dismiss). */
5
+ export function notificationsApi({ http, ws }: ApiContext) {
6
+ return {
7
+ // ---- notifications (human-actionable board items) ---------------------
8
+ listNotifications: (workspaceId: string) =>
9
+ http<Notification[]>(`${ws(workspaceId)}/notifications`),
10
+
11
+ // Act on a notification (merge the PR / confirm / retry), then resolve it.
12
+ actNotification: (workspaceId: string, id: string) =>
13
+ http<Notification>(`${ws(workspaceId)}/notifications/${encodeURIComponent(id)}/act`, {
14
+ method: 'POST',
15
+ }),
16
+
17
+ // Dismiss a notification without acting.
18
+ dismissNotification: (workspaceId: string, id: string) =>
19
+ http<Notification>(`${ws(workspaceId)}/notifications/${encodeURIComponent(id)}/dismiss`, {
20
+ method: 'POST',
21
+ }),
22
+ }
23
+ }
@@ -0,0 +1,29 @@
1
+ import type {
2
+ CreateMergePresetInput,
3
+ MergeThresholdPreset,
4
+ UpdateMergePresetInput,
5
+ } from '~/types/merge'
6
+ import type { ApiContext } from './context'
7
+
8
+ /** The per-workspace merge-threshold preset library (per-task auto-merge policy). */
9
+ export function presetsApi({ http, ws }: ApiContext) {
10
+ return {
11
+ // ---- merge threshold presets (per-task auto-merge policy library) -----
12
+ listMergePresets: (workspaceId: string) =>
13
+ http<MergeThresholdPreset[]>(`${ws(workspaceId)}/merge-presets`),
14
+
15
+ createMergePreset: (workspaceId: string, body: CreateMergePresetInput) =>
16
+ http<MergeThresholdPreset>(`${ws(workspaceId)}/merge-presets`, { method: 'POST', body }),
17
+
18
+ updateMergePreset: (workspaceId: string, presetId: string, body: UpdateMergePresetInput) =>
19
+ http<MergeThresholdPreset>(
20
+ `${ws(workspaceId)}/merge-presets/${encodeURIComponent(presetId)}`,
21
+ { method: 'PATCH', body },
22
+ ),
23
+
24
+ deleteMergePreset: (workspaceId: string, presetId: string) =>
25
+ http(`${ws(workspaceId)}/merge-presets/${encodeURIComponent(presetId)}`, {
26
+ method: 'DELETE',
27
+ }),
28
+ }
29
+ }