@cat-factory/app 0.77.0 → 0.78.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.
@@ -20,6 +20,7 @@ const documents = useDocumentsStore()
20
20
  const tasks = useTasksStore()
21
21
  const tracker = useTrackerStore()
22
22
  const releaseHealth = useReleaseHealthStore()
23
+ const packageRegistries = usePackageRegistriesStore()
23
24
  const userSecrets = useUserSecretsStore()
24
25
  const apiKeys = useApiKeysStore()
25
26
  const workspace = useWorkspaceStore()
@@ -49,6 +50,7 @@ watch(
49
50
  if (isOpen) {
50
51
  query.value = ''
51
52
  void releaseHealth.ensureLoaded().catch(() => {})
53
+ void packageRegistries.ensureLoaded().catch(() => {})
52
54
  void userSecrets.load().catch(() => {})
53
55
  // Drives the OpenRouter row's "Key connected" badge.
54
56
  if (workspace.workspaceId) void apiKeys.load(workspace.workspaceId).catch(() => {})
@@ -256,6 +258,27 @@ const groups = computed<IntegrationGroup[]>(() => {
256
258
  })
257
259
  }
258
260
 
261
+ // --- Development (private package registries) -------------------------------
262
+ // Gated like observability: hidden until a probe confirms the module is wired
263
+ // (`available === true`), so an unconfigured backend doesn't show a dead row.
264
+ if (packageRegistries.available) {
265
+ const hasEntries = packageRegistries.entries.length > 0
266
+ out.push({
267
+ title: t('layout.integrationsHub.groups.development'),
268
+ items: [
269
+ {
270
+ key: 'package-registries',
271
+ icon: 'i-lucide-package',
272
+ label: t('layout.integrationsHub.items.packageRegistries.label'),
273
+ description: t('layout.integrationsHub.items.packageRegistries.description'),
274
+ status: hasEntries ? t('layout.integrationsHub.status.connected') : undefined,
275
+ connected: hasEntries,
276
+ onClick: () => go(ui.openPackageRegistries),
277
+ },
278
+ ],
279
+ })
280
+ }
281
+
259
282
  // NOTE: Infrastructure (agent-container execution + Tester environments + the local-mode
260
283
  // warm pool/checkout) is no longer listed here — it moved to its OWN top-level navbar menu
261
284
  // (SideBar → "Infrastructure" → the tabbed Infrastructure window). See `ui.openInfrastructure`.
@@ -7,6 +7,8 @@ import StepRestartControl from '~/components/panels/StepRestartControl.vue'
7
7
  import StepMetadataCard from '~/components/panels/StepMetadataCard.vue'
8
8
  import StepTestReport from '~/components/panels/StepTestReport.vue'
9
9
  import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
10
+ import FrontendBindingsResolved from '~/components/panels/inspector/FrontendBindingsResolved.vue'
11
+ import { UI_TESTER_AGENT_KIND } from '@cat-factory/contracts'
10
12
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
11
13
  import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
12
14
  import { useStepTimer } from '~/composables/useStepTimer'
@@ -58,6 +60,24 @@ const testPhase = computed(() => step.value?.test ?? null)
58
60
  // coder consume it), so the panel shows its spinning-up/running/shutdown/errored state.
59
61
  const stepEnvironment = computed(() => step.value?.environment ?? null)
60
62
 
63
+ // For a frontend UI-test step (`tester-ui`): the enclosing `frontend` frame's backend-binding
64
+ // config, so the detail can project how each env var resolved (live URL | mocked) — rendered from
65
+ // the FROZEN bindings the engine stamped on the run (`instance.frontendBindings`), so a finished
66
+ // run shows what it actually drove against rather than re-resolving against current live state.
67
+ const frontendFrame = computed(() => (block.value ? board.serviceOf(block.value) : undefined))
68
+ const isFrontendFrame = computed(() => frontendFrame.value?.type === 'frontend')
69
+ const frontendConfig = computed(() =>
70
+ step.value?.agentKind === UI_TESTER_AGENT_KIND && isFrontendFrame.value
71
+ ? (frontendFrame.value!.frontendConfig ?? null)
72
+ : null,
73
+ )
74
+ // The frozen start-time resolution the tester ran against (absent for a non-frontend / pre-6b run).
75
+ const frontendBindings = computed(() => instance.value?.frontendBindings ?? [])
76
+ // The run-start advisories the engine stamped on the run (duplicate env vars / partially-mocked
77
+ // services) are a whole-RUN fact, so surface them on ANY step detail of a frontend-frame run, not
78
+ // only the `tester-ui` step — a duplicate-env-var note shouldn't be invisible from the coder step.
79
+ const runNotes = computed(() => (isFrontendFrame.value ? (instance.value?.notes ?? []) : []))
80
+
61
81
  // The run's infrastructure attempts (container/runner/env spin-up + tear-down), behind
62
82
  // a toggle. This is the surface that makes the per-run `container` log rows + the
63
83
  // executionId filter visible — most useful when the run failed to start a container.
@@ -345,6 +365,27 @@ async function copyOutput() {
345
365
  errored + the exact error), when this step runs against one -->
346
366
  <EnvironmentStatusPanel v-if="stepEnvironment" :environment="stepEnvironment" />
347
367
 
368
+ <!-- frontend UI-test: how the frame's backend bindings resolved (env var →
369
+ live URL | mocked) + the run-start advisories (duplicate env vars /
370
+ partially-mocked services) the engine stamped on the run. Rendered from the
371
+ FROZEN start-time bindings so a finished run shows what it actually drove
372
+ against, not a live re-resolution. -->
373
+ <FrontendBindingsResolved
374
+ v-if="frontendConfig"
375
+ :config="frontendConfig"
376
+ :resolved="frontendBindings"
377
+ />
378
+ <ul v-if="runNotes.length" class="space-y-1" data-testid="run-notes">
379
+ <li
380
+ v-for="(note, i) in runNotes"
381
+ :key="i"
382
+ class="flex items-start gap-1.5 text-[11px] leading-snug text-amber-300/80"
383
+ >
384
+ <UIcon name="i-lucide-info" class="mt-0.5 h-3.5 w-3.5 shrink-0" />
385
+ <span>{{ note }}</span>
386
+ </li>
387
+ </ul>
388
+
348
389
  <!-- this run's infrastructure attempts (container/runner/env spin-up +
349
390
  tear-down): the surface for the per-run container log rows + the exact
350
391
  provider error, behind a toggle (most useful on a failed-to-start run) -->
