@cat-factory/app 0.256.3 → 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/binaryCandidates/BinaryCandidatesWindow.vue +284 -0
- package/app/components/binaryOutput/BinaryOutputReport.vue +75 -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/forkDecision/ForkDecisionWindow.vue +3 -1
- package/app/components/panels/AgentStepDetail.vue +42 -20
- package/app/components/panels/InspectorPanel.vue +39 -0
- package/app/components/panels/ResultWindowShell.logic.spec.ts +4 -0
- package/app/components/panels/inspector/TaskExecution.vue +34 -34
- package/app/components/pipeline/BinaryOutputStepPicker.logic.spec.ts +119 -1
- package/app/components/pipeline/BinaryOutputStepPicker.logic.ts +115 -1
- package/app/components/pipeline/BinaryOutputStepPicker.vue +463 -1
- package/app/components/pipeline/PipelineBuilder.vue +44 -0
- package/app/components/pipeline/PipelinePreview.vue +20 -1
- package/app/components/pipeline/PipelineProgress.vue +50 -0
- package/app/composables/api/binaryCandidates.ts +36 -0
- package/app/composables/api/execution.ts +19 -0
- package/app/composables/useApi.ts +2 -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/modular/result-views.ts +4 -0
- package/app/stores/binaryCandidates.ts +89 -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/stores/ui/resultViews.ts +8 -6
- package/app/stores/ui/runStepOpeners.ts +23 -1
- package/app/types/domain.ts +6 -0
- package/app/types/execution.ts +5 -0
- package/app/utils/agentPalette.spec.ts +26 -0
- package/app/utils/agentPalette.ts +9 -4
- package/app/utils/binaryCandidates.spec.ts +110 -0
- package/app/utils/binaryCandidates.ts +126 -0
- package/app/utils/binaryOutput.spec.ts +122 -1
- package/app/utils/binaryOutput.ts +149 -2
- 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 +122 -1
- package/app/utils/pipelineRender.ts +113 -2
- package/i18n/locales/de.json +107 -3
- package/i18n/locales/en.json +107 -3
- package/i18n/locales/es.json +107 -3
- package/i18n/locales/fr.json +107 -3
- package/i18n/locales/he.json +107 -3
- package/i18n/locales/it.json +107 -3
- package/i18n/locales/ja.json +107 -3
- package/i18n/locales/pl.json +107 -3
- package/i18n/locales/tr.json +107 -3
- package/i18n/locales/uk.json +107 -3
- package/i18n/plural-forms.spec.ts +15 -0
- package/package.json +2 -2
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
type PreflightResult,
|
|
8
8
|
type ProvisioningRecommendation,
|
|
9
9
|
type StackRecipe,
|
|
10
|
+
ENVIRONMENT_ANALYST_AGENT_KIND,
|
|
11
|
+
adHocPipelineIdFor,
|
|
10
12
|
analystRecipeDraftSchema,
|
|
11
13
|
mergeAnalystRecipeDraft,
|
|
12
14
|
} from '@cat-factory/contracts'
|
|
@@ -15,7 +17,6 @@ import { useBoardStore } from '~/stores/board'
|
|
|
15
17
|
import { useExecutionStore } from '~/stores/execution'
|
|
16
18
|
import { useGitHubStore } from '~/stores/github'
|
|
17
19
|
import { useInfraConfigStore } from '~/stores/infraConfig'
|
|
18
|
-
import { usePipelinesStore } from '~/stores/pipelines'
|
|
19
20
|
import { usePreflightsStore } from '~/stores/preflights'
|
|
20
21
|
import { useServicesStore } from '~/stores/services'
|
|
21
22
|
import type { WizardContext } from '~/stores/environmentWizard/context'
|
|
@@ -43,10 +44,11 @@ import { createSaveActions } from '~/stores/environmentWizard/save'
|
|
|
43
44
|
// recipe / save) that close over the shared reactive {@link WizardContext} assembled here — a
|
|
44
45
|
// size-only extraction following the `board` store idiom, behaviour is unchanged.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// The "run deep analysis" trigger starts the analyst agent as a SINGLE-KIND run — one step, no
|
|
48
|
+
// pipeline — and reads the drafted recipe off that step's `result.custom`. Both the kind and the
|
|
49
|
+
// id its run reports come from the shared contract, so the wizard cannot go looking for a run
|
|
50
|
+
// under a name the backend stopped using.
|
|
51
|
+
const ANALYSIS_PIPELINE_ID = adHocPipelineIdFor(ENVIRONMENT_ANALYST_AGENT_KIND)
|
|
50
52
|
|
|
51
53
|
/** The analyst run's lifecycle as the wizard surfaces it. */
|
|
52
54
|
export type AnalysisStatus = 'idle' | 'running' | 'ready' | 'failed'
|
|
@@ -57,7 +59,6 @@ export const useEnvironmentWizardStore = defineStore('environmentWizard', () =>
|
|
|
57
59
|
const services = useServicesStore()
|
|
58
60
|
const infra = useInfraConfigStore()
|
|
59
61
|
const execution = useExecutionStore()
|
|
60
|
-
const pipelines = usePipelinesStore()
|
|
61
62
|
const preflights = usePreflightsStore()
|
|
62
63
|
|
|
63
64
|
// ---- Target frame -------------------------------------------------------
|
|
@@ -124,9 +125,10 @@ export const useEnvironmentWizardStore = defineStore('environmentWizard', () =>
|
|
|
124
125
|
|
|
125
126
|
const hasRepo = computed(() => repoContext.value !== undefined)
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
// Deep analysis needs only a repo to read: the agent is started by KIND, so there is no
|
|
129
|
+
// catalog row for the workspace to be missing (which is what the old `pl_environment_analysis`
|
|
130
|
+
// lookup guarded against).
|
|
131
|
+
const canAnalyze = computed(() => hasRepo.value)
|
|
130
132
|
|
|
131
133
|
/** The analyst run for this frame (newest matching instance), read live from the execution store.
|
|
132
134
|
* Filters the full instance list (not the collapsing `getByBlock`, which returns a single run per
|
|
@@ -153,7 +155,7 @@ export const useEnvironmentWizardStore = defineStore('environmentWizard', () =>
|
|
|
153
155
|
const analystDraft = computed<AnalystRecipeDraft | null>(() => {
|
|
154
156
|
const run = analystRun.value
|
|
155
157
|
if (!run) return null
|
|
156
|
-
const analystStep = run.steps.find((s) => s.agentKind ===
|
|
158
|
+
const analystStep = run.steps.find((s) => s.agentKind === ENVIRONMENT_ANALYST_AGENT_KIND)
|
|
157
159
|
if (!analystStep || analystStep.state !== 'done' || analystStep.custom === undefined)
|
|
158
160
|
return null
|
|
159
161
|
const parsed = v.safeParse(analystRecipeDraftSchema, analystStep.custom)
|
|
@@ -207,7 +209,6 @@ export const useEnvironmentWizardStore = defineStore('environmentWizard', () =>
|
|
|
207
209
|
trialError,
|
|
208
210
|
trialStarted,
|
|
209
211
|
repoContext,
|
|
210
|
-
analysisPipeline,
|
|
211
212
|
merged,
|
|
212
213
|
}
|
|
213
214
|
const flow = createFlowActions(context)
|
|
@@ -41,10 +41,15 @@ export function createExecutionCommands(ctx: ExecutionCommandContext) {
|
|
|
41
41
|
* and nothing merges. It is a request, not a decision — the task's merge preset can sandbox a
|
|
42
42
|
* role's runs whatever they asked for, so what the run got is read back off the run's own
|
|
43
43
|
* `mode`, never assumed from what was sent here.
|
|
44
|
+
*
|
|
45
|
+
* Takes the id + name it actually sends and reports, not a whole {@link Pipeline}: a caller may
|
|
46
|
+
* legitimately hold neither, because a task can be pinned to an INTERNAL pipeline that the
|
|
47
|
+
* library withholds from every picker. Demanding the row there would force the caller to either
|
|
48
|
+
* fabricate one or silently start something else.
|
|
44
49
|
*/
|
|
45
50
|
async function start(
|
|
46
51
|
blockId: string,
|
|
47
|
-
pipeline: Pipeline,
|
|
52
|
+
pipeline: Pick<Pipeline, 'id' | 'name'>,
|
|
48
53
|
options?: { mode?: RunMode },
|
|
49
54
|
): Promise<boolean> {
|
|
50
55
|
const ws = useWorkspaceStore()
|
|
@@ -71,6 +76,25 @@ export function createExecutionCommands(ctx: ExecutionCommandContext) {
|
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Start ONE agent kind against a block. The single-kind counterpart of {@link start}: same
|
|
81
|
+
* credential gate, same snapshot refresh, same false-on-refusal contract — only the thing being
|
|
82
|
+
* started is an agent rather than a pipeline.
|
|
83
|
+
*/
|
|
84
|
+
async function startAgentKind(blockId: string, agentKind: string): Promise<boolean> {
|
|
85
|
+
const ws = useWorkspaceStore()
|
|
86
|
+
const personal = usePersonalSubscriptionsStore()
|
|
87
|
+
try {
|
|
88
|
+
return await personal.withCredential(async (password) => {
|
|
89
|
+
await api.startAgentKindExecution(ws.requireId(), blockId, agentKind, password)
|
|
90
|
+
await ws.refresh()
|
|
91
|
+
})
|
|
92
|
+
} catch (e) {
|
|
93
|
+
runErrors.present(e, 'errors.action.startFailed')
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
// Interacting with a running individual-usage run (resolve/approve/request-changes) advances
|
|
75
99
|
// + re-dispatches the run, so the server re-mints its short-TTL activation from the personal
|
|
76
100
|
// password first. It rides the cached password transparently, and — like start/retry — is
|
|
@@ -237,6 +261,7 @@ export function createExecutionCommands(ctx: ExecutionCommandContext) {
|
|
|
237
261
|
|
|
238
262
|
return {
|
|
239
263
|
start,
|
|
264
|
+
startAgentKind,
|
|
240
265
|
resolveDecision,
|
|
241
266
|
approveStep,
|
|
242
267
|
requestStepChanges,
|
|
@@ -4,6 +4,7 @@ import { companionForProducer } from '~/utils/catalog'
|
|
|
4
4
|
import type { PipelinesContext } from './context'
|
|
5
5
|
import { createPipelineGateConfigActions } from './draftGateConfig'
|
|
6
6
|
import { createPipelineStepConfigActions } from './draftStepConfig'
|
|
7
|
+
import { createPipelineStepOptionActions } from './draftStepOptions'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* The pipeline-builder draft's STRUCTURE: inserting/removing/reordering steps, the companion
|
|
@@ -193,6 +194,7 @@ export function createPipelineDraftActions(ctx: PipelinesContext) {
|
|
|
193
194
|
|
|
194
195
|
return {
|
|
195
196
|
...createPipelineStepConfigActions(ctx),
|
|
197
|
+
...createPipelineStepOptionActions(ctx),
|
|
196
198
|
...createPipelineGateConfigActions(ctx),
|
|
197
199
|
addToDraft,
|
|
198
200
|
removeFromDraft,
|
|
@@ -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
|
+
}
|
|
@@ -164,12 +164,13 @@ export function createUiResultViews() {
|
|
|
164
164
|
// The run-scoped openers (a caller that knows only the RUN, so the step index has to be
|
|
165
165
|
// resolved) live in a sibling module: they share one shape and one hazard, and lifting them out
|
|
166
166
|
// keeps this factory inside its per-function line budget. Their two seams are bound here.
|
|
167
|
-
const { openFollowUps, openForkDecision, openPrReview, openTestEvidence } =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
const { openFollowUps, openForkDecision, openBinaryCandidates, openPrReview, openTestEvidence } =
|
|
168
|
+
createRunStepOpeners({
|
|
169
|
+
dispatchStepView: (instanceId, stepIndex) => dispatchStepView(instanceId, stepIndex),
|
|
170
|
+
setResultView: (view, instance, stepIndex) => {
|
|
171
|
+
resultView.value = { view, blockId: instance.blockId, instanceId: instance.id, stepIndex }
|
|
172
|
+
},
|
|
173
|
+
})
|
|
173
174
|
|
|
174
175
|
function closeResultView() {
|
|
175
176
|
resultView.value = null
|
|
@@ -209,6 +210,7 @@ export function createUiResultViews() {
|
|
|
209
210
|
openInitiativePlanning,
|
|
210
211
|
openFollowUps,
|
|
211
212
|
openForkDecision,
|
|
213
|
+
openBinaryCandidates,
|
|
212
214
|
openPrReview,
|
|
213
215
|
openTestEvidence,
|
|
214
216
|
openOutcome,
|
|
@@ -93,6 +93,28 @@ export function createRunStepOpeners(deps: RunStepOpenerDeps) {
|
|
|
93
93
|
)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Open the generated-candidate comparison window for a run's binary-output step (from the
|
|
97
|
+
// pipeline chip / inspector rail / step overlay). Resolves the step index from the run when not
|
|
98
|
+
// given, preferring the step parked awaiting a choice.
|
|
99
|
+
//
|
|
100
|
+
// Resolved by the CANDIDATE STATE rather than by an agent kind, unlike its neighbours: any kind
|
|
101
|
+
// carrying the `binary-output` trait can run a comparison, and a deployment's own kinds are
|
|
102
|
+
// exactly the ones a hard-coded kind list here would never name.
|
|
103
|
+
function openBinaryCandidates(instanceId: string, stepIndex: number | null = null) {
|
|
104
|
+
withStep(
|
|
105
|
+
instanceId,
|
|
106
|
+
stepIndex,
|
|
107
|
+
(instance) => {
|
|
108
|
+
const awaiting = indexOf(instance, (s) => s.binaryCandidates?.status === 'awaiting_choice')
|
|
109
|
+
if (awaiting >= 0) return awaiting
|
|
110
|
+
const current = instance.steps[instance.currentStep]
|
|
111
|
+
if (current?.binaryCandidates) return instance.currentStep
|
|
112
|
+
return indexOf(instance, (s) => !!s.binaryCandidates)
|
|
113
|
+
},
|
|
114
|
+
(instance, idx) => deps.setResultView('binary-candidates', instance, idx),
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
// Open the PR deep-review window for a run's `pr-reviewer` step (from the `pr_review_ready`
|
|
97
119
|
// notification / the step). Resolves the step index from the run when not given, preferring
|
|
98
120
|
// the step parked awaiting a finding selection.
|
|
@@ -139,5 +161,5 @@ export function createRunStepOpeners(deps: RunStepOpenerDeps) {
|
|
|
139
161
|
)
|
|
140
162
|
}
|
|
141
163
|
|
|
142
|
-
return { openFollowUps, openForkDecision, openPrReview, openTestEvidence }
|
|
164
|
+
return { openFollowUps, openForkDecision, openBinaryCandidates, openPrReview, openTestEvidence }
|
|
143
165
|
}
|
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
|
/**
|
package/app/types/execution.ts
CHANGED
|
@@ -95,6 +95,11 @@ export type {
|
|
|
95
95
|
BinaryOutputArtifact,
|
|
96
96
|
BinaryOutputConfig,
|
|
97
97
|
BinaryOutputReport,
|
|
98
|
+
// The candidate-comparison set on a step whose selection declares a `comparison`: the staged
|
|
99
|
+
// candidates, the live park state, and the human's keep/discard decision.
|
|
100
|
+
BinaryCandidate,
|
|
101
|
+
BinaryCandidateChoice,
|
|
102
|
+
BinaryCandidateStepState,
|
|
98
103
|
TesterStepState,
|
|
99
104
|
HumanTestEnvironment,
|
|
100
105
|
RunEnvironment,
|
|
@@ -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(
|