@cat-factory/app 0.77.0 → 0.79.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.
@@ -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>
@@ -18,6 +18,7 @@ import ArtifactLightbox from '~/components/media/ArtifactLightbox.vue'
18
18
  import StepRestartControl from '~/components/panels/StepRestartControl.vue'
19
19
  import StepRunMeta from '~/components/panels/StepRunMeta.vue'
20
20
  import StepContainerStatus from '~/components/panels/StepContainerStatus.vue'
21
+ import AttemptEntryHeader from '~/components/panels/AttemptEntryHeader.vue'
21
22
  import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
22
23
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
23
24
 
@@ -507,30 +508,18 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
507
508
  data-testid="tester-fixer-attempt"
508
509
  class="rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2"
509
510
  >
510
- <div class="flex items-center gap-2">
511
- <UIcon
512
- :name="a.outcome === 'completed' ? 'i-lucide-wrench' : 'i-lucide-circle-x'"
513
- class="h-3.5 w-3.5 shrink-0"
514
- :class="a.outcome === 'completed' ? 'text-amber-300' : 'text-rose-400'"
515
- />
516
- <span class="text-[13px] font-medium text-slate-200">
517
- {{ t('testing.fixerTimeline.attempt', { n: a.attempt }) }}
518
- </span>
519
- <UBadge
520
- :color="a.outcome === 'completed' ? 'neutral' : 'error'"
521
- variant="subtle"
522
- size="sm"
523
- >
524
- {{
525
- a.outcome === 'completed'
526
- ? t('testing.fixerTimeline.completed')
527
- : t('testing.fixerTimeline.failed')
528
- }}
529
- </UBadge>
530
- <span class="ms-auto text-[11px] text-slate-500">{{
531
- d(new Date(a.at), 'short')
532
- }}</span>
533
- </div>
511
+ <AttemptEntryHeader
512
+ :label="t('testing.fixerTimeline.attempt', { n: a.attempt })"
513
+ :outcome="a.outcome"
514
+ :outcome-label="
515
+ a.outcome === 'completed'
516
+ ? t('testing.fixerTimeline.completed')
517
+ : t('testing.fixerTimeline.failed')
518
+ "
519
+ :at="a.at"
520
+ :icon="a.outcome === 'completed' ? 'i-lucide-wrench' : 'i-lucide-circle-x'"
521
+ :icon-class="a.outcome === 'completed' ? 'text-amber-300' : 'text-rose-400'"
522
+ />
534
523
  <p v-if="a.summary" class="mt-1 text-[12px] leading-snug text-slate-400">
535
524
  {{ a.summary }}
536
525
  </p>
@@ -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.",
@@ -2945,6 +2981,8 @@
2945
2981
  },
2946
2982
  "attemptsHeading": "{helper} attempts",
2947
2983
  "attempt": "Attempt {number}",
2984
+ "attemptInstructions": "Handed to {helper}",
2985
+ "attemptReport": "{helper} report",
2948
2986
  "outcome": {
2949
2987
  "completed": "completed",
2950
2988
  "failed": "failed"