@cat-factory/app 0.36.0 → 0.37.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 (82) hide show
  1. package/app/components/auth/UserMenu.vue +11 -1
  2. package/app/components/brainstorm/BrainstormWindow.vue +2 -1
  3. package/app/components/clarity/ClarityReviewWindow.vue +2 -1
  4. package/app/components/layout/IntegrationBackTitle.vue +12 -7
  5. package/app/components/layout/IntegrationsHub.vue +191 -43
  6. package/app/components/layout/PersonalSetupModal.vue +141 -0
  7. package/app/components/pipeline/PipelineBuilder.vue +1 -1
  8. package/app/components/providers/VendorCredentialsModal.vue +7 -2
  9. package/app/composables/api/accounts.ts +36 -51
  10. package/app/composables/api/auth.ts +20 -19
  11. package/app/composables/api/board.ts +60 -40
  12. package/app/composables/api/bootstrap.ts +25 -22
  13. package/app/composables/api/client.ts +102 -0
  14. package/app/composables/api/context.ts +25 -6
  15. package/app/composables/api/documents.ts +36 -34
  16. package/app/composables/api/execution.ts +65 -48
  17. package/app/composables/api/followUps.ts +26 -26
  18. package/app/composables/api/fragments.ts +47 -34
  19. package/app/composables/api/github.ts +65 -45
  20. package/app/composables/api/humanReview.ts +7 -6
  21. package/app/composables/api/humanTest.ts +15 -11
  22. package/app/composables/api/kaizen.ts +8 -6
  23. package/app/composables/api/localSettings.ts +5 -4
  24. package/app/composables/api/models.ts +58 -51
  25. package/app/composables/api/notifications.ts +13 -7
  26. package/app/composables/api/presets.ts +34 -28
  27. package/app/composables/api/providerConnections.ts +68 -26
  28. package/app/composables/api/recurring.ts +40 -30
  29. package/app/composables/api/releaseHealth.ts +28 -26
  30. package/app/composables/api/reviews.ts +136 -114
  31. package/app/composables/api/sandbox.ts +52 -34
  32. package/app/composables/api/slack.ts +22 -25
  33. package/app/composables/api/spec.ts +3 -3
  34. package/app/composables/api/tasks.ts +42 -41
  35. package/app/composables/api/userSecrets.ts +12 -17
  36. package/app/composables/api/workspaces.ts +21 -15
  37. package/app/composables/useApi.ts +9 -1
  38. package/app/composables/useIntegrationBack.ts +9 -3
  39. package/app/pages/index.vue +2 -0
  40. package/app/stores/auth.ts +2 -1
  41. package/app/stores/board.ts +2 -1
  42. package/app/stores/brainstorm.ts +2 -2
  43. package/app/stores/clarity.ts +6 -2
  44. package/app/stores/execution.ts +3 -2
  45. package/app/stores/github.ts +1 -2
  46. package/app/stores/mergePresets.ts +2 -6
  47. package/app/stores/pipelines.ts +1 -1
  48. package/app/stores/recurringPipelines.ts +2 -7
  49. package/app/stores/sandbox.ts +1 -2
  50. package/app/stores/ui.ts +62 -19
  51. package/app/types/accountSettings.ts +11 -36
  52. package/app/types/accounts.ts +16 -71
  53. package/app/types/bootstrap.ts +13 -75
  54. package/app/types/brainstorm.ts +13 -38
  55. package/app/types/clarity.ts +12 -43
  56. package/app/types/consensus.ts +16 -89
  57. package/app/types/documents.ts +19 -94
  58. package/app/types/domain.ts +54 -586
  59. package/app/types/execution.ts +48 -515
  60. package/app/types/fragments.ts +15 -83
  61. package/app/types/github.ts +25 -161
  62. package/app/types/incidentEnrichment.ts +10 -25
  63. package/app/types/localModels.ts +11 -61
  64. package/app/types/localSettings.ts +9 -26
  65. package/app/types/merge.ts +10 -68
  66. package/app/types/model-presets.ts +7 -28
  67. package/app/types/models.ts +16 -164
  68. package/app/types/notifications.ts +18 -77
  69. package/app/types/openrouter.ts +8 -34
  70. package/app/types/providerConnections.ts +21 -41
  71. package/app/types/provisioningLogs.ts +9 -29
  72. package/app/types/recurring.ts +10 -63
  73. package/app/types/releaseHealth.ts +15 -39
  74. package/app/types/requirements.ts +14 -84
  75. package/app/types/sandbox.ts +45 -161
  76. package/app/types/services.ts +3 -22
  77. package/app/types/slack.ts +10 -47
  78. package/app/types/spec.ts +15 -68
  79. package/app/types/tasks.ts +15 -111
  80. package/app/types/tracker.ts +9 -24
  81. package/app/types/userSecrets.ts +12 -47
  82. package/package.json +9 -2
