@cat-factory/app 0.121.2 → 0.122.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.
@@ -81,6 +81,7 @@ const TASK_TYPES = computed<{ value: TaskTypeChoice; label: string; icon: string
81
81
  label: t('board.addTask.types.review'),
82
82
  icon: 'i-lucide-clipboard-check',
83
83
  },
84
+ { value: 'ralph', label: t('board.addTask.types.ralph'), icon: 'i-lucide-infinity' },
84
85
  { value: 'recurring', label: t('board.addTask.types.recurring'), icon: 'i-lucide-repeat' },
85
86
  ]
86
87
  // A document repository only accepts document/spike tasks (see BoardService.addTask).
@@ -309,6 +310,15 @@ const selectedPipelineLabel = computed(
309
310
  () => pipelines.getPipeline(pipelineId.value)?.name ?? t('board.addTask.chooseAtRunTime'),
310
311
  )
311
312
 
313
+ // Picking the Ralph loop task type auto-selects its pipeline, so the per-task validation
314
+ // command + iteration budget (contributed by the `ralph` agent) surface immediately — the
315
+ // loop is meaningless without them, so "choose at run time" would be a dead end here.
316
+ watch(taskType, (next) => {
317
+ if (next !== 'ralph') return
318
+ const ralph = pipelines.pipelines.find((p) => p.id === 'pl_ralph')
319
+ if (ralph) pipelineId.value = ralph.id
320
+ })
321
+
312
322
  // Task-level agent config contributed by the selected pipeline's agents (e.g. the
313
323
  // Tester's environment). Editable up front; persisted on the task and frozen once
314
324
  // the contributing agent runs. Defaults to each descriptor's default until changed.
