@cat-factory/app 0.6.0 → 0.7.3
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/LICENSE +21 -21
- package/app/components/board/ContextPicker.vue +367 -367
- package/app/components/gates/GateResultView.vue +90 -12
- package/app/components/layout/SideBar.vue +11 -0
- package/app/components/observability/StepMetricsBar.vue +102 -102
- package/app/components/observability/StepModelActivity.vue +49 -0
- package/app/components/panels/ObservabilityPanel.vue +1 -1
- package/app/components/panels/StepMetadataCard.vue +4 -16
- package/app/components/panels/StepRunMeta.vue +105 -0
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -178
- package/app/components/panels/inspector/TaskRunSettings.vue +77 -0
- package/app/components/recurring/RecurrenceEditor.vue +124 -124
- package/app/components/settings/IssueTrackerWritebackPanel.vue +103 -0
- package/app/components/testing/TestReportWindow.vue +17 -8
- package/app/composables/useBlockQueries.ts +154 -154
- package/app/composables/useContextLinking.ts +65 -65
- package/app/composables/useFrameResize.ts +54 -54
- package/app/pages/index.vue +2 -0
- package/app/stores/documents.ts +176 -176
- package/app/stores/services.ts +87 -87
- package/app/stores/tracker.ts +39 -27
- package/app/stores/ui.ts +12 -0
- package/app/types/documents.ts +104 -104
- package/app/types/domain.ts +5 -1
- package/app/types/execution.ts +18 -0
- package/app/types/github.ts +173 -173
- package/app/types/services.ts +27 -27
- package/app/types/tasks.ts +82 -82
- package/app/types/tracker.ts +27 -18
- package/app/utils/agentOutput.spec.ts +128 -128
- package/app/utils/agentOutput.ts +173 -173
- package/app/utils/observability.ts +52 -52
- package/package.json +6 -1
|
@@ -9,6 +9,7 @@ import { computed } from 'vue'
|
|
|
9
9
|
import { agentKindMeta } from '~/utils/catalog'
|
|
10
10
|
import type { GateStepState } from '~/types/execution'
|
|
11
11
|
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
12
|
+
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
12
13
|
|
|
13
14
|
const board = useBoardStore()
|
|
14
15
|
const execution = useExecutionStore()
|
|
@@ -17,6 +18,7 @@ const execution = useExecutionStore()
|
|
|
17
18
|
// nothing to fetch on open (no `onOpen` loader).
|
|
18
19
|
const { open, blockId, instanceId, stepIndex, close } = useResultView('gate')
|
|
19
20
|
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
21
|
+
const prUrl = computed(() => block.value?.pullRequest?.url ?? null)
|
|
20
22
|
|
|
21
23
|
const instance = computed(() =>
|
|
22
24
|
instanceId.value === null ? null : (execution.getInstance(instanceId.value) ?? null),
|
|
@@ -35,6 +37,13 @@ const helperMeta = computed(() => agentKindMeta(helperKind.value))
|
|
|
35
37
|
const failingChecks = computed(() => gate.value?.failingChecks ?? [])
|
|
36
38
|
const shortSha = computed(() => (gate.value?.headSha ? gate.value.headSha.slice(0, 7) : null))
|
|
37
39
|
|
|
40
|
+
// The helper-agent attempts this gate dispatched, newest first for the timeline.
|
|
41
|
+
const attempts = computed(() => [...(gate.value?.attemptLog ?? [])].reverse())
|
|
42
|
+
|
|
43
|
+
function formatClock(ms?: number | null): string | null {
|
|
44
|
+
return ms ? new Date(ms).toLocaleString() : null
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
/**
|
|
39
48
|
* The display status — a roll-up of the persisted gate state + the run's status, so the
|
|
40
49
|
* window reads as a conclusion rather than raw fields:
|
|
@@ -189,7 +198,21 @@ const conflictVerdict = computed(() => {
|
|
|
189
198
|
class="flex items-center gap-2 rounded-md border border-slate-800 bg-slate-950/40 px-3 py-1.5"
|
|
190
199
|
>
|
|
191
200
|
<UIcon name="i-lucide-circle-x" class="h-3.5 w-3.5 shrink-0 text-rose-400" />
|
|
192
|
-
<
|
|
201
|
+
<a
|
|
202
|
+
v-if="c.url"
|
|
203
|
+
:href="c.url"
|
|
204
|
+
target="_blank"
|
|
205
|
+
rel="noopener"
|
|
206
|
+
class="group min-w-0 flex-1 truncate text-[13px] text-sky-300 hover:text-sky-200 hover:underline"
|
|
207
|
+
:title="`Open ${c.name} on GitHub`"
|
|
208
|
+
>
|
|
209
|
+
{{ c.name }}
|
|
210
|
+
<UIcon
|
|
211
|
+
name="i-lucide-external-link"
|
|
212
|
+
class="ml-0.5 inline h-3 w-3 opacity-60 group-hover:opacity-100"
|
|
213
|
+
/>
|
|
214
|
+
</a>
|
|
215
|
+
<span v-else class="min-w-0 flex-1 truncate text-[13px] text-slate-200">{{
|
|
193
216
|
c.name
|
|
194
217
|
}}</span>
|
|
195
218
|
<span class="shrink-0 text-[11px] uppercase text-rose-300">
|
|
@@ -202,7 +225,7 @@ const conflictVerdict = computed(() => {
|
|
|
202
225
|
</p>
|
|
203
226
|
</template>
|
|
204
227
|
|
|
205
|
-
<!-- Conflicts: verdict +
|
|
228
|
+
<!-- Conflicts: verdict + the resolver's account of what it left -->
|
|
206
229
|
<template v-else>
|
|
207
230
|
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
208
231
|
Mergeability
|
|
@@ -217,17 +240,67 @@ const conflictVerdict = computed(() => {
|
|
|
217
240
|
/>
|
|
218
241
|
<span class="text-[13px] text-slate-200">{{ conflictVerdict }}</span>
|
|
219
242
|
</div>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
243
|
+
<!-- GitHub's API reports mergeability as a single bit (no file list), but the
|
|
244
|
+
conflict resolver discovers the conflicting files in the container and
|
|
245
|
+
reports them back — surface that account here. -->
|
|
246
|
+
<p
|
|
247
|
+
v-if="gate.lastFailureSummary"
|
|
248
|
+
class="mt-2 whitespace-pre-wrap rounded-md border border-slate-800 bg-slate-950/40 px-3 py-2 text-[12px] leading-relaxed text-slate-300"
|
|
249
|
+
>
|
|
250
|
+
{{ gate.lastFailureSummary }}
|
|
223
251
|
</p>
|
|
252
|
+
<a
|
|
253
|
+
v-if="prUrl"
|
|
254
|
+
:href="prUrl"
|
|
255
|
+
target="_blank"
|
|
256
|
+
rel="noopener"
|
|
257
|
+
class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200 hover:underline"
|
|
258
|
+
>
|
|
259
|
+
View pull request on GitHub
|
|
260
|
+
<UIcon name="i-lucide-external-link" class="h-3 w-3" />
|
|
261
|
+
</a>
|
|
224
262
|
</template>
|
|
263
|
+
|
|
264
|
+
<!-- Attempt history (both gates): what each helper run did and how it ended. -->
|
|
265
|
+
<section v-if="attempts.length" class="mt-5">
|
|
266
|
+
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
267
|
+
{{ helperMeta.label }} attempts
|
|
268
|
+
</h3>
|
|
269
|
+
<ol class="space-y-2">
|
|
270
|
+
<li
|
|
271
|
+
v-for="a in attempts"
|
|
272
|
+
:key="a.attempt"
|
|
273
|
+
class="rounded-md border border-slate-800 bg-slate-950/40 px-3 py-2"
|
|
274
|
+
>
|
|
275
|
+
<div class="flex items-center gap-2">
|
|
276
|
+
<span class="text-[12px] font-semibold text-slate-200"
|
|
277
|
+
>Attempt {{ a.attempt }}</span
|
|
278
|
+
>
|
|
279
|
+
<UBadge
|
|
280
|
+
:color="a.outcome === 'failed' ? 'error' : 'neutral'"
|
|
281
|
+
variant="subtle"
|
|
282
|
+
size="sm"
|
|
283
|
+
>{{ a.outcome }}</UBadge
|
|
284
|
+
>
|
|
285
|
+
<span v-if="formatClock(a.at)" class="ml-auto text-[11px] text-slate-500">{{
|
|
286
|
+
formatClock(a.at)
|
|
287
|
+
}}</span>
|
|
288
|
+
</div>
|
|
289
|
+
<p
|
|
290
|
+
v-if="a.summary"
|
|
291
|
+
class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-slate-400"
|
|
292
|
+
>
|
|
293
|
+
{{ a.summary }}
|
|
294
|
+
</p>
|
|
295
|
+
</li>
|
|
296
|
+
</ol>
|
|
297
|
+
</section>
|
|
225
298
|
</template>
|
|
226
299
|
</div>
|
|
227
300
|
|
|
228
301
|
<!-- Sidebar: gate state -->
|
|
229
302
|
<aside
|
|
230
|
-
class="hidden w-
|
|
303
|
+
class="hidden w-60 shrink-0 flex-col gap-4 border-l border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
|
|
231
304
|
>
|
|
232
305
|
<div v-if="gate">
|
|
233
306
|
<h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
@@ -263,12 +336,17 @@ const conflictVerdict = computed(() => {
|
|
|
263
336
|
<p class="font-mono text-[12px] text-slate-300">{{ shortSha }}</p>
|
|
264
337
|
</div>
|
|
265
338
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
339
|
+
<!-- Shared run metadata + embedded observability (model, run id, timing,
|
|
340
|
+
model-activity rollup) — identical to the agent step detail. -->
|
|
341
|
+
<StepRunMeta
|
|
342
|
+
v-if="step"
|
|
343
|
+
:step="step"
|
|
344
|
+
:instance-id="instanceId ?? undefined"
|
|
345
|
+
:step-number="stepIndex === null ? undefined : stepIndex + 1"
|
|
346
|
+
:total-steps="instance?.steps.length"
|
|
347
|
+
:run-failed="instance?.status === 'failed'"
|
|
348
|
+
:failure-at="instance?.failure?.occurredAt"
|
|
349
|
+
/>
|
|
272
350
|
|
|
273
351
|
<p class="mt-auto text-[10px] leading-relaxed text-slate-600">
|
|
274
352
|
A gate runs a programmatic precheck and only spins up the
|
|
@@ -262,6 +262,17 @@ watch(
|
|
|
262
262
|
>
|
|
263
263
|
Workspace settings
|
|
264
264
|
</UButton>
|
|
265
|
+
<UButton
|
|
266
|
+
block
|
|
267
|
+
color="primary"
|
|
268
|
+
variant="soft"
|
|
269
|
+
size="sm"
|
|
270
|
+
icon="i-lucide-message-square-reply"
|
|
271
|
+
class="justify-start"
|
|
272
|
+
@click="ui.openIssueWriteback()"
|
|
273
|
+
>
|
|
274
|
+
Issue tracker writeback
|
|
275
|
+
</UButton>
|
|
265
276
|
<UButton
|
|
266
277
|
block
|
|
267
278
|
color="primary"
|
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import type { StepMetrics } from '~/types/execution'
|
|
4
|
-
import {
|
|
5
|
-
formatMs,
|
|
6
|
-
formatTokens,
|
|
7
|
-
headroomColor,
|
|
8
|
-
headroomRatio,
|
|
9
|
-
pct,
|
|
10
|
-
transportRatio,
|
|
11
|
-
} from '~/utils/observability'
|
|
12
|
-
|
|
13
|
-
// Compact, at-a-glance LLM rollup for one pipeline step: token usage, an
|
|
14
|
-
// output-limit headroom bar (how close the step ran to truncation), a
|
|
15
|
-
// transport-vs-execution latency split, and error/warning badges. Rendered inline
|
|
16
|
-
// on the step surfaces (step detail, pipeline timeline). A no-op when there are no
|
|
17
|
-
// recorded calls. Clicking anywhere emits `inspect` so a parent can open the
|
|
18
|
-
// drill-down panel.
|
|
19
|
-
const props = defineProps<{ metrics: StepMetrics; clickable?: boolean }>()
|
|
20
|
-
defineEmits<{ inspect: [] }>()
|
|
21
|
-
|
|
22
|
-
const m = computed(() => props.metrics)
|
|
23
|
-
const headroom = computed(() => headroomRatio(m.value))
|
|
24
|
-
const transport = computed(() => transportRatio(m.value))
|
|
25
|
-
const headroomTone = computed(() => headroomColor(headroom.value, m.value.truncatedCalls > 0))
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<template>
|
|
29
|
-
<div
|
|
30
|
-
v-if="m.calls > 0"
|
|
31
|
-
class="rounded-lg border border-slate-800 bg-slate-900/40 p-2.5 text-[12px]"
|
|
32
|
-
:class="
|
|
33
|
-
clickable ? 'cursor-pointer transition hover:border-slate-700 hover:bg-slate-900/70' : ''
|
|
34
|
-
"
|
|
35
|
-
:role="clickable ? 'button' : undefined"
|
|
36
|
-
@click="clickable ? $emit('inspect') : undefined"
|
|
37
|
-
>
|
|
38
|
-
<!-- header line: call count + tokens + warning/error badges -->
|
|
39
|
-
<div class="flex items-center gap-2">
|
|
40
|
-
<UIcon name="i-lucide-activity" class="h-3.5 w-3.5 shrink-0 text-slate-500" />
|
|
41
|
-
<span class="text-slate-300"> {{ m.calls }} {{ m.calls === 1 ? 'call' : 'calls' }} </span>
|
|
42
|
-
<span class="text-slate-500">·</span>
|
|
43
|
-
<span class="tabular-nums text-slate-400" title="Prompt / completion tokens">
|
|
44
|
-
{{ formatTokens(m.promptTokens) }}↑ {{ formatTokens(m.completionTokens) }}↓
|
|
45
|
-
</span>
|
|
46
|
-
<div class="ml-auto flex items-center gap-1">
|
|
47
|
-
<UBadge v-if="m.errors > 0" color="error" variant="subtle" size="sm">
|
|
48
|
-
{{ m.errors }} error{{ m.errors === 1 ? '' : 's' }}
|
|
49
|
-
</UBadge>
|
|
50
|
-
<UBadge v-if="m.warnings > 0" color="warning" variant="subtle" size="sm">
|
|
51
|
-
{{ m.warnings }} warning{{ m.warnings === 1 ? '' : 's' }}
|
|
52
|
-
</UBadge>
|
|
53
|
-
<UIcon v-if="clickable" name="i-lucide-chevron-right" class="h-3.5 w-3.5 text-slate-600" />
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<!-- output-limit headroom -->
|
|
58
|
-
<div v-if="headroom !== null" class="mt-2">
|
|
59
|
-
<div class="flex items-center justify-between text-[11px]">
|
|
60
|
-
<span class="text-slate-500">Output limit</span>
|
|
61
|
-
<span class="tabular-nums" :class="headroomTone">
|
|
62
|
-
{{ formatTokens(m.peakCompletionTokens) }} /
|
|
63
|
-
{{ formatTokens(m.maxOutputTokens ?? 0) }} ({{ pct(headroom) }}%)
|
|
64
|
-
</span>
|
|
65
|
-
</div>
|
|
66
|
-
<div class="mt-1 h-1 overflow-hidden rounded-full bg-slate-700/60">
|
|
67
|
-
<div
|
|
68
|
-
class="h-full rounded-full transition-all duration-500"
|
|
69
|
-
:class="
|
|
70
|
-
m.truncatedCalls > 0 || headroom >= 0.98
|
|
71
|
-
? 'bg-rose-400'
|
|
72
|
-
: headroom >= 0.8
|
|
73
|
-
? 'bg-amber-400'
|
|
74
|
-
: 'bg-emerald-400'
|
|
75
|
-
"
|
|
76
|
-
:style="{ width: `${Math.max(2, pct(headroom))}%` }"
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
79
|
-
<p v-if="m.truncatedCalls > 0" class="mt-1 text-[11px] text-rose-400">
|
|
80
|
-
{{ m.truncatedCalls }} call{{ m.truncatedCalls === 1 ? '' : 's' }} truncated at the limit
|
|
81
|
-
</p>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<!-- transport overhead vs model execution -->
|
|
85
|
-
<div v-if="transport !== null" class="mt-2">
|
|
86
|
-
<div class="flex items-center justify-between text-[11px]">
|
|
87
|
-
<span class="text-slate-500">Transport vs execution</span>
|
|
88
|
-
<span class="tabular-nums text-slate-400">
|
|
89
|
-
{{ formatMs(m.overheadMs) }} / {{ formatMs(m.upstreamMs) }}
|
|
90
|
-
</span>
|
|
91
|
-
</div>
|
|
92
|
-
<div class="mt-1 flex h-1 overflow-hidden rounded-full bg-slate-700/60">
|
|
93
|
-
<div
|
|
94
|
-
class="h-full bg-sky-400/80"
|
|
95
|
-
:style="{ width: `${pct(transport)}%` }"
|
|
96
|
-
title="Transport / proxy overhead"
|
|
97
|
-
/>
|
|
98
|
-
<div class="h-full bg-indigo-400/80 flex-1" title="Model execution" />
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { StepMetrics } from '~/types/execution'
|
|
4
|
+
import {
|
|
5
|
+
formatMs,
|
|
6
|
+
formatTokens,
|
|
7
|
+
headroomColor,
|
|
8
|
+
headroomRatio,
|
|
9
|
+
pct,
|
|
10
|
+
transportRatio,
|
|
11
|
+
} from '~/utils/observability'
|
|
12
|
+
|
|
13
|
+
// Compact, at-a-glance LLM rollup for one pipeline step: token usage, an
|
|
14
|
+
// output-limit headroom bar (how close the step ran to truncation), a
|
|
15
|
+
// transport-vs-execution latency split, and error/warning badges. Rendered inline
|
|
16
|
+
// on the step surfaces (step detail, pipeline timeline). A no-op when there are no
|
|
17
|
+
// recorded calls. Clicking anywhere emits `inspect` so a parent can open the
|
|
18
|
+
// drill-down panel.
|
|
19
|
+
const props = defineProps<{ metrics: StepMetrics; clickable?: boolean }>()
|
|
20
|
+
defineEmits<{ inspect: [] }>()
|
|
21
|
+
|
|
22
|
+
const m = computed(() => props.metrics)
|
|
23
|
+
const headroom = computed(() => headroomRatio(m.value))
|
|
24
|
+
const transport = computed(() => transportRatio(m.value))
|
|
25
|
+
const headroomTone = computed(() => headroomColor(headroom.value, m.value.truncatedCalls > 0))
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div
|
|
30
|
+
v-if="m.calls > 0"
|
|
31
|
+
class="rounded-lg border border-slate-800 bg-slate-900/40 p-2.5 text-[12px]"
|
|
32
|
+
:class="
|
|
33
|
+
clickable ? 'cursor-pointer transition hover:border-slate-700 hover:bg-slate-900/70' : ''
|
|
34
|
+
"
|
|
35
|
+
:role="clickable ? 'button' : undefined"
|
|
36
|
+
@click="clickable ? $emit('inspect') : undefined"
|
|
37
|
+
>
|
|
38
|
+
<!-- header line: call count + tokens + warning/error badges -->
|
|
39
|
+
<div class="flex items-center gap-2">
|
|
40
|
+
<UIcon name="i-lucide-activity" class="h-3.5 w-3.5 shrink-0 text-slate-500" />
|
|
41
|
+
<span class="text-slate-300"> {{ m.calls }} {{ m.calls === 1 ? 'call' : 'calls' }} </span>
|
|
42
|
+
<span class="text-slate-500">·</span>
|
|
43
|
+
<span class="tabular-nums text-slate-400" title="Prompt / completion tokens">
|
|
44
|
+
{{ formatTokens(m.promptTokens) }}↑ {{ formatTokens(m.completionTokens) }}↓
|
|
45
|
+
</span>
|
|
46
|
+
<div class="ml-auto flex items-center gap-1">
|
|
47
|
+
<UBadge v-if="m.errors > 0" color="error" variant="subtle" size="sm">
|
|
48
|
+
{{ m.errors }} error{{ m.errors === 1 ? '' : 's' }}
|
|
49
|
+
</UBadge>
|
|
50
|
+
<UBadge v-if="m.warnings > 0" color="warning" variant="subtle" size="sm">
|
|
51
|
+
{{ m.warnings }} warning{{ m.warnings === 1 ? '' : 's' }}
|
|
52
|
+
</UBadge>
|
|
53
|
+
<UIcon v-if="clickable" name="i-lucide-chevron-right" class="h-3.5 w-3.5 text-slate-600" />
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- output-limit headroom -->
|
|
58
|
+
<div v-if="headroom !== null" class="mt-2">
|
|
59
|
+
<div class="flex items-center justify-between text-[11px]">
|
|
60
|
+
<span class="text-slate-500">Output limit</span>
|
|
61
|
+
<span class="tabular-nums" :class="headroomTone">
|
|
62
|
+
{{ formatTokens(m.peakCompletionTokens) }} /
|
|
63
|
+
{{ formatTokens(m.maxOutputTokens ?? 0) }} ({{ pct(headroom) }}%)
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="mt-1 h-1 overflow-hidden rounded-full bg-slate-700/60">
|
|
67
|
+
<div
|
|
68
|
+
class="h-full rounded-full transition-all duration-500"
|
|
69
|
+
:class="
|
|
70
|
+
m.truncatedCalls > 0 || headroom >= 0.98
|
|
71
|
+
? 'bg-rose-400'
|
|
72
|
+
: headroom >= 0.8
|
|
73
|
+
? 'bg-amber-400'
|
|
74
|
+
: 'bg-emerald-400'
|
|
75
|
+
"
|
|
76
|
+
:style="{ width: `${Math.max(2, pct(headroom))}%` }"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
<p v-if="m.truncatedCalls > 0" class="mt-1 text-[11px] text-rose-400">
|
|
80
|
+
{{ m.truncatedCalls }} call{{ m.truncatedCalls === 1 ? '' : 's' }} truncated at the limit
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<!-- transport overhead vs model execution -->
|
|
85
|
+
<div v-if="transport !== null" class="mt-2">
|
|
86
|
+
<div class="flex items-center justify-between text-[11px]">
|
|
87
|
+
<span class="text-slate-500">Transport vs execution</span>
|
|
88
|
+
<span class="tabular-nums text-slate-400">
|
|
89
|
+
{{ formatMs(m.overheadMs) }} / {{ formatMs(m.upstreamMs) }}
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="mt-1 flex h-1 overflow-hidden rounded-full bg-slate-700/60">
|
|
93
|
+
<div
|
|
94
|
+
class="h-full bg-sky-400/80"
|
|
95
|
+
:style="{ width: `${pct(transport)}%` }"
|
|
96
|
+
title="Transport / proxy overhead"
|
|
97
|
+
/>
|
|
98
|
+
<div class="h-full bg-indigo-400/80 flex-1" title="Model execution" />
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { StepMetrics } from '~/types/execution'
|
|
4
|
+
import StepMetricsBar from '~/components/observability/StepMetricsBar.vue'
|
|
5
|
+
|
|
6
|
+
// The shared "Model activity" block: the LLM observability rollup (StepMetricsBar) under
|
|
7
|
+
// a labelled header with a "View all calls →" link into the full per-call panel. Used by
|
|
8
|
+
// every step surface that shows a single step's metrics (the step metadata card, the
|
|
9
|
+
// gate / tester result windows) so the embedded-observability treatment can't drift.
|
|
10
|
+
// The "View all calls →" link opens the run-level panel, so it appears for any step that
|
|
11
|
+
// belongs to a run — including a gate, whose programmatic precheck records no per-step
|
|
12
|
+
// calls (the bar is omitted, but the link still reaches the helper agents' calls). Renders
|
|
13
|
+
// nothing only when there's neither a run to inspect nor any recorded calls.
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
metrics?: StepMetrics | null
|
|
16
|
+
/** The run whose per-call panel the header link / bar click opens. */
|
|
17
|
+
instanceId?: string
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const ui = useUiStore()
|
|
21
|
+
const hasCalls = computed(() => !!props.metrics && props.metrics.calls > 0)
|
|
22
|
+
|
|
23
|
+
function openObservability() {
|
|
24
|
+
if (props.instanceId) ui.openObservability(props.instanceId)
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div v-if="instanceId || hasCalls">
|
|
30
|
+
<div class="mb-1 flex items-center justify-between">
|
|
31
|
+
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
32
|
+
Model activity
|
|
33
|
+
</span>
|
|
34
|
+
<button
|
|
35
|
+
v-if="instanceId"
|
|
36
|
+
class="text-[11px] text-sky-400 hover:text-sky-300"
|
|
37
|
+
@click="openObservability"
|
|
38
|
+
>
|
|
39
|
+
View all calls →
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
<StepMetricsBar
|
|
43
|
+
v-if="hasCalls && metrics"
|
|
44
|
+
:metrics="metrics"
|
|
45
|
+
clickable
|
|
46
|
+
@inspect="openObservability"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
@@ -109,7 +109,7 @@ function exportJson() {
|
|
|
109
109
|
<Transition name="obs-fade">
|
|
110
110
|
<div
|
|
111
111
|
v-if="open"
|
|
112
|
-
class="fixed inset-0 z-
|
|
112
|
+
class="fixed inset-0 z-[60] flex flex-col bg-slate-950/96 backdrop-blur-sm"
|
|
113
113
|
role="dialog"
|
|
114
114
|
aria-modal="true"
|
|
115
115
|
>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import type { AgentState, PipelineStep, CompanionVerdict } from '~/types/execution'
|
|
4
4
|
import { subtaskIconClass } from '~/utils/pipelineRender'
|
|
5
|
-
import
|
|
5
|
+
import StepModelActivity from '~/components/observability/StepModelActivity.vue'
|
|
6
6
|
|
|
7
7
|
// The step's metadata card body: state/timing/model/run id, the container cold-boot
|
|
8
8
|
// phase, the live subtask breakdown, the LLM observability rollup, the applied
|
|
@@ -20,7 +20,6 @@ const props = defineProps<{
|
|
|
20
20
|
latestVerdict: CompanionVerdict | null
|
|
21
21
|
}>()
|
|
22
22
|
|
|
23
|
-
const ui = useUiStore()
|
|
24
23
|
const models = useModelsStore()
|
|
25
24
|
|
|
26
25
|
const STATE_META: Record<AgentState, { label: string; color: string }> = {
|
|
@@ -56,10 +55,6 @@ async function copyRunId() {
|
|
|
56
55
|
const id = props.step.runId ?? props.instanceId
|
|
57
56
|
if (id) await navigator.clipboard?.writeText(id)
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
function openObservability() {
|
|
61
|
-
if (props.instanceId) ui.openObservability(props.instanceId)
|
|
62
|
-
}
|
|
63
58
|
</script>
|
|
64
59
|
|
|
65
60
|
<template>
|
|
@@ -169,16 +164,9 @@ function openObservability() {
|
|
|
169
164
|
</div>
|
|
170
165
|
|
|
171
166
|
<!-- LLM observability rollup (tokens, output-limit headroom,
|
|
172
|
-
transport-vs-execution); click to open the full per-call panel
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<span class="text-[11px] uppercase tracking-wide text-slate-500"> Model activity </span>
|
|
176
|
-
<button class="text-[11px] text-sky-400 hover:text-sky-300" @click="openObservability">
|
|
177
|
-
View all calls →
|
|
178
|
-
</button>
|
|
179
|
-
</div>
|
|
180
|
-
<StepMetricsBar :metrics="step.metrics" clickable @inspect="openObservability" />
|
|
181
|
-
</div>
|
|
167
|
+
transport-vs-execution); click to open the full per-call panel. Self-gates: the
|
|
168
|
+
"View all calls →" link shows for any run, the metrics bar only when calls exist. -->
|
|
169
|
+
<StepModelActivity class="mt-4" :metrics="step.metrics" :instance-id="instanceId" />
|
|
182
170
|
|
|
183
171
|
<!-- standards (prompt fragments) folded into this step -->
|
|
184
172
|
<div v-if="step.selectedFragmentIds && step.selectedFragmentIds.length" class="mt-4">
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { PipelineStep } from '~/types/execution'
|
|
4
|
+
import { useStepTimer } from '~/composables/useStepTimer'
|
|
5
|
+
import StepModelActivity from '~/components/observability/StepModelActivity.vue'
|
|
6
|
+
|
|
7
|
+
// Shared run-metadata + observability block for the step-backed result windows
|
|
8
|
+
// (the CI/conflicts gate, the tester report). It carries the facts every step has in
|
|
9
|
+
// common — step position, live duration, model, run id, and the LLM model-activity
|
|
10
|
+
// rollup — so each window keeps only its own bespoke detail (the gate's verdict, the
|
|
11
|
+
// tester's scenarios) and the universal "which run is this / how did the model do"
|
|
12
|
+
// facts read the same everywhere. Laid out as a stack of labelled fields to drop into
|
|
13
|
+
// a window's sidebar; the canonical full-width version lives in StepMetadataCard.
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
step: PipelineStep
|
|
16
|
+
/** The enclosing run; copyable + opens the per-call observability panel. */
|
|
17
|
+
instanceId?: string
|
|
18
|
+
/** 1-based position in the pipeline, shown as "N of M" when both are given. */
|
|
19
|
+
stepNumber?: number
|
|
20
|
+
totalSteps?: number
|
|
21
|
+
/** The run failed: freezes the clock and reports a mid-flight step honestly. */
|
|
22
|
+
runFailed?: boolean
|
|
23
|
+
/** Epoch ms the run failed, so the frozen duration is the failure time. */
|
|
24
|
+
failureAt?: number | null
|
|
25
|
+
}>()
|
|
26
|
+
|
|
27
|
+
const models = useModelsStore()
|
|
28
|
+
|
|
29
|
+
const { isRunning, durationLabel } = useStepTimer({
|
|
30
|
+
step: () => props.step,
|
|
31
|
+
runFailed: () => props.runFailed ?? false,
|
|
32
|
+
failureAt: () => props.failureAt,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const modelLabel = computed(() => (props.step.model ? models.labelForRef(props.step.model) : null))
|
|
36
|
+
const runId = computed(() => props.step.runId ?? props.instanceId ?? null)
|
|
37
|
+
|
|
38
|
+
function formatClock(ms?: number | null): string | null {
|
|
39
|
+
return ms ? new Date(ms).toLocaleString() : null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function copyRunId() {
|
|
43
|
+
if (runId.value) await navigator.clipboard?.writeText(runId.value)
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div class="flex flex-col gap-4">
|
|
49
|
+
<div v-if="stepNumber && totalSteps">
|
|
50
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Step</h4>
|
|
51
|
+
<p class="text-[12px] text-slate-300">{{ stepNumber }} of {{ totalSteps }}</p>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div v-if="durationLabel">
|
|
55
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
56
|
+
Duration
|
|
57
|
+
</h4>
|
|
58
|
+
<p class="flex items-center gap-1.5 text-[12px] tabular-nums text-slate-300">
|
|
59
|
+
<UIcon
|
|
60
|
+
v-if="isRunning"
|
|
61
|
+
name="i-lucide-loader-circle"
|
|
62
|
+
class="h-3 w-3 animate-spin text-indigo-400"
|
|
63
|
+
/>
|
|
64
|
+
{{ durationLabel }}
|
|
65
|
+
<span v-if="isRunning" class="text-[11px] text-slate-500">elapsed</span>
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div v-if="formatClock(step.startedAt)">
|
|
70
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Started</h4>
|
|
71
|
+
<p class="text-[12px] text-slate-300">{{ formatClock(step.startedAt) }}</p>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div v-if="formatClock(step.finishedAt)">
|
|
75
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
76
|
+
Finished
|
|
77
|
+
</h4>
|
|
78
|
+
<p class="text-[12px] text-slate-300">{{ formatClock(step.finishedAt) }}</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div v-if="step.model">
|
|
82
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Model</h4>
|
|
83
|
+
<p class="break-all text-[12px] text-slate-300" :title="step.model">
|
|
84
|
+
{{ modelLabel ?? step.model }}
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div v-if="runId">
|
|
89
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Run</h4>
|
|
90
|
+
<p
|
|
91
|
+
class="cursor-pointer break-all font-mono text-[12px] text-slate-400 hover:text-slate-200"
|
|
92
|
+
:title="`${runId} — click to copy`"
|
|
93
|
+
@click="copyRunId"
|
|
94
|
+
>
|
|
95
|
+
{{ runId }}
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<!-- The model-activity rollup, embedded inline. The "View all calls →" link opens the
|
|
100
|
+
run's observability panel even when this step recorded no calls (e.g. a gate that
|
|
101
|
+
passed its precheck with no helper spun up), so every window reaches it the same
|
|
102
|
+
way; the metrics bar shows only when the step itself made calls. -->
|
|
103
|
+
<StepModelActivity :metrics="step.metrics" :instance-id="instanceId" />
|
|
104
|
+
</div>
|
|
105
|
+
</template>
|