@@ -1,44 +1,69 @@
1
- import type { ClarityReview, ResolveClarityExceededChoice } from '~/types/clarity'
1
+ import {
2
+ acceptRequirementRecommendationContract,
3
+ getBrainstormContract,
4
+ getClarityReviewContract,
5
+ getConsensusSessionContract,
6
+ getRequirementReviewContract,
7
+ incorporateBrainstormContract,
8
+ incorporateClarityContract,
9
+ incorporateRequirementsContract,
10
+ proceedBrainstormContract,
11
+ proceedClarityContract,
12
+ proceedRequirementsContract,
13
+ reRequestRequirementRecommendationContract,
14
+ reReviewBrainstormContract,
15
+ reReviewClarityContract,
16
+ reReviewRequirementsContract,
17
+ rejectRequirementRecommendationContract,
18
+ replyBrainstormItemContract,
19
+ replyClarityItemContract,
20
+ replyRequirementItemContract,
21
+ requestRequirementRecommendationsContract,
22
+ resolveBrainstormExceededContract,
23
+ resolveClarityExceededContract,
24
+ resolveRequirementsExceededContract,
25
+ updateBrainstormItemStatusContract,
26
+ updateClarityItemStatusContract,
27
+ updateRequirementItemStatusContract,
28
+ } from '@cat-factory/contracts'
2
29
  import type {
3
- BrainstormSession,
4
- BrainstormStage,
5
- ResolveBrainstormExceededChoice,
6
- } from '~/types/brainstorm'
7
- import type { ConsensusSession } from '~/types/consensus'
8
- import type {
9
- RequirementReview,
10
- ResolveRequirementsExceededChoice,
11
- ReviewItemStatus,
12
- } from '~/types/requirements'
30
+ UpdateBrainstormItemStatusInput,
31
+ UpdateClarityItemStatusInput,
32
+ } from '@cat-factory/contracts'
33
+ import type { ResolveClarityExceededChoice } from '~/types/clarity'
34
+ import type { BrainstormStage, ResolveBrainstormExceededChoice } from '~/types/brainstorm'
35
+ import type { ResolveRequirementsExceededChoice, ReviewItemStatus } from '~/types/requirements'
13
36
  import type { ApiContext } from './context'
14
37
 
38
+ // The clarity/brainstorm item-status routes accept a narrower set than the full
39
+ // requirements `ReviewItemStatus` (no `recommend_requested`).
40
+ type ClarityItemStatus = UpdateClarityItemStatusInput['status']
41
+ type BrainstormItemStatus = UpdateBrainstormItemStatusInput['status']
42
+
15
43
  /**
16
44
  * The two iterative gate reviewers (requirements + clarity) and the consensus
17
45
  * session read. Each reviewer follows the same answer → incorporate → re-review
18
46
  * → proceed/resolve-exceeded loop.
19
47
  */