@@ -489,10 +499,20 @@ const { requestClose } = useUnsavedGuard({
489
499
 
490
500
  // A recurring task only needs a target frame (its details are filled in the schedule
491
501
  // modal); every other type needs a title. A review task additionally needs a target PR.
502
+ // The Ralph loop's completion criterion (its `ralph.validationCommand` agent-config id). The
503
+ // loop is meaningless without it, so the create form requires it up front — the backend also
504
+ // refuses to start a Ralph run without one (a 422), this just fails fast in the UI.
505
+ const RALPH_VALIDATION_COMMAND_ID = 'ralph.validationCommand'
506
+
492
507
  const canAdd = computed(() => {
493
508
  if (isRecurring.value) return recurringFrameId.value !== null
494
509
  if (title.value.trim().length === 0) return false
495
510
  if (taskType.value === 'review' && !parseReviewPrRef(reviewPrRef.value)) return false
511
+ if (
512
+ taskType.value === 'ralph' &&
513
+ configValue(RALPH_VALIDATION_COMMAND_ID, '').trim().length === 0
514
+ )
515
+ return false
496
516
  return true
497
517
  })
498
518
 
@@ -898,7 +918,7 @@ async function add() {
898
918
  </span>
899
919
  <div v-for="d in configDescriptors" :key="d.id" class="space-y-1">
900
920
  <div class="text-[11px] text-slate-400">{{ d.label }}</div>
901
- <div class="flex flex-wrap gap-1">
921
+ <div v-if="d.type === 'select'" class="flex flex-wrap gap-1">
902
922
  <UButton
903
923
  v-for="opt in d.options"
904
924
  :key="opt.value"
@@ -910,6 +930,15 @@ async function add() {
910
930
  {{ opt.label }}
911
931
  </UButton>
912
932
  </div>
933
+ <UInput
934
+ v-else
935
+ :model-value="configValue(d.id, d.default)"
936
+ :type="d.type === 'number' ? 'number' : 'text'"
937
+ :placeholder="d.placeholder"
938
+ size="xs"
939
+ :data-testid="`agent-config-${d.id}`"
940
+ @update:model-value="(v: string | number) => setConfig(d.id, String(v))"
941
+ />
913
942
  <p class="text-[11px] leading-snug text-slate-500">{{ d.description }}</p>
914
943
  </div>
915
944
  </div>
@@ -28,6 +28,7 @@ import MergerResultView from '~/components/panels/MergerResultView.vue'
28
28
  import InitiativeTrackerWindow from '~/components/initiative/InitiativeTrackerWindow.vue'
29
29
  import InitiativePlanningWindow from '~/components/initiative/InitiativePlanningWindow.vue'
30
30
  import DocInterviewWindow from '~/components/docs/DocInterviewWindow.vue'
31
+ import RalphLoopResultView from '~/components/ralph/RalphLoopResultView.vue'
31
32
 
32
33
  const ui = useUiStore()
33
34
 
@@ -72,6 +73,9 @@ const STEP_RESULT_VIEWS: Record<string, Component> = {
72
73
  // The interactive document-interview gate (WS5): the interviewer's clarifying questions +
73
74
  // answer / continue / proceed, opened as the `doc-interviewer` step's result view.
74
75
  'doc-interview': DocInterviewWindow,
76
+ // The Ralph loop: the persistent retry-until-done iteration history + the programmatic
77
+ // validation command + its latest exit code / output. Opened as the `ralph` step's view.
78
+ 'ralph-loop': RalphLoopResultView,
75
79
  }
76
80
 
77
81
  const active = computed<Component | null>(() => {
@@ -0,0 +1,272 @@
1
+ <script setup lang="ts">
2
+ // Ralph loop window — the dedicated surface for a `ralph` step, opened via the universal
3
+ // result-view host. It surfaces the persistent retry-until-done loop the backend persists on
4
+ // `step.ralph`: the programmatic completion command, the iteration count vs the budget, the
5
+ // most recent validation exit code + output, and the per-iteration history. Synchronous — it
6
+ // reads straight off the execution step (no fetch on open).
7
+ import { computed } from 'vue'
8
+ import { agentKindMeta } from '~/utils/catalog'
9
+ import type { RalphStepState } from '~/types/execution'
10
+ import StepRestartControl from '~/components/panels/StepRestartControl.vue'
11
+ import StepRunMeta from '~/components/panels/StepRunMeta.vue'
12
+ import CopyButton from '~/components/common/CopyButton.vue'
13
+
14
+ const board = useBoardStore()
15
+ const execution = useExecutionStore()
16
+ const { t, d } = useI18n()
17
+
18
+ const { open, blockId, instanceId, stepIndex, close } = useResultView('ralph-loop')
19
+ const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
20
+ const prUrl = computed(() => block.value?.pullRequest?.url ?? null)
21
+
22
+ const instance = computed(() =>
23
+ instanceId.value === null ? null : (execution.getInstance(instanceId.value) ?? null),
24
+ )
25
+ const step = computed(() => {
26
+ if (instance.value === null || stepIndex.value === null) return null
27
+ return instance.value.steps[stepIndex.value] ?? null
28
+ })
29
+ const ralph = computed<RalphStepState | null>(() => step.value?.ralph ?? null)
30
+ const meta = computed(() => agentKindMeta('ralph'))
31
+
32
+ // Iterations, newest-first for the timeline.
33
+ const attempts = computed(() => [...(ralph.value?.attemptLog ?? [])].reverse())
34
+
35
+ /**
36
+ * The display status, rolled up from the persisted loop state + the run status:
37
+ * - `passed` — the step finished (the validation command exited 0);
38
+ * - `gave-up` — the run failed here (the iteration budget was spent);
39
+ * - `running` — an iteration is in flight;
40
+ * - `failing` — the last validation failed and another iteration is about to run.
41
+ */
42
+ type RalphDisplayStatus = 'passed' | 'gave-up' | 'running' | 'failing'
43
+ const status = computed<RalphDisplayStatus>(() => {
44
+ const s = step.value
45
+ if (!s) return 'running'
46
+ if (s.state === 'done') return 'passed'
47
+ if (instance.value?.status === 'failed') return 'gave-up'
48
+ if (s.container?.status === 'starting' || s.container?.status === 'up') return 'running'
49
+ return 'failing'
50
+ })
51
+
52
+ const STATUS_META = computed<
53
+ Record<
54
+ RalphDisplayStatus,
55
+ {
56
+ label: string
57
+ badge: 'success' | 'warning' | 'error' | 'neutral'
58
+ icon: string
59
+ text: string
60
+ }
61
+ >
62
+ >(() => ({
63
+ passed: {
64
+ label: t('ralph.status.passed'),
65
+ badge: 'success',
66
+ icon: 'i-lucide-circle-check',
67
+ text: 'text-emerald-300',
68
+ },
69
+ 'gave-up': {
70
+ label: t('ralph.status.gaveUp'),
71
+ badge: 'error',
72
+ icon: 'i-lucide-circle-x',
73
+ text: 'text-rose-300',
74
+ },
75
+ running: {
76
+ label: t('ralph.status.running'),
77
+ badge: 'warning',
78
+ icon: 'i-lucide-loader',
79
+ text: 'text-amber-300',
80
+ },
81
+ failing: {
82
+ label: t('ralph.status.failing'),
83
+ badge: 'error',
84
+ icon: 'i-lucide-circle-x',
85
+ text: 'text-rose-300',
86
+ },
87
+ }))
88
+ </script>
89
+
90
+ <template>
91
+ <Teleport to="body">
92
+ <div
93
+ v-if="open"
94
+ class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
95
+ @click.self="close"
96
+ >
97
+ <div
98
+ class="m-4 flex w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
99
+ role="dialog"
100
+ aria-modal="true"
101
+ data-testid="ralph-loop-window"
102
+ >
103
+ <header class="flex items-center gap-3 border-b border-slate-800 px-5 py-3">
104
+ <span
105
+ class="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-500/15 text-violet-300"
106
+ >
107
+ <UIcon :name="meta.icon" class="h-4 w-4" />
108
+ </span>
109
+ <div class="min-w-0 flex-1">
110
+ <h2 class="truncate text-sm font-semibold text-slate-100">
111
+ {{ meta.label }}{{ block ? ` — ${block.title}` : '' }}
112
+ </h2>
113
+ <p class="truncate text-[11px] text-slate-400">{{ t('ralph.subtitle') }}</p>
114
+ </div>
115
+ <UBadge
116
+ :color="STATUS_META[status].badge"
117
+ variant="subtle"
118
+ size="sm"
119
+ data-testid="ralph-status"
120
+ >
121
+ {{ STATUS_META[status].label }}
122
+ </UBadge>
123
+ <StepRestartControl
124
+ :instance-id="instanceId"
125
+ :step-index="stepIndex"
126
+ @restarted="close"
127
+ />
128
+ <button
129
+ class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
130
+ @click="close"
131
+ >
132
+ <UIcon name="i-lucide-x" class="h-4 w-4" />
133
+ </button>
134
+ </header>
135
+
136
+ <div class="flex min-h-0 flex-1">
137
+ <div class="min-w-0 flex-1 overflow-y-auto px-5 py-4">
138
+ <div
139
+ v-if="!ralph"
140
+ class="flex h-full flex-col items-center justify-center gap-2 text-center text-slate-400"
141
+ >
142
+ <UIcon :name="meta.icon" class="h-8 w-8 opacity-40" />
143
+ <p class="text-sm">{{ t('ralph.noActivity') }}</p>
144
+ </div>
145
+
146
+ <template v-else>
147
+ <!-- The completion criterion. -->
148
+ <h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
149
+ {{ t('ralph.validationCommand') }}
150
+ </h3>
151
+ <div class="relative rounded-md border border-slate-800 bg-slate-950/60 px-3 py-2">
152
+ <CopyButton :text="ralph.validationCommand" class="absolute end-1 top-1" />
153
+ <code class="block whitespace-pre-wrap pe-8 font-mono text-[12px] text-slate-200">{{
154
+ ralph.validationCommand
155
+ }}</code>
156
+ </div>
157
+
158
+ <!-- The most recent validation output. -->
159
+ <template v-if="ralph.lastValidationTail">
160
+ <h3
161
+ class="mb-1.5 mt-4 text-[11px] font-semibold uppercase tracking-wide text-slate-500"
162
+ >
163
+ {{ t('ralph.lastOutput', { exit: ralph.lastExitCode ?? '?' }) }}
164
+ </h3>
165
+ <div class="relative rounded-md border border-slate-800 bg-slate-950/60 px-3 py-2">
166
+ <CopyButton :text="ralph.lastValidationTail" class="absolute end-1 top-1" />
167
+ <pre
168
+ class="whitespace-pre-wrap pe-8 font-mono text-[11px] leading-relaxed text-slate-400"
169
+ >{{ ralph.lastValidationTail }}</pre
170
+ >
171
+ </div>
172
+ </template>
173
+
174
+ <a
175
+ v-if="prUrl"
176
+ :href="prUrl"
177
+ target="_blank"
178
+ rel="noopener"
179
+ class="mt-3 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200 hover:underline"
180
+ >
181
+ {{ t('ralph.viewPr') }}
182
+ <UIcon name="i-lucide-external-link" class="h-3 w-3" />
183
+ </a>
184
+
185
+ <!-- Iteration history: what each pass produced and whether its validation passed. -->
186
+ <section v-if="attempts.length" class="mt-5">
187
+ <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
188
+ {{ t('ralph.iterationsHeading') }}
189
+ </h3>
190
+ <ol class="space-y-2">
191
+ <li
192
+ v-for="a in attempts"
193
+ :key="a.attempt"
194
+ class="rounded-md border border-slate-800 bg-slate-950/40 px-3 py-2"
195
+ data-testid="ralph-iteration"
196
+ >
197
+ <div class="flex items-center gap-2">
198
+ <UIcon
199
+ :name="a.validationPassed ? 'i-lucide-circle-check' : 'i-lucide-circle-x'"
200
+ class="h-3.5 w-3.5"
201
+ :class="a.validationPassed ? 'text-emerald-400' : 'text-rose-400'"
202
+ />
203
+ <span class="text-[12px] font-medium text-slate-200">
204
+ {{ t('ralph.iteration', { number: a.attempt }) }}
205
+ </span>
206
+ <span class="text-[11px] text-slate-500">
207
+ {{
208
+ a.validationPassed
209
+ ? t('ralph.iterationPassed')
210
+ : t('ralph.iterationFailed', { exit: a.exitCode ?? '?' })
211
+ }}
212
+ </span>
213
+ <span class="ms-auto text-[10px] text-slate-600">{{
214
+ d(new Date(a.at), 'long')
215
+ }}</span>
216
+ </div>
217
+ <p
218
+ v-if="a.summary"
219
+ class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-slate-400"
220
+ >
221
+ {{ a.summary }}
222
+ </p>
223
+ </li>
224
+ </ol>
225
+ </section>
226
+ </template>
227
+ </div>
228
+
229
+ <aside
230
+ class="hidden w-60 shrink-0 flex-col gap-4 border-s border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
231
+ >
232
+ <div v-if="ralph">
233
+ <h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
234
+ {{ t('ralph.sidebar.state') }}
235
+ </h4>
236
+ <div class="flex items-center gap-2 text-[13px]">
237
+ <UIcon
238
+ :name="STATUS_META[status].icon"
239
+ class="h-4 w-4"
240
+ :class="STATUS_META[status].text"
241
+ />
242
+ <span :class="STATUS_META[status].text">{{ STATUS_META[status].label }}</span>
243
+ </div>
244
+ </div>
245
+ <div v-if="ralph">
246
+ <h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
247
+ {{ t('ralph.sidebar.iterations') }}
248
+ </h4>
249
+ <p class="text-[12px] text-slate-300" data-testid="ralph-iteration-count">
250
+ {{
251
+ t('ralph.sidebar.count', { attempts: ralph.attempts, max: ralph.maxIterations })
252
+ }}
253
+ </p>
254
+ </div>
255
+ <StepRunMeta
256
+ v-if="step"
257
+ :step="step"
258
+ :instance-id="instanceId ?? undefined"
259
+ :step-number="stepIndex === null ? undefined : stepIndex + 1"
260
+ :total-steps="instance?.steps.length"
261
+ :run-failed="instance?.status === 'failed'"
262
+ :failure-at="instance?.failure?.occurredAt"
263
+ />
264
+ <p class="mt-auto text-[10px] leading-relaxed text-slate-600">
265
+ {{ t('ralph.sidebar.footer') }}
266
+ </p>
267
+ </aside>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </Teleport>
272
+ </template>
@@ -99,6 +99,7 @@ const TASK_TYPE_KEYS: Record<CreateTaskType, string> = {
99
99
  document: 'settings.workspaceSettings.taskTypes.document',
100
100
  spike: 'settings.workspaceSettings.taskTypes.spike',
101
101
  review: 'settings.workspaceSettings.taskTypes.review',
102
+ ralph: 'settings.workspaceSettings.taskTypes.ralph',
102
103
  }
103
104
 
104
105
  const MODES = computed<{ value: TaskLimitMode; label: string }[]>(() => [
@@ -47,6 +47,9 @@ export type {
47
47
  GateFailingCheck,
48
48
  GateAttempt,
49
49
  GateStepState,
50
+ RalphStepState,
51
+ RalphAttempt,
52
+ RalphVerdict,
50
53
  TesterStepState,
51
54
  HumanTestEnvironment,
52
55
  RunEnvironment,
@@ -724,7 +724,8 @@
724
724
  "bug": "Bug",
725
725
  "document": "Dokument",
726
726
  "spike": "Spike",
727
- "review": "Review"
727
+ "review": "Review",
728
+ "ralph": "Ralph-Schleife"
728
729
  },
729
730
  "observability": {
730
731
  "heading": "Agenten-Observability",
@@ -2026,7 +2027,8 @@
2026
2027
  "document": "Dokument",
2027
2028
  "spike": "Spike",
2028
2029
  "recurring": "Wiederkehrend",
2029
- "review": "Review"
2030
+ "review": "Review",
2031
+ "ralph": "Ralph-Schleife"
2030
2032
  },
2031
2033
  "recurringWithFrame": "Eine wiederkehrende Aufgabe führt eine Pipeline in einem Takt aus. Fahren Sie fort, um Zeitplan + Prompt festzulegen.",
2032
2034
  "recurringNoFrame": "Eine wiederkehrende Aufgabe muss auf einem Service liegen. Fügen Sie sie aus einem Service-Frame (oder einem darin enthaltenen Modul) hinzu.",
@@ -4797,5 +4799,28 @@
4797
4799
  "test": "Tests",
4798
4800
  "other": "Sonstiges"
4799
4801
  }
4802
+ },
4803
+ "ralph": {
4804
+ "subtitle": "Eine dauerhafte Schleife: Aufgabe bearbeiten, Validierungsbefehl ausführen und wiederholen, bis er erfolgreich ist.",
4805
+ "status": {
4806
+ "passed": "Bestanden",
4807
+ "gaveUp": "Aufgegeben",
4808
+ "running": "Läuft",
4809
+ "failing": "Fehlgeschlagen"
4810
+ },
4811
+ "noActivity": "Noch keine Iterationen.",
4812
+ "validationCommand": "Validierungsbefehl",
4813
+ "lastOutput": "Letzte Validierung (Exit {exit})",
4814
+ "viewPr": "Pull Request ansehen",
4815
+ "iterationsHeading": "Iterationen",
4816
+ "iteration": "Iteration {number}",
4817
+ "iterationPassed": "Validierung bestanden",
4818
+ "iterationFailed": "Exit {exit}",
4819
+ "sidebar": {
4820
+ "state": "Status",
4821
+ "iterations": "Iterationen",
4822
+ "count": "{attempts} von {max}",
4823
+ "footer": "Die Schleife wird wiederholt, bis der Validierungsbefehl erfolgreich ist oder das Iterationsbudget aufgebraucht ist."
4824
+ }
4800
4825
  }
4801
4826
  }
@@ -186,7 +186,8 @@
186
186
  "document": "Document",
187
187
  "spike": "Spike",
188
188
  "recurring": "Recurring",
189
- "review": "Review"
189
+ "review": "Review",
190
+ "ralph": "Ralph loop"
190
191
  },
191
192
  "recurringWithFrame": "A recurring task runs a pipeline on a cadence. Continue to set the schedule + prompt.",
192
193
  "recurringNoFrame": "A recurring task must live on a service. Add it from a service frame (or a module inside one).",
@@ -2605,7 +2606,8 @@
2605
2606
  "bug": "bug",
2606
2607
  "document": "document",
2607
2608
  "spike": "spike",
2608
- "review": "review"
2609
+ "review": "review",
2610
+ "ralph": "Ralph loop"
2609
2611
  },
2610
2612
  "observability": {
2611
2613
  "heading": "Agent observability",
@@ -4923,5 +4925,28 @@
4923
4925
  "test": "Tests",
4924
4926
  "other": "Other"
4925
4927
  }
4928
+ },
4929
+ "ralph": {
4930
+ "subtitle": "A persistent loop: work the task, run the validation command, and repeat until it passes.",
4931
+ "status": {
4932
+ "passed": "Passed",
4933
+ "gaveUp": "Gave up",
4934
+ "running": "Running",
4935
+ "failing": "Failing"
4936
+ },
4937
+ "noActivity": "No iterations yet.",
4938
+ "validationCommand": "Validation command",
4939
+ "lastOutput": "Latest validation (exit {exit})",
4940
+ "viewPr": "View pull request",
4941
+ "iterationsHeading": "Iterations",
4942
+ "iteration": "Iteration {number}",
4943
+ "iterationPassed": "validation passed",
4944
+ "iterationFailed": "exit {exit}",
4945
+ "sidebar": {
4946
+ "state": "State",
4947
+ "iterations": "Iterations",
4948
+ "count": "{attempts} of {max}",
4949
+ "footer": "The loop retries until the validation command passes or the iteration budget is spent."
4950
+ }
4926
4951
  }
4927
4952
  }
@@ -168,7 +168,8 @@
168
168
  "document": "Documento",
169
169
  "spike": "Spike",
170
170
  "recurring": "Recurrente",
171
- "review": "Revisión"
171
+ "review": "Revisión",
172
+ "ralph": "Bucle Ralph"
172
173
  },