@@ -0,0 +1,111 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted } from 'vue'
3
+ import {
4
+ duplicateBindingEnvVars,
5
+ resolveFrontendBindings,
6
+ type FrontendBackendBinding,
7
+ type FrontendConfig,
8
+ type ResolvedFrontendBinding,
9
+ } from '@cat-factory/contracts'
10
+
11
+ // The resolution of a frontend frame's backend bindings — each env var → a bound service's live
12
+ // ephemeral URL, or WireMock. Two modes, same view:
13
+ // - **Live** (frame inspector, `resolved` omitted): resolves against the workspace's CURRENT env
14
+ // handles (fetched once via the environments store), so the operator sees how a run would
15
+ // resolve RIGHT NOW. Feeds the SAME pure helpers the backend uses so it can't drift.
16
+ // - **Projected** (`tester-ui` run/step detail, `resolved` provided): renders the FROZEN
17
+ // start-time bindings the engine stamped on the run, so a finished run shows what it ACTUALLY
18
+ // drove against — truthful even after the underlying envs are torn down (no live re-read).
19
+ // Also surfaces the duplicate-env-var misconfiguration in live mode (projected mode leaves that to
20
+ // the run-start note, which owns the frozen advisory).
21
+ const props = defineProps<{ config: FrontendConfig; resolved?: ResolvedFrontendBinding[] }>()
22
+
23
+ const environments = useEnvironmentsStore()
24
+ const board = useBoardStore()
25
+ const { t } = useI18n()
26
+
27
+ const projected = computed(() => props.resolved !== undefined)
28
+
29
+ // Live mode refreshes the env handles when this view opens so a just-provisioned service shows as
30
+ // live; a projected snapshot needs no live read.
31
+ onMounted(() => {
32
+ if (!projected.value) void environments.load()
33
+ })
34
+
35
+ // The duplicate advisory is config-derived; in projected mode the run-start note owns it (frozen
36
+ // at start), so don't re-derive it here against a possibly-since-edited config.
37
+ const duplicates = computed(() => (projected.value ? [] : duplicateBindingEnvVars(props.config)))
38
+
39
+ // Each resolved binding + the display metadata a bare {envVar, serviceUrl} can't carry: whether
40
+ // a mocked upstream was a `mock` source or a `service` with no live env, and the bound service's
41
+ // title. Joined off the LAST config binding per envVar (matching `resolveFrontendBindings`'
42
+ // last-wins dedup), so the extra labels stay in step with the canonical resolution.
43
+ const rows = computed(() => {
44
+ const resolved =
45
+ props.resolved ??
46
+ resolveFrontendBindings(props.config, environments.liveServiceEnvUrls(props.config))
47
+ const lastByEnvVar = new Map<string, FrontendBackendBinding>()
48
+ for (const b of props.config.backendBindings) {
49
+ const key = b.envVar.trim()
50
+ if (key) lastByEnvVar.set(key, b)
51
+ }
52
+ return resolved.map((r) => {
53
+ const source = lastByEnvVar.get(r.envVar)?.source
54
+ const serviceFrameId = source?.kind === 'service' ? source.serviceBlockId : undefined
55
+ return {
56
+ envVar: r.envVar,
57
+ serviceUrl: r.serviceUrl,
58
+ kind: r.serviceUrl ? 'live' : source?.kind === 'service' ? 'service-offline' : 'mock',
59
+ serviceTitle: serviceFrameId
60
+ ? (board.getBlock(serviceFrameId)?.title ?? serviceFrameId)
61
+ : undefined,
62
+ } as const
63
+ })
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <div v-if="rows.length || duplicates.length" class="space-y-1.5" data-testid="frontend-resolved">
69
+ <div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
70
+ {{ t('inspector.frontendConfig.resolved.title') }}
71
+ </div>
72
+
73
+ <p
74
+ v-if="duplicates.length"
75
+ class="text-[11px] leading-snug text-amber-300/80"
76
+ data-testid="frontend-resolved-duplicates"
77
+ >
78
+ {{ t('inspector.frontendConfig.resolved.duplicateWarning', { vars: duplicates.join(', ') }) }}
79
+ </p>
80
+
81
+ <ul v-if="rows.length" class="space-y-0.5">
82
+ <li
83
+ v-for="row in rows"
84
+ :key="row.envVar"
85
+ class="flex items-baseline gap-1.5 text-[11px] leading-snug"
86
+ data-testid="frontend-resolved-row"
87
+ >
88
+ <span
89
+ class="mt-1 h-1.5 w-1.5 shrink-0 rounded-full"
90
+ :class="{
91
+ 'bg-emerald-400': row.kind === 'live',
92
+ 'bg-amber-400': row.kind === 'service-offline',
93
+ 'bg-slate-500': row.kind === 'mock',
94
+ }"
95
+ />
96
+ <span class="font-mono text-slate-300">{{ row.envVar }}</span>
97
+ <span class="text-slate-600">→</span>
98
+ <template v-if="row.kind === 'live'">
99
+ <span class="truncate font-mono text-emerald-300/90">{{ row.serviceUrl }}</span>
100
+ <span v-if="row.serviceTitle" class="text-slate-500">({{ row.serviceTitle }})</span>
101
+ </template>
102
+ <span v-else-if="row.kind === 'service-offline'" class="text-amber-300/80">
103
+ {{ t('inspector.frontendConfig.resolved.serviceOffline', { service: row.serviceTitle }) }}
104
+ </span>
105
+ <span v-else class="text-slate-500">
106
+ {{ t('inspector.frontendConfig.resolved.mock') }}
107
+ </span>
108
+ </li>
109
+ </ul>
110
+ </div>
111
+ </template>
@@ -10,6 +10,7 @@ import type {
10
10
  FrontendServeMode,
11
11
  PreviewStatus,
12
12
  } from '~/types/domain'
13
+ import FrontendBindingsResolved from '~/components/panels/inspector/FrontendBindingsResolved.vue'
13
14
 
14
15
  // Frontend-frame (`type: 'frontend'`) configuration: how to build, serve, and mock this
15
16
  // frontend for a self-contained UI test (+ an optional browsable preview on local/node),
@@ -625,6 +626,13 @@ onUnmounted(() => preview.stopPolling(props.block.id))
625
626
  <div v-else class="text-[11px] text-slate-500">
626
627
  {{ t('inspector.frontendConfig.bindings.empty') }}
627
628
  </div>
629
+
630
+ <!-- How the bindings resolve RIGHT NOW: each env var → a bound service's live ephemeral
631
+ URL, or WireMock — plus the duplicate-env-var warning. The same view a UI-test run
632
+ would resolve against (shared helpers), so what you see is what a run will drive. -->
633
+ <div class="border-t border-slate-800/60 pt-2">
634
+ <FrontendBindingsResolved :config="config" />
635
+ </div>
628
636
  </div>
629
637
  </div>
630
638
 
@@ -0,0 +1,222 @@
1
+ <script setup lang="ts">
2
+ // Private package registries — the workspace's npm-registry entries (npm private
3
+ // orgs, GitHub Packages) that agent containers use to resolve private dependencies
4
+ // on checkout. Tokens are write-only: the list renders from the redacted summary
5
+ // (vendor + scopes + token tail) and an entry is edited by deleting + re-adding.
6
+ // Opened from the Integrations hub.
7
+ import { computed, reactive, ref, watch } from 'vue'
8
+ import type { PackageRegistryVendor } from '~/types/packageRegistries'
9
+ import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
10
+
11
+ const { t } = useI18n()
12
+ const ui = useUiStore()
13
+ const store = usePackageRegistriesStore()
14
+ const toast = useToast()
15
+ const { confirmAction, toastDone } = useConfirmAction()
16
+
17
+ const open = computed({
18
+ get: () => ui.packageRegistriesOpen,
19
+ set: (v: boolean) => (v ? ui.openPackageRegistries() : ui.closePackageRegistries()),
20
+ })
21
+ const back = useIntegrationBack(open)
22
+
23
+ // The registry vendors a workspace can connect. Fixed set — the host derives from the
24
+ // vendor server-side, so it renders read-only here. Vendor names stay verbatim.
25
+ const VENDORS: { value: PackageRegistryVendor; label: string; host: string }[] = [
26
+ { value: 'npmjs', label: 'npm (npmjs.com)', host: 'registry.npmjs.org' },
27
+ { value: 'github-packages', label: 'GitHub Packages', host: 'npm.pkg.github.com' },
28
+ ]
29
+
30
+ const form = reactive({
31
+ vendor: 'npmjs' as PackageRegistryVendor,
32
+ scopes: '',
33
+ token: '',
34
+ })
35
+ const busy = ref(false)
36
+
37
+ const vendorHost = computed(() => VENDORS.find((v) => v.value === form.vendor)?.host ?? '')
38
+
39
+ function vendorLabel(vendor: PackageRegistryVendor): string {
40
+ return VENDORS.find((v) => v.value === vendor)?.label ?? vendor
41
+ }
42
+
43
+ /** Parse the comma/space-separated scopes input into `@org` entries. */
44
+ const parsedScopes = computed(() =>
45
+ form.scopes
46
+ .split(/[\s,]+/)
47
+ .map((s) => s.trim())
48
+ .filter(Boolean)
49
+ .map((s) => (s.startsWith('@') ? s : `@${s}`)),
50
+ )
51
+
52
+ function notifyError(title: string, e: unknown) {
53
+ toast.add({
54
+ title,
55
+ description: e instanceof Error ? e.message : String(e),
56
+ icon: 'i-lucide-triangle-alert',
57
+ color: 'error',
58
+ })
59
+ }
60
+
61
+ watch(
62
+ open,
63
+ async (isOpen) => {
64
+ if (!isOpen) return
65
+ try {
66
+ await store.ensureLoaded()
67
+ } catch (e) {
68
+ notifyError(t('settings.packageRegistries.toast.loadFailed'), e)
69
+ }
70
+ },
71
+ { immediate: true },
72
+ )
73
+
74
+ async function addEntry() {
75
+ busy.value = true
76
+ try {
77
+ await store.add({
78
+ ecosystem: 'npm',
79
+ vendor: form.vendor,
80
+ scopes: parsedScopes.value,
81
+ token: form.token.trim(),
82
+ })
83
+ form.scopes = ''
84
+ form.token = ''
85
+ toast.add({
86
+ title: t('settings.packageRegistries.toast.added'),
87
+ icon: 'i-lucide-check',
88
+ color: 'success',
89
+ })
90
+ } catch (e) {
91
+ notifyError(t('settings.packageRegistries.toast.addFailed'), e)
92
+ } finally {
93
+ busy.value = false
94
+ }
95
+ }
96
+
97
+ async function removeEntry(entryId: string) {
98
+ const noun = t('settings.packageRegistries.entryNoun')
99
+ if (!(await confirmAction('remove', noun))) return
100
+ busy.value = true
101
+ try {
102
+ await store.remove(entryId)
103
+ toastDone('remove', noun)
104
+ } catch (e) {
105
+ notifyError(t('settings.packageRegistries.toast.removeFailed'), e)
106
+ } finally {
107
+ busy.value = false
108
+ }
109
+ }
110
+ </script>
111
+
112
+ <template>
113
+ <UModal
114
+ v-model:open="open"
115
+ :title="t('settings.packageRegistries.title')"
116
+ :ui="{ content: 'max-w-lg' }"
117
+ >
118
+ <template #title>
119
+ <IntegrationBackTitle :title="t('settings.packageRegistries.title')" @back="back" />
120
+ </template>
121
+ <template #body>
122
+ <div class="space-y-4" data-testid="package-registries-panel">
123
+ <p class="text-sm text-slate-400">
124
+ {{ t('settings.packageRegistries.intro') }}
125
+ </p>
126
+
127
+ <section
128
+ v-if="store.entries.length"
129
+ class="space-y-2 rounded-lg border border-slate-700 p-3"
130
+ >
131
+ <h3 class="text-sm font-semibold">
132
+ {{ t('settings.packageRegistries.list.heading') }}
133
+ </h3>
134
+ <div
135
+ v-for="entry in store.entries"
136
+ :key="entry.id"
137
+ class="flex items-center justify-between gap-2 rounded-md border border-slate-800 px-3 py-2"
138
+ >
139
+ <div class="min-w-0 space-y-1">
140
+ <div class="flex items-center gap-2">
141
+ <span class="text-sm font-medium">{{ vendorLabel(entry.vendor) }}</span>
142
+ <span class="text-[11px] text-slate-500">
143
+ {{ t('settings.packageRegistries.list.tokenTail', { tail: entry.tokenTail }) }}
144
+ </span>
145
+ </div>
146
+ <div class="flex flex-wrap gap-1">
147
+ <UBadge
148
+ v-for="scope in entry.scopes"
149
+ :key="scope"
150
+ color="neutral"
151
+ variant="soft"
152
+ size="sm"
153
+ >
154
+ {{ scope }}
155
+ </UBadge>
156
+ </div>
157
+ </div>
158
+ <UButton
159
+ color="error"
160
+ variant="ghost"
161
+ icon="i-lucide-trash-2"
162
+ size="sm"
163
+ :loading="busy"
164
+ :data-testid="`package-registry-delete-${entry.id}`"
165
+ :aria-label="t('settings.packageRegistries.list.remove')"
166
+ @click="removeEntry(entry.id)"
167
+ />
168
+ </div>
169
+ </section>
170
+
171
+ <section class="space-y-3 rounded-lg border border-slate-700 p-3">
172
+ <h3 class="text-sm font-semibold">
173
+ {{ t('settings.packageRegistries.add.heading') }}
174
+ </h3>
175
+
176
+ <UFormField :label="t('settings.packageRegistries.add.vendor')">
177
+ <USelect
178
+ v-model="form.vendor"
179
+ :items="VENDORS"
180
+ value-key="value"
181
+ class="w-full"
182
+ data-testid="package-registry-vendor"
183
+ />
184
+ </UFormField>
185
+ <p class="text-[11px] text-slate-500">
186
+ {{ t('settings.packageRegistries.add.host', { host: vendorHost }) }}
187
+ </p>
188
+
189
+ <UFormField
190
+ :label="t('settings.packageRegistries.add.scopes')"
191
+ :help="t('settings.packageRegistries.add.scopesHelp')"
192
+ >
193
+ <UInput
194
+ v-model="form.scopes"
195
+ placeholder="@my-org, @my-other-org"
196
+ class="w-full"
197
+ data-testid="package-registry-scopes"
198
+ />
199
+ </UFormField>
200
+
201
+ <UFormField :label="t('settings.packageRegistries.add.token')">
202
+ <UInput
203
+ v-model="form.token"
204
+ type="password"
205
+ class="w-full"
206
+ data-testid="package-registry-token"
207
+ />
208
+ </UFormField>
209
+
210
+ <UButton
211
+ :loading="busy"
212
+ :disabled="!parsedScopes.length || !form.token.trim()"
213
+ data-testid="package-registry-save"
214
+ @click="addEntry"
215
+ >
216
+ {{ t('settings.packageRegistries.add.save') }}
217
+ </UButton>
218
+ </section>
219
+ </div>
220
+ </template>
221
+ </UModal>
222
+ </template>
@@ -0,0 +1,10 @@
1
+ import { listEnvironmentsContract } from '@cat-factory/contracts'
2
+ import type { ApiContext } from './context'
3
+
4
+ /** Ephemeral environments: the workspace's live env handles (used to resolve frontend bindings). */
5
+ export function environmentsApi({ send, ws }: ApiContext) {
6
+ return {
7
+ listEnvironments: (workspaceId: string) =>
8
+ send(listEnvironmentsContract, { pathPrefix: ws(workspaceId) }),
9
+ }
10
+ }
@@ -0,0 +1,24 @@
1
+ import {
2
+ addPackageRegistryContract,
3
+ deletePackageRegistryContract,
4
+ listPackageRegistriesContract,
5
+ } from '@cat-factory/contracts'
6
+ import type { AddPackageRegistryInput } from '~/types/packageRegistries'
7
+ import type { ApiContext } from './context'
8
+
9
+ /** Private package registries: the workspace's entries agent containers install with. */
10
+ export function packageRegistriesApi({ send, ws }: ApiContext) {
11
+ return {
12
+ listPackageRegistries: (workspaceId: string) =>
13
+ send(listPackageRegistriesContract, { pathPrefix: ws(workspaceId) }),
14
+
15
+ addPackageRegistry: (workspaceId: string, body: AddPackageRegistryInput) =>
16
+ send(addPackageRegistryContract, { pathPrefix: ws(workspaceId), body }),
17
+
18
+ deletePackageRegistry: (workspaceId: string, entryId: string) =>
19
+ send(deletePackageRegistryContract, {
20
+ pathPrefix: ws(workspaceId),
21
+ pathParams: { entryId },
22
+ }),
23
+ }
24
+ }
@@ -18,11 +18,13 @@ import { kaizenApi } from './api/kaizen'
18
18
  import { localSettingsApi } from './api/localSettings'
19
19
  import { modelsApi } from './api/models'
20
20
  import { notificationsApi } from './api/notifications'
21
+ import { packageRegistriesApi } from './api/packageRegistries'
21
22
  import { presetsApi } from './api/presets'
22
23
  import { providerConnectionsApi } from './api/providerConnections'
23
24
  import { provisioningLogsApi } from './api/provisioningLogs'
24
25
  import { recurringApi } from './api/recurring'
25
26
  import { previewApi } from './api/preview'
27
+ import { environmentsApi } from './api/environments'
26
28
  import { releaseHealthApi } from './api/releaseHealth'
27
29
  import { sandboxApi } from './api/sandbox'
28
30
  import { reviewsApi } from './api/reviews'
@@ -112,7 +114,9 @@ export function useApi() {
112
114
  ...infraHandlersApi(ctx),
113
115
  ...provisioningLogsApi(ctx),
114
116
  ...releaseHealthApi(ctx),
117
+ ...packageRegistriesApi(ctx),
115
118
  ...previewApi(ctx),
119
+ ...environmentsApi(ctx),
116
120
  ...recurringApi(ctx),
117
121
  ...sandboxApi(ctx),
118
122
  ...githubApi(ctx),
@@ -85,6 +85,9 @@ const AccountSettingsPanel = defineAsyncComponent(
85
85
  const ObservabilityConnectionPanel = defineAsyncComponent(
86
86
  () => import('~/components/settings/ObservabilityConnectionPanel.vue'),
87
87
  )
88
+ const PackageRegistriesPanel = defineAsyncComponent(
89
+ () => import('~/components/settings/PackageRegistriesPanel.vue'),
90
+ )
88
91
  const InfrastructureWindow = defineAsyncComponent(
89
92
  () => import('~/components/settings/InfrastructureWindow.vue'),
90
93
  )
@@ -356,6 +359,7 @@ watch(
356
359
  <WorkspaceSettingsPanel v-if="ui.workspaceSettingsOpen" />
357
360
  <AccountSettingsPanel v-if="ui.accountSettingsOpen" />
358
361
  <ObservabilityConnectionPanel v-if="ui.observabilityConnectionOpen" />
362
+ <PackageRegistriesPanel v-if="ui.packageRegistriesOpen" />
359
363
  <InfrastructureWindow v-if="ui.infrastructureOpen" />
360
364
  <ModelConfigurationPanel v-if="ui.modelConfigOpen" />
361
365
  <LocalModelEndpointsPanel v-if="ui.localModelsOpen" />
@@ -0,0 +1,52 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+ import {
4
+ boundServiceFrameIds,
5
+ indexLiveServiceEnvUrls,
6
+ type FrontendConfig,
7
+ } from '@cat-factory/contracts'
8
+ import type { EnvironmentHandle } from '~/types/domain'
9
+ import { useWorkspaceStore } from '~/stores/workspace'
10
+
11
+ /**
12
+ * The workspace's live ephemeral-environment handles, fetched on demand from
13
+ * `GET /workspaces/:ws/environments`. Used to resolve a `frontend` frame's backend bindings to
14
+ * their live service URLs (the SPA mirror of the backend's `AgentContextBuilder` resolution) — so
15
+ * the inspector and the run/step detail can show each `envVar → live URL | mocked` the SAME way a
16
+ * UI-test run would. Kept as a thin, load-on-open cache (no snapshot delivery, no self-poll):
17
+ * the callers refresh it when the frontend inspector / a UI-test step detail opens.
18
+ */
19
+ export const useEnvironmentsStore = defineStore('environments', () => {
20
+ const api = useApi()
21
+
22
+ /** The last-fetched env handles for the current workspace. */
23
+ const handles = ref<EnvironmentHandle[]>([])
24
+ /** A load is in flight (drives an optional spinner). */
25
+ const loading = ref(false)
26
+
27
+ /** (Re)load the workspace's environment handles; failures leave the last-known list. */
28
+ async function load(): Promise<void> {
29
+ const ws = useWorkspaceStore()
30
+ loading.value = true
31
+ try {
32
+ handles.value = await api.listEnvironments(ws.requireId())
33
+ } catch {
34
+ // Transient: keep the last-known handles rather than blanking the resolved view.
35
+ } finally {
36
+ loading.value = false
37
+ }
38
+ }
39
+
40
+ /**
41
+ * The live `serviceFrameId → url` map for exactly the service FRAMES a frontend config binds —
42
+ * the same newest-wins, ready-with-URL indexing the backend applies (`indexLiveServiceEnvUrls`),
43
+ * so the SPA's resolved-binding view can't drift from what a run would resolve.
44
+ */
45
+ function liveServiceEnvUrls(
46
+ config: Pick<FrontendConfig, 'backendBindings'>,
47
+ ): Map<string, string> {
48
+ return indexLiveServiceEnvUrls(handles.value, boundServiceFrameIds(config))
49
+ }
50
+
51
+ return { handles, loading, load, liveServiceEnvUrls }
52
+ })
@@ -0,0 +1,66 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+ import type { AddPackageRegistryInput, PackageRegistryEntryView } from '~/types/packageRegistries'
4
+ import { useWorkspaceStore } from '~/stores/workspace'
5
+ import { apiErrorStatus } from '~/composables/api/errors'
6
+
7
+ /**
8
+ * The workspace's private package-registry entries (npm private orgs, GitHub
9
+ * Packages) that agent containers install with. Tokens are write-only — the store
10
+ * only ever holds the redacted summary views. Loaded on demand (the registries
11
+ * panel + the Integrations hub badge), not from the snapshot.
12
+ */
13
+ export const usePackageRegistriesStore = defineStore('packageRegistries', () => {
14
+ const api = useApi()
15
+
16
+ const entries = ref<PackageRegistryEntryView[]>([])
17
+ const loading = ref(false)
18
+ // Mirrors the backend's opt-in gate (the module 503s when the encryption key is
19
+ // absent): `null` until first probed, then `true`/`false`. The hub hides its
20
+ // registries entry point when this is false.
21
+ const available = ref<boolean | null>(null)
22
+ let inFlight: Promise<void> | null = null
23
+
24
+ /** Force a refresh of the entry list (used after an add/remove). */
25
+ async function load() {
26
+ const ws = useWorkspaceStore()
27
+ loading.value = true
28
+ try {
29
+ entries.value = (await api.listPackageRegistries(ws.requireId())).entries
30
+ available.value = true
31
+ } catch (err) {
32
+ if (apiErrorStatus(err) === 503) {
33
+ // A definitive 503 means the integration is unconfigured (no encryption key on
34
+ // the backend): hide the UI entry points and stop probing.
35
+ available.value = false
36
+ entries.value = []
37
+ }
38
+ // Any other failure (transient 5xx / network) is left untouched: it must not hide
39
+ // an already-available panel nor cache a false "unavailable". `available` stays
40
+ // `null` when never probed, so `ensureLoaded` remains retryable on the next open.
41
+ } finally {
42
+ loading.value = false
43
+ }
44
+ }
45
+
46
+ /** Load once and share the result (coalescing concurrent callers); `load()` refreshes. */
47
+ async function ensureLoaded() {
48
+ if (available.value !== null) return
49
+ if (!inFlight) inFlight = load().finally(() => (inFlight = null))
50
+ return inFlight
51
+ }
52
+
53
+ async function add(input: AddPackageRegistryInput) {
54
+ const ws = useWorkspaceStore()
55
+ entries.value = (await api.addPackageRegistry(ws.requireId(), input)).entries
56
+ available.value = true
57
+ }
58
+
59
+ async function remove(entryId: string) {
60
+ const ws = useWorkspaceStore()
61
+ await api.deletePackageRegistry(ws.requireId(), entryId)
62
+ entries.value = entries.value.filter((entry) => entry.id !== entryId)
63
+ }
64
+
65
+ return { entries, loading, available, load, ensureLoaded, add, remove }
66
+ })
package/app/stores/ui.ts CHANGED
@@ -153,6 +153,9 @@ export const useUiStore = defineStore('ui', () => {
153
153
  // today, pluggable). NB: distinct from `observabilityInstanceId` below, which is the
154
154
  // LLM per-call observability panel.
155
155
  const observabilityConnectionOpen = ref(false)
156
+ // Private package registries: the workspace's npm/GitHub-Packages entries agent
157
+ // containers install with. Opened from the Integrations hub.
158
+ const packageRegistriesOpen = ref(false)
156
159
  // The single tabbed Infrastructure window — a TOP-LEVEL navbar destination (no longer
157
160
  // reached via the Integrations hub). Two topical tabs: "Agent containers" (the execution
158
161
  // backend + self-hosted runner pool, plus the local-mode warm pool/checkout) and "Test
@@ -561,6 +564,13 @@ export const useUiStore = defineStore('ui', () => {
561
564
  function closeObservabilityConnection() {
562
565
  observabilityConnectionOpen.value = false
563
566
  }
567
+ function openPackageRegistries() {
568
+ resetHubReturn()
569
+ packageRegistriesOpen.value = true
570
+ }
571
+ function closePackageRegistries() {
572
+ packageRegistriesOpen.value = false
573
+ }
564
574
  // Top-level navbar entry into the Infrastructure window. No hub-return marker (it isn't
565
575
  // reached from the Integrations hub), so the window shows no "Back to Integrations" control.
566
576
  function openInfrastructure(tab: 'environment' | 'runner-pool' = 'runner-pool') {
@@ -791,6 +801,7 @@ export const useUiStore = defineStore('ui', () => {
791
801
  accountSettingsTab,
792
802
  accountSettingsScrollTarget,
793
803
  observabilityConnectionOpen,
804
+ packageRegistriesOpen,
794
805
  infrastructureOpen,
795
806
  infrastructureTab,
796
807
  openInfrastructure,
@@ -880,6 +891,8 @@ export const useUiStore = defineStore('ui', () => {
880
891
  setAccountSettingsTab,
881
892
  openObservabilityConnection,
882
893
  closeObservabilityConnection,
894
+ openPackageRegistries,
895
+ closePackageRegistries,
883
896
  openProviderConnection,
884
897
  closeProviderConnection,
885
898
  k3sSetupPrefill,
@@ -33,6 +33,8 @@ export type {
33
33
  FrontendConfig,
34
34
  FrontendBackendBinding,
35
35
  FrontendBackendSource,
36
+ ResolvedFrontendBinding,
37
+ EnvironmentHandle,
36
38
  ServiceConnection,
37
39
  FrontendBranch,
38
40
  FrontendPackageManager,
@@ -0,0 +1,13 @@
1
+ // Private package-registry settings shapes. Per-workspace registry entries (npm
2
+ // private orgs, GitHub Packages) whose tokens are write-only — the list view only
3
+ // ever carries the non-secret summary (vendor + scopes + token tail).
4
+ //
5
+ // All wire shapes are sourced from @cat-factory/contracts (single source of truth).
6
+
7
+ export type {
8
+ PackageEcosystem,
9
+ PackageRegistryVendor,
10
+ AddPackageRegistryInput,
11
+ PackageRegistryEntryView,
12
+ PackageRegistryListView,
13
+ } from '@cat-factory/contracts'
@@ -544,6 +544,12 @@
544
544
  "failed": "Failed",
545
545
  "stopped": "Not running"
546
546
  }
547
+ },
548
+ "resolved": {
549
+ "title": "Resolves to",
550
+ "duplicateWarning": "Used on more than one binding, so only the last applies: {vars}.",
551
+ "serviceOffline": "{service}: no live environment (mocked)",
552
+ "mock": "Mock (WireMock)"
547
553
  }
548
554
  },
549
555
  "serviceConnections": {
@@ -1491,6 +1497,7 @@
1491
1497
  "documents": "Documents",
1492
1498
  "taskTrackers": "Task trackers",
1493
1499
  "observability": "Observability",
1500
+ "development": "Development",
1494
1501
  "personal": "Personal (only you)"
1495
1502
  },
1496
1503
  "items": {
@@ -1528,6 +1535,10 @@
1528
1535
  "label": "Post-release health",
1529
1536
  "description": "Watch monitors and SLOs after a release ships (Datadog)."
1530
1537
  },
1538
+ "packageRegistries": {
1539
+ "label": "Private package registries",
1540
+ "description": "npm and GitHub Packages tokens agents use to install private dependencies."
1541
+ },
1531
1542
  "githubPat": {
1532
1543
  "label": "My GitHub token",
1533
1544
  "description": "A personal access token used for runs you start (pushes, PRs, CI, merge)."
@@ -2025,6 +2036,31 @@
2025
2036
  "connectionNoun": "the observability connection",
2026
2037
  "incidentNoun": "the incident provider"
2027
2038
  },
2039
+ "packageRegistries": {
2040
+ "title": "Private package registries",
2041
+ "intro": "Connect the private registries your repositories install from. Agents receive them when they check out a repository, so private dependencies resolve during installs. Tokens are write-only: to change one, remove the entry and add it again.",
2042
+ "entryNoun": "registry entry",
2043
+ "list": {
2044
+ "heading": "Connected registries",
2045
+ "tokenTail": "token …{tail}",
2046
+ "remove": "Remove entry"
2047
+ },
2048
+ "add": {
2049
+ "heading": "Add a registry",
2050
+ "vendor": "Registry",
2051
+ "host": "The token is sent only to {host}.",
2052
+ "scopes": "Package scopes",
2053
+ "scopesHelp": "Comma-separated npm scopes",
2054
+ "token": "Access token",
2055
+ "save": "Add registry"
2056
+ },
2057
+ "toast": {
2058
+ "loadFailed": "Could not load package registries",
2059
+ "added": "Registry added",
2060
+ "addFailed": "Could not add the registry",
2061
+ "removeFailed": "Could not remove the registry entry"
2062
+ }
2063
+ },
2028
2064
  "localMode": {
2029
2065
  "title": "Local mode",
2030
2066
  "intro": "Tuning for the local container runner, stored on this machine's deployment (it replaced the {poolVars} / {harnessVars} env vars). Saving resizes the warm pool live, no restart needed; in-flight runs keep the container they already hold.",
@@ -501,6 +501,12 @@
501
501
  "failed": "Error",
502
502
  "stopped": "No iniciada"
503
503
  }
504
+ },
505
+ "resolved": {
506
+ "title": "Se resuelve a",
507
+ "duplicateWarning": "Usado en más de un binding, por lo que solo se aplica el último: {vars}.",
508
+ "serviceOffline": "{service}: sin entorno activo (simulado)",
509
+ "mock": "Mock (WireMock)"
504
510
  }
505
511
  },
506
512
  "serviceConnections": {
@@ -1438,6 +1444,7 @@
1438
1444
  "documents": "Documentos",
1439
1445
  "taskTrackers": "Rastreadores de tareas",
1440
1446
  "observability": "Observabilidad",
1447
+ "development": "Desarrollo",
1441
1448
  "personal": "Personal (solo tú)"
1442
1449
  },
1443
1450
  "items": {
@@ -1475,6 +1482,10 @@
1475
1482
  "label": "Salud posterior al lanzamiento",
1476
1483
  "description": "Vigila los monitores y SLO después de publicar una versión (Datadog)."
1477
1484
  },
1485
+ "packageRegistries": {
1486
+ "label": "Registros privados de paquetes",
1487
+ "description": "Tokens de npm y GitHub Packages que los agentes usan para instalar dependencias privadas."
1488
+ },
1478
1489
  "githubPat": {
1479
1490
  "label": "Mi token de GitHub",
1480
1491
  "description": "Un token de acceso personal usado para las ejecuciones que inicias (pushes, PR, CI, fusión)."
@@ -1854,6 +1865,31 @@
1854
1865
  "connectionNoun": "la conexión de observabilidad",
1855
1866
  "incidentNoun": "el proveedor de incidencias"
1856
1867
  },
1868
+ "packageRegistries": {
1869
+ "title": "Registros privados de paquetes",
1870
+ "intro": "Conecta los registros privados desde los que instalan tus repositorios. Los agentes los reciben al hacer checkout de un repositorio, de modo que las dependencias privadas se resuelven durante la instalación. Los tokens son de solo escritura: para cambiar uno, elimina la entrada y vuelve a agregarla.",
1871
+ "entryNoun": "entrada de registro",
1872
+ "list": {
1873
+ "heading": "Registros conectados",
1874
+ "tokenTail": "token …{tail}",
1875
+ "remove": "Eliminar entrada"
1876
+ },
1877
+ "add": {
1878
+ "heading": "Agregar un registro",
1879
+ "vendor": "Registro",
1880
+ "host": "El token solo se envía a {host}.",
1881
+ "scopes": "Ámbitos de paquetes",
1882
+ "scopesHelp": "Ámbitos de npm separados por comas",
1883
+ "token": "Token de acceso",
1884
+ "save": "Agregar registro"
1885
+ },
1886
+ "toast": {
1887
+ "loadFailed": "No se pudieron cargar los registros de paquetes",
1888
+ "added": "Registro agregado",
1889
+ "addFailed": "No se pudo agregar el registro",
1890
+ "removeFailed": "No se pudo eliminar la entrada del registro"
1891
+ }
1892
+ },
1857
1893
  "localMode": {
1858
1894
  "title": "Modo local",
1859
1895
  "intro": "Ajustes del ejecutor de contenedores local, almacenados en el despliegue de esta máquina (reemplazó las variables de entorno {poolVars} / {harnessVars}). Guardar redimensiona el grupo en caliente en vivo, sin necesidad de reiniciar; las ejecuciones en curso conservan el contenedor que ya tienen.",
@@ -501,6 +501,12 @@
501
501
  "failed": "Échec",
502
502
  "stopped": "Non démarré"
503
503
  }
504
+ },
505
+ "resolved": {
506
+ "title": "Résolution actuelle",
507
+ "duplicateWarning": "Utilisé sur plusieurs liaisons, seule la dernière s'applique : {vars}.",
508
+ "serviceOffline": "{service} : aucun environnement actif (simulé)",
509
+ "mock": "Mock (WireMock)"
504
510
  }
505
511
  },
506
512
  "serviceConnections": {
@@ -1438,6 +1444,7 @@
1438
1444
  "documents": "Documents",
1439
1445
  "taskTrackers": "Outils de suivi des tâches",
1440
1446
  "observability": "Observabilité",
1447
+ "development": "Développement",
1441
1448
  "personal": "Personnel (vous uniquement)"
1442
1449
  },
1443
1450
  "items": {
@@ -1475,6 +1482,10 @@
1475
1482
  "label": "Santé après publication",
1476
1483
  "description": "Surveillez les moniteurs et les SLO après la publication d'une version (Datadog)."
1477
1484
  },
1485
+ "packageRegistries": {
1486
+ "label": "Registres de paquets privés",
1487
+ "description": "Jetons npm et GitHub Packages que les agents utilisent pour installer les dépendances privées."
1488
+ },
1478
1489
  "githubPat": {
1479
1490
  "label": "Mon jeton GitHub",
1480
1491
  "description": "Un jeton d'accès personnel utilisé pour les exécutions que vous lancez (pushes, PR, CI, fusion)."
@@ -1854,6 +1865,31 @@
1854
1865
  "connectionNoun": "la connexion d’observabilité",
1855
1866
  "incidentNoun": "le fournisseur d’incidents"
1856
1867
  },
1868
+ "packageRegistries": {
1869
+ "title": "Registres de paquets privés",
1870
+ "intro": "Connectez les registres privés depuis lesquels vos dépôts installent leurs dépendances. Les agents les reçoivent lors du checkout d'un dépôt, afin que les dépendances privées se résolvent pendant l'installation. Les jetons sont en écriture seule : pour en changer un, supprimez l'entrée puis ajoutez-la de nouveau.",
1871
+ "entryNoun": "entrée de registre",
1872
+ "list": {
1873
+ "heading": "Registres connectés",
1874
+ "tokenTail": "jeton …{tail}",
1875
+ "remove": "Supprimer l'entrée"
1876
+ },
1877
+ "add": {
1878
+ "heading": "Ajouter un registre",
1879
+ "vendor": "Registre",
1880
+ "host": "Le jeton n'est envoyé qu'à {host}.",
1881
+ "scopes": "Portées de paquets",
1882
+ "scopesHelp": "Portées npm séparées par des virgules",
1883
+ "token": "Jeton d'accès",
1884
+ "save": "Ajouter le registre"
1885
+ },
1886
+ "toast": {
1887
+ "loadFailed": "Impossible de charger les registres de paquets",
1888
+ "added": "Registre ajouté",
1889
+ "addFailed": "Impossible d'ajouter le registre",
1890
+ "removeFailed": "Impossible de supprimer l'entrée du registre"
1891
+ }
1892
+ },
1857
1893
  "localMode": {
1858
1894
  "title": "Mode local",
1859
1895
  "intro": "Réglage de l'exécuteur de conteneurs local, stocké sur le déploiement de cette machine (il a remplacé les variables d'environnement {poolVars} / {harnessVars}). L'enregistrement redimensionne le pool à chaud en direct, sans redémarrage nécessaire; les exécutions en cours conservent le conteneur qu'elles détiennent déjà.",
@@ -501,6 +501,12 @@
501
501
  "failed": "נכשל",
502
502
  "stopped": "לא פועל"
503
503
  }
504
+ },
505
+ "resolved": {
506
+ "title": "מתפרש כעת אל",
507
+ "duplicateWarning": "בשימוש ביותר מקישור אחד, ולכן רק האחרון חל: {vars}.",
508
+ "serviceOffline": "{service}: אין סביבה פעילה (מדומה)",
509
+ "mock": "Mock (WireMock)"
504
510
  }
505
511
  },
506
512
  "serviceConnections": {
@@ -1438,6 +1444,7 @@
1438
1444
  "documents": "מסמכים",
1439
1445
  "taskTrackers": "עוקבי משימות",
1440
1446
  "observability": "תצפיתיות",
1447
+ "development": "פיתוח",
1441
1448
  "personal": "אישי (רק אתה)"
1442
1449
  },
1443
1450
  "items": {
@@ -1475,6 +1482,10 @@
1475
1482
  "label": "בריאות לאחר שחרור",
1476
1483
  "description": "עקוב אחר מוניטורים ו-SLO לאחר שחרור גרסה (Datadog)."
1477
1484
  },
1485
+ "packageRegistries": {
1486
+ "label": "מאגרי חבילות פרטיים",
1487
+ "description": "אסימוני npm ו-GitHub Packages שסוכנים משתמשים בהם להתקנת תלויות פרטיות."
1488
+ },
1478
1489
  "githubPat": {
1479
1490
  "label": "אסימון ה-GitHub שלי",
1480
1491
  "description": "אסימון גישה אישי המשמש להרצות שאתה מתחיל (דחיפות, PR, CI, מיזוג)."
@@ -1974,6 +1985,31 @@
1974
1985
  "connectionNoun": "חיבור התצפיתיות",
1975
1986
  "incidentNoun": "ספק התקריות"
1976
1987
  },
1988
+ "packageRegistries": {
1989
+ "title": "מאגרי חבילות פרטיים",
1990
+ "intro": "חברו את המאגרים הפרטיים שמהם הריפוזיטוריים שלכם מתקינים. הסוכנים מקבלים אותם בעת משיכת ריפוזיטורי, כך שתלויות פרטיות נפתרות בזמן ההתקנה. האסימונים הם לכתיבה בלבד: כדי לשנות אסימון, הסירו את הרשומה והוסיפו אותה מחדש.",
1991
+ "entryNoun": "רשומת מאגר",
1992
+ "list": {
1993
+ "heading": "מאגרים מחוברים",
1994
+ "tokenTail": "אסימון …{tail}",
1995
+ "remove": "הסרת רשומה"
1996
+ },
1997
+ "add": {
1998
+ "heading": "הוספת מאגר",
1999
+ "vendor": "מאגר",
2000
+ "host": "האסימון נשלח רק אל {host}.",
2001
+ "scopes": "היקפי חבילות",
2002
+ "scopesHelp": "היקפי npm מופרדים בפסיקים",
2003
+ "token": "אסימון גישה",
2004
+ "save": "הוספת מאגר"
2005
+ },
2006
+ "toast": {
2007
+ "loadFailed": "טעינת מאגרי החבילות נכשלה",
2008
+ "added": "המאגר נוסף",
2009
+ "addFailed": "הוספת המאגר נכשלה",
2010
+ "removeFailed": "הסרת רשומת המאגר נכשלה"
2011
+ }
2012
+ },
1977
2013
  "localMode": {
1978
2014
  "title": "מצב מקומי",
1979
2015
  "intro": "כוונון עבור מריץ הקונטיינרים המקומי, נשמר בפריסה של מכונה זו (הוא החליף את משתני הסביבה {poolVars} / {harnessVars}). שמירה משנה את גודל המאגר החם בזמן אמת, ללא צורך באתחול מחדש; הרצות פעילות שומרות את הקונטיינר שהן כבר מחזיקות.",
@@ -501,6 +501,12 @@
501
501
  "failed": "失敗",
502
502
  "stopped": "停止中"
503
503
  }
504
+ },
505
+ "resolved": {
506
+ "title": "現在の解決先",
507
+ "duplicateWarning": "複数のバインディングで使用されているため、最後のもののみが適用されます: {vars}。",
508
+ "serviceOffline": "{service}: 稼働環境なし(モック)",
509
+ "mock": "モック (WireMock)"
504
510
  }
505
511
  },
506
512
  "serviceConnections": {
@@ -1438,6 +1444,7 @@
1438
1444
  "documents": "ドキュメント",
1439
1445
  "taskTrackers": "タスクトラッカー",
1440
1446
  "observability": "オブザーバビリティ",
1447
+ "development": "開発",
1441
1448
  "personal": "個人(あなたのみ)"
1442
1449
  },
1443
1450
  "items": {
@@ -1475,6 +1482,10 @@
1475
1482
  "label": "リリース後のヘルス",
1476
1483
  "description": "リリース後にモニターと SLO を監視します (Datadog)。"
1477
1484
  },
1485
+ "packageRegistries": {
1486
+ "label": "プライベートパッケージレジストリ",
1487
+ "description": "エージェントがプライベート依存関係のインストールに使う npm と GitHub Packages のトークン。"
1488
+ },
1478
1489
  "githubPat": {
1479
1490
  "label": "マイ GitHub トークン",
1480
1491
  "description": "自分が開始した実行 (push、PR、CI、マージ) に使用するパーソナルアクセストークン。"
@@ -1976,6 +1987,31 @@
1976
1987
  "connectionNoun": "オブザーバビリティ接続",
1977
1988
  "incidentNoun": "インシデントプロバイダー"
1978
1989
  },
1990
+ "packageRegistries": {
1991
+ "title": "プライベートパッケージレジストリ",
1992
+ "intro": "リポジトリが依存関係をインストールするプライベートレジストリを接続します。エージェントはリポジトリのチェックアウト時にこれらを受け取り、インストール時にプライベート依存関係を解決できます。トークンは書き込み専用です。変更するにはエントリを削除してから追加し直してください。",
1993
+ "entryNoun": "レジストリエントリ",
1994
+ "list": {
1995
+ "heading": "接続済みのレジストリ",
1996
+ "tokenTail": "トークン …{tail}",
1997
+ "remove": "エントリを削除"
1998
+ },
1999
+ "add": {
2000
+ "heading": "レジストリを追加",
2001
+ "vendor": "レジストリ",
2002
+ "host": "トークンは {host} にのみ送信されます。",
2003
+ "scopes": "パッケージスコープ",
2004
+ "scopesHelp": "カンマ区切りの npm スコープ",
2005
+ "token": "アクセストークン",
2006
+ "save": "レジストリを追加"
2007
+ },
2008
+ "toast": {
2009
+ "loadFailed": "パッケージレジストリを読み込めませんでした",
2010
+ "added": "レジストリを追加しました",
2011
+ "addFailed": "レジストリを追加できませんでした",
2012
+ "removeFailed": "レジストリエントリを削除できませんでした"
2013
+ }
2014
+ },
1979
2015
  "localMode": {
1980
2016
  "title": "ローカルモード",
1981
2017
  "intro": "ローカルコンテナランナーの調整設定で、このマシンのデプロイに保存されます ({poolVars} / {harnessVars} 環境変数を置き換えました)。保存するとウォームプールがライブでサイズ変更され、再起動は不要です。実行中のランは、すでに保持しているコンテナをそのまま使います。",
@@ -501,6 +501,12 @@
501
501
  "failed": "Błąd",
502
502
  "stopped": "Nie uruchomiono"
503
503
  }
504
+ },
505
+ "resolved": {
506
+ "title": "Rozwiązuje się na",
507
+ "duplicateWarning": "Użyte w więcej niż jednym powiązaniu, więc obowiązuje tylko ostatnie: {vars}.",
508
+ "serviceOffline": "{service}: brak działającego środowiska (zamockowane)",
509
+ "mock": "Mock (WireMock)"
504
510
  }
505
511
  },
506
512
  "serviceConnections": {
@@ -1438,6 +1444,7 @@
1438
1444
  "documents": "Dokumenty",
1439
1445
  "taskTrackers": "Narzędzia do śledzenia zadań",
1440
1446
  "observability": "Obserwowalność",
1447
+ "development": "Programowanie",
1441
1448
  "personal": "Osobiste (tylko Ty)"
1442
1449
  },
1443
1450
  "items": {
@@ -1475,6 +1482,10 @@
1475
1482
  "label": "Kondycja po wydaniu",
1476
1483
  "description": "Obserwuj monitory i SLO po wdrożeniu wydania (Datadog)."
1477
1484
  },
1485
+ "packageRegistries": {
1486
+ "label": "Prywatne rejestry pakietów",
1487
+ "description": "Tokeny npm i GitHub Packages, których agenci używają do instalowania prywatnych zależności."
1488
+ },
1478
1489
  "githubPat": {
1479
1490
  "label": "Mój token GitHub",
1480
1491
  "description": "Osobisty token dostępu używany do uruchomień, które rozpoczynasz (pushe, PR-y, CI, scalanie)."
@@ -1854,6 +1865,31 @@
1854
1865
  "connectionNoun": "połączenie obserwowalności",
1855
1866
  "incidentNoun": "dostawcę incydentów"
1856
1867
  },
1868
+ "packageRegistries": {
1869
+ "title": "Prywatne rejestry pakietów",
1870
+ "intro": "Połącz prywatne rejestry, z których instalują Twoje repozytoria. Agenci otrzymują je przy pobieraniu repozytorium, dzięki czemu prywatne zależności rozwiązują się podczas instalacji. Tokeny są tylko do zapisu: aby zmienić token, usuń wpis i dodaj go ponownie.",
1871
+ "entryNoun": "wpis rejestru",
1872
+ "list": {
1873
+ "heading": "Połączone rejestry",
1874
+ "tokenTail": "token …{tail}",
1875
+ "remove": "Usuń wpis"
1876
+ },
1877
+ "add": {
1878
+ "heading": "Dodaj rejestr",
1879
+ "vendor": "Rejestr",
1880
+ "host": "Token jest wysyłany tylko do {host}.",
1881
+ "scopes": "Zakresy pakietów",
1882
+ "scopesHelp": "Zakresy npm oddzielone przecinkami",
1883
+ "token": "Token dostępu",
1884
+ "save": "Dodaj rejestr"
1885
+ },
1886
+ "toast": {
1887
+ "loadFailed": "Nie udało się wczytać rejestrów pakietów",
1888
+ "added": "Rejestr dodany",
1889
+ "addFailed": "Nie udało się dodać rejestru",
1890
+ "removeFailed": "Nie udało się usunąć wpisu rejestru"
1891
+ }
1892
+ },
1857
1893
  "localMode": {
1858
1894
  "title": "Tryb lokalny",
1859
1895
  "intro": "Strojenie lokalnego uruchamiacza kontenerów, przechowywane we wdrożeniu tej maszyny (zastąpiło zmienne środowiskowe {poolVars} / {harnessVars}). Zapisanie zmienia rozmiar ciepłej puli na żywo, bez potrzeby restartu; trwające uruchomienia zachowują kontener, który już mają.",
@@ -501,6 +501,12 @@
501
501
  "failed": "Başarısız",
502
502
  "stopped": "Başlatılmadı"
503
503
  }
504
+ },
505
+ "resolved": {
506
+ "title": "Şu anda çözümleniyor",
507
+ "duplicateWarning": "Birden fazla bağlamada kullanılıyor, bu yüzden yalnızca sonuncusu geçerli: {vars}.",
508
+ "serviceOffline": "{service}: çalışan ortam yok (taklit ediliyor)",
509
+ "mock": "Mock (WireMock)"
504
510
  }
