@cat-factory/app 0.48.1 → 0.48.3

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.
@@ -19,10 +19,6 @@ const connection = computed(() =>
19
19
  source.value ? documents.connectionFor(source.value) : undefined,
20
20
  )
21
21
  const connected = computed(() => connection.value !== undefined)
22
- // A `credentialScope: 'user'` source (e.g. Claude Design) stores a PERSONAL credential
23
- // keyed to the signed-in user and never shared with the workspace — surface that so a
24
- // member understands they're connecting their own account, not the team's.
25
- const isPersonal = computed(() => descriptor.value?.credentialScope === 'user')
26
22
 
27
23
  const open = computed({
28
24
  get: () => ui.documentConnect !== null,
@@ -99,17 +95,6 @@ async function disconnect() {
99
95
  {{ t('documents.connect.intro', { source: descriptor.label }) }}
100
96
  </p>
101
97
 
102
- <p
103
- v-if="isPersonal"
104
- class="flex items-start gap-1.5 text-xs text-amber-400/90"
105
- data-testid="document-source-personal-note"
106
- >
107
- <UIcon name="i-lucide-user" class="mt-0.5 size-3.5 shrink-0" />
108
- <span>
109
- {{ t('documents.connect.personalNote') }}
110
- </span>
111
- </p>
112
-
113
98
  <div class="space-y-3">
114
99
  <UFormField
115
100
  v-for="field in descriptor.credentialFields"
@@ -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
+ }
@@ -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)",
@@ -2080,7 +2100,6 @@
2080
2100
  "title": "Connect source",
2081
2101
  "sourceFallback": "Source",
2082
2102
  "intro": "Connect {source} to import requirements, RFCs and PRDs, then spawn board structure or attach them to tasks as agent context.",
2083
- "personalNote": "Personal connection — this credential authenticates as you and is stored only for your account, never shared with the rest of the workspace.",
2084
2103
  "disconnect": "Disconnect",
2085
2104
  "connect": "Connect",
2086
2105
  "update": "Update connection",
@@ -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": {
@@ -2026,7 +2046,6 @@
2026
2046
  "title": "Conectar fuente",
2027
2047
  "sourceFallback": "Fuente",
2028
2048
  "intro": "Conecta {source} para importar requisitos, RFC y PRD, y luego generar la estructura del tablero o adjuntarlos a las tareas como contexto para los agentes.",
2029
- "personalNote": "Conexión personal: esta credencial te autentica a ti y se almacena solo para tu cuenta, nunca se comparte con el resto del espacio de trabajo.",
2030
2049
  "disconnect": "Desconectar",
2031
2050
  "connect": "Conectar",
2032
2051
  "update": "Actualizar conexión",
@@ -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": {
@@ -2026,7 +2046,6 @@
2026
2046
  "title": "Connecter une source",
2027
2047
  "sourceFallback": "Source",
2028
2048
  "intro": "Connectez {source} pour importer des exigences, des RFC et des PRD, puis générer la structure du tableau ou les joindre aux tâches comme contexte pour les agents.",
2029
- "personalNote": "Connexion personnelle : cet identifiant vous authentifie et n'est stocké que pour votre compte, jamais partagé avec le reste de l'espace de travail.",
2030
2049
  "disconnect": "Déconnecter",
2031
2050
  "connect": "Connecter",
2032
2051
  "update": "Mettre à jour la connexion",
@@ -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": {
@@ -2026,7 +2046,6 @@
2026
2046
  "title": "Połącz źródło",
2027
2047
  "sourceFallback": "Źródło",
2028
2048
  "intro": "Połącz {source}, aby importować wymagania, dokumenty RFC i PRD, a następnie utworzyć strukturę tablicy lub dołączyć je do zadań jako kontekst dla agentów.",
2029
- "personalNote": "Połączenie osobiste: te poświadczenia uwierzytelniają Ciebie i są przechowywane tylko dla Twojego konta, nigdy nie są udostępniane reszcie obszaru roboczego.",
2030
2049
  "disconnect": "Rozłącz",
2031
2050
  "connect": "Połącz",
2032
2051
  "update": "Zaktualizuj połączenie",
@@ -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": {
@@ -2026,7 +2046,6 @@
2026
2046
  "title": "Підключити джерело",
2027
2047
  "sourceFallback": "Джерело",
2028
2048
  "intro": "Підключіть {source}, щоб імпортувати вимоги, RFC та PRD, а потім створити структуру дошки або долучити їх до завдань як контекст для агентів.",
2029
- "personalNote": "Особисте підключення: ці облікові дані автентифікують саме вас і зберігаються лише для вашого облікового запису, ніколи не передаються решті робочого простору.",
2030
2049
  "disconnect": "Відключити",
2031
2050
  "connect": "Підключити",
2032
2051
  "update": "Оновити підключення",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.48.1",
3
+ "version": "0.48.3",
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.47.0"
37
+ "@cat-factory/contracts": "0.49.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",