@cat-factory/app 0.66.0 → 0.68.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.
@@ -37,6 +37,7 @@ const accounts = useAccountsStore()
37
37
  const github = useGitHubStore()
38
38
  const services = useServicesStore()
39
39
  const infra = useInfraConfigStore()
40
+ const agentRuns = useAgentRunsStore()
40
41
  const { t } = useI18n()
41
42
 
42
43
  // The custom-manifest-type catalog feeds the `custom` picker. Cheap + shared (coalesced).
@@ -83,6 +84,12 @@ watch(
83
84
  )
84
85
  const customManifestId = computed(() => props.block.provisioning?.manifestId ?? '')
85
86
  const customManifestPath = computed(() => props.block.provisioning?.manifestPath ?? '')
87
+ // The catalog entry for the pinned custom type — supplies its default manifest path (prefill +
88
+ // detection seed) and whether it can generate/fix (a `fixerPrompt` is declared).
89
+ const selectedCustomType = computed(() =>
90
+ infra.customTypes.find((c) => c.manifestId === customManifestId.value),
91
+ )
92
+ const manifestFixerAvailable = computed(() => !!selectedCustomType.value?.fixerPrompt)
86
93
 
87
94
  const PROVISION_TYPES = computed<{ value: ProvisionType; label: string }[]>(() => [
88
95
  { value: 'infraless', label: t('inspector.testConfig.provisionTypes.infraless') },
@@ -158,14 +165,82 @@ function setKubeRenderer(value: KubernetesRenderer) {
158
165
  commitManifestSource()
159
166
  }
160
167
 
168
+ // Root a service-relative default under the service subtree, normalizing `.`/`..`/empty segments
169
+ // (mirrors the backend `joinPath`). A stored `manifestPath` is always REPO-root-relative — the
170
+ // same form auto-detection produces — so prefill and generate agree with detect.
171
+ function rootUnderDirectory(directory: string | null | undefined, path: string): string {
172
+ const segs: string[] = []
173
+ for (const part of [directory ?? '', path]) {
174
+ for (const seg of part.split('/')) {
175
+ if (!seg || seg === '.') continue
176
+ if (seg === '..') segs.pop()
177
+ else segs.push(seg)
178
+ }
179
+ }
180
+ return segs.join('/')
181
+ }
182
+
161
183
  function setCustomManifestId(value: string) {
162
- patchProvisioning({ type: 'custom', manifestId: value || undefined })
184
+ // Prefill the manifest path with the selected type's default, rooted under the service subtree
185
+ // (repo-root-relative, editable afterwards) so a monorepo service targets the right location
186
+ // even without running Detect. Leave the existing path untouched when the type declares no default.
187
+ const type = infra.customTypes.find((c) => c.manifestId === value)
188
+ const rooted = type?.defaultManifestPath
189
+ ? rootUnderDirectory(repoContext.value?.directory, type.defaultManifestPath)
190
+ : ''
191
+ patchProvisioning({
192
+ type: 'custom',
193
+ manifestId: value || undefined,
194
+ ...(rooted ? { manifestPath: rooted } : {}),
195
+ })
163
196
  }
164
197
 
165
198
  function setCustomManifestPath(value: string) {
166
199
  patchProvisioning({ type: 'custom', manifestPath: value.trim() || undefined })
167
200
  }
168
201
 
202
+ // Generate (or fix) the custom manifest via the fixer coding agent (async repair run). Shown only
203
+ // when the selected type declares a `fixerPrompt`. The dispatched run is tracked live below by id.
204
+ const generating = ref(false)
205
+ const manifestRepairError = ref(false)
206
+ const manifestRepairJobId = ref<string | null>(null)
207
+ const manifestRepairJob = computed(() =>
208
+ manifestRepairJobId.value ? agentRuns.envConfigRepairById(manifestRepairJobId.value) : undefined,
209
+ )
210
+
211
+ async function generateOrFixManifest() {
212
+ const ctx = repoContext.value
213
+ const type = selectedCustomType.value
214
+ if (!ctx || !type?.fixerPrompt) return
215
+ const repo = github.repoFor(ctx.githubId)
216
+ // `manifestPath` is already repo-root-relative (prefill/detect root it); the fallback default
217
+ // still needs rooting for the rare case where nothing was prefilled yet.
218
+ const path =
219
+ customManifestPath.value.trim() ||
220
+ (type.defaultManifestPath ? rootUnderDirectory(ctx.directory, type.defaultManifestPath) : '')
221
+ if (!repo || !path) {
222
+ manifestRepairError.value = true
223
+ return
224
+ }
225
+ generating.value = true
226
+ manifestRepairError.value = false
227
+ manifestRepairJobId.value = null
228
+ try {
229
+ const res = await infra.repairCustomManifest({
230
+ manifestId: type.manifestId,
231
+ owner: repo.owner,
232
+ repo: repo.name,
233
+ manifestPath: path,
234
+ })
235
+ manifestRepairJobId.value = res.repairJobId ?? null
236
+ if (!res.usedAgent) manifestRepairError.value = true
237
+ } catch {
238
+ manifestRepairError.value = true
239
+ } finally {
240
+ generating.value = false
241
+ }
242
+ }
243
+
169
244
  // The provisioning hints (cloud provider + instance size) are advisory inputs to the
170
245
  // ephemeral-environment provisioner, not commonly tuned — keep them collapsed by default.
171
246
  const showProvisioning = ref(false)
@@ -239,19 +314,27 @@ async function detectFromRepo() {
239
314
  owner: repo.owner,
240
315
  repo: repo.name,
241
316
  ...(ctx.directory ? { directory: ctx.directory } : {}),
242
- // Prioritize the option matching the currently-selected tab (kubernetes vs
243
- // docker-compose); the detector falls back to the other when the preferred isn't found.
317
+ // Prioritize the option matching the currently-selected tab (kubernetes / docker-compose /
318
+ // custom); the detector falls back to the other when the preferred isn't found.
244
319
  prefer: provisionType.value,
320
+ // `custom`: the selected type seeds the path search from its default; the current path is
321
+ // kept when it already resolves.
322
+ ...(provisionType.value === 'custom' && customManifestId.value
323
+ ? {
324
+ manifestId: customManifestId.value,
325
+ ...(customManifestPath.value ? { currentManifestPath: customManifestPath.value } : {}),
326
+ }
327
+ : {}),
245
328
  })
246
329
  detectResult.value = rec
247
330
  // Pre-select the recommended compose service so the picker opens on a real choice.
248
331
  pickedComposeService.value =
249
332
  rec.composeServiceCandidates?.find((c) => c.recommended)?.service ?? null
250
- // Only prefill when the detector actually inferred something. A `detected: false`
251
- // recommendation is `infraless`; applying it would WIPE the service's existing
252
- // provisioning (board.updateBlock persists immediately). Leave the current config
253
- // untouched and just surface the "nothing found" note.
254
- if (rec.detected) {
333
+ // Prefill when the detector inferred something. A non-custom `detected: false` recommendation
334
+ // is `infraless`; applying it would WIPE the service's existing provisioning (updateBlock
335
+ // persists immediately) — so we skip it. A `custom` recommendation is non-destructive (it just
336
+ // carries the resolved/default manifest path), so we apply it even when the file wasn't found.
337
+ if (rec.detected || rec.provisioning.type === 'custom') {
255
338
  board.updateBlock(props.block.id, { provisioning: rec.provisioning })
256
339
  if (rec.provisioning.type === 'kubernetes') seedKubeSource(rec.provisioning.manifestSource)
257
340
  }
@@ -369,7 +452,10 @@ function setSize(value: InstanceSize) {
369
452
  </p>
370
453
 
371
454
  <template v-if="detectResult && !detecting">
372
- <p v-if="!detectResult.detected" class="text-[11px] text-amber-300/80">
455
+ <p
456
+ v-if="!detectResult.detected && detectResult.provisioning.type !== 'custom'"
457
+ class="text-[11px] text-amber-300/80"
458
+ >
373
459
  {{ t('inspector.testConfig.detect.none') }}
374
460
  </p>
375
461
  <template v-else>
@@ -659,6 +745,53 @@ function setSize(value: InstanceSize) {
659
745
  (e: KeyboardEvent) => setCustomManifestPath((e.target as HTMLInputElement).value)
660
746
  "
661
747
  />
748
+ <p class="text-[11px] leading-snug text-slate-500">
749
+ {{ t('inspector.testConfig.customManifestPathHint') }}
750
+ </p>
751
+ </div>
752
+
753
+ <!-- Generate (when missing) or fix (when invalid) the manifest via the fixer agent. Only
754
+ shown when the selected type declares a fixer prompt. -->
755
+ <div
756
+ v-if="manifestFixerAvailable && repoContext"
757
+ class="space-y-1.5 rounded border border-slate-800 bg-slate-900/40 p-2"
758
+ >
759
+ <div class="flex items-center justify-between gap-2">
760
+ <span class="text-[11px] text-slate-400">{{
761
+ t('inspector.testConfig.generateManifest.title')
762
+ }}</span>
763
+ <UButton
764
+ size="xs"
765
+ variant="soft"
766
+ color="primary"
767
+ icon="i-lucide-file-cog"
768
+ :loading="generating"
769
+ :disabled="!customManifestPath && !selectedCustomType?.defaultManifestPath"
770
+ @click="generateOrFixManifest"
771
+ >
772
+ {{ t('inspector.testConfig.generateManifest.button') }}
773
+ </UButton>
774
+ </div>
775
+ <p class="text-[11px] leading-snug text-slate-500">
776
+ {{ t('inspector.testConfig.generateManifest.hint') }}
777
+ </p>
778
+ <p v-if="manifestRepairError" class="text-[11px] text-rose-300/80">
779
+ {{ t('inspector.testConfig.generateManifest.error') }}
780
+ </p>
781
+ <p
782
+ v-else-if="manifestRepairJob"
783
+ class="text-[11px]"
784
+ :class="{
785
+ 'text-sky-300/80': manifestRepairJob.status === 'running',
786
+ 'text-emerald-300/80': manifestRepairJob.status === 'succeeded',
787
+ 'text-rose-300/80': manifestRepairJob.status === 'failed',
788
+ }"
789
+ >
790
+ {{ t(`inspector.testConfig.generateManifest.status.${manifestRepairJob.status}`) }}
791
+ </p>
792
+ <p v-else-if="manifestRepairJobId" class="text-[11px] text-sky-300/80">
793
+ {{ t('inspector.testConfig.generateManifest.dispatched') }}
794
+ </p>
662
795
  </div>
663
796
  </div>
664
797
 
@@ -12,7 +12,14 @@ const infra = useInfraConfigStore()
12
12
  const toast = useToast()
13
13
 
14
14
  // A draft for the add/edit form. `manifestId` is locked on edit (it's the PK).
15
- const draft = reactive({ manifestId: '', label: '', acceptsInputHint: '', description: '' })
15
+ const draft = reactive({
16
+ manifestId: '',
17
+ label: '',
18
+ acceptsInputHint: '',
19
+ description: '',
20
+ defaultManifestPath: '',
21
+ fixerPrompt: '',
22
+ })
16
23
  const editing = ref(false)
17
24
  const busy = ref(false)
18
25
 
@@ -22,7 +29,14 @@ const canSave = computed(
22
29
  )
23
30
 
24
31
  function startAdd() {
25
- Object.assign(draft, { manifestId: '', label: '', acceptsInputHint: '', description: '' })
32
+ Object.assign(draft, {
33
+ manifestId: '',
34
+ label: '',
35
+ acceptsInputHint: '',
36
+ description: '',
37
+ defaultManifestPath: '',
38
+ fixerPrompt: '',
39
+ })
26
40
  editing.value = false
27
41
  }
28
42
 
@@ -32,6 +46,8 @@ function startEdit(type: CustomManifestType) {
32
46
  label: type.label,
33
47
  acceptsInputHint: type.acceptsInputHint ?? '',
34
48
  description: type.description ?? '',
49
+ defaultManifestPath: type.defaultManifestPath ?? '',
50
+ fixerPrompt: type.fixerPrompt ?? '',
35
51
  })
36
52
  editing.value = true
37
53
  }
@@ -44,6 +60,10 @@ async function save() {
44
60
  label: draft.label.trim(),
45
61
  ...(draft.acceptsInputHint.trim() ? { acceptsInputHint: draft.acceptsInputHint.trim() } : {}),
46
62
  ...(draft.description.trim() ? { description: draft.description.trim() } : {}),
63
+ ...(draft.defaultManifestPath.trim()
64
+ ? { defaultManifestPath: draft.defaultManifestPath.trim() }
65
+ : {}),
66
+ ...(draft.fixerPrompt.trim() ? { fixerPrompt: draft.fixerPrompt.trim() } : {}),
47
67
  })