505
511
  },
506
512
  "serviceConnections": {
@@ -1438,6 +1444,7 @@
1438
1444
  "documents": "Belgeler",
1439
1445
  "taskTrackers": "Görev izleyiciler",
1440
1446
  "observability": "Gözlemlenebilirlik",
1447
+ "development": "Geliştirme",
1441
1448
  "personal": "Kişisel (yalnızca siz)"
1442
1449
  },
1443
1450
  "items": {
@@ -1475,6 +1482,10 @@
1475
1482
  "label": "Sürüm sonrası sağlık",
1476
1483
  "description": "Bir sürüm yayınlandıktan sonra monitörleri ve SLO'ları izleyin (Datadog)."
1477
1484
  },
1485
+ "packageRegistries": {
1486
+ "label": "Özel paket kayıt defterleri",
1487
+ "description": "Aracıların özel bağımlılıkları yüklemek için kullandığı npm ve GitHub Packages token'ları."
1488
+ },
1478
1489
  "githubPat": {
1479
1490
  "label": "GitHub token'ım",
1480
1491
  "description": "Başlattığınız çalıştırmalar için kullanılan kişisel erişim token'ı (push'lar, PR'lar, CI, birleştirme)."
@@ -1976,6 +1987,31 @@
1976
1987
  "connectionNoun": "gözlemlenebilirlik bağlantısı",
1977
1988
  "incidentNoun": "olay sağlayıcısı"
1978
1989
  },