173
174
  "recurringWithFrame": "Una tarea recurrente ejecuta una pipeline con cierta cadencia. Continúa para definir el horario y el prompt.",
174
175
  "recurringNoFrame": "Una tarea recurrente debe vivir en un servicio. Añádela desde un marco de servicio (o un módulo dentro de él).",
@@ -2420,7 +2421,8 @@
2420
2421
  "bug": "error",
2421
2422
  "document": "documento",
2422
2423
  "spike": "spike",
2423
- "review": "revisión"
2424
+ "review": "revisión",
2425
+ "ralph": "Bucle Ralph"
2424
2426
  },
2425
2427
  "observability": {
2426
2428
  "heading": "Observabilidad del agente",
@@ -4785,5 +4787,28 @@
4785
4787
  "test": "Pruebas",
4786
4788
  "other": "Otros"
4787
4789
  }
4790
+ },
4791
+ "ralph": {
4792
+ "subtitle": "Un bucle persistente: trabaja la tarea, ejecuta el comando de validación y repite hasta que pase.",
4793
+ "status": {
4794
+ "passed": "Superado",
4795
+ "gaveUp": "Abandonado",
4796
+ "running": "En ejecución",
4797
+ "failing": "Fallando"
4798
+ },
4799
+ "noActivity": "Aún no hay iteraciones.",
4800
+ "validationCommand": "Comando de validación",
4801
+ "lastOutput": "Última validación (salida {exit})",
4802
+ "viewPr": "Ver pull request",
4803
+ "iterationsHeading": "Iteraciones",
4804
+ "iteration": "Iteración {number}",
4805
+ "iterationPassed": "validación superada",
4806
+ "iterationFailed": "salida {exit}",
4807
+ "sidebar": {
4808
+ "state": "Estado",
4809
+ "iterations": "Iteraciones",
4810
+ "count": "{attempts} de {max}",
4811
+ "footer": "El bucle se repite hasta que el comando de validación pasa o se agota el presupuesto de iteraciones."
4812
+ }
4788
4813
  }
