@cat-factory/app 0.228.0 → 0.229.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.
Files changed (34) hide show
  1. package/app/components/board/AddTaskModal.vue +11 -7
  2. package/app/components/board/RecurringPipelineModal.vue +11 -7
  3. package/app/components/bootstrap/BootstrapModal.vue +11 -7
  4. package/app/components/documents/RepoContextDocPicker.vue +4 -1
  5. package/app/components/fragments/FragmentLibraryManager.vue +19 -18
  6. package/app/components/gates/GateResultView.vue +3 -3
  7. package/app/components/github/AddServiceFromRepoModal.vue +10 -9
  8. package/app/components/panels/AgentStepDetail.vue +9 -7
  9. package/app/components/panels/InspectorPanel.vue +4 -4
  10. package/app/components/panels/inspector/ServiceTestConfig.vue +12 -11
  11. package/app/components/panels/inspector/TaskExecution.vue +7 -7
  12. package/app/components/pipeline/PipelineProgress.vue +5 -4
  13. package/app/components/providers/ApiKeysSection.vue +3 -1
  14. package/app/components/ralph/RalphLoopResultView.vue +3 -3
  15. package/app/components/settings/McpOAuthCallbackScreen.vue +103 -0
  16. package/app/components/settings/ToolServerChecklist.vue +111 -0
  17. package/app/components/visualConfirm/VisualConfirmationWindow.vue +14 -14
  18. package/app/composables/api/toolServers.ts +23 -1
  19. package/app/pages/index.vue +21 -18
  20. package/app/pages/mcp-oauth-callback.vue +7 -0
  21. package/app/stores/execution.ts +6 -6
  22. package/app/stores/toolServers.ts +57 -1
  23. package/app/types/toolServers.ts +2 -0
  24. package/i18n/locales/de.json +26 -1
  25. package/i18n/locales/en.json +26 -1
  26. package/i18n/locales/es.json +26 -1
  27. package/i18n/locales/fr.json +26 -1
  28. package/i18n/locales/he.json +26 -1
  29. package/i18n/locales/it.json +26 -1
  30. package/i18n/locales/ja.json +26 -1
  31. package/i18n/locales/pl.json +26 -1
  32. package/i18n/locales/tr.json +26 -1
  33. package/i18n/locales/uk.json +26 -1
  34. package/package.json +2 -2