1990
+ "packageRegistries": {
1991
+ "title": "Özel paket kayıt defterleri",
1992
+ "intro": "Depolarınızın yükleme yaptığı özel kayıt defterlerini bağlayın. Aracılar bir depoyu çekerken bunları alır, böylece özel bağımlılıklar yükleme sırasında çözülür. Token'lar yalnızca yazılırdır: birini değiştirmek için girdiyi silin ve yeniden ekleyin.",
1993
+ "entryNoun": "kayıt defteri girdisi",
1994
+ "list": {
1995
+ "heading": "Bağlı kayıt defterleri",
1996
+ "tokenTail": "token …{tail}",
1997
+ "remove": "Girdiyi kaldır"
1998
+ },
1999
+ "add": {
2000
+ "heading": "Kayıt defteri ekle",
2001
+ "vendor": "Kayıt defteri",
2002
+ "host": "Token yalnızca {host} adresine gönderilir.",
2003
+ "scopes": "Paket kapsamları",
2004
+ "scopesHelp": "Virgülle ayrılmış npm kapsamları",
2005
+ "token": "Erişim token'ı",
2006
+ "save": "Kayıt defteri ekle"
2007
+ },
2008
+ "toast": {
2009
+ "loadFailed": "Paket kayıt defterleri yüklenemedi",
2010
+ "added": "Kayıt defteri eklendi",
2011
+ "addFailed": "Kayıt defteri eklenemedi",
2012
+ "removeFailed": "Kayıt defteri girdisi kaldırılamadı"
2013
+ }
2014
+ },
1979
2015
  "localMode": {
1980
2016
  "title": "Yerel mod",
1981
2017
  "intro": "Yerel konteyner çalıştırıcısı için ayarlar, bu makinenin dağıtımında saklanır ({poolVars} / {harnessVars} ortam değişkenlerinin yerini aldı). Kaydetmek sıcak havuzu anında yeniden boyutlandırır, yeniden başlatma gerekmez; devam eden çalıştırmalar zaten tuttukları konteyneri korur.",
@@ -501,6 +501,12 @@
501
501
  "failed": "Помилка",
502
502
  "stopped": "Не запущено"
503
503
  }