4789
4814
  }
@@ -168,7 +168,8 @@
168
168
  "document": "Document",
169
169
  "spike": "Spike",
170
170
  "recurring": "Récurrent",
171
- "review": "Revue"
171
+ "review": "Revue",
172
+ "ralph": "Boucle Ralph"
172
173
  },
173
174
  "recurringWithFrame": "Une tâche récurrente exécute une pipeline selon une cadence. Continuez pour définir le calendrier et le prompt.",
174
175
  "recurringNoFrame": "Une tâche récurrente doit appartenir à un service. Ajoutez-la depuis un cadre de service (ou un module à l’intérieur).",
@@ -2420,7 +2421,8 @@
2420
2421
  "bug": "bug",
2421
2422
  "document": "document",
2422
2423
  "spike": "spike",
2423
- "review": "revue"
2424
+ "review": "revue",
2425
+ "ralph": "Boucle Ralph"
2424
2426
  },
2425
2427
  "observability": {
2426
2428
  "heading": "Observabilité de l'agent",
@@ -4785,5 +4787,28 @@
4785
4787
  "test": "Tests",
4786
4788
  "other": "Autre"
4787
4789
  }
4790
+ },
4791
+ "ralph": {
4792
+ "subtitle": "Une boucle persistante : traiter la tâche, exécuter la commande de validation, et recommencer jusqu'à ce qu'elle réussisse.",
4793
+ "status": {
4794
+ "passed": "Réussi",
4795
+ "gaveUp": "Abandonné",
4796
+ "running": "En cours",
4797
+ "failing": "En échec"
4798
+ },
4799
+ "noActivity": "Aucune itération pour l'instant.",
4800
+ "validationCommand": "Commande de validation",
4801
+ "lastOutput": "Dernière validation (code {exit})",
4802
+ "viewPr": "Voir la pull request",
4803
+ "iterationsHeading": "Itérations",
4804
+ "iteration": "Itération {number}",
4805
+ "iterationPassed": "validation réussie",
4806
+ "iterationFailed": "code {exit}",
4807
+ "sidebar": {
4808
+ "state": "État",
4809
+ "iterations": "Itérations",
4810
+ "count": "{attempts} sur {max}",
4811
+ "footer": "La boucle réessaie jusqu'à ce que la commande de validation réussisse ou que le budget d'itérations soit épuisé."
4812
+ }
4788
4813
  }
