@cat-factory/app 0.48.0 → 0.48.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.
@@ -0,0 +1,205 @@
1
+ <script setup lang="ts">
2
+ // The Kubernetes "agent runner backend" connect form — one option of the runner-pool tab's
3
+ // backend-type selector (the other being the manifest pool). It builds the discriminated
4
+ // `{ kind: 'kubernetes', kubernetes }` config + the `apiToken` secret bundle and emits
5
+ // test/save to the parent tab (which calls the shared provider-connections store).
6
+ import { computed, reactive, ref, watch } from 'vue'
7
+ import { KUBERNETES_RUNNER_TOKEN_SECRET_KEY } from '@cat-factory/contracts'
8
+ import type { ProviderConnection } from '~/types/providerConnections'
9
+
10
+ const props = defineProps<{
11
+ connection: ProviderConnection | null
12
+ supportsTest: boolean
13
+ testing: boolean
14
+ busy: boolean
15
+ testResult: { ok: boolean; message?: string } | null
16
+ }>()
17
+
18
+ const emit = defineEmits<{
19
+ test: [payload: { config: Record<string, unknown>; secrets: Record<string, string> }]
20
+ save: [payload: { config: Record<string, unknown>; secrets: Record<string, string> }]
21
+ }>()
22
+
23
+ const { t } = useI18n()
24
+
25
+ const form = reactive({
26
+ label: '',
27
+ apiServerUrl: '',
28
+ namespace: '',
29
+ image: '',
30
+ imageUi: '',
31
+ caCertPem: '',
32
+ harnessPort: '',
33
+ })
34
+ const apiToken = ref('')
35
+
36
+ // A registered k8s connection exposes its non-secret config, so prefill every non-secret
37
+ // field from it (never the token — secrets are write-only and re-entered on update). This
38
+ // lets an edit change one field without re-typing the whole form.
39
+ watch(
40
+ () => props.connection,
41
+ (c) => {
42
+ if (c?.kind !== 'kubernetes') return
43
+ form.label = c.label
44
+ form.apiServerUrl = c.baseUrl
45
+ const k =
46
+ c.config && (c.config as { kind?: string }).kind === 'kubernetes'
47
+ ? (c.config as { kubernetes: Record<string, unknown> }).kubernetes
48
+ : undefined
49
+ if (k) {
50
+ form.namespace = typeof k.namespace === 'string' ? k.namespace : ''
51
+ form.image = typeof k.image === 'string' ? k.image : ''
52
+ form.imageUi = typeof k.imageUi === 'string' ? k.imageUi : ''
53
+ form.caCertPem = typeof k.caCertPem === 'string' ? k.caCertPem : ''
54
+ form.harnessPort = typeof k.harnessPort === 'number' ? String(k.harnessPort) : ''
55
+ }
56
+ },
57
+ { immediate: true },
58
+ )
59
+
60
+ const canSave = computed(
61
+ () =>
62
+ !!form.label.trim() &&
63
+ !!form.apiServerUrl.trim() &&
64
+ !!form.namespace.trim() &&
65
+ !!form.image.trim() &&
66
+ !!apiToken.value.trim(),
67
+ )
68
+
69
+ function buildPayload(): { config: Record<string, unknown>; secrets: Record<string, string> } {
70
+ const kubernetes: Record<string, unknown> = {
71
+ label: form.label.trim(),
72
+ apiServerUrl: form.apiServerUrl.trim(),
73
+ namespace: form.namespace.trim(),
74
+ image: form.image.trim(),
75
+ }
76
+ if (form.imageUi.trim()) kubernetes.imageUi = form.imageUi.trim()
77
+ if (form.caCertPem.trim()) kubernetes.caCertPem = form.caCertPem.trim()
78
+ const port = Number(form.harnessPort)
79
+ if (form.harnessPort.trim() && Number.isFinite(port)) kubernetes.harnessPort = port
80
+ return {
81
+ config: { kind: 'kubernetes', kubernetes },
82
+ secrets: { [KUBERNETES_RUNNER_TOKEN_SECRET_KEY]: apiToken.value.trim() },
83
+ }
84
+ }
85
+ </script>
86
+
87
+ <template>
88
+ <div class="rounded-lg border border-dashed border-slate-700 p-3 space-y-3">
89
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
90
+ {{
91
+ connection?.kind === 'kubernetes'
92
+ ? t('settings.providerConnection.form.updateConfiguration')
93
+ : t('settings.providerConnection.form.connect')
94
+ }}
95
+ </p>
96
+
97
+ <UFormField :label="t('settings.providerConnection.kubernetes.label')">
98
+ <UInput
99
+ v-model="form.label"
100
+ :placeholder="t('settings.providerConnection.kubernetes.labelPlaceholder')"
101
+ />
102
+ </UFormField>
103
+
104
+ <UFormField
105
+ :label="t('settings.providerConnection.kubernetes.apiServerUrl')"
106
+ :help="t('settings.providerConnection.kubernetes.apiServerUrlHelp')"
107
+ >
108
+ <UInput v-model="form.apiServerUrl" class="font-mono" placeholder="https://10.0.0.1:6443" />
109
+ </UFormField>
110
+
111
+ <UFormField :label="t('settings.providerConnection.kubernetes.namespace')">
112
+ <UInput v-model="form.namespace" class="font-mono" placeholder="cat-factory" />
113
+ </UFormField>
114
+
115
+ <UFormField
116
+ :label="t('settings.providerConnection.kubernetes.image')"
117
+ :help="t('settings.providerConnection.kubernetes.imageHelp')"
118
+ >
119
+ <UInput
120
+ v-model="form.image"
121
+ class="font-mono"
122
+ placeholder="ghcr.io/acme/cat-factory-executor:latest"
123
+ />
124
+ </UFormField>
125
+
126
+ <UFormField
127
+ :label="
128
+ t('settings.providerConnection.form.optionalLabel', {
129
+ label: t('settings.providerConnection.kubernetes.imageUi'),
130
+ })
131
+ "
132
+ >
133
+ <UInput v-model="form.imageUi" class="font-mono" />
134
+ </UFormField>
135
+
136
+ <UFormField
137
+ :label="t('settings.providerConnection.kubernetes.apiToken')"
138
+ :help="t('settings.providerConnection.kubernetes.apiTokenHelp')"
139
+ >
140
+ <UInput v-model="apiToken" type="password" class="font-mono" />
141
+ </UFormField>
142
+
143
+ <UFormField
144
+ :label="
145
+ t('settings.providerConnection.form.optionalLabel', {
146
+ label: t('settings.providerConnection.kubernetes.caCertPem'),
147
+ })
148
+ "
149
+ :help="t('settings.providerConnection.kubernetes.caCertPemHelp')"
150
+ >
151
+ <UTextarea
152
+ v-model="form.caCertPem"
153
+ :rows="3"
154
+ class="font-mono"
155
+ placeholder="-----BEGIN CERTIFICATE-----"
156
+ />
157
+ </UFormField>
158
+
159
+ <UFormField
160
+ :label="
161
+ t('settings.providerConnection.form.optionalLabel', {
162
+ label: t('settings.providerConnection.kubernetes.harnessPort'),
163
+ })
164
+ "
165
+ >
166
+ <UInput v-model="form.harnessPort" type="number" class="font-mono" placeholder="8080" />
167
+ </UFormField>
168
+
169
+ <div v-if="supportsTest" class="flex items-center gap-2">
170
+ <UButton
171
+ color="neutral"
172
+ variant="soft"
173
+ size="sm"
174
+ icon="i-lucide-plug-zap"
175
+ :loading="testing"
176
+ :disabled="!canSave"
177
+ @click="emit('test', buildPayload())"
178
+ >
179
+ {{ t('settings.providerConnection.test.button') }}
180
+ </UButton>
181
+ <span v-if="testResult && testResult.ok" class="text-xs text-emerald-400">
182
+ {{ testResult.message ?? t('settings.providerConnection.test.ok') }}
183
+ </span>
184
+ <span v-else-if="testResult" class="text-xs text-rose-400">
185
+ {{ testResult.message ?? t('settings.providerConnection.test.failed') }}
186
+ </span>
187
+ </div>
188
+
189
+ <div class="flex justify-end">
190
+ <UButton
191
+ color="primary"
192
+ size="sm"
193
+ :loading="busy"
194
+ :disabled="!canSave"
195
+ @click="emit('save', buildPayload())"
196
+ >
197
+ {{
198
+ connection?.kind === 'kubernetes'
199
+ ? t('common.save')
200
+ : t('settings.providerConnection.form.connect')
201
+ }}
202
+ </UButton>
203
+ </div>
204
+ </div>
205
+ </template>
@@ -10,6 +10,7 @@ import { computed, ref, toRaw, watch } from 'vue'
10
10
  import type { ProviderConnectionKind } from '~/types/providerConnections'