504
+ },
505
+ "resolved": {
506
+ "title": "Розв'язується як",
507
+ "duplicateWarning": "Використано в кількох прив'язках, тож застосовується лише остання: {vars}.",
508
+ "serviceOffline": "{service}: немає активного середовища (мок)",
509
+ "mock": "Mock (WireMock)"
504
510
  }
505
511
  },
506
512
  "serviceConnections": {
@@ -1438,6 +1444,7 @@
1438
1444
  "documents": "Документи",
1439
1445
  "taskTrackers": "Трекери завдань",
1440
1446
  "observability": "Спостережуваність",
1447
+ "development": "Розробка",
1441
1448
  "personal": "Особисте (лише ви)"
1442
1449
  },
1443
1450
  "items": {
@@ -1475,6 +1482,10 @@
1475
1482
  "label": "Стан після випуску",
1476
1483
  "description": "Стежте за моніторами й SLO після випуску версії (Datadog)."
1477
1484
  },
1485
+ "packageRegistries": {
1486
+ "label": "Приватні реєстри пакетів",
1487
+ "description": "Токени npm і GitHub Packages, які агенти використовують для встановлення приватних залежностей."
1488
+ },
1478
1489
  "githubPat": {
1479
1490
  "label": "Мій токен GitHub",
1480
1491
  "description": "Особистий токен доступу, який використовується для запусків, що ви розпочинаєте (pushes, PR-и, CI, злиття)."
@@ -1854,6 +1865,31 @@
1854
1865
  "connectionNoun": "з’єднання спостережуваності",
1855
1866
  "incidentNoun": "постачальника інцидентів"
1856
1867
  },
