@cat-factory/app 0.243.0 → 0.245.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.
- package/app/components/outcome/OutcomeSummaryWindow.vue +93 -0
- package/app/components/panels/StepToolServers.logic.spec.ts +108 -1
- package/app/components/panels/StepToolServers.logic.ts +150 -2
- package/app/components/panels/StepToolServers.vue +89 -6
- package/app/types/toolServers.ts +2 -0
- package/app/utils/runOutcome.ts +3 -0
- package/i18n/locales/de.json +19 -0
- package/i18n/locales/en.json +19 -0
- package/i18n/locales/es.json +19 -0
- package/i18n/locales/fr.json +19 -0
- package/i18n/locales/he.json +19 -0
- package/i18n/locales/it.json +19 -0
- package/i18n/locales/ja.json +19 -0
- package/i18n/locales/pl.json +19 -0
- package/i18n/locales/tr.json +19 -0
- package/i18n/locales/uk.json +19 -0
- package/package.json +2 -2
|
@@ -19,14 +19,18 @@ import type {
|
|
|
19
19
|
OutcomeCheckKind,
|
|
20
20
|
OutcomeCheckState,
|
|
21
21
|
OutcomeDisposition,
|
|
22
|
+
OutcomeSource,
|
|
22
23
|
OutcomeSpecJoin,
|
|
23
24
|
OutcomeVisual,
|
|
24
25
|
RequirementsGap,
|
|
26
|
+
SourcesGap,
|
|
25
27
|
TestsGap,
|
|
26
28
|
TestsVerdict,
|
|
27
29
|
VisualsGap,
|
|
28
30
|
} from '~/utils/runOutcome'
|
|
29
31
|
import { composeRunOutcome } from '~/utils/runOutcome'
|
|
32
|
+
import { GAP_KEYS } from '~/components/documents/DocumentSyncState.logic'
|
|
33
|
+
import DocumentOriginLink from '~/components/documents/DocumentOriginLink.vue'
|
|
30
34
|
import { REPRODUCTION_STATUS_KEYS } from '~/utils/reproduction'
|
|
31
35
|
import type { RequirementVerdictStatus, TestConcernSeverity } from '~/types/domain'
|
|
32
36
|
import type { TestEnvironment } from '@cat-factory/contracts'
|
|
@@ -37,6 +41,7 @@ import MarkdownProse from '~/components/common/MarkdownProse.vue'
|
|
|
37
41
|
import EmptyState from '~/components/common/EmptyState.vue'
|
|
38
42
|
|
|
39
43
|
const board = useBoardStore()
|
|
44
|
+
const documents = useDocumentsStore()
|
|
40
45
|
const execution = useExecutionStore()
|
|
41
46
|
const serviceSpec = useServiceSpecStore()
|
|
42
47
|
const ui = useUiStore()
|
|
@@ -128,6 +133,10 @@ const TESTS_GAP_KEYS: Record<TestsGap, string> = {
|
|
|
128
133
|
no_tester_step: 'outcome.tests.gap.no_tester_step',
|
|
129
134
|
tester_not_reported: 'outcome.tests.gap.tester_not_reported',
|
|
130
135
|
}
|
|
136
|
+
const SOURCES_GAP_KEYS: Record<SourcesGap, string> = {
|
|
137
|
+
run_unavailable: RUN_UNAVAILABLE_KEY,
|
|
138
|
+
none_linked: 'outcome.sources.gap.none_linked',
|
|
139
|
+
}
|
|
131
140
|
const VISUALS_GAP_KEYS: Record<VisualsGap, string> = {
|
|
132
141
|
run_unavailable: RUN_UNAVAILABLE_KEY,
|
|
133
142
|
no_visual_step: 'outcome.visuals.gap.no_visual_step',
|
|
@@ -288,6 +297,47 @@ const checkRows = computed(() =>
|
|
|
288
297
|
})),
|
|
289
298
|
)
|
|
290
299
|
|
|
300
|
+
/**
|
|
301
|
+
* What one linked page's row says about how current the copy the agents read was.
|
|
302
|
+
*
|
|
303
|
+
* Four outcomes, and none of them may collapse into another: a named revision to check the work
|
|
304
|
+
* against, a copy nobody could confirm (a warning about the WORK, not about the platform), a body
|
|
305
|
+
* with no source to trail, and a deployment that runs no freshness check at all. The last two are
|
|
306
|
+
* the pair a dash would merge, and they are opposite facts about whether anything is missing.
|
|
307
|
+
*/
|
|
308
|
+
function revisionLabel(freshness: OutcomeSource['freshness']): string {
|
|
309
|
+
if (!freshness) return t('outcome.sources.unchecked')
|
|
310
|
+
switch (freshness.status) {
|
|
311
|
+
case 'confirmed':
|
|
312
|
+
return t('documents.freshness.revision', { version: freshness.version })
|
|
313
|
+
case 'not-applicable':
|
|
314
|
+
return t('outcome.sources.noSource')
|
|
315
|
+
case 'unconfirmed':
|
|
316
|
+
return t(GAP_KEYS[freshness.reason])
|
|
317
|
+
default:
|
|
318
|
+
return exhaustiveFreshness(freshness)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Compile-time totality: a new verdict member fails the build rather than rendering a blank. */
|
|
323
|
+
function exhaustiveFreshness(freshness: never): string {
|
|
324
|
+
return String(freshness)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The linked pages with their labels resolved, so the exhaustive mapping above is applied once
|
|
329
|
+
* per row rather than on every re-render, and the template stays a list.
|
|
330
|
+
*/
|
|
331
|
+
const sourceRows = computed(() => {
|
|
332
|
+
const sources = outcome.value?.sources
|
|
333
|
+
if (!sources || sources.status !== 'reported') return []
|
|
334
|
+
return sources.sources.map((source) => ({
|
|
335
|
+
...source,
|
|
336
|
+
icon: documents.descriptorForOrigin(source.origin)?.icon ?? 'i-lucide-file-text',
|
|
337
|
+
revision: revisionLabel(source.freshness),
|
|
338
|
+
}))
|
|
339
|
+
})
|
|
340
|
+
|
|
291
341
|
/** Drill into the full test report (this card is the summary, never a replacement for it). */
|
|
292
342
|
function openTestReport() {
|
|
293
343
|
if (instance.value) ui.openTestEvidence(instance.value.id)
|
|
@@ -358,6 +408,49 @@ function openTestReport() {
|
|
|
358
408
|
</p>
|
|
359
409
|
</section>
|
|
360
410
|
|
|
411
|
+
<!-- What the run built FROM, and which revision of it. Directly under the ask, because it
|
|
412
|
+
is the rest of the brief: the description is what a person wrote, this is what the
|
|
413
|
+
agents actually read, and every section below is a statement about work done against
|
|
414
|
+
it. A design that moved mid-run is the reading that changes all of them. -->
|
|
415
|
+
<section class="mb-5" data-testid="outcome-sources">
|
|
416
|
+
<h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
417
|
+
{{ t('outcome.sources.title') }}
|
|
418
|
+
</h3>
|
|
419
|
+
<div v-if="outcome.sources.status === 'reported'" class="space-y-1">
|
|
420
|
+
<div
|
|
421
|
+
v-for="source in sourceRows"
|
|
422
|
+
:key="`${source.origin}:${source.url || source.title}`"
|
|
423
|
+
class="rounded-md border border-slate-800 bg-slate-900/60 px-2 py-1.5"
|
|
424
|
+
data-testid="outcome-source"
|
|
425
|
+
>
|
|
426
|
+
<DocumentOriginLink
|
|
427
|
+
:url="source.url ?? ''"
|
|
428
|
+
class="flex items-center gap-1.5 text-xs text-slate-300"
|
|
429
|
+
hover-class="hover:text-white"
|
|
430
|
+
>
|
|
431
|
+
<UIcon :name="source.icon" class="h-3.5 w-3.5 shrink-0 text-indigo-400" />
|
|
432
|
+
<span class="truncate">{{ source.title }}</span>
|
|
433
|
+
</DocumentOriginLink>
|
|
434
|
+
<p class="mt-0.5 text-[11px] text-slate-500" data-testid="outcome-source-revision">
|
|
435
|
+
{{ source.revision }}
|
|
436
|
+
</p>
|
|
437
|
+
<!-- Stated separately from the revision above: the last revision alone says the run
|
|
438
|
+
ENDED current, and says nothing about the step that finished before the page
|
|
439
|
+
changed under it. -->
|
|
440
|
+
<p
|
|
441
|
+
v-if="source.movedDuringRun"
|
|
442
|
+
class="mt-0.5 text-[11px] text-amber-300"
|
|
443
|
+
data-testid="outcome-source-moved"
|
|
444
|
+
>
|
|
445
|
+
{{ t('outcome.sources.moved') }}
|
|
446
|
+
</p>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
<p v-else class="text-[13px] italic leading-relaxed text-slate-500">
|
|
450
|
+
{{ t(SOURCES_GAP_KEYS[outcome.sources.gap]) }}
|
|
451
|
+
</p>
|
|
452
|
+
</section>
|
|
453
|
+
|
|
361
454
|
<!-- Requirement coverage: which required behaviours were checked, and what was seen. -->
|
|
362
455
|
<section class="mb-5" data-testid="outcome-requirements">
|
|
363
456
|
<h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
2
|
import {
|
|
3
|
+
KNOWN_OBSERVED_STATUSES,
|
|
3
4
|
KNOWN_REASONS,
|
|
5
|
+
OBSERVED_STATUS_KEY,
|
|
4
6
|
REASON_KEY,
|
|
5
7
|
REMEDY_KEY,
|
|
8
|
+
observationFor,
|
|
9
|
+
observationIsFault,
|
|
10
|
+
observationText,
|
|
6
11
|
reasonText,
|
|
7
12
|
remedyText,
|
|
13
|
+
unattributedObservations,
|
|
8
14
|
} from './StepToolServers.logic'
|
|
9
|
-
import type { ToolServerUnavailableReason } from '~/types/toolServers'
|
|
15
|
+
import type { ObservedToolServer, ToolServerUnavailableReason } from '~/types/toolServers'
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* A dropped tool server is the whole point of this surface: until it existed, a run that quietly
|
|
@@ -98,3 +104,104 @@ function render(reason: string): { key: string; params?: Record<string, unknown>
|
|
|
98
104
|
})
|
|
99
105
|
return seen
|
|
100
106
|
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The OBSERVED half: what the agent's CLI said about the servers the platform wired. Its whole
|
|
110
|
+
* value is a set of distinctions, and each one collapses into "looks fine" if it is lost — so
|
|
111
|
+
* these pin the distinctions rather than the copy.
|
|
112
|
+
*/
|
|
113
|
+
describe('tool-server startup observations', () => {
|
|
114
|
+
it('gives every observed status but `ready` its own copy', () => {
|
|
115
|
+
// `ready` is deliberately absent: a started server's line depends on its tool count, which is
|
|
116
|
+
// three sentences rather than one key. Everything else is exhaustive against the schema, so a
|
|
117
|
+
// member added on the backend fails here instead of rendering blank.
|
|
118
|
+
expect([...Object.keys(OBSERVED_STATUS_KEY), 'ready'].sort()).toEqual(
|
|
119
|
+
[...KNOWN_OBSERVED_STATUSES].sort(),
|
|
120
|
+
)
|
|
121
|
+
const keys = Object.values(OBSERVED_STATUS_KEY)
|
|
122
|
+
expect(new Set(keys).size).toBe(keys.length)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('says NOTHING when no observation was made', () => {
|
|
126
|
+
// The distinction the whole field rests on. A codex run, an image one version behind and an
|
|
127
|
+
// unmapped runner pool all report nothing, and any placeholder here would read as a verdict
|
|
128
|
+
// about a server that was very likely fine.
|
|
129
|
+
expect(observationFor(undefined, 'slack')).toBeNull()
|
|
130
|
+
expect(observationText(null, (key) => key)).toBeNull()
|
|
131
|
+
expect(observationIsFault(null)).toBe(false)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('distinguishes “nobody looked” from “the CLI never loaded it”', () => {
|
|
135
|
+
// Both render as an absent server on the chip if they are conflated, and only the second is
|
|
136
|
+
// evidence of anything. The first must never raise a fault.
|
|
137
|
+
const notLoaded = observationFor([{ id: 'jira', status: 'ready' }], 'slack')
|
|
138
|
+
expect(notLoaded).toEqual({ kind: 'not_loaded' })
|
|
139
|
+
expect(observationText(notLoaded, (key) => key)).toBe(
|
|
140
|
+
'panels.stepDetail.toolServers.observed.notLoaded',
|
|
141
|
+
)
|
|
142
|
+
expect(observationIsFault(notLoaded)).toBe(true)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('separates “started with no tools” from “started, tools uncounted”', () => {
|
|
146
|
+
// A server that connected and exposes nothing reaches the agent exactly like one that was
|
|
147
|
+
// never wired, and every other signal about it says healthy — so it gets its own sentence
|
|
148
|
+
// rather than a "0 tools" that reads as a rendering artefact.
|
|
149
|
+
const none = observationFor([{ id: 'slack', status: 'ready', toolCount: 0 }], 'slack')
|
|
150
|
+
const uncounted = observationFor([{ id: 'slack', status: 'ready' }], 'slack')
|
|
151
|
+
const some = observationFor([{ id: 'slack', status: 'ready', toolCount: 3 }], 'slack')
|
|
152
|
+
expect(observationText(none, (key) => key)).toBe(
|
|
153
|
+
'panels.stepDetail.toolServers.observed.readyNoTools',
|
|
154
|
+
)
|
|
155
|
+
expect(observationText(uncounted, (key) => key)).toBe(
|
|
156
|
+
'panels.stepDetail.toolServers.observed.ready',
|
|
157
|
+
)
|
|
158
|
+
expect(observationText(some, (key) => key)).toBe(
|
|
159
|
+
'panels.stepDetail.toolServers.observed.readyTools',
|
|
160
|
+
)
|
|
161
|
+
// None of the three is a fault: the count is a diagnosis for a person to read, not a verdict
|
|
162
|
+
// the surface should paint red.
|
|
163
|
+
for (const observation of [none, uncounted, some]) {
|
|
164
|
+
expect(observationIsFault(observation)).toBe(false)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('flags the two states the platform promised a tool it did not get', () => {
|
|
169
|
+
for (const status of ['failed', 'needs_auth'] as const) {
|
|
170
|
+
expect(observationIsFault(observationFor([{ id: 'slack', status }], 'slack'))).toBe(true)
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('never flags a state the report did not resolve', () => {
|
|
175
|
+
// `unknown` is a fact about THIS build (a word it could not map) or about the moment the
|
|
176
|
+
// report was taken (a server still handshaking), never about the server itself. Painting it
|
|
177
|
+
// as a fault would send an operator to debug a working integration every time a CLI adds a
|
|
178
|
+
// status word or starts a server a moment slower than the session announcement.
|
|
179
|
+
// Cast, because the point is a value the TYPE excludes and the DATA carries: the vocabulary
|
|
180
|
+
// is persisted on a run, so a status recorded by a newer harness (or retired since) reads back
|
|
181
|
+
// here as a member this build has no case for.
|
|
182
|
+
const persisted = observationFor(
|
|
183
|
+
[{ id: 'slack', status: 'reticulating' } as unknown as ObservedToolServer],
|
|
184
|
+
'slack',
|
|
185
|
+
)
|
|
186
|
+
expect(persisted).toEqual({ kind: 'loaded', status: 'unknown' })
|
|
187
|
+
expect(observationIsFault(persisted)).toBe(false)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('states a report naming servers the dispatch did not wire', () => {
|
|
191
|
+
// Empty on every ordinary run (`--strict-mcp-config`). The only way to reach it is a producer
|
|
192
|
+
// describing some OTHER job, and silently filtering those rows would present that report as
|
|
193
|
+
// this run's clean bill of health.
|
|
194
|
+
const record = {
|
|
195
|
+
agentKind: 'coder',
|
|
196
|
+
wired: [{ id: 'slack', label: 'Slack', transport: 'http' as const }],
|
|
197
|
+
unavailable: [],
|
|
198
|
+
observed: [
|
|
199
|
+
{ id: 'slack', status: 'ready' as const },
|
|
200
|
+
{ id: 'stranger', status: 'ready' as const },
|
|
201
|
+
],
|
|
202
|
+
}
|
|
203
|
+
expect(unattributedObservations(record).map((s) => s.id)).toEqual(['stranger'])
|
|
204
|
+
// …and nothing to state when there was no report at all.
|
|
205
|
+
expect(unattributedObservations({ ...record, observed: undefined })).toEqual([])
|
|
206
|
+
})
|
|
207
|
+
})
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
toolServerObservedStatusSchema,
|
|
3
|
+
toolServerUnavailableReasonSchema,
|
|
4
|
+
} from '@cat-factory/contracts'
|
|
5
|
+
import type {
|
|
6
|
+
ObservedToolServer,
|
|
7
|
+
StepToolServers,
|
|
8
|
+
ToolServerObservedStatus,
|
|
9
|
+
ToolServerUnavailableReason,
|
|
10
|
+
} from '~/types/toolServers'
|
|
3
11
|
|
|
4
12
|
// The step surface's tool-server (MCP) reason mapping. Kept out of the SFC on the same seam as
|
|
5
13
|
// `StepTestReport.logic.ts`, so the vocabulary rule can be asserted without mounting a component.
|
|
@@ -102,3 +110,143 @@ export function remedyText(
|
|
|
102
110
|
): string | null {
|
|
103
111
|
return isKnownReason(reason) ? t(REMEDY_KEY[reason]) : null
|
|
104
112
|
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// The OBSERVED half: what the agent's CLI reported about the servers it loaded.
|
|
116
|
+
//
|
|
117
|
+
// The mapping above answers "why did the platform withhold this tool". This one answers the
|
|
118
|
+
// question that mapping structurally cannot: a server that passed every check, was promised to the
|
|
119
|
+
// agent in its prompt, and then did not come up. The two are rendered together on one chip because
|
|
120
|
+
// they are two halves of one answer about one server — but they are never merged into one status,
|
|
121
|
+
// because a withheld server and a failed one need different people to fix different things.
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* What this build knows about one WIRED server after joining the CLI's report onto it.
|
|
126
|
+
*
|
|
127
|
+
* `null` and `not_loaded` are the pair this whole type exists to keep apart, and conflating them is
|
|
128
|
+
* the mistake that would make the surface lie. `null` means NO OBSERVATION WAS MADE — the run's
|
|
129
|
+
* harness publishes no report (codex's CLI does not, nor does any image older than the one that
|
|
130
|
+
* introduced this, nor an unmapped runner pool) — so nothing at all is known about whether the
|
|
131
|
+
* server started. `not_loaded` means an observation WAS made and this server was not in it, which
|
|
132
|
+
* is positive evidence that the CLI never loaded it. Rendering the first as the second would
|
|
133
|
+
* accuse every wired server on every deployment one image behind.
|
|
134
|
+
*/
|
|
135
|
+
export type ToolServerObservation =
|
|
136
|
+
| null
|
|
137
|
+
| { kind: 'loaded'; status: ToolServerObservedStatus; toolCount?: number }
|
|
138
|
+
| { kind: 'not_loaded' }
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Join the CLI's report onto one wired server id.
|
|
142
|
+
*
|
|
143
|
+
* Takes the whole `observed` field rather than a prepared map so the absent-vs-empty decision is
|
|
144
|
+
* made HERE, in the one place that owns the distinction, instead of by each caller building the
|
|
145
|
+
* map. An absent field yields `null` for every server; a present one yields either the CLI's line
|
|
146
|
+
* or `not_loaded`.
|
|
147
|
+
*/
|
|
148
|
+
export function observationFor(
|
|
149
|
+
observed: readonly ObservedToolServer[] | undefined,
|
|
150
|
+
id: string,
|
|
151
|
+
): ToolServerObservation {
|
|
152
|
+
if (!observed) return null
|
|
153
|
+
const found = observed.find((server) => server.id === id)
|
|
154
|
+
if (!found) return { kind: 'not_loaded' }
|
|
155
|
+
return {
|
|
156
|
+
kind: 'loaded',
|
|
157
|
+
status: isKnownObservedStatus(found.status) ? found.status : 'unknown',
|
|
158
|
+
// Carried only when the CLI counted, and `0` is carried: a server that connected and exposed
|
|
159
|
+
// nothing reaches the agent exactly like one that was never wired, so it is the most
|
|
160
|
+
// diagnostic count on the field and the one a truthiness guard would erase.
|
|
161
|
+
...(typeof found.toolCount === 'number' ? { toolCount: found.toolCount } : {}),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The servers the CLI named that this step's record does not list as wired.
|
|
167
|
+
*
|
|
168
|
+
* Empty by construction on every ordinary run: the harness starts the CLI under
|
|
169
|
+
* `--strict-mcp-config`, so the only servers it can load are the ones this dispatch wrote for it.
|
|
170
|
+
* Surfaced anyway rather than filtered out, because the one way to reach it is a producer whose
|
|
171
|
+
* report does not describe this job — a runner-pool manifest pointing `toolServersPath` at the
|
|
172
|
+
* wrong field, say — and a surface that silently dropped those rows would present a report about
|
|
173
|
+
* some other job as a clean bill of health for this one.
|
|
174
|
+
*/
|
|
175
|
+
export function unattributedObservations(record: StepToolServers): ObservedToolServer[] {
|
|
176
|
+
if (!record.observed) return []
|
|
177
|
+
const wired = new Set(record.wired.map((server) => server.id))
|
|
178
|
+
return record.observed.filter((server) => !wired.has(server.id))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The i18n key per observed status, for a server the CLI DID name. An exhaustive `Record` for the
|
|
183
|
+
* same reason {@link REASON_KEY} is one: a member added to the wire vocabulary must fail to
|
|
184
|
+
* compile here rather than render as a blank line.
|
|
185
|
+
*
|
|
186
|
+
* `ready` is deliberately absent — a started server's line depends on its tool COUNT, which is
|
|
187
|
+
* three separate sentences (counted some, counted none, did not count), so it is resolved by
|
|
188
|
+
* {@link observationText} rather than by a single key.
|
|
189
|
+
*/
|
|
190
|
+
export const OBSERVED_STATUS_KEY: Record<Exclude<ToolServerObservedStatus, 'ready'>, string> = {
|
|
191
|
+
failed: 'panels.stepDetail.toolServers.observed.failed',
|
|
192
|
+
needs_auth: 'panels.stepDetail.toolServers.observed.needsAuth',
|
|
193
|
+
unknown: 'panels.stepDetail.toolServers.observed.unknown',
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The observed-status vocabulary as the SCHEMA states it: what a parity assertion grades against. */
|
|
197
|
+
export const KNOWN_OBSERVED_STATUSES = toolServerObservedStatusSchema.options
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Whether a persisted status is a member THIS build knows, derived from the picklist's own options.
|
|
201
|
+
* A predicate rather than a truthiness check on the lookup, for the reason {@link isKnownReason}
|
|
202
|
+
* gives: an `Object.prototype` member name reads back as a truthy non-key.
|
|
203
|
+
*/
|
|
204
|
+
export function isKnownObservedStatus(status: string): status is ToolServerObservedStatus {
|
|
205
|
+
return (KNOWN_OBSERVED_STATUSES as readonly string[]).includes(status)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Render one observation, or `null` when there is nothing to say.
|
|
210
|
+
*
|
|
211
|
+
* `null` is returned for `null` — no observation was made — and it is the whole reason this
|
|
212
|
+
* function exists rather than a template expression: the surface must be BYTE-FOR-BYTE what it was
|
|
213
|
+
* before this field existed on every run that carries no report, which is every codex run, every
|
|
214
|
+
* run on an image one version behind, and every run on an unmapped runner pool. Silence is the
|
|
215
|
+
* only honest rendering of "nobody looked".
|
|
216
|
+
*/
|
|
217
|
+
export function observationText(
|
|
218
|
+
observation: ToolServerObservation,
|
|
219
|
+
t: (key: string, params?: Record<string, unknown>) => string,
|
|
220
|
+
): string | null {
|
|
221
|
+
if (observation === null) return null
|
|
222
|
+
if (observation.kind === 'not_loaded') {
|
|
223
|
+
return t('panels.stepDetail.toolServers.observed.notLoaded')
|
|
224
|
+
}
|
|
225
|
+
if (observation.status !== 'ready') return t(OBSERVED_STATUS_KEY[observation.status])
|
|
226
|
+
if (observation.toolCount === undefined) {
|
|
227
|
+
return t('panels.stepDetail.toolServers.observed.ready')
|
|
228
|
+
}
|
|
229
|
+
// Zero gets its own sentence rather than "0 tools": a server that started and offers nothing is
|
|
230
|
+
// a distinct fault (a narrowed `allowedTools` matching nothing, a vendor that authenticated and
|
|
231
|
+
// served an empty catalog), and it is the one an operator would otherwise never suspect,
|
|
232
|
+
// because every other signal about it says healthy.
|
|
233
|
+
return observation.toolCount === 0
|
|
234
|
+
? t('panels.stepDetail.toolServers.observed.readyNoTools')
|
|
235
|
+
: t('panels.stepDetail.toolServers.observed.readyTools', { count: observation.toolCount })
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Whether an observation should DRAW ATTENTION on the chip: the server was promised to the agent
|
|
240
|
+
* and the CLI could not deliver it.
|
|
241
|
+
*
|
|
242
|
+
* `unknown` is deliberately NOT alarming. It covers a word this build could not map (a fact about
|
|
243
|
+
* this build rather than about the server) and a server the CLI reported as still handshaking when
|
|
244
|
+
* it announced the session (a fact about the moment the report was taken). Neither says anything
|
|
245
|
+
* happened to the server, and painting either as a fault would send an operator to debug a working
|
|
246
|
+
* integration every time a CLI adds a status or starts a server a moment slower.
|
|
247
|
+
*/
|
|
248
|
+
export function observationIsFault(observation: ToolServerObservation): boolean {
|
|
249
|
+
if (observation === null) return false
|
|
250
|
+
if (observation.kind === 'not_loaded') return true
|
|
251
|
+
return observation.status === 'failed' || observation.status === 'needs_auth'
|
|
252
|
+
}
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
// unavailable server is a chip of its own with its own translated reason, never a shorter list of
|
|
9
9
|
// the wired ones: "absent" and "zero" must not render the same.
|
|
10
10
|
//
|
|
11
|
+
// The wired half carries a SECOND answer when the run's harness reported one: what the agent's
|
|
12
|
+
// CLI said about each server when it started up. The two are deliberately not merged into one
|
|
13
|
+
// status — the platform withholding a tool and the CLI failing to start one are different faults
|
|
14
|
+
// for different people — and the absence of a CLI report renders as nothing at all, because
|
|
15
|
+
// "nobody looked" and "the CLI loaded nothing" are opposite facts and only silence states the
|
|
16
|
+
// first one honestly (a codex run, an older image and an unmapped runner pool all reach it).
|
|
17
|
+
//
|
|
11
18
|
// SELF-HIDING when the record holds nothing, like the sibling panels around it. The record is
|
|
12
19
|
// written on EVERY container dispatch, so both lists empty is the state of every step on every
|
|
13
20
|
// deployment that registers no tool servers at all — the overwhelming default. That state is a
|
|
@@ -16,7 +23,14 @@
|
|
|
16
23
|
// section on every step of every run to say nothing happened. The distinction absent-vs-empty is
|
|
17
24
|
// still carried on the wire and answered by the debug API, which is where a reader asking it looks.
|
|
18
25
|
import type { StepToolServers } from '~/types/toolServers'
|
|
19
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
observationFor,
|
|
28
|
+
observationIsFault,
|
|
29
|
+
observationText,
|
|
30
|
+
reasonText,
|
|
31
|
+
remedyText,
|
|
32
|
+
unattributedObservations,
|
|
33
|
+
} from '~/components/panels/StepToolServers.logic'
|
|
20
34
|
import { agentKindMeta } from '~/utils/catalog'
|
|
21
35
|
|
|
22
36
|
const props = defineProps<{
|
|
@@ -33,8 +47,22 @@ const props = defineProps<{
|
|
|
33
47
|
|
|
34
48
|
const { t } = useI18n()
|
|
35
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Servers the CLI named that this dispatch did not wire. Empty on every ordinary run (the CLI runs
|
|
52
|
+
* under `--strict-mcp-config`), and surfaced rather than filtered because the only way to reach it
|
|
53
|
+
* is a report that describes some other job — which must not be presented as this job's clean bill
|
|
54
|
+
* of health.
|
|
55
|
+
*/
|
|
56
|
+
const unattributed = computed(() => unattributedObservations(props.toolServers))
|
|
57
|
+
|
|
36
58
|
const hasAny = computed(
|
|
37
|
-
() =>
|
|
59
|
+
() =>
|
|
60
|
+
props.toolServers.wired.length > 0 ||
|
|
61
|
+
props.toolServers.unavailable.length > 0 ||
|
|
62
|
+
// A report about servers this dispatch did not wire is the one thing worth showing on an
|
|
63
|
+
// otherwise empty record: both lists empty says the dispatched kind declared none, and a CLI
|
|
64
|
+
// naming servers anyway contradicts exactly that.
|
|
65
|
+
unattributed.value.length > 0,
|
|
38
66
|
)
|
|
39
67
|
|
|
40
68
|
/** Set only when a helper re-dispatch owns this record, so the ordinary case renders no extra line. */
|
|
@@ -44,6 +72,22 @@ const dispatchedAs = computed(() =>
|
|
|
44
72
|
: null,
|
|
45
73
|
)
|
|
46
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Each wired server with the CLI's own verdict joined onto it, when a verdict was reported. Bound
|
|
77
|
+
* here for the same reason `drops` is: the join and the absent-vs-not-loaded decision are pure and
|
|
78
|
+
* assertable without mounting, and resolving them once per record beats once per re-render.
|
|
79
|
+
*/
|
|
80
|
+
const wired = computed(() =>
|
|
81
|
+
props.toolServers.wired.map((server) => {
|
|
82
|
+
const observation = observationFor(props.toolServers.observed, server.id)
|
|
83
|
+
return {
|
|
84
|
+
...server,
|
|
85
|
+
observedText: observationText(observation, (key, params) => t(key, params ?? {})),
|
|
86
|
+
isFault: observationIsFault(observation),
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
|
|
47
91
|
/**
|
|
48
92
|
* Each dropped server with both halves of its answer resolved: WHY it was dropped, and what to
|
|
49
93
|
* change so the next run gets it. Bound here rather than called from the template so the pure
|
|
@@ -80,20 +124,59 @@ const drops = computed(() =>
|
|
|
80
124
|
{{ t('panels.stepDetail.toolServers.dispatchedAs', { agent: dispatchedAs }) }}
|
|
81
125
|
</p>
|
|
82
126
|
|
|
83
|
-
<ul v-if="
|
|
127
|
+
<ul v-if="wired.length" class="flex flex-wrap gap-1.5">
|
|
84
128
|
<li
|
|
85
|
-
v-for="server in
|
|
129
|
+
v-for="server in wired"
|
|
86
130
|
:key="server.id"
|
|
87
131
|
data-testid="step-tool-server-wired"
|
|
88
|
-
|
|
132
|
+
:data-observed-fault="server.isFault ? 'true' : undefined"
|
|
133
|
+
class="inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-[12px]"
|
|
134
|
+
:class="
|
|
135
|
+
server.isFault
|
|
136
|
+
? 'border-amber-500/30 bg-amber-500/10 text-amber-200'
|
|
137
|
+
: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-200'
|
|
138
|
+
"
|
|
89
139
|
:title="
|
|
90
140
|
server.tools?.length
|
|
91
141
|
? t('panels.stepDetail.toolServers.narrowed', { tools: server.tools.join(', ') })
|
|
92
142
|
: t('panels.stepDetail.toolServers.allTools')
|
|
93
143
|
"
|
|
94
144
|
>
|
|
95
|
-
<UIcon
|
|
145
|
+
<UIcon
|
|
146
|
+
:name="server.isFault ? 'i-lucide-triangle-alert' : 'i-lucide-check'"
|
|
147
|
+
class="h-3.5 w-3.5"
|
|
148
|
+
/>
|
|
96
149
|
<span>{{ server.label || server.id }}</span>
|
|
150
|
+
<!--
|
|
151
|
+
The CLI's own verdict, when the run's harness reported one. Absent renders NOTHING: a
|
|
152
|
+
harness that publishes no report has said nothing about this server, and a placeholder
|
|
153
|
+
would read as a verdict.
|
|
154
|
+
-->
|
|
155
|
+
<span
|
|
156
|
+
v-if="server.observedText"
|
|
157
|
+
data-testid="step-tool-server-observed"
|
|
158
|
+
class="opacity-80"
|
|
159
|
+
>{{ server.observedText }}</span
|
|
160
|
+
>
|
|
161
|
+
</li>
|
|
162
|
+
</ul>
|
|
163
|
+
|
|
164
|
+
<!--
|
|
165
|
+
A report naming servers this dispatch did not wire can only come from a producer describing
|
|
166
|
+
some other job (a runner-pool manifest mapped at the wrong field). Stated rather than
|
|
167
|
+
filtered, so the rest of the report is not read as authoritative about this run.
|
|
168
|
+
-->
|
|
169
|
+
<ul v-if="unattributed.length" class="mt-2 space-y-1.5">
|
|
170
|
+
<li
|
|
171
|
+
v-for="server in unattributed"
|
|
172
|
+
:key="server.id"
|
|
173
|
+
data-testid="step-tool-server-unattributed"
|
|
174
|
+
class="flex items-start gap-1.5 text-[12px] text-slate-400"
|
|
175
|
+
>
|
|
176
|
+
<UIcon name="i-lucide-circle-help" class="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
|
177
|
+
<span>{{
|
|
178
|
+
t('panels.stepDetail.toolServers.observed.unattributed', { id: server.id })
|
|
179
|
+
}}</span>
|
|
97
180
|
</li>
|
|
98
181
|
</ul>
|
|
99
182
|
|
package/app/types/toolServers.ts
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
// member added on one side only renders as a blank chip rather than failing to compile.
|
|
7
7
|
|
|
8
8
|
export type {
|
|
9
|
+
ObservedToolServer,
|
|
9
10
|
StepToolServers,
|
|
10
11
|
ToolServerUnavailableReason,
|
|
11
12
|
ToolServerAllowedToolsCheck,
|
|
12
13
|
ToolServerCredential,
|
|
13
14
|
ToolServerNotProbeableReason,
|
|
15
|
+
ToolServerObservedStatus,
|
|
14
16
|
ToolServerOAuthGrant,
|
|
15
17
|
ToolServerOAuthStatus,
|
|
16
18
|
ToolServerProbeResult,
|
package/app/utils/runOutcome.ts
CHANGED
|
@@ -27,6 +27,8 @@ export type {
|
|
|
27
27
|
OutcomePullRequest,
|
|
28
28
|
OutcomeRequirement,
|
|
29
29
|
OutcomeRequirements,
|
|
30
|
+
OutcomeSource,
|
|
31
|
+
OutcomeSources,
|
|
30
32
|
OutcomeSpecJoin,
|
|
31
33
|
OutcomeTests,
|
|
32
34
|
OutcomeVisual,
|
|
@@ -34,6 +36,7 @@ export type {
|
|
|
34
36
|
RequirementsGap,
|
|
35
37
|
RunOutcome,
|
|
36
38
|
RunUnavailableGap,
|
|
39
|
+
SourcesGap,
|
|
37
40
|
TestsGap,
|
|
38
41
|
TestsVerdict,
|
|
39
42
|
VisualsGap,
|
package/i18n/locales/de.json
CHANGED
|
@@ -2001,6 +2001,16 @@
|
|
|
2001
2001
|
"oauthNotConnected": "Verbinden Sie dieses Board im Infrastruktur-Fenster damit. Ein Deployment ohne ENCRYPTION_KEY hat keinen Ort für eine Berechtigung, das muss ein Betreiber also zuerst setzen.",
|
|
2002
2002
|
"oauthTokenFailed": "Verbinden Sie es im Infrastruktur-Fenster neu, oder warten Sie die Störung des Anbieters ab.",
|
|
2003
2003
|
"overBudget": "Kürzen Sie, was der Agent deklariert, damit ein Lauf alles mitführen kann."
|
|
2004
|
+
},
|
|
2005
|
+
"observed": {
|
|
2006
|
+
"ready": "gestartet",
|
|
2007
|
+
"readyTools": "{count} Tools",
|
|
2008
|
+
"readyNoTools": "gestartet, keine Tools",
|
|
2009
|
+
"failed": "Start fehlgeschlagen",
|
|
2010
|
+
"needsAuth": "Autorisierung erforderlich",
|
|
2011
|
+
"unknown": "Zustand ungeklärt",
|
|
2012
|
+
"notLoaded": "nie geladen",
|
|
2013
|
+
"unattributed": "Die Agenten-CLI meldete einen Server, den dieser Schritt nicht eingebunden hat: {id}."
|
|
2004
2014
|
}
|
|
2005
2015
|
},
|
|
2006
2016
|
"adherence": {
|
|
@@ -6116,6 +6126,15 @@
|
|
|
6116
6126
|
"title": "Was gefordert war",
|
|
6117
6127
|
"none": "Diese Aufgabe hat keine Beschreibung, es gibt hier also nichts, womit sich das Ergebnis vergleichen ließe."
|
|
6118
6128
|
},
|
|
6129
|
+
"sources": {
|
|
6130
|
+
"title": "Erstellt aus",
|
|
6131
|
+
"unchecked": "Nicht mit der Quelle abgeglichen.",
|
|
6132
|
+
"noSource": "Keine Quellseite zum Abgleichen vorhanden.",
|
|
6133
|
+
"moved": "Diese Seite wurde während des Laufs geändert, frühere Schritte haben also eine ältere Revision gelesen.",
|
|
6134
|
+
"gap": {
|
|
6135
|
+
"none_linked": "Zu diesem Lauf gelangte keine Seite zu den Agenten, sie arbeiteten allein mit der Aufgabenbeschreibung."
|
|
6136
|
+
}
|
|
6137
|
+
},
|
|
6119
6138
|
"gap": {
|
|
6120
6139
|
"run_unavailable": "Der Lauf dieser Aufgabe wurde nicht geladen, deshalb können die von ihm erfassten Belege hier nicht gezeigt werden. Das ist nicht dasselbe wie ein Lauf, der nichts erfasst hat."
|
|
6121
6140
|
},
|
package/i18n/locales/en.json
CHANGED
|
@@ -1535,6 +1535,16 @@
|
|
|
1535
1535
|
"oauthNotConnected": "Connect this board to it from the Infrastructure window. A deployment with no ENCRYPTION_KEY has nowhere to keep a grant, so an operator has to set that first.",
|
|
1536
1536
|
"oauthTokenFailed": "Reconnect it from the Infrastructure window, or wait out the vendor's outage.",
|
|
1537
1537
|
"overBudget": "Trim what the agent declares, so one run can carry all of it."
|
|
1538
|
+
},
|
|
1539
|
+
"observed": {
|
|
1540
|
+
"ready": "started",
|
|
1541
|
+
"readyTools": "{count} tools",
|
|
1542
|
+
"readyNoTools": "started, no tools",
|
|
1543
|
+
"failed": "failed to start",
|
|
1544
|
+
"needsAuth": "needs authorization",
|
|
1545
|
+
"unknown": "state unresolved",
|
|
1546
|
+
"notLoaded": "never loaded",
|
|
1547
|
+
"unattributed": "The agent CLI reported a server this step did not wire: {id}."
|
|
1538
1548
|
}
|
|
1539
1549
|
},
|
|
1540
1550
|
"adherence": {
|
|
@@ -5829,6 +5839,15 @@
|
|
|
5829
5839
|
"title": "What was asked",
|
|
5830
5840
|
"none": "This task carries no description, so there is nothing here to compare the result against."
|
|
5831
5841
|
},
|
|
5842
|
+
"sources": {
|
|
5843
|
+
"title": "Built from",
|
|
5844
|
+
"unchecked": "Not checked against the source.",
|
|
5845
|
+
"noSource": "No source page to compare against.",
|
|
5846
|
+
"moved": "This page changed while the run was in flight, so earlier steps read an older revision.",
|
|
5847
|
+
"gap": {
|
|
5848
|
+
"none_linked": "No page reached this run's agents, so they worked from the task description alone."
|
|
5849
|
+
}
|
|
5850
|
+
},
|
|
5832
5851
|
"gap": {
|
|
5833
5852
|
"run_unavailable": "This task's run was not loaded, so none of the evidence it recorded can be shown here. That is not the same as a run that recorded nothing."
|
|
5834
5853
|
},
|
package/i18n/locales/es.json
CHANGED
|
@@ -1444,6 +1444,16 @@
|
|
|
1444
1444
|
"oauthNotConnected": "Conecta este tablero con él desde la ventana de Infraestructura. Un despliegue sin ENCRYPTION_KEY no tiene dónde guardar una concesión, así que un operador debe configurarla primero.",
|
|
1445
1445
|
"oauthTokenFailed": "Vuelve a conectarlo desde la ventana de Infraestructura, o espera a que pase la caída del proveedor.",
|
|
1446
1446
|
"overBudget": "Recorta lo que declara el agente, para que una ejecución pueda llevarlo todo."
|
|
1447
|
+
},
|
|
1448
|
+
"observed": {
|
|
1449
|
+
"ready": "iniciado",
|
|
1450
|
+
"readyTools": "{count} herramientas",
|
|
1451
|
+
"readyNoTools": "iniciado, sin herramientas",
|
|
1452
|
+
"failed": "no arrancó",
|
|
1453
|
+
"needsAuth": "requiere autorización",
|
|
1454
|
+
"unknown": "estado sin determinar",
|
|
1455
|
+
"notLoaded": "nunca se cargó",
|
|
1456
|
+
"unattributed": "La CLI del agente informó de un servidor que este paso no conectó: {id}."
|
|
1447
1457
|
}
|
|
1448
1458
|
},
|
|
1449
1459
|
"adherence": {
|
|
@@ -5560,6 +5570,15 @@
|
|
|
5560
5570
|
"title": "Qué se pidió",
|
|
5561
5571
|
"none": "Esta tarea no tiene descripción, así que no hay nada con lo que comparar el resultado."
|
|
5562
5572
|
},
|
|
5573
|
+
"sources": {
|
|
5574
|
+
"title": "Creado a partir de",
|
|
5575
|
+
"unchecked": "No se ha comprobado con la fuente.",
|
|
5576
|
+
"noSource": "No hay página de origen con la que comparar.",
|
|
5577
|
+
"moved": "Esta página cambió mientras la ejecución estaba en curso, así que los pasos anteriores leyeron una revisión más antigua.",
|
|
5578
|
+
"gap": {
|
|
5579
|
+
"none_linked": "Ninguna página llegó a los agentes de esta ejecución, así que trabajaron solo con la descripción de la tarea."
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5563
5582
|
"gap": {
|
|
5564
5583
|
"run_unavailable": "La ejecución de esta tarea no se cargó, así que aquí no pueden mostrarse las pruebas que registró. No es lo mismo que una ejecución que no registró nada."
|
|
5565
5584
|
},
|
package/i18n/locales/fr.json
CHANGED
|
@@ -1444,6 +1444,16 @@
|
|
|
1444
1444
|
"oauthNotConnected": "Connectez ce tableau à ce serveur depuis la fenêtre Infrastructure. Un déploiement sans ENCRYPTION_KEY n’a nulle part où conserver une autorisation : un opérateur doit d’abord la définir.",
|
|
1445
1445
|
"oauthTokenFailed": "Reconnectez-le depuis la fenêtre Infrastructure, ou attendez la fin de la panne du fournisseur.",
|
|
1446
1446
|
"overBudget": "Réduisez ce que l’agent déclare, pour qu’une exécution puisse tout transporter."
|
|
1447
|
+
},
|
|
1448
|
+
"observed": {
|
|
1449
|
+
"ready": "démarré",
|
|
1450
|
+
"readyTools": "{count} outils",
|
|
1451
|
+
"readyNoTools": "démarré, aucun outil",
|
|
1452
|
+
"failed": "n'a pas démarré",
|
|
1453
|
+
"needsAuth": "autorisation requise",
|
|
1454
|
+
"unknown": "état non déterminé",
|
|
1455
|
+
"notLoaded": "jamais chargé",
|
|
1456
|
+
"unattributed": "La CLI d'agent a signalé un serveur que cette étape n'a pas branché : {id}."
|
|
1447
1457
|
}
|
|
1448
1458
|
},
|
|
1449
1459
|
"adherence": {
|
|
@@ -5560,6 +5570,15 @@
|
|
|
5560
5570
|
"title": "Ce qui était demandé",
|
|
5561
5571
|
"none": "Cette tâche n'a pas de description, il n'y a donc rien à quoi comparer le résultat."
|
|
5562
5572
|
},
|
|
5573
|
+
"sources": {
|
|
5574
|
+
"title": "Construit à partir de",
|
|
5575
|
+
"unchecked": "Non vérifié auprès de la source.",
|
|
5576
|
+
"noSource": "Aucune page source à laquelle comparer.",
|
|
5577
|
+
"moved": "Cette page a changé pendant l'exécution : les étapes antérieures ont donc lu une révision plus ancienne.",
|
|
5578
|
+
"gap": {
|
|
5579
|
+
"none_linked": "Aucune page n'est parvenue aux agents de cette exécution, ils ont donc travaillé uniquement à partir de la description de la tâche."
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5563
5582
|
"gap": {
|
|
5564
5583
|
"run_unavailable": "L'exécution de cette tâche n'a pas été chargée : les éléments qu'elle a enregistrés ne peuvent donc pas être affichés ici. Ce n'est pas la même chose qu'une exécution qui n'a rien enregistré."
|
|
5565
5584
|
},
|
package/i18n/locales/he.json
CHANGED
|
@@ -1444,6 +1444,16 @@
|
|
|
1444
1444
|
"oauthNotConnected": "חברו את הלוח הזה אליו מחלון התשתית. בפריסה ללא ENCRYPTION_KEY אין היכן לשמור הרשאה, ולכן מפעיל צריך להגדיר אותו קודם.",
|
|
1445
1445
|
"oauthTokenFailed": "חברו אותו מחדש מחלון התשתית, או המתינו לסיום התקלה אצל הספק.",
|
|
1446
1446
|
"overBudget": "צמצמו את מה שהסוכן מצהיר עליו, כדי שריצה אחת תוכל לשאת הכול."
|
|
1447
|
+
},
|
|
1448
|
+
"observed": {
|
|
1449
|
+
"ready": "הופעל",
|
|
1450
|
+
"readyTools": "{count} כלים",
|
|
1451
|
+
"readyNoTools": "הופעל, ללא כלים",
|
|
1452
|
+
"failed": "לא עלה",
|
|
1453
|
+
"needsAuth": "דורש הרשאה",
|
|
1454
|
+
"unknown": "מצב לא ידוע",
|
|
1455
|
+
"notLoaded": "מעולם לא נטען",
|
|
1456
|
+
"unattributed": "ממשק הסוכן דיווח על שרת שהשלב הזה לא חיבר: {id}."
|
|
1447
1457
|
}
|
|
1448
1458
|
},
|
|
1449
1459
|
"adherence": {
|
|
@@ -5560,6 +5570,15 @@
|
|
|
5560
5570
|
"title": "מה התבקש",
|
|
5561
5571
|
"none": "למשימה הזאת אין תיאור, ולכן אין מול מה להשוות את התוצאה."
|
|
5562
5572
|
},
|
|
5573
|
+
"sources": {
|
|
5574
|
+
"title": "נבנה מתוך",
|
|
5575
|
+
"unchecked": "לא נבדק מול המקור.",
|
|
5576
|
+
"noSource": "אין עמוד מקור להשוואה.",
|
|
5577
|
+
"moved": "העמוד הזה השתנה בזמן שההרצה רצה, ולכן שלבים מוקדמים קראו גרסה ישנה יותר.",
|
|
5578
|
+
"gap": {
|
|
5579
|
+
"none_linked": "לא הגיע אף עמוד לסוכנים של הרצה זו, ולכן הם עבדו לפי תיאור המשימה בלבד."
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5563
5582
|
"gap": {
|
|
5564
5583
|
"run_unavailable": "ההרצה של המשימה הזאת לא נטענה, ולכן הראיות שהיא תיעדה לא מוצגות כאן. זה לא אותו דבר כמו הרצה שלא תיעדה כלום."
|
|
5565
5584
|
},
|
package/i18n/locales/it.json
CHANGED
|
@@ -2001,6 +2001,16 @@
|
|
|
2001
2001
|
"oauthNotConnected": "Collega questa lavagna al server dalla finestra Infrastruttura. Un deployment senza ENCRYPTION_KEY non ha dove conservare una concessione, quindi un operatore deve impostarla prima.",
|
|
2002
2002
|
"oauthTokenFailed": "Ricollegalo dalla finestra Infrastruttura, oppure attendi la fine del disservizio del fornitore.",
|
|
2003
2003
|
"overBudget": "Riduci ciò che l'agente dichiara, così una singola esecuzione può portarlo tutto."
|
|
2004
|
+
},
|
|
2005
|
+
"observed": {
|
|
2006
|
+
"ready": "avviato",
|
|
2007
|
+
"readyTools": "{count} strumenti",
|
|
2008
|
+
"readyNoTools": "avviato, nessuno strumento",
|
|
2009
|
+
"failed": "non si è avviato",
|
|
2010
|
+
"needsAuth": "richiede autorizzazione",
|
|
2011
|
+
"unknown": "stato non determinato",
|
|
2012
|
+
"notLoaded": "mai caricato",
|
|
2013
|
+
"unattributed": "La CLI dell'agente ha segnalato un server che questo passo non ha collegato: {id}."
|
|
2004
2014
|
}
|
|
2005
2015
|
},
|
|
2006
2016
|
"adherence": {
|
|
@@ -6116,6 +6126,15 @@
|
|
|
6116
6126
|
"title": "Che cosa era stato chiesto",
|
|
6117
6127
|
"none": "Questa attività non ha una descrizione, quindi non c'è nulla con cui confrontare l'esito."
|
|
6118
6128
|
},
|
|
6129
|
+
"sources": {
|
|
6130
|
+
"title": "Costruito a partire da",
|
|
6131
|
+
"unchecked": "Non verificato con la sorgente.",
|
|
6132
|
+
"noSource": "Nessuna pagina di origine con cui confrontare.",
|
|
6133
|
+
"moved": "Questa pagina è cambiata mentre l'esecuzione era in corso, quindi i passi precedenti hanno letto una revisione più vecchia.",
|
|
6134
|
+
"gap": {
|
|
6135
|
+
"none_linked": "Nessuna pagina è arrivata agli agenti di questa esecuzione, che hanno lavorato solo sulla descrizione dell'attività."
|
|
6136
|
+
}
|
|
6137
|
+
},
|
|
6119
6138
|
"gap": {
|
|
6120
6139
|
"run_unavailable": "L'esecuzione di questa attività non è stata caricata, quindi le prove che ha registrato non possono essere mostrate qui. Non è la stessa cosa di un'esecuzione che non ha registrato nulla."
|
|
6121
6140
|
},
|
package/i18n/locales/ja.json
CHANGED
|
@@ -1444,6 +1444,16 @@
|
|
|
1444
1444
|
"oauthNotConnected": "インフラストラクチャ ウィンドウからこのボードを接続してください。ENCRYPTION_KEY のないデプロイには許可を保管する場所がないため、まず運用者がそれを設定する必要があります。",
|
|
1445
1445
|
"oauthTokenFailed": "インフラストラクチャ ウィンドウから接続し直すか、提供元の障害が収まるのを待ってください。",
|
|
1446
1446
|
"overBudget": "1 回の実行ですべて運べるよう、このエージェントの宣言を減らしてください。"
|
|
1447
|
+
},
|
|
1448
|
+
"observed": {
|
|
1449
|
+
"ready": "起動済み",
|
|
1450
|
+
"readyTools": "ツール {count} 個",
|
|
1451
|
+
"readyNoTools": "起動済み、ツールなし",
|
|
1452
|
+
"failed": "起動に失敗",
|
|
1453
|
+
"needsAuth": "認可が必要",
|
|
1454
|
+
"unknown": "状態は未確定",
|
|
1455
|
+
"notLoaded": "読み込まれませんでした",
|
|
1456
|
+
"unattributed": "エージェント CLI が、このステップでは接続していないサーバーを報告しました: {id}。"
|
|
1447
1457
|
}
|
|
1448
1458
|
},
|
|
1449
1459
|
"adherence": {
|
|
@@ -5560,6 +5570,15 @@
|
|
|
5560
5570
|
"title": "依頼内容",
|
|
5561
5571
|
"none": "このタスクには説明がないため、結果と照らし合わせるものがありません。"
|
|
5562
5572
|
},
|
|
5573
|
+
"sources": {
|
|
5574
|
+
"title": "参照した資料",
|
|
5575
|
+
"unchecked": "ソースとの照合は行われていません。",
|
|
5576
|
+
"noSource": "比較できるソースページがありません。",
|
|
5577
|
+
"moved": "このページは実行中に変更されたため、前半のステップは古いリビジョンを読んでいます。",
|
|
5578
|
+
"gap": {
|
|
5579
|
+
"none_linked": "この実行のエージェントにはページが渡されなかったため、タスクの説明だけを頼りに作業しました。"
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5563
5582
|
"gap": {
|
|
5564
5583
|
"run_unavailable": "このタスクの実行が読み込まれていないため、そこに記録された根拠をここには表示できません。何も記録しなかった実行とは別のことです。"
|
|
5565
5584
|
},
|
package/i18n/locales/pl.json
CHANGED
|
@@ -1444,6 +1444,16 @@
|
|
|
1444
1444
|
"oauthNotConnected": "Połącz tę tablicę z serwerem w oknie Infrastruktura. Wdrożenie bez ENCRYPTION_KEY nie ma gdzie przechować zgody, więc operator musi ją najpierw ustawić.",
|
|
1445
1445
|
"oauthTokenFailed": "Połącz go ponownie w oknie Infrastruktura albo przeczekaj awarię dostawcy.",
|
|
1446
1446
|
"overBudget": "Skróć to, co deklaruje agent, aby jedno uruchomienie uniosło całość."
|
|
1447
|
+
},
|
|
1448
|
+
"observed": {
|
|
1449
|
+
"ready": "uruchomiony",
|
|
1450
|
+
"readyTools": "narzędzia: {count}",
|
|
1451
|
+
"readyNoTools": "uruchomiony, bez narzędzi",
|
|
1452
|
+
"failed": "nie wystartował",
|
|
1453
|
+
"needsAuth": "wymaga autoryzacji",
|
|
1454
|
+
"unknown": "stan nieustalony",
|
|
1455
|
+
"notLoaded": "nigdy nie wczytany",
|
|
1456
|
+
"unattributed": "CLI agenta zgłosiło serwer, którego ten krok nie podłączył: {id}."
|
|
1447
1457
|
}
|
|
1448
1458
|
},
|
|
1449
1459
|
"adherence": {
|
|
@@ -5560,6 +5570,15 @@
|
|
|
5560
5570
|
"title": "O co proszono",
|
|
5561
5571
|
"none": "To zadanie nie ma opisu, więc nie ma do czego porównać wyniku."
|
|
5562
5572
|
},
|
|
5573
|
+
"sources": {
|
|
5574
|
+
"title": "Zbudowano na podstawie",
|
|
5575
|
+
"unchecked": "Nie sprawdzono ze źródłem.",
|
|
5576
|
+
"noSource": "Brak strony źródłowej do porównania.",
|
|
5577
|
+
"moved": "Ta strona zmieniła się w trakcie uruchomienia, więc wcześniejsze kroki czytały starszą wersję.",
|
|
5578
|
+
"gap": {
|
|
5579
|
+
"none_linked": "Do agentów tego przebiegu nie trafiła żadna strona, więc pracowali wyłącznie na opisie zadania."
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5563
5582
|
"gap": {
|
|
5564
5583
|
"run_unavailable": "Przebieg tego zadania nie został wczytany, więc zapisane w nim dowody nie są tutaj pokazane. To co innego niż przebieg, który niczego nie zapisał."
|
|
5565
5584
|
},
|
package/i18n/locales/tr.json
CHANGED
|
@@ -1444,6 +1444,16 @@
|
|
|
1444
1444
|
"oauthNotConnected": "Bu panoyu Altyapı penceresinden ona bağlayın. ENCRYPTION_KEY olmayan bir dağıtımda izni saklayacak bir yer yoktur, bu yüzden önce bir operatörün bunu ayarlaması gerekir.",
|
|
1445
1445
|
"oauthTokenFailed": "Altyapı penceresinden yeniden bağlayın ya da sağlayıcının kesintisinin geçmesini bekleyin.",
|
|
1446
1446
|
"overBudget": "Tek bir çalıştırma hepsini taşıyabilsin diye ajanın bildirdiklerini kısaltın."
|
|
1447
|
+
},
|
|
1448
|
+
"observed": {
|
|
1449
|
+
"ready": "başlatıldı",
|
|
1450
|
+
"readyTools": "{count} araç",
|
|
1451
|
+
"readyNoTools": "başlatıldı, araç yok",
|
|
1452
|
+
"failed": "başlatılamadı",
|
|
1453
|
+
"needsAuth": "yetkilendirme gerekiyor",
|
|
1454
|
+
"unknown": "durum belirsiz",
|
|
1455
|
+
"notLoaded": "hiç yüklenmedi",
|
|
1456
|
+
"unattributed": "Ajan CLI'si bu adımın bağlamadığı bir sunucu bildirdi: {id}."
|
|
1447
1457
|
}
|
|
1448
1458
|
},
|
|
1449
1459
|
"adherence": {
|
|
@@ -5560,6 +5570,15 @@
|
|
|
5560
5570
|
"title": "Ne istenmişti",
|
|
5561
5571
|
"none": "Bu görevin açıklaması yok, dolayısıyla sonucu karşılaştıracak bir şey de yok."
|
|
5562
5572
|
},
|
|
5573
|
+
"sources": {
|
|
5574
|
+
"title": "Şuradan üretildi",
|
|
5575
|
+
"unchecked": "Kaynakla karşılaştırılmadı.",
|
|
5576
|
+
"noSource": "Karşılaştırılacak bir kaynak sayfa yok.",
|
|
5577
|
+
"moved": "Bu sayfa çalışma sürerken değişti, bu yüzden önceki adımlar daha eski bir sürümü okudu.",
|
|
5578
|
+
"gap": {
|
|
5579
|
+
"none_linked": "Bu çalıştırmanın ajanlarına hiçbir sayfa ulaşmadı, bu yüzden yalnızca görev açıklamasıyla çalıştılar."
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5563
5582
|
"gap": {
|
|
5564
5583
|
"run_unavailable": "Bu görevin çalışması yüklenmediği için kaydettiği kanıtlar burada gösterilemiyor. Bu, hiçbir şey kaydetmemiş bir çalışmayla aynı şey değildir."
|
|
5565
5584
|
},
|
package/i18n/locales/uk.json
CHANGED
|
@@ -1444,6 +1444,16 @@
|
|
|
1444
1444
|
"oauthNotConnected": "Під’єднайте цю дошку до нього у вікні інфраструктури. Розгортання без ENCRYPTION_KEY не має де зберігати дозвіл, тож оператор має спершу задати його.",
|
|
1445
1445
|
"oauthTokenFailed": "Під’єднайте його заново у вікні інфраструктури або перечекайте збій постачальника.",
|
|
1446
1446
|
"overBudget": "Скоротіть те, що оголошує агент, щоб один запуск ніс усе."
|
|
1447
|
+
},
|
|
1448
|
+
"observed": {
|
|
1449
|
+
"ready": "запущено",
|
|
1450
|
+
"readyTools": "інструментів: {count}",
|
|
1451
|
+
"readyNoTools": "запущено, без інструментів",
|
|
1452
|
+
"failed": "не запустився",
|
|
1453
|
+
"needsAuth": "потрібна авторизація",
|
|
1454
|
+
"unknown": "стан невизначений",
|
|
1455
|
+
"notLoaded": "жодного разу не завантажено",
|
|
1456
|
+
"unattributed": "CLI агента повідомив про сервер, який цей крок не під’єднував: {id}."
|
|
1447
1457
|
}
|
|
1448
1458
|
},
|
|
1449
1459
|
"adherence": {
|
|
@@ -5560,6 +5570,15 @@
|
|
|
5560
5570
|
"title": "Про що просили",
|
|
5561
5571
|
"none": "У цього завдання немає опису, тож немає з чим порівняти результат."
|
|
5562
5572
|
},
|
|
5573
|
+
"sources": {
|
|
5574
|
+
"title": "Створено на основі",
|
|
5575
|
+
"unchecked": "Не звірено з джерелом.",
|
|
5576
|
+
"noSource": "Немає сторінки джерела для звіряння.",
|
|
5577
|
+
"moved": "Ця сторінка змінилася під час виконання, тож попередні кроки читали давнішу редакцію.",
|
|
5578
|
+
"gap": {
|
|
5579
|
+
"none_linked": "До агентів цього запуску не потрапила жодна сторінка, тож вони працювали лише з опису завдання."
|
|
5580
|
+
}
|
|
5581
|
+
},
|
|
5563
5582
|
"gap": {
|
|
5564
5583
|
"run_unavailable": "Запуск цього завдання не завантажено, тому зібрані ним докази тут не показані. Це не те саме, що запуск, який нічого не зібрав."
|
|
5565
5584
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.245.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.40",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.267.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|