@cat-factory/app 0.261.7 → 0.263.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.
@@ -57,6 +57,23 @@ function minterLabel(key: PublicApiKey): string | null {
57
57
  ? t('settings.apiTokens.list.createdByYou')
58
58
  : key.createdByUserId
59
59
  }
60
+
61
+ /**
62
+ * The badge for a key bound to a person's subscription, WHOSE person named.
63
+ *
64
+ * Keys are workspace-scoped and this list is shared, so a colleague's bound key is right here in
65
+ * everyone's panel: a fixed "your subscription" tells every other member that a token they never
66
+ * minted reaches theirs, which is alarming and false. The same comparison {@link minterLabel} makes
67
+ * is available (a binding is always to the minter), so the honest badge names the owner, and falls
68
+ * back to the `usr_*` id for the same reason that function does — there is no user-name lookup here,
69
+ * and an id is not misleading.
70
+ */
71
+ function boundLabel(key: PublicApiKey): string | null {
72
+ if (!key.actsAsUserId) return null
73
+ return key.actsAsUserId === auth.user?.id
74
+ ? t('settings.apiTokens.list.boundToYou')
75
+ : t('settings.apiTokens.list.boundToOther', { user: key.actsAsUserId })
76
+ }
60
77
  const toast = useToast()
61
78
  const { present } = usePipelineErrorToast()
62
79
  const { confirmAction, toastDone } = useConfirmAction()
@@ -70,6 +87,35 @@ const back = useIntegrationBack(open)
70
87
  const label = ref('')
71
88
  // The scope the next minted key will carry; defaults to the safe middle of the ladder.
72
89
  const scope = ref<PublicApiScope>('write')
90
+ // WHO the next key runs as. Two named options rather than an unchecked box, because they are two
91
+ // different credentials with two different blast radii and neither is a mere setting on the other:
92
+ //
93
+ // - `system` (the default): the token belongs to the workspace. Its runs are attributed to nobody
94
+ // and it can reach no personal subscription, so a leaked shared credential can never spend one
95
+ // person's Claude quota. This is what a CI job or a shared integration should hold.
96
+ // - `self`: the token belongs to the person minting it. Its runs are theirs and may unlock their
97
+ // personal Claude / Codex / GLM subscription, with the password sent on each such call.
98
+ //
99
+ // NOT gated on the interface mode, unlike an override field. This whole panel is the Integrations
100
+ // hub's Development surface, reached only by someone already minting an API key, and `scope` next
101
+ // to it is ungated for the same reason. Hiding it in basic mode would leave that person a key that
102
+ // silently cannot run their own models, with nothing on screen to say why.
103
+ type TokenIdentity = 'system' | 'self'
104
+ const identity = ref<TokenIdentity>('system')
105
+ // Nobody to bind on a board with no signed-in user (a dev-open deployment): the server refuses
106
+ // such a mint outright, so the choice is withheld and every key is a system key, which is what
107
+ // that deployment can honestly offer.
108
+ const canBindSelf = computed(() => auth.user !== null && auth.user !== undefined)
109
+ const identityItems = computed(() => [
110
+ { value: 'system' as const, label: t('settings.apiTokens.add.identitySystem') },
111
+ { value: 'self' as const, label: t('settings.apiTokens.add.identitySelf') },
112
+ ])
113
+ /** The help text under the picker, so each option explains ITSELF rather than only its opposite. */
114
+ const identityHelp = computed(() =>
115
+ identity.value === 'self'
116
+ ? t('settings.apiTokens.add.identitySelfHelp')
117
+ : t('settings.apiTokens.add.identitySystemHelp'),
118
+ )
73
119
  const busy = ref(false)
74
120
  // The full raw secret from the most recent create — surfaced once, then dismissed. Never
75
121
  // re-fetchable, so it lives only in this transient ref (not the store).