1868
+ "packageRegistries": {
1869
+ "title": "Приватні реєстри пакетів",
1870
+ "intro": "Підключіть приватні реєстри, з яких встановлюють ваші репозиторії. Агенти отримують їх під час отримання репозиторію, тож приватні залежності розв'язуються під час встановлення. Токени доступні лише для запису: щоб змінити токен, видаліть запис і додайте його знову.",
1871
+ "entryNoun": "запис реєстру",
1872
+ "list": {
1873
+ "heading": "Підключені реєстри",
1874
+ "tokenTail": "токен …{tail}",
1875
+ "remove": "Видалити запис"
1876
+ },
1877
+ "add": {
1878
+ "heading": "Додати реєстр",
1879
+ "vendor": "Реєстр",
1880
+ "host": "Токен надсилається лише до {host}.",
1881
+ "scopes": "Скоупи пакетів",
1882
+ "scopesHelp": "Скоупи npm через кому",
1883
+ "token": "Токен доступу",
1884
+ "save": "Додати реєстр"
1885
+ },
1886
+ "toast": {
1887
+ "loadFailed": "Не вдалося завантажити реєстри пакетів",
1888
+ "added": "Реєстр додано",
1889
+ "addFailed": "Не вдалося додати реєстр",
1890
+ "removeFailed": "Не вдалося видалити запис реєстру"
1891
+ }
1892
+ },
1857
1893
  "localMode": {
1858
1894
  "title": "Локальний режим",
1859
1895
  "intro": "Налаштування локального запускача контейнерів, збережене на розгортанні цієї машини (воно замінило змінні середовища {poolVars} / {harnessVars}). Збереження змінює розмір теплого пулу наживо, без потреби в перезапуску; поточні запуски зберігають контейнер, який вони вже утримують.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.77.0",
3
+ "version": "0.78.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.83.0"
37
+ "@cat-factory/contracts": "0.84.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",