@cat-factory/app 0.109.4 → 0.110.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.
- package/app/components/board/AddTaskModal.vue +15 -15
- package/app/components/panels/inspector/TaskRunSettings.vue +12 -12
- package/app/components/pipeline/PipelineBuilder.vue +22 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +300 -203
- package/app/components/settings/{MergePresetHealthModal.vue → RiskPolicyHealthModal.vue} +19 -19
- package/app/components/settings/{MergeThresholdsPanel.vue → RiskPolicyPanel.vue} +46 -46
- package/app/components/settings/UsageSettings.vue +142 -0
- package/app/components/settings/WorkspaceSettingsPanel.vue +14 -2
- package/app/composables/api/board.ts +1 -1
- package/app/composables/api/execution.ts +5 -0
- package/app/composables/api/presets.ts +18 -18
- package/app/composables/api/reviews.ts +7 -6
- package/app/composables/{useMergePresetHealth.ts → useRiskPolicyHealth.ts} +10 -10
- package/app/pages/index.vue +9 -9
- package/app/stores/board.ts +2 -2
- package/app/stores/pipelines.ts +41 -1
- package/app/stores/requirements.ts +9 -13
- package/app/stores/{mergePresets.ts → riskPolicies.ts} +13 -13
- package/app/stores/ui.ts +17 -17
- package/app/stores/usage.ts +60 -0
- package/app/stores/workspace.spec.ts +1 -1
- package/app/stores/workspace.ts +4 -4
- package/app/types/merge.ts +3 -3
- package/app/types/requirements.ts +1 -0
- package/app/utils/{mergePreset.ts → riskPolicy.ts} +4 -4
- package/i18n/locales/de.json +52 -27
- package/i18n/locales/en.json +52 -27
- package/i18n/locales/es.json +52 -27
- package/i18n/locales/fr.json +53 -28
- package/i18n/locales/he.json +53 -28
- package/i18n/locales/it.json +53 -28
- package/i18n/locales/ja.json +52 -27
- package/i18n/locales/pl.json +53 -28
- package/i18n/locales/tr.json +52 -27
- package/i18n/locales/uk.json +53 -28
- package/package.json +2 -2
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
createRiskPolicyContract,
|
|
3
3
|
createModelPresetContract,
|
|
4
|
-
|
|
4
|
+
deleteRiskPolicyContract,
|
|
5
5
|
deleteModelPresetContract,
|
|
6
|
-
|
|
6
|
+
listRiskPoliciesContract,
|
|
7
7
|
listModelPresetsContract,
|
|
8
|
-
|
|
8
|
+
reseedRiskPolicyContract,
|
|
9
9
|
reseedModelPresetContract,
|
|
10
|
-
|
|
10
|
+
updateRiskPolicyContract,
|
|
11
11
|
updateModelPresetContract,
|
|
12
12
|
} from '@cat-factory/contracts'
|
|
13
|
-
import type {
|
|
13
|
+
import type { UpdateRiskPolicyInput } from '~/types/merge'
|
|
14
14
|
import type { CreateModelPresetInput, UpdateModelPresetInput } from '~/types/model-presets'
|
|
15
15
|
import type { SendParams } from './client'
|
|
16
16
|
import type { ApiContext } from './context'
|
|
17
17
|
|
|
18
18
|
// The merge-preset create body is typed from the contract's INPUT shape so the
|
|
19
19
|
// valibot-defaulted fields (release/grace windows, isDefault) stay optional for callers
|
|
20
|
-
// (the exported `
|
|
21
|
-
type
|
|
20
|
+
// (the exported `CreateRiskPolicyInput` is the post-default OUTPUT shape).
|
|
21
|
+
type CreateRiskPolicyBody = NonNullable<SendParams<typeof createRiskPolicyContract>['body']>
|
|
22
22
|
|
|
23
23
|
/** The per-workspace preset libraries: merge-threshold policy + model->agent mapping. */
|
|
24
24
|
export function presetsApi({ send, ws }: ApiContext) {
|
|
25
25
|
return {
|
|
26
26
|
// ---- merge threshold presets (per-task auto-merge policy library) -----
|
|
27
|
-
|
|
28
|
-
send(
|
|
27
|
+
listRiskPolicies: (workspaceId: string) =>
|
|
28
|
+
send(listRiskPoliciesContract, { pathPrefix: ws(workspaceId) }),
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
send(
|
|
30
|
+
createRiskPolicy: (workspaceId: string, body: CreateRiskPolicyBody) =>
|
|
31
|
+
send(createRiskPolicyContract, { pathPrefix: ws(workspaceId), body }),
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
send(
|
|
33
|
+
updateRiskPolicy: (workspaceId: string, presetId: string, body: UpdateRiskPolicyInput) =>
|
|
34
|
+
send(updateRiskPolicyContract, {
|
|
35
35
|
pathPrefix: ws(workspaceId),
|
|
36
36
|
pathParams: { presetId },
|
|
37
37
|
body,
|
|
38
38
|
}),
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
send(
|
|
40
|
+
deleteRiskPolicy: (workspaceId: string, presetId: string) =>
|
|
41
|
+
send(deleteRiskPolicyContract, { pathPrefix: ws(workspaceId), pathParams: { presetId } }),
|
|
42
42
|
|
|
43
43
|
// Restore a built-in preset to its current catalog definition (adopt an update, repair a
|
|
44
44
|
// drifted one, or materialise a new built-in that appeared). Custom presets reject this.
|
|
45
|
-
|
|
46
|
-
send(
|
|
45
|
+
reseedRiskPolicy: (workspaceId: string, presetId: string) =>
|
|
46
|
+
send(reseedRiskPolicyContract, { pathPrefix: ws(workspaceId), pathParams: { presetId } }),
|
|
47
47
|
|
|
48
48
|
// ---- model presets (per-task model->agent mapping library) ------------
|
|
49
49
|
listModelPresets: (workspaceId: string) =>
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
updateRequirementItemStatusContract,
|
|
28
28
|
} from '@cat-factory/contracts'
|
|
29
29
|
import type {
|
|
30
|
+
RequestRecommendationItem,
|
|
30
31
|
UpdateBrainstormItemStatusInput,
|
|
31
32
|
UpdateClarityItemStatusInput,
|
|
32
33
|
} from '@cat-factory/contracts'
|
|
@@ -109,19 +110,19 @@ export function reviewsApi({ send, ws }: ApiContext) {
|
|
|
109
110
|
body: { choice },
|
|
110
111
|
}),
|
|
111
112
|
|
|
112
|
-
// Ask the Requirement Writer to recommend grounded answers for a batch of findings
|
|
113
|
-
// item id
|
|
114
|
-
//
|
|
113
|
+
// Ask the Requirement Writer to recommend grounded answers for a batch of findings. Each
|
|
114
|
+
// item carries its finding id plus optional per-finding guidance (the note the human typed
|
|
115
|
+
// before choosing "recommend something"). Returns the review with `pending` placeholder
|
|
116
|
+
// recommendations; they fill in (`ready`) asynchronously via the `requirements` stream.
|
|
115
117
|
requestRecommendations: (
|
|
116
118
|
workspaceId: string,
|
|
117
119
|
blockId: string,
|
|
118
|
-
|
|
119
|
-
note?: string,
|
|
120
|
+
items: RequestRecommendationItem[],
|
|
120
121
|
) =>
|
|
121
122
|
send(requestRequirementRecommendationsContract, {
|
|
122
123
|
pathPrefix: ws(workspaceId),
|
|
123
124
|
pathParams: { blockId },
|
|
124
|
-
body: {
|
|
125
|
+
body: { items },
|
|
125
126
|
}),
|
|
126
127
|
|
|
127
128
|
// Accept a recommendation (becomes the finding's answer), reject it, or re-request it
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { computed } from 'vue'
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
2
|
+
import type { RiskPolicy } from '~/types/merge'
|
|
3
|
+
import { useRiskPoliciesStore } from '~/stores/riskPolicies'
|
|
4
4
|
|
|
5
|
-
export type
|
|
5
|
+
export type RiskPolicyIssueType = 'outdated' | 'new'
|
|
6
6
|
|
|
7
7
|
/** A built-in merge preset that the workspace should reseed (an update, or a new one to add). */
|
|
8
|
-
export interface
|
|
9
|
-
type:
|
|
8
|
+
export interface RiskPolicyIssue {
|
|
9
|
+
type: RiskPolicyIssueType
|
|
10
10
|
/** The catalog (built-in) id — what the reseed endpoint is keyed by. */
|
|
11
11
|
id: string
|
|
12
12
|
/** The preset name (the stored copy's for `outdated`, the built-in id for a `new` one). */
|
|
@@ -18,7 +18,7 @@ export interface MergePresetIssue {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/** A built-in's display name for an issue message (humanise its catalog id as a fallback). */
|
|
21
|
-
function builtinName(id: string, stored:
|
|
21
|
+
function builtinName(id: string, stored: RiskPolicy | undefined): string {
|
|
22
22
|
if (stored) return stored.name
|
|
23
23
|
// `mp_manual_review` -> "Manual review" — only used until the row is reseeded into existence.
|
|
24
24
|
return id.replace(/^mp_/, '').replace(/_/g, ' ')
|
|
@@ -32,11 +32,11 @@ function builtinName(id: string, stored: MergeThresholdPreset | undefined): stri
|
|
|
32
32
|
* a stored preset is a built-in iff its id is a catalog key, and a catalog key with no stored
|
|
33
33
|
* preset is a new built-in.
|
|
34
34
|
*/
|
|
35
|
-
export function
|
|
36
|
-
const store =
|
|
35
|
+
export function useRiskPolicyHealth() {
|
|
36
|
+
const store = useRiskPoliciesStore()
|
|
37
37
|
|
|
38
|
-
const issues = computed<
|
|
39
|
-
const out:
|
|
38
|
+
const issues = computed<RiskPolicyIssue[]>(() => {
|
|
39
|
+
const out: RiskPolicyIssue[] = []
|
|
40
40
|
const byId = new Map(store.presets.map((p) => [p.id, p]))
|
|
41
41
|
for (const [id, catalogVersion] of Object.entries(store.catalogVersions)) {
|
|
42
42
|
const stored = byId.get(id)
|
package/app/pages/index.vue
CHANGED
|
@@ -71,8 +71,8 @@ const PipelineHealthModal = defineAsyncComponent(
|
|
|
71
71
|
() => import('~/components/pipeline/PipelineHealthModal.vue'),
|
|
72
72
|
)
|
|
73
73
|
// Startup advisory for new / outdated built-in merge presets — same once-per-session pattern.
|
|
74
|
-
const
|
|
75
|
-
() => import('~/components/settings/
|
|
74
|
+
const RiskPolicyHealthModal = defineAsyncComponent(
|
|
75
|
+
() => import('~/components/settings/RiskPolicyHealthModal.vue'),
|
|
76
76
|
)
|
|
77
77
|
// Startup advisory for new / outdated built-in model presets — same once-per-session pattern.
|
|
78
78
|
const ModelPresetHealthModal = defineAsyncComponent(
|
|
@@ -185,12 +185,12 @@ watch(
|
|
|
185
185
|
)
|
|
186
186
|
// Same advisory for built-in merge presets: surface new / outdated ones once per session. Defers
|
|
187
187
|
// to the pipeline advisory when both fire, so at most one modal auto-opens on a given load.
|
|
188
|
-
const { hasIssues:
|
|
188
|
+
const { hasIssues: riskPolicyIssues } = useRiskPolicyHealth()
|
|
189
189
|
watch(
|
|
190
|
-
() => [workspace.ready,
|
|
190
|
+
() => [workspace.ready, riskPolicyIssues.value, ui.pipelineHealthOpen],
|
|
191
191
|
() => {
|
|
192
|
-
if (workspace.ready &&
|
|
193
|
-
ui.
|
|
192
|
+
if (workspace.ready && riskPolicyIssues.value && !ui.pipelineHealthOpen) {
|
|
193
|
+
ui.maybeOpenRiskPolicyHealth()
|
|
194
194
|
}
|
|
195
195
|
},
|
|
196
196
|
{ immediate: true },
|
|
@@ -200,13 +200,13 @@ watch(
|
|
|
200
200
|
// to the pipeline + merge-preset advisories when they fire, so at most one modal auto-opens.
|
|
201
201
|
const { hasIssues: modelPresetIssues } = useModelPresetHealth()
|
|
202
202
|
watch(
|
|
203
|
-
() => [workspace.ready, modelPresetIssues.value, ui.pipelineHealthOpen, ui.
|
|
203
|
+
() => [workspace.ready, modelPresetIssues.value, ui.pipelineHealthOpen, ui.riskPolicyHealthOpen],
|
|
204
204
|
() => {
|
|
205
205
|
if (
|
|
206
206
|
workspace.ready &&
|
|
207
207
|
modelPresetIssues.value &&
|
|
208
208
|
!ui.pipelineHealthOpen &&
|
|
209
|
-
!ui.
|
|
209
|
+
!ui.riskPolicyHealthOpen
|
|
210
210
|
) {
|
|
211
211
|
ui.maybeOpenModelPresetHealth()
|
|
212
212
|
}
|
|
@@ -390,7 +390,7 @@ watch(
|
|
|
390
390
|
<SlackPanel v-if="ui.slackOpen" />
|
|
391
391
|
<FragmentLibraryPanel v-if="ui.fragmentLibraryOpen" />
|
|
392
392
|
<PipelineHealthModal v-if="ui.pipelineHealthOpen" />
|
|
393
|
-
<
|
|
393
|
+
<RiskPolicyHealthModal v-if="ui.riskPolicyHealthOpen" />
|
|
394
394
|
<ModelPresetHealthModal v-if="ui.modelPresetHealthOpen" />
|
|
395
395
|
<IntegrationsHub v-if="ui.integrationsOpen" />
|
|
396
396
|
<PersonalSetupModal v-if="ui.personalSetupOpen" />
|
package/app/stores/board.ts
CHANGED
|
@@ -169,7 +169,7 @@ export const useBoardStore = defineStore('board', () => {
|
|
|
169
169
|
options?: {
|
|
170
170
|
taskType?: CreateTaskType
|
|
171
171
|
taskTypeFields?: TaskTypeFields
|
|
172
|
-
|
|
172
|
+
riskPolicyId?: string
|
|
173
173
|
modelPresetId?: string
|
|
174
174
|
pipelineId?: string
|
|
175
175
|
agentConfig?: Record<string, string>
|
|
@@ -182,7 +182,7 @@ export const useBoardStore = defineStore('board', () => {
|
|
|
182
182
|
description,
|
|
183
183
|
...(options?.taskType ? { taskType: options.taskType } : {}),
|
|
184
184
|
...(options?.taskTypeFields ? { taskTypeFields: options.taskTypeFields } : {}),
|
|
185
|
-
...(options?.
|
|
185
|
+
...(options?.riskPolicyId ? { riskPolicyId: options.riskPolicyId } : {}),
|
|
186
186
|
...(options?.modelPresetId ? { modelPresetId: options.modelPresetId } : {}),
|
|
187
187
|
...(options?.pipelineId ? { pipelineId: options.pipelineId } : {}),
|
|
188
188
|
...(options?.agentConfig ? { agentConfig: options.agentConfig } : {}),
|
package/app/stores/pipelines.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
3
|
import type { AgentKind, Pipeline } from '~/types/domain'
|
|
4
4
|
import type { ConsensusStepConfig, StepGating } from '~/types/consensus'
|
|
5
|
-
import type { TesterQualityConfig } from '@cat-factory/contracts'
|
|
5
|
+
import type { StepOptions, TesterQualityConfig } from '@cat-factory/contracts'
|
|
6
6
|
import { companionForProducer, uid } from '~/utils/catalog'
|
|
7
7
|
import { useWorkspaceStore } from '~/stores/workspace'
|
|
8
8
|
|
|
@@ -67,6 +67,13 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
67
67
|
* entry with `gating` makes it conditional on the task estimate.
|
|
68
68
|
*/
|
|
69
69
|
const draftTesterQuality = ref<(TesterQualityConfig | null)[]>([])
|
|
70
|
+
/**
|
|
71
|
+
* Per-step options bag, kept index-aligned with `draft`: the extensible home for new per-step
|
|
72
|
+
* parameters (see `StepOptions`). `null`/absent per step ⇒ that step's defaults. Today the only
|
|
73
|
+
* field is `autoRecommend` (requirements-review); by convention we store ONLY deviations from a
|
|
74
|
+
* default, so an entry exists only when a step opts out of something.
|
|
75
|
+
*/
|
|
76
|
+
const draftStepOptions = ref<(StepOptions | null)[]>([])
|
|
70
77
|
/** Organizational labels for the pipeline being assembled/edited. */
|
|
71
78
|
const draftLabels = ref<string[]>([])
|
|
72
79
|
const draftName = ref('New pipeline')
|
|
@@ -93,6 +100,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
93
100
|
draftGating.value.splice(index, 0, null)
|
|
94
101
|
draftFollowUps.value.splice(index, 0, null)
|
|
95
102
|
draftTesterQuality.value.splice(index, 0, null)
|
|
103
|
+
draftStepOptions.value.splice(index, 0, null)
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
function addToDraft(kind: AgentKind) {
|
|
@@ -108,6 +116,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
108
116
|
draftGating.value.splice(index, 1)
|
|
109
117
|
draftFollowUps.value.splice(index, 1)
|
|
110
118
|
draftTesterQuality.value.splice(index, 1)
|
|
119
|
+
draftStepOptions.value.splice(index, 1)
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
function moveInDraft(from: number, to: number) {
|
|
@@ -128,6 +137,8 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
128
137
|
draftFollowUps.value.splice(to, 0, fu ?? null)
|
|
129
138
|
const [tq] = draftTesterQuality.value.splice(from, 1)
|
|
130
139
|
draftTesterQuality.value.splice(to, 0, tq ?? null)
|
|
140
|
+
const [so] = draftStepOptions.value.splice(from, 1)
|
|
141
|
+
draftStepOptions.value.splice(to, 0, so ?? null)
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
/** Whether the producer step at `index` currently has its companion attached after it. */
|
|
@@ -204,6 +215,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
204
215
|
draftGating.value = reorder(draftGating.value)
|
|
205
216
|
draftFollowUps.value = reorder(draftFollowUps.value)
|
|
206
217
|
draftTesterQuality.value = reorder(draftTesterQuality.value)
|
|
218
|
+
draftStepOptions.value = reorder(draftStepOptions.value)
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
/** Toggle the consensus mechanism on the draft step at `index` (default config / off). */
|
|
@@ -259,6 +271,23 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
259
271
|
draftEnabled.value[index] = draftEnabled.value[index] === false
|
|
260
272
|
}
|
|
261
273
|
|
|
274
|
+
/** Whether auto-recommendation is on for the draft (requirements-review) step at `index`. */
|
|
275
|
+
function draftAutoRecommendEnabled(index: number): boolean {
|
|
276
|
+
return draftStepOptions.value[index]?.autoRecommend !== false
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Toggle the requirements-review auto-recommendation on the draft step at `index`. It is on by
|
|
281
|
+
* default, so we store ONLY the opt-out (`{ autoRecommend: false }`); toggling back drops the
|
|
282
|
+
* flag. Merges with any other future StepOptions fields rather than clobbering the whole bag.
|
|
283
|
+
*/
|
|
284
|
+
function toggleDraftAutoRecommend(index: number) {
|
|
285
|
+
const next: StepOptions = { ...draftStepOptions.value[index] }
|
|
286
|
+
if (draftAutoRecommendEnabled(index)) next.autoRecommend = false
|
|
287
|
+
else delete next.autoRecommend
|
|
288
|
+
draftStepOptions.value[index] = Object.keys(next).length ? next : null
|
|
289
|
+
}
|
|
290
|
+
|
|
262
291
|
function clearDraft() {
|
|
263
292
|
draft.value = []
|
|
264
293
|
draftGates.value = []
|
|
@@ -268,6 +297,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
268
297
|
draftGating.value = []
|
|
269
298
|
draftFollowUps.value = []
|
|
270
299
|
draftTesterQuality.value = []
|
|
300
|
+
draftStepOptions.value = []
|
|
271
301
|
draftLabels.value = []
|
|
272
302
|
draftName.value = 'New pipeline'
|
|
273
303
|
editingId.value = null
|
|
@@ -285,6 +315,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
285
315
|
draftTesterQuality.value = pipeline.agentKinds.map(
|
|
286
316
|
(_, i) => pipeline.testerQuality?.[i] ?? null,
|
|
287
317
|
)
|
|
318
|
+
draftStepOptions.value = pipeline.agentKinds.map((_, i) => pipeline.stepOptions?.[i] ?? null)
|
|
288
319
|
draftLabels.value = [...(pipeline.labels ?? [])]
|
|
289
320
|
draftName.value = pipeline.name
|
|
290
321
|
editingId.value = pipeline.id
|
|
@@ -320,6 +351,12 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
320
351
|
...(draftTesterQuality.value.some((q) => q?.enabled === false || q?.gating?.enabled)
|
|
321
352
|
? { testerQuality: [...draftTesterQuality.value] }
|
|
322
353
|
: {}),
|
|
354
|
+
// ALWAYS send stepOptions, unlike the legacy per-step arrays above. Those omit-when-default,
|
|
355
|
+
// which means an update can never CLEAR them (an omitted field reads as "keep existing"), so
|
|
356
|
+
// toggling the last opt-out back to its default on a saved pipeline would silently not
|
|
357
|
+
// persist. Sending the aligned array always lets `update` overwrite; the backend normalizes
|
|
358
|
+
// an all-default array away (stores nothing), so this is a no-op on create / all-default.
|
|
359
|
+
stepOptions: draftStepOptions.value.map((o) => o ?? null),
|
|
323
360
|
// Only send labels when there are any.
|
|
324
361
|
...(draftLabels.value.length ? { labels: [...draftLabels.value] } : {}),
|
|
325
362
|
}
|
|
@@ -393,6 +430,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
393
430
|
draftGating,
|
|
394
431
|
draftFollowUps,
|
|
395
432
|
draftTesterQuality,
|
|
433
|
+
draftStepOptions,
|
|
396
434
|
draftLabels,
|
|
397
435
|
draftName,
|
|
398
436
|
editingId,
|
|
@@ -410,6 +448,8 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
410
448
|
toggleDraftFollowUps,
|
|
411
449
|
toggleDraftTesterQuality,
|
|
412
450
|
toggleDraftTesterQualityGating,
|
|
451
|
+
draftAutoRecommendEnabled,
|
|
452
|
+
toggleDraftAutoRecommend,
|
|
413
453
|
toggleDraftEnabled,
|
|
414
454
|
toggleDraftConsensus,
|
|
415
455
|
setDraftConsensus,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import type {
|
|
4
|
+
RequestRecommendationItem,
|
|
4
5
|
RequirementReview,
|
|
5
6
|
ResolveRequirementsExceededChoice,
|
|
6
7
|
ReviewItemStatus,
|
|
@@ -208,22 +209,17 @@ export const useRequirementsStore = defineStore('requirements', () => {
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
/**
|
|
211
|
-
* Ask the Requirement Writer to recommend answers for a batch of findings
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* `
|
|
216
|
-
*
|
|
212
|
+
* Ask the Requirement Writer to recommend answers for a batch of findings. Each item carries
|
|
213
|
+
* its finding id plus optional per-finding guidance (the note the human typed before choosing
|
|
214
|
+
* "recommend something"). ASYNCHRONOUS: returns at once with `pending` placeholder
|
|
215
|
+
* recommendations (the Writer runs per finding in the durable driver), which fill in (`ready`)
|
|
216
|
+
* via live `requirements` stream events; a notification calls the user back when the batch is
|
|
217
|
+
* ready. The board shows the `recommending` background stage while any placeholder is pending.
|
|
217
218
|
*/
|
|
218
|
-
async function requestRecommendations(blockId: string,
|
|
219
|
+
async function requestRecommendations(blockId: string, items: RequestRecommendationItem[]) {
|
|
219
220
|
withFlag(recommending, blockId, true)
|
|
220
221
|
try {
|
|
221
|
-
const updated = await api.requestRecommendations(
|
|
222
|
-
workspace.requireId(),
|
|
223
|
-
blockId,
|
|
224
|
-
itemIds,
|
|
225
|
-
note,
|
|
226
|
-
)
|
|
222
|
+
const updated = await api.requestRecommendations(workspace.requireId(), blockId, items)
|
|
227
223
|
if (updated) store(updated)
|
|
228
224
|
return updated
|
|
229
225
|
} finally {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
|
-
import type {
|
|
3
|
+
import type { RiskPolicy, UpdateRiskPolicyInput } from '~/types/domain'
|
|
4
4
|
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -9,20 +9,20 @@ import { useWorkspaceStore } from '~/stores/workspace'
|
|
|
9
9
|
* resolved preset). Hydrated from the workspace snapshot; managed via a small
|
|
10
10
|
* settings UI. The backend always keeps at least one default preset.
|
|
11
11
|
*/
|
|
12
|
-
export const
|
|
12
|
+
export const useRiskPoliciesStore = defineStore('riskPolicies', () => {
|
|
13
13
|
const api = useApi()
|
|
14
14
|
|
|
15
|
-
const presets = ref<
|
|
15
|
+
const presets = ref<RiskPolicy[]>([])
|
|
16
16
|
/**
|
|
17
|
-
* Current built-in catalog versions (`
|
|
17
|
+
* Current built-in catalog versions (`seedRiskPolicies()`), keyed by preset id, from the
|
|
18
18
|
* workspace snapshot. The keys ARE the set of built-in ids: a stored preset whose id is a
|
|
19
19
|
* key here is a built-in (and is outdated when its `version` is below the catalog value),
|
|
20
20
|
* and a key with no matching stored preset is a NEW built-in the workspace can add. Drives
|
|
21
|
-
* `
|
|
21
|
+
* `useRiskPolicyHealth`.
|
|
22
22
|
*/
|
|
23
23
|
const catalogVersions = ref<Record<string, number>>({})
|
|
24
24
|
|
|
25
|
-
function hydrate(list:
|
|
25
|
+
function hydrate(list: RiskPolicy[], versions?: Record<string, number>) {
|
|
26
26
|
presets.value = [...list].sort((a, b) => a.createdAt - b.createdAt)
|
|
27
27
|
if (versions) catalogVersions.value = versions
|
|
28
28
|
}
|
|
@@ -31,7 +31,7 @@ export const useMergePresetsStore = defineStore('mergePresets', () => {
|
|
|
31
31
|
const defaultPreset = computed(() => presets.value.find((p) => p.isDefault) ?? null)
|
|
32
32
|
|
|
33
33
|
/** Resolve a task's effective preset by id, falling back to the default. */
|
|
34
|
-
function resolve(presetId: string | undefined):
|
|
34
|
+
function resolve(presetId: string | undefined): RiskPolicy | null {
|
|
35
35
|
if (presetId) {
|
|
36
36
|
const picked = presets.value.find((p) => p.id === presetId)
|
|
37
37
|
if (picked) return picked
|
|
@@ -39,23 +39,23 @@ export const useMergePresetsStore = defineStore('mergePresets', () => {
|
|
|
39
39
|
return defaultPreset.value
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
async function create(input: Parameters<typeof api.
|
|
42
|
+
async function create(input: Parameters<typeof api.createRiskPolicy>[1]) {
|
|
43
43
|
const ws = useWorkspaceStore()
|
|
44
|
-
const created = await api.
|
|
44
|
+
const created = await api.createRiskPolicy(ws.requireId(), input)
|
|
45
45
|
await ws.refresh()
|
|
46
46
|
return created
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async function update(presetId: string, patch:
|
|
49
|
+
async function update(presetId: string, patch: UpdateRiskPolicyInput) {
|
|
50
50
|
const ws = useWorkspaceStore()
|
|
51
|
-
const updated = await api.
|
|
51
|
+
const updated = await api.updateRiskPolicy(ws.requireId(), presetId, patch)
|
|
52
52
|
await ws.refresh()
|
|
53
53
|
return updated
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async function remove(presetId: string) {
|
|
57
57
|
const ws = useWorkspaceStore()
|
|
58
|
-
await api.
|
|
58
|
+
await api.deleteRiskPolicy(ws.requireId(), presetId)
|
|
59
59
|
await ws.refresh()
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -66,7 +66,7 @@ export const useMergePresetsStore = defineStore('mergePresets', () => {
|
|
|
66
66
|
*/
|
|
67
67
|
async function reseed(presetId: string) {
|
|
68
68
|
const ws = useWorkspaceStore()
|
|
69
|
-
const updated = await api.
|
|
69
|
+
const updated = await api.reseedRiskPolicy(ws.requireId(), presetId)
|
|
70
70
|
await ws.refresh()
|
|
71
71
|
return updated
|
|
72
72
|
}
|
package/app/stores/ui.ts
CHANGED
|
@@ -40,10 +40,10 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
40
40
|
const pipelineHealthOpen = ref(false)
|
|
41
41
|
const pipelineHealthSeen = ref(false)
|
|
42
42
|
// Merge-preset health startup advisory: lists built-ins with a newer catalog version (reseed)
|
|
43
|
-
// and new built-in presets the workspace can add. `
|
|
43
|
+
// and new built-in presets the workspace can add. `riskPolicyHealthSeen` gates auto-open to
|
|
44
44
|
// once per session so it does not re-pop on every snapshot re-hydration (mirrors pipelines).
|
|
45
|
-
const
|
|
46
|
-
const
|
|
45
|
+
const riskPolicyHealthOpen = ref(false)
|
|
46
|
+
const riskPolicyHealthSeen = ref(false)
|
|
47
47
|
// Model-preset health startup advisory: lists built-ins with a newer catalog version (reseed)
|
|
48
48
|
// and new built-in presets the workspace can add. `modelPresetHealthSeen` gates auto-open to
|
|
49
49
|
// once per session so it does not re-pop on every snapshot re-hydration (mirrors pipelines).
|
|
@@ -313,19 +313,19 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
/** Auto-open the merge-preset health advisory once per session (no-op after it's been shown). */
|
|
316
|
-
function
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
function maybeOpenRiskPolicyHealth() {
|
|
317
|
+
if (riskPolicyHealthSeen.value) return
|
|
318
|
+
riskPolicyHealthSeen.value = true
|
|
319
|
+
riskPolicyHealthOpen.value = true
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
function
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
function openRiskPolicyHealth() {
|
|
323
|
+
riskPolicyHealthSeen.value = true
|
|
324
|
+
riskPolicyHealthOpen.value = true
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
function
|
|
328
|
-
|
|
327
|
+
function closeRiskPolicyHealth() {
|
|
328
|
+
riskPolicyHealthOpen.value = false
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
/** Auto-open the model-preset health advisory once per session (no-op after it's been shown). */
|
|
@@ -839,8 +839,8 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
839
839
|
builderOpen,
|
|
840
840
|
pipelineHealthOpen,
|
|
841
841
|
pipelineHealthSeen,
|
|
842
|
-
|
|
843
|
-
|
|
842
|
+
riskPolicyHealthOpen,
|
|
843
|
+
riskPolicyHealthSeen,
|
|
844
844
|
modelPresetHealthOpen,
|
|
845
845
|
modelPresetHealthSeen,
|
|
846
846
|
decisionContext,
|
|
@@ -907,9 +907,9 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
907
907
|
maybeOpenPipelineHealth,
|
|
908
908
|
openPipelineHealth,
|
|
909
909
|
closePipelineHealth,
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
910
|
+
maybeOpenRiskPolicyHealth,
|
|
911
|
+
openRiskPolicyHealth,
|
|
912
|
+
closeRiskPolicyHealth,
|
|
913
913
|
maybeOpenModelPresetHealth,
|
|
914
914
|
openModelPresetHealth,
|
|
915
915
|
closeModelPresetHealth,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { UsageReport } from '@cat-factory/contracts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The workspace's token-usage report for the current billing period (the "Usage" settings
|
|
7
|
+
* tab). Loaded on demand from `GET /workspaces/:ws/usage`; covers BOTH metered API/proxy
|
|
8
|
+
* calls and flat-rate subscription harness usage (Claude Code / Codex / GLM / pooled Kimi &
|
|
9
|
+
* DeepSeek). Reporting only — the spend budget still counts only the metered rows.
|
|
10
|
+
*/
|
|
11
|
+
export const useUsageStore = defineStore('usage', () => {
|
|
12
|
+
const api = useApi()
|
|
13
|
+
const report = ref<UsageReport | null>(null)
|
|
14
|
+
const loading = ref(false)
|
|
15
|
+
const error = ref<string | null>(null)
|
|
16
|
+
|
|
17
|
+
async function load(ws: string) {
|
|
18
|
+
loading.value = true
|
|
19
|
+
error.value = null
|
|
20
|
+
try {
|
|
21
|
+
report.value = await api.getUsage(ws)
|
|
22
|
+
} catch (e) {
|
|
23
|
+
error.value = e instanceof Error ? e.message : String(e)
|
|
24
|
+
} finally {
|
|
25
|
+
loading.value = false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const rows = computed(() => report.value?.rows ?? [])
|
|
30
|
+
const metered = computed(() => rows.value.filter((r) => r.billing === 'metered'))
|
|
31
|
+
const subscription = computed(() => rows.value.filter((r) => r.billing === 'subscription'))
|
|
32
|
+
|
|
33
|
+
/** Summed input/output tokens + cost for a set of rows. */
|
|
34
|
+
function totalOf(list: UsageReport['rows']) {
|
|
35
|
+
return list.reduce(
|
|
36
|
+
(acc, r) => ({
|
|
37
|
+
inputTokens: acc.inputTokens + r.inputTokens,
|
|
38
|
+
outputTokens: acc.outputTokens + r.outputTokens,
|
|
39
|
+
costEstimate: acc.costEstimate + r.costEstimate,
|
|
40
|
+
calls: acc.calls + r.calls,
|
|
41
|
+
}),
|
|
42
|
+
{ inputTokens: 0, outputTokens: 0, costEstimate: 0, calls: 0 },
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const meteredTotal = computed(() => totalOf(metered.value))
|
|
47
|
+
const subscriptionTotal = computed(() => totalOf(subscription.value))
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
report,
|
|
51
|
+
loading,
|
|
52
|
+
error,
|
|
53
|
+
load,
|
|
54
|
+
rows,
|
|
55
|
+
metered,
|
|
56
|
+
subscription,
|
|
57
|
+
meteredTotal,
|
|
58
|
+
subscriptionTotal,
|
|
59
|
+
}
|
|
60
|
+
})
|
package/app/stores/workspace.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { usePipelinesStore } from '~/stores/pipelines'
|
|
|
13
13
|
import { useExecutionStore } from '~/stores/execution'
|
|
14
14
|
import { useAgentRunsStore } from '~/stores/agentRuns'
|
|
15
15
|
import { useNotificationsStore } from '~/stores/notifications'
|
|
16
|
-
import {
|
|
16
|
+
import { useRiskPoliciesStore } from '~/stores/riskPolicies'
|
|
17
17
|
import { useSharedStacksStore } from '~/stores/sharedStacks'
|
|
18
18
|
import { useWorkspaceSettingsStore } from '~/stores/workspaceSettings'
|
|
19
19
|
import { useAgentConfigStore } from '~/stores/agentConfig'
|
|
@@ -120,9 +120,9 @@ export const useWorkspaceStore = defineStore(
|
|
|
120
120
|
useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [], snapshot.workspace.id)
|
|
121
121
|
useAgentRunsStore().hydrateEnvConfigRepair(snapshot.envConfigRepairJobs ?? [])
|
|
122
122
|
useNotificationsStore().hydrate(snapshot.notifications ?? [])
|
|
123
|
-
|
|
124
|
-
snapshot.
|
|
125
|
-
snapshot.
|
|
123
|
+
useRiskPoliciesStore().hydrate(
|
|
124
|
+
snapshot.riskPolicies ?? [],
|
|
125
|
+
snapshot.riskPolicyCatalogVersions,
|
|
126
126
|
)
|
|
127
127
|
useSharedStacksStore().hydrate(snapshot.sharedStacks ?? [])
|
|
128
128
|
useWorkspaceSettingsStore().hydrate(snapshot.settings)
|
package/app/types/merge.ts
CHANGED