4789
4814
  }
@@ -168,7 +168,8 @@
168
168
  "document": "מסמך",
169
169
  "spike": "ספייק",
170
170
  "recurring": "מחזורי",
171
- "review": "סקירה"
171
+ "review": "סקירה",
172
+ "ralph": "לולאת Ralph"
172
173
  },
173
174
  "recurringWithFrame": "משימה מחזורית מריצה צינור במרווחים קבועים. המשך כדי להגדיר את התזמון והפרומפט.",
174
175
  "recurringNoFrame": "משימה מחזורית חייבת להתקיים על שירות. הוסף אותה ממסגרת שירות (או ממודול בתוכה).",
@@ -2541,7 +2542,8 @@
2541
2542
  "bug": "באג",
2542
2543
  "document": "מסמך",
2543
2544
  "spike": "חקירה",
2544
- "review": "סקירה"
2545
+ "review": "סקירה",
2546
+ "ralph": "לולאת Ralph"
2545
2547
  },
2546
2548
  "observability": {
2547
2549
  "heading": "תצפיתיות סוכנים",
@@ -4796,5 +4798,28 @@
4796
4798
  "test": "בדיקות",
4797
4799
  "other": "אחר"
4798
4800
  }
4801
+ },
4802
+ "ralph": {
4803
+ "subtitle": "לולאה מתמשכת: לעבוד על המשימה, להריץ את פקודת האימות, ולחזור עד שהיא עוברת.",
4804
+ "status": {
4805
+ "passed": "עבר",
4806
+ "gaveUp": "ויתר",
4807
+ "running": "פועל",
4808
+ "failing": "נכשל"
4809
+ },
4810
+ "noActivity": "עדיין אין איטרציות.",
4811
+ "validationCommand": "פקודת אימות",
4812
+ "lastOutput": "אימות אחרון (יציאה {exit})",
4813
+ "viewPr": "הצג בקשת משיכה",
4814
+ "iterationsHeading": "איטרציות",
4815
+ "iteration": "איטרציה {number}",
4816
+ "iterationPassed": "האימות עבר",
4817
+ "iterationFailed": "יציאה {exit}",
4818
+ "sidebar": {
4819
+ "state": "מצב",
4820
+ "iterations": "איטרציות",
4821
+ "count": "{attempts} מתוך {max}",
4822
+ "footer": "הלולאה חוזרת עד שפקודת האימות עוברת או שתקציב האיטרציות מסתיים."
4823
+ }
4799
4824
  }
