@cat-factory/app 0.257.0 → 0.258.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/binaryOutput/BinaryOutputReport.vue +42 -0
- package/app/components/board/AddTaskModal.vue +38 -12
- package/app/components/board/RecurringPipelineModal.vue +24 -12
- package/app/components/board/nodes/TaskCard.vue +34 -7
- package/app/components/panels/InspectorPanel.vue +39 -0
- package/app/components/pipeline/BinaryOutputStepPicker.logic.spec.ts +29 -0
- package/app/components/pipeline/BinaryOutputStepPicker.logic.ts +23 -0
- package/app/components/pipeline/BinaryOutputStepPicker.vue +109 -0
- package/app/components/pipeline/PipelineBuilder.vue +44 -0
- package/app/components/pipeline/PipelinePreview.vue +20 -1
- package/app/components/pipeline/PipelineProgress.vue +13 -0
- package/app/composables/api/execution.ts +19 -0
- package/app/composables/usePipelineHealth.spec.ts +42 -5
- package/app/composables/usePipelineHealth.ts +109 -48
- package/app/modular/agent-kinds.ts +5 -0
- package/app/stores/environmentWizard/context.ts +0 -2
- package/app/stores/environmentWizard/flow.ts +11 -6
- package/app/stores/environmentWizard.ts +12 -11
- package/app/stores/execution/commands.ts +26 -1
- package/app/stores/pipelines/draftActions.ts +2 -0
- package/app/stores/pipelines/draftStepConfig.ts +4 -161
- package/app/stores/pipelines/draftStepOptions.ts +204 -0
- package/app/types/domain.ts +6 -0
- package/app/utils/agentPalette.spec.ts +26 -0
- package/app/utils/agentPalette.ts +9 -4
- package/app/utils/binaryOutput.spec.ts +122 -1
- package/app/utils/binaryOutput.ts +101 -0
- package/app/utils/catalog.spec.ts +24 -0
- package/app/utils/catalog.ts +21 -4
- package/app/utils/pipeline.spec.ts +35 -3
- package/app/utils/pipeline.ts +54 -3
- package/app/utils/pipelineRender.spec.ts +78 -2
- package/app/utils/pipelineRender.ts +43 -0
- package/i18n/locales/de.json +29 -1
- package/i18n/locales/en.json +29 -1
- package/i18n/locales/es.json +29 -1
- package/i18n/locales/fr.json +29 -1
- package/i18n/locales/he.json +29 -1
- package/i18n/locales/it.json +29 -1
- package/i18n/locales/ja.json +29 -1
- package/i18n/locales/pl.json +29 -1
- package/i18n/locales/tr.json +29 -1
- package/i18n/locales/uk.json +29 -1
- package/package.json +2 -2
|
@@ -1,41 +1,19 @@
|
|
|
1
|
-
import type { BinaryOutputConfig, StepOptions } from '@cat-factory/contracts'
|
|
2
1
|
import type { ConsensusStepConfig } from '~/types/consensus'
|
|
3
2
|
import { defaultConsensusConfig, type PipelinesContext } from './context'
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* The pipeline-builder draft's PER-STEP CONFIG toggles: consensus (inline panel and the workspace
|
|
7
6
|
* consensus-GROUP tier set), the human approval gate, the estimate gate on a companion step, the
|
|
8
|
-
* follow-up and test-QC companions, the per-step enable flag
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* follow-up and test-QC companions, and the per-step enable flag — every toggle that writes a
|
|
8
|
+
* PARALLEL ARRAY. The `StepOptions` bag accessors are the sibling `./draftStepOptions`, and the
|
|
9
|
+
* step's GATE configuration `./draftGateConfig`; each is split along the state it writes rather
|
|
10
|
+
* than an arbitrary line count.
|
|
12
11
|
*
|
|
13
12
|
* Split out of `./draftActions`, which owns the draft's STRUCTURE (insert / remove / reorder /
|
|
14
13
|
* units). Every function here reads and writes one of the parallel per-step arrays at an index and
|
|
15
14
|
* touches nothing else, which is what makes the two independent; both are spread into the store,
|
|
16
15
|
* so the store's API is unchanged.
|
|
17
16
|
*/
|
|
18
|
-
/**
|
|
19
|
-
* Write ONE field of the step's `StepOptions` bag at `index`, merging into whatever else that step
|
|
20
|
-
* carries and normalizing an emptied bag back to `null`. `undefined` CLEARS the field.
|
|
21
|
-
*
|
|
22
|
-
* Every per-step option below goes through this rather than repeating the clone/assign/normalize
|
|
23
|
-
* dance, because the two halves that are easy to get wrong are shared by all of them: replacing
|
|
24
|
-
* the bag loses the neighbouring options a step may also carry, and leaving a `{}` behind makes a
|
|
25
|
-
* step that is back on every default persist a shape it never had.
|
|
26
|
-
*/
|
|
27
|
-
function patchStepOption<K extends keyof StepOptions>(
|
|
28
|
-
draftStepOptions: PipelinesContext['draftStepOptions'],
|
|
29
|
-
index: number,
|
|
30
|
-
key: K,
|
|
31
|
-
value: StepOptions[K] | undefined,
|
|
32
|
-
) {
|
|
33
|
-
const next: StepOptions = { ...draftStepOptions.value[index] }
|
|
34
|
-
if (value === undefined) delete next[key]
|
|
35
|
-
else next[key] = value
|
|
36
|
-
draftStepOptions.value[index] = Object.keys(next).length ? next : null
|
|
37
|
-
}
|
|
38
|
-
|
|
39
17
|
export function createPipelineStepConfigActions(ctx: PipelinesContext) {
|
|
40
18
|
const {
|
|
41
19
|
draftGates,
|
|
@@ -44,7 +22,6 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
|
|
|
44
22
|
draftGating,
|
|
45
23
|
draftFollowUps,
|
|
46
24
|
draftTesterQuality,
|
|
47
|
-
draftStepOptions,
|
|
48
25
|
} = ctx
|
|
49
26
|
|
|
50
27
|
/** Toggle estimate gating on/off for the (companion) step at `index`. */
|
|
@@ -128,128 +105,6 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
|
|
|
128
105
|
draftEnabled.value[index] = draftEnabled.value[index] === false
|
|
129
106
|
}
|
|
130
107
|
|
|
131
|
-
/** Whether auto-recommendation is on for the draft (requirements-review) step at `index`. */
|
|
132
|
-
function draftAutoRecommendEnabled(index: number): boolean {
|
|
133
|
-
return draftStepOptions.value[index]?.autoRecommend !== false
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Toggle the requirements-review auto-recommendation on the draft step at `index`. It is on by
|
|
138
|
-
* default, so we store ONLY the opt-out (`{ autoRecommend: false }`); toggling back drops the
|
|
139
|
-
* flag. Merges with any other future StepOptions fields rather than clobbering the whole bag.
|
|
140
|
-
*/
|
|
141
|
-
function toggleDraftAutoRecommend(index: number) {
|
|
142
|
-
const off = draftAutoRecommendEnabled(index) ? false : undefined
|
|
143
|
-
patchStepOption(draftStepOptions, index, 'autoRecommend', off)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Whether the draft `deployer` step at `index` declares that its environments outlive the run
|
|
148
|
-
* (its `stepOptions.retainEnvironment`). OFF by default: the everyday shape is a run that
|
|
149
|
-
* reclaims what it stood up, and the save boundary refuses a Deployer that does neither.
|
|
150
|
-
*/
|
|
151
|
-
function draftRetainEnvironment(index: number): boolean {
|
|
152
|
-
return draftStepOptions.value[index]?.retainEnvironment === true
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Toggle the retain declaration on the draft `deployer` step at `index`. It is off by default,
|
|
157
|
-
* so we store ONLY the opt-in (the mirror of `toggleDraftAutoRecommend`, which stores only the
|
|
158
|
-
* opt-out); toggling back drops the flag and, if the bag empties, the whole entry.
|
|
159
|
-
*/
|
|
160
|
-
function toggleDraftRetainEnvironment(index: number) {
|
|
161
|
-
const on = draftRetainEnvironment(index) ? undefined : true
|
|
162
|
-
patchStepOption(draftStepOptions, index, 'retainEnvironment', on)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** The skill picked for the draft `skill` step at `index` (its `stepOptions.skillId`). */
|
|
166
|
-
function draftSkillId(index: number): string | undefined {
|
|
167
|
-
return draftStepOptions.value[index]?.skillId
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Set (or clear) the picked skill on the draft `skill` step at `index`. Merges into the
|
|
172
|
-
* step's `StepOptions` bag rather than clobbering it; clearing drops the field and, if the
|
|
173
|
-
* bag empties, the whole entry (so it normalizes away like the other options).
|
|
174
|
-
*/
|
|
175
|
-
function setDraftSkillId(index: number, skillId: string | undefined) {
|
|
176
|
-
patchStepOption(draftStepOptions, index, 'skillId', skillId || undefined)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* The agent-kind VARIANT picked for the draft step at `index` (its
|
|
181
|
-
* `stepOptions.agentVariantId`), or undefined when it runs the kind's shipped prompt.
|
|
182
|
-
*/
|
|
183
|
-
function draftAgentVariantId(index: number): string | undefined {
|
|
184
|
-
return draftStepOptions.value[index]?.agentVariantId
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Set (or clear) the picked variant on the draft step at `index`. Merges into the step's
|
|
189
|
-
* `StepOptions` bag rather than clobbering it; clearing drops the field and, if the bag
|
|
190
|
-
* empties, the whole entry — exactly like the other options here, so a step back on the
|
|
191
|
-
* shipped prompt persists nothing.
|
|
192
|
-
*/
|
|
193
|
-
function setDraftAgentVariantId(index: number, agentVariantId: string | undefined) {
|
|
194
|
-
patchStepOption(draftStepOptions, index, 'agentVariantId', agentVariantId || undefined)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* The binary-output SELECTION on the draft step at `index` (its `stepOptions.binaryOutput`) —
|
|
199
|
-
* the foundational storage service a generator kind's artifacts are stored through, plus any
|
|
200
|
-
* services consulted for the generation's scope. Undefined on every step of every stock
|
|
201
|
-
* pipeline; required on a step whose kind carries the `binary-output` trait.
|
|
202
|
-
*/
|
|
203
|
-
function draftBinaryOutput(index: number): BinaryOutputConfig | undefined {
|
|
204
|
-
return draftStepOptions.value[index]?.binaryOutput
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Set (or clear) the binary-output selection on the draft step at `index`. Merges into the
|
|
209
|
-
* step's `StepOptions` bag rather than clobbering it; clearing drops the field and, if the
|
|
210
|
-
* bag empties, the whole entry — exactly like the other options here, so a step that never
|
|
211
|
-
* used it persists the shape it always did.
|
|
212
|
-
*
|
|
213
|
-
* An EMPTY `contextServiceIds` is dropped rather than stored, for the reason the consensus
|
|
214
|
-
* tier set drops its own empty array: the field's absence means "no scope service was
|
|
215
|
-
* selected", while `[]` reads as "context was considered and rejected" — a different claim,
|
|
216
|
-
* and one the brief renderer would repeat to the agent. `generatorIds` and `modalities` take
|
|
217
|
-
* the same treatment for the same reason: an absent `generatorIds` means the step generates
|
|
218
|
-
* through whatever its agent already has, and an absent `modalities` imposes no delivery
|
|
219
|
-
* requirement — both of which the brief STATES, so persisting `[]` would have it state the
|
|
220
|
-
* wrong thing.
|
|
221
|
-
*/
|
|
222
|
-
function setDraftBinaryOutput(index: number, config: BinaryOutputConfig | undefined) {
|
|
223
|
-
const { storageServiceId, contextServiceIds, generatorIds, modalities } = config ?? {}
|
|
224
|
-
const selection = storageServiceId
|
|
225
|
-
? {
|
|
226
|
-
storageServiceId,
|
|
227
|
-
...(contextServiceIds?.length ? { contextServiceIds } : {}),
|
|
228
|
-
...(generatorIds?.length ? { generatorIds } : {}),
|
|
229
|
-
...(modalities?.length ? { modalities } : {}),
|
|
230
|
-
}
|
|
231
|
-
: undefined
|
|
232
|
-
patchStepOption(draftStepOptions, index, 'binaryOutput', selection)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* The output-token ceiling pinned on the draft step at `index`, or undefined when the step
|
|
237
|
-
* inherits (the workspace's per-kind setting, else the deployment default).
|
|
238
|
-
*/
|
|
239
|
-
function draftMaxOutputTokens(index: number): number | undefined {
|
|
240
|
-
return draftStepOptions.value[index]?.maxOutputTokens
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Set (or clear) this step's own output-token ceiling. Merges into the step's `StepOptions`
|
|
245
|
-
* bag rather than clobbering it; clearing drops the field and, if the bag empties, the whole
|
|
246
|
-
* entry — so a step back on the inherited budget persists no options at all, exactly like the
|
|
247
|
-
* other fields here.
|
|
248
|
-
*/
|
|
249
|
-
function setDraftMaxOutputTokens(index: number, maxOutputTokens: number | undefined) {
|
|
250
|
-
patchStepOption(draftStepOptions, index, 'maxOutputTokens', maxOutputTokens ?? undefined)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
108
|
return {
|
|
254
109
|
toggleDraftGating,
|
|
255
110
|
toggleDraftConsensus,
|
|
@@ -260,17 +115,5 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
|
|
|
260
115
|
toggleDraftTesterQuality,
|
|
261
116
|
toggleDraftTesterQualityGating,
|
|
262
117
|
toggleDraftEnabled,
|
|
263
|
-
draftAutoRecommendEnabled,
|
|
264
|
-
toggleDraftAutoRecommend,
|
|
265
|
-
draftRetainEnvironment,
|
|
266
|
-
toggleDraftRetainEnvironment,
|
|
267
|
-
draftSkillId,
|
|
268
|
-
setDraftSkillId,
|
|
269
|
-
draftAgentVariantId,
|
|
270
|
-
setDraftAgentVariantId,
|
|
271
|
-
draftBinaryOutput,
|
|
272
|
-
setDraftBinaryOutput,
|
|
273
|
-
draftMaxOutputTokens,
|
|
274
|
-
setDraftMaxOutputTokens,
|
|
275
118
|
}
|
|
276
119
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { BinaryOutputConfig, StepOptions, StepServiceScope } from '@cat-factory/contracts'
|
|
2
|
+
import type { PipelinesContext } from './context'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The pipeline-builder draft's `StepOptions` BAG accessors: every per-step parameter that lives in
|
|
6
|
+
* the one extensible options object rather than in a parallel array of its own — the requirements
|
|
7
|
+
* auto-recommendation, the Deployer's retain declaration, the step's run CONDITION, the picked
|
|
8
|
+
* skill, the picked agent-kind variant, the binary-output selection, and the per-step output-token
|
|
9
|
+
* ceiling.
|
|
10
|
+
*
|
|
11
|
+
* Its own module for the reason `./draftGateConfig` is: the sibling `./draftStepConfig` owns the
|
|
12
|
+
* per-step TOGGLES that each write a parallel array, while every function here reads and writes
|
|
13
|
+
* one FIELD of one bag through {@link patchStepOption}. The split is along that seam rather than
|
|
14
|
+
* an arbitrary line count, so a new per-step knob has an obvious home — and since the bag is the
|
|
15
|
+
* declared home for every NEW knob, this is the half that keeps growing.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Write ONE field of the step's `StepOptions` bag at `index`, merging into whatever else that step
|
|
20
|
+
* carries and normalizing an emptied bag back to `null`. `undefined` CLEARS the field.
|
|
21
|
+
*
|
|
22
|
+
* Every per-step option below goes through this rather than repeating the clone/assign/normalize
|
|
23
|
+
* dance, because the two halves that are easy to get wrong are shared by all of them: replacing
|
|
24
|
+
* the bag loses the neighbouring options a step may also carry, and leaving a `{}` behind makes a
|
|
25
|
+
* step that is back on every default persist a shape it never had.
|
|
26
|
+
*/
|
|
27
|
+
function patchStepOption<K extends keyof StepOptions>(
|
|
28
|
+
draftStepOptions: PipelinesContext['draftStepOptions'],
|
|
29
|
+
index: number,
|
|
30
|
+
key: K,
|
|
31
|
+
value: StepOptions[K] | undefined,
|
|
32
|
+
) {
|
|
33
|
+
const next: StepOptions = { ...draftStepOptions.value[index] }
|
|
34
|
+
if (value === undefined) delete next[key]
|
|
35
|
+
else next[key] = value
|
|
36
|
+
draftStepOptions.value[index] = Object.keys(next).length ? next : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createPipelineStepOptionActions(ctx: PipelinesContext) {
|
|
40
|
+
const { draftStepOptions } = ctx
|
|
41
|
+
|
|
42
|
+
/** Whether auto-recommendation is on for the draft (requirements-review) step at `index`. */
|
|
43
|
+
function draftAutoRecommendEnabled(index: number): boolean {
|
|
44
|
+
return draftStepOptions.value[index]?.autoRecommend !== false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Toggle the requirements-review auto-recommendation on the draft step at `index`. It is on by
|
|
49
|
+
* default, so we store ONLY the opt-out (`{ autoRecommend: false }`); toggling back drops the
|
|
50
|
+
* flag. Merges with any other future StepOptions fields rather than clobbering the whole bag.
|
|
51
|
+
*/
|
|
52
|
+
function toggleDraftAutoRecommend(index: number) {
|
|
53
|
+
const off = draftAutoRecommendEnabled(index) ? false : undefined
|
|
54
|
+
patchStepOption(draftStepOptions, index, 'autoRecommend', off)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether the draft `deployer` step at `index` declares that its environments outlive the run
|
|
59
|
+
* (its `stepOptions.retainEnvironment`). OFF by default: the everyday shape is a run that
|
|
60
|
+
* reclaims what it stood up, and the save boundary refuses a Deployer that does neither.
|
|
61
|
+
*/
|
|
62
|
+
function draftRetainEnvironment(index: number): boolean {
|
|
63
|
+
return draftStepOptions.value[index]?.retainEnvironment === true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Toggle the retain declaration on the draft `deployer` step at `index`. It is off by default,
|
|
68
|
+
* so we store ONLY the opt-in (the mirror of `toggleDraftAutoRecommend`, which stores only the
|
|
69
|
+
* opt-out); toggling back drops the flag and, if the bag empties, the whole entry.
|
|
70
|
+
*/
|
|
71
|
+
function toggleDraftRetainEnvironment(index: number) {
|
|
72
|
+
const on = draftRetainEnvironment(index) ? undefined : true
|
|
73
|
+
patchStepOption(draftStepOptions, index, 'retainEnvironment', on)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The RUN CONDITION on the draft step at `index` — the service scope it applies to, or
|
|
78
|
+
* `undefined` when it runs on every task (`stepOptions.condition`).
|
|
79
|
+
*/
|
|
80
|
+
function draftStepCondition(index: number): StepServiceScope | undefined {
|
|
81
|
+
return draftStepOptions.value[index]?.condition?.serviceScope
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Cycle the draft step's run condition: unconditional → frontend-only → backend-only →
|
|
86
|
+
* unconditional.
|
|
87
|
+
*
|
|
88
|
+
* A CYCLE rather than a picker because the states are three and mutually exclusive, and the
|
|
89
|
+
* control has to live in a dense per-step icon row beside eight others. It cycles back to
|
|
90
|
+
* unconditional deliberately: a condition arrives on a step by CLONING a built-in (the tester
|
|
91
|
+
* pair), so the state a user most needs to reach from here is the one that clears it.
|
|
92
|
+
*/
|
|
93
|
+
function cycleDraftStepCondition(index: number) {
|
|
94
|
+
const current = draftStepCondition(index)
|
|
95
|
+
const next: StepServiceScope | undefined =
|
|
96
|
+
current === undefined ? 'frontend' : current === 'frontend' ? 'backend' : undefined
|
|
97
|
+
patchStepOption(draftStepOptions, index, 'condition', next ? { serviceScope: next } : undefined)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The skill picked for the draft `skill` step at `index` (its `stepOptions.skillId`). */
|
|
101
|
+
function draftSkillId(index: number): string | undefined {
|
|
102
|
+
return draftStepOptions.value[index]?.skillId
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Set (or clear) the picked skill on the draft `skill` step at `index`. Merges into the
|
|
107
|
+
* step's `StepOptions` bag rather than clobbering it; clearing drops the field and, if the
|
|
108
|
+
* bag empties, the whole entry (so it normalizes away like the other options).
|
|
109
|
+
*/
|
|
110
|
+
function setDraftSkillId(index: number, skillId: string | undefined) {
|
|
111
|
+
patchStepOption(draftStepOptions, index, 'skillId', skillId || undefined)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The agent-kind VARIANT picked for the draft step at `index` (its
|
|
116
|
+
* `stepOptions.agentVariantId`), or undefined when it runs the kind's shipped prompt.
|
|
117
|
+
*/
|
|
118
|
+
function draftAgentVariantId(index: number): string | undefined {
|
|
119
|
+
return draftStepOptions.value[index]?.agentVariantId
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set (or clear) the picked variant on the draft step at `index`. Merges into the step's
|
|
124
|
+
* `StepOptions` bag rather than clobbering it; clearing drops the field and, if the bag
|
|
125
|
+
* empties, the whole entry — exactly like the other options here, so a step back on the
|
|
126
|
+
* shipped prompt persists nothing.
|
|
127
|
+
*/
|
|
128
|
+
function setDraftAgentVariantId(index: number, agentVariantId: string | undefined) {
|
|
129
|
+
patchStepOption(draftStepOptions, index, 'agentVariantId', agentVariantId || undefined)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The binary-output SELECTION on the draft step at `index` (its `stepOptions.binaryOutput`) —
|
|
134
|
+
* the foundational storage service a generator kind's artifacts are stored through, plus any
|
|
135
|
+
* services consulted for the generation's scope. Undefined on every step of every stock
|
|
136
|
+
* pipeline; required on a step whose kind carries the `binary-output` trait.
|
|
137
|
+
*/
|
|
138
|
+
function draftBinaryOutput(index: number): BinaryOutputConfig | undefined {
|
|
139
|
+
return draftStepOptions.value[index]?.binaryOutput
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Set (or clear) the binary-output selection on the draft step at `index`. Merges into the
|
|
144
|
+
* step's `StepOptions` bag rather than clobbering it; clearing drops the field and, if the
|
|
145
|
+
* bag empties, the whole entry — exactly like the other options here, so a step that never
|
|
146
|
+
* used it persists the shape it always did.
|
|
147
|
+
*
|
|
148
|
+
* An EMPTY `contextServiceIds` is dropped rather than stored, for the reason the consensus
|
|
149
|
+
* tier set drops its own empty array: the field's absence means "no scope service was
|
|
150
|
+
* selected", while `[]` reads as "context was considered and rejected" — a different claim,
|
|
151
|
+
* and one the brief renderer would repeat to the agent. `generatorIds` and `modalities` take
|
|
152
|
+
* the same treatment for the same reason: an absent `generatorIds` means the step generates
|
|
153
|
+
* through whatever its agent already has, and an absent `modalities` imposes no delivery
|
|
154
|
+
* requirement — both of which the brief STATES, so persisting `[]` would have it state the
|
|
155
|
+
* wrong thing.
|
|
156
|
+
*/
|
|
157
|
+
function setDraftBinaryOutput(index: number, config: BinaryOutputConfig | undefined) {
|
|
158
|
+
const { storageServiceId, contextServiceIds, generatorIds, modalities } = config ?? {}
|
|
159
|
+
const selection = storageServiceId
|
|
160
|
+
? {
|
|
161
|
+
storageServiceId,
|
|
162
|
+
...(contextServiceIds?.length ? { contextServiceIds } : {}),
|
|
163
|
+
...(generatorIds?.length ? { generatorIds } : {}),
|
|
164
|
+
...(modalities?.length ? { modalities } : {}),
|
|
165
|
+
}
|
|
166
|
+
: undefined
|
|
167
|
+
patchStepOption(draftStepOptions, index, 'binaryOutput', selection)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The output-token ceiling pinned on the draft step at `index`, or undefined when the step
|
|
172
|
+
* inherits (the workspace's per-kind setting, else the deployment default).
|
|
173
|
+
*/
|
|
174
|
+
function draftMaxOutputTokens(index: number): number | undefined {
|
|
175
|
+
return draftStepOptions.value[index]?.maxOutputTokens
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Set (or clear) this step's own output-token ceiling. Merges into the step's `StepOptions`
|
|
180
|
+
* bag rather than clobbering it; clearing drops the field and, if the bag empties, the whole
|
|
181
|
+
* entry — so a step back on the inherited budget persists no options at all, exactly like the
|
|
182
|
+
* other fields here.
|
|
183
|
+
*/
|
|
184
|
+
function setDraftMaxOutputTokens(index: number, maxOutputTokens: number | undefined) {
|
|
185
|
+
patchStepOption(draftStepOptions, index, 'maxOutputTokens', maxOutputTokens ?? undefined)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
draftAutoRecommendEnabled,
|
|
190
|
+
toggleDraftAutoRecommend,
|
|
191
|
+
draftRetainEnvironment,
|
|
192
|
+
toggleDraftRetainEnvironment,
|
|
193
|
+
draftStepCondition,
|
|
194
|
+
cycleDraftStepCondition,
|
|
195
|
+
draftSkillId,
|
|
196
|
+
setDraftSkillId,
|
|
197
|
+
draftAgentVariantId,
|
|
198
|
+
setDraftAgentVariantId,
|
|
199
|
+
draftBinaryOutput,
|
|
200
|
+
setDraftBinaryOutput,
|
|
201
|
+
draftMaxOutputTokens,
|
|
202
|
+
setDraftMaxOutputTokens,
|
|
203
|
+
}
|
|
204
|
+
}
|
package/app/types/domain.ts
CHANGED
|
@@ -164,6 +164,12 @@ export interface AgentArchetype {
|
|
|
164
164
|
* which is every built-in kind.
|
|
165
165
|
*/
|
|
166
166
|
binaryOutput?: boolean
|
|
167
|
+
/**
|
|
168
|
+
* The platform dispatches this kind for a flow of its own, so the builder palette never offers
|
|
169
|
+
* it as a placeable block (`narrowAgentPalette` drops it). It still resolves through
|
|
170
|
+
* `agentKindMeta`, because a run of it has to RENDER. Absent ⇒ an ordinary palette block.
|
|
171
|
+
*/
|
|
172
|
+
internal?: boolean
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
/**
|
|
@@ -33,6 +33,32 @@ const CATALOG: AgentArchetype[] = [
|
|
|
33
33
|
archetype('acme-auditor'),
|
|
34
34
|
]
|
|
35
35
|
|
|
36
|
+
describe('narrowAgentPalette — internal kinds', () => {
|
|
37
|
+
// An INTERNAL kind is one the platform dispatches for a flow of its own (the environment
|
|
38
|
+
// analyst, which hands its draft to the setup wizard). It is not hidden by a dial, it is not a
|
|
39
|
+
// palette block at all.
|
|
40
|
+
const internal: AgentArchetype = {
|
|
41
|
+
...archetype('environment-analyst', 'design', 'basic'),
|
|
42
|
+
internal: true,
|
|
43
|
+
}
|
|
44
|
+
const catalog = [archetype('architect', 'design', 'basic'), internal]
|
|
45
|
+
|
|
46
|
+
it('never offers one, at any purpose or tier', () => {
|
|
47
|
+
for (const tier of ['basic', 'intermediate', 'advanced'] as const) {
|
|
48
|
+
const { offered } = narrowAgentPalette(catalog, 'build', tier)
|
|
49
|
+
expect(offered.map((a) => a.kind)).toEqual(['architect'])
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('counts one against NEITHER dial, so no hint promises a control that would reveal it', () => {
|
|
54
|
+
// The whole point of the counts is "relax THIS dial and you get n more". An internal kind is
|
|
55
|
+
// revealed by neither, so counting it would send a reader chasing a control that cannot help.
|
|
56
|
+
const { hiddenByPurpose, hiddenByTier } = narrowAgentPalette(catalog, 'review', 'basic')
|
|
57
|
+
expect(hiddenByPurpose).toBe(1) // the architect alone: a review pipeline designs nothing
|
|
58
|
+
expect(hiddenByTier).toBe(0)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
36
62
|
describe('narrowAgentPalette', () => {
|
|
37
63
|
it('narrows nothing for a build pipeline at the widest tier', () => {
|
|
38
64
|
const { offered, hiddenByPurpose, hiddenByTier } = narrowAgentPalette(
|
|
@@ -44,14 +44,19 @@ export interface NarrowedAgentPalette<T> {
|
|
|
44
44
|
* nothing behaves, and none of them is a reason to drop it from the catalog.
|
|
45
45
|
*/
|
|
46
46
|
export function narrowAgentPalette<
|
|
47
|
-
T extends Pick<AgentArchetype, 'tier' | 'category' | 'purposes'>,
|
|
47
|
+
T extends Pick<AgentArchetype, 'tier' | 'category' | 'purposes' | 'internal'>,
|
|
48
48
|
>(archetypes: readonly T[], purpose: PipelinePurpose, tier: AgentTier): NarrowedAgentPalette<T> {
|
|
49
|
+
// INTERNAL kinds are removed BEFORE either dial, and are counted by neither. They are not
|
|
50
|
+
// hidden, they are not placeable at all: the platform dispatches them for a flow of its own
|
|
51
|
+
// (the environment analyst hands its draft to the setup wizard), so counting one as
|
|
52
|
+
// "3 more at the widest tier" would send a reader to a dial that will never reveal it.
|
|
53
|
+
const placeable = archetypes.filter((a) => !a.internal)
|
|
49
54
|
const relevant = (a: T) => purposeSuggestsAgentKind(purpose, a)
|
|
50
55
|
const inTier = (a: T) => agentTierVisibleAt(a.tier, tier)
|
|
51
56
|
return {
|
|
52
|
-
offered:
|
|
53
|
-
hiddenByPurpose:
|
|
54
|
-
hiddenByTier:
|
|
57
|
+
offered: placeable.filter((a) => relevant(a) && inTier(a)),
|
|
58
|
+
hiddenByPurpose: placeable.filter((a) => inTier(a) && !relevant(a)).length,
|
|
59
|
+
hiddenByTier: placeable.filter((a) => relevant(a) && !inTier(a)).length,
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import type { BinaryOutputReport, PipelineStep } from '~/types/execution'
|
|
2
|
+
import type { BinaryOutputArtifact, BinaryOutputReport, PipelineStep } from '~/types/execution'
|
|
3
3
|
import {
|
|
4
4
|
BINARY_OUTPUT_STATE_KEYS,
|
|
5
5
|
binaryOutputHasWarnings,
|
|
@@ -371,6 +371,95 @@ describe('the delivered-format check', () => {
|
|
|
371
371
|
})
|
|
372
372
|
})
|
|
373
373
|
|
|
374
|
+
// The size axis's delivery-side half. Admission checked what the selected integrations can be
|
|
375
|
+
// ASKED for; only this checks what came back, which is the failure the requirement exists for:
|
|
376
|
+
// an asset stored at the wrong size passes every other check on this panel.
|
|
377
|
+
describe('binaryOutputView, the delivered size', () => {
|
|
378
|
+
const sized = (
|
|
379
|
+
outputSize: { width: number; height: number } | undefined,
|
|
380
|
+
stored: {
|
|
381
|
+
location: string
|
|
382
|
+
dimensions?: { width: number; height: number }
|
|
383
|
+
modality?: BinaryOutputArtifact['modality']
|
|
384
|
+
}[],
|
|
385
|
+
) =>
|
|
386
|
+
binaryOutputView(
|
|
387
|
+
step({
|
|
388
|
+
stepOptions: {
|
|
389
|
+
binaryOutput: {
|
|
390
|
+
storageServiceId: 'files',
|
|
391
|
+
...(outputSize ? { generation: { outputSize } } : {}),
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
binaryOutputs: report({
|
|
395
|
+
stored: stored.map((entry) => ({ service: 'files', ...entry })),
|
|
396
|
+
}),
|
|
397
|
+
}),
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
it('counts an artifact delivered at another size, and marks its row', () => {
|
|
401
|
+
const view = sized({ width: 96, height: 96 }, [
|
|
402
|
+
{ location: 'a.png', dimensions: { width: 96, height: 96 } },
|
|
403
|
+
{ location: 'b.png', dimensions: { width: 1024, height: 1024 } },
|
|
404
|
+
])
|
|
405
|
+
expect(view?.requiredSize).toEqual({ width: 96, height: 96 })
|
|
406
|
+
expect(view?.missized).toBe(1)
|
|
407
|
+
expect(view?.rows.map((row) => row.missized)).toEqual([false, true])
|
|
408
|
+
expect(binaryOutputHasWarnings(view!)).toBe(true)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('counts an UNMEASURED artifact apart, never as a delivered one', () => {
|
|
412
|
+
// The rule this whole feature runs on: "we were not told" and "it came back wrong" are the
|
|
413
|
+
// same value and opposite facts. Folded together, a run that reported nothing would read as
|
|
414
|
+
// one that delivered everything wrong; dropped, it would read as a clean one.
|
|
415
|
+
const view = sized({ width: 96, height: 96 }, [{ location: 'a.png' }])
|
|
416
|
+
expect(view?.missized).toBe(0)
|
|
417
|
+
expect(view?.sizeUnreported).toBe(1)
|
|
418
|
+
expect(binaryOutputHasWarnings(view!)).toBe(true)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('judges nothing when the step asked for no size', () => {
|
|
422
|
+
const view = sized(undefined, [
|
|
423
|
+
{ location: 'a.png', dimensions: { width: 1024, height: 1024 } },
|
|
424
|
+
{ location: 'b.png' },
|
|
425
|
+
])
|
|
426
|
+
expect(view?.requiredSize).toBeNull()
|
|
427
|
+
expect(view?.missized).toBe(0)
|
|
428
|
+
expect(view?.sizeUnreported).toBe(0)
|
|
429
|
+
expect(binaryOutputHasWarnings(view!)).toBe(false)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
// A size is a statement about what is MEASURED in pixels. A step that generates the icon and
|
|
433
|
+
// its pickup sound delivered exactly what was asked, and counting the audio as unmeasured
|
|
434
|
+
// would warn about it forever, on a panel whose whole discipline is that a warning means
|
|
435
|
+
// something went wrong.
|
|
436
|
+
it('says nothing about an artifact a size cannot describe', () => {
|
|
437
|
+
const view = sized({ width: 96, height: 96 }, [
|
|
438
|
+
{ location: 'a.png', modality: 'image', dimensions: { width: 96, height: 96 } },
|
|
439
|
+
{ location: 'a.mp3', modality: 'audio' },
|
|
440
|
+
{ location: 'a.glb', modality: '3d-model' },
|
|
441
|
+
])
|
|
442
|
+
expect(view?.missized).toBe(0)
|
|
443
|
+
expect(view?.sizeUnreported).toBe(0)
|
|
444
|
+
expect(binaryOutputHasWarnings(view!)).toBe(false)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
// The other direction, and the one that must not go quiet: an artifact the platform could not
|
|
448
|
+
// CLASSIFY may well be an image, so it stays covered. Excluding it would turn an unreadable
|
|
449
|
+
// content type into a silent pass on the one axis the requirement exists to check, and a
|
|
450
|
+
// RETIRED modality a stale run carries reads the same way.
|
|
451
|
+
it('keeps an unclassified artifact under the requirement', () => {
|
|
452
|
+
const view = sized({ width: 96, height: 96 }, [
|
|
453
|
+
{ location: 'a.bin' },
|
|
454
|
+
// A modality the union no longer has, which a run saved before the 3D split goes on
|
|
455
|
+
// carrying. Cast because the TYPE cannot express it and the DATA can, which is the whole
|
|
456
|
+
// reason the read narrows through `isBinaryModality` before it looks anything up.
|
|
457
|
+
{ location: 'b.bin', modality: '3d' as BinaryOutputArtifact['modality'] },
|
|
458
|
+
])
|
|
459
|
+
expect(view?.sizeUnreported).toBe(2)
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
374
463
|
describe('binaryOutputPickIssues, generative half', () => {
|
|
375
464
|
const catalog = [{ id: 'files', capabilities: ['asset-storage'] }]
|
|
376
465
|
const generators = [
|
|
@@ -400,6 +489,38 @@ describe('binaryOutputPickIssues, generative half', () => {
|
|
|
400
489
|
expect(pick.uncoveredModalities).toEqual(['audio'])
|
|
401
490
|
})
|
|
402
491
|
|
|
492
|
+
// The builder offers a ratio, an exact size and an upscale together, so the conflict it can
|
|
493
|
+
// save is one it must state: reported only by the failed round trip, the fix (delete one of two
|
|
494
|
+
// fields on this form) arrives as backend prose about a step the reader has already left.
|
|
495
|
+
it('states the save refusal for two statements of one delivered size', () => {
|
|
496
|
+
const pick = binaryOutputPickIssues(
|
|
497
|
+
{
|
|
498
|
+
storageServiceId: 'files',
|
|
499
|
+
generatorIds: ['retro'],
|
|
500
|
+
generation: { outputSize: { width: 96, height: 96 }, aspectRatio: '16:9' },
|
|
501
|
+
},
|
|
502
|
+
catalog,
|
|
503
|
+
true,
|
|
504
|
+
generators,
|
|
505
|
+
)
|
|
506
|
+
expect(pick.issues).toContain('output_size_ambiguous')
|
|
507
|
+
expect(pick.conflictingSizeOptions).toEqual(['aspectRatio'])
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
// A step with no storage pick still gets the whole list: the two halves resolve against
|
|
511
|
+
// different registries, and reporting them one round at a time is the retry cycle this
|
|
512
|
+
// function returns every issue together to avoid.
|
|
513
|
+
it('states it on a step whose storage is not picked either', () => {
|
|
514
|
+
const pick = binaryOutputPickIssues(
|
|
515
|
+
{ storageServiceId: '', generation: { outputSize: { width: 96, height: 96 }, upscale: 2 } },
|
|
516
|
+
catalog,
|
|
517
|
+
true,
|
|
518
|
+
generators,
|
|
519
|
+
)
|
|
520
|
+
expect(pick.issues).toEqual(expect.arrayContaining(['not_selected', 'output_size_ambiguous']))
|
|
521
|
+
expect(pick.conflictingSizeOptions).toEqual(['upscale'])
|
|
522
|
+
})
|
|
523
|
+
|
|
403
524
|
it('reports BOTH faults when an unknown id was the one covering a requirement', () => {
|
|
404
525
|
// One edit should clear the step. Naming only the missing id would leave the user to
|
|
405
526
|
// discover the uncovered requirement on the next round trip.
|