11
11
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
12
12
  import ProviderManifestEditor from '~/components/settings/ProviderManifestEditor.vue'
13
+ import KubernetesRunnerForm from '~/components/settings/KubernetesRunnerForm.vue'
13
14
 
14
15
  const props = defineProps<{ kind: ProviderConnectionKind }>()
15
16
 
@@ -184,6 +185,57 @@ async function saveManifest(payload: {
184
185
  }
185
186
  }
186
187
 
188
+ // --- Runner-backend selector (runner-pool only) -------------------------------------
189
+ // The runner-pool tab can configure either the manifest pool OR a native Kubernetes
190
+ // cluster; environments are manifest-only. Defaults to the saved connection's kind.
191
+ const RUNNER_BACKEND_KINDS = ['manifest', 'kubernetes'] as const
192
+ type RunnerBackendKind = (typeof RUNNER_BACKEND_KINDS)[number]
193
+ const backendKind = ref<RunnerBackendKind>('manifest')
194
+ const showBackendSelector = computed(() => props.kind === 'runner-pool')
195
+ const backendKindItems = computed(() =>
196
+ RUNNER_BACKEND_KINDS.map((k) => ({
197
+ label: t(`settings.providerConnection.backend.${k}`),
198
+ value: k,
199
+ })),
200
+ )
201
+ watch(
202
+ () => connection.value,
203
+ (c) => {
204
+ if (c?.kind === 'kubernetes' || c?.kind === 'manifest') backendKind.value = c.kind
205
+ },
206
+ { immediate: true },
207
+ )
208
+
209
+ async function testConfig(payload: {
210
+ config: Record<string, unknown>
211
+ secrets: Record<string, string>
212
+ }) {
213
+ testing.value = true
214
+ testResult.value = null
215
+ try {
216
+ testResult.value = await store.test(props.kind, payload)
217
+ } catch (e) {
218
+ testResult.value = { ok: false, message: e instanceof Error ? e.message : String(e) }
219
+ } finally {
220
+ testing.value = false
221
+ }
222
+ }
223
+
224
+ async function saveConfig(payload: {
225
+ config: Record<string, unknown>
226
+ secrets: Record<string, string>
227
+ }) {
228
+ busy.value = true
229
+ try {
230
+ await store.register(props.kind, payload)
231
+ toastSaved()
232
+ } catch (e) {
233
+ notifyError(t('settings.providerConnection.toast.saveFailed'), e)
234
+ } finally {
235
+ busy.value = false
236
+ }
237
+ }
238
+
187
239
  async function remove() {
188
240
  busy.value = true
189
241
  try {
@@ -265,8 +317,31 @@ function fieldHelp(key: string): string | undefined {
265
317
  }}
266
318
  </div>
267
319
 
320
+ <!-- Runner-backend selector: the manifest pool or a native Kubernetes cluster. -->
321
+ <UFormField
322
+ v-if="showBackendSelector"
323
+ :label="t('settings.providerConnection.backend.selectorLabel')"
324
+ >
325
+ <USelect v-model="backendKind" :items="backendKindItems" />
326
+ </UFormField>
327
+
328
+ <!-- Native Kubernetes runner backend. -->
329
+ <KubernetesRunnerForm
330
+ v-if="showBackendSelector && backendKind === 'kubernetes'"
331
+ :connection="connection"
332
+ :supports-test="descriptor.supportsTest"
333
+ :testing="testing"
334
+ :busy="busy"
335
+ :test-result="testResult"
336
+ @test="testConfig"
337
+ @save="saveConfig"
338
+ />
339
+
268
340
  <!-- NATIVE provider: the friendly, descriptor-driven flat field form. -->
269
- <div v-if="isNative" class="rounded-lg border border-dashed border-slate-700 p-3 space-y-3">
341
+ <div
342
+ v-else-if="isNative"
343
+ class="rounded-lg border border-dashed border-slate-700 p-3 space-y-3"
344
+ >
270
345
  <p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
271
346
  {{
272
347
  connection
@@ -58,9 +58,10 @@ export function providerConnectionsApi({ send, ws }: ApiContext) {
58
58
  send(CONTRACTS[kind].get, { pathPrefix: ws(workspaceId) }),
59
59
 
60
60
  // The connect form builds the manifest dynamically from a server-provided scaffold, so
61
- // the FE input keeps `manifest` opaque (`Record<string, unknown>`); narrow on the kind
62
- // and cast to the matching per-kind contract input at this single boundary (the backend
63
- // re-validates the manifest against the precise contract on receipt).
61
+ // the FE input keeps `manifest`/`config` opaque (`Record<string, unknown>`); narrow on
62
+ // the kind and cast to the matching per-kind contract input at this single boundary (the
63
+ // backend re-validates against the precise contract on receipt). The runner-pool provider
64
+ // takes a discriminated `config`; a bare `manifest` is wrapped into the manifest backend.
64
65
  registerProviderConnection: (
65
66
  workspaceId: string,
66
67
  kind: ProviderConnectionKind,
@@ -73,7 +74,10 @@ export function providerConnectionsApi({ send, ws }: ApiContext) {
73
74
  })
74
75
  : send(CONTRACTS['runner-pool'].register, {
75
76
  pathPrefix: ws(workspaceId),
76
- body: body as RegisterRunnerPoolInput,
77
+ body: {
78
+ config: runnerBackendConfig(body),
79
+ secrets: body.secrets,
80
+ } as RegisterRunnerPoolInput,
77
81
  }),
78
82
 
79
83
  updateProviderSecrets: (
@@ -94,10 +98,25 @@ export function providerConnectionsApi({ send, ws }: ApiContext) {
94
98
  })
95
99
  : send(CONTRACTS['runner-pool'].test, {
96
100
  pathPrefix: ws(workspaceId),
97
- body: body as TestRunnerPoolConnectionInput,
101
+ body: {
102
+ ...(body.manifest || body.config ? { config: runnerBackendConfig(body) } : {}),
103
+ ...(body.secrets ? { secrets: body.secrets } : {}),
104
+ } as TestRunnerPoolConnectionInput,
98
105
  }),
99
106
 
100
107
  deleteProviderConnection: (workspaceId: string, kind: ProviderConnectionKind) =>
101
108
  send(CONTRACTS[kind].unregister, { pathPrefix: ws(workspaceId) }),
102
109
  }
103
110
  }
111
+
112
+ /**
113
+ * Resolve the discriminated runner-backend config from a connect-form payload: an
114
+ * explicit `config` (the Kubernetes form) wins; otherwise a bare `manifest` (the manifest
115
+ * editor) is wrapped into the manifest backend kind.
116
+ */
117
+ function runnerBackendConfig(
118
+ body: RegisterProviderInput | TestProviderInput,
119
+ ): Record<string, unknown> {
120
+ if (body.config) return body.config
121
+ return { kind: 'manifest', manifest: body.manifest ?? {} }
122
+ }
@@ -64,6 +64,11 @@ export function useWorkspaceStream() {
64
64
  // or a failed badge) without a full refresh.
65
65
  agentRuns.upsertBootstrap(event.job)
66
66
  if (event.block) board.upsert(event.block)
67
+ } else if (event.type === 'env-config-repair') {
68
+ // A provider config-repair run advanced — patch its live status/subtasks/outcome so
69
+ // the infrastructure-providers window's "repairing…" indicator updates in place
70
+ // (then flips to ok / residual issues / a failure) without a refetch. No board block.
71
+ agentRuns.upsertEnvConfigRepair(event.job)
67
72
  } else if (event.type === 'notification') {
68
73
  // A PR needs a merge decision, a pipeline finished, or CI gave up — patch the
69
74
  // inbox + per-block badge in place (resolved ones drop out of the inbox).
@@ -1,6 +1,12 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { computed, ref } from 'vue'
3
- import type { AgentFailure, AgentRunKind, BootstrapJob, StepSubtasks } from '~/types/domain'
3
+ import type {
4
+ AgentFailure,
5
+ AgentRunKind,
6
+ BootstrapJob,
7
+ EnvConfigRepairJob,
8
+ StepSubtasks,
9
+ } from '~/types/domain'
4
10
  import { useWorkspaceStore } from '~/stores/workspace'
5
11
  import { useExecutionStore } from '~/stores/execution'
6
12
 
@@ -45,11 +51,40 @@ export const useAgentRunsStore = defineStore('agentRuns', () => {
45
51
  /** Bootstrap runs for this workspace, newest-first. */
46
52
  const bootstrapJobs = ref<BootstrapJob[]>([])
47
53
 
54
+ /**
55
+ * Env-config-repair runs for this workspace, newest-first. These have NO board block —
56
+ * they're surfaced only on the infrastructure-providers window (looked up by the
57
+ * `repairJobId` the `bootstrapRepo` response returned), so they're held separately and
58
+ * NOT merged into {@link byBlock}.
59
+ */
60
+ const envConfigRepairJobs = ref<EnvConfigRepairJob[]>([])
61
+
48
62
  /** Replace the cached bootstrap runs with a server snapshot. */
49
63
  function hydrate(jobs: BootstrapJob[]) {
50
64
  bootstrapJobs.value = [...jobs].sort((a, b) => b.createdAt - a.createdAt)
51
65
  }
52
66
 
67
+ /** Replace the cached env-config-repair runs with a server snapshot. */
68
+ function hydrateEnvConfigRepair(jobs: EnvConfigRepairJob[]) {
69
+ envConfigRepairJobs.value = [...jobs].sort((a, b) => b.createdAt - a.createdAt)
70
+ }
71
+
72
+ /**
73
+ * Patch an env-config-repair run from a real-time `env-config-repair` event (or after
74
+ * launching one): replace it in place by id, else prepend it. Keeps the infra window's
75
+ * "repairing…" indicator reactive to live progress / outcome without a refetch.
76
+ */
77
+ function upsertEnvConfigRepair(job: EnvConfigRepairJob) {
78
+ const i = envConfigRepairJobs.value.findIndex((j) => j.id === job.id)
79
+ if (i >= 0) envConfigRepairJobs.value[i] = job
80
+ else envConfigRepairJobs.value.unshift(job)
81
+ }
82
+
83
+ /** Look up a single env-config-repair run by id (the infra window tracks one by `repairJobId`). */
84
+ function envConfigRepairById(id: string): EnvConfigRepairJob | undefined {
85
+ return envConfigRepairJobs.value.find((j) => j.id === id)
86
+ }
87
+
53
88
  /**
54
89
  * Patch a bootstrap run from a real-time `bootstrap` event (or after launching
55
90
  * one): replace it in place by id, else prepend it. Keeps the service card
@@ -126,5 +161,16 @@ export const useAgentRunsStore = defineStore('agentRuns', () => {
126
161
  return kind
127
162
  }
128
163
 
129
- return { bootstrapJobs, hydrate, upsertBootstrap, byBlock, retry, stop }
164
+ return {
165
+ bootstrapJobs,
166
+ hydrate,
167
+ upsertBootstrap,
168
+ envConfigRepairJobs,
169
+ hydrateEnvConfigRepair,
170
+ upsertEnvConfigRepair,
171
+ envConfigRepairById,
172
+ byBlock,
173
+ retry,
174
+ stop,
175
+ }
130
176
  })
@@ -85,6 +85,7 @@ export const useWorkspaceStore = defineStore(
85
85
  usePipelinesStore().hydrate(snapshot.pipelines, snapshot.pipelineCatalogVersions)
86
86
  useExecutionStore().hydrate(snapshot.executions)
87
87
  useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [])
88
+ useAgentRunsStore().hydrateEnvConfigRepair(snapshot.envConfigRepairJobs ?? [])
88
89
  useNotificationsStore().hydrate(snapshot.notifications ?? [])
89
90
  useMergePresetsStore().hydrate(snapshot.mergePresets ?? [])
90
91
  useWorkspaceSettingsStore().hydrate(snapshot.settings)
@@ -105,6 +105,7 @@ export type * from './fragments'
105
105
  export type * from './documents'
106
106
  export type * from './tasks'
107
107
  export type * from './bootstrap'
108
+ export type * from './envConfigRepair'
108
109
  export type * from './github'
109
110
  export type * from './accounts'
110
111
  export type * from './notifications'
@@ -0,0 +1,15 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Environment-provider config-repair domain types. Mirrors the
3
+ // `@cat-factory/contracts` env-config-repair schemas so backend payloads drop
4
+ // straight into the Pinia store.
5
+ //
6
+ // A config-repair run is the durable agent fallback dispatched when mechanical
7
+ // provider-config bootstrap can't produce a valid config: a coding agent fixes the
8
+ // provider's config file in an existing repo and pushes the fix back, then the
9
+ // backend re-validates. It has no board block — it's surfaced only on the
10
+ // infrastructure-providers window that triggered it.
11
+ // ---------------------------------------------------------------------------
12
+ //
13
+ // All wire shapes are sourced from @cat-factory/contracts (single source of truth).
14
+
15
+ export type { EnvConfigRepairStatus, EnvConfigRepairJob } from '@cat-factory/contracts'
@@ -19,12 +19,20 @@ export type ProviderConnectionKind = 'environment' | 'runner-pool'
19
19
 
20
20
  /** A workspace's provider binding, as exposed to clients (never secret values). */
21
21
  export interface ProviderConnection {
22
+ /** The runner-backend kind for a runner-pool connection (`manifest` | `kubernetes`). */
23
+ kind?: string
22
24
  providerId: string
23
25
  label: string
24
26
  baseUrl: string
25
27
  connectedAt: number
26
28
  /** Which secret/config keys are stored (names only), so the UI shows completeness. */
27
29
  secretKeys: string[]
30
+ /**
31
+ * The stored discriminated runner-backend config, sans secrets, so the connect form
32
+ * can prefill its non-secret fields on edit. Shape mirrors the backend
33
+ * `RunnerBackendConfig` ({ kind: 'manifest' | 'kubernetes', … }); kept opaque here.
34
+ */
35
+ config?: Record<string, unknown>
28
36
  }
29
37
 
30
38
  // The connect form builds the manifest dynamically from a server-provided scaffold
@@ -33,15 +41,23 @@ export interface ProviderConnection {
33
41
  // per-provider manifest contract on receipt; the composable casts to the contract input
34
42
  // type at the single `send` boundary.
35
43
 
36
- /** The assembled register payload (a full manifest + the write-only secret bundle). */
44
+ /**
45
+ * The assembled register payload. The environment provider sends a full `manifest`.
46
+ * The runner-pool ("agent runner backend") provider sends a discriminated `config`
47
+ * ({ kind: 'manifest' | 'kubernetes', … }); for back-compat of the manifest editor it
48
+ * may instead send a bare `manifest`, which the composable wraps into the manifest
49
+ * backend config. The write-only secret bundle rides alongside.
50
+ */
37
51
  export interface RegisterProviderInput {
38
- manifest: Record<string, unknown>
52
+ manifest?: Record<string, unknown>
53
+ /** The discriminated runner-backend config (manifest pool or kubernetes). */
54
+ config?: Record<string, unknown>
39
55
  secrets: Record<string, string>
40
56
  }
41
57
 
42
- /** The test/probe payload (manifest-driven or native), shared by both providers. */
58
+ /** The test/probe payload (manifest-driven, native, or a discriminated runner config). */
43
59
  export interface TestProviderInput {
44
60
  manifest?: Record<string, unknown>
45
- config?: Record<string, string>
61
+ config?: Record<string, unknown>
46
62
  secrets?: Record<string, string>
47
63
  }
@@ -1272,10 +1272,30 @@
1272
1272
  "blurb": "Where the Tester agent runs against a live preview environment. Configure the per-workspace settings and credentials your provider needs."
1273
1273
  },
1274
1274
  "runner-pool": {
1275
- "title": "Self-hosted runner pool",
1276
- "blurb": "Where the coding agents run when not using Cloudflare Containers. Configure the pool scheduler endpoint and credentials."
1275
+ "title": "Agent runner backend",
1276
+ "blurb": "Where the coding agents run when not using Cloudflare Containers. Choose a self-hosted runner pool (your own scheduler) or a Kubernetes cluster, then configure its endpoint and credentials."
1277
1277
  }
1278
1278
  },
1279
+ "backend": {
1280
+ "selectorLabel": "Runner backend",
1281
+ "manifest": "Self-hosted pool (manifest)",
1282
+ "kubernetes": "Kubernetes"
1283
+ },
1284
+ "kubernetes": {
1285
+ "label": "Name",
1286
+ "labelPlaceholder": "Production cluster",
1287
+ "apiServerUrl": "API server URL",
1288
+ "apiServerUrlHelp": "The kube-apiserver root, e.g. https://10.0.0.1:6443. The orchestrator reaches each run's pod through the apiserver, so only this endpoint must be reachable.",
1289
+ "namespace": "Namespace",
1290
+ "image": "Executor image",
1291
+ "imageHelp": "The executor-harness image each per-run pod runs.",
1292
+ "imageUi": "UI-tester image",
1293
+ "apiToken": "ServiceAccount token",
1294
+ "apiTokenHelp": "A bearer token with RBAC to create, get and delete pods and pods/proxy in the namespace. Stored encrypted; never shown again.",
1295
+ "caCertPem": "Cluster CA certificate (PEM)",
1296
+ "caCertPemHelp": "Paste the cluster CA bundle so the apiserver's TLS certificate verifies. Omit only for a publicly-trusted CA.",
1297
+ "harnessPort": "Harness port"
1298
+ },
1279
1299
  "manifestEditor": {
1280
1300
  "title": "Provider manifest",
1281
1301
  "jsonLabel": "Manifest (JSON)",
@@ -1283,6 +1283,26 @@
1283
1283
  "saveFailed": "No se pudo guardar la conexión",
1284
1284
  "removed": "Conexión eliminada",
1285
1285
  "removeFailed": "No se pudo eliminar la conexión"
1286
+ },
1287
+ "backend": {
1288
+ "selectorLabel": "Backend de ejecución",
1289
+ "manifest": "Pool autohospedado (manifiesto)",
1290
+ "kubernetes": "Kubernetes"
1291
+ },
1292
+ "kubernetes": {
1293
+ "label": "Nombre",
1294
+ "labelPlaceholder": "Clúster de producción",
1295
+ "apiServerUrl": "URL del API server",
1296
+ "apiServerUrlHelp": "La raíz del kube-apiserver, p. ej. https://10.0.0.1:6443. El orquestador llega al pod de cada ejecución a través del apiserver, así que solo este endpoint debe ser accesible.",
1297
+ "namespace": "Namespace",
1298
+ "image": "Imagen del ejecutor",
1299
+ "imageHelp": "La imagen de executor-harness que ejecuta cada pod por ejecución.",
1300
+ "imageUi": "Imagen de tester de UI",
1301
+ "apiToken": "Token de ServiceAccount",
1302
+ "apiTokenHelp": "Un token bearer con permisos RBAC para crear, obtener y eliminar pods y pods/proxy en el namespace. Se almacena cifrado; no se vuelve a mostrar.",
1303
+ "caCertPem": "Certificado CA del clúster (PEM)",
1304
+ "caCertPemHelp": "Pega el bundle CA del clúster para que el certificado TLS del apiserver se verifique. Omítelo solo para una CA de confianza pública.",
1305
+ "harnessPort": "Puerto del harness"
1286
1306
  }
1287
1307
  },
1288
1308
  "serviceFragmentDefaults": {
@@ -1283,6 +1283,26 @@
1283
1283
  "saveFailed": "Impossible d'enregistrer la connexion",
1284
1284
  "removed": "Connexion supprimée",
1285
1285
  "removeFailed": "Impossible de supprimer la connexion"
1286
+ },
1287
+ "backend": {
1288
+ "selectorLabel": "Backend d'exécution",
1289
+ "manifest": "Pool auto-hébergé (manifeste)",
1290
+ "kubernetes": "Kubernetes"
1291
+ },
1292
+ "kubernetes": {
1293
+ "label": "Nom",
1294
+ "labelPlaceholder": "Cluster de production",
1295
+ "apiServerUrl": "URL du serveur d'API",
1296
+ "apiServerUrlHelp": "La racine du kube-apiserver, p. ex. https://10.0.0.1:6443. L'orchestrateur atteint le pod de chaque exécution via l'apiserver, donc seul ce point d'accès doit être accessible.",
1297
+ "namespace": "Namespace",
1298
+ "image": "Image de l'exécuteur",
1299
+ "imageHelp": "L'image executor-harness exécutée par chaque pod d'exécution.",
1300
+ "imageUi": "Image testeur d'UI",
1301
+ "apiToken": "Jeton de ServiceAccount",
1302
+ "apiTokenHelp": "Un jeton bearer avec les droits RBAC pour créer, lire et supprimer les pods et pods/proxy dans le namespace. Stocké chiffré ; jamais réaffiché.",
1303
+ "caCertPem": "Certificat CA du cluster (PEM)",
1304
+ "caCertPemHelp": "Collez le bundle CA du cluster pour que le certificat TLS de l'apiserver soit vérifié. À omettre uniquement pour une CA publiquement approuvée.",
1305
+ "harnessPort": "Port du harness"
1286
1306
  }
1287
1307
  },
1288
1308
  "serviceFragmentDefaults": {
@@ -1283,6 +1283,26 @@
1283
1283
  "saveFailed": "Nie udało się zapisać połączenia",
1284
1284
  "removed": "Połączenie usunięte",
1285
1285
  "removeFailed": "Nie udało się usunąć połączenia"
1286
+ },
1287
+ "backend": {
1288
+ "selectorLabel": "Backend wykonawczy",
1289
+ "manifest": "Własny pool (manifest)",
1290
+ "kubernetes": "Kubernetes"
1291
+ },
1292
+ "kubernetes": {
1293
+ "label": "Nazwa",
1294
+ "labelPlaceholder": "Klaster produkcyjny",
1295
+ "apiServerUrl": "URL serwera API",
1296
+ "apiServerUrlHelp": "Główny adres kube-apiserver, np. https://10.0.0.1:6443. Orkiestrator łączy się z podem każdego uruchomienia przez apiserver, więc tylko ten endpoint musi być osiągalny.",
1297
+ "namespace": "Namespace",
1298
+ "image": "Obraz wykonawcy",
1299
+ "imageHelp": "Obraz executor-harness uruchamiany przez każdy pod uruchomienia.",
1300
+ "imageUi": "Obraz testera UI",
1301
+ "apiToken": "Token ServiceAccount",
1302
+ "apiTokenHelp": "Token bearer z uprawnieniami RBAC do tworzenia, odczytu i usuwania podów oraz pods/proxy w namespace. Przechowywany w postaci zaszyfrowanej; nie jest ponownie pokazywany.",
1303
+ "caCertPem": "Certyfikat CA klastra (PEM)",
1304
+ "caCertPemHelp": "Wklej pakiet CA klastra, aby certyfikat TLS apiservera został zweryfikowany. Pomiń tylko dla publicznie zaufanego CA.",
1305
+ "harnessPort": "Port harnessa"
1286
1306
  }
1287
1307
  },
1288
1308
  "serviceFragmentDefaults": {
@@ -1283,6 +1283,26 @@
1283
1283
  "saveFailed": "Не вдалося зберегти підключення",
1284
1284
  "removed": "Підключення видалено",
1285
1285
  "removeFailed": "Не вдалося видалити підключення"
1286
+ },
1287
+ "backend": {
1288
+ "selectorLabel": "Бекенд виконавця",
1289
+ "manifest": "Власний пул (маніфест)",
1290
+ "kubernetes": "Kubernetes"
1291
+ },
1292
+ "kubernetes": {
1293
+ "label": "Назва",
1294
+ "labelPlaceholder": "Робочий кластер",
1295
+ "apiServerUrl": "URL сервера API",
1296
+ "apiServerUrlHelp": "Кореневий адрес kube-apiserver, напр. https://10.0.0.1:6443. Оркестратор звертається до пода кожного запуску через apiserver, тож лише цей endpoint має бути доступним.",
1297
+ "namespace": "Простір імен",
1298
+ "image": "Образ виконавця",
1299
+ "imageHelp": "Образ executor-harness, який запускає кожен под запуску.",
1300
+ "imageUi": "Образ UI-тестувальника",
1301
+ "apiToken": "Токен ServiceAccount",
1302
+ "apiTokenHelp": "Bearer-токен з правами RBAC на створення, читання та видалення подів і pods/proxy у просторі імен. Зберігається зашифрованим; більше не показується.",
1303
+ "caCertPem": "Сертифікат CA кластера (PEM)",
1304
+ "caCertPemHelp": "Вставте CA-набір кластера, щоб TLS-сертифікат apiserver проходив перевірку. Пропустіть лише для публічно довіреного CA.",
1305
+ "harnessPort": "Порт harness"
1286
1306
  }
1287
1307
  },
1288
1308
  "serviceFragmentDefaults": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.48.0",
3
+ "version": "0.48.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",
@@ -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.46.0"
37
+ "@cat-factory/contracts": "0.48.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",