4800
4825
  }
@@ -724,7 +724,8 @@
724
724
  "bug": "bug",
725
725
  "document": "documento",
726
726
  "spike": "spike",
727
- "review": "revisione"
727
+ "review": "revisione",
728
+ "ralph": "Ciclo Ralph"
728
729
  },
729
730
  "observability": {
730
731
  "heading": "Osservabilita degli agenti",
@@ -2026,7 +2027,8 @@
2026
2027
  "document": "Documento",
2027
2028
  "spike": "Spike",
2028
2029
  "recurring": "Ricorrente",
2029
- "review": "Revisione"
2030
+ "review": "Revisione",
2031
+ "ralph": "Ciclo Ralph"
2030
2032
  },
2031
2033
  "recurringWithFrame": "Un'attività ricorrente esegue una pipeline a cadenza regolare. Continua per impostare la pianificazione e il prompt.",
2032
2034
  "recurringNoFrame": "Un'attività ricorrente deve risiedere su un servizio. Aggiungila da un frame di servizio (o da un modulo al suo interno).",
@@ -4797,5 +4799,28 @@
4797
4799
  "test": "Test",
4798
4800
  "other": "Altro"
4799
4801
  }
4802
+ },
4803
+ "ralph": {
4804
+ "subtitle": "Un ciclo persistente: lavora sull'attività, esegui il comando di validazione e ripeti finché non passa.",
4805
+ "status": {
4806
+ "passed": "Superato",
4807
+ "gaveUp": "Interrotto",
4808
+ "running": "In esecuzione",
4809
+ "failing": "In errore"
4810
+ },
4811
+ "noActivity": "Ancora nessuna iterazione.",
4812
+ "validationCommand": "Comando di validazione",
4813
+ "lastOutput": "Ultima validazione (uscita {exit})",
4814
+ "viewPr": "Visualizza pull request",
4815
+ "iterationsHeading": "Iterazioni",
4816
+ "iteration": "Iterazione {number}",
4817
+ "iterationPassed": "validazione superata",
4818
+ "iterationFailed": "uscita {exit}",
4819
+ "sidebar": {
4820
+ "state": "Stato",
4821
+ "iterations": "Iterazioni",
4822
+ "count": "{attempts} di {max}",
4823
+ "footer": "Il ciclo riprova finché il comando di validazione non passa o il budget di iterazioni è esaurito."
4824
+ }
4800
4825
  }