20
- export function reviewsApi({ http, ws }: ApiContext) {
48
+ export function reviewsApi({ send, ws }: ApiContext) {
21
49
  return {
22
50
  // ---- requirements review (stateless reviewer agent) ------------------
23
51
  // The current review for a block (null when none has been run). A 503 means
24
52
  // the feature is unconfigured (the panel hides on any error here).
25
53
  getRequirementReview: (workspaceId: string, blockId: string) =>
26
- http<RequirementReview | null>(
27
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/requirement-review`,
28
- ),
54
+ send(getRequirementReviewContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
29
55
 
30
56
  // The latest consensus session for a block (`{ session: null }` when none / consensus
31
57
  // off). The live transcript also arrives via the `consensus` stream event.
32
58
  getConsensusSession: (workspaceId: string, blockId: string) =>
33
- http<{ session: ConsensusSession | null }>(
34
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/consensus-session`,
35
- ),
59
+ send(getConsensusSessionContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
36
60
 
37
61
  replyRequirementItem: (workspaceId: string, reviewId: string, itemId: string, reply: string) =>
38
- http<RequirementReview>(
39
- `${ws(workspaceId)}/requirement-reviews/${encodeURIComponent(reviewId)}/items/${encodeURIComponent(itemId)}/reply`,
40
- { method: 'POST', body: { reply } },
41
- ),
62
+ send(replyRequirementItemContract, {
63
+ pathPrefix: ws(workspaceId),
64
+ pathParams: { reviewId, itemId },
65
+ body: { reply },
66
+ }),
42
67
 
43
68
  setRequirementItemStatus: (
44
69
  workspaceId: string,
@@ -46,35 +71,31 @@ export function reviewsApi({ http, ws }: ApiContext) {
46
71
  itemId: string,
47
72
  status: ReviewItemStatus,
48
73
  ) =>
49
- http<RequirementReview>(
50
- `${ws(workspaceId)}/requirement-reviews/${encodeURIComponent(reviewId)}/items/${encodeURIComponent(itemId)}`,
51
- { method: 'PATCH', body: { status } },
52
- ),
74
+ send(updateRequirementItemStatusContract, {
75
+ pathPrefix: ws(workspaceId),
76
+ pathParams: { reviewId, itemId },
77
+ body: { status },
78
+ }),
53
79
 
54
80
  // Incorporate the answers ASYNCHRONOUSLY (every finding must be answered or dismissed).
55
81
  // The durable driver folds them and re-reviews in the background. Optional `feedback` is
56
82
  // the "do it differently" lever when redoing a merge. Returns the `incorporating` review
57
83
  // at once; a notification calls the user back only if the re-review needs input.
58
84
  incorporateRequirements: (workspaceId: string, blockId: string, feedback?: string) =>
59
- http<RequirementReview>(
60
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/requirement-review/incorporate`,
61
- { method: 'POST', body: feedback ? { feedback } : {} },
62
- ),
85
+ send(incorporateRequirementsContract, {
86
+ pathPrefix: ws(workspaceId),
87
+ pathParams: { blockId },
88
+ body: feedback ? { feedback } : {},
89
+ }),
63
90
 
64
91
  // Re-review the incorporated document (one more reviewer pass). On convergence the
65
92
  // parked run advances; otherwise the response carries the next cycle / cap state.
66
93
  reReviewRequirements: (workspaceId: string, blockId: string) =>
67
- http<RequirementReview>(
68
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/requirement-review/re-review`,
69
- { method: 'POST' },
70
- ),
94
+ send(reReviewRequirementsContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
71
95
 
72
96
  // Proceed: settle the requirements and advance the parked run (all findings dismissed).
73
97
  proceedRequirements: (workspaceId: string, blockId: string) =>
74
- http<RequirementReview>(
75
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/requirement-review/proceed`,
76
- { method: 'POST' },
77
- ),
98
+ send(proceedRequirementsContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
78
99
 
79
100
  // Resolve a review that hit its iteration cap: extra-round / proceed / stop-reset.
80
101
  resolveRequirementsExceeded: (
@@ -82,10 +103,11 @@ export function reviewsApi({ http, ws }: ApiContext) {
82
103
  blockId: string,
83
104
  choice: ResolveRequirementsExceededChoice,
84
105
  ) =>
85
- http<RequirementReview>(
86
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/requirement-review/resolve-exceeded`,
87
- { method: 'POST', body: { choice } },
88
- ),
106
+ send(resolveRequirementsExceededContract, {
107
+ pathPrefix: ws(workspaceId),
108
+ pathParams: { blockId },
109
+ body: { choice },
110
+ }),
89
111
 
90
112
  // Ask the Requirement Writer to recommend grounded answers for a batch of findings (by
91
113
  // item id). Returns the review with `pending` placeholder recommendations; they fill in
@@ -96,80 +118,77 @@ export function reviewsApi({ http, ws }: ApiContext) {
96
118
  itemIds: string[],
97
119
  note?: string,
98
120
  ) =>
99
- http<RequirementReview | null>(
100
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/requirement-review/recommend`,
101
- { method: 'POST', body: { itemIds, ...(note ? { note } : {}) } },
102
- ),
121
+ send(requestRequirementRecommendationsContract, {
122
+ pathPrefix: ws(workspaceId),
123
+ pathParams: { blockId },
124
+ body: { itemIds, ...(note ? { note } : {}) },
125
+ }),
103
126
 
104
127
  // Accept a recommendation (becomes the finding's answer), reject it, or re-request it
105
128
  // with a "do it differently" note.
106
129
  acceptRecommendation: (workspaceId: string, reviewId: string, recId: string) =>
107
- http<RequirementReview>(
108
- `${ws(workspaceId)}/requirement-reviews/${encodeURIComponent(reviewId)}/recommendations/${encodeURIComponent(recId)}/accept`,
109
- { method: 'POST' },
110
- ),
130
+ send(acceptRequirementRecommendationContract, {
131
+ pathPrefix: ws(workspaceId),
132
+ pathParams: { reviewId, recId },
133
+ }),
111
134
 
112
135
  rejectRecommendation: (workspaceId: string, reviewId: string, recId: string) =>
113
- http<RequirementReview>(
114
- `${ws(workspaceId)}/requirement-reviews/${encodeURIComponent(reviewId)}/recommendations/${encodeURIComponent(recId)}/reject`,
115
- { method: 'POST' },
116
- ),
136
+ send(rejectRequirementRecommendationContract, {
137
+ pathPrefix: ws(workspaceId),
138
+ pathParams: { reviewId, recId },
139
+ }),
117
140
 
118
141
  reRequestRecommendation: (workspaceId: string, reviewId: string, recId: string, note: string) =>
119
- http<RequirementReview>(
120
- `${ws(workspaceId)}/requirement-reviews/${encodeURIComponent(reviewId)}/recommendations/${encodeURIComponent(recId)}/re-request`,
121
- { method: 'POST', body: { note } },
122
- ),
142
+ send(reRequestRequirementRecommendationContract, {
143
+ pathPrefix: ws(workspaceId),
144
+ pathParams: { reviewId, recId },
145
+ body: { note },
146
+ }),
123
147
 
124
148
  // ---- clarity review (bug-report triage reviewer agent) ---------------
125
149
  // The current review for a block (null when none has been run). A 503 means
126
150
  // the feature is unconfigured (the panel hides on any error here).
127
151
  getClarityReview: (workspaceId: string, blockId: string) =>
128
- http<ClarityReview | null>(
129
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/clarity-review`,
130
- ),
152
+ send(getClarityReviewContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
131
153
 
132
154
  replyClarityItem: (workspaceId: string, reviewId: string, itemId: string, reply: string) =>
133
- http<ClarityReview>(
134
- `${ws(workspaceId)}/clarity-reviews/${encodeURIComponent(reviewId)}/items/${encodeURIComponent(itemId)}/reply`,
135
- { method: 'POST', body: { reply } },
136
- ),
155
+ send(replyClarityItemContract, {
156
+ pathPrefix: ws(workspaceId),
157
+ pathParams: { reviewId, itemId },
158
+ body: { reply },
159
+ }),
137
160
 
138
161
  setClarityItemStatus: (
139
162
  workspaceId: string,
140
163
  reviewId: string,
141
164
  itemId: string,
142
- status: ReviewItemStatus,
165
+ status: ClarityItemStatus,
143
166
  ) =>
144
- http<ClarityReview>(
145
- `${ws(workspaceId)}/clarity-reviews/${encodeURIComponent(reviewId)}/items/${encodeURIComponent(itemId)}`,
146
- { method: 'PATCH', body: { status } },
147
- ),
167
+ send(updateClarityItemStatusContract, {
168
+ pathPrefix: ws(workspaceId),
169
+ pathParams: { reviewId, itemId },
170
+ body: { status },
171
+ }),
148
172
 
149
173
  // Incorporate the answers ASYNCHRONOUSLY (every finding must be answered or dismissed).
150
174
  // The durable driver folds them and re-reviews in the background. Optional `feedback` is
151
175
  // the "do it differently" lever when redoing a merge. Returns the `incorporating` review
152
176
  // at once; a notification calls the user back only if the re-review needs input.
153
177
  incorporateClarity: (workspaceId: string, blockId: string, feedback?: string) =>
154
- http<ClarityReview>(
155
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/clarity-review/incorporate`,
156
- { method: 'POST', body: feedback ? { feedback } : {} },
157
- ),
178
+ send(incorporateClarityContract, {
179
+ pathPrefix: ws(workspaceId),
180
+ pathParams: { blockId },
181
+ body: feedback ? { feedback } : {},
182
+ }),
158
183
 
159
184
  // Re-review the clarified report (one more reviewer pass). On convergence the parked run
160
185
  // advances; otherwise the response carries the next cycle / cap state.
161
186
  reReviewClarity: (workspaceId: string, blockId: string) =>
162
- http<ClarityReview>(
163
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/clarity-review/re-review`,
164
- { method: 'POST' },
165
- ),
187
+ send(reReviewClarityContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
166
188
 
167
189
  // Proceed: settle the clarity review and advance the parked run (all findings dismissed).
168
190
  proceedClarity: (workspaceId: string, blockId: string) =>
169
- http<ClarityReview>(
170
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/clarity-review/proceed`,
171
- { method: 'POST' },
172
- ),
191
+ send(proceedClarityContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
173
192
 
174
193
  // Resolve a review that hit its iteration cap: extra-round / proceed / stop-reset.
175
194
  resolveClarityExceeded: (
@@ -177,35 +196,36 @@ export function reviewsApi({ http, ws }: ApiContext) {
177
196
  blockId: string,
178
197
  choice: ResolveClarityExceededChoice,
179
198
  ) =>
180
- http<ClarityReview>(
181
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/clarity-review/resolve-exceeded`,
182
- { method: 'POST', body: { choice } },
183
- ),
199
+ send(resolveClarityExceededContract, {
200
+ pathPrefix: ws(workspaceId),
201
+ pathParams: { blockId },
202
+ body: { choice },
203
+ }),
184
204
 
185
205
  // ---- brainstorm (structured-dialogue agent, stage-scoped) ------------
186
206
  // The current session for a block + stage (null when none has been run). A 503 means
187
207
  // the feature is unconfigured (the panel hides on any error here).
188
208
  getBrainstorm: (workspaceId: string, blockId: string, stage: BrainstormStage) =>
189
- http<BrainstormSession | null>(
190
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/brainstorm/${stage}`,
191
- ),
209
+ send(getBrainstormContract, { pathPrefix: ws(workspaceId), pathParams: { blockId, stage } }),
192
210
 
193
211
  replyBrainstormItem: (workspaceId: string, sessionId: string, itemId: string, reply: string) =>
194
- http<BrainstormSession>(
195
- `${ws(workspaceId)}/brainstorm-sessions/${encodeURIComponent(sessionId)}/items/${encodeURIComponent(itemId)}/reply`,
196
- { method: 'POST', body: { reply } },
197
- ),
212
+ send(replyBrainstormItemContract, {
213
+ pathPrefix: ws(workspaceId),
214
+ pathParams: { sessionId, itemId },
215
+ body: { reply },
216
+ }),
198
217
 
199
218
  setBrainstormItemStatus: (
200
219
  workspaceId: string,
201
220
  sessionId: string,
202
221
  itemId: string,
203
- status: ReviewItemStatus,
222
+ status: BrainstormItemStatus,
204
223
  ) =>
205
- http<BrainstormSession>(
206
- `${ws(workspaceId)}/brainstorm-sessions/${encodeURIComponent(sessionId)}/items/${encodeURIComponent(itemId)}`,
207
- { method: 'PATCH', body: { status } },
208
- ),
224
+ send(updateBrainstormItemStatusContract, {
225
+ pathPrefix: ws(workspaceId),
226
+ pathParams: { sessionId, itemId },
227
+ body: { status },
228
+ }),
209
229
 
210
230
  // Incorporate the picks ASYNCHRONOUSLY (the durable driver folds + re-runs).
211
231
  incorporateBrainstorm: (
@@ -214,24 +234,25 @@ export function reviewsApi({ http, ws }: ApiContext) {
214
234
  stage: BrainstormStage,
215
235
  feedback?: string,
216
236
  ) =>
217
- http<BrainstormSession>(
218
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/brainstorm/${stage}/incorporate`,
219
- { method: 'POST', body: feedback ? { feedback } : {} },
220
- ),
237
+ send(incorporateBrainstormContract, {
238
+ pathPrefix: ws(workspaceId),
239
+ pathParams: { blockId, stage },
240
+ body: feedback ? { feedback } : {},
241
+ }),
221
242
 
222
243
  // Re-run the brainstorm against the converged direction (one more pass).
223
244
  reReviewBrainstorm: (workspaceId: string, blockId: string, stage: BrainstormStage) =>
224
- http<BrainstormSession>(
225
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/brainstorm/${stage}/re-review`,
226
- { method: 'POST' },
227
- ),
245
+ send(reReviewBrainstormContract, {
246
+ pathPrefix: ws(workspaceId),
247
+ pathParams: { blockId, stage },
248
+ }),
228
249
 
229
250
  // Proceed: settle the brainstorm and advance the parked run (all options dismissed).
230
251
  proceedBrainstorm: (workspaceId: string, blockId: string, stage: BrainstormStage) =>
231
- http<BrainstormSession>(
232
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/brainstorm/${stage}/proceed`,
233
- { method: 'POST' },
234
- ),
252
+ send(proceedBrainstormContract, {
253
+ pathPrefix: ws(workspaceId),
254
+ pathParams: { blockId, stage },
255
+ }),
235
256
 
236
257
  // Resolve a session that hit its iteration cap: extra-round / proceed / stop-reset.
237
258
  resolveBrainstormExceeded: (
@@ -240,9 +261,10 @@ export function reviewsApi({ http, ws }: ApiContext) {
240
261
  stage: BrainstormStage,
241
262
  choice: ResolveBrainstormExceededChoice,
242
263
  ) =>
243
- http<BrainstormSession>(
244
- `${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/brainstorm/${stage}/resolve-exceeded`,
245
- { method: 'POST', body: { choice } },
246
- ),
264
+ send(resolveBrainstormExceededContract, {
265
+ pathPrefix: ws(workspaceId),
266
+ pathParams: { blockId, stage },
267
+ body: { choice },
268
+ }),
247
269
  }
248
270
  }
@@ -1,57 +1,75 @@
1
- import type {
2
- CloneSandboxPromptInput,
3
- CreateSandboxExperimentInput,
4
- SandboxExperiment,
5
- SandboxExperimentDetail,
6
- SandboxFixture,
7
- SandboxOverview,
8
- SandboxPromptVersion,
9
- SaveSandboxVersionInput,
10
- } from '~/types/sandbox'
1
+ import {
2
+ archiveSandboxPromptContract,
3
+ cloneSandboxPromptContract,
4
+ createSandboxExperimentContract,
5
+ createSandboxFixtureContract,
6
+ getSandboxExperimentContract,
7
+ launchSandboxExperimentContract,
8
+ removeSandboxFixtureContract,
9
+ sandboxOverviewContract,
10
+ saveSandboxPromptContract,
11
+ setSandboxPromptLabelsContract,
12
+ } from '@cat-factory/contracts'
13
+ import type { SendParams } from './client'
11
14
  import type { ApiContext } from './context'
12
15
 
16
+ // Request bodies are typed from the contract's INPUT shape (`SendParams[...]['body']`),
17
+ // so valibot-defaulted fields (labels / repeats / budgetTokens) stay optional for callers —
18
+ // the contract's exported `*Input` types are the post-default OUTPUT shape and would force
19
+ // callers to supply them.
20
+ type CloneSandboxPromptBody = NonNullable<SendParams<typeof cloneSandboxPromptContract>['body']>
21
+ type SaveSandboxVersionBody = NonNullable<SendParams<typeof saveSandboxPromptContract>['body']>
22
+ type CreateSandboxFixtureBody = NonNullable<SendParams<typeof createSandboxFixtureContract>['body']>
23
+ type CreateSandboxExperimentBody = NonNullable<
24
+ SendParams<typeof createSandboxExperimentContract>['body']
25
+ >
26
+
13
27
  /**
14
28
  * The Sandbox API (the parallel prompt/model testing surface): manage versioned prompt
15
29
  * candidates + the fixture library, define experiments (prompt × model × fixture), and
16
30
  * launch one to run + grade every cell. Opt-in: every endpoint 503s when the deployment
17
31
  * hasn't wired the Sandbox (its dedicated DB / schema).
18
32
  */
19
- export function sandboxApi({ http, ws }: ApiContext) {
20
- const base = (workspaceId: string) => `${ws(workspaceId)}/sandbox`
33
+ export function sandboxApi({ send, ws }: ApiContext) {
21
34
  return {
22
35
  getSandboxOverview: (workspaceId: string) =>
23
- http<SandboxOverview>(`${base(workspaceId)}/overview`),
36
+ send(sandboxOverviewContract, { pathPrefix: ws(workspaceId) }),
24
37
 
25
38
  // ---- prompt versions -------------------------------------------------
26
- cloneSandboxPrompt: (workspaceId: string, body: CloneSandboxPromptInput) =>
27
- http<SandboxPromptVersion>(`${base(workspaceId)}/prompts/clone`, { method: 'POST', body }),
28
- saveSandboxVersion: (workspaceId: string, body: SaveSandboxVersionInput) =>
29
- http<SandboxPromptVersion>(`${base(workspaceId)}/prompts`, { method: 'POST', body }),
39
+ cloneSandboxPrompt: (workspaceId: string, body: CloneSandboxPromptBody) =>
40
+ send(cloneSandboxPromptContract, { pathPrefix: ws(workspaceId), body }),
41
+ saveSandboxVersion: (workspaceId: string, body: SaveSandboxVersionBody) =>
42
+ send(saveSandboxPromptContract, { pathPrefix: ws(workspaceId), body }),
30
43
  setSandboxPromptLabels: (workspaceId: string, promptId: string, labels: string[]) =>
31
- http<SandboxPromptVersion>(
32
- `${base(workspaceId)}/prompts/${encodeURIComponent(promptId)}/labels`,
33
- { method: 'PATCH', body: { labels } },
34
- ),
44
+ send(setSandboxPromptLabelsContract, {
45
+ pathPrefix: ws(workspaceId),
46
+ pathParams: { promptId },
47
+ body: { labels },
48
+ }),
35
49
  archiveSandboxPrompt: (workspaceId: string, promptId: string) =>
36
- http(`${base(workspaceId)}/prompts/${encodeURIComponent(promptId)}`, { method: 'DELETE' }),
50
+ send(archiveSandboxPromptContract, { pathPrefix: ws(workspaceId), pathParams: { promptId } }),
37
51
 
38
52
  // ---- fixtures --------------------------------------------------------
39
- createSandboxFixture: (workspaceId: string, body: Partial<SandboxFixture>) =>
40
- http<SandboxFixture>(`${base(workspaceId)}/fixtures`, { method: 'POST', body }),
53
+ createSandboxFixture: (workspaceId: string, body: CreateSandboxFixtureBody) =>
54
+ send(createSandboxFixtureContract, { pathPrefix: ws(workspaceId), body }),
41
55
  deleteSandboxFixture: (workspaceId: string, fixtureId: string) =>
42
- http(`${base(workspaceId)}/fixtures/${encodeURIComponent(fixtureId)}`, { method: 'DELETE' }),
56
+ send(removeSandboxFixtureContract, {
57
+ pathPrefix: ws(workspaceId),
58
+ pathParams: { fixtureId },
59
+ }),
43
60
 
44
61
  // ---- experiments -----------------------------------------------------
45
- createSandboxExperiment: (workspaceId: string, body: CreateSandboxExperimentInput) =>
46
- http<SandboxExperiment>(`${base(workspaceId)}/experiments`, { method: 'POST', body }),
62
+ createSandboxExperiment: (workspaceId: string, body: CreateSandboxExperimentBody) =>
63
+ send(createSandboxExperimentContract, { pathPrefix: ws(workspaceId), body }),
47
64
  getSandboxExperiment: (workspaceId: string, experimentId: string) =>
48
- http<SandboxExperimentDetail>(
49
- `${base(workspaceId)}/experiments/${encodeURIComponent(experimentId)}`,
50
- ),
65
+ send(getSandboxExperimentContract, {
66
+ pathPrefix: ws(workspaceId),
67
+ pathParams: { experimentId },
68
+ }),
51
69
  launchSandboxExperiment: (workspaceId: string, experimentId: string) =>
52
- http<SandboxExperimentDetail>(
53
- `${base(workspaceId)}/experiments/${encodeURIComponent(experimentId)}/launch`,
54
- { method: 'POST' },
55
- ),
70
+ send(launchSandboxExperimentContract, {
71
+ pathPrefix: ws(workspaceId),
72
+ pathParams: { experimentId },
73
+ }),
56
74
  }
57
75
  }
@@ -1,54 +1,51 @@
1
- import type {
2
- SlackChannel,
3
- SlackConnection,
4
- SlackMemberMappingEntry,
5
- SlackNotificationSettings,
6
- } from '~/types/slack'
1
+ import {
2
+ connectSlackContract,
3
+ disconnectSlackContract,
4
+ getSlackConnectionContract,
5
+ getSlackInstallUrlContract,
6
+ getSlackMemberMappingContract,
7
+ getSlackSettingsContract,
8
+ listSlackChannelsContract,
9
+ updateSlackMemberMappingContract,
10
+ updateSlackSettingsContract,
11
+ } from '@cat-factory/contracts'
12
+ import type { SlackMemberMappingEntry, SlackNotificationSettings } from '~/types/slack'
7
13
  import type { ApiContext } from './context'
8
14
 
9
15
  /** Slack integration: per-account connection, per-workspace routing + member map. */
10
- export function slackApi({ http, ws }: ApiContext) {
16
+ export function slackApi({ send, ws }: ApiContext) {
11
17
  return {
12
18
  // ---- slack integration (extra notification transport) -----------------
13
19
  // Per-account connection (manual bot-token paste + the OAuth "Add to Slack"
14
20
  // URL), per-workspace routing, and the per-account member map. A 503 from
15
21
  // `getSlackConnection` means the integration is off (the store hides its UI).
16
22
  getSlackConnection: (workspaceId: string) =>
17
- http<{ connection: SlackConnection | null; oauthEnabled: boolean }>(
18
- `${ws(workspaceId)}/slack/connection`,
19
- ),
23
+ send(getSlackConnectionContract, { pathPrefix: ws(workspaceId) }),
20
24
 
21
25
  getSlackInstallUrl: (workspaceId: string) =>
22
- http<{ url: string }>(`${ws(workspaceId)}/slack/install-url`),
26
+ send(getSlackInstallUrlContract, { pathPrefix: ws(workspaceId) }),
23
27
 
24
28
  connectSlack: (workspaceId: string, token: string) =>
25
- http<SlackConnection>(`${ws(workspaceId)}/slack/connect`, {
26
- method: 'POST',
27
- body: { token },
28
- }),
29
+ send(connectSlackContract, { pathPrefix: ws(workspaceId), body: { token } }),
29
30
 
30
31
  disconnectSlack: (workspaceId: string) =>
31
- http(`${ws(workspaceId)}/slack/connection`, { method: 'DELETE' }),
32
+ send(disconnectSlackContract, { pathPrefix: ws(workspaceId) }),
32
33
 
33
34
  listSlackChannels: (workspaceId: string) =>
34
- http<{ channels: SlackChannel[] }>(`${ws(workspaceId)}/slack/channels`),
35
+ send(listSlackChannelsContract, { pathPrefix: ws(workspaceId) }),
35
36
 
36
37
  getSlackSettings: (workspaceId: string) =>
37
- http<SlackNotificationSettings>(`${ws(workspaceId)}/slack/settings`),
38
+ send(getSlackSettingsContract, { pathPrefix: ws(workspaceId) }),
38
39
 
39
40
  updateSlackSettings: (
40
41
  workspaceId: string,
41
42
  body: { routes: SlackNotificationSettings['routes']; mentionsEnabled: boolean },
42
- ) =>
43
- http<SlackNotificationSettings>(`${ws(workspaceId)}/slack/settings`, { method: 'PUT', body }),
43
+ ) => send(updateSlackSettingsContract, { pathPrefix: ws(workspaceId), body }),
44
44
 
45
45
  getSlackMemberMapping: (workspaceId: string) =>
46
- http<{ entries: SlackMemberMappingEntry[] }>(`${ws(workspaceId)}/slack/member-mapping`),
46
+ send(getSlackMemberMappingContract, { pathPrefix: ws(workspaceId) }),
47
47
 
48
48
  updateSlackMemberMapping: (workspaceId: string, entries: SlackMemberMappingEntry[]) =>
49
- http<{ entries: SlackMemberMappingEntry[] }>(`${ws(workspaceId)}/slack/member-mapping`, {
50
- method: 'PUT',
51
- body: { entries },
52
- }),
49
+ send(updateSlackMemberMappingContract, { pathPrefix: ws(workspaceId), body: { entries } }),
53
50
  }
54
51
  }
@@ -1,4 +1,4 @@
1
- import type { ServiceSpecView } from '~/types/spec'
1
+ import { getServiceSpecContract } from '@cat-factory/contracts'
2
2
  import type { ApiContext } from './context'
3
3
 
4
4
  /**
@@ -6,9 +6,9 @@ import type { ApiContext } from './context'
6
6
  * sharded `spec/` artifact from the service repo's default branch. Always 200: a service
7
7
  * with no spec on main (or no GitHub connected) returns `{ present: false }`.
8
8
  */
9
- export function specApi({ http, ws }: ApiContext) {
9
+ export function specApi({ send, ws }: ApiContext) {
10
10
  return {
11
11
  getServiceSpec: (workspaceId: string, blockId: string) =>
12
- http<ServiceSpecView>(`${ws(workspaceId)}/blocks/${encodeURIComponent(blockId)}/spec`),
12
+ send(getServiceSpecContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
13
13
  }
14
14
  }