@cat-factory/app 0.255.0 → 0.255.1

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.
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from 'vue'
3
- import { purposeAllowsAgentCategory } from '@cat-factory/contracts'
3
+ import { DEPLOYER_AGENT_KIND } from '@cat-factory/contracts'
4
4
  import type { AgentKind, Pipeline } from '~/types/domain'
5
5
  import AgentPalette from '~/components/palettes/AgentPalette.vue'
6
6
  import AgentKindIcon from '~/components/pipeline/AgentKindIcon.vue'
@@ -189,31 +189,6 @@ const skills = useSkillsStore()
189
189
  // skill picker. A `skill` step is parametrized by the picked skill (`stepOptions.skillId`).
190
190
  const skillSelectItems = computed(() => skills.catalog.map((s) => ({ label: s.name, value: s.id })))
191
191
 
192
- // An enabled `skill` step with no picked skill — mirrors the backend save/start rejection
193
- // (`assertValidSkillSteps`), surfaced as an inline hint so the user fixes it before saving.
194
- const skillStepNeedsPick = computed(() =>
195
- pipelines.draft.some(
196
- (k, i) => k === 'skill' && pipelines.draftEnabled[i] !== false && !pipelines.draftSkillId(i),
197
- ),
198
- )
199
-
200
- // Steps whose agent category the chosen purpose CONTRADICTS (a non-`build` purpose writes no code
201
- // and runs no tests, so the Implementation/Testing categories are disallowed, see
202
- // `purposeAllowsAgentCategory`). Only reachable by switching an existing draft to a non-`build`
203
- // purpose AFTER such steps were added, since the palette offers neither. The backend has no
204
- // kind→category map to gate on, so the builder is the enforcement point: save is blocked until the
205
- // offending steps are removed (or the purpose set back to Build).
206
- //
207
- // Deliberately the compatibility predicate, not the palette's narrower relevance one: a purpose
208
- // that merely stops SUGGESTING a category must not turn a pipeline somebody already built into
209
- // one they cannot save.
210
- const stepsDisallowedByPurpose = computed(() =>
211
- pipelines.draft.filter((kind) => {
212
- const category = agentKindMeta(kind).category
213
- return !!category && !purposeAllowsAgentCategory(pipelines.draftPurpose, category)
214
- }),
215
- )
216
-
217
192
  // The workspace's foundational-services catalog, for the binary-output storage/context picker.
218
193
  const foundational = useFoundationalServicesStore()
219
194
 
@@ -231,18 +206,22 @@ function showBinaryOutputPicker(kind: AgentKind): boolean {
231
206
  return agentKindMeta(kind).binaryOutput === true
232
207
  }
233
208
 
234
- // An enabled generator step with no storage selection — mirrors the backend save/start
235
- // rejection (`assertValidBinaryOutputSteps`), surfaced as an inline hint so the user fixes it
236
- // before the round trip. Same disposition as `skillStepNeedsPick`, for the same reason: both
237
- // are a step parametrized by a selection it cannot run without.
238
- const binaryOutputStepNeedsPick = computed(() =>
239
- pipelines.draft.some(
240
- (kind, i) =>
241
- showBinaryOutputPicker(kind) &&
242
- pipelines.draftEnabled[i] !== false &&
243
- !pipelines.draftBinaryOutput(i)?.storageServiceId,
244
- ),
245
- )
209
+ /**
210
+ * Whether to offer the Deployer's "keep this environment past the run" declaration on the step at
211
+ * `index`. An OVERRIDE in the `showOverrideField` sense reclaiming what a run stood up is the
212
+ * default and the everyday shape, and a deliberately retained preview environment is not the
213
+ * everyday delivery loop — so it is `advanced`-tier, and a step that already CARRIES the
214
+ * declaration keeps showing it in either mode (never hide the way back).
215
+ *
216
+ * Hiding it strands nobody: the fault it answers (`deployer_without_disposer`) always has the
217
+ * tier-neutral fix of adding the Disposer back, which both the inline hint and the save refusal
218
+ * name FIRST. That is what makes this different from the binary-output picker above, where the
219
+ * selection is required and hiding it would leave a step with no savable form at all.
220
+ */
221
+ function showRetainEnvironmentToggle(kind: AgentKind, index: number): boolean {
222
+ if (kind !== DEPLOYER_AGENT_KIND) return false
223
+ return showOverrideField(uiMode.isAdvanced, pipelines.draftRetainEnvironment(index) || null)
224
+ }
246
225
 
247
226
  // A step's picked skill id is no longer in the account catalog (the source dir was renamed or
248
227
  // unlinked). The step will fail cleanly at dispatch; flag it so the user re-picks.
@@ -370,22 +349,12 @@ function companionLabel(kind: string): string | null {
370
349
  return companion ? agentKindMeta(companion).label : null
371
350
  }
372
351
 
373
- // Surfaced as an inline hint: a gated step needs a task-estimator before it (mirrors the
374
- // backend validation, which also rejects the save/start). Both the companion estimate gate
375
- // (`draftGating`) and the Tester QC companion's estimate gate (`draftTesterQuality[i].gating`)
376
- // count — either without a preceding estimator is rejected on save.
377
- const gatingNeedsEstimator = computed(() => {
378
- const kinds = pipelines.draft
379
- const hasEstimatorBefore = (i: number) =>
380
- kinds.slice(0, i).some((k, j) => k === 'task-estimator' && pipelines.draftEnabled[j] !== false)
381
- for (let i = 0; i < kinds.length; i++) {
382
- if (pipelines.draftEnabled[i] === false) continue
383
- const gated =
384
- pipelines.draftGating[i]?.enabled || pipelines.draftTesterQuality[i]?.gating?.enabled
385
- if (gated && !hasEstimatorBefore(i)) return true
386
- }
387
- return false
388
- })
352
+ // Everything that is WRONG with the draft, as an ordered list of hints the template renders once,
353
+ // plus the purpose conflict that also disables Save. Lives in its own composable because each of
354
+ // these is one predicate beside one identically-styled line, and five of them had crowded out the
355
+ // component (`usePipelineDraftWarnings`).
356
+ const { hints: draftWarnings, stepsDisallowedByPurpose } =
357
+ usePipelineDraftWarnings(showBinaryOutputPicker)
389
358
 
390
359
  // ---- draft labels ----------------------------------------------------------
391
360
  const newLabel = ref('')