4801
4826
  }
@@ -168,7 +168,8 @@
168
168
  "document": "ドキュメント",
169
169
  "spike": "スパイク",
170
170
  "recurring": "繰り返し",
171
- "review": "レビュー"
171
+ "review": "レビュー",
172
+ "ralph": "Ralph ループ"
172
173
  },
173
174
  "recurringWithFrame": "繰り返しタスクは一定の周期でパイプラインを実行します。続行してスケジュールとプロンプトを設定してください。",
174
175
  "recurringNoFrame": "繰り返しタスクはサービス上に配置する必要があります。サービスフレーム(またはその中のモジュール)から追加してください。",
@@ -2542,7 +2543,8 @@
2542
2543
  "bug": "バグ",
2543
2544
  "document": "ドキュメント",
2544
2545
  "spike": "スパイク",
2545
- "review": "レビュー"
2546
+ "review": "レビュー",
2547
+ "ralph": "Ralph ループ"
2546
2548
  },
2547
2549
  "observability": {
2548
2550
  "heading": "エージェントの可観測性",
@@ -4797,5 +4799,28 @@
4797
4799
  "test": "テスト",
4798
4800
  "other": "その他"
4799
4801
  }
4802
+ },
4803
+ "ralph": {
4804
+ "subtitle": "永続的なループ:タスクを進め、検証コマンドを実行し、成功するまで繰り返します。",
4805
+ "status": {
4806
+ "passed": "合格",
4807
+ "gaveUp": "中止",
4808
+ "running": "実行中",
4809
+ "failing": "失敗中"
4810
+ },
4811
+ "noActivity": "まだ反復はありません。",
4812
+ "validationCommand": "検証コマンド",
4813
+ "lastOutput": "最新の検証(終了コード {exit})",
4814
+ "viewPr": "プルリクエストを表示",
4815
+ "iterationsHeading": "反復",
4816
+ "iteration": "反復 {number}",
4817
+ "iterationPassed": "検証に合格",
4818
+ "iterationFailed": "終了コード {exit}",
4819
+ "sidebar": {
4820
+ "state": "状態",
4821
+ "iterations": "反復",
4822
+ "count": "{max} 回中 {attempts} 回",
4823
+ "footer": "検証コマンドが成功するか、反復の上限に達するまでループは再試行します。"
4824
+ }
4800
4825
  }
4801
4826
  }
@@ -168,7 +168,8 @@
168
168
  "document": "Dokument",
169
169
  "spike": "Spike",
170
170
  "recurring": "Cykliczne",
171
- "review": "Przegląd"
171
+ "review": "Przegląd",
172
+ "ralph": "Pętla Ralph"
172
173
  },
173
174
  "recurringWithFrame": "Zadanie cykliczne uruchamia pipeline w określonym rytmie. Kontynuuj, aby ustawić harmonogram i prompt.",
174
175
  "recurringNoFrame": "Zadanie cykliczne musi należeć do usługi. Dodaj je z ramki usługi (lub modułu w jej obrębie).",
@@ -2420,7 +2421,8 @@
2420
2421
  "bug": "błąd",
2421
2422
  "document": "dokument",
2422
2423
  "spike": "spike",
2423
- "review": "przegląd"
2424
+ "review": "przegląd",
2425
+ "ralph": "Pętla Ralph"
2424
2426
  },
2425
2427
  "observability": {
2426
2428
  "heading": "Obserwowalność agenta",
@@ -4785,5 +4787,28 @@
4785
4787
  "test": "Testy",
4786
4788
  "other": "Inne"
4787
4789
  }
4790
+ },
4791
+ "ralph": {
4792
+ "subtitle": "Trwała pętla: pracuj nad zadaniem, uruchom polecenie walidacji i powtarzaj, aż się powiedzie.",
4793
+ "status": {
4794
+ "passed": "Zaliczono",
4795
+ "gaveUp": "Poddano się",
4796
+ "running": "Działa",
4797
+ "failing": "Niepowodzenie"
4798
+ },
4799
+ "noActivity": "Brak iteracji.",
4800
+ "validationCommand": "Polecenie walidacji",
4801
+ "lastOutput": "Ostatnia walidacja (kod {exit})",
4802
+ "viewPr": "Zobacz pull request",
4803
+ "iterationsHeading": "Iteracje",
4804
+ "iteration": "Iteracja {number}",
4805
+ "iterationPassed": "walidacja zaliczona",
4806
+ "iterationFailed": "kod {exit}",
4807
+ "sidebar": {
4808
+ "state": "Stan",
4809
+ "iterations": "Iteracje",
4810
+ "count": "{attempts} z {max}",
4811
+ "footer": "Pętla ponawia próby, aż polecenie walidacji się powiedzie lub wyczerpie się budżet iteracji."
4812
+ }
4788
4813
  }
