@cat-factory/app 0.50.0 → 0.51.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/panels/StepContainerStatus.vue +116 -0
- package/app/components/panels/StepMetadataCard.vue +4 -9
- package/app/components/panels/inspector/ServiceFragments.vue +31 -5
- package/app/components/panels/inspector/TaskExecution.vue +19 -6
- package/app/components/panels/inspector/TaskStructure.vue +31 -5
- package/app/components/pipeline/PipelineProgress.vue +25 -7
- package/app/components/testing/TestReportWindow.vue +129 -4
- package/app/types/execution.ts +2 -0
- package/app/utils/pipelineRender.ts +18 -0
- package/i18n/locales/en.json +27 -1
- package/i18n/locales/es.json +28 -2
- package/i18n/locales/fr.json +28 -2
- package/i18n/locales/pl.json +28 -2
- package/i18n/locales/uk.json +28 -2
- package/package.json +2 -2
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { PipelineStep, RunContainerStatus } from '~/types/execution'
|
|
4
|
+
import { containerPhaseLabel } from '~/utils/pipelineRender'
|
|
5
|
+
|
|
6
|
+
// The per-run container lifecycle for a container-backed step: its status (spinning up /
|
|
7
|
+
// running / errored / reclaimed), the live phase (preparing the checkout vs the agent
|
|
8
|
+
// making calls), and the container's id + reachable URL once up. Shared by the generic
|
|
9
|
+
// step detail (StepMetadataCard) and the dedicated Tester window so both surface WHAT the
|
|
10
|
+
// container is doing and WHERE it lives instead of a bare "working" — identical parity.
|
|
11
|
+
const props = defineProps<{ step: PipelineStep; runFailed: boolean }>()
|
|
12
|
+
|
|
13
|
+
const { t, te } = useI18n()
|
|
14
|
+
|
|
15
|
+
// The container lifecycle to display. The backend persists starting / up / errored; we
|
|
16
|
+
// derive `destroyed` once the container is reclaimed — when this step has finished or the
|
|
17
|
+
// whole run is no longer running (the per-run container goes as a unit). `errored` wins,
|
|
18
|
+
// and a reclaimed container ALWAYS reads "destroyed" — even one caught mid cold-boot
|
|
19
|
+
// (`starting`) when the run terminated must NOT linger as a perpetual spinner.
|
|
20
|
+
const containerStatus = computed<RunContainerStatus | null>(() => {
|
|
21
|
+
const c = props.step.container
|
|
22
|
+
if (!c) return null
|
|
23
|
+
if (c.status === 'errored') return 'errored'
|
|
24
|
+
if (props.step.state === 'done' || props.runFailed) return 'destroyed'
|
|
25
|
+
return c.status
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Static literal keys (not a runtime-built `t(`…${status}`)`) so the typed-message-keys
|
|
29
|
+
// check covers them; exhaustive over the union so a new status fails the typecheck here.
|
|
30
|
+
const CONTAINER_STATUS_KEYS: Record<RunContainerStatus, string> = {
|
|
31
|
+
starting: 'panels.stepMeta.container.status.starting',
|
|
32
|
+
up: 'panels.stepMeta.container.status.up',
|
|
33
|
+
errored: 'panels.stepMeta.container.status.errored',
|
|
34
|
+
destroyed: 'panels.stepMeta.container.status.destroyed',
|
|
35
|
+
}
|
|
36
|
+
const CONTAINER_STATUS_META: Record<
|
|
37
|
+
RunContainerStatus,
|
|
38
|
+
{ icon: string; spin: boolean; cls: string }
|
|
39
|
+
> = {
|
|
40
|
+
starting: {
|
|
41
|
+
icon: 'i-lucide-loader-circle',
|
|
42
|
+
spin: true,
|
|
43
|
+
cls: 'border-sky-900/50 bg-sky-950/30 text-sky-300',
|
|
44
|
+
},
|
|
45
|
+
up: {
|
|
46
|
+
icon: 'i-lucide-box',
|
|
47
|
+
spin: false,
|
|
48
|
+
cls: 'border-emerald-900/50 bg-emerald-950/30 text-emerald-300',
|
|
49
|
+
},
|
|
50
|
+
errored: {
|
|
51
|
+
icon: 'i-lucide-circle-x',
|
|
52
|
+
spin: false,
|
|
53
|
+
cls: 'border-rose-900/50 bg-rose-950/30 text-rose-300',
|
|
54
|
+
},
|
|
55
|
+
destroyed: {
|
|
56
|
+
icon: 'i-lucide-power-off',
|
|
57
|
+
spin: false,
|
|
58
|
+
cls: 'border-slate-800 bg-slate-900/40 text-slate-400',
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The friendly phase label (clone → "Preparing workspace", …); only meaningful while up.
|
|
63
|
+
const phaseLabel = computed(() => containerPhaseLabel(props.step.container?.phase, { t, te }))
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<!-- Single conditional root so a passed-through `class` (e.g. layout margin) applies
|
|
68
|
+
cleanly. Renders nothing for a non-container step / one not yet dispatched. -->
|
|
69
|
+
<div v-if="containerStatus" data-testid="step-container-status">
|
|
70
|
+
<!-- container lifecycle: status (spinning up / running / errored / reclaimed), the
|
|
71
|
+
live phase (preparing the checkout vs the agent making calls), and the
|
|
72
|
+
container's id + reachable URL once up. -->
|
|
73
|
+
<div
|
|
74
|
+
class="rounded-lg border px-3 py-2 text-[12px]"
|
|
75
|
+
:class="CONTAINER_STATUS_META[containerStatus].cls"
|
|
76
|
+
>
|
|
77
|
+
<div class="flex items-center gap-2">
|
|
78
|
+
<UIcon
|
|
79
|
+
:name="CONTAINER_STATUS_META[containerStatus].icon"
|
|
80
|
+
class="h-4 w-4 shrink-0"
|
|
81
|
+
:class="CONTAINER_STATUS_META[containerStatus].spin ? 'animate-spin' : ''"
|
|
82
|
+
/>
|
|
83
|
+
<span class="font-medium">{{ t(CONTAINER_STATUS_KEYS[containerStatus]) }}</span>
|
|
84
|
+
<template v-if="phaseLabel && containerStatus === 'up'">
|
|
85
|
+
<span class="text-slate-500">·</span>
|
|
86
|
+
<span>{{ phaseLabel }}</span>
|
|
87
|
+
</template>
|
|
88
|
+
</div>
|
|
89
|
+
<dl v-if="step.container?.id || step.container?.url" class="mt-2 space-y-1">
|
|
90
|
+
<div v-if="step.container?.id" class="flex items-center gap-2">
|
|
91
|
+
<dt class="shrink-0 text-[11px] uppercase tracking-wide text-slate-500">
|
|
92
|
+
{{ t('panels.stepMeta.container.id') }}
|
|
93
|
+
</dt>
|
|
94
|
+
<dd class="truncate font-mono text-[11px] text-slate-300" :title="step.container.id">
|
|
95
|
+
{{ step.container.id }}
|
|
96
|
+
</dd>
|
|
97
|
+
</div>
|
|
98
|
+
<div v-if="step.container?.url" class="flex items-center gap-2">
|
|
99
|
+
<dt class="shrink-0 text-[11px] uppercase tracking-wide text-slate-500">
|
|
100
|
+
{{ t('panels.stepMeta.container.url') }}
|
|
101
|
+
</dt>
|
|
102
|
+
<dd class="truncate font-mono text-[11px] text-slate-300">
|
|
103
|
+
<a
|
|
104
|
+
:href="step.container.url"
|
|
105
|
+
target="_blank"
|
|
106
|
+
rel="noopener noreferrer"
|
|
107
|
+
class="hover:underline"
|
|
108
|
+
>
|
|
109
|
+
{{ step.container.url }}
|
|
110
|
+
</a>
|
|
111
|
+
</dd>
|
|
112
|
+
</div>
|
|
113
|
+
</dl>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</template>
|
|
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|
|
3
3
|
import type { AgentState, PipelineStep, CompanionVerdict, StepApproval } from '~/types/execution'
|
|
4
4
|
import { subtaskIconClass } from '~/utils/pipelineRender'
|
|
5
5
|
import StepModelActivity from '~/components/observability/StepModelActivity.vue'
|
|
6
|
+
import StepContainerStatus from '~/components/panels/StepContainerStatus.vue'
|
|
6
7
|
|
|
7
8
|
// The step's metadata card body: state/timing/model/run id, the container cold-boot
|
|
8
9
|
// phase, the live subtask breakdown, the LLM observability rollup, the applied
|
|
@@ -153,15 +154,9 @@ async function copyRunId() {
|
|
|
153
154
|
</div>
|
|
154
155
|
</dl>
|
|
155
156
|
|
|
156
|
-
<!-- container
|
|
157
|
-
the
|
|
158
|
-
<
|
|
159
|
-
v-if="step.startingContainer && !runFailed"
|
|
160
|
-
class="mt-4 flex items-center gap-2 rounded-lg border border-sky-900/50 bg-sky-950/30 px-3 py-2 text-[12px] text-sky-300"
|
|
161
|
-
>
|
|
162
|
-
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 shrink-0 animate-spin" />
|
|
163
|
-
<span>{{ t('panels.stepMeta.spinningUpContainer') }}</span>
|
|
164
|
-
</div>
|
|
157
|
+
<!-- container lifecycle (status / live phase / id + url) — shared with the Tester
|
|
158
|
+
window so both surface what the container is doing and where it lives. -->
|
|
159
|
+
<StepContainerStatus :step="step" :run-failed="runFailed" class="mt-4" />
|
|
165
160
|
|
|
166
161
|
<!-- live subtask breakdown -->
|
|
167
162
|
<div v-if="step.subtasks && step.subtasks.total > 0" class="mt-4">
|
|
@@ -9,27 +9,53 @@ const props = defineProps<{ block: Block }>()
|
|
|
9
9
|
|
|
10
10
|
const board = useBoardStore()
|
|
11
11
|
const fragments = useFragmentsStore()
|
|
12
|
+
const ui = useUiStore()
|
|
13
|
+
const accounts = useAccountsStore()
|
|
12
14
|
const { t } = useI18n()
|
|
13
15
|
|
|
14
16
|
onMounted(() => fragments.ensureLoaded())
|
|
15
17
|
|
|
18
|
+
type MenuItem = { label: string; icon?: string; onSelect: () => void }
|
|
19
|
+
|
|
16
20
|
const selectedFragments = computed(() =>
|
|
17
21
|
(props.block.serviceFragmentIds ?? [])
|
|
18
22
|
.map((id) => fragments.getFragment(id))
|
|
19
23
|
.filter((f): f is NonNullable<typeof f> => !!f),
|
|
20
24
|
)
|
|
21
25
|
|
|
22
|
-
//
|
|
23
|
-
|
|
26
|
+
// A trailing group that jumps from "attach a fragment" to authoring/editing the
|
|
27
|
+
// library itself (board tier always; account tier when accounts are enabled).
|
|
28
|
+
// Open to every member — managing fragments is not an admin-only action.
|
|
29
|
+
const manageItems = computed<MenuItem[]>(() => {
|
|
30
|
+
const items: MenuItem[] = [
|
|
31
|
+
{
|
|
32
|
+
label: t('inspector.fragments.manageBoard'),
|
|
33
|
+
icon: 'i-lucide-book-marked',
|
|
34
|
+
onSelect: () => ui.openFragmentLibrary(),
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
if (accounts.enabled) {
|
|
38
|
+
items.push({
|
|
39
|
+
label: t('inspector.fragments.manageAccount'),
|
|
40
|
+
icon: 'i-lucide-users',
|
|
41
|
+
onSelect: () => ui.openAccountSettings('fragments'),
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
return items
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Picker menu: every pool fragment not already selected, grouped by category, with
|
|
48
|
+
// the management links appended as the final group.
|
|
49
|
+
const fragmentMenu = computed<MenuItem[][]>(() => {
|
|
24
50
|
const selected = new Set(props.block.serviceFragmentIds ?? [])
|
|
25
|
-
const groups = new Map<string,
|
|
51
|
+
const groups = new Map<string, MenuItem[]>()
|
|
26
52
|
for (const f of fragments.fragments) {
|
|
27
53
|
if (selected.has(f.id)) continue
|
|
28
54
|
const items = groups.get(f.category) ?? []
|
|
29
55
|
items.push({ label: f.title, onSelect: () => addFragment(f.id) })
|
|
30
56
|
groups.set(f.category, items)
|
|
31
57
|
}
|
|
32
|
-
return [...groups.values()]
|
|
58
|
+
return [...groups.values(), manageItems.value]
|
|
33
59
|
})
|
|
34
60
|
|
|
35
61
|
function addFragment(id: string) {
|
|
@@ -52,7 +78,7 @@ function removeFragment(id: string) {
|
|
|
52
78
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
53
79
|
{{ t('inspector.fragments.serviceTitle') }}
|
|
54
80
|
</span>
|
|
55
|
-
<UDropdownMenu
|
|
81
|
+
<UDropdownMenu :items="fragmentMenu">
|
|
56
82
|
<UButton
|
|
57
83
|
size="xs"
|
|
58
84
|
variant="ghost"
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Block } from '~/types/domain'
|
|
3
3
|
import { agentKindMeta } from '~/utils/catalog'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
gateCompanionFor,
|
|
6
|
+
COMPANION_STATE_META,
|
|
7
|
+
isCompanionKind,
|
|
8
|
+
containerPhaseLabel,
|
|
9
|
+
} from '~/utils/pipelineRender'
|
|
5
10
|
import AgentFailureCard from '~/components/board/AgentFailureCard.vue'
|
|
6
11
|
|
|
7
12
|
const props = defineProps<{ block: Block }>()
|
|
@@ -11,7 +16,7 @@ const agentRuns = useAgentRunsStore()
|
|
|
11
16
|
const ui = useUiStore()
|
|
12
17
|
const models = useModelsStore()
|
|
13
18
|
const reviews = useReviewStage()
|
|
14
|
-
const { t } = useI18n()
|
|
19
|
+
const { t, te } = useI18n()
|
|
15
20
|
|
|
16
21
|
// The async stage this task's iterative reviewer gate (requirements-review / clarity-review)
|
|
17
22
|
// is mid-cycle in (folding the answers, then re-reviewing), or null. While set, the gate is
|
|
@@ -68,7 +73,7 @@ function labelForStep(s: {
|
|
|
68
73
|
agentKind?: string
|
|
69
74
|
approval?: { status: string } | null
|
|
70
75
|
companion?: { exceeded?: boolean } | null
|
|
71
|
-
|
|
76
|
+
container?: { status: string; phase?: string | null } | null
|
|
72
77
|
}) {
|
|
73
78
|
// A step left mid-flight on a failed run reads "Failed", not the misleading "Working".
|
|
74
79
|
if (stepFailed(s)) return t('inspector.execution.failed')
|
|
@@ -79,9 +84,17 @@ function labelForStep(s: {
|
|
|
79
84
|
if (s.approval?.status === 'pending' && s.companion?.exceeded)
|
|
80
85
|
return t('inspector.execution.needsDecision')
|
|
81
86
|
if (s.approval?.status === 'pending') return t('inspector.execution.needsApproval')
|
|
82
|
-
// A container-backed step
|
|
83
|
-
//
|
|
84
|
-
|
|
87
|
+
// A container-backed step: surface its live lifecycle while the run is running. The
|
|
88
|
+
// container is still cold-booting → "Spinning up"; up with a known phase → the phase
|
|
89
|
+
// label ("Agent running" / "Preparing workspace"), so a finished cold-boot no longer
|
|
90
|
+
// collapses into a blank "Working". A failed run's mid-flight step isn't booting.
|
|
91
|
+
if (!runFailed.value) {
|
|
92
|
+
if (s.container?.status === 'starting') return t('inspector.execution.spinningUp')
|
|
93
|
+
if (s.container?.status === 'up') {
|
|
94
|
+
const label = containerPhaseLabel(s.container.phase, { t, te })
|
|
95
|
+
if (label) return label
|
|
96
|
+
}
|
|
97
|
+
}
|
|
85
98
|
const key = stepLabel[s.state]
|
|
86
99
|
return key ? t(key) : s.state
|
|
87
100
|
}
|
|
@@ -5,8 +5,12 @@ const props = defineProps<{ block: Block }>()
|
|
|
5
5
|
|
|
6
6
|
const board = useBoardStore()
|
|
7
7
|
const fragments = useFragmentsStore()
|
|
8
|
+
const ui = useUiStore()
|
|
9
|
+
const accounts = useAccountsStore()
|
|
8
10
|
const { t } = useI18n()
|
|
9
11
|
|
|
12
|
+
type MenuItem = { label: string; icon?: string; onSelect: () => void }
|
|
13
|
+
|
|
10
14
|
// ---- best-practice prompt fragments ----------------------------------------
|
|
11
15
|
// Selected fragments (resolved against the catalog; unknown ids are dropped).
|
|
12
16
|
const selectedFragments = computed(() =>
|
|
@@ -15,18 +19,40 @@ const selectedFragments = computed(() =>
|
|
|
15
19
|
.filter((f): f is NonNullable<typeof f> => !!f),
|
|
16
20
|
)
|
|
17
21
|
|
|
22
|
+
// A trailing group that jumps from "attach a fragment" to authoring/editing the
|
|
23
|
+
// library itself (board tier always; account tier when accounts are enabled).
|
|
24
|
+
// Open to every member — managing fragments is not an admin-only action.
|
|
25
|
+
const manageItems = computed<MenuItem[]>(() => {
|
|
26
|
+
const items: MenuItem[] = [
|
|
27
|
+
{
|
|
28
|
+
label: t('inspector.fragments.manageBoard'),
|
|
29
|
+
icon: 'i-lucide-book-marked',
|
|
30
|
+
onSelect: () => ui.openFragmentLibrary(),
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
if (accounts.enabled) {
|
|
34
|
+
items.push({
|
|
35
|
+
label: t('inspector.fragments.manageAccount'),
|
|
36
|
+
icon: 'i-lucide-users',
|
|
37
|
+
onSelect: () => ui.openAccountSettings('fragments'),
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
return items
|
|
41
|
+
})
|
|
42
|
+
|
|
18
43
|
// Picker menu: fragments suitable for this block's type, not already selected,
|
|
19
|
-
// grouped by category so the dropdown reads like the catalog
|
|
20
|
-
|
|
44
|
+
// grouped by category so the dropdown reads like the catalog, with the management
|
|
45
|
+
// links appended as the final group.
|
|
46
|
+
const fragmentMenu = computed<MenuItem[][]>(() => {
|
|
21
47
|
const selected = new Set(props.block.fragmentIds ?? [])
|
|
22
|
-
const groups = new Map<string,
|
|
48
|
+
const groups = new Map<string, MenuItem[]>()
|
|
23
49
|
for (const f of fragments.forBlockType(props.block.type)) {
|
|
24
50
|
if (selected.has(f.id)) continue
|
|
25
51
|
const items = groups.get(f.category) ?? []
|
|
26
52
|
items.push({ label: f.title, onSelect: () => addFragment(f.id) })
|
|
27
53
|
groups.set(f.category, items)
|
|
28
54
|
}
|
|
29
|
-
return [...groups.values()]
|
|
55
|
+
return [...groups.values(), manageItems.value]
|
|
30
56
|
})
|
|
31
57
|
|
|
32
58
|
function addFragment(id: string) {
|
|
@@ -65,7 +91,7 @@ function removeFragment(id: string) {
|
|
|
65
91
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
66
92
|
{{ t('inspector.structure.bestPractices') }}
|
|
67
93
|
</span>
|
|
68
|
-
<UDropdownMenu
|
|
94
|
+
<UDropdownMenu :items="fragmentMenu">
|
|
69
95
|
<UButton
|
|
70
96
|
size="xs"
|
|
71
97
|
variant="ghost"
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isCompanionKind,
|
|
10
10
|
isFailedStep,
|
|
11
11
|
FAILED_STEP_META,
|
|
12
|
+
containerPhaseLabel,
|
|
12
13
|
} from '~/utils/pipelineRender'
|
|
13
14
|
import StepMetricsBar from '~/components/observability/StepMetricsBar.vue'
|
|
14
15
|
|
|
@@ -22,7 +23,15 @@ const models = useModelsStore()
|
|
|
22
23
|
const ui = useUiStore()
|
|
23
24
|
const execution = useExecutionStore()
|
|
24
25
|
const reviews = useReviewStage()
|
|
25
|
-
const { t } = useI18n()
|
|
26
|
+
const { t, te } = useI18n()
|
|
27
|
+
|
|
28
|
+
// The friendly container phase label for a step whose container is up — null otherwise.
|
|
29
|
+
// Lets the board fill the gap between the cold-boot badge clearing and the first subtask
|
|
30
|
+
// count (the old "blank working"). Shared with the step-detail card + inspector label.
|
|
31
|
+
function stepPhaseLabel(s: { container?: { status: string; phase?: string | null } | null }) {
|
|
32
|
+
if (s.container?.status !== 'up') return null
|
|
33
|
+
return containerPhaseLabel(s.container.phase, { t, te })
|
|
34
|
+
}
|
|
26
35
|
|
|
27
36
|
// While an iterative reviewer gate (requirements-review / clarity-review) folds the
|
|
28
37
|
// answers / re-reviews in the background it needs NO human, so its parked approval is
|
|
@@ -126,9 +135,9 @@ const total = computed(() => steps.value.length)
|
|
|
126
135
|
// human can see at a glance whether the fixer ran or was skipped.
|
|
127
136
|
const companionByStep = computed(() => steps.value.map((s) => gateCompanionFor(s, runFailed.value)))
|
|
128
137
|
|
|
129
|
-
// A failed run is no longer executing: a step left mid-flight (state still
|
|
130
|
-
//
|
|
131
|
-
// no
|
|
138
|
+
// A failed run is no longer executing: a step left mid-flight (state still `working`,
|
|
139
|
+
// its container caught mid cold-boot) must stop looking live — no spinner, no pulse,
|
|
140
|
+
// no "spinning up container" phase.
|
|
132
141
|
const runFailed = computed(() => props.instance.status === 'failed')
|
|
133
142
|
/** A step that is genuinely, currently working (not a stale mid-flight step). */
|
|
134
143
|
function liveWorking(state: AgentState) {
|
|
@@ -356,16 +365,25 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
356
365
|
class="mt-3"
|
|
357
366
|
/>
|
|
358
367
|
|
|
359
|
-
<!-- container cold-boot phase: shown
|
|
360
|
-
agent starts reporting progress -->
|
|
368
|
+
<!-- container cold-boot phase: shown while the container is spinning up. -->
|
|
361
369
|
<div
|
|
362
|
-
v-if="s.
|
|
370
|
+
v-if="s.container?.status === 'starting' && !runFailed"
|
|
363
371
|
class="mt-2 flex items-center gap-1.5 text-[11px] text-sky-300"
|
|
364
372
|
>
|
|
365
373
|
<UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 shrink-0 animate-spin" />
|
|
366
374
|
<span>{{ t('pipeline.progress.spinningUpContainer') }}</span>
|
|
367
375
|
</div>
|
|
368
376
|
|
|
377
|
+
<!-- container is up: show WHAT it's doing (preparing the checkout vs the agent
|
|
378
|
+
making calls) so the step isn't a blank "working" before subtasks appear. -->
|
|
379
|
+
<div
|
|
380
|
+
v-else-if="stepPhaseLabel(s) && !runFailed"
|
|
381
|
+
class="mt-2 flex items-center gap-1.5 text-[11px] text-emerald-300"
|
|
382
|
+
>
|
|
383
|
+
<UIcon name="i-lucide-box" class="h-3.5 w-3.5 shrink-0" />
|
|
384
|
+
<span>{{ stepPhaseLabel(s) }}</span>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
369
387
|
<!-- live subtask counts from the agent's todo list -->
|
|
370
388
|
<div v-if="s.subtasks && s.subtasks.total > 0" class="mt-2">
|
|
371
389
|
<div class="flex items-center justify-between text-[10px] text-slate-400">
|
|
@@ -17,10 +17,13 @@ import { useFocusTrap } from '~/composables/useFocusTrap'
|
|
|
17
17
|
import ArtifactLightbox from '~/components/media/ArtifactLightbox.vue'
|
|
18
18
|
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
19
19
|
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
20
|
+
import StepContainerStatus from '~/components/panels/StepContainerStatus.vue'
|
|
21
|
+
import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
|
|
22
|
+
import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
|
|
20
23
|
|
|
21
24
|
const board = useBoardStore()
|
|
22
25
|
const execution = useExecutionStore()
|
|
23
|
-
const { t } = useI18n()
|
|
26
|
+
const { t, d } = useI18n()
|
|
24
27
|
|
|
25
28
|
// Per-window blob cache for the captured screenshots; revoked on unmount.
|
|
26
29
|
const blobs = useArtifactBlobs()
|
|
@@ -40,6 +43,20 @@ const step = computed(() => {
|
|
|
40
43
|
})
|
|
41
44
|
const report = computed<TestReport | null>(() => step.value?.test?.lastReport ?? null)
|
|
42
45
|
const testState = computed(() => step.value?.test ?? null)
|
|
46
|
+
// The inspectable Fixer history: one entry per fixer round (what it was handed + how it
|
|
47
|
+
// ended), newest first, so the otherwise-opaque fixer sub-jobs have a surface here.
|
|
48
|
+
const fixerAttempts = computed(() => [...(testState.value?.attemptLog ?? [])].reverse())
|
|
49
|
+
|
|
50
|
+
// Infrastructure observability — parity with the Coder's generic step detail, so the
|
|
51
|
+
// Tester window surfaces WHERE its job runs (the container lifecycle: spinning up /
|
|
52
|
+
// running phase / id+url / errored), the ephemeral environment it tests against, and the
|
|
53
|
+
// run's infrastructure attempts + logs (container/runner/env spin-up), not just the
|
|
54
|
+
// report. The container/subtask signals already flow onto the step via the generic poll.
|
|
55
|
+
const runFailed = computed(() => instance.value?.status === 'failed')
|
|
56
|
+
const stepEnvironment = computed(() => step.value?.environment ?? null)
|
|
57
|
+
const executionId = computed(() => instance.value?.id ?? null)
|
|
58
|
+
// The infra-attempts log drawer is opened on demand (it fetches the per-run log rows).
|
|
59
|
+
const showProvisioning = ref(false)
|
|
43
60
|
|
|
44
61
|
const screenshots = computed<TestScreenshot[]>(() => report.value?.screenshots ?? [])
|
|
45
62
|
// Resolve each capture into an object URL for the gallery + lightbox. The shared cache
|
|
@@ -258,6 +275,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
258
275
|
tabindex="-1"
|
|
259
276
|
role="dialog"
|
|
260
277
|
aria-modal="true"
|
|
278
|
+
data-testid="tester-report-window"
|
|
261
279
|
:aria-label="t('testing.title')"
|
|
262
280
|
class="m-4 flex w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl focus:outline-none"
|
|
263
281
|
>
|
|
@@ -310,11 +328,118 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
310
328
|
</header>
|
|
311
329
|
|
|
312
330
|
<div class="flex min-h-0 flex-1">
|
|
313
|
-
<!-- Main: scenarios → outcomes → concerns tree -->
|
|
314
|
-
<div class="min-w-0 flex-1 overflow-y-auto px-5 py-4">
|
|
331
|
+
<!-- Main: infrastructure observability + scenarios → outcomes → concerns tree -->
|
|
332
|
+
<div class="min-w-0 flex-1 space-y-4 overflow-y-auto px-5 py-4">
|
|
333
|
+
<!-- Infrastructure: container lifecycle (where + what it's doing), the
|
|
334
|
+
ephemeral environment, and the run's infra attempts + logs — parity with
|
|
335
|
+
the Coder's step detail. Shown even before a report lands, so the infra
|
|
336
|
+
spin-up is visible WHILE the Tester is still standing it up. -->
|
|
337
|
+
<!-- Only when there's genuine infrastructure to show — a container or an ephemeral
|
|
338
|
+
environment. A no-infra tester (no container, no env) has no infra attempts
|
|
339
|
+
either, so we don't render an empty header + a log toggle over nothing. -->
|
|
340
|
+
<section
|
|
341
|
+
v-if="step && (step.container || stepEnvironment)"
|
|
342
|
+
data-testid="tester-infrastructure"
|
|
343
|
+
class="space-y-3"
|
|
344
|
+
>
|
|
345
|
+
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
346
|
+
{{ t('testing.infrastructure') }}
|
|
347
|
+
</h3>
|
|
348
|
+
<StepContainerStatus :step="step" :run-failed="runFailed" />
|
|
349
|
+
<EnvironmentStatusPanel v-if="stepEnvironment" :environment="stepEnvironment" />
|
|
350
|
+
<div v-if="executionId">
|
|
351
|
+
<UButton
|
|
352
|
+
:icon="showProvisioning ? 'i-lucide-chevron-up' : 'i-lucide-scroll-text'"
|
|
353
|
+
variant="ghost"
|
|
354
|
+
size="xs"
|
|
355
|
+
data-testid="tester-infra-attempts-toggle"
|
|
356
|
+
@click="showProvisioning = !showProvisioning"
|
|
357
|
+
>
|
|
358
|
+
{{
|
|
359
|
+
showProvisioning
|
|
360
|
+
? t('panels.stepDetail.hideInfraAttempts')
|
|
361
|
+
: t('panels.stepDetail.infraAttempts')
|
|
362
|
+
}}
|
|
363
|
+
</UButton>
|
|
364
|
+
<ProvisioningLogsDrawer
|
|
365
|
+
v-if="showProvisioning"
|
|
366
|
+
class="mt-2"
|
|
367
|
+
:execution-id="executionId"
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
</section>
|
|
371
|
+
|
|
372
|
+
<!-- Fixer timeline: one inspectable entry per fixer round (what it was handed and
|
|
373
|
+
how it ended), so the otherwise-opaque fixer sub-jobs have a surface — the
|
|
374
|
+
analogue of the polling gate's attempt history. -->
|
|
375
|
+
<section
|
|
376
|
+
v-if="fixerAttempts.length"
|
|
377
|
+
data-testid="tester-fixer-attempts"
|
|
378
|
+
class="space-y-2"
|
|
379
|
+
>
|
|
380
|
+
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
381
|
+
{{ t('testing.fixerAttempts') }}
|
|
382
|
+
</h3>
|
|
383
|
+
<ol class="space-y-2">
|
|
384
|
+
<li
|
|
385
|
+
v-for="a in fixerAttempts"
|
|
386
|
+
:key="a.attempt"
|
|
387
|
+
data-testid="tester-fixer-attempt"
|
|
388
|
+
class="rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2"
|
|
389
|
+
>
|
|
390
|
+
<div class="flex items-center gap-2">
|
|
391
|
+
<UIcon
|
|
392
|
+
:name="a.outcome === 'completed' ? 'i-lucide-wrench' : 'i-lucide-circle-x'"
|
|
393
|
+
class="h-3.5 w-3.5 shrink-0"
|
|
394
|
+
:class="a.outcome === 'completed' ? 'text-amber-300' : 'text-rose-400'"
|
|
395
|
+
/>
|
|
396
|
+
<span class="text-[13px] font-medium text-slate-200">
|
|
397
|
+
{{ t('testing.fixerTimeline.attempt', { n: a.attempt }) }}
|
|
398
|
+
</span>
|
|
399
|
+
<UBadge
|
|
400
|
+
:color="a.outcome === 'completed' ? 'neutral' : 'error'"
|
|
401
|
+
variant="subtle"
|
|
402
|
+
size="sm"
|
|
403
|
+
>
|
|
404
|
+
{{
|
|
405
|
+
a.outcome === 'completed'
|
|
406
|
+
? t('testing.fixerTimeline.completed')
|
|
407
|
+
: t('testing.fixerTimeline.failed')
|
|
408
|
+
}}
|
|
409
|
+
</UBadge>
|
|
410
|
+
<span class="ml-auto text-[11px] text-slate-500">{{
|
|
411
|
+
d(new Date(a.at), 'short')
|
|
412
|
+
}}</span>
|
|
413
|
+
</div>
|
|
414
|
+
<p v-if="a.summary" class="mt-1 text-[12px] leading-snug text-slate-400">
|
|
415
|
+
{{ a.summary }}
|
|
416
|
+
</p>
|
|
417
|
+
<div v-if="a.concerns && a.concerns.length" class="mt-1.5">
|
|
418
|
+
<p class="text-[11px] text-slate-500">
|
|
419
|
+
{{ t('testing.fixerTimeline.addressed') }}
|
|
420
|
+
</p>
|
|
421
|
+
<ul class="mt-1 space-y-0.5">
|
|
422
|
+
<li
|
|
423
|
+
v-for="(c, ci) in a.concerns"
|
|
424
|
+
:key="`fa${a.attempt}-c${ci}`"
|
|
425
|
+
class="flex items-center gap-1.5 text-[12px] text-slate-300"
|
|
426
|
+
>
|
|
427
|
+
<span
|
|
428
|
+
class="rounded px-1 text-[10px] uppercase"
|
|
429
|
+
:class="SEVERITY_META[c.severity].chip"
|
|
430
|
+
>{{ SEVERITY_LABELS[c.severity] }}</span
|
|
431
|
+
>
|
|
432
|
+
<span class="truncate">{{ c.title }}</span>
|
|
433
|
+
</li>
|
|
434
|
+
</ul>
|
|
435
|
+
</div>
|
|
436
|
+
</li>
|
|
437
|
+
</ol>
|
|
438
|
+
</section>
|
|
439
|
+
|
|
315
440
|
<div
|
|
316
441
|
v-if="!report"
|
|
317
|
-
class="flex
|
|
442
|
+
class="flex flex-col items-center justify-center gap-2 py-12 text-center text-slate-400"
|
|
318
443
|
>
|
|
319
444
|
<UIcon name="i-lucide-flask-conical" class="h-8 w-8 opacity-40" />
|
|
320
445
|
<p class="text-sm">{{ t('testing.empty.title') }}</p>
|
package/app/types/execution.ts
CHANGED
|
@@ -133,6 +133,24 @@ export function isCompanionKind(kind: string): boolean {
|
|
|
133
133
|
)
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* The friendly label for a container's live phase (clone → "Preparing workspace",
|
|
138
|
+
* agent → "Agent running", …), falling back to the raw phase string for an unknown/new
|
|
139
|
+
* phase (the phase vocabulary is harness-controlled and open-ended). `null` when there's
|
|
140
|
+
* no phase to show. Kept here so the three views that render it (the step-detail card,
|
|
141
|
+
* the inspector label, the board node) resolve it identically rather than re-deriving the
|
|
142
|
+
* key + `te()`-fallback inline. `t`/`te` are passed in since this is a pure util, not a
|
|
143
|
+
* composable.
|
|
144
|
+
*/
|
|
145
|
+
export function containerPhaseLabel(
|
|
146
|
+
phase: string | null | undefined,
|
|
147
|
+
i18n: { t: (key: string) => string; te: (key: string) => boolean },
|
|
148
|
+
): string | null {
|
|
149
|
+
if (!phase) return null
|
|
150
|
+
const key = `panels.stepMeta.container.phase.${phase}`
|
|
151
|
+
return i18n.te(key) ? i18n.t(key) : phase
|
|
152
|
+
}
|
|
153
|
+
|
|
136
154
|
/**
|
|
137
155
|
* Tailwind classes for a subtask-item status icon. An in-progress item spins only
|
|
138
156
|
* while the run is live: once the run has failed, a step left mid-flight (its item
|
package/i18n/locales/en.json
CHANGED
|
@@ -397,7 +397,9 @@
|
|
|
397
397
|
},
|
|
398
398
|
"fragments": {
|
|
399
399
|
"serviceTitle": "Service best practices",
|
|
400
|
-
"serviceEmpty": "None. Code-aware agents on this service follow their default guidance."
|
|
400
|
+
"serviceEmpty": "None. Code-aware agents on this service follow their default guidance.",
|
|
401
|
+
"manageBoard": "Manage this board's fragment library…",
|
|
402
|
+
"manageAccount": "Manage account fragments…"
|
|
401
403
|
},
|
|
402
404
|
"releaseHealth": {
|
|
403
405
|
"title": "Post-release health",
|
|
@@ -599,6 +601,23 @@
|
|
|
599
601
|
"run": "Run",
|
|
600
602
|
"clickToCopy": "{id} (click to copy)",
|
|
601
603
|
"spinningUpContainer": "Spinning up container…",
|
|
604
|
+
"container": {
|
|
605
|
+
"status": {
|
|
606
|
+
"starting": "Spinning up container…",
|
|
607
|
+
"up": "Container running",
|
|
608
|
+
"errored": "Container errored",
|
|
609
|
+
"destroyed": "Container reclaimed"
|
|
610
|
+
},
|
|
611
|
+
"phase": {
|
|
612
|
+
"starting": "Starting up",
|
|
613
|
+
"clone": "Preparing workspace",
|
|
614
|
+
"agent": "Agent running",
|
|
615
|
+
"push": "Pushing changes",
|
|
616
|
+
"done": "Finishing up"
|
|
617
|
+
},
|
|
618
|
+
"id": "Container",
|
|
619
|
+
"url": "Address"
|
|
620
|
+
},
|
|
602
621
|
"subtasks": "Subtasks · {completed}/{total}",
|
|
603
622
|
"standardsApplied": "Standards applied",
|
|
604
623
|
"decision": "Decision",
|
|
@@ -2930,6 +2949,12 @@
|
|
|
2930
2949
|
"fixerAttempts": "Fixer attempts",
|
|
2931
2950
|
"fixCount": "{attempts}/{max} fix",
|
|
2932
2951
|
"fixingSuffix": "· fixing…",
|
|
2952
|
+
"fixerTimeline": {
|
|
2953
|
+
"attempt": "Attempt {n}",
|
|
2954
|
+
"completed": "Completed",
|
|
2955
|
+
"failed": "Failed",
|
|
2956
|
+
"addressed": "Addressing"
|
|
2957
|
+
},
|
|
2933
2958
|
"empty": {
|
|
2934
2959
|
"title": "No test report yet.",
|
|
2935
2960
|
"hint": "The report appears once the Tester finishes a pass. While it runs, the step shows live progress on the board."
|
|
@@ -2973,6 +2998,7 @@
|
|
|
2973
2998
|
"blocking": "({count} blocking) | ({count} blocking)"
|
|
2974
2999
|
},
|
|
2975
3000
|
"environment": "Environment",
|
|
3001
|
+
"infrastructure": "Infrastructure",
|
|
2976
3002
|
"footer": "Scenarios are the areas the Tester chose to exercise (its spec acceptance scenarios). Outcomes and concerns are grouped under them by name.",
|
|
2977
3003
|
"@screenshotAlt": {
|
|
2978
3004
|
"description": "Alt text for a captured screenshot thumbnail; {view} is the screen/view name. The literal word 'screenshot' should be localized."
|
package/i18n/locales/es.json
CHANGED
|
@@ -361,7 +361,9 @@
|
|
|
361
361
|
},
|
|
362
362
|
"fragments": {
|
|
363
363
|
"serviceTitle": "Buenas prácticas del servicio",
|
|
364
|
-
"serviceEmpty": "Ninguna. Los agentes que conocen el código en este servicio siguen su guía predeterminada."
|
|
364
|
+
"serviceEmpty": "Ninguna. Los agentes que conocen el código en este servicio siguen su guía predeterminada.",
|
|
365
|
+
"manageBoard": "Gestionar la biblioteca de fragmentos de este tablero…",
|
|
366
|
+
"manageAccount": "Gestionar los fragmentos de la cuenta…"
|
|
365
367
|
},
|
|
366
368
|
"releaseHealth": {
|
|
367
369
|
"title": "Salud posterior al lanzamiento",
|
|
@@ -582,6 +584,23 @@
|
|
|
582
584
|
"approved": "Aprobado",
|
|
583
585
|
"changes_requested": "Cambios solicitados",
|
|
584
586
|
"rejected": "Rechazado"
|
|
587
|
+
},
|
|
588
|
+
"container": {
|
|
589
|
+
"status": {
|
|
590
|
+
"starting": "Iniciando contenedor…",
|
|
591
|
+
"up": "Contenedor en ejecución",
|
|
592
|
+
"errored": "Error del contenedor",
|
|
593
|
+
"destroyed": "Contenedor liberado"
|
|
594
|
+
},
|
|
595
|
+
"phase": {
|
|
596
|
+
"starting": "Iniciando",
|
|
597
|
+
"clone": "Preparando el espacio de trabajo",
|
|
598
|
+
"agent": "Agente en ejecución",
|
|
599
|
+
"push": "Enviando cambios",
|
|
600
|
+
"done": "Finalizando"
|
|
601
|
+
},
|
|
602
|
+
"id": "Contenedor",
|
|
603
|
+
"url": "Dirección"
|
|
585
604
|
}
|
|
586
605
|
},
|
|
587
606
|
"stepDetail": {
|
|
@@ -2843,6 +2862,12 @@
|
|
|
2843
2862
|
"fixerAttempts": "Intentos del corrector",
|
|
2844
2863
|
"fixCount": "{attempts}/{max} corrección",
|
|
2845
2864
|
"fixingSuffix": "· corrigiendo…",
|
|
2865
|
+
"fixerTimeline": {
|
|
2866
|
+
"attempt": "Intento {n}",
|
|
2867
|
+
"completed": "Completado",
|
|
2868
|
+
"failed": "Falló",
|
|
2869
|
+
"addressed": "Abordando"
|
|
2870
|
+
},
|
|
2846
2871
|
"empty": {
|
|
2847
2872
|
"title": "Aún no hay informe de pruebas.",
|
|
2848
2873
|
"hint": "El informe aparece cuando el Tester termina una pasada. Mientras se ejecuta, el paso muestra el progreso en vivo en el tablero."
|
|
@@ -2886,7 +2911,8 @@
|
|
|
2886
2911
|
"blocking": "({count} bloqueante) | ({count} bloqueantes)"
|
|
2887
2912
|
},
|
|
2888
2913
|
"environment": "Entorno",
|
|
2889
|
-
"footer": "Los escenarios son las áreas que el Tester decidió ejercitar (sus escenarios de aceptación de la especificación). Los resultados y las incidencias se agrupan bajo ellos por nombre."
|
|
2914
|
+
"footer": "Los escenarios son las áreas que el Tester decidió ejercitar (sus escenarios de aceptación de la especificación). Los resultados y las incidencias se agrupan bajo ellos por nombre.",
|
|
2915
|
+
"infrastructure": "Infraestructura"
|
|
2890
2916
|
},
|
|
2891
2917
|
"visualConfirm": {
|
|
2892
2918
|
"ariaLabel": "Confirmación visual",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -361,7 +361,9 @@
|
|
|
361
361
|
},
|
|
362
362
|
"fragments": {
|
|
363
363
|
"serviceTitle": "Bonnes pratiques du service",
|
|
364
|
-
"serviceEmpty": "Aucune. Les agents conscients du code sur ce service suivent leurs consignes par défaut."
|
|
364
|
+
"serviceEmpty": "Aucune. Les agents conscients du code sur ce service suivent leurs consignes par défaut.",
|
|
365
|
+
"manageBoard": "Gérer la bibliothèque de fragments de ce tableau…",
|
|
366
|
+
"manageAccount": "Gérer les fragments du compte…"
|
|
365
367
|
},
|
|
366
368
|
"releaseHealth": {
|
|
367
369
|
"title": "Santé post-déploiement",
|
|
@@ -582,6 +584,23 @@
|
|
|
582
584
|
"approved": "Approuvé",
|
|
583
585
|
"changes_requested": "Modifications demandées",
|
|
584
586
|
"rejected": "Rejeté"
|
|
587
|
+
},
|
|
588
|
+
"container": {
|
|
589
|
+
"status": {
|
|
590
|
+
"starting": "Démarrage du conteneur…",
|
|
591
|
+
"up": "Conteneur en cours d’exécution",
|
|
592
|
+
"errored": "Erreur du conteneur",
|
|
593
|
+
"destroyed": "Conteneur libéré"
|
|
594
|
+
},
|
|
595
|
+
"phase": {
|
|
596
|
+
"starting": "Démarrage",
|
|
597
|
+
"clone": "Préparation de l’espace de travail",
|
|
598
|
+
"agent": "Agent en cours d’exécution",
|
|
599
|
+
"push": "Envoi des modifications",
|
|
600
|
+
"done": "Finalisation"
|
|
601
|
+
},
|
|
602
|
+
"id": "Conteneur",
|
|
603
|
+
"url": "Adresse"
|
|
585
604
|
}
|
|
586
605
|
},
|
|
587
606
|
"stepDetail": {
|
|
@@ -2843,6 +2862,12 @@
|
|
|
2843
2862
|
"fixerAttempts": "Tentatives du correcteur",
|
|
2844
2863
|
"fixCount": "{attempts}/{max} correction",
|
|
2845
2864
|
"fixingSuffix": "· correction…",
|
|
2865
|
+
"fixerTimeline": {
|
|
2866
|
+
"attempt": "Tentative {n}",
|
|
2867
|
+
"completed": "Terminé",
|
|
2868
|
+
"failed": "Échec",
|
|
2869
|
+
"addressed": "Corrige"
|
|
2870
|
+
},
|
|
2846
2871
|
"empty": {
|
|
2847
2872
|
"title": "Aucun rapport de tests pour l'instant.",
|
|
2848
2873
|
"hint": "Le rapport apparaît une fois que le Testeur a terminé une passe. Pendant son exécution, l'étape affiche la progression en direct sur le tableau."
|
|
@@ -2886,7 +2911,8 @@
|
|
|
2886
2911
|
"blocking": "({count} bloquante) | ({count} bloquantes)"
|
|
2887
2912
|
},
|
|
2888
2913
|
"environment": "Environnement",
|
|
2889
|
-
"footer": "Les scénarios sont les domaines que le Testeur a choisi d'éprouver (ses scénarios d'acceptation de la spécification). Les résultats et les réserves y sont regroupés par nom."
|
|
2914
|
+
"footer": "Les scénarios sont les domaines que le Testeur a choisi d'éprouver (ses scénarios d'acceptation de la spécification). Les résultats et les réserves y sont regroupés par nom.",
|
|
2915
|
+
"infrastructure": "Infrastructure"
|
|
2890
2916
|
},
|
|
2891
2917
|
"visualConfirm": {
|
|
2892
2918
|
"ariaLabel": "Confirmation visuelle",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -361,7 +361,9 @@
|
|
|
361
361
|
},
|
|
362
362
|
"fragments": {
|
|
363
363
|
"serviceTitle": "Dobre praktyki usługi",
|
|
364
|
-
"serviceEmpty": "Brak. Agenci świadomi kodu w tej usłudze stosują swoje domyślne wytyczne."
|
|
364
|
+
"serviceEmpty": "Brak. Agenci świadomi kodu w tej usłudze stosują swoje domyślne wytyczne.",
|
|
365
|
+
"manageBoard": "Zarządzaj biblioteką fragmentów tej tablicy…",
|
|
366
|
+
"manageAccount": "Zarządzaj fragmentami konta…"
|
|
365
367
|
},
|
|
366
368
|
"releaseHealth": {
|
|
367
369
|
"title": "Kondycja po wydaniu",
|
|
@@ -582,6 +584,23 @@
|
|
|
582
584
|
"approved": "Zatwierdzone",
|
|
583
585
|
"changes_requested": "Zażądano zmian",
|
|
584
586
|
"rejected": "Odrzucone"
|
|
587
|
+
},
|
|
588
|
+
"container": {
|
|
589
|
+
"status": {
|
|
590
|
+
"starting": "Uruchamianie kontenera…",
|
|
591
|
+
"up": "Kontener działa",
|
|
592
|
+
"errored": "Błąd kontenera",
|
|
593
|
+
"destroyed": "Kontener zwolniony"
|
|
594
|
+
},
|
|
595
|
+
"phase": {
|
|
596
|
+
"starting": "Uruchamianie",
|
|
597
|
+
"clone": "Przygotowywanie obszaru roboczego",
|
|
598
|
+
"agent": "Agent działa",
|
|
599
|
+
"push": "Wysyłanie zmian",
|
|
600
|
+
"done": "Kończenie"
|
|
601
|
+
},
|
|
602
|
+
"id": "Kontener",
|
|
603
|
+
"url": "Adres"
|
|
585
604
|
}
|
|
586
605
|
},
|
|
587
606
|
"stepDetail": {
|
|
@@ -2843,6 +2862,12 @@
|
|
|
2843
2862
|
"fixerAttempts": "Próby naprawy",
|
|
2844
2863
|
"fixCount": "{attempts}/{max} naprawa",
|
|
2845
2864
|
"fixingSuffix": "· naprawianie…",
|
|
2865
|
+
"fixerTimeline": {
|
|
2866
|
+
"attempt": "Próba {n}",
|
|
2867
|
+
"completed": "Ukończono",
|
|
2868
|
+
"failed": "Niepowodzenie",
|
|
2869
|
+
"addressed": "Naprawia"
|
|
2870
|
+
},
|
|
2846
2871
|
"empty": {
|
|
2847
2872
|
"title": "Brak raportu z testów.",
|
|
2848
2873
|
"hint": "Raport pojawi się, gdy Tester zakończy przebieg. W trakcie działania krok pokazuje postęp na żywo na tablicy."
|
|
@@ -2886,7 +2911,8 @@
|
|
|
2886
2911
|
"blocking": "({count} blokujące) | ({count} blokujące) | ({count} blokujących)"
|
|
2887
2912
|
},
|
|
2888
2913
|
"environment": "Środowisko",
|
|
2889
|
-
"footer": "Scenariusze to obszary, które Tester postanowił sprawdzić (jego scenariusze akceptacyjne ze specyfikacji). Wyniki i zastrzeżenia są pod nimi grupowane według nazwy."
|
|
2914
|
+
"footer": "Scenariusze to obszary, które Tester postanowił sprawdzić (jego scenariusze akceptacyjne ze specyfikacji). Wyniki i zastrzeżenia są pod nimi grupowane według nazwy.",
|
|
2915
|
+
"infrastructure": "Infrastruktura"
|
|
2890
2916
|
},
|
|
2891
2917
|
"visualConfirm": {
|
|
2892
2918
|
"ariaLabel": "Potwierdzenie wizualne",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -361,7 +361,9 @@
|
|
|
361
361
|
},
|
|
362
362
|
"fragments": {
|
|
363
363
|
"serviceTitle": "Найкращі практики сервісу",
|
|
364
|
-
"serviceEmpty": "Немає. Агенти, що працюють з кодом цього сервісу, дотримуються типових інструкцій."
|
|
364
|
+
"serviceEmpty": "Немає. Агенти, що працюють з кодом цього сервісу, дотримуються типових інструкцій.",
|
|
365
|
+
"manageBoard": "Керувати бібліотекою фрагментів цієї дошки…",
|
|
366
|
+
"manageAccount": "Керувати фрагментами облікового запису…"
|
|
365
367
|
},
|
|
366
368
|
"releaseHealth": {
|
|
367
369
|
"title": "Стан після релізу",
|
|
@@ -582,6 +584,23 @@
|
|
|
582
584
|
"approved": "Затверджено",
|
|
583
585
|
"changes_requested": "Запитано зміни",
|
|
584
586
|
"rejected": "Відхилено"
|
|
587
|
+
},
|
|
588
|
+
"container": {
|
|
589
|
+
"status": {
|
|
590
|
+
"starting": "Запуск контейнера…",
|
|
591
|
+
"up": "Контейнер працює",
|
|
592
|
+
"errored": "Помилка контейнера",
|
|
593
|
+
"destroyed": "Контейнер звільнено"
|
|
594
|
+
},
|
|
595
|
+
"phase": {
|
|
596
|
+
"starting": "Запуск",
|
|
597
|
+
"clone": "Підготовка робочого простору",
|
|
598
|
+
"agent": "Агент працює",
|
|
599
|
+
"push": "Надсилання змін",
|
|
600
|
+
"done": "Завершення"
|
|
601
|
+
},
|
|
602
|
+
"id": "Контейнер",
|
|
603
|
+
"url": "Адреса"
|
|
585
604
|
}
|
|
586
605
|
},
|
|
587
606
|
"stepDetail": {
|
|
@@ -2843,6 +2862,12 @@
|
|
|
2843
2862
|
"fixerAttempts": "Спроби виправлення",
|
|
2844
2863
|
"fixCount": "{attempts}/{max} виправлення",
|
|
2845
2864
|
"fixingSuffix": "· виправлення…",
|
|
2865
|
+
"fixerTimeline": {
|
|
2866
|
+
"attempt": "Спроба {n}",
|
|
2867
|
+
"completed": "Завершено",
|
|
2868
|
+
"failed": "Помилка",
|
|
2869
|
+
"addressed": "Виправляє"
|
|
2870
|
+
},
|
|
2846
2871
|
"empty": {
|
|
2847
2872
|
"title": "Звіту про тестування ще немає.",
|
|
2848
2873
|
"hint": "Звіт зʼявиться після того, як Тестувальник завершить прохід. Поки він виконується, крок показує прогрес у реальному часі на дошці."
|
|
@@ -2886,7 +2911,8 @@
|
|
|
2886
2911
|
"blocking": "({count} блокувальне) | ({count} блокувальні) | ({count} блокувальних)"
|
|
2887
2912
|
},
|
|
2888
2913
|
"environment": "Середовище",
|
|
2889
|
-
"footer": "Сценарії — це області, які Тестувальник вирішив перевірити (його сценарії приймання зі специфікації). Результати та зауваження групуються під ними за назвою."
|
|
2914
|
+
"footer": "Сценарії — це області, які Тестувальник вирішив перевірити (його сценарії приймання зі специфікації). Результати та зауваження групуються під ними за назвою.",
|
|
2915
|
+
"infrastructure": "Інфраструктура"
|
|
2890
2916
|
},
|
|
2891
2917
|
"visualConfirm": {
|
|
2892
2918
|
"ariaLabel": "Візуальне підтвердження",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"valibot": "^1.4.2",
|
|
35
35
|
"vue": "^3.5.39",
|
|
36
36
|
"wretch": "^3.0.9",
|
|
37
|
-
"@cat-factory/contracts": "0.
|
|
37
|
+
"@cat-factory/contracts": "0.53.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|