@@ -563,37 +532,16 @@ async function clone(p: Pipeline) {
563
532
  />
564
533
  </div>
565
534
 
535
+ <!-- Every draft fault the builder can name, each mirroring a refusal the save boundary
536
+ makes, so the user fixes it before the round trip (`usePipelineDraftWarnings`). -->
566
537
  <p
567
- v-if="gatingNeedsEstimator"
568
- class="mb-2 flex items-center gap-1.5 rounded-md border border-amber-800/50 bg-amber-950/30 px-2 py-1 text-[11px] text-amber-300"
569
- >
570
- <UIcon name="i-lucide-alert-triangle" class="h-3.5 w-3.5 shrink-0" />
571
- {{ t('pipeline.builder.gatingNeedsEstimator') }}
572
- </p>
573
-
574
- <p
575
- v-if="skillStepNeedsPick"
538
+ v-for="warning in draftWarnings"
539
+ :key="warning.key"
576
540
  class="mb-2 flex items-center gap-1.5 rounded-md border border-amber-800/50 bg-amber-950/30 px-2 py-1 text-[11px] text-amber-300"
541
+ :data-testid="warning.testId"
577
542
  >
578
543
  <UIcon name="i-lucide-alert-triangle" class="h-3.5 w-3.5 shrink-0" />
579
- {{ t('pipeline.builder.skillNeedsPick') }}
580
- </p>
581
-
582
- <p
583
- v-if="binaryOutputStepNeedsPick"
584
- class="mb-2 flex items-center gap-1.5 rounded-md border border-amber-800/50 bg-amber-950/30 px-2 py-1 text-[11px] text-amber-300"
585
- data-testid="binary-output-needs-pick"
586
- >
587
- <UIcon name="i-lucide-alert-triangle" class="h-3.5 w-3.5 shrink-0" />
588
- {{ t('pipeline.builder.binaryOutputNeedsPick') }}
589
- </p>
590
-
591
- <p
592
- v-if="stepsDisallowedByPurpose.length"
593
- class="mb-2 flex items-center gap-1.5 rounded-md border border-amber-800/50 bg-amber-950/30 px-2 py-1 text-[11px] text-amber-300"
594
- >
595
- <UIcon name="i-lucide-alert-triangle" class="h-3.5 w-3.5 shrink-0" />
596
- {{ t('pipeline.builder.purposeStepsConflict') }}
544
+ {{ t(warning.key) }}
597
545
  </p>
598
546
 
599
547
  <div
@@ -766,6 +714,27 @@ async function clone(p: Pipeline) {
766
714
  "
767
715
  @click="pipelines.toggleDraftAutoRecommend(unit.index)"
768
716
  />
717
+ <!-- Keep the environment this Deployer stands up past the end of the run: the
718
+ preview a reviewer pokes at after the PR is open. Off by default, and the
719
+ save boundary refuses a Deployer that neither reclaims nor declares this,
720
+ so the tick is how an unreclaimed environment says it is deliberate. -->
721
+ <UButton
722
+ v-if="showRetainEnvironmentToggle(unit.kind, unit.index)"
723
+ :icon="
724
+ pipelines.draftRetainEnvironment(unit.index)
725
+ ? 'i-lucide-lock'
726
+ : 'i-lucide-cloud-off'
727
+ "
728
+ :color="pipelines.draftRetainEnvironment(unit.index) ? 'warning' : 'neutral'"
729
+ variant="ghost"
730
+ size="xs"
731
+ :title="
732
+ pipelines.draftRetainEnvironment(unit.index)
733
+ ? t('pipeline.builder.retainEnvironmentClearTooltip')
734
+ : t('pipeline.builder.retainEnvironmentSetTooltip')
735
+ "
736
+ @click="pipelines.toggleDraftRetainEnvironment(unit.index)"
737
+ />
769
738
  <!-- System prompt: replace what this agent kind ships with, for every run in
770
739
  this workspace, with the full revision history to switch back through. -->
771
740
  <UButton
@@ -0,0 +1,136 @@
1
+ import { computed, type ComputedRef } from 'vue'
2
+ import {
3
+ pipelineEnvironmentProblems,
4
+ purposeAllowsAgentCategory,
5
+ type PipelineEnvironmentProblemReason,
6
+ } from '@cat-factory/contracts'
7
+ import type { AgentKind } from '~/types/domain'
8
+ import { agentKindMeta } from '~/utils/catalog'
9
+ import { usePipelinesStore } from '~/stores/pipelines'
10
+
11
+ /**
12
+ * Everything the pipeline builder can say is WRONG with the draft in front of it, as an ordered
13
+ * list of translation keys, plus the one finding that also blocks the save.
14
+ *
15
+ * Extracted from `PipelineBuilder.vue` because these grew into one concern wearing five copies of
16
+ * the same coat: each was a `computed` predicate beside a `<p>` with byte-identical classes, so
17
+ * every new rule cost another near-duplicate pair in a component that had run out of room for
18
+ * them. Here they are a list the template renders once.
19
+ *
20
+ * Every entry MIRRORS a refusal the backend already makes at save (or, for the purpose conflict,
21
+ * one only the builder can make, since the backend has no kind→category map). The point is to
22
+ * surface it before the round trip, never to be the sole enforcement: a hint that disagrees with
23
+ * the boundary would let an unsavable draft look clean, so each is derived from the same shared
24
+ * rule the boundary runs rather than from a hand-written restatement of it.
25
+ */
26
+ export interface PipelineDraftWarnings {
27
+ /** One entry per distinct finding, in the order the builder shows them. */
28
+ hints: ComputedRef<PipelineDraftHint[]>
29
+ /**
30
+ * The draft steps whose agent category the chosen purpose CONTRADICTS. Exposed as the list
31
+ * rather than folded into {@link hints} alone because it is the one finding that also disables
32
+ * the Save button: the builder is the enforcement point, so an empty list is a precondition.
33
+ */
34
+ stepsDisallowedByPurpose: ComputedRef<AgentKind[]>
35
+ }
36
+
37
+ /** One rendered warning: its i18n key, plus the test id where a spec asserts on it. */
38
+ export interface PipelineDraftHint {
39
+ key: string
40
+ testId?: string
41
+ }
42
+
43
+ /**
44
+ * The environment-lifecycle faults, mapped onto the copy that names the fix for each. An
45
+ * exhaustive `Record` over the reason union, so a fault added to the shared rule fails the build
46
+ * here rather than rendering as a blank hint.
47
+ */
48
+ const ENVIRONMENT_HINT_KEYS: Record<PipelineEnvironmentProblemReason, string> = {
49
+ consumer_without_deployer: 'pipeline.builder.envNeedsDeployer',
50
+ consumer_after_disposer: 'pipeline.builder.envConsumerAfterDisposer',
51
+ deployer_without_disposer: 'pipeline.builder.envNeedsDisposer',
52
+ disposer_without_deployer: 'pipeline.builder.envDisposerNeedsDeployer',
53
+ retained_deployer_reclaimed: 'pipeline.builder.envRetainedButReclaimed',
54
+ }
55
+
56
+ export function usePipelineDraftWarnings(
57
+ showBinaryOutputPicker: (kind: AgentKind) => boolean,
58
+ ): PipelineDraftWarnings {
59
+ const pipelines = usePipelinesStore()
60
+
61
+ const enabled = (i: number) => pipelines.draftEnabled[i] !== false
62
+
63
+ // A gated step with no task-estimator before it (mirrors `assertValidGating`, which rejects the
64
+ // save and the start). Both the step's own estimate gate (`draftGating`) and the Tester QC
65
+ // companion's (`draftTesterQuality[i].gating`) count.
66
+ const gatingNeedsEstimator = computed(() => {
67
+ const kinds = pipelines.draft
68
+ const hasEstimatorBefore = (i: number) =>
69
+ kinds.slice(0, i).some((k, j) => k === 'task-estimator' && enabled(j))
70
+ return kinds.some((_, i) => {
71
+ if (!enabled(i)) return false
72
+ const gated =
73
+ pipelines.draftGating[i]?.enabled || pipelines.draftTesterQuality[i]?.gating?.enabled
74
+ return !!gated && !hasEstimatorBefore(i)
75
+ })
76
+ })
77
+
78
+ // The environment lifecycle a draft has to spell out: provision (Deployer) → consume (a tester /
79
+ // acceptance / human-test step) → reclaim (Disposer, or a Deployer that declares its environment
80
+ // outlives the run). One message per DISTINCT fault, so a draft missing both a Deployer and a
81
+ // Disposer says so at once instead of over two rejected saves.
82
+ const environmentHintKeys = computed(() => {
83
+ const problems = pipelineEnvironmentProblems(
84
+ pipelines.draft,
85
+ pipelines.draftEnabled,
86
+ pipelines.draftStepOptions,
87
+ )
88
+ return [...new Set(problems.map((p) => ENVIRONMENT_HINT_KEYS[p.reason]))]
89
+ })
90
+
91
+ // An enabled `skill` step with no picked skill (mirrors `assertValidSkillSteps`).
92
+ const skillStepNeedsPick = computed(() =>
93
+ pipelines.draft.some((k, i) => k === 'skill' && enabled(i) && !pipelines.draftSkillId(i)),
94
+ )
95
+
96
+ // An enabled generator step with no storage selection (mirrors `assertValidBinaryOutputSteps`).
97
+ // Same disposition as `skillStepNeedsPick`: both are a step parametrized by a selection it
98
+ // cannot run without.
99
+ const binaryOutputStepNeedsPick = computed(() =>
100
+ pipelines.draft.some(
101
+ (kind, i) =>
102
+ showBinaryOutputPicker(kind) &&
103
+ enabled(i) &&
104
+ !pipelines.draftBinaryOutput(i)?.storageServiceId,
105
+ ),
106
+ )
107
+
108
+ // Steps whose agent category the chosen purpose CONTRADICTS (a non-`build` purpose writes no
109
+ // code and runs no tests, so the Implementation/Testing categories are disallowed). Only
110
+ // reachable by switching an existing draft to a non-`build` purpose AFTER such steps were added,
111
+ // since the palette offers neither.
112
+ //
113
+ // Deliberately the COMPATIBILITY predicate, not the palette's narrower relevance one: a purpose
114
+ // that merely stops SUGGESTING a category must not turn a pipeline somebody already built into
115
+ // one they cannot save.
116
+ const stepsDisallowedByPurpose = computed(() =>
117
+ pipelines.draft.filter((kind) => {
118
+ const category = agentKindMeta(kind).category
119
+ return !!category && !purposeAllowsAgentCategory(pipelines.draftPurpose, category)
120
+ }),
121
+ )
122
+
123
+ const hints = computed<PipelineDraftHint[]>(() => [
124
+ ...(gatingNeedsEstimator.value ? [{ key: 'pipeline.builder.gatingNeedsEstimator' }] : []),
125
+ ...environmentHintKeys.value.map((key) => ({ key, testId: 'env-lifecycle-hint' })),
126
+ ...(skillStepNeedsPick.value ? [{ key: 'pipeline.builder.skillNeedsPick' }] : []),
127
+ ...(binaryOutputStepNeedsPick.value
128
+ ? [{ key: 'pipeline.builder.binaryOutputNeedsPick', testId: 'binary-output-needs-pick' }]
129
+ : []),
130
+ ...(stepsDisallowedByPurpose.value.length
131
+ ? [{ key: 'pipeline.builder.purposeStepsConflict' }]
132
+ : []),
133
+ ])
134
+
135
+ return { hints, stepsDisallowedByPurpose }
136
+ }
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { pipelineEnvironmentProblems } from '@cat-factory/contracts'
3
+ import { usePipelinesStore } from '~/stores/pipelines'
4
+
5
+ /**
6
+ * The pipeline-builder draft's retain declaration (`stepOptions.retainEnvironment`): how an author
7
+ * says a Deployer's environment is MEANT to outlive the run, which is the only savable form of
8
+ * "deploy a preview and leave it up" (the reclaim rule refuses a Deployer that neither reclaims
9
+ * nor declares this). Same contract as the option helpers beside it: merge into the bag, normalize
10
+ * an emptied bag back to null.
11
+ */
12
+ describe('pipelines store — per-step retain-environment declaration', () => {
13
+ it('stores only the opt-in, and normalizes the bag away when it is cleared', () => {
14
+ const pipelines = usePipelinesStore()
15
+ pipelines.addToDraft('deployer')
16
+ // OFF by default, and stored as ABSENCE rather than `false`: reclaiming is the default, so a
17
+ // step that never touched this persists exactly the shape it always did.
18
+ expect(pipelines.draftRetainEnvironment(0)).toBe(false)
19
+ expect(pipelines.draftStepOptions[0]).toBeNull()
20
+
21
+ pipelines.toggleDraftRetainEnvironment(0)
22
+ expect(pipelines.draftRetainEnvironment(0)).toBe(true)
23
+ expect(pipelines.draftStepOptions[0]).toEqual({ retainEnvironment: true })
24
+
25
+ pipelines.toggleDraftRetainEnvironment(0)
26
+ expect(pipelines.draftRetainEnvironment(0)).toBe(false)
27
+ expect(pipelines.draftStepOptions[0]).toBeNull()
28
+ })
29
+
30
+ it('merges with the other options on the same step rather than clobbering them', () => {
31
+ const pipelines = usePipelinesStore()
32
+ pipelines.addToDraft('deployer')
33
+ pipelines.setDraftAgentVariantId(0, 'acme:fast')
34
+
35
+ pipelines.toggleDraftRetainEnvironment(0)
36
+ expect(pipelines.draftStepOptions[0]).toEqual({
37
+ agentVariantId: 'acme:fast',
38
+ retainEnvironment: true,
39
+ })
40
+
41
+ pipelines.toggleDraftRetainEnvironment(0)
42
+ expect(pipelines.draftStepOptions[0]).toEqual({ agentVariantId: 'acme:fast' })
43
+ })
44
+
45
+ it('turns a draft the save boundary refuses into one it accepts', () => {
46
+ // The point of the whole field, asserted against the SAME rule the backend refuses on rather
47
+ // than against a restatement of it: without the declaration this draft has no savable form,
48
+ // since dropping the Deployer instead just moves the fault onto the tester.
49
+ const pipelines = usePipelinesStore()
50
+ for (const kind of ['coder', 'deployer', 'human-test'] as const) pipelines.addToDraft(kind)
51
+ const problems = () =>
52
+ pipelineEnvironmentProblems(
53
+ pipelines.draft,
54
+ pipelines.draftEnabled,
55
+ pipelines.draftStepOptions,
56
+ ).map((p) => p.reason)
57
+
58
+ expect(problems()).toEqual(['deployer_without_disposer'])
59
+ pipelines.toggleDraftRetainEnvironment(1)
60
+ expect(problems()).toEqual([])
61
+ })
62
+ })
@@ -15,6 +15,27 @@ import { defaultConsensusConfig, type PipelinesContext } from './context'
15
15
  * touches nothing else, which is what makes the two independent; both are spread into the store,
16
16
  * so the store's API is unchanged.
17
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
+
18
39
  export function createPipelineStepConfigActions(ctx: PipelinesContext) {
19
40
  const {
20
41
  draftGates,
@@ -118,10 +139,27 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
118
139
  * flag. Merges with any other future StepOptions fields rather than clobbering the whole bag.
119
140
  */
120
141
  function toggleDraftAutoRecommend(index: number) {
121
- const next: StepOptions = { ...draftStepOptions.value[index] }
122
- if (draftAutoRecommendEnabled(index)) next.autoRecommend = false
123
- else delete next.autoRecommend
124
- draftStepOptions.value[index] = Object.keys(next).length ? next : null
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)
125
163
  }
126
164
 
127
165
  /** The skill picked for the draft `skill` step at `index` (its `stepOptions.skillId`). */
@@ -135,10 +173,7 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
135
173
  * bag empties, the whole entry (so it normalizes away like the other options).
136
174
  */
137
175
  function setDraftSkillId(index: number, skillId: string | undefined) {
138
- const next: StepOptions = { ...draftStepOptions.value[index] }
139
- if (skillId) next.skillId = skillId
140
- else delete next.skillId
141
- draftStepOptions.value[index] = Object.keys(next).length ? next : null
176
+ patchStepOption(draftStepOptions, index, 'skillId', skillId || undefined)
142
177
  }
143
178
 
144
179
  /**
@@ -156,10 +191,7 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
156
191
  * shipped prompt persists nothing.
157
192
  */
158
193
  function setDraftAgentVariantId(index: number, agentVariantId: string | undefined) {
159
- const next: StepOptions = { ...draftStepOptions.value[index] }
160
- if (agentVariantId) next.agentVariantId = agentVariantId
161
- else delete next.agentVariantId
162
- draftStepOptions.value[index] = Object.keys(next).length ? next : null
194
+ patchStepOption(draftStepOptions, index, 'agentVariantId', agentVariantId || undefined)
163
195
  }
164
196
 
165
197
  /**
@@ -188,17 +220,16 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
188
220
  * wrong thing.
189
221
  */
190
222
  function setDraftBinaryOutput(index: number, config: BinaryOutputConfig | undefined) {
191
- const next: StepOptions = { ...draftStepOptions.value[index] }
192
- if (config?.storageServiceId) {
193
- const { storageServiceId, contextServiceIds, generatorIds, modalities } = config
194
- next.binaryOutput = {
195
- storageServiceId,
196
- ...(contextServiceIds?.length ? { contextServiceIds } : {}),
197
- ...(generatorIds?.length ? { generatorIds } : {}),
198
- ...(modalities?.length ? { modalities } : {}),
199
- }
200
- } else delete next.binaryOutput
201
- draftStepOptions.value[index] = Object.keys(next).length ? next : null
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)
202
233
  }
203
234
 
204
235
  /**
@@ -216,10 +247,7 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
216
247
  * other fields here.
217
248
  */
218
249
  function setDraftMaxOutputTokens(index: number, maxOutputTokens: number | undefined) {
219
- const next: StepOptions = { ...draftStepOptions.value[index] }
220
- if (maxOutputTokens != null) next.maxOutputTokens = maxOutputTokens
221
- else delete next.maxOutputTokens
222
- draftStepOptions.value[index] = Object.keys(next).length ? next : null
250
+ patchStepOption(draftStepOptions, index, 'maxOutputTokens', maxOutputTokens ?? undefined)
223
251
  }
224
252
 
225
253
  return {
@@ -234,6 +262,8 @@ export function createPipelineStepConfigActions(ctx: PipelinesContext) {
234
262
  toggleDraftEnabled,
235
263
  draftAutoRecommendEnabled,
236
264
  toggleDraftAutoRecommend,
265
+ draftRetainEnvironment,
266
+ toggleDraftRetainEnvironment,
237
267
  draftSkillId,
238
268
  setDraftSkillId,
239
269
  draftAgentVariantId,
@@ -210,12 +210,14 @@ export const AGENT_ARCHETYPES: AgentArchetype[] = [
210
210
  },
211
211
  {
212
212
  // Provisions the ephemeral environment the tester / human-test / playwright steps read, which
213
- // is why it leads the testing group. A palette block for the same reason `disposer` is one:
214
- // `assertDeployerBeforeConsumer` REFUSES a run whose chain reaches an env consumer with no
215
- // Deployer in front of it on a deployable service, and a hand-built pipeline that hits that
216
- // refusal has no reseed to fall back on.
213
+ // is why it leads the testing group.
214
+ //
215
+ // `basic`, and it has to be: a pipeline that reaches an env consumer with no Deployer in
216
+ // front of it is refused at SAVE (`validatePipelineAuthoring`), and the API Tester it serves
217
+ // is itself `basic`. Leaving the Deployer out of the basic palette would leave a basic-mode
218
+ // user composing a pipeline they cannot save and cannot see the fix for.
217
219
  kind: 'deployer',
218
- tier: 'intermediate',
220
+ tier: 'basic',
219
221
  label: 'Deployer',
220
222
  icon: 'i-lucide-cloud-upload',
221
223
  color: '#34d399',
@@ -276,8 +278,11 @@ export const AGENT_ARCHETYPES: AgentArchetype[] = [
276
278
  // is the point of it: after the automated tester, or after a human has finished with the live
277
279
  // URL. Without one, the TTL sweep reclaims environments on a timer long after the run
278
280
  // settled, which is a fine backstop and cannot close the run's own teardown proof.
281
+ //
282
+ // `basic` for the same reason the Deployer is: a chain that deploys and never reclaims is
283
+ // refused at save, so the fix has to be reachable wherever the fault can be composed.
279
284
  kind: 'disposer',
280
- tier: 'intermediate',
285
+ tier: 'basic',
281
286
  label: 'Disposer',
282
287
  icon: 'i-lucide-cloud-off',
283
288
  color: '#34d399',
@@ -4293,6 +4293,13 @@
4293
4293
  "binaryOutputPlaceholder": "Speicherdienst wählen",
4294
4294
  "binaryOutputContextPlaceholder": "Optional: Dienste, die den Umfang bestimmen",
4295
4295
  "binaryOutputNeedsPick": "Für einen Schritt, der binäre Ausgaben erzeugt, ist kein Speicherdienst gewählt. Wähle einen vor dem Speichern.",
4296
+ "envNeedsDeployer": "Vor einem Tester-, manuellen Test- oder Playwright-Schritt wird ein Deployer benötigt. Füge einen hinzu, sonst lässt sich die Pipeline nicht speichern (bei einem Service ohne Provisionierung bleibt er wirkungslos).",
4297
+ "envNeedsDisposer": "Nach einem Deployer wird ein Disposer benötigt, der die bereitgestellte Umgebung wieder freigibt. Füge einen hinzu oder markiere den Deployer so, dass er seine Umgebung über den Lauf hinaus behält, sonst lässt sich die Pipeline nicht speichern.",
4298
+ "envDisposerNeedsDeployer": "Vor diesem Disposer steht kein Deployer, es gibt also nichts freizugeben. Füge einen Deployer hinzu oder entferne den Disposer.",
4299
+ "envConsumerAfterDisposer": "Ein Tester-, manueller Test- oder Playwright-Schritt läuft erst, nachdem der Disposer die Umgebung bereits freigegeben hat, es bliebe also nichts übrig, wogegen er laufen könnte. Verschiebe den Disposer hinter diesen Schritt oder füge davor einen weiteren Deployer ein.",
4300
+ "envRetainedButReclaimed": "Dieser Deployer ist so markiert, dass er seine Umgebung über den Lauf hinaus behält, aber ein nachfolgender Disposer gibt genau diese Umgebung frei. Entferne den Disposer oder hebe die Markierung auf.",
4301
+ "retainEnvironmentSetTooltip": "Wird am Ende des Laufs freigegeben. Klicke, um diese Umgebung danach weiterlaufen zu lassen (eine Vorschau, die Prüfende nach dem Öffnen des PR nutzen); die TTL oder ein Betreiber fährt sie dann herunter.",
4302
+ "retainEnvironmentClearTooltip": "Läuft nach dem Ende des Laufs weiter. Klicke, um sie wieder per Disposer-Schritt freizugeben.",
4296
4303
  "binaryOutputNoStorage": "Kein Dienst im Katalog dieses Boards deklariert die Fähigkeit {capability}. Registriere einen, oder ergänze die Fähigkeit beim gemeinten Dienst unter den grundlegenden Diensten.",
4297
4304
  "binaryOutputMissing": "Dieser Speicherdienst ist nicht mehr im Katalog; wähle einen anderen.",
4298
4305
  "binaryOutputNotStorage": "Dieser Dienst deklariert die Fähigkeit {capability} nicht mehr, deshalb werden Läufe abgelehnt; wähle einen anderen.",
@@ -4881,6 +4881,13 @@
4881
4881
  "binaryOutputPlaceholder": "Pick a storage service",
4882
4882
  "binaryOutputContextPlaceholder": "Optional: services that scope the generation",
4883
4883
  "binaryOutputNeedsPick": "A step that generates binary outputs has no storage service selected. Pick one before saving.",
4884
+ "envNeedsDeployer": "A Tester, human-test or Playwright step needs a Deployer before it. Add one or the pipeline won't save (it is a no-op on a service that provisions nothing).",
4885
+ "envNeedsDisposer": "A Deployer needs a Disposer after it to reclaim the environment it stands up. Add one, or mark the Deployer as keeping its environment past the run, otherwise the pipeline won't save.",
4886
+ "envDisposerNeedsDeployer": "This Disposer has no Deployer before it, so there is nothing for it to reclaim. Add a Deployer, or remove the Disposer.",
4887
+ "envConsumerAfterDisposer": "A Tester, human-test or Playwright step runs after the Disposer has already reclaimed the environment, so nothing would be left to run against. Move the Disposer below that step, or add another Deployer before it.",
4888
+ "envRetainedButReclaimed": "This Deployer is marked as keeping its environment past the run, but a Disposer after it reclaims exactly that environment. Remove the Disposer, or clear the keep-environment setting.",
4889
+ "retainEnvironmentSetTooltip": "Reclaimed at the end of the run. Click to keep this environment running afterwards (a preview reviewers use once the PR is open); its TTL or an operator then takes it down.",
4890
+ "retainEnvironmentClearTooltip": "Kept running after the run ends. Click to go back to reclaiming it with a Disposer step.",
4884
4891
  "binaryOutputNoStorage": "No service in this board's catalog declares the {capability} capability. Register one, or add the capability to the service you meant, under foundational services.",
4885
4892
  "binaryOutputMissing": "This storage service is no longer in the catalog; pick another.",
4886
4893
  "binaryOutputNotStorage": "This service no longer declares the {capability} capability, so runs will be refused; pick another.",
@@ -4732,6 +4732,13 @@
4732
4732
  "binaryOutputPlaceholder": "Elige un servicio de almacenamiento",
4733
4733
  "binaryOutputContextPlaceholder": "Opcional: servicios que delimitan la generación",
4734
4734
  "binaryOutputNeedsPick": "Un paso que genera salidas binarias no tiene servicio de almacenamiento elegido. Elige uno antes de guardar.",
4735
+ "envNeedsDeployer": "Un paso de Tester, prueba manual o Playwright necesita un Deployer antes. Añade uno o la canalización no se guardará (no hace nada en un servicio que no aprovisiona).",
4736
+ "envNeedsDisposer": "Un Deployer necesita un Disposer después para liberar el entorno que levanta. Añade uno, o marca el Deployer para que conserve su entorno más allá de la ejecución; de lo contrario la canalización no se guardará.",
4737
+ "envDisposerNeedsDeployer": "Este Disposer no tiene ningún Deployer antes, así que no hay nada que liberar. Añade un Deployer o quita el Disposer.",
4738
+ "envConsumerAfterDisposer": "Un paso de Tester, prueba manual o Playwright se ejecuta después de que el Disposer ya haya liberado el entorno, así que no quedaría nada contra lo que ejecutarlo. Mueve el Disposer por debajo de ese paso, o añade otro Deployer antes.",
4739
+ "envRetainedButReclaimed": "Este Deployer está marcado para conservar su entorno más allá de la ejecución, pero un Disposer posterior libera exactamente ese entorno. Quita el Disposer o desmarca la opción de conservar el entorno.",
4740
+ "retainEnvironmentSetTooltip": "Se libera al terminar la ejecución. Haz clic para mantener este entorno en marcha después (una vista previa que los revisores usan con el PR abierto); luego lo retirará su TTL o un operador.",
4741
+ "retainEnvironmentClearTooltip": "Se mantiene en marcha tras la ejecución. Haz clic para volver a liberarlo con un paso Disposer.",
4735
4742
  "binaryOutputNoStorage": "Ningún servicio del catálogo de este tablero declara la capacidad {capability}. Registra uno, o añade la capacidad al servicio que tenías en mente, en servicios fundamentales.",
4736
4743
  "binaryOutputMissing": "Este servicio de almacenamiento ya no está en el catálogo; elige otro.",
4737
4744
  "binaryOutputNotStorage": "Este servicio ya no declara la capacidad {capability}, así que las ejecuciones se rechazarán; elige otro.",
@@ -4732,6 +4732,13 @@
4732
4732
  "binaryOutputPlaceholder": "Choisir un service de stockage",
4733
4733
  "binaryOutputContextPlaceholder": "Facultatif : services qui délimitent la génération",
4734
4734
  "binaryOutputNeedsPick": "Une étape qui génère des sorties binaires n'a aucun service de stockage choisi. Choisissez-en un avant d'enregistrer.",
4735
+ "envNeedsDeployer": "Une étape Tester, de test manuel ou Playwright a besoin d'un Deployer avant elle. Ajoutez-en un, sinon le pipeline ne sera pas enregistré (il ne fait rien sur un service qui ne provisionne rien).",
4736
+ "envNeedsDisposer": "Un Deployer a besoin d'un Disposer après lui pour libérer l'environnement qu'il provisionne. Ajoutez-en un, ou marquez le Deployer comme conservant son environnement au-delà de l'exécution, sinon le pipeline ne sera pas enregistré.",
4737
+ "envDisposerNeedsDeployer": "Ce Disposer n'a aucun Deployer avant lui, il n'a donc rien à libérer. Ajoutez un Deployer ou retirez le Disposer.",
4738
+ "envConsumerAfterDisposer": "Une étape Tester, de test manuel ou Playwright s'exécute après que le Disposer a déjà libéré l'environnement : il ne resterait rien pour l'exécuter. Déplacez le Disposer après cette étape, ou ajoutez un autre Deployer avant elle.",
4739
+ "envRetainedButReclaimed": "Ce Deployer est marqué comme conservant son environnement au-delà de l'exécution, mais un Disposer placé après lui libère précisément cet environnement. Retirez le Disposer, ou désactivez la conservation de l'environnement.",
4740
+ "retainEnvironmentSetTooltip": "Libéré à la fin de l'exécution. Cliquez pour laisser cet environnement actif ensuite (un aperçu que les relecteurs utilisent une fois la PR ouverte) ; son TTL ou un opérateur l'arrêtera.",
4741
+ "retainEnvironmentClearTooltip": "Reste actif après la fin de l'exécution. Cliquez pour le libérer de nouveau via une étape Disposer.",
4735
4742
  "binaryOutputNoStorage": "Aucun service du catalogue de ce tableau ne déclare la capacité {capability}. Enregistrez-en un, ou ajoutez la capacité au service que vous visiez, dans les services fondamentaux.",
4736
4743
  "binaryOutputMissing": "Ce service de stockage n'est plus dans le catalogue ; choisissez-en un autre.",
4737
4744
  "binaryOutputNotStorage": "Ce service ne déclare plus la capacité {capability}, les exécutions seront donc refusées ; choisissez-en un autre.",
@@ -4732,6 +4732,13 @@
4732
4732
  "binaryOutputPlaceholder": "בחר שירות אחסון",
4733
4733
  "binaryOutputContextPlaceholder": "לא חובה: שירותים שמגדירים את היקף היצירה",
4734
4734
  "binaryOutputNeedsPick": "לשלב שמייצר פלטים בינאריים לא נבחר שירות אחסון. בחר אחד לפני השמירה.",
4735
+ "envNeedsDeployer": "שלב Tester, בדיקה ידנית או Playwright דורש Deployer לפניו. הוסף אחד, אחרת הצינור לא יישמר (בשירות שאינו מקצה סביבה הוא לא עושה דבר).",
4736
+ "envNeedsDisposer": "אחרי Deployer נדרש Disposer שישחרר את הסביבה שהוקצתה. הוסף אחד, או סמן את ה-Deployer כשומר על הסביבה שלו גם אחרי הריצה, אחרת הצינור לא יישמר.",
4737
+ "envDisposerNeedsDeployer": "לפני ה-Disposer הזה אין Deployer, ולכן אין מה לשחרר. הוסף Deployer או הסר את ה-Disposer.",
4738
+ "envConsumerAfterDisposer": "שלב Tester, בדיקה ידנית או Playwright רץ אחרי שה-Disposer כבר שחרר את הסביבה, ולכן לא יישאר דבר להריץ מולו. העבר את ה-Disposer מתחת לשלב הזה, או הוסף Deployer נוסף לפניו.",
4739
+ "envRetainedButReclaimed": "ה-Deployer הזה מסומן כשומר על הסביבה שלו גם אחרי הריצה, אך Disposer שאחריו משחרר בדיוק את אותה סביבה. הסר את ה-Disposer, או בטל את סימון שמירת הסביבה.",
4740
+ "retainEnvironmentSetTooltip": "משוחררת בסוף הריצה. לחץ כדי להשאיר את הסביבה פעילה אחריה (תצוגה מקדימה שסוקרים משתמשים בה אחרי פתיחת ה-PR); ה-TTL או מפעיל יורידו אותה בהמשך.",
4741
+ "retainEnvironmentClearTooltip": "נשארת פעילה אחרי סיום הריצה. לחץ כדי לחזור לשחרור שלה בשלב Disposer.",
4735
4742
  "binaryOutputNoStorage": "אף שירות בקטלוג של לוח זה אינו מצהיר על יכולת {capability}. רשום שירות כזה, או הוסף את היכולת לשירות שהתכוונת אליו, תחת שירותי בסיס.",
4736
4743
  "binaryOutputMissing": "שירות אחסון זה כבר אינו בקטלוג; בחר אחר.",
4737
4744
  "binaryOutputNotStorage": "השירות הזה כבר אינו מצהיר על יכולת {capability}, ולכן הרצות יידחו; בחר אחר.",
@@ -4293,6 +4293,13 @@
4293
4293
  "binaryOutputPlaceholder": "Scegli un servizio di archiviazione",
4294
4294
  "binaryOutputContextPlaceholder": "Facoltativo: servizi che delimitano la generazione",
4295
4295
  "binaryOutputNeedsPick": "Un passo che genera output binari non ha un servizio di archiviazione scelto. Scegline uno prima di salvare.",
4296
+ "envNeedsDeployer": "Un passaggio Tester, di test manuale o Playwright ha bisogno di un Deployer prima di sé. Aggiungine uno, altrimenti la pipeline non verrà salvata (su un servizio che non effettua provisioning non fa nulla).",
4297
+ "envNeedsDisposer": "Un Deployer ha bisogno di un Disposer dopo di sé, che liberi l'ambiente creato. Aggiungine uno, oppure contrassegna il Deployer come tale da mantenere il proprio ambiente oltre l'esecuzione, altrimenti la pipeline non verrà salvata.",
4298
+ "envDisposerNeedsDeployer": "Questo Disposer non ha alcun Deployer prima di sé, quindi non ha nulla da liberare. Aggiungi un Deployer oppure rimuovi il Disposer.",
4299
+ "envConsumerAfterDisposer": "Un passaggio Tester, di test manuale o Playwright viene eseguito dopo che il Disposer ha già liberato l'ambiente, quindi non resterebbe nulla su cui eseguirlo. Sposta il Disposer dopo quel passaggio, oppure aggiungi un altro Deployer prima di esso.",
4300
+ "envRetainedButReclaimed": "Questo Deployer è contrassegnato come tale da mantenere il proprio ambiente oltre l'esecuzione, ma un Disposer successivo libera proprio quell'ambiente. Rimuovi il Disposer, oppure togli il contrassegno di mantenimento dell'ambiente.",
4301
+ "retainEnvironmentSetTooltip": "Liberato al termine dell'esecuzione. Fai clic per lasciare questo ambiente attivo dopo (un'anteprima che i revisori usano a PR aperta); lo spegneranno poi il suo TTL o un operatore.",
4302
+ "retainEnvironmentClearTooltip": "Resta attivo dopo la fine dell'esecuzione. Fai clic per tornare a liberarlo con un passaggio Disposer.",
4296
4303
  "binaryOutputNoStorage": "Nessun servizio nel catalogo di questa board dichiara la capacità {capability}. Registrane uno, oppure aggiungi la capacità al servizio che intendevi, nei servizi fondamentali.",
4297
4304
  "binaryOutputMissing": "Questo servizio di archiviazione non è più nel catalogo; scegline un altro.",
4298
4305
  "binaryOutputNotStorage": "Questo servizio non dichiara più la capacità {capability}, quindi le esecuzioni verranno rifiutate; scegline un altro.",
@@ -4732,6 +4732,13 @@
4732
4732
  "binaryOutputPlaceholder": "保存サービスを選択",
4733
4733
  "binaryOutputContextPlaceholder": "任意: 生成の範囲を定めるサービス",
4734
4734
  "binaryOutputNeedsPick": "バイナリ成果物を生成するステップに保存サービスが選択されていません。保存前に選んでください。",
4735
+ "envNeedsDeployer": "Tester、手動テスト、または Playwright のステップの前には Deployer が必要です。追加しないとパイプラインは保存できません(プロビジョニングしないサービスでは何もしません)。",
4736
+ "envNeedsDisposer": "Deployer の後には、用意した環境を解放する Disposer が必要です。追加するか、Deployer に実行後も環境を残す設定を付けてください。どちらもない場合、パイプラインは保存できません。",
4737
+ "envDisposerNeedsDeployer": "この Disposer の前に Deployer がないため、解放する対象がありません。Deployer を追加するか、Disposer を削除してください。",
4738
+ "envConsumerAfterDisposer": "Tester、手動テスト、または Playwright のステップが、Disposer が環境を解放した後に実行されるため、実行対象が残りません。Disposer をそのステップより後ろに移すか、その前にもう一つ Deployer を追加してください。",
4739
+ "envRetainedButReclaimed": "この Deployer には実行後も環境を残す設定が付いていますが、後続の Disposer がまさにその環境を解放します。Disposer を削除するか、環境を残す設定を解除してください。",
4740
+ "retainEnvironmentSetTooltip": "実行の終了時に解放されます。クリックすると実行後も環境を残します(PR を開いた後にレビュアーが使うプレビュー用)。その後は TTL または運用者が停止します。",
4741
+ "retainEnvironmentClearTooltip": "実行の終了後も環境を残します。クリックすると Disposer ステップで解放する動作に戻ります。",
4735
4742
  "binaryOutputNoStorage": "このボードのカタログに {capability} 機能を宣言するサービスがありません。基盤サービスで新たに登録するか、意図したサービスにこの機能を追加してください。",
4736
4743
  "binaryOutputMissing": "この保存サービスはカタログに存在しません。別のサービスを選んでください。",
4737
4744
  "binaryOutputNotStorage": "このサービスは {capability} 機能を宣言しなくなったため、実行は拒否されます。別のサービスを選んでください。",
@@ -4732,6 +4732,13 @@
4732
4732
  "binaryOutputPlaceholder": "Wybierz usługę przechowywania",
4733
4733
  "binaryOutputContextPlaceholder": "Opcjonalnie: usługi wyznaczające zakres generowania",
4734
4734
  "binaryOutputNeedsPick": "Krok generujący wyjścia binarne nie ma wybranej usługi przechowywania. Wybierz ją przed zapisaniem.",
4735
+ "envNeedsDeployer": "Krok Tester, testu manualnego lub Playwright wymaga Deployera przed sobą. Dodaj go, inaczej potok nie zostanie zapisany (przy usłudze bez provisioningu nic nie robi).",
4736
+ "envNeedsDisposer": "Deployer wymaga Disposera po sobie, który zwolni utworzone środowisko. Dodaj go albo oznacz Deployera jako zachowującego środowisko po zakończeniu przebiegu, inaczej potok nie zostanie zapisany.",
4737
+ "envDisposerNeedsDeployer": "Przed tym Disposerem nie ma Deployera, więc nie ma czego zwalniać. Dodaj Deployera albo usuń Disposera.",
4738
+ "envConsumerAfterDisposer": "Krok Tester, testu manualnego lub Playwright wykonuje się po tym, jak Disposer zwolnił już środowisko, więc nie zostanie nic, na czym mógłby działać. Przesuń Disposera poniżej tego kroku albo dodaj przed nim kolejnego Deployera.",
4739
+ "envRetainedButReclaimed": "Ten Deployer jest oznaczony jako zachowujący środowisko po zakończeniu przebiegu, ale Disposer za nim zwalnia dokładnie to środowisko. Usuń Disposera albo wyłącz zachowywanie środowiska.",
4740
+ "retainEnvironmentSetTooltip": "Zwalniane na koniec przebiegu. Kliknij, aby zostawić to środowisko działające po jego zakończeniu (podgląd, z którego recenzenci korzystają po otwarciu PR); później wyłączy je TTL lub operator.",
4741
+ "retainEnvironmentClearTooltip": "Działa po zakończeniu przebiegu. Kliknij, aby wrócić do zwalniania go krokiem Disposer.",
4735
4742
  "binaryOutputNoStorage": "Żadna usługa w katalogu tej tablicy nie deklaruje możliwości {capability}. Zarejestruj taką usługę albo dodaj tę możliwość właściwej usłudze w usługach podstawowych.",
4736
4743
  "binaryOutputMissing": "Tej usługi przechowywania nie ma już w katalogu; wybierz inną.",
4737
4744
  "binaryOutputNotStorage": "Ta usługa nie deklaruje już możliwości {capability}, więc uruchomienia będą odrzucane; wybierz inną.",
@@ -4732,6 +4732,13 @@
4732
4732
  "binaryOutputPlaceholder": "Bir depolama hizmeti seç",
4733
4733
  "binaryOutputContextPlaceholder": "İsteğe bağlı: üretimin kapsamını belirleyen hizmetler",
4734
4734
  "binaryOutputNeedsPick": "İkili çıktı üreten bir adımda depolama hizmeti seçilmemiş. Kaydetmeden önce birini seç.",
4735
+ "envNeedsDeployer": "Tester, manuel test veya Playwright adımının öncesinde bir Deployer gerekir. Bir tane ekle, yoksa işlem hattı kaydedilmez (hiçbir şey sağlamayan bir hizmette etkisizdir).",
4736
+ "envNeedsDisposer": "Bir Deployer'ın ardından, oluşturduğu ortamı geri alan bir Disposer gerekir. Bir tane ekle ya da Deployer'ı, ortamını çalıştırma sonrasında da koruyacak biçimde işaretle; aksi hâlde işlem hattı kaydedilmez.",
4737
+ "envDisposerNeedsDeployer": "Bu Disposer'dan önce bir Deployer yok, dolayısıyla geri alınacak bir şey de yok. Bir Deployer ekle ya da Disposer'ı kaldır.",
4738
+ "envConsumerAfterDisposer": "Tester, manuel test veya Playwright adımı, Disposer ortamı geri aldıktan sonra çalışıyor; dolayısıyla üzerinde çalışılacak bir şey kalmaz. Disposer'ı bu adımın altına taşı ya da öncesine bir Deployer daha ekle.",
4739
+ "envRetainedButReclaimed": "Bu Deployer, ortamını çalıştırma sonrasında da koruyacak biçimde işaretli, ancak ardındaki bir Disposer tam olarak o ortamı geri alıyor. Disposer'ı kaldır ya da ortamı koruma işaretini temizle.",
4740
+ "retainEnvironmentSetTooltip": "Çalıştırmanın sonunda geri alınır. Bu ortamı sonrasında da açık bırakmak için tıkla (PR açıldıktan sonra inceleyicilerin kullandığı bir önizleme); ardından TTL'i ya da bir operatör kapatır.",
4741
+ "retainEnvironmentClearTooltip": "Çalıştırma bittikten sonra da açık kalır. Disposer adımıyla geri almaya dönmek için tıkla.",
4735
4742
  "binaryOutputNoStorage": "Bu panonun katalogundaki hiçbir hizmet {capability} yeteneğini bildirmiyor. Temel hizmetler altında bir hizmet kaydet ya da kastettiğin hizmete bu yeteneği ekle.",
4736
4743
  "binaryOutputMissing": "Bu depolama hizmeti artık katalogda yok; başka birini seç.",
4737
4744
  "binaryOutputNotStorage": "Bu hizmet artık {capability} yeteneğini bildirmiyor, bu yüzden çalıştırmalar reddedilecek; başka birini seç.",
@@ -4732,6 +4732,13 @@
4732
4732
  "binaryOutputPlaceholder": "Оберіть службу зберігання",
4733
4733
  "binaryOutputContextPlaceholder": "Необовʼязково: служби, які визначають межі генерації",
4734
4734
  "binaryOutputNeedsPick": "Для кроку, який генерує бінарні результати, не обрано службу зберігання. Оберіть її перед збереженням.",
4735
+ "envNeedsDeployer": "Крок Tester, ручного тесту або Playwright потребує Deployer перед собою. Додай його, інакше конвеєр не збережеться (для служби без провізіювання він нічого не робить).",
4736
+ "envNeedsDisposer": "Після Deployer потрібен Disposer, який звільнить створене середовище. Додай його або познач Deployer як такий, що зберігає своє середовище після завершення прогону, інакше конвеєр не збережеться.",
4737
+ "envDisposerNeedsDeployer": "Перед цим Disposer немає Deployer, тож звільняти нічого. Додай Deployer або прибери Disposer.",
4738
+ "envConsumerAfterDisposer": "Крок Tester, ручного тесту або Playwright виконується після того, як Disposer уже звільнив середовище, тож не залишиться нічого, на чому його запускати. Перемісти Disposer нижче за цей крок або додай ще один Deployer перед ним.",
4739
+ "envRetainedButReclaimed": "Цей Deployer позначено як такий, що зберігає своє середовище після прогону, але Disposer після нього звільняє саме це середовище. Прибери Disposer або зніми позначку збереження середовища.",
4740
+ "retainEnvironmentSetTooltip": "Звільняється наприкінці прогону. Натисни, щоб залишити це середовище працювати після нього (попередній перегляд, яким рецензенти користуються після відкриття PR); згодом його вимкне TTL або оператор.",
4741
+ "retainEnvironmentClearTooltip": "Працює після завершення прогону. Натисни, щоб повернутися до звільнення його кроком Disposer.",
4735
4742
  "binaryOutputNoStorage": "Жодна служба в каталозі цієї дошки не заявляє здатність {capability}. Зареєструйте таку службу або додайте цю здатність потрібній службі в базових службах.",
4736
4743
  "binaryOutputMissing": "Цієї служби зберігання більше немає в каталозі; оберіть іншу.",
4737
4744
  "binaryOutputNotStorage": "Ця служба більше не заявляє здатність {capability}, тому запуски буде відхилено; оберіть іншу.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.255.0",
3
+ "version": "0.255.1",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.281.0"
43
+ "@cat-factory/contracts": "0.282.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",