@@ -97,10 +143,15 @@ async function createToken() {
97
143
  if (!trimmed) return
98
144
  busy.value = true
99
145
  try {
100
- const created = await store.create(trimmed, scope.value)
146
+ const created = await store.create(
147
+ trimmed,
148
+ scope.value,
149
+ canBindSelf.value && identity.value === 'self',
150
+ )
101
151
  newSecret.value = created.secret
102
152
  label.value = ''
103
153
  scope.value = 'write'
154
+ identity.value = 'system'
104
155
  toast.add({
105
156
  title: t('settings.apiTokens.toast.created'),
106
157
  icon: 'i-lucide-check',
@@ -195,6 +246,18 @@ async function revokeToken(key: PublicApiKey) {
195
246
  >
196
247
  {{ scopeLabel(key.scope) }}
197
248
  </UBadge>
249
+ <!-- Always shown, never mode-gated: an existing key can already carry the
250
+ binding, and this is the only place its holder can see that a token they
251
+ are about to hand out reaches a personal subscription. -->
252
+ <UBadge
253
+ v-if="boundLabel(key)"
254
+ color="warning"
255
+ variant="subtle"
256
+ size="sm"
257
+ :data-testid="`api-token-bound-${key.id}`"
258
+ >
259
+ {{ boundLabel(key) }}
260
+ </UBadge>
198
261
  </div>
199
262
  <div class="text-[11px] text-slate-500">
200
263
  {{
@@ -255,6 +318,18 @@ async function revokeToken(key: PublicApiKey) {
255
318
  data-testid="api-token-scope"
256
319
  />
257
320
  </UFormField>
321
+ <UFormField
322
+ v-if="canBindSelf"
323
+ :label="t('settings.apiTokens.add.identity')"
324
+ :help="identityHelp"
325
+ >
326
+ <USelect
327
+ v-model="identity"
328
+ :items="identityItems"
329
+ class="w-full"
330
+ data-testid="api-token-identity"
331
+ />
332
+ </UFormField>
258
333
  <UButton
259
334
  :loading="busy"
260
335
  :disabled="!label.trim()"
@@ -6,9 +6,12 @@
6
6
  // serves and tick which to enable. Save persists the endpoint; the enabled models then surface
7
7
  // automatically in the per-workspace model picker. One endpoint per runner type.
8
8
  import { computed, ref, watch } from 'vue'
9
+
9
10
  import {
11
+ knownLocalModel,
10
12
  LOCAL_RUNNER_DEFAULTS,
11
13
  LOCAL_RUNNER_LABELS,
14
+ type LocalModelDeclaration,
12
15
  type LocalRunner,
13
16
  type LocalRunnerUrlReason,
14
17
  } from '~/types/localModels'
@@ -28,16 +31,6 @@ const open = computed({
28
31
  })
29
32
  const back = useIntegrationBack(open)
30
33
 
31
- // Load the user's endpoints whenever the panel opens (loaded independently of the
32
- // workspace snapshot, like personal subscriptions).
33
- watch(
34
- open,
35
- (isOpen) => {
36
- if (isOpen) void store.load()
37
- },
38
- { immediate: true },
39
- )
40
-
41
34
  const RUNNERS: { value: LocalRunner; label: string }[] = (
42
35
  Object.keys(LOCAL_RUNNER_LABELS) as LocalRunner[]
43
36
  ).map((value) => ({ value, label: LOCAL_RUNNER_LABELS[value] }))
@@ -59,14 +52,76 @@ function urlReasonText(reason: LocalRunnerUrlReason): string {
59
52
  return t(URL_REASON_KEYS[reason])
60
53
  }
61
54
 
55
+ // Whether an enabled model reads IMAGES. Three states, because the runner's `/models` probe cannot
56
+ // tell us and "nobody has said" is not the same answer as "no": undeclared says the platform never
57
+ // asked, while `no` says the model cannot. Mirrors `LocalModelDeclaration.acceptsImages`.
58
+ //
59
+ // For a RECOGNISED family the platform already knows, so leaving this alone is the right answer and
60
+ // the "not set" option says which way that falls: the control is the ESCAPE HATCH for a build the
61
+ // table cannot know about (a text-only quant, a fine-tune, a re-tagged copy), not a step everyone
62
+ // has to take.
63
+ const IMAGE_INPUT_CHOICES = ['unknown', 'yes', 'no'] as const
64
+ type ImageInputChoice = (typeof IMAGE_INPUT_CHOICES)[number]
65
+
66
+ function choiceFor(declared: LocalModelDeclaration): ImageInputChoice {
67
+ return declared.acceptsImages === undefined ? 'unknown' : declared.acceptsImages ? 'yes' : 'no'
68
+ }
69
+
70
+ /** The declared modality for a choice, as a spread-ready slice (undeclared adds no key at all). */
71
+ function modalityOf(choice: ImageInputChoice | undefined): { acceptsImages?: boolean } {
72
+ if (choice === 'yes') return { acceptsImages: true }
73
+ if (choice === 'no') return { acceptsImages: false }
74
+ return {}
75
+ }
76
+
77
+ /**
78
+ * What "not set" will actually do for one model id: name the recognised family and the modality it
79
+ * implies, else say plainly that nothing has been said. Read from the SAME table the engine folds
80
+ * onto the dispatched ref, so this label cannot promise a picture the run then withholds.
81
+ */
82
+ function unsetLabelFor(modelId: string): string {
83
+ const known = knownLocalModel(modelId)
84
+ if (!known) return t('settings.localModelEndpoints.imageInput.unknown')
85
+ return t(
86
+ known.acceptsImages
87
+ ? 'settings.localModelEndpoints.imageInput.autoYes'
88
+ : 'settings.localModelEndpoints.imageInput.autoNo',
89
+ { family: known.label },
90
+ )
91
+ }
92
+
62
93
  // ---- add / edit draft ------------------------------------------------------
63
94
  const provider = ref<LocalRunner>('ollama')
64
95
  const label = ref('')
65
96
  const baseUrl = ref(LOCAL_RUNNER_DEFAULTS.ollama ?? '')
66
97
  const apiKey = ref('')
67
- // The models discovered by the last "Test connection", plus the user's tick selection.
98
+ // The models discovered by the last "Test connection", plus the user's tick selection and what
99
+ // they declared about each ticked one (kept per model id, so un-ticking and re-ticking a model
100
+ // does not silently drop the declaration they already made for it).
68
101
  const discovered = ref<string[]>([])
69
102
  const selected = ref<string[]>([])
103
+ const imageInput = ref<Record<string, ImageInputChoice>>({})
104
+
105
+ /**
106
+ * The three options per discovered model: "not set" carries what the recognised-family table will
107
+ * do. Built once per discovered set rather than per row per render, because the "not set" label
108
+ * scans the family table and a fresh array identity each tick also defeats the select's own
109
+ * memoisation (a runner serving forty models re-ran both on every keystroke elsewhere in the form).
110
+ */
111
+ const imageInputItems = computed<Record<string, { value: ImageInputChoice; label: string }[]>>(() =>
112
+ Object.fromEntries(
113
+ discovered.value.map((modelId) => [
114
+ modelId,
115
+ IMAGE_INPUT_CHOICES.map((value) => ({
116
+ value,
117
+ label:
118
+ value === 'unknown'
119
+ ? unsetLabelFor(modelId)
120
+ : t(`settings.localModelEndpoints.imageInput.${value}`),
121
+ })),
122
+ ]),
123
+ ),
124
+ )
70
125
  const testError = ref<string | null>(null)
71
126
  // The backend's own wording, kept as DETAIL beside a translated refusal rather than being
72
127
  // shown as the description (it names env vars an operator, not this user, acts on).
@@ -77,25 +132,48 @@ const busy = ref(false)
77
132
 
78
133
  const existing = computed(() => store.endpoints.find((e) => e.provider === provider.value))
79
134
 
80
- // Switching runner type prefills the default base URL and resets the discovered models —
81
- // editing an already-connected runner loads its stored config instead.
82
- watch(provider, (p) => {
135
+ /**
136
+ * Point the draft at one runner: its stored config when that runner is already connected (so the
137
+ * ticks and the declarations the user made come back), else the defaults for a fresh one.
138
+ *
139
+ * Called EXPLICITLY from each event that means "start editing this runner", never watched off
140
+ * `provider`, because the commonest of those events does not change it: clicking Edit on the row
141
+ * the form is already showing assigns the same value, which fires no watcher. The draft would then
142
+ * be whatever the empty initial state was, and saving it PUTs every model with no declaration,
143
+ * destroying what the user had recorded with nothing saying so.
144
+ */
145
+ function seedDraft(p: LocalRunner) {
83
146
  const e = store.endpoints.find((x) => x.provider === p)
84
- if (e) {
85
- label.value = e.label
86
- baseUrl.value = e.baseUrl
87
- discovered.value = [...e.models]
88
- selected.value = [...e.models]
89
- } else {
90
- label.value = ''
91
- baseUrl.value = LOCAL_RUNNER_DEFAULTS[p] ?? ''
92
- discovered.value = []
93
- selected.value = []
94
- }
147
+ label.value = e?.label ?? ''
148
+ baseUrl.value = e?.baseUrl ?? LOCAL_RUNNER_DEFAULTS[p] ?? ''
149
+ discovered.value = e?.models.map((m) => m.id) ?? []
150
+ selected.value = e?.models.map((m) => m.id) ?? []
151
+ imageInput.value = Object.fromEntries(e?.models.map((m) => [m.id, choiceFor(m)]) ?? [])
95
152
  apiKey.value = ''
96
153
  testError.value = null
154
+ testErrorDetail.value = null
97
155
  tested.value = false
98
- })
156
+ }
157
+
158
+ /** Select a runner in the form: the runner-type select and each row's Edit button share this. */
159
+ function selectRunner(p: LocalRunner) {
160
+ provider.value = p
161
+ seedDraft(p)
162
+ }
163
+
164
+ // Load the user's endpoints whenever the panel opens (loaded independently of the workspace
165
+ // snapshot, like personal subscriptions), then seed the draft from what arrived. The seed WAITS
166
+ // for the load: the panel mounts against an empty store, so seeding before it resolves would
167
+ // leave a form headed "Edit runner" holding none of that runner's config.
168
+ watch(
169
+ open,
170
+ async (isOpen) => {
171
+ if (!isOpen) return
172
+ await store.load()
173
+ seedDraft(provider.value)
174
+ },
175
+ { immediate: true },
176
+ )
99
177
 
100
178
  async function test() {
101
179
  if (!baseUrl.value.trim()) return
@@ -147,7 +225,7 @@ async function save() {
147
225
  label: label.value.trim() || undefined,
148
226
  baseUrl: baseUrl.value.trim(),
149
227
  apiKey: apiKey.value.trim() || undefined,
150
- models: selected.value,
228
+ models: selected.value.map((id) => ({ id, ...modalityOf(imageInput.value[id]) })),
151
229
  })
152
230
  apiKey.value = ''
153
231
  toast.add({
@@ -178,13 +256,9 @@ async function remove(p: LocalRunner) {
178
256
  busy.value = true
179
257
  try {
180
258
  await store.remove(p)
181
- if (provider.value === p) {
182
- baseUrl.value = LOCAL_RUNNER_DEFAULTS[p] ?? ''
183
- label.value = ''
184
- discovered.value = []
185
- selected.value = []
186
- tested.value = false
187
- }
259
+ // The row is gone from the store, so re-seeding the draft it was showing yields the
260
+ // fresh-runner defaults: the same reset, without a second copy of what a reset means.
261
+ if (provider.value === p) seedDraft(p)
188
262
  toast.add({ title: t('settings.localModelEndpoints.toast.removed'), icon: 'i-lucide-check' })
189
263
  } catch (e) {
190
264
  present(e, 'settings.localModelEndpoints.toast.removeFailed')
@@ -246,6 +320,12 @@ async function remove(p: LocalRunner) {
246
320
  {{ t('settings.localModelEndpoints.blocked') }}
247
321
  <span class="block text-amber-300/70">{{ urlReasonText(e.urlBlockedReason) }}</span>
248
322
  </div>
323
+ <!-- Part of the stored model list could not be read and was discarded. Without this
324
+ the shortened list reads exactly like a runner nothing was ever enabled on, and
325
+ only one of those is fixed by re-ticking. -->
326
+ <div v-if="e.unreadableModels" class="mt-1 text-[11px] text-amber-400">
327
+ {{ t('settings.localModelEndpoints.modelsDiscarded') }}
328
+ </div>
249
329
  </div>
250
330
  <div class="flex items-center gap-1">
251
331
  <UButton
@@ -255,11 +335,7 @@ async function remove(p: LocalRunner) {
255
335
  size="xs"
256
336
  :disabled="busy"
257
337
  :title="t('settings.localModelEndpoints.edit')"
258
- @click="
259
- () => {
260
- provider = e.provider
261
- }
262
- "
338
+ @click="selectRunner(e.provider)"
263
339
  />
264
340
  <UButton
265
341
  icon="i-lucide-trash-2"
@@ -284,7 +360,13 @@ async function remove(p: LocalRunner) {
284
360
 
285
361
  <div class="flex flex-wrap items-end gap-3">
286
362
  <UFormField :label="t('settings.localModelEndpoints.runnerType')">
287
- <USelect v-model="provider" :items="RUNNERS" value-key="value" class="w-48" />
363
+ <USelect
364
+ :model-value="provider"
365
+ :items="RUNNERS"
366
+ value-key="value"
367
+ class="w-48"
368
+ @update:model-value="(v: string) => selectRunner(v as LocalRunner)"
369
+ />
288
370
  </UFormField>
289
371
  <UFormField
290
372
  :label="t('settings.localModelEndpoints.labelOptional')"
@@ -349,23 +431,38 @@ async function remove(p: LocalRunner) {
349
431
  }}</span>
350
432
  </div>
351
433
 
352
- <!-- discovered models multi-select -->
434
+ <!-- discovered models multi-select, each with its declared image support -->
353
435
  <div v-if="discovered.length" class="space-y-1.5">
354
436
  <span class="block text-[10px] uppercase tracking-wide text-slate-500">
355
437
  {{ t('settings.localModelEndpoints.enableModels') }}
356
438
  </span>
357
- <div class="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
358
- <label
359
- v-for="m in discovered"
360
- :key="m"
361
- class="flex items-center gap-2 text-sm text-slate-300"
362
- >
363
- <UCheckbox
364
- :model-value="selected.includes(m)"
365
- @update:model-value="(v: boolean | 'indeterminate') => toggleModel(m, v === true)"
439
+ <p class="text-[11px] text-slate-500">
440
+ {{ t('settings.localModelEndpoints.imageInputHint') }}
441
+ </p>
442
+ <div class="space-y-1.5">
443
+ <div v-for="m in discovered" :key="m" class="flex items-center gap-2">
444
+ <label class="flex min-w-0 flex-1 items-center gap-2 text-sm text-slate-300">
445
+ <UCheckbox
446
+ :model-value="selected.includes(m)"
447
+ @update:model-value="
448
+ (v: boolean | 'indeterminate') => toggleModel(m, v === true)
449
+ "
450
+ />
451
+ <span class="truncate font-mono text-xs">{{ m }}</span>
452
+ </label>
453
+ <!-- Shown only for a model that is actually enabled: declaring a modality for one
454
+ nothing can run would be a setting with no effect. -->
455
+ <USelect
456
+ v-if="selected.includes(m)"
457
+ :model-value="imageInput[m] ?? 'unknown'"
458
+ :items="imageInputItems[m]"
459
+ value-key="value"
460
+ size="xs"
461
+ class="w-52 shrink-0"
462
+ :aria-label="t('settings.localModelEndpoints.imageInputLabel', { model: m })"
463
+ @update:model-value="(v: string) => (imageInput[m] = v as ImageInputChoice)"
366
464
  />
367
- <span class="truncate font-mono text-xs">{{ m }}</span>
368
- </label>
465
+ </div>
369
466
  </div>
370
467
  </div>
371
468
 
@@ -14,6 +14,7 @@ function key(over: Partial<PublicApiKey> = {}): PublicApiKey {
14
14
  createdByUserId: null,
15
15
  createdByKeyId: null,
16
16
  externalIdentity: null,
17
+ actsAsUserId: null,
17
18
  createdAt: 1,
18
19
  lastUsedAt: null,
19
20
  revokedAt: null,
@@ -94,6 +95,21 @@ describe('publicApiKeys store', () => {
94
95
  expect(store.available).toBe(true)
95
96
  })
96
97
 
98
+ it('mints a SYSTEM token unless the caller asks to be bound', async () => {
99
+ // The default decides whose subscription an unattended run may spend, so it is worth an
100
+ // assertion rather than a reading of the signature: a key that silently acted as its minter
101
+ // would put one person's Claude quota behind every integration the workspace hands a token to.
102
+ const body = vi.fn((_body: unknown) => Promise.resolve({ key: key(), secret: 's' }))
103
+ vi.stubGlobal('useApi', () => ({ createPublicApiKey: (_ws: string, b: unknown) => body(b) }))
104
+
105
+ const store = usePublicApiKeysStore()
106
+ await store.create('ci', 'write')
107
+ expect(body).toHaveBeenCalledWith({ label: 'ci', scope: 'write', actsAsSelf: false })
108
+
109
+ await store.create('mine', 'write', true)
110
+ expect(body).toHaveBeenCalledWith({ label: 'mine', scope: 'write', actsAsSelf: true })
111
+ })
112
+
97
113
  it('revoke drops the key from the list', async () => {
98
114
  vi.stubGlobal('useApi', () => ({
99
115
  listPublicApiKeys: () => Promise.resolve({ keys: [key({ id: 'a' }), key({ id: 'b' })] }),
@@ -54,10 +54,18 @@ export const usePublicApiKeysStore = defineStore('publicApiKeys', () => {
54
54
  /**
55
55
  * Mint a key with a permission `scope` (read ⊂ write ⊂ admin). Returns the created record
56
56
  * PLUS the one-time raw secret (shown once).
57
+ *
58
+ * `actsAsSelf` binds the key to the signed-in user's PERSONAL subscriptions, so a headless run
59
+ * it starts can unlock them with the password sent on that call. Passed through rather than
60
+ * defaulted here: the server writes the id off the session, so this is only ever a yes/no.
57
61
  */
58
- async function create(label: string, scope: PublicApiScope): Promise<CreatedPublicApiKey> {
62
+ async function create(
63
+ label: string,
64
+ scope: PublicApiScope,
65
+ actsAsSelf = false,
66
+ ): Promise<CreatedPublicApiKey> {
59
67
  const ws = useWorkspaceStore()
60
- const created = await api.createPublicApiKey(ws.requireId(), { label, scope })
68
+ const created = await api.createPublicApiKey(ws.requireId(), { label, scope, actsAsSelf })
61
69
  // Prepend: the backend lists newest-first, so the freshly minted key belongs at the
62
70
  // top — matching the order a subsequent `load()` would produce.
63
71
  keys.value = [created.key, ...keys.value]
@@ -13,6 +13,7 @@
13
13
 
14
14
  export type {
15
15
  LocalRunner,
16
+ LocalModelDeclaration,
16
17
  LocalModelEndpoint,
17
18
  LocalRunnerUrlReason,
18
19
  UpsertLocalModelEndpointInput,
@@ -22,3 +23,9 @@ export type {
22
23
 
23
24
  // Value re-exports (the per-runner default base URL + display labels).
24
25
  export { LOCAL_RUNNER_DEFAULTS, LOCAL_RUNNER_LABELS } from '@cat-factory/contracts'
26
+
27
+ // What the platform already KNOWS about the popular local model families. The panel reads the same
28
+ // table the engine folds onto a dispatched ref, so the "not set" option can state what will happen
29
+ // instead of implying nothing will.
30
+ export { knownLocalModel } from '@cat-factory/contracts'
31
+ export type { KnownLocalModel } from '@cat-factory/contracts'
@@ -793,7 +793,9 @@
793
793
  "createdBy": "erstellt von {user}",
794
794
  "createdByYou": "Ihnen",
795
795
  "createdByKey": "API-Schlüssel {id}",
796
- "revoke": "Token widerrufen"
796
+ "revoke": "Token widerrufen",
797
+ "boundToYou": "Ihr Abonnement",
798
+ "boundToOther": "Abonnement von {user}"
797
799
  },
798
800
  "add": {
799
801
  "heading": "Token erstellen",
@@ -802,7 +804,12 @@
802
804
  "labelPlaceholder": "z. B. CI-Pipeline",
803
805
  "scope": "Berechtigung",
804
806
  "scopeHelp": "Was dieses Token darf: nur Lesen, Lesen und Schreiben oder Vollzugriff (der auch das Löschen erlaubt).",
805
- "create": "Token erstellen"
807
+ "create": "Token erstellen",
808
+ "identity": "Läuft als",
809
+ "identitySystem": "Dieser Arbeitsbereich (System-Token)",
810
+ "identitySelf": "Ich (persönliches Token)",
811
+ "identitySystemHelp": "Mit diesem Token gestartete Läufe gehören dem Arbeitsbereich und werden keiner Person zugeordnet. Es kann kein persönliches Claude-/Codex-/GLM-Abonnement nutzen: Eine Aufgabe mit einem solchen Modell wird abgelehnt, statt jemandem in Abwesenheit berechnet zu werden. Für CI und gemeinsam genutzte Integrationen.",
812
+ "identitySelfHelp": "Mit diesem Token gestartete Läufe gelten als Ihre und können Ihr persönliches Claude-/Codex-/GLM-Abonnement nutzen. Jeder solche Aufruf muss zusätzlich Ihr persönliches Passwort im Header X-Personal-Password senden; es wird nie gespeichert. Für Ihre eigenen Headless-Läufe."
806
813
  },
807
814
  "scopes": {
808
815
  "read": "Nur Lesen",
@@ -1133,6 +1140,15 @@
1133
1140
  "reachable": "Erreichbar · keine Modelle | Erreichbar · {count} Modell | Erreichbar · {count} Modelle",
1134
1141
  "noModels": "Keine Modelle gemeldet.",
1135
1142
  "enableModels": "Modelle aktivieren",
1143
+ "imageInputHint": "Manche lokalen Modelle lesen Screenshots und Design-Renderings. Bekannte Modellfamilien werden automatisch erkannt, und die Option Nicht gesetzt zeigt pro Modell, wie es behandelt wird; setzen Sie den Wert selbst für einen Build, den die Plattform nicht kennen kann, etwa eine reine Text-Quantisierung oder ein Fine-Tuning.",
1144
+ "imageInputLabel": "Bildunterstützung für {model}",
1145
+ "imageInput": {
1146
+ "autoYes": "Nicht gesetzt: {family} liest Bilder",
1147
+ "autoNo": "Nicht gesetzt: {family} ist nur Text",
1148
+ "unknown": "Bilder: nicht gesetzt",
1149
+ "yes": "Liest Bilder",
1150
+ "no": "Nur Text"
1151
+ },
1136
1152
  "toast": {
1137
1153
  "saved": "{name} gespeichert",
1138
1154
  "saveFailed": "Runner konnte nicht gespeichert werden",
@@ -1144,6 +1160,7 @@
1144
1160
  "body": "\"{name}\" wird entfernt. Dies kann nicht rückgängig gemacht werden."
1145
1161
  },
1146
1162
  "blocked": "Die URL dieses Runners ist auf dieser Installation nicht erlaubt, daher sind seine Modelle in der Auswahl ausgeblendet.",
1163
+ "modelsDiscarded": "Einige der aktivierten Modelle dieses Runners konnten nicht gelesen werden und wurden verworfen. Wählen Sie die gewünschten erneut aus und speichern Sie.",
1147
1164
  "urlReason": {
1148
1165
  "invalid_url": "Das ist keine gültige URL.",
1149
1166
  "scheme_not_allowed": "Eine Runner-URL muss mit http:// oder https:// beginnen.",
@@ -3527,7 +3527,9 @@
3527
3527
  "createdBy": "created by {user}",
3528
3528
  "createdByYou": "you",
3529
3529
  "createdByKey": "API key {id}",
3530
- "revoke": "Revoke token"
3530
+ "revoke": "Revoke token",
3531
+ "boundToYou": "Your subscription",
3532
+ "boundToOther": "{user}'s subscription"
3531
3533
  },
3532
3534
  "add": {
3533
3535
  "heading": "Create a token",
@@ -3536,7 +3538,12 @@
3536
3538
  "labelPlaceholder": "e.g. CI pipeline",
3537
3539
  "scope": "Scope",
3538
3540
  "scopeHelp": "What this token can do: read-only, read and write, or full access (which also allows deleting).",
3539
- "create": "Create token"
3541
+ "create": "Create token",
3542
+ "identity": "Runs as",
3543
+ "identitySystem": "This workspace (system token)",
3544
+ "identitySelf": "Me (personal token)",
3545
+ "identitySystemHelp": "Runs started with this token belong to the workspace and are attributed to no person. It cannot use anyone’s personal Claude / Codex / GLM subscription, so a task pinned to one of those models is refused rather than charged to someone who is not there. Use this for CI and shared integrations.",
3546
+ "identitySelfHelp": "Runs started with this token count as yours and can use your personal Claude / Codex / GLM subscription. Every such call must also send your personal password in the X-Personal-Password header; it is never stored. Use this to drive your own headless runs."
3540
3547
  },
3541
3548
  "scopes": {
3542
3549
  "read": "Read only",
@@ -3870,6 +3877,15 @@
3870
3877
  "reachable": "Reachable · no models | Reachable · {count} model | Reachable · {count} models",
3871
3878
  "noModels": "No models reported.",
3872
3879
  "enableModels": "Enable models",
3880
+ "imageInputHint": "Some local models read screenshots and design renders. Well-known families are recognised automatically, and the not-set option on each model says which way it falls; set it yourself for a build the platform cannot know about, such as a text-only quant or a fine-tune.",
3881
+ "imageInputLabel": "Image support for {model}",
3882
+ "imageInput": {
3883
+ "autoYes": "Not set: {family} reads images",
3884
+ "autoNo": "Not set: {family} is text only",
3885
+ "unknown": "Images: not set",
3886
+ "yes": "Reads images",
3887
+ "no": "Text only"
3888
+ },
3873
3889
  "toast": {
3874
3890
  "saved": "{name} saved",
3875
3891
  "saveFailed": "Could not save runner",
@@ -3881,6 +3897,7 @@
3881
3897
  "body": "\"{name}\" will be removed. This can't be undone."
3882
3898
  },
3883
3899
  "blocked": "This runner's URL is not allowed on this deployment, so its models are hidden from the picker.",
3900
+ "modelsDiscarded": "Some of this runner's enabled models could not be read and were discarded. Re-select the ones you want and save.",
3884
3901
  "urlReason": {
3885
3902
  "invalid_url": "That is not a valid URL.",
3886
3903
  "scheme_not_allowed": "A runner URL must start with http:// or https://.",
@@ -3248,7 +3248,9 @@
3248
3248
  "createdBy": "creado por {user}",
3249
3249
  "createdByYou": "ti",
3250
3250
  "createdByKey": "la clave de API {id}",
3251
- "revoke": "Revocar token"
3251
+ "revoke": "Revocar token",
3252
+ "boundToYou": "Tu suscripción",
3253
+ "boundToOther": "Suscripción de {user}"
3252
3254
  },
3253
3255
  "add": {
3254
3256
  "heading": "Crear un token",
@@ -3257,7 +3259,12 @@
3257
3259
  "labelPlaceholder": "p. ej. pipeline de CI",
3258
3260
  "scope": "Alcance",
3259
3261
  "scopeHelp": "Lo que puede hacer este token: solo lectura, lectura y escritura o acceso completo (que también permite eliminar).",
3260
- "create": "Crear token"
3262
+ "create": "Crear token",
3263
+ "identity": "Se ejecuta como",
3264
+ "identitySystem": "Este espacio de trabajo (token de sistema)",
3265
+ "identitySelf": "Yo (token personal)",
3266
+ "identitySystemHelp": "Las ejecuciones iniciadas con este token pertenecen al espacio de trabajo y no se atribuyen a ninguna persona. No puede usar la suscripción personal de Claude / Codex / GLM de nadie, así que una tarea fijada a uno de esos modelos se rechaza en lugar de cargarse a alguien ausente. Úsalo para CI e integraciones compartidas.",
3267
+ "identitySelfHelp": "Las ejecuciones iniciadas con este token se te atribuyen y pueden usar tu suscripción personal de Claude / Codex / GLM. Cada llamada debe enviar además tu contraseña personal en la cabecera X-Personal-Password; nunca se almacena. Úsalo para tus propias ejecuciones headless."
3261
3268
  },
3262
3269
  "scopes": {
3263
3270
  "read": "Solo lectura",
@@ -3588,6 +3595,15 @@
3588
3595
  "reachable": "Accesible · ningún modelo | Accesible · {count} modelo | Accesible · {count} modelos",
3589
3596
  "noModels": "No se informó de ningún modelo.",
3590
3597
  "enableModels": "Habilitar modelos",
3598
+ "imageInputHint": "Algunos modelos locales leen capturas de pantalla y renders de diseño. Las familias conocidas se reconocen automáticamente, y la opción Sin definir de cada modelo indica cómo se tratará; defínelo tú mismo para una compilación que la plataforma no puede conocer, como una cuantización solo de texto o un ajuste fino.",
3599
+ "imageInputLabel": "Compatibilidad con imágenes de {model}",
3600
+ "imageInput": {
3601
+ "autoYes": "Sin definir: {family} lee imágenes",
3602
+ "autoNo": "Sin definir: {family} es solo texto",
3603
+ "unknown": "Imágenes: sin definir",
3604
+ "yes": "Lee imágenes",
3605
+ "no": "Solo texto"
3606
+ },
3591
3607
  "toast": {
3592
3608
  "saved": "{name} guardado",
3593
3609
  "saveFailed": "No se pudo guardar el runner",
@@ -3599,6 +3615,7 @@
3599
3615
  "body": "Se eliminará \"{name}\". Esta acción no se puede deshacer."
3600
3616
  },
3601
3617
  "blocked": "La URL de este runner no está permitida en esta instalación, por lo que sus modelos están ocultos en el selector.",
3618
+ "modelsDiscarded": "Algunos de los modelos habilitados de este runner no se pudieron leer y se descartaron. Vuelve a seleccionar los que quieras y guarda.",
3602
3619
  "urlReason": {
3603
3620
  "invalid_url": "Esa URL no es válida.",
3604
3621
  "scheme_not_allowed": "La URL de un runner debe empezar por http:// o https://.",
@@ -3248,7 +3248,9 @@
3248
3248
  "createdBy": "créé par {user}",
3249
3249
  "createdByYou": "vous",
3250
3250
  "createdByKey": "la clé d’API {id}",
3251
- "revoke": "Révoquer le jeton"
3251
+ "revoke": "Révoquer le jeton",
3252
+ "boundToYou": "Votre abonnement",
3253
+ "boundToOther": "Abonnement de {user}"
3252
3254
  },
3253
3255
  "add": {
3254
3256
  "heading": "Créer un jeton",
@@ -3257,7 +3259,12 @@
3257
3259
  "labelPlaceholder": "ex. pipeline CI",
3258
3260
  "scope": "Portée",
3259
3261
  "scopeHelp": "Ce que ce jeton peut faire : lecture seule, lecture et écriture, ou accès complet (qui autorise aussi la suppression).",
3260
- "create": "Créer le jeton"
3262
+ "create": "Créer le jeton",
3263
+ "identity": "S'exécute en tant que",
3264
+ "identitySystem": "Cet espace de travail (jeton système)",
3265
+ "identitySelf": "Moi (jeton personnel)",
3266
+ "identitySystemHelp": "Les exécutions lancées avec ce jeton appartiennent à l'espace de travail et ne sont attribuées à personne. Il ne peut utiliser l'abonnement personnel Claude / Codex / GLM de qui que ce soit : une tâche fixée sur un tel modèle est refusée plutôt que facturée à quelqu'un d'absent. À utiliser pour la CI et les intégrations partagées.",
3267
+ "identitySelfHelp": "Les exécutions lancées avec ce jeton vous sont attribuées et peuvent utiliser votre abonnement personnel Claude / Codex / GLM. Chaque appel doit aussi envoyer votre mot de passe personnel dans l'en-tête X-Personal-Password ; il n'est jamais conservé. À utiliser pour vos propres exécutions headless."
3261
3268
  },
3262
3269
  "scopes": {
3263
3270
  "read": "Lecture seule",
@@ -3588,6 +3595,15 @@
3588
3595
  "reachable": "Joignable · aucun modèle | Joignable · {count} modèle | Joignable · {count} modèles",
3589
3596
  "noModels": "Aucun modèle signalé.",
3590
3597
  "enableModels": "Activer les modèles",
3598
+ "imageInputHint": "Certains modèles locaux lisent les captures d'écran et les rendus de design. Les familles connues sont reconnues automatiquement, et l'option Non défini de chaque modèle indique ce qui s'appliquera ; définissez-la vous-même pour une version que la plateforme ne peut pas connaître, comme une quantification texte seulement ou un modèle affiné.",
3599
+ "imageInputLabel": "Prise en charge des images pour {model}",
3600
+ "imageInput": {
3601
+ "autoYes": "Non défini : {family} lit les images",
3602
+ "autoNo": "Non défini : {family} est texte seulement",
3603
+ "unknown": "Images : non défini",
3604
+ "yes": "Lit les images",
3605
+ "no": "Texte seulement"
3606
+ },
3591
3607
  "toast": {
3592
3608
  "saved": "{name} enregistré",
3593
3609
  "saveFailed": "Impossible d'enregistrer le runner",
@@ -3599,6 +3615,7 @@
3599
3615
  "body": "\"{name}\" sera supprimé. Cette action est irréversible."
3600
3616
  },
3601
3617
  "blocked": "L'URL de ce runner n'est pas autorisée sur ce déploiement, ses modèles sont donc masqués dans le sélecteur.",
3618
+ "modelsDiscarded": "Certains modèles activés de ce runner n'ont pas pu être lus et ont été supprimés. Sélectionnez à nouveau ceux que vous voulez, puis enregistrez.",
3602
3619
  "urlReason": {
3603
3620
  "invalid_url": "Cette URL n'est pas valide.",
3604
3621
  "scheme_not_allowed": "L'URL d'un runner doit commencer par http:// ou https://.",
@@ -3390,7 +3390,9 @@
3390
3390
  "createdBy": "נוצר על ידי {user}",
3391
3391
  "createdByYou": "אתה",
3392
3392
  "createdByKey": "מפתח API {id}",
3393
- "revoke": "בטל אסימון"
3393
+ "revoke": "בטל אסימון",
3394
+ "boundToYou": "המנוי שלך",
3395
+ "boundToOther": "המנוי של {user}"
3394
3396
  },
3395
3397
  "add": {
3396
3398
  "heading": "צור אסימון",
@@ -3399,7 +3401,12 @@
3399
3401
  "labelPlaceholder": "לדוגמה, צינור CI",
3400
3402
  "scope": "היקף הרשאות",
3401
3403
  "scopeHelp": "מה האסימון הזה יכול לעשות: קריאה בלבד, קריאה וכתיבה, או גישה מלאה (שמאפשרת גם מחיקה).",
3402
- "create": "צור אסימון"
3404
+ "create": "צור אסימון",
3405
+ "identity": "פועל בשם",
3406
+ "identitySystem": "סביבת העבודה הזו (אסימון מערכת)",
3407
+ "identitySelf": "אני (אסימון אישי)",
3408
+ "identitySystemHelp": "הרצות שמתחילות עם האסימון הזה שייכות לסביבת העבודה ואינן משויכות לאף אדם. הוא אינו יכול להשתמש במנוי האישי של אף אחד ל-Claude / Codex / GLM, ולכן משימה שמוצמדת למודל כזה תידחה במקום להיזקף לחובת מי שאינו נוכח. השתמשו בו ל-CI ולאינטגרציות משותפות.",
3409
+ "identitySelfHelp": "הרצות שמתחילות עם האסימון הזה נחשבות שלך ויכולות להשתמש במנוי האישי שלך ל-Claude / Codex / GLM. כל קריאה כזו חייבת לשלוח גם את הסיסמה האישית שלך בכותרת X-Personal-Password; היא לעולם אינה נשמרת. השתמשו בו להרצות headless משלכם."
3403
3410
  },
3404
3411
  "scopes": {
3405
3412
  "read": "קריאה בלבד",
@@ -3730,6 +3737,15 @@
3730
3737
  "reachable": "נגיש · אין מודלים | נגיש · {count} מודל | נגיש · {count} מודלים | נגיש · {count} מודלים",
3731
3738
  "noModels": "לא דווחו מודלים.",
3732
3739
  "enableModels": "אפשר מודלים",
3740
+ "imageInputHint": "מודלים מקומיים מסוימים קוראים צילומי מסך ורנדרים של עיצוב. משפחות מודלים מוכרות מזוהות אוטומטית, והאפשרות לא הוגדר אצל כל מודל מציינת כיצד הוא יטופל; הגדירו זאת בעצמכם עבור בנייה שהפלטפורמה אינה יכולה להכיר, כמו קוונטיזציה לטקסט בלבד או כיוונון עדין.",
3741
+ "imageInputLabel": "תמיכה בתמונות עבור {model}",
3742
+ "imageInput": {
3743
+ "autoYes": "לא הוגדר: {family} קורא תמונות",
3744
+ "autoNo": "לא הוגדר: {family} הוא טקסט בלבד",
3745
+ "unknown": "תמונות: לא הוגדר",
3746
+ "yes": "קורא תמונות",
3747
+ "no": "טקסט בלבד"
3748
+ },
3733
3749
  "toast": {
3734
3750
  "saved": "{name} נשמר",
3735
3751
  "saveFailed": "לא ניתן היה לשמור מריץ",
@@ -3741,6 +3757,7 @@
3741
3757
  "body": "\"{name}\" יימחק. לא ניתן לבטל פעולה זו."
3742
3758
  },
3743
3759
  "blocked": "כתובת ה-URL של מריץ זה אינה מותרת בפריסה הזו, ולכן הדגמים שלו מוסתרים מהבורר.",
3760
+ "modelsDiscarded": "חלק מהמודלים שהופעלו במריץ הזה לא ניתנו לקריאה והושמטו. בחרו מחדש את אלה שאתם רוצים ושמרו.",
3744
3761
  "urlReason": {
3745
3762
  "invalid_url": "זו אינה כתובת URL תקפה.",
3746
3763
  "scheme_not_allowed": "כתובת URL של מריץ חייבת להתחיל ב-http:// או ב-https://.",
@@ -793,7 +793,9 @@
793
793
  "createdBy": "creato da {user}",
794
794
  "createdByYou": "te",
795
795
  "createdByKey": "la chiave API {id}",
796
- "revoke": "Revoca token"
796
+ "revoke": "Revoca token",
797
+ "boundToYou": "Il tuo abbonamento",
798
+ "boundToOther": "Abbonamento di {user}"
797
799
  },
798
800
  "add": {
799
801
  "heading": "Crea un token",
@@ -802,7 +804,12 @@
802
804
  "labelPlaceholder": "es. pipeline CI",
803
805
  "scope": "Ambito",
804
806
  "scopeHelp": "Cosa può fare questo token: sola lettura, lettura e scrittura o accesso completo (che consente anche l'eliminazione).",
805
- "create": "Crea token"
807
+ "create": "Crea token",
808
+ "identity": "Viene eseguito come",
809
+ "identitySystem": "Questo spazio di lavoro (token di sistema)",
810
+ "identitySelf": "Io (token personale)",
811
+ "identitySystemHelp": "Le esecuzioni avviate con questo token appartengono allo spazio di lavoro e non sono attribuite a nessuna persona. Non può usare l'abbonamento personale Claude / Codex / GLM di nessuno: un'attività fissata su uno di quei modelli viene rifiutata anziché addebitata a chi non c'è. Usalo per la CI e le integrazioni condivise.",
812
+ "identitySelfHelp": "Le esecuzioni avviate con questo token sono attribuite a te e possono usare il tuo abbonamento personale Claude / Codex / GLM. Ogni chiamata deve inviare anche la tua password personale nell'intestazione X-Personal-Password; non viene mai memorizzata. Usalo per le tue esecuzioni headless."
806
813
  },
807
814
  "scopes": {
808
815
  "read": "Sola lettura",
@@ -1133,6 +1140,15 @@
1133
1140
  "reachable": "Raggiungibile · nessun modello | Raggiungibile · {count} modello | Raggiungibile · {count} modelli",
1134
1141
  "noModels": "Nessun modello riportato.",
1135
1142
  "enableModels": "Abilita i modelli",
1143
+ "imageInputHint": "Alcuni modelli locali leggono screenshot e render di design. Le famiglie note vengono riconosciute automaticamente e l'opzione Non impostato di ogni modello indica come verrà trattato; impostalo tu per una build che la piattaforma non può conoscere, come una quantizzazione solo testo o un fine-tuning.",
1144
+ "imageInputLabel": "Supporto immagini per {model}",
1145
+ "imageInput": {
1146
+ "autoYes": "Non impostato: {family} legge immagini",
1147
+ "autoNo": "Non impostato: {family} è solo testo",
1148
+ "unknown": "Immagini: non impostato",
1149
+ "yes": "Legge immagini",
1150
+ "no": "Solo testo"
1151
+ },
1136
1152
  "toast": {
1137
1153
  "saved": "{name} salvato",
1138
1154
  "saveFailed": "Impossibile salvare il runner",
@@ -1144,6 +1160,7 @@
1144
1160
  "body": "\"{name}\" verra rimosso. Questa operazione non puo essere annullata."
1145
1161
  },
1146
1162
  "blocked": "L'URL di questo runner non è consentito su questo deployment, quindi i suoi modelli sono nascosti nel selettore.",
1163
+ "modelsDiscarded": "Alcuni dei modelli abilitati di questo runner non sono risultati leggibili e sono stati scartati. Riseleziona quelli che vuoi e salva.",
1147
1164
  "urlReason": {
1148
1165
  "invalid_url": "Questo URL non è valido.",
1149
1166
  "scheme_not_allowed": "L'URL di un runner deve iniziare con http:// o https://.",
@@ -3390,7 +3390,9 @@
3390
3390
  "createdBy": "作成者: {user}",
3391
3391
  "createdByYou": "あなた",
3392
3392
  "createdByKey": "APIキー {id}",
3393
- "revoke": "トークンを取り消す"
3393
+ "revoke": "トークンを取り消す",
3394
+ "boundToYou": "あなたのサブスクリプション",
3395
+ "boundToOther": "{user} のサブスクリプション"
3394
3396
  },
3395
3397
  "add": {
3396
3398
  "heading": "トークンを作成",
@@ -3399,7 +3401,12 @@
3399
3401
  "labelPlaceholder": "例: CI パイプライン",
3400
3402
  "scope": "権限範囲",
3401
3403
  "scopeHelp": "このトークンでできること: 読み取り専用、読み取りと書き込み、またはフルアクセス(削除も可能)。",
3402
- "create": "トークンを作成"
3404
+ "create": "トークンを作成",
3405
+ "identity": "実行者",
3406
+ "identitySystem": "このワークスペース(システムトークン)",
3407
+ "identitySelf": "自分(個人トークン)",
3408
+ "identitySystemHelp": "このトークンで開始した実行はワークスペースに属し、個人には紐づきません。誰の個人 Claude / Codex / GLM サブスクリプションも利用できないため、それらのモデルを指定したタスクは、不在の相手に課金される代わりに拒否されます。CI や共有連携にはこちらを使ってください。",
3409
+ "identitySelfHelp": "このトークンで開始した実行はあなたのものとして扱われ、あなたの個人 Claude / Codex / GLM サブスクリプションを利用できます。その際は毎回のリクエストで X-Personal-Password ヘッダーに個人パスワードを送る必要があります。パスワードは保存されません。自分のヘッドレス実行にはこちらを使ってください。"
3403
3410
  },
3404
3411
  "scopes": {
3405
3412
  "read": "読み取り専用",
@@ -3730,6 +3737,15 @@
3730
3737
  "reachable": "接続可能 · モデルなし | 接続可能 · {count} 個のモデル | 接続可能 · {count} 個のモデル",
3731
3738
  "noModels": "報告されたモデルがありません。",
3732
3739
  "enableModels": "モデルを有効化",
3740
+ "imageInputHint": "一部のローカルモデルはスクリーンショットやデザインのレンダー画像を読み取れます。よく使われるモデルファミリーは自動的に判別され、各モデルの未設定の選択肢にどちらとして扱われるかが表示されます。テキスト専用の量子化版やファインチューンなど、プラットフォームでは判断できないビルドの場合にご自身で設定してください。",
3741
+ "imageInputLabel": "{model} の画像対応",
3742
+ "imageInput": {
3743
+ "autoYes": "未設定: {family} は画像を読み取る",
3744
+ "autoNo": "未設定: {family} はテキストのみ",
3745
+ "unknown": "画像: 未設定",
3746
+ "yes": "画像を読み取る",
3747
+ "no": "テキストのみ"
3748
+ },
3733
3749
  "toast": {
3734
3750
  "saved": "{name} を保存しました",
3735
3751
  "saveFailed": "ランナーを保存できませんでした",
@@ -3741,6 +3757,7 @@
3741
3757
  "body": "「{name}」が削除されます。 この操作は取り消せません。"
3742
3758
  },
3743
3759
  "blocked": "このランナーの URL はこのデプロイでは許可されていないため、モデルはピッカーに表示されません。",
3760
+ "modelsDiscarded": "このランナーで有効になっていたモデルの一部を読み取れず、破棄しました。必要なものを選び直して保存してください。",
3744
3761
  "urlReason": {
3745
3762
  "invalid_url": "有効な URL ではありません。",
3746
3763
  "scheme_not_allowed": "ランナーの URL は http:// または https:// で始める必要があります。",
@@ -3248,7 +3248,9 @@
3248
3248
  "createdBy": "utworzone przez {user}",
3249
3249
  "createdByYou": "Ciebie",
3250
3250
  "createdByKey": "klucz API {id}",
3251
- "revoke": "Unieważnij token"
3251
+ "revoke": "Unieważnij token",
3252
+ "boundToYou": "Twoja subskrypcja",
3253
+ "boundToOther": "Subskrypcja użytkownika {user}"
3252
3254
  },
3253
3255
  "add": {
3254
3256
  "heading": "Utwórz token",
@@ -3257,7 +3259,12 @@
3257
3259
  "labelPlaceholder": "np. potok CI",
3258
3260
  "scope": "Zakres",
3259
3261
  "scopeHelp": "Co może ten token: tylko odczyt, odczyt i zapis lub pełny dostęp (który pozwala także na usuwanie).",
3260
- "create": "Utwórz token"
3262
+ "create": "Utwórz token",
3263
+ "identity": "Działa jako",
3264
+ "identitySystem": "Ten obszar roboczy (token systemowy)",
3265
+ "identitySelf": "Ja (token osobisty)",
3266
+ "identitySystemHelp": "Uruchomienia rozpoczęte tym tokenem należą do obszaru roboczego i nie są przypisywane do żadnej osoby. Token nie może korzystać z niczyjej osobistej subskrypcji Claude / Codex / GLM, więc zadanie przypięte do takiego modelu zostanie odrzucone, zamiast obciążyć kogoś nieobecnego. Użyj go do CI i wspólnych integracji.",
3267
+ "identitySelfHelp": "Uruchomienia rozpoczęte tym tokenem są przypisywane Tobie i mogą korzystać z Twojej osobistej subskrypcji Claude / Codex / GLM. Każde takie wywołanie musi dodatkowo przesłać Twoje hasło osobiste w nagłówku X-Personal-Password; nie jest ono nigdzie zapisywane. Użyj go do własnych uruchomień headless."
3261
3268
  },
3262
3269
  "scopes": {
3263
3270
  "read": "Tylko odczyt",
@@ -3588,6 +3595,15 @@
3588
3595
  "reachable": "Osiągalny · {count} model | Osiągalny · {count} modele | Osiągalny · {count} modeli",
3589
3596
  "noModels": "Nie zgłoszono żadnych modeli.",
3590
3597
  "enableModels": "Włącz modele",
3598
+ "imageInputHint": "Część modeli lokalnych czyta zrzuty ekranu i rendery projektów. Znane rodziny modeli są rozpoznawane automatycznie, a opcja Nie ustawiono przy każdym modelu mówi, jak zostanie potraktowany; ustaw ją samodzielnie dla wersji, której platforma nie może znać, na przykład kwantyzacji tylko tekstowej lub własnego dostrojenia.",
3599
+ "imageInputLabel": "Obsługa obrazów dla {model}",
3600
+ "imageInput": {
3601
+ "autoYes": "Nie ustawiono: {family} czyta obrazy",
3602
+ "autoNo": "Nie ustawiono: {family} obsługuje tylko tekst",
3603
+ "unknown": "Obrazy: nie ustawiono",
3604
+ "yes": "Czyta obrazy",
3605
+ "no": "Tylko tekst"
3606
+ },
3591
3607
  "toast": {
3592
3608
  "saved": "Zapisano {name}",
3593
3609
  "saveFailed": "Nie udało się zapisać runnera",
@@ -3599,6 +3615,7 @@
3599
3615
  "body": "\"{name}\" zostanie usunięty. Tej operacji nie można cofnąć."
3600
3616
  },
3601
3617
  "blocked": "Adres URL tego runnera nie jest dozwolony w tym wdrożeniu, dlatego jego modele są ukryte w selektorze.",
3618
+ "modelsDiscarded": "Części włączonych modeli tego runnera nie udało się odczytać i zostały odrzucone. Wybierz ponownie te, których chcesz, i zapisz.",
3602
3619
  "urlReason": {
3603
3620
  "invalid_url": "To nie jest prawidłowy adres URL.",
3604
3621
  "scheme_not_allowed": "Adres URL runnera musi zaczynać się od http:// lub https://.",
@@ -3390,7 +3390,9 @@
3390
3390
  "createdBy": "oluşturan: {user}",
3391
3391
  "createdByYou": "siz",
3392
3392
  "createdByKey": "{id} API anahtarı",
3393
- "revoke": "Belirteci iptal et"
3393
+ "revoke": "Belirteci iptal et",
3394
+ "boundToYou": "Aboneliğiniz",
3395
+ "boundToOther": "{user} kullanıcısının aboneliği"
3394
3396
  },
3395
3397
  "add": {
3396
3398
  "heading": "Belirteç oluştur",
@@ -3399,7 +3401,12 @@
3399
3401
  "labelPlaceholder": "örn. CI hattı",
3400
3402
  "scope": "Kapsam",
3401
3403
  "scopeHelp": "Bu belirtecin yapabilecekleri: yalnızca okuma, okuma ve yazma ya da tam erişim (silmeye de izin verir).",
3402
- "create": "Belirteç oluştur"
3404
+ "create": "Belirteç oluştur",
3405
+ "identity": "Şu kimlikle çalışır",
3406
+ "identitySystem": "Bu çalışma alanı (sistem belirteci)",
3407
+ "identitySelf": "Ben (kişisel belirteç)",
3408
+ "identitySystemHelp": "Bu belirteçle başlatılan çalıştırmalar çalışma alanına aittir ve hiçbir kişiye atfedilmez. Kimsenin kişisel Claude / Codex / GLM aboneliğini kullanamaz; bu nedenle böyle bir modele sabitlenmiş görev, orada olmayan birine fatura edilmek yerine reddedilir. CI ve paylaşılan entegrasyonlar için bunu kullanın.",
3409
+ "identitySelfHelp": "Bu belirteçle başlatılan çalıştırmalar size ait sayılır ve kişisel Claude / Codex / GLM aboneliğinizi kullanabilir. Bu tür her çağrının ayrıca kişisel parolanızı X-Personal-Password başlığında göndermesi gerekir; parola hiçbir zaman saklanmaz. Kendi headless çalıştırmalarınız için bunu kullanın."
3403
3410
  },
3404
3411
  "scopes": {
3405
3412
  "read": "Yalnızca okuma",
@@ -3730,6 +3737,15 @@
3730
3737
  "reachable": "Ulaşılabilir · model yok | Ulaşılabilir · {count} model | Ulaşılabilir · {count} model",
3731
3738
  "noModels": "Model bildirilmedi.",
3732
3739
  "enableModels": "Modelleri etkinleştir",
3740
+ "imageInputHint": "Bazı yerel modeller ekran görüntülerini ve tasarım render'larını okuyabilir. Bilinen model aileleri otomatik olarak tanınır ve her modeldeki Ayarlanmadı seçeneği hangi sonucu vereceğini belirtir; yalnızca metin niceleme ya da ince ayar gibi platformun bilemeyeceği bir yapı için bunu kendiniz ayarlayın.",
3741
+ "imageInputLabel": "{model} için görsel desteği",
3742
+ "imageInput": {
3743
+ "autoYes": "Ayarlanmadı: {family} görsel okur",
3744
+ "autoNo": "Ayarlanmadı: {family} yalnızca metin",
3745
+ "unknown": "Görseller: ayarlanmadı",
3746
+ "yes": "Görsel okur",
3747
+ "no": "Yalnızca metin"
3748
+ },
3733
3749
  "toast": {
3734
3750
  "saved": "{name} kaydedildi",
3735
3751
  "saveFailed": "Çalıştırıcı kaydedilemedi",
@@ -3741,6 +3757,7 @@
3741
3757
  "body": "\"{name}\" kaldırılacak. Bu işlem geri alınamaz."
3742
3758
  },
3743
3759
  "blocked": "Bu çalıştırıcının URL adresi bu kurulumda izinli değil, bu nedenle modelleri seçicide gizlendi.",
3760
+ "modelsDiscarded": "Bu çalıştırıcının etkin modellerinin bir kısmı okunamadı ve atıldı. İstediklerinizi yeniden seçip kaydedin.",
3744
3761
  "urlReason": {
3745
3762
  "invalid_url": "Bu geçerli bir URL değil.",
3746
3763
  "scheme_not_allowed": "Çalıştırıcı URL adresi http:// veya https:// ile başlamalıdır.",
@@ -3248,7 +3248,9 @@
3248
3248
  "createdBy": "створено {user}",
3249
3249
  "createdByYou": "вами",
3250
3250
  "createdByKey": "ключ API {id}",
3251
- "revoke": "Відкликати токен"
3251
+ "revoke": "Відкликати токен",
3252
+ "boundToYou": "Ваша підписка",
3253
+ "boundToOther": "Підписка {user}"
3252
3254
  },
3253
3255
  "add": {
3254
3256
  "heading": "Створити токен",
@@ -3257,7 +3259,12 @@
3257
3259
  "labelPlaceholder": "напр. конвеєр CI",
3258
3260
  "scope": "Обсяг доступу",
3259
3261
  "scopeHelp": "Що може цей токен: лише читання, читання та запис або повний доступ (який також дозволяє видалення).",
3260
- "create": "Створити токен"
3262
+ "create": "Створити токен",
3263
+ "identity": "Виконується як",
3264
+ "identitySystem": "Цей робочий простір (системний токен)",
3265
+ "identitySelf": "Я (особистий токен)",
3266
+ "identitySystemHelp": "Запуски, розпочаті цим токеном, належать робочому простору й не приписуються жодній особі. Він не може використовувати чиюсь особисту підписку Claude / Codex / GLM, тож завдання, закріплене за такою моделлю, буде відхилено, а не оплачено коштом відсутньої людини. Використовуйте його для CI та спільних інтеграцій.",
3267
+ "identitySelfHelp": "Запуски, розпочаті цим токеном, вважаються вашими й можуть використовувати вашу особисту підписку Claude / Codex / GLM. Кожен такий виклик має також надсилати ваш особистий пароль у заголовку X-Personal-Password; він ніколи не зберігається. Використовуйте його для власних headless-запусків."
3261
3268
  },
3262
3269
  "scopes": {
3263
3270
  "read": "Лише читання",
@@ -3588,6 +3595,15 @@
3588
3595
  "reachable": "Доступний · {count} модель | Доступний · {count} моделі | Доступний · {count} моделей",
3589
3596
  "noModels": "Жодної моделі не повідомлено.",
3590
3597
  "enableModels": "Увімкнути моделі",
3598
+ "imageInputHint": "Деякі локальні моделі читають знімки екрана та рендери дизайну. Відомі сімейства моделей розпізнаються автоматично, а параметр Не задано біля кожної моделі показує, як її буде оброблено; задайте його самостійно для збірки, про яку платформа не може знати, як-от суто текстова квантизація або власне доналаштування.",
3599
+ "imageInputLabel": "Підтримка зображень для {model}",
3600
+ "imageInput": {
3601
+ "autoYes": "Не задано: {family} читає зображення",
3602
+ "autoNo": "Не задано: {family} лише текст",
3603
+ "unknown": "Зображення: не задано",
3604
+ "yes": "Читає зображення",
3605
+ "no": "Лише текст"
3606
+ },
3591
3607
  "toast": {
3592
3608
  "saved": "{name} збережено",
3593
3609
  "saveFailed": "Не вдалося зберегти раннер",
@@ -3599,6 +3615,7 @@
3599
3615
  "body": "\"{name}\" буде видалено. Цю дію не можна скасувати."
3600
3616
  },
3601
3617
  "blocked": "URL цього раннера не дозволений у цьому розгортанні, тому його моделі приховані у виборі.",
3618
+ "modelsDiscarded": "Частину увімкнених моделей цього раннера не вдалося прочитати, і вони були відкинуті. Виберіть потрібні знову та збережіть.",
3602
3619
  "urlReason": {
3603
3620
  "invalid_url": "Це недійсний URL.",
3604
3621
  "scheme_not_allowed": "URL раннера має починатися з http:// або https://.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.261.7",
3
+ "version": "0.263.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",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.41",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.296.0"
43
+ "@cat-factory/contracts": "0.298.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",