@@ -40,6 +40,8 @@ const STATUS_LABELS = computed<Record<ToolServerProbeStatus, string>>(() => ({
40
40
  ok: t('settings.toolServers.status.ok'),
41
41
  credentials_missing: t('settings.toolServers.status.credentialsMissing'),
42
42
  credential_refused: t('settings.toolServers.status.credentialRefused'),
43
+ oauth_not_connected: t('settings.toolServers.status.oauthNotConnected'),
44
+ oauth_token_failed: t('settings.toolServers.status.oauthTokenFailed'),
43
45
  unreachable: t('settings.toolServers.status.unreachable'),
44
46
  http_error: t('settings.toolServers.status.httpError'),
45
47
  protocol_error: t('settings.toolServers.status.protocolError'),
@@ -53,6 +55,36 @@ const NOT_PROBEABLE_LABELS = computed<Record<ToolServerNotProbeableReason, strin
53
55
 
54
56
  const servers = computed<ToolServerView[]>(() => store.view?.servers ?? [])
55
57
 
58
+ /**
59
+ * Whether a row offers the Connect / Disconnect pair.
60
+ *
61
+ * Only the INTERACTIVE grant does. A `client_credentials` declaration authenticates as the
62
+ * deployment's own client and mints its token on the first dispatch that needs one, so there is
63
+ * nothing for a person to authorise and a button would promise an action that does not exist.
64
+ */
65
+ function isInteractive(server: ToolServerView): boolean {
66
+ return server.oauth?.grant === 'authorization_code'
67
+ }
68
+
69
+ async function connect(id: string) {
70
+ try {
71
+ await store.connectOAuth(id)
72
+ } catch (e) {
73
+ // Everything that can refuse does so BEFORE the browser leaves the app: an unconfigured
74
+ // redirect URL, a deployment with no grant store, an authorization server that publishes no
75
+ // metadata. Each carries a `details.reason` the shared funnel maps to translated copy.
76
+ present(e, 'settings.toolServers.toast.connectFailed')
77
+ }
78
+ }
79
+
80
+ async function disconnect(id: string) {
81
+ try {
82
+ await store.disconnectOAuth(id)
83
+ } catch (e) {
84
+ present(e, 'settings.toolServers.toast.disconnectFailed')
85
+ }
86
+ }
87
+
56
88
  function resultFor(id: string) {
57
89
  return store.results[id]
58
90
  }
@@ -138,6 +170,85 @@ async function runProbe(id: string) {
138
170
  }}
139
171
  </p>
140
172
 
173
+ <!-- OAuth. Absent for a server that authenticates with a static credential, so the Connect
174
+ affordance never appears on a row it does not apply to. `connected` and `lastError` are
175
+ rendered TOGETHER rather than as alternatives: a grant that is on file and no longer
176
+ producing tokens is exactly the state that reads as working and is not. -->
177
+ <div
178
+ v-if="server.oauth"
179
+ class="space-y-1 rounded-md border border-slate-800 bg-slate-900/40 p-2"
180
+ :data-testid="`tool-server-oauth-${server.id}`"
181
+ >
182
+ <div class="flex flex-wrap items-center gap-2">
183
+ <UBadge
184
+ :color="server.oauth.connected ? 'success' : 'neutral'"
185
+ variant="soft"
186
+ size="sm"
187
+ :data-testid="`tool-server-oauth-state-${server.id}`"
188
+ >
189
+ {{
190
+ server.oauth.connected
191
+ ? t('settings.toolServers.oauth.connected')
192
+ : isInteractive(server)
193
+ ? t('settings.toolServers.oauth.notConnected')
194
+ : t('settings.toolServers.oauth.machineGrant')
195
+ }}
196
+ </UBadge>
197
+ <span v-if="server.oauth.connectedBy" class="text-[11px] text-slate-400">
198
+ {{ t('settings.toolServers.oauth.connectedBy', { user: server.oauth.connectedBy }) }}
199
+ </span>
200
+ </div>
201
+
202
+ <p v-if="server.oauth.scopes?.length" class="text-[11px] text-slate-400">
203
+ {{ t('settings.toolServers.oauth.scopes', { scopes: server.oauth.scopes.join(', ') }) }}
204
+ </p>
205
+ <!-- A grant with no refresh token works until its access token expires and then needs
206
+ granting again by hand. Said BEFORE it happens, which is the only time it is useful. -->
207
+ <p
208
+ v-if="server.oauth.connected && server.oauth.refreshable === false"
209
+ class="text-[11px] text-amber-400"
210
+ >
211
+ {{ t('settings.toolServers.oauth.notRefreshable') }}
212
+ </p>
213
+ <p
214
+ v-if="server.oauth.lastError"
215
+ class="text-[11px] text-red-400"
216
+ :data-testid="`tool-server-oauth-error-${server.id}`"
217
+ >
218
+ {{ t('settings.toolServers.oauth.lastError', { detail: server.oauth.lastError }) }}
219
+ </p>
220
+
221
+ <div v-if="isInteractive(server)" class="flex flex-wrap items-center gap-2 pt-1">
222
+ <UButton
223
+ size="xs"
224
+ variant="subtle"
225
+ icon="i-lucide-link"
226
+ :loading="store.connecting === server.id"
227
+ :disabled="store.connecting !== null"
228
+ :data-testid="`tool-server-connect-${server.id}`"
229
+ @click="connect(server.id)"
230
+ >
231
+ {{
232
+ server.oauth.connected
233
+ ? t('settings.toolServers.oauth.reconnect')
234
+ : t('settings.toolServers.oauth.connect')
235
+ }}
236
+ </UButton>
237
+ <UButton
238
+ v-if="server.oauth.connected"
239
+ size="xs"
240
+ variant="ghost"
241
+ color="error"
242
+ :loading="store.connecting === server.id"
243
+ :disabled="store.connecting !== null"
244
+ :data-testid="`tool-server-disconnect-${server.id}`"
245
+ @click="disconnect(server.id)"
246
+ >
247
+ {{ t('settings.toolServers.oauth.disconnect') }}
248
+ </UButton>
249
+ </div>
250
+ </div>
251
+
141
252
  <div class="flex flex-wrap items-center gap-2 pt-1">
142
253
  <UButton
143
254
  v-if="server.probeable"
@@ -131,6 +131,20 @@ function buildFindings(): { text: string; structured: { view?: string; note: str
131
131
  return { text: blocks.join('\n\n'), structured }
132
132
  }
133
133
 
134
+ // Degraded-basis approval guard (no capture / a fix landed after these shots): require an
135
+ // explicit "I reviewed this another way" acknowledgement before the one-click approve.
136
+ const ackDegraded = ref(false)
137
+ watch(
138
+ () => vc.value?.degradedReason ?? null,
139
+ () => {
140
+ ackDegraded.value = false
141
+ },
142
+ )
143
+ const needsAck = computed(() => !!vc.value?.degradedReason)
144
+ const canApprove = computed(
145
+ () => awaitingHuman.value && !busy.value && (!needsAck.value || ackDegraded.value),
146
+ )
147
+
134
148
  async function approve() {
135
149
  if (!blockId.value || !canApprove.value) return
136
150
  await visualConfirm.approve(blockId.value)
@@ -169,20 +183,6 @@ async function onFilePicked(e: Event) {
169
183
  uploadView.value = ''
170
184
  if (fileInput.value) fileInput.value.value = ''
171
185
  }
172
-
173
- // Degraded-basis approval guard (no capture / a fix landed after these shots): require an
174
- // explicit "I reviewed this another way" acknowledgement before the one-click approve.
175
- const ackDegraded = ref(false)
176
- watch(
177
- () => vc.value?.degradedReason ?? null,
178
- () => {
179
- ackDegraded.value = false
180
- },
181
- )
182
- const needsAck = computed(() => !!vc.value?.degradedReason)
183
- const canApprove = computed(
184
- () => awaitingHuman.value && !busy.value && (!needsAck.value || ackDegraded.value),
185
- )
186
186
  </script>
187
187
 
188
188
  <template>
@@ -1,4 +1,10 @@
1
- import { listToolServersContract, probeToolServerContract } from '@cat-factory/contracts'
1
+ import {
2
+ completeToolServerOAuthContract,
3
+ disconnectToolServerOAuthContract,
4
+ listToolServersContract,
5
+ probeToolServerContract,
6
+ startToolServerOAuthContract,
7
+ } from '@cat-factory/contracts'
2
8
  import type { ApiContext } from './context'
3
9
 
4
10
  /**
@@ -17,5 +23,21 @@ export function toolServersApi({ send, ws }: ApiContext) {
17
23
 
18
24
  probeToolServer: (workspaceId: string, id: string) =>
19
25
  send(probeToolServerContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
26
+
27
+ // Begin an interactive OAuth grant: answers with the VENDOR's authorization URL for the
28
+ // operator's browser to follow, rather than redirecting, since a redirect from a `fetch` lands
29
+ // in a cross-origin document this app cannot observe.
30
+ startToolServerOAuth: (workspaceId: string, id: string) =>
31
+ send(startToolServerOAuthContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
32
+
33
+ disconnectToolServerOAuth: (workspaceId: string, id: string) =>
34
+ send(disconnectToolServerOAuthContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
35
+
36
+ // Finish a grant with what the vendor's redirect carried. NOT workspace-prefixed: the board is
37
+ // sealed into the `state`, so the caller does not know it and could not be trusted with it
38
+ // anyway. This is the request that makes the flow's session, user binding and permission
39
+ // re-check enforceable, which a vendor's redirect landing on the backend never could.
40
+ completeToolServerOAuth: (body: { code: string; state: string }) =>
41
+ send(completeToolServerOAuthContract, { body }),
20
42
  }
21
43
  }
@@ -285,6 +285,27 @@ watch(
285
285
  { immediate: true },
286
286
  )
287
287
 
288
+ // Probe the GitHub integration as soon as a board is active (re-probe per board —
289
+ // connections are per workspace). The result drives the onboarding gate in the template
290
+ // before the board mounts, so an unconnected user can't slip past it. `ensureProbed`
291
+ // single-flights per board (app-startup initiative, item 12), so this and the SideBar's
292
+ // probe collapse to one request on a cold open instead of two.
293
+ watch(
294
+ () => workspace.workspaceId,
295
+ (id) => {
296
+ if (id) void github.ensureProbed()
297
+ },
298
+ { immediate: true },
299
+ )
300
+
301
+ // Hard gate: the App is enabled on the backend but this workspace has no
302
+ // installation yet. `available === null` means the probe is still in flight.
303
+ // Both are declared here, ahead of the tutorial offer below, because that offer reads them
304
+ // from a watcher that runs synchronously during setup (`immediate: true`). Declared after
305
+ // it, they'd still be in their TDZ and the first run would throw.
306
+ const needsGitHubInstall = computed(() => github.available === true && !github.connected)
307
+ const githubProbePending = computed(() => github.available === null)
308
+
288
309
  // Offer the tutorial on launch, once the board is up. Yields to every other startup
289
310
  // surface — the GitHub onboarding gate and the advisory/onboarding modals above — so a
290
311
  // first launch never stacks the tour prompt on top of a dialog that needs answering
@@ -347,24 +368,6 @@ useTutorialNudge()
347
368
  // simply has nothing to talk to, and the browser-persisted store carries on alone.
348
369
  useTutorialSync()
349
370
 
350
- // Probe the GitHub integration as soon as a board is active (re-probe per board —
351
- // connections are per workspace). The result drives the onboarding gate below
352
- // before the board mounts, so an unconnected user can't slip past it. `ensureProbed`
353
- // single-flights per board (app-startup initiative, item 12), so this and the SideBar's
354
- // probe collapse to one request on a cold open instead of two.
355
- watch(
356
- () => workspace.workspaceId,
357
- (id) => {
358
- if (id) void github.ensureProbed()
359
- },
360
- { immediate: true },
361
- )
362
-
363
- // Hard gate: the App is enabled on the backend but this workspace has no
364
- // installation yet. `available === null` means the probe is still in flight.
365
- const needsGitHubInstall = computed(() => github.available === true && !github.connected)
366
- const githubProbePending = computed(() => github.available === null)
367
-
368
371
  // Subscribe to the backend's real-time event stream and (re)connect whenever the
369
372
  // active workspace changes. Runs advance durably server-side; progress arrives as
370
373
  // pushed events rather than by polling.
@@ -0,0 +1,7 @@
1
+ <script setup lang="ts">
2
+ import McpOAuthCallbackScreen from '~/components/settings/McpOAuthCallbackScreen.vue'
3
+ </script>
4
+
5
+ <template>
6
+ <McpOAuthCallbackScreen />
7
+ </template>
@@ -131,6 +131,12 @@ export const useExecutionStore = defineStore('execution', () => {
131
131
  } else instances.value.push(instance)
132
132
  }
133
133
 
134
+ const byId = computed(() => {
135
+ const map = new Map<string, ExecutionInstance>()
136
+ for (const e of instances.value) map.set(e.id, e)
137
+ return map
138
+ })
139
+
134
140
  /**
135
141
  * Run an action that returns a run's authoritative sub-state and apply that state to the cached
136
142
  * run as an OPTIMISTIC ECHO — but only when the event stream has not delivered a newer revision
@@ -169,12 +175,6 @@ export const useExecutionStore = defineStore('execution', () => {
169
175
  return state
170
176
  }
171
177
 
172
- const byId = computed(() => {
173
- const map = new Map<string, ExecutionInstance>()
174
- for (const e of instances.value) map.set(e.id, e)
175
- return map
176
- })
177
-
178
178
  function getInstance(id: string | null | undefined) {
179
179
  return id ? byId.value.get(id) : undefined
180
180
  }
@@ -25,6 +25,9 @@ export const useToolServersStore = defineStore('toolServers', () => {
25
25
  // operator just asked for.
26
26
  const results = ref<Record<string, ToolServerProbeResult>>({})
27
27
  const probing = ref<string | null>(null)
28
+ // The server whose OAuth grant is being connected or disconnected, so one row's button spins and
29
+ // the rest stay clickable — the same shape `probing` has, and for the same reason.
30
+ const connecting = ref<string | null>(null)
28
31
  const loading = ref(false)
29
32
  // The backend's two definitive refusals: no `secrets.manage` (403), and — unlike the credential
30
33
  // store — never a 503, since the inventory needs no encryption key to project a registry. `null`
@@ -108,5 +111,58 @@ export const useToolServersStore = defineStore('toolServers', () => {
108
111
  }
109
112
  }
110
113
 
111
- return { view, results, probing, loading, available, hasSurface, load, ensureLoaded, probe }
114
+ /**
115
+ * Start an OAuth grant and hand the browser to the vendor.
116
+ *
117
+ * A full-page navigation rather than a popup: the operator has to sign in at a third party, and a
118
+ * popup is what a browser blocks and a password manager cannot fill. Nothing is stored here — the
119
+ * vendor redirects back to `/mcp-oauth-callback`, which finishes the grant over the authenticated
120
+ * API and returns here, so the connection state comes from the row rather than from anything this
121
+ * store guessed across a navigation that leaves the app entirely.
122
+ */
123
+ async function connectOAuth(id: string) {
124
+ const ws = useWorkspaceStore()
125
+ connecting.value = id
126
+ try {
127
+ const { url } = await api.startToolServerOAuth(ws.requireId(), id)
128
+ window.location.href = url
129
+ } finally {
130
+ connecting.value = null
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Drop the workspace's grant, then re-read so the row's state comes from the backend rather than
136
+ * from an optimistic edit: a disconnect is the one action whose whole point is that the server
137
+ * stops being usable, and a row that looked connected for another second would be the misreport
138
+ * this panel exists to prevent.
139
+ */
140
+ async function disconnectOAuth(id: string) {
141
+ const ws = useWorkspaceStore()
142
+ connecting.value = id
143
+ try {
144
+ await api.disconnectToolServerOAuth(ws.requireId(), id)
145
+ // The stored probe result described a server this board could still reach. It cannot now.
146
+ const { [id]: _dropped, ...rest } = results.value
147
+ results.value = rest
148
+ } finally {
149
+ connecting.value = null
150
+ }
151
+ await load()
152
+ }
153
+
154
+ return {
155
+ view,
156
+ results,
157
+ probing,
158
+ connecting,
159
+ loading,
160
+ available,
161
+ hasSurface,
162
+ load,
163
+ ensureLoaded,
164
+ probe,
165
+ connectOAuth,
166
+ disconnectOAuth,
167
+ }
112
168
  })
@@ -9,6 +9,8 @@ export type {
9
9
  ToolServerAllowedToolsCheck,
10
10
  ToolServerCredential,
11
11
  ToolServerNotProbeableReason,
12
+ ToolServerOAuthGrant,
13
+ ToolServerOAuthStatus,
12
14
  ToolServerProbeResult,
13
15
  ToolServerProbeStatus,
14
16
  ToolServerTransport,
@@ -624,6 +624,27 @@
624
624
  "servableHarnessesNone": "Keine Agenten-CLI kann diesen Transport bedienen, daher greift dieser Server in keinem Lauf.",
625
625
  "allowedTools": "Eingeschränkt auf: {tools}",
626
626
  "credentials": "Zugangsdaten: {keys}",
627
+ "oauth": {
628
+ "connected": "Verbunden",
629
+ "notConnected": "Nicht verbunden",
630
+ "machineGrant": "Meldet sich als dieses Deployment an",
631
+ "connectedBy": "Verbunden von {user}",
632
+ "scopes": "Gewährt: {scopes}",
633
+ "notRefreshable": "Der Anbieter hat kein Refresh-Token ausgestellt, daher muss diese Verbindung neu hergestellt werden, sobald das Zugriffstoken abläuft.",
634
+ "lastError": "Die letzte Token-Erneuerung ist fehlgeschlagen: {detail}",
635
+ "connect": "Verbinden",
636
+ "reconnect": "Neu verbinden",
637
+ "disconnect": "Trennen",
638
+ "callback": {
639
+ "working": "Verbindung wird abgeschlossen …",
640
+ "done": "Mit {server} verbunden",
641
+ "doneHint": "Die Läufe dieses Boards können den Werkzeugserver jetzt mit dem Konto nutzen, mit dem Sie sich angemeldet haben.",
642
+ "back": "Zurück zur App",
643
+ "failedTitle": "Die Verbindung konnte nicht abgeschlossen werden",
644
+ "failed": "Die Autorisierung konnte nicht abgeschlossen werden. Starten Sie die Verbindung im Infrastruktur-Fenster erneut.",
645
+ "missingParams": "In diesem Link fehlen die vom Anbieter zurückgesendeten Werte, daher gibt es nichts abzuschließen. Starten Sie die Verbindung erneut."
646
+ }
647
+ },
627
648
  "test": "Testen",
628
649
  "notProbeable": {
629
650
  "stdio": "Läuft im Container des Agenten und kann von hier aus nicht getestet werden.",
@@ -634,6 +655,8 @@
634
655
  "ok": "Hat geantwortet",
635
656
  "credentialsMissing": "Keine Zugangsdaten",
636
657
  "credentialRefused": "Zugangsdaten abgelehnt",
658
+ "oauthNotConnected": "Nicht verbunden",
659
+ "oauthTokenFailed": "Verbindung funktioniert nicht mehr",
637
660
  "unreachable": "Keine Antwort",
638
661
  "httpError": "Anfrage abgewiesen",
639
662
  "protocolError": "Kein MCP-Server",
@@ -650,7 +673,9 @@
650
673
  "hideDetails": "Details verbergen",
651
674
  "toast": {
652
675
  "loadFailed": "Die Werkzeugserver konnten nicht geladen werden",
653
- "probeFailed": "Der Werkzeugserver konnte nicht getestet werden"
676
+ "probeFailed": "Der Werkzeugserver konnte nicht getestet werden",
677
+ "connectFailed": "Die Verbindung konnte nicht gestartet werden",
678
+ "disconnectFailed": "Der Werkzeugserver konnte nicht getrennt werden"
654
679
  }
655
680
  },
656
681
  "capabilityCredentials": {
@@ -3125,6 +3125,27 @@
3125
3125
  "servableHarnessesNone": "No agent CLI can serve this transport, so this server never applies to any run.",
3126
3126
  "allowedTools": "Narrowed to: {tools}",
3127
3127
  "credentials": "Credentials: {keys}",
3128
+ "oauth": {
3129
+ "connected": "Connected",
3130
+ "notConnected": "Not connected",
3131
+ "machineGrant": "Signs in as this deployment",
3132
+ "connectedBy": "Connected by {user}",
3133
+ "scopes": "Granted: {scopes}",
3134
+ "notRefreshable": "The vendor issued no refresh token, so this connection has to be made again once its access token expires.",
3135
+ "lastError": "The last token renewal failed: {detail}",
3136
+ "connect": "Connect",
3137
+ "reconnect": "Reconnect",
3138
+ "disconnect": "Disconnect",
3139
+ "callback": {
3140
+ "working": "Finishing the connection…",
3141
+ "done": "Connected to {server}",
3142
+ "doneHint": "This board's runs can now use the tool server as the account you signed in with.",
3143
+ "back": "Back to the app",
3144
+ "failedTitle": "The connection could not be finished",
3145
+ "failed": "The authorization could not be completed. Start the connection again from the Infrastructure window.",
3146
+ "missingParams": "This link is missing the values the vendor sends back, so there is nothing to complete. Start the connection again."
3147
+ }
3148
+ },
3128
3149
  "test": "Test",
3129
3150
  "notProbeable": {
3130
3151
  "stdio": "Runs inside the agent's container, so it cannot be tested from here.",
@@ -3135,6 +3156,8 @@
3135
3156
  "ok": "Answered",
3136
3157
  "credentialsMissing": "No credential",
3137
3158
  "credentialRefused": "Credential refused",
3159
+ "oauthNotConnected": "Not connected",
3160
+ "oauthTokenFailed": "Connection stopped working",
3138
3161
  "unreachable": "No answer",
3139
3162
  "httpError": "Rejected the request",
3140
3163
  "protocolError": "Not an MCP server",
@@ -3151,7 +3174,9 @@
3151
3174
  "hideDetails": "Hide details",
3152
3175
  "toast": {
3153
3176
  "loadFailed": "Could not load the tool servers",
3154
- "probeFailed": "Could not test the tool server"
3177
+ "probeFailed": "Could not test the tool server",
3178
+ "connectFailed": "Could not start the connection",
3179
+ "disconnectFailed": "Could not disconnect the tool server"
3155
3180
  }
3156
3181
  },
3157
3182
  "capabilityCredentials": {
@@ -2874,6 +2874,27 @@
2874
2874
  "servableHarnessesNone": "Ninguna CLI de agente puede servir este transporte, así que este servidor nunca se aplica a una ejecución.",
2875
2875
  "allowedTools": "Limitado a: {tools}",
2876
2876
  "credentials": "Credenciales: {keys}",
2877
+ "oauth": {
2878
+ "connected": "Conectado",
2879
+ "notConnected": "Sin conexión",
2880
+ "machineGrant": "Se identifica como este despliegue",
2881
+ "connectedBy": "Conectado por {user}",
2882
+ "scopes": "Concedido: {scopes}",
2883
+ "notRefreshable": "El proveedor no emitió un token de actualización, así que habrá que volver a conectar cuando caduque el token de acceso.",
2884
+ "lastError": "La última renovación del token falló: {detail}",
2885
+ "connect": "Conectar",
2886
+ "reconnect": "Volver a conectar",
2887
+ "disconnect": "Desconectar",
2888
+ "callback": {
2889
+ "working": "Finalizando la conexión…",
2890
+ "done": "Conectado a {server}",
2891
+ "doneHint": "Las ejecuciones de este tablero ya pueden usar el servidor de herramientas con la cuenta con la que iniciaste sesión.",
2892
+ "back": "Volver a la aplicación",
2893
+ "failedTitle": "No se pudo finalizar la conexión",
2894
+ "failed": "No se pudo completar la autorización. Vuelve a iniciar la conexión desde la ventana de Infraestructura.",
2895
+ "missingParams": "A este enlace le faltan los valores que devuelve el proveedor, así que no hay nada que completar. Vuelve a iniciar la conexión."
2896
+ }
2897
+ },
2877
2898
  "test": "Probar",
2878
2899
  "notProbeable": {
2879
2900
  "stdio": "Se ejecuta dentro del contenedor del agente, así que no puede probarse desde aquí.",
@@ -2884,6 +2905,8 @@
2884
2905
  "ok": "Respondió",
2885
2906
  "credentialsMissing": "Sin credencial",
2886
2907
  "credentialRefused": "Credencial rechazada",
2908
+ "oauthNotConnected": "Sin conexión",
2909
+ "oauthTokenFailed": "La conexión dejó de funcionar",
2887
2910
  "unreachable": "Sin respuesta",
2888
2911
  "httpError": "Rechazó la solicitud",
2889
2912
  "protocolError": "No es un servidor MCP",
@@ -2900,7 +2923,9 @@
2900
2923
  "hideDetails": "Ocultar detalles",
2901
2924
  "toast": {
2902
2925
  "loadFailed": "No se pudieron cargar los servidores de herramientas",
2903
- "probeFailed": "No se pudo probar el servidor de herramientas"
2926
+ "probeFailed": "No se pudo probar el servidor de herramientas",
2927
+ "connectFailed": "No se pudo iniciar la conexión",
2928
+ "disconnectFailed": "No se pudo desconectar el servidor de herramientas"
2904
2929
  }
2905
2930
  },
2906
2931
  "capabilityCredentials": {
@@ -2874,6 +2874,27 @@
2874
2874
  "servableHarnessesNone": "Aucune CLI d’agent ne peut servir ce transport, donc ce serveur ne s’applique à aucune exécution.",
2875
2875
  "allowedTools": "Restreint à : {tools}",
2876
2876
  "credentials": "Identifiants : {keys}",
2877
+ "oauth": {
2878
+ "connected": "Connecté",
2879
+ "notConnected": "Non connecté",
2880
+ "machineGrant": "S’authentifie en tant que ce déploiement",
2881
+ "connectedBy": "Connecté par {user}",
2882
+ "scopes": "Accordé : {scopes}",
2883
+ "notRefreshable": "Le fournisseur n’a pas émis de jeton de rafraîchissement : il faudra reconnecter dès que le jeton d’accès expirera.",
2884
+ "lastError": "Le dernier renouvellement du jeton a échoué : {detail}",
2885
+ "connect": "Connecter",
2886
+ "reconnect": "Reconnecter",
2887
+ "disconnect": "Déconnecter",
2888
+ "callback": {
2889
+ "working": "Finalisation de la connexion…",
2890
+ "done": "Connecté à {server}",
2891
+ "doneHint": "Les exécutions de ce tableau peuvent désormais utiliser le serveur d’outils avec le compte auquel vous vous êtes connecté.",
2892
+ "back": "Retour à l’application",
2893
+ "failedTitle": "La connexion n’a pas pu être finalisée",
2894
+ "failed": "L’autorisation n’a pas pu être menée à bien. Relancez la connexion depuis la fenêtre Infrastructure.",
2895
+ "missingParams": "Il manque à ce lien les valeurs renvoyées par le fournisseur, il n’y a donc rien à finaliser. Relancez la connexion."
2896
+ }
2897
+ },
2877
2898
  "test": "Tester",
2878
2899
  "notProbeable": {
2879
2900
  "stdio": "Il s’exécute dans le conteneur de l’agent et ne peut donc pas être testé d’ici.",
@@ -2884,6 +2905,8 @@
2884
2905
  "ok": "A répondu",
2885
2906
  "credentialsMissing": "Aucun identifiant",
2886
2907
  "credentialRefused": "Identifiant refusé",
2908
+ "oauthNotConnected": "Non connecté",
2909
+ "oauthTokenFailed": "La connexion ne fonctionne plus",
2887
2910
  "unreachable": "Aucune réponse",
2888
2911
  "httpError": "Requête rejetée",
2889
2912
  "protocolError": "Pas un serveur MCP",
@@ -2900,7 +2923,9 @@
2900
2923
  "hideDetails": "Masquer les détails",
2901
2924
  "toast": {
2902
2925
  "loadFailed": "Impossible de charger les serveurs d’outils",
2903
- "probeFailed": "Impossible de tester le serveur d’outils"
2926
+ "probeFailed": "Impossible de tester le serveur d’outils",
2927
+ "connectFailed": "Impossible de démarrer la connexion",
2928
+ "disconnectFailed": "Impossible de déconnecter le serveur d’outils"
2904
2929
  }
2905
2930
  },
2906
2931
  "capabilityCredentials": {
@@ -3015,6 +3015,27 @@
3015
3015
  "servableHarnessesNone": "אף CLI של סוכן אינו יכול לשרת תעבורה זו, ולכן שרת זה לא חל על שום הרצה.",
3016
3016
  "allowedTools": "מוגבל אל: {tools}",
3017
3017
  "credentials": "פרטי הזדהות: {keys}",
3018
+ "oauth": {
3019
+ "connected": "מחובר",
3020
+ "notConnected": "לא מחובר",
3021
+ "machineGrant": "מזדהה בשם פריסה זו",
3022
+ "connectedBy": "חובר על ידי {user}",
3023
+ "scopes": "ניתנו ההרשאות: {scopes}",
3024
+ "notRefreshable": "הספק לא הנפיק אסימון רענון, ולכן יש לחבר מחדש כאשר אסימון הגישה יפוג.",
3025
+ "lastError": "חידוש האסימון האחרון נכשל: {detail}",
3026
+ "connect": "התחברות",
3027
+ "reconnect": "התחברות מחדש",
3028
+ "disconnect": "ניתוק",
3029
+ "callback": {
3030
+ "working": "מסיים את החיבור…",
3031
+ "done": "מחובר אל {server}",
3032
+ "doneHint": "ההרצות של לוח זה יכולות מעכשיו להשתמש בשרת הכלים בחשבון שאיתו נכנסת.",
3033
+ "back": "חזרה לאפליקציה",
3034
+ "failedTitle": "לא ניתן היה לסיים את החיבור",
3035
+ "failed": "לא ניתן היה להשלים את ההרשאה. התחל את החיבור מחדש מחלון התשתית.",
3036
+ "missingParams": "בקישור הזה חסרים הערכים שהספק מחזיר, ולכן אין מה להשלים. התחל את החיבור מחדש."
3037
+ }
3038
+ },
3018
3039
  "test": "בדיקה",
3019
3040
  "notProbeable": {
3020
3041
  "stdio": "רץ בתוך המכולה של הסוכן, ולכן לא ניתן לבדוק אותו מכאן.",
@@ -3025,6 +3046,8 @@
3025
3046
  "ok": "השיב",
3026
3047
  "credentialsMissing": "אין פרטי הזדהות",
3027
3048
  "credentialRefused": "פרטי ההזדהות נדחו",
3049
+ "oauthNotConnected": "לא מחובר",
3050
+ "oauthTokenFailed": "החיבור הפסיק לעבוד",
3028
3051
  "unreachable": "אין תשובה",
3029
3052
  "httpError": "דחה את הבקשה",
3030
3053
  "protocolError": "אינו שרת MCP",
@@ -3041,7 +3064,9 @@
3041
3064
  "hideDetails": "הסתרת פרטים",
3042
3065
  "toast": {
3043
3066
  "loadFailed": "לא ניתן לטעון את שרתי הכלים",
3044
- "probeFailed": "לא ניתן לבדוק את שרת הכלים"
3067
+ "probeFailed": "לא ניתן לבדוק את שרת הכלים",
3068
+ "connectFailed": "לא ניתן להתחיל את החיבור",
3069
+ "disconnectFailed": "לא ניתן לנתק את שרת הכלים"
3045
3070
  }
3046
3071
  },
3047
3072
  "capabilityCredentials": {