4789
4814
  }
@@ -168,7 +168,8 @@
168
168
  "document": "Belge",
169
169
  "spike": "Spike",
170
170
  "recurring": "Yinelenen",
171
- "review": "İnceleme"
171
+ "review": "İnceleme",
172
+ "ralph": "Ralph döngüsü"
172
173
  },
173
174
  "recurringWithFrame": "Yinelenen bir görev, bir işlem hattını belirli bir aralıkta çalıştırır. Programı ve istemi ayarlamak için devam edin.",
174
175
  "recurringNoFrame": "Yinelenen bir görev bir serviste bulunmalıdır. Bir servis çerçevesinden (veya içindeki bir modülden) ekleyin.",
@@ -2542,7 +2543,8 @@
2542
2543
  "bug": "hata",
2543
2544
  "document": "belge",
2544
2545
  "spike": "inceleme",
2545
- "review": "inceleme"
2546
+ "review": "inceleme",
2547
+ "ralph": "Ralph döngüsü"
2546
2548
  },
2547
2549
  "observability": {
2548
2550
  "heading": "Agent gözlemlenebilirliği",
@@ -4797,5 +4799,28 @@
4797
4799
  "test": "Testler",
4798
4800
  "other": "Diğer"
4799
4801
  }
4802
+ },
4803
+ "ralph": {
4804
+ "subtitle": "Kalıcı bir döngü: görev üzerinde çalış, doğrulama komutunu çalıştır ve geçene kadar tekrarla.",
4805
+ "status": {
4806
+ "passed": "Geçti",
4807
+ "gaveUp": "Vazgeçildi",
4808
+ "running": "Çalışıyor",
4809
+ "failing": "Başarısız"
4810
+ },
4811
+ "noActivity": "Henüz yineleme yok.",
4812
+ "validationCommand": "Doğrulama komutu",
4813
+ "lastOutput": "Son doğrulama (çıkış {exit})",
4814
+ "viewPr": "Pull request'i görüntüle",
4815
+ "iterationsHeading": "Yinelemeler",
4816
+ "iteration": "Yineleme {number}",
4817
+ "iterationPassed": "doğrulama geçti",
4818
+ "iterationFailed": "çıkış {exit}",
4819
+ "sidebar": {
4820
+ "state": "Durum",
4821
+ "iterations": "Yinelemeler",
4822
+ "count": "{max} içinden {attempts}",
4823
+ "footer": "Döngü, doğrulama komutu geçene kadar veya yineleme bütçesi tükenene kadar yeniden dener."
4824
+ }
4800
4825
  }
4801
4826
  }
@@ -168,7 +168,8 @@
168
168
  "document": "Документ",
169
169
  "spike": "Spike",
170
170
  "recurring": "Періодичне",
171
- "review": "Огляд"
171
+ "review": "Огляд",
172
+ "ralph": "Цикл Ralph"
172
173
  },
173
174
  "recurringWithFrame": "Періодичне завдання запускає конвеєр із заданою періодичністю. Продовжте, щоб задати розклад і промпт.",
174
175
  "recurringNoFrame": "Періодичне завдання має належати сервісу. Додайте його з рамки сервісу (або модуля всередині неї).",
@@ -2420,7 +2421,8 @@
2420
2421
  "bug": "помилка",
2421
2422
  "document": "документ",
2422
2423
  "spike": "spike",
2423
- "review": "огляд"
2424
+ "review": "огляд",
2425
+ "ralph": "Цикл Ralph"
2424
2426
  },
2425
2427
  "observability": {
2426
2428
  "heading": "Спостережуваність агента",
@@ -4785,5 +4787,28 @@
4785
4787
  "test": "Тести",
4786
4788
  "other": "Інше"
4787
4789
  }
4790
+ },
4791
+ "ralph": {
4792
+ "subtitle": "Постійний цикл: працюйте над завданням, запускайте команду перевірки та повторюйте, доки вона не пройде.",
4793
+ "status": {
4794
+ "passed": "Пройдено",
4795
+ "gaveUp": "Припинено",
4796
+ "running": "Виконується",
4797
+ "failing": "Помилка"
4798
+ },
4799
+ "noActivity": "Ще немає ітерацій.",
4800
+ "validationCommand": "Команда перевірки",
4801
+ "lastOutput": "Остання перевірка (код {exit})",
4802
+ "viewPr": "Переглянути pull request",
4803
+ "iterationsHeading": "Ітерації",
4804
+ "iteration": "Ітерація {number}",
4805
+ "iterationPassed": "перевірку пройдено",
4806
+ "iterationFailed": "код {exit}",
4807
+ "sidebar": {
4808
+ "state": "Стан",
4809
+ "iterations": "Ітерації",
4810
+ "count": "{attempts} з {max}",
4811
+ "footer": "Цикл повторюється, доки команда перевірки не пройде або не буде вичерпано бюджет ітерацій."
4812
+ }
4788
4813
  }
4789
4814
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.121.2",
3
+ "version": "0.122.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.134.0"
37
+ "@cat-factory/contracts": "0.136.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",