@cat-factory/app 0.273.0 → 0.273.2

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.
@@ -6,7 +6,18 @@
6
6
  import type { InfraEngine, ProvisionType } from '@cat-factory/contracts'
7
7
  import type { RunEnvironment, HumanTestEnvironmentStatus } from '~/types/execution'
8
8
 
9
- const props = defineProps<{ environment: RunEnvironment | null; degradedReason?: string | null }>()
9
+ const props = defineProps<{
10
+ environment: RunEnvironment | null
11
+ /**
12
+ * Whether the enclosing run is still being driven (`runIsActive`). A transitional status
13
+ * (`provisioning` / `tearing_down`) keeps its label once the run stops, because that is the
14
+ * last thing the provider reported, but its icon stops spinning: nothing is standing this
15
+ * environment up any more. Required rather than defaulted so a new call site has to say which
16
+ * run it is rendering, instead of silently inheriting a perpetual spinner.
17
+ */
18
+ runActive: boolean
19
+ degradedReason?: string | null
20
+ }>()
10
21
 
11
22
  const { t, d } = useI18n()
12
23
 
@@ -74,6 +85,13 @@ const ENV_STATUS_META = computed<
74
85
  icon: 'i-lucide-circle-off',
75
86
  },
76
87
  }))
88
+
89
+ // The two statuses that describe a transition IN FLIGHT. Only these ever animate, and only
90
+ // while the run driving the transition is still being driven itself.
91
+ const envInTransition = computed(
92
+ () =>
93
+ props.environment?.status === 'provisioning' || props.environment?.status === 'tearing_down',
94
+ )
77
95
  </script>
78
96
 
79
97
  <template>
@@ -88,10 +106,7 @@ const ENV_STATUS_META = computed<
88
106
  class="h-3.5 w-3.5"
89
107
  :class="[
90
108
  ENV_STATUS_META[environment.status].color,
91
- {
92
- 'animate-spin':
93
- environment.status === 'provisioning' || environment.status === 'tearing_down',
94
- },
109
+ { 'animate-spin': runActive && envInTransition },
95
110
  ]"
96
111
  />