48
68
  startAdd()
49
69
  } catch (e) {
@@ -158,6 +178,22 @@ async function remove(type: CustomManifestType) {
158
178
  <UFormField :label="t('settings.infrastructure.customType.description')">
159
179
  <UTextarea v-model="draft.description" :rows="2" />
160
180
  </UFormField>
181
+ <UFormField
182
+ :label="t('settings.infrastructure.customType.defaultManifestPath')"
183
+ :help="t('settings.infrastructure.customType.defaultManifestPathHelp')"
184
+ >
185
+ <UInput
186
+ v-model="draft.defaultManifestPath"
187
+ class="font-mono"
188
+ placeholder="deploy/preview.yaml"
189
+ />
190
+ </UFormField>
191
+ <UFormField
192
+ :label="t('settings.infrastructure.customType.fixerPrompt')"
193
+ :help="t('settings.infrastructure.customType.fixerPromptHelp')"
194
+ >
195
+ <UTextarea v-model="draft.fixerPrompt" :rows="3" />
196
+ </UFormField>
161
197
  <div class="flex justify-end gap-2">
162
198
  <UButton v-if="editing" color="neutral" variant="ghost" size="sm" @click="startAdd">
163
199
  {{ t('common.cancel') }}
@@ -66,6 +66,30 @@ const infraSetup = computed(() => testState.value?.infraSetup ?? null)
66
66
  // The captured stand-up logs are shown on demand (they can be long).
67
67
  const showInfraSetupLogs = ref(false)
68
68
 
69
+ // Once ALL of a tester's infrastructure is up (its container is running, the ephemeral
70
+ // environment (when it tests against one) is ready, and any in-container dependency stand-up
71
+ // succeeded) the agent can actually begin exercising the change. Surface an explicit line
72
+ // saying so, so a run's details don't jump silently from "provisioning" straight into a blank
73
+ // "working" state. This only fills the gap BEFORE the first working signal, so it is scoped
74
+ // to a still-running step that hasn't produced a report yet: a finished step (state 'done'),
75
+ // a failed run, or one that already has a report is past "starting", so the banner clears.
76
+ // (The backend keeps the raw container status at 'up' after the step ends; 'destroyed' is a
77
+ // display-only derivation in StepContainerStatus, so gating on the step state is what stops
78
+ // the banner lingering.) Requires the container to actually report 'up' (a not-yet-created
79
+ // container is not "up"), and requires the tester to genuinely depend on a test environment
80
+ // (an ephemeral env or an in-container stand-up); an infraless tester has nothing to announce.
81
+ const infraReady = computed(() => {
82
+ const s = step.value
83
+ if (!s || runFailed.value || s.state === 'done' || report.value) return false
84
+ if (s.container?.status !== 'up') return false
85
+ const env = stepEnvironment.value
86
+ const infra = infraSetup.value
87
+ if (!env && !infra) return false
88
+ const envReady = !env || env.status === 'ready'
89
+ const standupReady = !infra || infra.started
90
+ return envReady && standupReady
91
+ })
92
+
69
93
  const screenshots = computed<TestScreenshot[]>(() => report.value?.screenshots ?? [])
70
94
  // Resolve each capture into an object URL for the gallery + lightbox. The shared cache
71
95
  // dedupes, so the lightbox reuses what the thumbnails fetched. (The reference design is not
@@ -424,6 +448,19 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
424
448
  </template>
425
449
  </div>
426
450
 
451
+ <!-- Explicit confirmation that every piece of the tester's infrastructure is up
452
+ (container running, the ephemeral environment ready, any in-container
453
+ dependency stand-up done) and the agent is now starting its work — so the
454
+ details don't jump silently from "provisioning" into a blank working state. -->
455
+ <div
456
+ v-if="infraReady"
457
+ data-testid="tester-env-ready"
458
+ class="flex items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-[13px] text-emerald-200"
459
+ >
460
+ <UIcon name="i-lucide-rocket" class="h-4 w-4 shrink-0 text-emerald-400" />
461
+ <span>{{ t('testing.readyBanner') }}</span>
462
+ </div>
463
+
427
464
  <div v-if="executionId">
428
465
  <UButton
429
466
  :icon="showProvisioning ? 'i-lucide-chevron-up' : 'i-lucide-scroll-text'"
@@ -5,6 +5,7 @@ import {
5
5
  registerEnvironmentHandlerContract,
6
6
  removeCustomManifestTypeContract,
7
7
  removeEnvironmentUserHandlerContract,
8
+ repairCustomManifestContract,
8
9
  testEnvironmentHandlerContract,
9
10
  unregisterEnvironmentHandlerContract,
10
11
  upsertCustomManifestTypeContract,
@@ -14,6 +15,7 @@ import type {
14
15
  DetectServiceProvisioningInput,
15
16
  ProvisionType,
16
17
  RegisterEnvironmentHandlerInput,
18
+ RepairCustomManifestInput,
17
19
  TestEnvironmentHandlerInput,
18
20
  UpsertCustomManifestTypeInput,
19
21
  UpsertEnvironmentUserHandlerBody,
@@ -45,6 +47,10 @@ export function infraHandlersApi({ send, ws }: ApiContext) {
45
47
  detectServiceProvisioning: (workspaceId: string, body: DetectServiceProvisioningInput) =>
46
48
  send(detectServiceProvisioningContract, { pathPrefix: ws(workspaceId), body }),
47
49
 
50
+ // Generate/fix a service's custom manifest via the fixer coding agent (async repair run).
51
+ repairCustomManifest: (workspaceId: string, body: RepairCustomManifestInput) =>
52
+ send(repairCustomManifestContract, { pathPrefix: ws(workspaceId), body }),
53
+
48
54
  // `manifestId` (for a `custom` handler) rides as a query param; absent ⇒ the bare handler.
49
55
  unregisterEnvironmentHandler: (
50
56
  workspaceId: string,
@@ -6,6 +6,7 @@ import type {
6
6
  EnvironmentHandlerView,
7
7
  ProvisionType,
8
8
  RegisterEnvironmentHandlerInput,
9
+ RepairCustomManifestInput,
9
10
  TestEnvironmentHandlerInput,
10
11
  UpsertCustomManifestTypeInput,
11
12
  UpsertEnvironmentUserHandlerBody,
@@ -112,6 +113,16 @@ export const useInfraConfigStore = defineStore('infraConfig', () => {
112
113
  return api.detectServiceProvisioning(ws.requireId(), input)
113
114
  }
114
115
 
116
+ /**
117
+ * Generate (or fix) a service's custom manifest via the fixer coding agent. Dispatches a
118
+ * durable async repair run and returns immediately with `usedAgent`/`repairJobId`; the run is
119
+ * tracked via the workspace stream like the provider-config repair. Nothing persisted here.
120
+ */
121
+ async function repairCustomManifest(input: RepairCustomManifestInput) {
122
+ const ws = useWorkspaceStore()
123
+ return api.repairCustomManifest(ws.requireId(), input)
124
+ }
125
+
115
126
  async function unregisterHandler(type: ProvisionType, manifestId?: string | null) {
116
127
  const ws = useWorkspaceStore()
117
128
  await api.unregisterEnvironmentHandler(ws.requireId(), type, manifestId ?? undefined)
@@ -181,6 +192,7 @@ export const useInfraConfigStore = defineStore('infraConfig', () => {
181
192
  registerHandler,
182
193
  testHandler,
183
194
  detectProvisioning,
195
+ repairCustomManifest,
184
196
  unregisterHandler,
185
197
  upsertCustomType,
186
198
  removeCustomType,
@@ -476,6 +476,19 @@
476
476
  "customNoTypes": "No custom manifest types are defined yet. Add one in the Infrastructure window.",
477
477
  "customManifestIdHint": "The custom type this service produces, matched to a remote-custom handler the workspace configures.",
478
478
  "customManifestPath": "Manifest path (optional)",
479
+ "customManifestPathHint": "Prefilled from the type's default when you select it. Use Detect to locate an existing manifest in the repo.",
480
+ "generateManifest": {
481
+ "title": "Manifest file",
482
+ "button": "Generate / fix",
483
+ "hint": "Runs a coding agent to create the manifest at this path (or fix it if it exists but is invalid) and push it to the branch.",
484
+ "error": "Could not start the manifest generation run.",
485
+ "dispatched": "A manifest generation run was started.",
486
+ "status": {
487
+ "running": "Generating the manifest…",
488
+ "succeeded": "Manifest generated and pushed.",
489
+ "failed": "The manifest generation run failed. Retry it from the Infrastructure window."
490
+ }
491
+ },
479
492
  "detect": {
480
493
  "title": "Auto-detect",
481
494
  "button": "Detect from repo",
@@ -1404,6 +1417,10 @@
1404
1417
  "acceptsInputHint": "Input hint (optional)",
1405
1418
  "acceptsInputHintHelp": "Describes the input shape the provider expects.",
1406
1419
  "description": "Description (optional)",
1420
+ "defaultManifestPath": "Default manifest path (optional)",
1421
+ "defaultManifestPathHelp": "Prefilled on a service that pins this type, and the seed for path auto-detection. A complete path (deploy/preview.yaml) or a bare filename.",
1422
+ "fixerPrompt": "Fixer prompt (optional)",
1423
+ "fixerPromptHelp": "Coding-agent instructions to generate or fix the manifest. When set, a service shows a Generate / fix button.",
1407
1424
  "add": "Add type",
1408
1425
  "saveFailed": "Could not save the custom type",
1409
1426
  "removeFailed": "Could not remove the custom type"
@@ -3163,6 +3180,7 @@
3163
3180
  "showLogs": "Show stand-up logs",
3164
3181
  "hideLogs": "Hide stand-up logs"
3165
3182
  },
3183
+ "readyBanner": "Test environment is up. The tester is starting its work.",
3166
3184
  "footer": "Scenarios are the areas the Tester chose to exercise (its spec acceptance scenarios). Outcomes and concerns are grouped under them by name.",
3167
3185
  "@screenshotAlt": {
3168
3186
  "description": "Alt text for a captured screenshot thumbnail; {view} is the screen/view name. The literal word 'screenshot' should be localized."
@@ -454,6 +454,19 @@
454
454
  "namespace": "Los manifiestos fijan el espacio de nombres \"{namespace}\"; se recomienda respetarlo en el gestor del espacio de trabajo.",
455
455
  "confidenceHigh": "Detectado",
456
456
  "confidenceLow": "Sugerencia"
457
+ },
458
+ "customManifestPathHint": "Se rellena con el valor predeterminado del tipo al seleccionarlo. Usa Detectar para localizar un manifiesto existente en el repositorio.",
459
+ "generateManifest": {
460
+ "title": "Archivo de manifiesto",
461
+ "button": "Generar / corregir",
462
+ "hint": "Ejecuta un agente de código para crear el manifiesto en esta ruta (o corregirlo si existe pero no es válido) y subirlo a la rama.",
463
+ "error": "No se pudo iniciar la generación del manifiesto.",
464
+ "dispatched": "Se inició una ejecución de generación del manifiesto.",
465
+ "status": {
466
+ "running": "Generando el manifiesto…",
467
+ "succeeded": "Manifiesto generado y subido.",
468
+ "failed": "La generación del manifiesto falló. Vuelve a intentarlo desde la ventana de Infraestructura."
469
+ }
457
470
  }
458
471
  },
459
472
  "agentConfig": {
@@ -1815,7 +1828,11 @@
1815
1828
  "description": "Descripción (opcional)",
1816
1829
  "add": "Añadir tipo",
1817
1830
  "saveFailed": "No se pudo guardar el tipo personalizado",
1818
- "removeFailed": "No se pudo eliminar el tipo personalizado"
1831
+ "removeFailed": "No se pudo eliminar el tipo personalizado",
1832
+ "defaultManifestPath": "Ruta de manifiesto predeterminada (opcional)",
1833
+ "defaultManifestPathHelp": "Se rellena en un servicio que fije este tipo, y es la base para la detección automática de la ruta. Una ruta completa (deploy/preview.yaml) o solo un nombre de archivo.",
1834
+ "fixerPrompt": "Prompt de corrección (opcional)",
1835
+ "fixerPromptHelp": "Instrucciones para el agente de código para generar o corregir el manifiesto. Cuando se define, el servicio muestra un botón Generar / corregir."
1819
1836
  },
1820
1837
  "handler": {
1821
1838
  "intro": "Configura cómo se gestiona cada tipo de aprovisionamiento que declara un servicio: el motor y la conexión. Los manifiestos o la ruta de compose de un servicio (el qué/dónde) se definen en el servicio.",
@@ -3035,6 +3052,7 @@
3035
3052
  "blocking": "({count} bloqueante) | ({count} bloqueantes)"
3036
3053
  },
3037
3054
  "environment": "Entorno",
3055
+ "readyBanner": "El entorno de pruebas está listo. El Tester está comenzando su trabajo.",
3038
3056
  "footer": "Los escenarios son las áreas que el Tester decidió ejercitar (sus escenarios de aceptación de la especificación). Los resultados y las incidencias se agrupan bajo ellos por nombre.",
3039
3057
  "infrastructure": "Infraestructura",
3040
3058
  "standup": {
@@ -454,6 +454,19 @@
454
454
  "namespace": "Les manifestes fixent l'espace de noms « {namespace} » ; il est recommandé de le respecter sur le gestionnaire de l'espace de travail.",
455
455
  "confidenceHigh": "Détecté",
456
456
  "confidenceLow": "Suggestion"
457
+ },
458
+ "customManifestPathHint": "Prérempli avec la valeur par défaut du type lors de sa sélection. Utilisez Détecter pour localiser un manifeste existant dans le dépôt.",
459
+ "generateManifest": {
460
+ "title": "Fichier de manifeste",
461
+ "button": "Générer / corriger",
462
+ "hint": "Lance un agent de code pour créer le manifeste à ce chemin (ou le corriger s'il existe mais n'est pas valide) et le pousser sur la branche.",
463
+ "error": "Impossible de démarrer la génération du manifeste.",
464
+ "dispatched": "Une exécution de génération du manifeste a démarré.",
465
+ "status": {
466
+ "running": "Génération du manifeste…",
467
+ "succeeded": "Manifeste généré et poussé.",
468
+ "failed": "La génération du manifeste a échoué. Réessayez depuis la fenêtre Infrastructure."
469
+ }
457
470
  }
458
471
  },
459
472
  "agentConfig": {
@@ -1815,7 +1828,11 @@
1815
1828
  "description": "Description (facultatif)",
1816
1829
  "add": "Ajouter le type",
1817
1830
  "saveFailed": "Impossible d'enregistrer le type personnalisé",
1818
- "removeFailed": "Impossible de supprimer le type personnalisé"
1831
+ "removeFailed": "Impossible de supprimer le type personnalisé",
1832
+ "defaultManifestPath": "Chemin de manifeste par défaut (facultatif)",
1833
+ "defaultManifestPathHelp": "Prérempli sur un service qui épingle ce type, et base de la détection automatique du chemin. Un chemin complet (deploy/preview.yaml) ou un simple nom de fichier.",
1834
+ "fixerPrompt": "Prompt de correction (facultatif)",
1835
+ "fixerPromptHelp": "Instructions pour l'agent de code afin de générer ou corriger le manifeste. Lorsqu'il est défini, le service affiche un bouton Générer / corriger."
1819
1836
  },
1820
1837
  "handler": {
1821
1838
  "intro": "Configurez la manière dont chaque type de provisionnement déclaré par un service est géré: le moteur et la connexion. Les manifestes ou le chemin compose d'un service (le quoi/où) se définissent sur le service.",
@@ -3035,6 +3052,7 @@
3035
3052
  "blocking": "({count} bloquante) | ({count} bloquantes)"
3036
3053
  },
3037
3054
  "environment": "Environnement",
3055
+ "readyBanner": "L'environnement de test est prêt. Le Testeur commence son travail.",
3038
3056
  "footer": "Les scénarios sont les domaines que le Testeur a choisi d'éprouver (ses scénarios d'acceptation de la spécification). Les résultats et les réserves y sont regroupés par nom.",
3039
3057
  "infrastructure": "Infrastructure",
3040
3058
  "standup": {
@@ -454,6 +454,19 @@
454
454
  "namespace": "המניפסטים מקבעים את מרחב השמות \"{namespace}\"; מומלץ לכבד אותו במטפל של המרחב.",
455
455
  "confidenceHigh": "זוהה",
456
456
  "confidenceLow": "הצעה"
457
+ },
458
+ "customManifestPathHint": "מתמלא מברירת המחדל של הסוג בעת הבחירה. השתמש ב'זיהוי' כדי לאתר מניפסט קיים במאגר.",
459
+ "generateManifest": {
460
+ "title": "קובץ מניפסט",
461
+ "button": "צור / תקן",
462
+ "hint": "מריץ סוכן קוד ליצירת המניפסט בנתיב זה (או לתיקונו אם קיים אך אינו תקין) ולדחיפתו לענף.",
463
+ "error": "לא ניתן להתחיל את הפקת המניפסט.",
464
+ "dispatched": "הפקת מניפסט הופעלה.",
465
+ "status": {
466
+ "running": "מפיק את המניפסט…",
467
+ "succeeded": "המניפסט נוצר ונדחף.",
468
+ "failed": "הפקת המניפסט נכשלה. נסה שוב מחלון התשתית."
469
+ }
457
470
  }
458
471
  },
459
472
  "agentConfig": {
@@ -1364,7 +1377,11 @@
1364
1377
  "description": "תיאור (אופציונלי)",
1365
1378
  "add": "הוסף סוג",
1366
1379
  "saveFailed": "לא ניתן לשמור את הסוג המותאם",
1367
- "removeFailed": "לא ניתן להסיר את הסוג המותאם"
1380
+ "removeFailed": "לא ניתן להסיר את הסוג המותאם",
1381
+ "defaultManifestPath": "נתיב מניפסט ברירת מחדל (אופציונלי)",
1382
+ "defaultManifestPathHelp": "מתמלא בשירות שמצמיד סוג זה, ומשמש בסיס לזיהוי אוטומטי של הנתיב. נתיב מלא (deploy/preview.yaml) או שם קובץ בלבד.",
1383
+ "fixerPrompt": "הנחיית תיקון (אופציונלי)",
1384
+ "fixerPromptHelp": "הוראות לסוכן הקוד ליצירה או תיקון של המניפסט. כאשר מוגדר, השירות מציג כפתור צור / תקן."
1368
1385
  },
1369
1386
  "handler": {
1370
1387
  "intro": "הגדר כיצד מטופל כל סוג אספקה שהשירות מצהיר עליו: המנוע והחיבור. המניפסטים או נתיב ה-compose של שירות (מה/היכן) מוגדרים על השירות.",
@@ -3054,6 +3071,7 @@
3054
3071
  "showLogs": "הצג יומני הקמה",
3055
3072
  "hideLogs": "הסתר יומני הקמה"
3056
3073
  },
3074
+ "readyBanner": "סביבת הבדיקה פעילה. הבודק מתחיל בעבודתו.",
3057
3075
  "footer": "תרחישים הם התחומים שהבודק בחר לבחון (תרחישי קבלת המפרט שלו). תוצאות וחששות מקובצים תחתם לפי שם."
3058
3076
  },
3059
3077
  "visualConfirm": {
@@ -454,6 +454,19 @@
454
454
  "namespace": "マニフェストは名前空間「{namespace}」を固定しています。ワークスペースのハンドラーでそれを尊重することを推奨します。",
455
455
  "confidenceHigh": "検出",
456
456
  "confidenceLow": "提案"
457
+ },
458
+ "customManifestPathHint": "タイプを選択すると既定値が自動入力されます。リポジトリ内の既存のマニフェストを探すには「検出」を使用してください。",
459
+ "generateManifest": {
460
+ "title": "マニフェストファイル",
461
+ "button": "生成 / 修正",
462
+ "hint": "コーディングエージェントを実行して、このパスにマニフェストを作成(存在するが無効な場合は修正)し、ブランチにプッシュします。",
463
+ "error": "マニフェスト生成の実行を開始できませんでした。",
464
+ "dispatched": "マニフェスト生成の実行を開始しました。",
465
+ "status": {
466
+ "running": "マニフェストを生成しています…",
467
+ "succeeded": "マニフェストを生成してプッシュしました。",
468
+ "failed": "マニフェスト生成の実行に失敗しました。インフラウィンドウから再試行してください。"
469
+ }
457
470
  }
458
471
  },
459
472
  "agentConfig": {
@@ -1366,7 +1379,11 @@
1366
1379
  "description": "説明(任意)",
1367
1380
  "add": "タイプを追加",
1368
1381
  "saveFailed": "カスタムタイプを保存できませんでした",
1369
- "removeFailed": "カスタムタイプを削除できませんでした"
1382
+ "removeFailed": "カスタムタイプを削除できませんでした",
1383
+ "defaultManifestPath": "既定のマニフェストパス(任意)",
1384
+ "defaultManifestPathHelp": "このタイプを固定するサービスに自動入力され、パス自動検出の起点になります。完全なパス(deploy/preview.yaml)またはファイル名のみ。",
1385
+ "fixerPrompt": "修正プロンプト(任意)",
1386
+ "fixerPromptHelp": "マニフェストを生成または修正するためのコーディングエージェントへの指示。設定すると、サービスに「生成 / 修正」ボタンが表示されます。"
1370
1387
  },
1371
1388
  "handler": {
1372
1389
  "intro": "サービスが宣言する各プロビジョニングタイプの処理方法(エンジンと接続)を構成します。サービスのマニフェストや compose パス(何を/どこで)はサービス側で設定します。",
@@ -3056,6 +3073,7 @@
3056
3073
  "showLogs": "スタンドアップログを表示",
3057
3074
  "hideLogs": "スタンドアップログを非表示"
3058
3075
  },
3076
+ "readyBanner": "テスト環境が起動しました。テスターが作業を開始します。",
3059
3077
  "footer": "シナリオはTesterが検証対象に選んだ領域(スペックの受け入れシナリオ)です。結果と懸念は名前ごとにその下にグループ化されます。"
3060
3078
  },
3061
3079
  "visualConfirm": {
@@ -454,6 +454,19 @@
454
454
  "namespace": "Manifesty ustalają przestrzeń nazw \"{namespace}\"; zaleca się jej przestrzeganie w handlerze przestrzeni roboczej.",
455
455
  "confidenceHigh": "Wykryto",
456
456
  "confidenceLow": "Sugestia"
457
+ },
458
+ "customManifestPathHint": "Wypełniane wartością domyślną typu po jego wybraniu. Użyj Wykryj, aby znaleźć istniejący manifest w repozytorium.",
459
+ "generateManifest": {
460
+ "title": "Plik manifestu",
461
+ "button": "Generuj / napraw",
462
+ "hint": "Uruchamia agenta kodu, aby utworzyć manifest w tej ścieżce (lub naprawić go, jeśli istnieje, ale jest nieprawidłowy) i wypchnąć go do gałęzi.",
463
+ "error": "Nie udało się rozpocząć generowania manifestu.",
464
+ "dispatched": "Rozpoczęto generowanie manifestu.",
465
+ "status": {
466
+ "running": "Generowanie manifestu…",
467
+ "succeeded": "Manifest wygenerowany i wypchnięty.",
468
+ "failed": "Generowanie manifestu nie powiodło się. Spróbuj ponownie w oknie Infrastruktura."
469
+ }
457
470
  }
458
471
  },
459
472
  "agentConfig": {
@@ -1815,7 +1828,11 @@
1815
1828
  "description": "Opis (opcjonalnie)",
1816
1829
  "add": "Dodaj typ",
1817
1830
  "saveFailed": "Nie udało się zapisać niestandardowego typu",
1818
- "removeFailed": "Nie udało się usunąć niestandardowego typu"
1831
+ "removeFailed": "Nie udało się usunąć niestandardowego typu",
1832
+ "defaultManifestPath": "Domyślna ścieżka manifestu (opcjonalnie)",
1833
+ "defaultManifestPathHelp": "Wypełniane dla usługi, która przypina ten typ, oraz podstawa automatycznego wykrywania ścieżki. Pełna ścieżka (deploy/preview.yaml) lub sama nazwa pliku.",
1834
+ "fixerPrompt": "Prompt naprawczy (opcjonalnie)",
1835
+ "fixerPromptHelp": "Instrukcje dla agenta kodu do wygenerowania lub naprawy manifestu. Gdy ustawione, usługa pokazuje przycisk Generuj / napraw."
1819
1836
  },
1820
1837
  "handler": {
1821
1838
  "intro": "Skonfiguruj, jak obsługiwany jest każdy typ provisioningu zadeklarowany przez usługę: silnik i połączenie. Manifesty lub ścieżka compose usługi (co/gdzie) są ustawiane na usłudze.",
@@ -3035,6 +3052,7 @@
3035
3052
  "blocking": "({count} blokujące) | ({count} blokujące) | ({count} blokujących)"
3036
3053
  },
3037
3054
  "environment": "Środowisko",
3055
+ "readyBanner": "Środowisko testowe jest gotowe. Tester rozpoczyna pracę.",
3038
3056
  "footer": "Scenariusze to obszary, które Tester postanowił sprawdzić (jego scenariusze akceptacyjne ze specyfikacji). Wyniki i zastrzeżenia są pod nimi grupowane według nazwy.",
3039
3057
  "infrastructure": "Infrastruktura",
3040
3058
  "standup": {
@@ -454,6 +454,19 @@
454
454
  "namespace": "Manifestler \"{namespace}\" ad alanını sabitliyor; çalışma alanı işleyicisinde buna uymanız önerilir.",
455
455
  "confidenceHigh": "Algılandı",
456
456
  "confidenceLow": "Öneri"
457
+ },
458
+ "customManifestPathHint": "Türü seçtiğinizde varsayılan değeriyle doldurulur. Depodaki mevcut bir manifesti bulmak için Algıla'yı kullanın.",
459
+ "generateManifest": {
460
+ "title": "Manifest dosyası",
461
+ "button": "Oluştur / düzelt",
462
+ "hint": "Bu yolda manifesti oluşturmak (veya varsa ancak geçersizse düzeltmek) ve dala göndermek için bir kod aracısı çalıştırır.",
463
+ "error": "Manifest oluşturma çalışması başlatılamadı.",
464
+ "dispatched": "Bir manifest oluşturma çalışması başlatıldı.",
465
+ "status": {
466
+ "running": "Manifest oluşturuluyor…",
467
+ "succeeded": "Manifest oluşturuldu ve gönderildi.",
468
+ "failed": "Manifest oluşturma çalışması başarısız oldu. Altyapı penceresinden yeniden deneyin."
469
+ }
457
470
  }
458
471
  },
459
472
  "agentConfig": {
@@ -1366,7 +1379,11 @@
1366
1379
  "description": "Açıklama (isteğe bağlı)",
1367
1380
  "add": "Tür ekle",
1368
1381
  "saveFailed": "Özel tür kaydedilemedi",
1369
- "removeFailed": "Özel tür kaldırılamadı"
1382
+ "removeFailed": "Özel tür kaldırılamadı",
1383
+ "defaultManifestPath": "Varsayılan manifest yolu (isteğe bağlı)",
1384
+ "defaultManifestPathHelp": "Bu türü sabitleyen bir hizmette önceden doldurulur ve yol otomatik algılamanın temelidir. Tam bir yol (deploy/preview.yaml) veya yalnızca bir dosya adı.",
1385
+ "fixerPrompt": "Düzeltme istemi (isteğe bağlı)",
1386
+ "fixerPromptHelp": "Manifesti oluşturmak veya düzeltmek için kod aracısı talimatları. Ayarlandığında hizmette bir Oluştur / düzelt düğmesi gösterilir."
1370
1387
  },
1371
1388
  "handler": {
1372
1389
  "intro": "Bir servisin bildirdiği her sağlama türünün nasıl ele alınacağını yapılandırın: motor ve bağlantı. Bir servisin manifestleri veya compose yolu (ne/nerede) serviste ayarlanır.",
@@ -3056,6 +3073,7 @@
3056
3073
  "showLogs": "Başlatma günlüklerini göster",
3057
3074
  "hideLogs": "Başlatma günlüklerini gizle"
3058
3075
  },
3076
+ "readyBanner": "Test ortamı hazır. Test aracı çalışmaya başlıyor.",
3059
3077
  "footer": "Senaryolar, Tester'ın test etmek için seçtiği alanlardır (spesifikasyon kabul senaryoları). Sonuçlar ve endişeler ada göre bunların altında gruplanır."
3060
3078
  },
3061
3079
  "visualConfirm": {
@@ -454,6 +454,19 @@
454
454
  "namespace": "Маніфести фіксують простір імен \"{namespace}\"; рекомендуємо дотримуватися його в обробнику робочого простору.",
455
455
  "confidenceHigh": "Виявлено",
456
456
  "confidenceLow": "Пропозиція"
457
+ },
458
+ "customManifestPathHint": "Заповнюється значенням типу за замовчуванням під час вибору. Скористайтеся «Виявити», щоб знайти наявний маніфест у репозиторії.",
459
+ "generateManifest": {
460
+ "title": "Файл маніфесту",
461
+ "button": "Згенерувати / виправити",
462
+ "hint": "Запускає агента коду, щоб створити маніфест за цим шляхом (або виправити його, якщо він існує, але недійсний) і надіслати до гілки.",
463
+ "error": "Не вдалося розпочати генерування маніфесту.",
464
+ "dispatched": "Розпочато генерування маніфесту.",
465
+ "status": {
466
+ "running": "Генерування маніфесту…",
467
+ "succeeded": "Маніфест згенеровано та надіслано.",
468
+ "failed": "Генерування маніфесту не вдалося. Повторіть спробу у вікні «Інфраструктура»."
469
+ }
457
470
  }
458
471
  },
459
472
  "agentConfig": {
@@ -1815,7 +1828,11 @@
1815
1828
  "description": "Опис (необов'язково)",
1816
1829
  "add": "Додати тип",
1817
1830
  "saveFailed": "Не вдалося зберегти власний тип",
1818
- "removeFailed": "Не вдалося видалити власний тип"
1831
+ "removeFailed": "Не вдалося видалити власний тип",
1832
+ "defaultManifestPath": "Типовий шлях маніфесту (необов’язково)",
1833
+ "defaultManifestPathHelp": "Заповнюється для сервісу, який закріплює цей тип, і є основою для автоматичного визначення шляху. Повний шлях (deploy/preview.yaml) або лише ім’я файлу.",
1834
+ "fixerPrompt": "Промпт для виправлення (необов’язково)",
1835
+ "fixerPromptHelp": "Інструкції для агента коду щодо генерування або виправлення маніфесту. Якщо задано, сервіс показує кнопку «Згенерувати / виправити»."
1819
1836
  },
1820
1837
  "handler": {
1821
1838
  "intro": "Налаштуйте, як обробляється кожен тип провіженінгу, оголошений службою: рушій і з'єднання. Маніфести або шлях compose служби (що/де) задаються на службі.",
@@ -3035,6 +3052,7 @@
3035
3052
  "blocking": "({count} блокувальне) | ({count} блокувальні) | ({count} блокувальних)"
3036
3053
  },
3037
3054
  "environment": "Середовище",
3055
+ "readyBanner": "Тестове середовище готове. Тестувальник починає роботу.",
3038
3056
  "footer": "Сценарії — це області, які Тестувальник вирішив перевірити (його сценарії приймання зі специфікації). Результати та зауваження групуються під ними за назвою.",
3039
3057
  "infrastructure": "Інфраструктура",
3040
3058
  "standup": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.66.0",
3
+ "version": "0.68.0",
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",
@@ -34,7 +34,7 @@
34
34
  "valibot": "^1.4.2",
35
35
  "vue": "^3.5.39",
36
36
  "wretch": "^3.0.9",
37
- "@cat-factory/contracts": "0.72.0"
37
+ "@cat-factory/contracts": "0.73.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",