97
112
  <span :class="ENV_STATUS_META[environment.status].color">{{
@@ -31,6 +31,7 @@ const FAMILIES = [
31
31
  'kimi',
32
32
  'deepseek',
33
33
  'glm',
34
+ 'grok',
34
35
  ] as const satisfies readonly ModelFamily[]
35
36
  const REGIONS = ['usa', 'europe', 'china', 'other'] as const satisfies readonly AccountRegion[]
36
37
  const MODES = ['off', 'blocklist', 'allowlist'] as const satisfies readonly ModelPolicyMode[]
@@ -47,6 +48,7 @@ const familyLabels = computed<Record<ModelFamily, string>>(() => ({
47
48
  kimi: t('settings.modelPolicy.families.kimi'),
48
49
  deepseek: t('settings.modelPolicy.families.deepseek'),
49
50
  glm: t('settings.modelPolicy.families.glm'),
51
+ grok: t('settings.modelPolicy.families.grok'),
50
52
  }))
51
53
  const regionLabels = computed<Record<AccountRegion, string>>(() => ({
52
54
  usa: t('settings.modelPolicy.regions.usa'),
@@ -25,6 +25,7 @@ import {
25
25
  REDIRECT_PARK_PRESENTATION,
26
26
  type RedirectParkView,
27
27
  dedicatedParkView,
28
+ runIsActive,
28
29
  } from '~/utils/pipelineRender'
29
30
  import InputGateNotice from '~/components/inputGate/InputGateNotice.vue'
30
31
 
@@ -125,13 +126,12 @@ const showHistory = ref(false)
125
126
  // "spinning up" phase, no spinner.
126
127
  const runFailed = computed(() => instance.value?.status === 'failed')
127
128
 
128
- // Whether the run is still doing something (can still spin infra up/down). A terminal
129
- // run (`done`/`failed`) has nothing left to provision, so the infra-attempts drawer
130
- // stops its background live-polling (manual refresh stays available).
131
- const runLive = computed(() => {
132
- const status = instance.value?.status
133
- return status != null && status !== 'done' && status !== 'failed'
134
- })
129
+ // Whether the engine is still driving this run, and so can still spin infrastructure up or down.
130
+ // One shared predicate drives every infra surface below: the attempts drawer's background poll
131
+ // (manual refresh stays available regardless), the container card's cold-boot spinner, and the
132
+ // environment panel's transition spinner. A run that is terminal OR parked has nothing in flight,
133
+ // so none of those may keep animating.
134
+ const runActive = computed(() => runIsActive(instance.value?.status))
135
135
 
136
136
  // Live elapsed-time clock for the open step.
137
137
  const { isRunning, durationLabel } = useStepTimer({
@@ -497,6 +497,7 @@ async function copyOutput() {
497
497
  <StepMetadataCard
498
498
  :step="step"
499
499
  :run-failed="runFailed"
500
+ :run-active="runActive"
500
501
  :duration-label="durationLabel"
501
502
  :is-running="isRunning"
502
503
  :step-number="stepNumber"
@@ -592,7 +593,11 @@ async function copyOutput() {
592
593
 
593
594
  <!-- ephemeral environment lifecycle (spinning up / running / shut down /
594
595
  errored + the exact error), when this step runs against one -->
595
- <EnvironmentStatusPanel v-if="stepEnvironment" :environment="stepEnvironment" />
596
+ <EnvironmentStatusPanel
597
+ v-if="stepEnvironment"
598
+ :environment="stepEnvironment"
599
+ :run-active="runActive"
600
+ />
596
601
 
597
602
  <!-- frontend UI-test: how the frame's backend bindings resolved (env var →
598
603
  live URL | mocked) + the run-start advisories (duplicate env vars /
@@ -639,7 +644,7 @@ async function copyOutput() {
639
644
  v-if="showProvisioning"
640
645
  class="mt-2"
641
646
  :execution-id="executionId"
642
- :live="runLive"
647
+ :live="runActive"
643
648
  />
644
649
  </div>
645
650
 
@@ -8,7 +8,18 @@ import { containerPhaseLabel } from '~/utils/pipelineRender'
8
8
  // making calls), and the container's id + reachable URL once up. Shared by the generic
9
9
  // step detail (StepMetadataCard) and the dedicated Tester window so both surface WHAT the
10
10
  // container is doing and WHERE it lives instead of a bare "working" — identical parity.
11
- const props = defineProps<{ step: PipelineStep; runFailed: boolean }>()
11
+ const props = defineProps<{
12
+ step: PipelineStep
13
+ runFailed: boolean
14
+ /**
15
+ * Whether the enclosing run is still being driven (`runIsActive`). Distinct from
16
+ * {@link runFailed}, which answers whether the container was RECLAIMED: a run parked on a
17
+ * human decision or a spend budget has reclaimed nothing, so its container keeps whatever
18
+ * status it stopped at. A `starting` one is no longer cold-booting, though, so it must not
19
+ * keep spinning as though it were.
20
+ */
21
+ runActive: boolean
22
+ }>()
12
23
 
13
24
  const { t, te } = useI18n()
14
25
 
@@ -59,6 +70,14 @@ const CONTAINER_STATUS_META: Record<
59
70
  },
60
71
  }
61
72
 
73
+ // Whether the status icon animates: only a cold-boot genuinely in flight, i.e. one whose run is
74
+ // still being driven. A `starting` container on a parked or terminated run keeps its label and
75
+ // freezes, the same way a mid-flight subtask item does (`subtaskIconClass`).
76
+ const spinIcon = computed(
77
+ () =>
78
+ props.runActive && !!containerStatus.value && CONTAINER_STATUS_META[containerStatus.value].spin,
79
+ )
80
+
62
81
  // The friendly phase label (clone → "Preparing workspace", …); only meaningful while up.
63
82
  const phaseLabel = computed(() => containerPhaseLabel(props.step.container?.phase, { t, te }))
64
83
 
@@ -82,7 +101,7 @@ const { copy: copyText } = useCopyToClipboard()
82
101
  <UIcon
83
102
  :name="CONTAINER_STATUS_META[containerStatus].icon"
84
103
  class="h-4 w-4 shrink-0"
85
- :class="CONTAINER_STATUS_META[containerStatus].spin ? 'animate-spin' : ''"
104
+ :class="spinIcon ? 'animate-spin' : ''"
86
105
  />
87
106
  <span class="font-medium">{{ t(CONTAINER_STATUS_KEYS[containerStatus]) }}</span>
88
107
  <template v-if="phaseLabel && containerStatus === 'up'">
@@ -20,6 +20,8 @@ import MarkdownProse from '~/components/common/MarkdownProse.vue'
20
20
  const props = defineProps<{
21
21
  step: PipelineStep
22
22
  runFailed: boolean
23
+ /** Whether the run is still being driven; passed through so its container card can freeze. */
24
+ runActive: boolean
23
25
  durationLabel: string | null
24
26
  isRunning: boolean
25
27
  stepNumber: number
@@ -246,7 +248,12 @@ async function copyRunId() {
246
248
 
247
249
  <!-- container lifecycle (status / live phase / id + url) — shared with the Tester
248
250
  window so both surface what the container is doing and where it lives. -->
249
- <StepContainerStatus :step="step" :run-failed="runFailed" class="mt-4" />
251
+ <StepContainerStatus
252
+ :step="step"
253
+ :run-failed="runFailed"
254
+ :run-active="runActive"
255
+ class="mt-4"
256
+ />
250
257
 
251
258
  <!-- live subtask breakdown -->
252
259
  <div v-if="step.subtasks && step.subtasks.total > 0" class="mt-4">
@@ -110,6 +110,13 @@ const DIRECT_PROVIDERS = computed<ProviderMeta[]>(() => [
110
110
  t('providers.apiKeys.providers.moonshot.step2'),
111
111
  ],
112
112
  },
113
+ {
114
+ value: 'xai',
115
+ label: t('providers.apiKeys.providers.xai.label'),
116
+ url: 'https://console.x.ai/',
117
+ steps: [t('providers.apiKeys.providers.xai.step1'), t('providers.apiKeys.providers.xai.step2')],
118
+ caches: true,
119
+ },
113
120
  ])
114
121
 
115
122
  /** Proxies / gateways: one key fronts many vendors. These are intermediaries, not vendors. */
@@ -6,13 +6,16 @@
6
6
  // panels' drawer, or `executionId` for a run's "Infrastructure attempts" drawer (which
7
7
  // surfaces that run's container/runner/env attempts).
8
8
  //
9
- // In `executionId` mode the drawer LIVE-tracks: while the run is active (`live`) it
10
- // silently re-polls so each container spin-up / tear-down appears with its timestamp as
11
- // it happens, and it does one final poll when the run goes terminal to catch the last
12
- // tear-down row (written just before the terminal event), after which the auto-poll
13
- // stops. Background polls never spin the refresh button (they're silent), but the manual
14
- // refresh control stays available even once the run is terminal so a tear-down row that
15
- // was missed or not yet persisted at the terminal instant can always be refetched.
9
+ // In `executionId` mode the drawer LIVE-tracks: while the engine is driving the run (`live`,
10
+ // from `runIsActive`) it silently re-polls so each container spin-up / tear-down appears with
11
+ // its timestamp as it happens, and it does one final poll when the run stops being driven, to
12
+ // catch the last tear-down row (written just before the terminal event), after which the
13
+ // auto-poll stops. "Stops being driven" covers a PARK as well as a terminal state: a run waiting
14
+ // on a human decision or a raised spend budget writes no further attempts until it resumes, and
15
+ // the same watch restarts the poll when it does. Background polls never spin the refresh button
16
+ // (they're silent), but the manual refresh control stays available even once the run is terminal,
17
+ // so a tear-down row that was missed or not yet persisted at the terminal instant can always be
18
+ // refetched.
16
19
  import { onBeforeUnmount, onMounted, watch } from 'vue'
17
20
  import type {
18
21
  ProvisioningOperation,
@@ -23,7 +26,10 @@ import type {
23
26
  const props = defineProps<{
24
27
  subsystem?: ProvisioningSubsystem
25
28
  executionId?: string
26
- /** Run-details mode only: whether the run is still active (drives live polling). */
29
+ /**
30
+ * Run-details mode only: whether the engine is still driving the run (`runIsActive`), which
31
+ * drives the background poll. A terminal OR parked run writes no further attempts.
32
+ */
27
33
  live?: boolean
28
34
  }>()
29
35
 
@@ -68,8 +74,8 @@ watch(
68
74
  // clears, `live` and `executionId` fall away in the same tick, so stop the interval
69
75
  // unconditionally or it leaks (firing no-op reloads) for the component's lifetime.
70
76
  stopPolling()
71
- // On the active→terminal transition, poll once more (silently) to pick up the
72
- // tear-down row the engine writes just before it emits the terminal state.
77
+ // On the active→stopped transition (parked or terminal), poll once more (silently) to pick
78
+ // up the tear-down row the engine writes just before it emits that state.
73
79
  if (wasLive && props.executionId != null) reload(true)
74
80
  },
75
81
  { immediate: true },
@@ -21,6 +21,7 @@ import AttemptEntryHeader from '~/components/panels/AttemptEntryHeader.vue'
21
21
  import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
22
22
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
23
23
  import MarkdownProse from '~/components/common/MarkdownProse.vue'
24
+ import { runIsActive } from '~/utils/pipelineRender'
24
25
 
25
26
  const board = useBoardStore()
26
27
  const execution = useExecutionStore()
@@ -65,12 +66,10 @@ const qualityVerdicts = computed(() => [...(quality.value?.verdicts ?? [])].reve
65
66
  // run's infrastructure attempts + logs (container/runner/env spin-up), not just the
66
67
  // report. The container/subtask signals already flow onto the step via the generic poll.
67
68
  const runFailed = computed(() => instance.value?.status === 'failed')
68
- // A terminal run (done/failed) can't spin more infra: the attempts drawer stops its
69
- // background live-polling (manual refresh stays available).
70
- const runLive = computed(() => {
71
- const status = instance.value?.status
72
- return status != null && status !== 'done' && status !== 'failed'
73
- })
69
+ // Whether the engine is still driving this run. A run that is terminal OR parked can't spin more
70
+ // infra, so the attempts drawer stops its background poll (manual refresh stays available) and
71
+ // neither the container card nor the environment panel keeps animating over it.
72
+ const runActive = computed(() => runIsActive(instance.value?.status))
74
73
  const stepEnvironment = computed(() => step.value?.environment ?? null)
75
74
  const executionId = computed(() => instance.value?.id ?? null)
76
75
  // The infra-attempts log drawer is opened on demand (it fetches the per-run log rows).
@@ -355,8 +354,12 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
355
354
  <h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
356
355
  {{ t('testing.infrastructure') }}
357
356
  </h3>
358
- <StepContainerStatus :step="step" :run-failed="runFailed" />
359
- <EnvironmentStatusPanel v-if="stepEnvironment" :environment="stepEnvironment" />
357
+ <StepContainerStatus :step="step" :run-failed="runFailed" :run-active="runActive" />
358
+ <EnvironmentStatusPanel
359
+ v-if="stepEnvironment"
360
+ :environment="stepEnvironment"
361
+ :run-active="runActive"
362
+ />
360
363
 
361
364
  <!-- In-container docker-compose dependency stand-up (local-infra tester): the
362
365
  outcome + the captured `docker compose up` logs. This is the stand-up that
@@ -459,7 +462,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
459
462
  v-if="showProvisioning"
460
463
  class="mt-2"
461
464
  :execution-id="executionId"
462
- :live="runLive"
465
+ :live="runActive"
463
466
  />
464
467
  </div>
465
468
  </section>
@@ -115,6 +115,7 @@ export type {
115
115
  VisualConfirmDesignReferences,
116
116
  VisualConfirmRound,
117
117
  ExecutionInstance,
118
+ ExecutionStatus,
118
119
  // The historical frontend name for a per-block review comment is the contract's
119
120
  // StepReviewComment; the env-status union is the contract's EnvironmentStatus.
120
121
  StepReviewComment as ReviewComment,
@@ -1,8 +1,17 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { binaryCandidateStatusSchema, stepSkipReasonSchema } from '@cat-factory/contracts'
2
+ import {
3
+ binaryCandidateStatusSchema,
4
+ executionStatusSchema,
5
+ stepSkipReasonSchema,
6
+ } from '@cat-factory/contracts'
3
7
  import type { ExecutionInstance, PipelineStep } from '~/types/execution'
4
8
  import { missingI18nKeys } from '../../test/i18nKeys'
5
- import { REDIRECT_PARK_PRESENTATION, dedicatedParkView, stepSkipReasonKey } from './pipelineRender'
9
+ import {
10
+ REDIRECT_PARK_PRESENTATION,
11
+ dedicatedParkView,
12
+ runIsActive,
13
+ stepSkipReasonKey,
14
+ } from './pipelineRender'
6
15
 
7
16
  /** A minimal coder step; the predicate only reads approval/followUps/forkDecision. */
8
17
  const step = (over: Partial<PipelineStep>): PipelineStep =>
@@ -229,3 +238,29 @@ describe('stepSkipReasonKey', () => {
229
238
  expect(missingI18nKeys([...keys, 'pipeline.progress.skipped.unknown'])).toEqual([])
230
239
  })
231
240
  })
241
+
242
+ describe('runIsActive', () => {
243
+ it('answers true for exactly the one status the engine is driving under', () => {
244
+ // Derived from the vocabulary the engine writes, not a hand-listed set: a status added to the
245
+ // picklist fails here until someone decides whether infrastructure can move under it. That
246
+ // decision is what every animated infra indicator is gated on, so defaulting a new member
247
+ // either way silently is the failure this pins.
248
+ expect(executionStatusSchema.options.filter(runIsActive)).toEqual(['running'])
249
+ })
250
+
251
+ it('treats a PARK as inactive, not only a terminal state', () => {
252
+ // The distinction that makes this more than `status !== 'done' && status !== 'failed'`: a run
253
+ // waiting on a human decision (`blocked`) or on a raised spend budget (`paused`) is asleep in
254
+ // the durable driver. Nothing is cold-booting or being torn down under it, so a container left
255
+ // `starting` or an environment left `provisioning` must stop claiming otherwise.
256
+ expect(runIsActive('blocked')).toBe(false)
257
+ expect(runIsActive('paused')).toBe(false)
258
+ })
259
+
260
+ it('answers false when the run has no status to read', () => {
261
+ // The overlays render before/after their run resolves, and an unknown run is not a running
262
+ // one: the indicator must start still and begin turning once the run says it is being driven.
263
+ expect(runIsActive(null)).toBe(false)
264
+ expect(runIsActive(undefined)).toBe(false)
265
+ })
266
+ })
@@ -2,9 +2,38 @@
2
2
  // TaskPipelineMini, AgentStepDetail), so the "is this step still live?" logic stays
3
3
  // in one place rather than being re-derived as inline ternaries per component.
4
4
 
5
- import type { AgentState, ExecutionInstance, PipelineStep } from '~/types/execution'
5
+ import type {
6
+ AgentState,
7
+ ExecutionInstance,
8
+ ExecutionStatus,
9
+ PipelineStep,
10
+ } from '~/types/execution'
6
11
  import { isStepSkipReason } from '@cat-factory/contracts'
7
12
 
13
+ /**
14
+ * Whether the engine is presently DRIVING this run, which is the one condition under which
15
+ * infrastructure can still be moving (a container cold-booting, an environment coming up or
16
+ * being torn down, a fresh provisioning attempt landing in the log).
17
+ *
18
+ * `running` is that condition and nothing else is. `done`/`failed` are terminal, and the other
19
+ * two are parks where the durable driver is asleep on an event: `blocked` waits on a human
20
+ * decision, `paused` on a spend budget that has to be raised. A parked run holds whatever infra
21
+ * state it stopped at, so nothing about it is in flight.
22
+ *
23
+ * This is what every ANIMATED infra indicator is gated on. A spinner is a claim that something is
24
+ * happening right now, so a container left `starting` or an environment left `provisioning` when
25
+ * its run stopped must keep its label (that IS the last thing the provider reported) and stop
26
+ * turning: the run's own status says why it stopped, and a perpetual spinner over a run nobody is
27
+ * driving reads as a live cold-boot that is simply taking a while. The same rule governs the
28
+ * infra-attempts drawer's background poll, which has nothing to re-read off a parked run.
29
+ *
30
+ * The step-level sibling is `stepIsRunning` (`useStepTimer`), which answers the narrower question
31
+ * of whether one step's clock should tick; this one is about the run as a whole.
32
+ */
33
+ export function runIsActive(status: ExecutionStatus | null | undefined): boolean {
34
+ return status === 'running'
35
+ }
36
+
8
37
  /**
9
38
  * Visual state of a conditionally-run companion attached to a gate step (today the
10
39
  * Tester's `fixer`): it MIGHT run (`possible`), is running now (`running`), ran at
@@ -1240,7 +1240,8 @@
1240
1240
  "qwen": "Qwen (Alibaba)",
1241
1241
  "kimi": "Kimi (Moonshot)",
1242
1242
  "deepseek": "DeepSeek",
1243
- "glm": "GLM (Z.ai)"
1243
+ "glm": "GLM (Z.ai)",
1244
+ "grok": "Grok (xAI)"
1244
1245
  },
1245
1246
  "regions": {
1246
1247
  "usa": "Vereinigte Staaten",
@@ -3394,6 +3395,11 @@
3394
3395
  "step1": "Öffnen Sie platform.moonshot.ai → API Keys und erstellen Sie einen Schlüssel.",
3395
3396
  "step2": "Kopieren Sie den Schlüssel; er authentifiziert den OpenAI-kompatiblen Moonshot-Endpunkt."
3396
3397
  },
3398
+ "xai": {
3399
+ "label": "xAI (Grok API)",
3400
+ "step1": "Öffnen Sie console.x.ai → API Keys und erstellen Sie einen Schlüssel.",
3401
+ "step2": "Kopieren Sie den Schlüssel; er authentifiziert den OpenAI-kompatiblen xAI-Endpunkt."
3402
+ },
3397
3403
  "openrouter": {
3398
3404
  "label": "OpenRouter",
3399
3405
  "step1": "Öffnen Sie openrouter.ai → Keys und erstellen Sie einen API-Schlüssel.",
@@ -4001,7 +4001,8 @@
4001
4001
  "qwen": "Qwen (Alibaba)",
4002
4002
  "kimi": "Kimi (Moonshot)",
4003
4003
  "deepseek": "DeepSeek",
4004
- "glm": "GLM (Z.ai)"
4004
+ "glm": "GLM (Z.ai)",
4005
+ "grok": "Grok (xAI)"
4005
4006
  },
4006
4007
  "regions": {
4007
4008
  "usa": "United States",
@@ -4280,6 +4281,11 @@
4280
4281
  "step1": "Open platform.moonshot.ai → API Keys and create a key.",
4281
4282
  "step2": "Copy the key; it authenticates the OpenAI-compatible Moonshot endpoint."
4282
4283
  },
4284
+ "xai": {
4285
+ "label": "xAI (Grok API)",
4286
+ "step1": "Open console.x.ai → API Keys and create a key.",
4287
+ "step2": "Copy the key; it authenticates the OpenAI-compatible xAI endpoint."
4288
+ },
4283
4289
  "openrouter": {
4284
4290
  "label": "OpenRouter",
4285
4291
  "step1": "Open openrouter.ai → Keys and create an API key.",
@@ -3861,7 +3861,8 @@
3861
3861
  "qwen": "Qwen (Alibaba)",
3862
3862
  "kimi": "Kimi (Moonshot)",
3863
3863
  "deepseek": "DeepSeek",
3864
- "glm": "GLM (Z.ai)"
3864
+ "glm": "GLM (Z.ai)",
3865
+ "grok": "Grok (xAI)"
3865
3866
  },
3866
3867
  "regions": {
3867
3868
  "usa": "Estados Unidos",
@@ -4137,6 +4138,11 @@
4137
4138
  "step1": "Abre platform.moonshot.ai → API Keys y crea una clave.",
4138
4139
  "step2": "Copia la clave; autentica el endpoint de Moonshot compatible con OpenAI."
4139
4140
  },
4141
+ "xai": {
4142
+ "label": "xAI (API de Grok)",
4143
+ "step1": "Abre console.x.ai → API Keys y crea una clave.",
4144
+ "step2": "Copia la clave; autentica el endpoint de xAI compatible con OpenAI."
4145
+ },
4140
4146
  "openrouter": {
4141
4147
  "label": "OpenRouter",
4142
4148
  "step1": "Abre openrouter.ai → Keys y crea una clave de API.",
@@ -3861,7 +3861,8 @@
3861
3861
  "qwen": "Qwen (Alibaba)",
3862
3862
  "kimi": "Kimi (Moonshot)",
3863
3863
  "deepseek": "DeepSeek",
3864
- "glm": "GLM (Z.ai)"
3864
+ "glm": "GLM (Z.ai)",
3865
+ "grok": "Grok (xAI)"
3865
3866
  },
3866
3867
  "regions": {
3867
3868
  "usa": "États-Unis",
@@ -4137,6 +4138,11 @@
4137
4138
  "step1": "Ouvrez platform.moonshot.ai → API Keys et créez une clé.",
4138
4139
  "step2": "Copiez la clé ; elle authentifie l'endpoint Moonshot compatible OpenAI."
4139
4140
  },
4141
+ "xai": {
4142
+ "label": "xAI (API Grok)",
4143
+ "step1": "Ouvrez console.x.ai → API Keys et créez une clé.",
4144
+ "step2": "Copiez la clé ; elle authentifie l'endpoint xAI compatible OpenAI."
4145
+ },
4140
4146
  "openrouter": {
4141
4147
  "label": "OpenRouter",
4142
4148
  "step1": "Ouvrez openrouter.ai → Keys et créez une clé d'API.",
@@ -3861,7 +3861,8 @@
3861
3861
  "qwen": "Qwen (Alibaba)",
3862
3862
  "kimi": "Kimi (Moonshot)",
3863
3863
  "deepseek": "DeepSeek",
3864
- "glm": "GLM (Z.ai)"
3864
+ "glm": "GLM (Z.ai)",
3865
+ "grok": "Grok (xAI)"
3865
3866
  },
3866
3867
  "regions": {
3867
3868
  "usa": "ארצות הברית",
@@ -4137,6 +4138,11 @@
4137
4138
  "step1": "פתח את platform.moonshot.ai → API Keys וצור מפתח.",
4138
4139
  "step2": "העתק את המפתח; הוא מאמת את נקודת הקצה של Moonshot התואמת ל-OpenAI."
4139
4140
  },
4141
+ "xai": {
4142
+ "label": "xAI (Grok API)",
4143
+ "step1": "פתח את console.x.ai → API Keys וצור מפתח.",
4144
+ "step2": "העתק את המפתח; הוא מאמת את נקודת הקצה של xAI התואמת ל-OpenAI."
4145
+ },
4140
4146
  "openrouter": {
4141
4147
  "label": "OpenRouter",
4142
4148
  "step1": "פתח את openrouter.ai → Keys וצור מפתח API.",
@@ -1240,7 +1240,8 @@
1240
1240
  "qwen": "Qwen (Alibaba)",
1241
1241
  "kimi": "Kimi (Moonshot)",
1242
1242
  "deepseek": "DeepSeek",
1243
- "glm": "GLM (Z.ai)"
1243
+ "glm": "GLM (Z.ai)",
1244
+ "grok": "Grok (xAI)"
1244
1245
  },
1245
1246
  "regions": {
1246
1247
  "usa": "Stati Uniti",
@@ -3394,6 +3395,11 @@
3394
3395
  "step1": "Apri platform.moonshot.ai → API Keys e crea una chiave.",
3395
3396
  "step2": "Copia la chiave; autentica l'endpoint Moonshot compatibile con OpenAI."
3396
3397
  },
3398
+ "xai": {
3399
+ "label": "xAI (Grok API)",
3400
+ "step1": "Apri console.x.ai → API Keys e crea una chiave.",
3401
+ "step2": "Copia la chiave; autentica l'endpoint xAI compatibile con OpenAI."
3402
+ },
3397
3403
  "openrouter": {
3398
3404
  "label": "OpenRouter",
3399
3405
  "step1": "Apri openrouter.ai → Keys e crea una chiave API.",
@@ -3861,7 +3861,8 @@
3861
3861
  "qwen": "Qwen (Alibaba)",
3862
3862
  "kimi": "Kimi (Moonshot)",
3863
3863
  "deepseek": "DeepSeek",
3864
- "glm": "GLM (Z.ai)"
3864
+ "glm": "GLM (Z.ai)",
3865
+ "grok": "Grok (xAI)"
3865
3866
  },
3866
3867
  "regions": {
3867
3868
  "usa": "アメリカ合衆国",
@@ -4137,6 +4138,11 @@
4137
4138
  "step1": "platform.moonshot.ai → API Keys を開き、キーを作成します。",
4138
4139
  "step2": "キーをコピーします。OpenAI 互換の Moonshot エンドポイントを認証します。"
4139
4140
  },
4141
+ "xai": {
4142
+ "label": "xAI (Grok API)",
4143
+ "step1": "console.x.ai → API Keys を開き、キーを作成します。",
4144
+ "step2": "キーをコピーします。OpenAI 互換の xAI エンドポイントを認証します。"
4145
+ },
4140
4146
  "openrouter": {
4141
4147
  "label": "OpenRouter",
4142
4148
  "step1": "openrouter.ai → Keys を開き、API キーを作成します。",
@@ -3861,7 +3861,8 @@
3861
3861
  "qwen": "Qwen (Alibaba)",
3862
3862
  "kimi": "Kimi (Moonshot)",
3863
3863
  "deepseek": "DeepSeek",
3864
- "glm": "GLM (Z.ai)"
3864
+ "glm": "GLM (Z.ai)",
3865
+ "grok": "Grok (xAI)"
3865
3866
  },
3866
3867
  "regions": {
3867
3868
  "usa": "Stany Zjednoczone",
@@ -4137,6 +4138,11 @@
4137
4138
  "step1": "Otwórz platform.moonshot.ai → API Keys i utwórz klucz.",
4138
4139
  "step2": "Skopiuj klucz; uwierzytelnia on zgodny z OpenAI endpoint Moonshot."
4139
4140
  },
4141
+ "xai": {
4142
+ "label": "xAI (API Grok)",
4143
+ "step1": "Otwórz console.x.ai → API Keys i utwórz klucz.",
4144
+ "step2": "Skopiuj klucz; uwierzytelnia on zgodny z OpenAI endpoint xAI."
4145
+ },
4140
4146
  "openrouter": {
4141
4147
  "label": "OpenRouter",
4142
4148
  "step1": "Otwórz openrouter.ai → Keys i utwórz klucz API.",
@@ -3861,7 +3861,8 @@
3861
3861
  "qwen": "Qwen (Alibaba)",
3862
3862
  "kimi": "Kimi (Moonshot)",
3863
3863
  "deepseek": "DeepSeek",
3864
- "glm": "GLM (Z.ai)"
3864
+ "glm": "GLM (Z.ai)",
3865
+ "grok": "Grok (xAI)"
3865
3866
  },
3866
3867
  "regions": {
3867
3868
  "usa": "Amerika Birleşik Devletleri",
@@ -4137,6 +4138,11 @@
4137
4138
  "step1": "platform.moonshot.ai → API Keys adresini açın ve bir anahtar oluşturun.",
4138
4139
  "step2": "Anahtarı kopyalayın; OpenAI uyumlu Moonshot uç noktasının kimliğini doğrular."
4139
4140
  },
4141
+ "xai": {
4142
+ "label": "xAI (Grok API)",
4143
+ "step1": "console.x.ai → API Keys adresini açın ve bir anahtar oluşturun.",
4144
+ "step2": "Anahtarı kopyalayın; OpenAI uyumlu xAI uç noktasının kimliğini doğrular."
4145
+ },
4140
4146
  "openrouter": {
4141
4147
  "label": "OpenRouter",
4142
4148
  "step1": "openrouter.ai → Keys adresini açın ve bir API anahtarı oluşturun.",
@@ -3861,7 +3861,8 @@
3861
3861
  "qwen": "Qwen (Alibaba)",
3862
3862
  "kimi": "Kimi (Moonshot)",
3863
3863
  "deepseek": "DeepSeek",
3864
- "glm": "GLM (Z.ai)"
3864
+ "glm": "GLM (Z.ai)",
3865
+ "grok": "Grok (xAI)"
3865
3866
  },
3866
3867
  "regions": {
3867
3868
  "usa": "Сполучені Штати",
@@ -4137,6 +4138,11 @@
4137
4138
  "step1": "Відкрийте platform.moonshot.ai → API Keys і створіть ключ.",
4138
4139
  "step2": "Скопіюйте ключ; він автентифікує сумісний з OpenAI endpoint Moonshot."
4139
4140
  },
4141
+ "xai": {
4142
+ "label": "xAI (API Grok)",
4143
+ "step1": "Відкрийте console.x.ai → API Keys і створіть ключ.",
4144
+ "step2": "Скопіюйте ключ; він автентифікує сумісний з OpenAI endpoint xAI."
4145
+ },
4140
4146
  "openrouter": {
4141
4147
  "label": "OpenRouter",
4142
4148
  "step1": "Відкрийте openrouter.ai → Keys і створіть ключ API.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.273.0",
3
+ "version": "0.273.2",
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.41",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.312.0"
43
+ "@cat-factory/contracts": "0.313.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",