@cat-factory/app 0.95.0 → 0.96.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/board/AgentFailureHistory.vue +3 -33
- package/app/components/board/FailureHistoryList.vue +46 -0
- package/app/components/clarity/ClarityReviewWindow.vue +83 -27
- package/app/components/panels/AgentStepDetail.vue +45 -0
- package/app/components/panels/ObservabilityPanel.vue +137 -4
- package/app/components/requirements/RequirementsReviewWindow.vue +27 -12
- package/app/composables/api/execution.ts +9 -0
- package/app/composables/api/providerConnections.ts +6 -2
- package/app/composables/useResultView.ts +11 -1
- package/app/pages/index.vue +1 -0
- package/app/stores/execution.spec.ts +35 -0
- package/app/stores/execution.ts +38 -3
- package/app/stores/observability.ts +38 -1
- package/app/types/execution.ts +3 -0
- package/i18n/locales/en.json +12 -2
- package/i18n/locales/es.json +12 -2
- package/i18n/locales/fr.json +12 -2
- package/i18n/locales/he.json +12 -2
- package/i18n/locales/ja.json +12 -2
- package/i18n/locales/pl.json +12 -2
- package/i18n/locales/tr.json +12 -2
- package/i18n/locales/uk.json +12 -2
- package/package.json +2 -2
|
@@ -5,14 +5,11 @@
|
|
|
5
5
|
// banner disappears, but this collapsed history stays available so every previous error
|
|
6
6
|
// remains viewable. Renders nothing when there is no trail.
|
|
7
7
|
import type { AgentFailure } from '~/types/domain'
|
|
8
|
-
import
|
|
8
|
+
import FailureHistoryList from '~/components/board/FailureHistoryList.vue'
|
|
9
9
|
|
|
10
10
|
const props = defineProps<{ failures: AgentFailure[] }>()
|
|
11
11
|
|
|
12
|
-
const { t
|
|
13
|
-
|
|
14
|
-
// Newest attempt first — the most recent failure is the most relevant to look at.
|
|
15
|
-
const ordered = computed(() => [...props.failures].reverse())
|
|
12
|
+
const { t } = useI18n()
|
|
16
13
|
</script>
|
|
17
14
|
|
|
18
15
|
<template>
|
|
@@ -28,33 +25,6 @@ const ordered = computed(() => [...props.failures].reverse())
|
|
|
28
25
|
{{ t('board.failure.history.previousErrors', { count: failures.length }, failures.length) }}
|
|
29
26
|
</summary>
|
|
30
27
|
|
|
31
|
-
<
|
|
32
|
-
<li
|
|
33
|
-
v-for="failure in ordered"
|
|
34
|
-
:key="failure.occurredAt"
|
|
35
|
-
class="rounded-md border border-slate-800/80 bg-slate-950/50 px-2.5 py-2"
|
|
36
|
-
data-testid="agent-failure-history-entry"
|
|
37
|
-
>
|
|
38
|
-
<div class="flex items-center gap-1.5 text-[10px] text-slate-500">
|
|
39
|
-
<UIcon name="i-lucide-alert-triangle" class="h-3 w-3 shrink-0 text-rose-400/70" />
|
|
40
|
-
<time>{{ d(new Date(failure.occurredAt), 'long') }}</time>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<p class="mt-1 text-[11px] leading-snug text-slate-300" :title="failure.message">
|
|
44
|
-
{{ failure.message }}
|
|
45
|
-
</p>
|
|
46
|
-
|
|
47
|
-
<p v-if="failure.hint" class="mt-1 text-[10px] leading-snug text-slate-500">
|
|
48
|
-
{{ failure.hint }}
|
|
49
|
-
</p>
|
|
50
|
-
|
|
51
|
-
<FailureDetail
|
|
52
|
-
:detail="failure.detail"
|
|
53
|
-
:message="failure.message"
|
|
54
|
-
summary-class="text-[10px] text-slate-500 hover:text-slate-300"
|
|
55
|
-
pre-class="bg-slate-950/80 text-[10px] text-slate-400"
|
|
56
|
-
/>
|
|
57
|
-
</li>
|
|
58
|
-
</ol>
|
|
28
|
+
<FailureHistoryList :failures="props.failures" class="mt-2" />
|
|
59
29
|
</details>
|
|
60
30
|
</template>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The newest-first list of failed-attempt entries (timestamp + message + hint + collapsible
|
|
3
|
+
// detail), shared by the task-inspector's "previous errors" disclosure (AgentFailureHistory)
|
|
4
|
+
// and the step-detail overlay's per-step "execution history". Presentational only — the caller
|
|
5
|
+
// decides which trail to pass (the whole run's, or one step's) and how to reveal it.
|
|
6
|
+
import type { AgentFailure } from '~/types/domain'
|
|
7
|
+
import FailureDetail from '~/components/board/FailureDetail.vue'
|
|
8
|
+
|
|
9
|
+
const props = defineProps<{ failures: AgentFailure[] }>()
|
|
10
|
+
|
|
11
|
+
const { d } = useI18n()
|
|
12
|
+
|
|
13
|
+
// Newest attempt first — the most recent failure is the most relevant to look at.
|
|
14
|
+
const ordered = computed(() => [...props.failures].reverse())
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<ol class="space-y-2">
|
|
19
|
+
<li
|
|
20
|
+
v-for="failure in ordered"
|
|
21
|
+
:key="failure.occurredAt"
|
|
22
|
+
class="rounded-md border border-slate-800/80 bg-slate-950/50 px-2.5 py-2"
|
|
23
|
+
data-testid="agent-failure-history-entry"
|
|
24
|
+
>
|
|
25
|
+
<div class="flex items-center gap-1.5 text-[10px] text-slate-500">
|
|
26
|
+
<UIcon name="i-lucide-alert-triangle" class="h-3 w-3 shrink-0 text-rose-400/70" />
|
|
27
|
+
<time>{{ d(new Date(failure.occurredAt), 'long') }}</time>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<p class="mt-1 text-[11px] leading-snug text-slate-300" :title="failure.message">
|
|
31
|
+
{{ failure.message }}
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p v-if="failure.hint" class="mt-1 text-[10px] leading-snug text-slate-500">
|
|
35
|
+
{{ failure.hint }}
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<FailureDetail
|
|
39
|
+
:detail="failure.detail"
|
|
40
|
+
:message="failure.message"
|
|
41
|
+
summary-class="text-[10px] text-slate-500 hover:text-slate-300"
|
|
42
|
+
pre-class="bg-slate-950/80 text-[10px] text-slate-400"
|
|
43
|
+
/>
|
|
44
|
+
</li>
|
|
45
|
+
</ol>
|
|
46
|
+
</template>
|
|
@@ -27,6 +27,10 @@ const { t } = useI18n()
|
|
|
27
27
|
|
|
28
28
|
// Draft replies, keyed by item id, so editing one item doesn't disturb others.
|
|
29
29
|
const drafts = ref<Record<string, string>>({})
|
|
30
|
+
// The server-side reply each draft was last seeded/synced to, so the seeding watch can refresh
|
|
31
|
+
// a draft when the recorded reply changes server-side WITHOUT clobbering one the human is
|
|
32
|
+
// actively editing (mirrors the requirements window).
|
|
33
|
+
const seededReply = ref<Record<string, string>>({})
|
|
30
34
|
// Freeform "do it differently" comment when redoing a merge the human was unhappy with.
|
|
31
35
|
const redoComment = ref('')
|
|
32
36
|
const showRedo = ref(false)
|
|
@@ -39,10 +43,14 @@ const showRedo = ref(false)
|
|
|
39
43
|
const { open, blockId, close } = useResultView('clarity-review', {
|
|
40
44
|
onOpen: (id) => {
|
|
41
45
|
drafts.value = {}
|
|
46
|
+
seededReply.value = {}
|
|
42
47
|
redoComment.value = ''
|
|
43
48
|
showRedo.value = false
|
|
44
49
|
void clarity.load(id)
|
|
45
50
|
},
|
|
51
|
+
// Flush any typed-but-unblurred answer before the view tears down (X, backdrop, Escape) so
|
|
52
|
+
// closing the window never silently drops it (UX-33).
|
|
53
|
+
onClose: () => void flushDrafts(),
|
|
46
54
|
})
|
|
47
55
|
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
48
56
|
const review = computed<ClarityReview | null>(() =>
|
|
@@ -144,18 +152,70 @@ function notifyError(title: string, e: unknown) {
|
|
|
144
152
|
})
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
// Answers auto-save on blur — no explicit "save" button (matching the requirements window, so
|
|
156
|
+
// muscle memory carries across the two, UX-34). The textarea is pre-seeded with the recorded
|
|
157
|
+
// reply (see the watch below); persist only when the trimmed draft actually differs from what's
|
|
158
|
+
// already recorded, so blurring an untouched field is a no-op.
|
|
159
|
+
async function persistDraft(item: ClarityReviewItem, r: ClarityReview | null = review.value) {
|
|
160
|
+
if (!r || frozen.value) return
|
|
149
161
|
const text = (drafts.value[item.id] ?? '').trim()
|
|
150
|
-
if (!text) return
|
|
162
|
+
if (!text || text === (item.reply ?? '').trim()) return
|
|
151
163
|
try {
|
|
152
|
-
await clarity.reply(
|
|
153
|
-
drafts.value = { ...drafts.value, [item.id]: '' }
|
|
164
|
+
await clarity.reply(r, item.id, text)
|
|
154
165
|
} catch (e) {
|
|
155
166
|
notifyError(t('clarity.error.saveAnswer'), e)
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
|
|
170
|
+
// Persist every dirty draft before an action that consumes the answers (or on window close).
|
|
171
|
+
// Snapshots the review up front and threads it through, so the persist completes even if the
|
|
172
|
+
// window closes mid-flush (the reactive `review` goes null the moment the view tears down).
|
|
173
|
+
async function flushDrafts() {
|
|
174
|
+
const r = review.value
|
|
175
|
+
if (!r) return
|
|
176
|
+
for (const item of r.items) {
|
|
177
|
+
if (item.status === 'open' || item.status === 'answered') await persistDraft(item, r)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Seed a draft for each finding from its recorded reply so the textarea shows the current
|
|
182
|
+
// answer (editing in place). New findings from a re-review get seeded; a draft the user hasn't
|
|
183
|
+
// diverged from is refreshed when the recorded reply changes server-side; drafts the user is
|
|
184
|
+
// actively editing are left untouched.
|
|
185
|
+
watch(
|
|
186
|
+
review,
|
|
187
|
+
(r) => {
|
|
188
|
+
if (!r) return
|
|
189
|
+
const nextDrafts = { ...drafts.value }
|
|
190
|
+
const nextSeeded = { ...seededReply.value }
|
|
191
|
+
let changed = false
|
|
192
|
+
for (const item of r.items) {
|
|
193
|
+
const reply = item.reply ?? ''
|
|
194
|
+
if (!(item.id in nextDrafts)) {
|
|
195
|
+
nextDrafts[item.id] = reply
|
|
196
|
+
nextSeeded[item.id] = reply
|
|
197
|
+
changed = true
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
const draft = nextDrafts[item.id] ?? ''
|
|
201
|
+
const seeded = nextSeeded[item.id] ?? ''
|
|
202
|
+
if (draft === seeded && draft !== reply) {
|
|
203
|
+
nextDrafts[item.id] = reply
|
|
204
|
+
nextSeeded[item.id] = reply
|
|
205
|
+
changed = true
|
|
206
|
+
} else if (draft === reply && seeded !== reply) {
|
|
207
|
+
nextSeeded[item.id] = reply
|
|
208
|
+
changed = true
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (changed) {
|
|
212
|
+
drafts.value = nextDrafts
|
|
213
|
+
seededReply.value = nextSeeded
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
{ immediate: true },
|
|
217
|
+
)
|
|
218
|
+
|
|
159
219
|
async function setStatus(item: ClarityReviewItem, itemStatus: ClarityItemStatus) {
|
|
160
220
|
if (!review.value) return
|
|
161
221
|
try {
|
|
@@ -168,6 +228,7 @@ async function setStatus(item: ClarityReviewItem, itemStatus: ClarityItemStatus)
|
|
|
168
228
|
async function incorporate(feedback?: string) {
|
|
169
229
|
if (!review.value || !blockId.value) return
|
|
170
230
|
try {
|
|
231
|
+
await flushDrafts()
|
|
171
232
|
await clarity.incorporate(review.value, feedback)
|
|
172
233
|
} catch (e) {
|
|
173
234
|
notifyError(t('clarity.error.incorporate'), e)
|
|
@@ -208,6 +269,7 @@ async function proceed() {
|
|
|
208
269
|
if (!blockId.value) return
|
|
209
270
|
acting.value = true
|
|
210
271
|
try {
|
|
272
|
+
await flushDrafts()
|
|
211
273
|
await clarity.proceed(blockId.value)
|
|
212
274
|
toast.add({ title: t('clarity.toast.proceeding'), icon: 'i-lucide-arrow-right' })
|
|
213
275
|
} catch (e) {
|
|
@@ -269,7 +331,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
269
331
|
</div>
|
|
270
332
|
</header>
|
|
271
333
|
|
|
272
|
-
<div class="flex min-h-0 flex-1">
|
|
334
|
+
<div class="flex min-h-0 flex-1 flex-col lg:flex-row">
|
|
273
335
|
<!-- main column -->
|
|
274
336
|
<div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
|
|
275
337
|
<p class="mb-4 text-sm text-slate-400">
|
|
@@ -372,9 +434,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
372
434
|
{{ item.detail }}
|
|
373
435
|
</p>
|
|
374
436
|
|
|
375
|
-
<!-- recorded answer
|
|
437
|
+
<!-- recorded answer (only for non-editable findings — for editable ones
|
|
438
|
+
the answer lives in the textarea below, seeded from the reply) -->
|
|
376
439
|
<div
|
|
377
|
-
v-if="item.reply"
|
|
440
|
+
v-if="item.reply && item.status !== 'open' && item.status !== 'answered'"
|
|
378
441
|
class="mt-2 rounded-md border-s-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
|
|
379
442
|
>
|
|
380
443
|
<span class="text-[10px] uppercase tracking-wide text-slate-500">
|
|
@@ -383,7 +446,8 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
383
446
|
<p class="whitespace-pre-line">{{ item.reply }}</p>
|
|
384
447
|
</div>
|
|
385
448
|
|
|
386
|
-
<!-- react: answer (relevant) or dismiss (irrelevant).
|
|
449
|
+
<!-- react: answer (relevant) or dismiss (irrelevant). The answer
|
|
450
|
+
auto-saves on blur — no explicit save button. Disabled once the
|
|
387
451
|
bug report is clarified / awaiting a higher-level decision. -->
|
|
388
452
|
<template v-if="item.status === 'open' || item.status === 'answered'">
|
|
389
453
|
<UTextarea
|
|
@@ -392,24 +456,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
392
456
|
autoresize
|
|
393
457
|
size="sm"
|
|
394
458
|
class="mt-2 w-full"
|
|
395
|
-
:placeholder="
|
|
396
|
-
item.reply
|
|
397
|
-
? t('clarity.refineAnswerPlaceholder')
|
|
398
|
-
: t('clarity.answerPlaceholder')
|
|
399
|
-
"
|
|
459
|
+
:placeholder="t('clarity.answerPlaceholder')"
|
|
400
460
|
:disabled="frozen"
|
|
461
|
+
@blur="persistDraft(item)"
|
|
401
462
|
/>
|
|
402
463
|
<div class="mt-2 flex flex-wrap items-center gap-2">
|
|
403
|
-
<UButton
|
|
404
|
-
color="primary"
|
|
405
|
-
variant="soft"
|
|
406
|
-
size="xs"
|
|
407
|
-
icon="i-lucide-corner-down-left"
|
|
408
|
-
:disabled="!(drafts[item.id] ?? '').trim() || frozen"
|
|
409
|
-
@click="submitReply(item)"
|
|
410
|
-
>
|
|
411
|
-
{{ t('clarity.saveAnswer') }}
|
|
412
|
-
</UButton>
|
|
413
464
|
<UButton
|
|
414
465
|
color="neutral"
|
|
415
466
|
variant="ghost"
|
|
@@ -476,10 +527,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
476
527
|
</template>
|
|
477
528
|
</div>
|
|
478
529
|
|
|
479
|
-
<!-- right action
|
|
480
|
-
|
|
530
|
+
<!-- action rail: a right-hand column on wide screens, a bottom action bar below `lg`
|
|
531
|
+
(never hidden — the gate is otherwise unadvanceable on a laptop split-screen /
|
|
532
|
+
tablet, UX-32). The informational stats collapse away below `lg` to keep the
|
|
533
|
+
bottom bar compact; the actions themselves always show. -->
|
|
534
|
+
<aside
|
|
535
|
+
class="flex w-full shrink-0 flex-col border-t border-slate-800 lg:w-72 lg:border-s lg:border-t-0"
|
|
536
|
+
>
|
|
481
537
|
<div class="flex flex-col gap-4 px-4 py-5">
|
|
482
|
-
<div v-if="review" class="space-y-2 text-xs text-slate-400">
|
|
538
|
+
<div v-if="review" class="hidden space-y-2 text-xs text-slate-400 lg:block">
|
|
483
539
|
<div class="flex items-center justify-between">
|
|
484
540
|
<span>{{ t('clarity.rail.findings') }}</span>
|
|
485
541
|
<span class="text-slate-300">{{ review.items.length }}</span>
|
|
@@ -11,6 +11,7 @@ import FrontendBindingsResolved from '~/components/panels/inspector/FrontendBind
|
|
|
11
11
|
import { UI_TESTER_AGENT_KIND } from '@cat-factory/contracts'
|
|
12
12
|
import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
|
|
13
13
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
14
|
+
import FailureHistoryList from '~/components/board/FailureHistoryList.vue'
|
|
14
15
|
import { useStepTimer } from '~/composables/useStepTimer'
|
|
15
16
|
import { useStepProse } from '~/composables/useStepProse'
|
|
16
17
|
import { useStepApproval } from '~/composables/useStepApproval'
|
|
@@ -84,6 +85,19 @@ const runNotes = computed(() => (isFrontendFrame.value ? (instance.value?.notes
|
|
|
84
85
|
const showProvisioning = ref(false)
|
|
85
86
|
const executionId = computed(() => instance.value?.id ?? null)
|
|
86
87
|
|
|
88
|
+
// This step's own "execution history": the run-level failure trail narrowed to the failures
|
|
89
|
+
// recorded for THIS step (each carries the `stepIndex` it failed at). Includes the current
|
|
90
|
+
// failure when the run is presently failed at this step (it moves into `failureHistory` only on
|
|
91
|
+
// the next retry). Revealed behind a toggle, mirroring the infra-attempts drawer above.
|
|
92
|
+
const stepFailures = computed(() => {
|
|
93
|
+
const idx = ctx.value?.stepIndex
|
|
94
|
+
if (idx == null) return []
|
|
95
|
+
const trail = [...(instance.value?.failureHistory ?? [])]
|
|
96
|
+
if (instance.value?.failure) trail.push(instance.value.failure)
|
|
97
|
+
return trail.filter((f) => f.stepIndex === idx)
|
|
98
|
+
})
|
|
99
|
+
const showHistory = ref(false)
|
|
100
|
+
|
|
87
101
|
// A failed run is no longer executing: a step left mid-flight (state still
|
|
88
102
|
// `working`, no `finishedAt`) must stop looking live — no ticking clock, no
|
|
89
103
|
// "spinning up" phase, no spinner.
|
|
@@ -188,6 +202,8 @@ watch(
|
|
|
188
202
|
() => {
|
|
189
203
|
prose.reset()
|
|
190
204
|
approval.resetForStep()
|
|
205
|
+
// Collapse the per-step execution history so reopening a different step starts clean.
|
|
206
|
+
showHistory.value = false
|
|
191
207
|
},
|
|
192
208
|
)
|
|
193
209
|
|
|
@@ -423,6 +439,35 @@ async function copyOutput() {
|
|
|
423
439
|
/>
|
|
424
440
|
</div>
|
|
425
441
|
|
|
442
|
+
<!-- this step's failure trail (the run-level history narrowed to this step),
|
|
443
|
+
behind a toggle — mirrors the "previous errors" history on the task inspector
|
|
444
|
+
but scoped to the step the user is looking at -->
|
|
445
|
+
<div v-if="stepFailures.length">
|
|
446
|
+
<UButton
|
|
447
|
+
:icon="showHistory ? 'i-lucide-chevron-up' : 'i-lucide-history'"
|
|
448
|
+
variant="ghost"
|
|
449
|
+
size="xs"
|
|
450
|
+
data-testid="step-execution-history-toggle"
|
|
451
|
+
@click="
|
|
452
|
+
() => {
|
|
453
|
+
showHistory = !showHistory
|
|
454
|
+
}
|
|
455
|
+
"
|
|
456
|
+
>
|
|
457
|
+
{{
|
|
458
|
+
showHistory
|
|
459
|
+
? t('panels.stepDetail.hideExecutionHistory')
|
|
460
|
+
: t('panels.stepDetail.executionHistory')
|
|
461
|
+
}}
|
|
462
|
+
</UButton>
|
|
463
|
+
<FailureHistoryList
|
|
464
|
+
v-if="showHistory"
|
|
465
|
+
class="mt-2"
|
|
466
|
+
:failures="stepFailures"
|
|
467
|
+
data-testid="step-execution-history"
|
|
468
|
+
/>
|
|
469
|
+
</div>
|
|
470
|
+
|
|
426
471
|
<!-- tester report: what was tested, the per-area outcomes, the concerns
|
|
427
472
|
it raised and the greenlight verdict; plus the fixer-loop phase -->
|
|
428
473
|
<StepTestReport v-if="testReport" :report="testReport" :phase="testPhase" />
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, reactive, ref, watch } from 'vue'
|
|
3
3
|
import { onKeyStroke } from '@vueuse/core'
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AgentContextSnapshot,
|
|
6
|
+
AgentSearchQuery,
|
|
7
|
+
LlmCallMetric,
|
|
8
|
+
WebSearchProvider,
|
|
9
|
+
} from '~/types/execution'
|
|
5
10
|
import { agentKindMeta } from '~/utils/catalog'
|
|
6
11
|
import { formatMs, formatTokens, pct } from '~/utils/observability'
|
|
7
12
|
|
|
@@ -33,8 +38,9 @@ const error = computed(() =>
|
|
|
33
38
|
executionId.value ? (observability.errors[executionId.value] ?? null) : null,
|
|
34
39
|
)
|
|
35
40
|
|
|
36
|
-
// Which view is shown:
|
|
37
|
-
|
|
41
|
+
// Which view is shown: per-call model activity, the complete provided context, or the
|
|
42
|
+
// performed web searches.
|
|
43
|
+
const view = ref<'calls' | 'context' | 'search'>('calls')
|
|
38
44
|
|
|
39
45
|
const contextSnapshots = computed<AgentContextSnapshot[]>(() =>
|
|
40
46
|
executionId.value ? observability.contextFor(executionId.value) : [],
|
|
@@ -43,6 +49,39 @@ const contextLoading = computed(
|
|
|
43
49
|
() => !!executionId.value && observability.isContextLoading(executionId.value),
|
|
44
50
|
)
|
|
45
51
|
|
|
52
|
+
const searchQueries = computed<AgentSearchQuery[]>(() =>
|
|
53
|
+
executionId.value ? observability.searchQueriesFor(executionId.value) : [],
|
|
54
|
+
)
|
|
55
|
+
const searchLoading = computed(
|
|
56
|
+
() => !!executionId.value && observability.isSearchQueriesLoading(executionId.value),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// Brand names, kept verbatim across locales (not translatable prose).
|
|
60
|
+
const PROVIDER_LABEL: Record<WebSearchProvider, string> = { brave: 'Brave', searxng: 'SearXNG' }
|
|
61
|
+
function providerLabel(provider: WebSearchProvider | null): string {
|
|
62
|
+
return provider ? PROVIDER_LABEL[provider] : ''
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Whether web search was available to this run's container agents, and which provider(s)
|
|
66
|
+
// served it — a static per-run fact set on each container step at dispatch (not gated by
|
|
67
|
+
// prompt-recording telemetry, unlike the performed queries below).
|
|
68
|
+
const searchAvailability = computed<{ available: boolean; providers: WebSearchProvider[] } | null>(
|
|
69
|
+
() => {
|
|
70
|
+
const steps = (instance.value?.steps ?? []).filter((s) => s.search)
|
|
71
|
+
if (!steps.length) return null
|
|
72
|
+
const available = steps.some((s) => s.search?.available)
|
|
73
|
+
const providers = [
|
|
74
|
+
...new Set(
|
|
75
|
+
steps
|
|
76
|
+
.map((s) => s.search)
|
|
77
|
+
.filter((x): x is NonNullable<typeof x> => !!x?.available && !!x.provider)
|
|
78
|
+
.map((x) => x.provider as WebSearchProvider),
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
return { available, providers }
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
|
|
46
85
|
// Load (and refresh) whenever a different run's panel opens. Reset to the calls view
|
|
47
86
|
// and load both the calls and the provided-context snapshots.
|
|
48
87
|
watch(
|
|
@@ -52,6 +91,7 @@ watch(
|
|
|
52
91
|
view.value = 'calls'
|
|
53
92
|
void observability.load(id)
|
|
54
93
|
void observability.loadContext(id)
|
|
94
|
+
void observability.loadSearchQueries(id)
|
|
55
95
|
}
|
|
56
96
|
},
|
|
57
97
|
// Lazy v-if mount: the panel mounts with executionId already set, so load immediately.
|
|
@@ -182,6 +222,17 @@ function exportJson() {
|
|
|
182
222
|
>
|
|
183
223
|
{{ t('observability.providedContext') }}
|
|
184
224
|
</button>
|
|
225
|
+
<button
|
|
226
|
+
class="rounded-md px-2.5 py-1 transition"
|
|
227
|
+
:class="
|
|
228
|
+
view === 'search'
|
|
229
|
+
? 'bg-slate-800 text-slate-100'
|
|
230
|
+
: 'text-slate-400 hover:text-slate-200'
|
|
231
|
+
"
|
|
232
|
+
@click="view = 'search'"
|
|
233
|
+
>
|
|
234
|
+
{{ t('observability.webSearch') }}
|
|
235
|
+
</button>
|
|
185
236
|
</div>
|
|
186
237
|
<UButton
|
|
187
238
|
v-if="view === 'calls'"
|
|
@@ -434,7 +485,7 @@ function exportJson() {
|
|
|
434
485
|
</div>
|
|
435
486
|
|
|
436
487
|
<!-- Provided context: the complete context each container agent was given. -->
|
|
437
|
-
<div v-else class="mx-auto max-w-4xl space-y-5">
|
|
488
|
+
<div v-else-if="view === 'context'" class="mx-auto max-w-4xl space-y-5">
|
|
438
489
|
<p
|
|
439
490
|
v-if="contextLoading && !contextSnapshots.length"
|
|
440
491
|
class="flex items-center justify-center gap-2 py-8 text-center text-sm text-slate-500"
|
|
@@ -553,6 +604,88 @@ function exportJson() {
|
|
|
553
604
|
</li>
|
|
554
605
|
</ul>
|
|
555
606
|
</div>
|
|
607
|
+
|
|
608
|
+
<div v-else class="mx-auto max-w-4xl space-y-5">
|
|
609
|
+
<!-- Availability header: a static per-run fact (not telemetry-gated). -->
|
|
610
|
+
<section
|
|
611
|
+
v-if="searchAvailability"
|
|
612
|
+
class="flex flex-wrap items-center gap-x-3 gap-y-1 rounded-xl border border-slate-800 bg-slate-900/50 px-4 py-3 text-[13px]"
|
|
613
|
+
>
|
|
614
|
+
<span class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
615
|
+
{{ t('observability.webSearch') }}
|
|
616
|
+
</span>
|
|
617
|
+
<span
|
|
618
|
+
class="inline-flex items-center gap-1.5"
|
|
619
|
+
:class="searchAvailability.available ? 'text-emerald-300' : 'text-slate-400'"
|
|
620
|
+
>
|
|
621
|
+
<UIcon
|
|
622
|
+
:name="searchAvailability.available ? 'i-lucide-globe' : 'i-lucide-globe-lock'"
|
|
623
|
+
class="h-4 w-4"
|
|
624
|
+
/>
|
|
625
|
+
{{
|
|
626
|
+
searchAvailability.available
|
|
627
|
+
? t('observability.search.available')
|
|
628
|
+
: t('observability.search.unavailable')
|
|
629
|
+
}}
|
|
630
|
+
</span>
|
|
631
|
+
<span v-if="searchAvailability.providers.length" class="text-slate-400 tabular-nums">
|
|
632
|
+
{{ t('observability.search.provider') }}:
|
|
633
|
+
{{ searchAvailability.providers.map(providerLabel).join(', ') }}
|
|
634
|
+
</span>
|
|
635
|
+
</section>
|
|
636
|
+
|
|
637
|
+
<p
|
|
638
|
+
v-if="searchLoading && !searchQueries.length"
|
|
639
|
+
class="flex items-center justify-center gap-2 py-8 text-center text-sm text-slate-500"
|
|
640
|
+
>
|
|
641
|
+
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
|
|
642
|
+
{{ t('observability.loadingSearch') }}
|
|
643
|
+
</p>
|
|
644
|
+
<p
|
|
645
|
+
v-else-if="!searchQueries.length"
|
|
646
|
+
class="rounded-lg border border-dashed border-slate-800 py-8 text-center text-sm text-slate-500"
|
|
647
|
+
>
|
|
648
|
+
{{ t('observability.noSearch') }}
|
|
649
|
+
</p>
|
|
650
|
+
|
|
651
|
+
<div v-else>
|
|
652
|
+
<div class="mb-2 text-[11px] uppercase tracking-wide text-slate-500">
|
|
653
|
+
{{ t('observability.search.queriesTitle') }}
|
|
654
|
+
</div>
|
|
655
|
+
<ul class="space-y-2">
|
|
656
|
+
<li
|
|
657
|
+
v-for="q in searchQueries"
|
|
658
|
+
:key="q.id"
|
|
659
|
+
class="flex items-center gap-3 rounded-xl border border-slate-800 bg-slate-900/40 px-4 py-2.5"
|
|
660
|
+
>
|
|
661
|
+
<UIcon
|
|
662
|
+
:name="agentMeta(q.agentKind).icon"
|
|
663
|
+
class="h-4 w-4 shrink-0"
|
|
664
|
+
:style="{ color: agentMeta(q.agentKind).color }"
|
|
665
|
+
:title="agentMeta(q.agentKind).label"
|
|
666
|
+
/>
|
|
667
|
+
<span class="min-w-0 flex-1 truncate text-[13px] text-slate-200" :title="q.query">
|
|
668
|
+
{{ q.query }}
|
|
669
|
+
</span>
|
|
670
|
+
<div
|
|
671
|
+
class="flex shrink-0 items-center gap-2.5 text-[11px] tabular-nums text-slate-400"
|
|
672
|
+
>
|
|
673
|
+
<span v-if="q.provider" class="hidden sm:inline">{{
|
|
674
|
+
providerLabel(q.provider)
|
|
675
|
+
}}</span>
|
|
676
|
+
<span>{{
|
|
677
|
+
t(
|
|
678
|
+
'observability.search.resultsCount',
|
|
679
|
+
{ count: q.resultCount },
|
|
680
|
+
q.resultCount,
|
|
681
|
+
)
|
|
682
|
+
}}</span>
|
|
683
|
+
<span class="hidden text-slate-600 md:inline">{{ clock(q.createdAt) }}</span>
|
|
684
|
+
</div>
|
|
685
|
+
</li>
|
|
686
|
+
</ul>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
556
689
|
</div>
|
|
557
690
|
</div>
|
|
558
691
|
</Transition>
|
|
@@ -59,6 +59,10 @@ const { open, blockId, instanceId, stepIndex, close } = useResultView('requireme
|
|
|
59
59
|
docCollapsedOverride.value = null
|
|
60
60
|
void requirements.load(id)
|
|
61
61
|
},
|
|
62
|
+
// Closing the window (X, backdrop, Escape) must not silently drop an answer the user typed
|
|
63
|
+
// but never blurred out of. Flush before the view tears down; flushDrafts captures the
|
|
64
|
+
// review up front so the persist survives blockId going null on close (UX-33).
|
|
65
|
+
onClose: () => void flushDrafts(),
|
|
62
66
|
})
|
|
63
67
|
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
64
68
|
const review = computed<RequirementReview | null>(() =>
|
|
@@ -167,23 +171,29 @@ function notifyError(title: string, e: unknown) {
|
|
|
167
171
|
// the recorded reply (see the watch below); editing and blurring persists it. Persist only
|
|
168
172
|
// when the trimmed draft actually differs from what's already recorded, so blurring an
|
|
169
173
|
// untouched field is a no-op.
|
|
170
|
-
async function persistDraft(
|
|
171
|
-
|
|
174
|
+
async function persistDraft(
|
|
175
|
+
item: RequirementReviewItem,
|
|
176
|
+
r: RequirementReview | null = review.value,
|
|
177
|
+
) {
|
|
178
|
+
if (!r || frozen.value) return
|
|
172
179
|
const text = (drafts.value[item.id] ?? '').trim()
|
|
173
180
|
if (!text || text === (item.reply ?? '').trim()) return
|
|
174
181
|
try {
|
|
175
|
-
await requirements.reply(
|
|
182
|
+
await requirements.reply(r, item.id, text)
|
|
176
183
|
} catch (e) {
|
|
177
184
|
notifyError(t('requirements.errors.saveAnswer'), e)
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
|
|
181
|
-
// Persist every dirty draft before an action that consumes the answers
|
|
182
|
-
// user typed but never blurred out of isn't lost.
|
|
188
|
+
// Persist every dirty draft before an action that consumes the answers (or on window close),
|
|
189
|
+
// so a value the user typed but never blurred out of isn't lost. Snapshots the review up front
|
|
190
|
+
// and threads it through, so the persist completes even if the window closes mid-flush (the
|
|
191
|
+
// reactive `review` goes null the moment the view tears down).
|
|
183
192
|
async function flushDrafts() {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
const r = review.value
|
|
194
|
+
if (!r) return
|
|
195
|
+
for (const item of r.items) {
|
|
196
|
+
if (item.status === 'open' || item.status === 'answered') await persistDraft(item, r)
|
|
187
197
|
}
|
|
188
198
|
}
|
|
189
199
|
|
|
@@ -483,7 +493,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
483
493
|
</div>
|
|
484
494
|
</header>
|
|
485
495
|
|
|
486
|
-
<div class="flex min-h-0 flex-1">
|
|
496
|
+
<div class="flex min-h-0 flex-1 flex-col lg:flex-row">
|
|
487
497
|
<!-- main column -->
|
|
488
498
|
<div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
|
|
489
499
|
<i18n-t
|
|
@@ -842,10 +852,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
842
852
|
</template>
|
|
843
853
|
</div>
|
|
844
854
|
|
|
845
|
-
<!-- right action
|
|
846
|
-
|
|
855
|
+
<!-- action rail: a right-hand column on wide screens, a bottom action bar below `lg`
|
|
856
|
+
(never hidden — the gate is otherwise unadvanceable on a laptop split-screen /
|
|
857
|
+
tablet, UX-32). The informational stats collapse away below `lg` to keep the
|
|
858
|
+
bottom bar compact; the actions themselves always show. -->
|
|
859
|
+
<aside
|
|
860
|
+
class="flex w-full shrink-0 flex-col border-t border-slate-800 lg:w-72 lg:border-s lg:border-t-0"
|
|
861
|
+
>
|
|
847
862
|
<div class="flex flex-col gap-4 px-4 py-5">
|
|
848
|
-
<div v-if="review" class="space-y-2 text-xs text-slate-400">
|
|
863
|
+
<div v-if="review" class="hidden space-y-2 text-xs text-slate-400 lg:block">
|
|
849
864
|
<div class="flex items-center justify-between">
|
|
850
865
|
<span>{{ t('requirements.stats.findings') }}</span>
|
|
851
866
|
<span class="text-slate-300">{{ review.items.length }}</span>
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
exportExecutionLlmMetricsContract,
|
|
5
5
|
getExecutionAgentContextContract,
|
|
6
6
|
getExecutionLlmMetricsContract,
|
|
7
|
+
getExecutionSearchQueriesContract,
|
|
7
8
|
mergeBlockContract,
|
|
8
9
|
rejectStepContract,
|
|
9
10
|
requestStepChangesContract,
|
|
@@ -145,6 +146,14 @@ export function executionApi({ send, sendWith, ws, pwHeaders }: ApiContext) {
|
|
|
145
146
|
pathParams: { executionId },
|
|
146
147
|
}),
|
|
147
148
|
|
|
149
|
+
// The web searches each container agent performed in a run (query, provider,
|
|
150
|
+
// result count). Empty when not wired / storing is off.
|
|
151
|
+
getSearchQueries: (workspaceId: string, executionId: string) =>
|
|
152
|
+
send(getExecutionSearchQueriesContract, {
|
|
153
|
+
pathPrefix: ws(workspaceId),
|
|
154
|
+
pathParams: { executionId },
|
|
155
|
+
}),
|
|
156
|
+
|
|
148
157
|
// ---- spend safeguard --------------------------------------------------
|
|
149
158
|
resumeSpend: (workspaceId: string) =>
|
|
150
159
|
send(resumeSpendContract, { pathPrefix: ws(workspaceId) }),
|
|
@@ -56,14 +56,18 @@ export function providerConnectionsApi({ send, ws }: ApiContext) {
|
|
|
56
56
|
// Branch on the kind so `send` sees a single concrete contract (a union contract can't
|
|
57
57
|
// type-check the optional `queryParams`).
|
|
58
58
|
describeProvider: (workspaceId: string, kind: ProviderConnectionKind, backendKind?: string) =>
|
|
59
|
+
// Only send the `kind` query when a concrete backend is requested. Passing
|
|
60
|
+
// `{ kind: undefined }` serializes to `?kind=` (an empty string), which the backend
|
|
61
|
+
// reads as a real — unknown — backend kind and rejects with 422; omitting the param
|
|
62
|
+
// lets it fall back to the workspace's stored/default kind as intended.
|
|
59
63
|
kind === 'environment'
|
|
60
64
|
? send(CONTRACTS.environment.describe, {
|
|
61
65
|
pathPrefix: ws(workspaceId),
|
|
62
|
-
queryParams: { kind: backendKind },
|
|
66
|
+
queryParams: backendKind ? { kind: backendKind } : {},
|
|
63
67
|
})
|
|
64
68
|
: send(CONTRACTS['runner-pool'].describe, {
|
|
65
69
|
pathPrefix: ws(workspaceId),
|
|
66
|
-
queryParams: { kind: backendKind },
|
|
70
|
+
queryParams: backendKind ? { kind: backendKind } : {},
|
|
67
71
|
}),
|
|
68
72
|
|
|
69
73
|
getProviderConnection: (workspaceId: string, kind: ProviderConnectionKind) =>
|
|
@@ -12,8 +12,17 @@
|
|
|
12
12
|
*
|
|
13
13
|
* A synchronous window (one that reads its data straight off the execution step, like the
|
|
14
14
|
* test report) simply omits `onOpen`.
|
|
15
|
+
*
|
|
16
|
+
* `onClose` runs on EVERY close path — the X button, backdrop click, and the Escape key
|
|
17
|
+
* handled here — BEFORE the view is torn down, so a window with unsaved draft input (the
|
|
18
|
+
* review windows) can flush it in one place instead of every caller having to remember to.
|
|
19
|
+
* It runs synchronously; if it kicks off async work it must capture whatever it needs first,
|
|
20
|
+
* because `blockId`/the derived state go null the moment the view closes.
|
|
15
21
|
*/
|
|
16
|
-
export function useResultView(
|
|
22
|
+
export function useResultView(
|
|
23
|
+
viewId: string,
|
|
24
|
+
opts?: { onOpen?: (blockId: string) => void; onClose?: () => void },
|
|
25
|
+
) {
|
|
17
26
|
const ui = useUiStore()
|
|
18
27
|
|
|
19
28
|
const open = computed(() => ui.resultView?.view === viewId)
|
|
@@ -26,6 +35,7 @@ export function useResultView(viewId: string, opts?: { onOpen?: (blockId: string
|
|
|
26
35
|
const stage = computed(() => (open.value ? (ui.resultView!.stage ?? null) : null))
|
|
27
36
|
|
|
28
37
|
function close() {
|
|
38
|
+
if (open.value) opts?.onClose?.()
|
|
29
39
|
ui.closeResultView()
|
|
30
40
|
}
|
|
31
41
|
|
package/app/pages/index.vue
CHANGED
|
@@ -17,6 +17,7 @@ import DecisionModal from '~/components/panels/DecisionModal.vue'
|
|
|
17
17
|
import AgentStepDetail from '~/components/panels/AgentStepDetail.vue'
|
|
18
18
|
import StepResultViewHost from '~/components/panels/StepResultViewHost.vue'
|
|
19
19
|
import AddTaskModal from '~/components/board/AddTaskModal.vue'
|
|
20
|
+
import CreateInitiativeModal from '~/components/board/CreateInitiativeModal.vue'
|
|
20
21
|
import GitHubOnboarding from '~/components/github/GitHubOnboarding.vue'
|
|
21
22
|
import CommandBar from '~/components/layout/CommandBar.vue'
|
|
22
23
|
import PersonalCredentialModal from '~/components/providers/PersonalCredentialModal.vue'
|
|
@@ -76,6 +76,41 @@ describe('execution store snapshot/event reconcile', () => {
|
|
|
76
76
|
expect(store.getInstance('e1')?.rev).toBe(2)
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
+
it('drops a superseded failed run when a retry replaces it under a new id (same block)', () => {
|
|
80
|
+
// A failed run for a block is cached...
|
|
81
|
+
store.hydrate(
|
|
82
|
+
[{ id: 'e_old', blockId: 'b1', steps: [], status: 'failed', rev: 1 } as never],
|
|
83
|
+
'ws1',
|
|
84
|
+
)
|
|
85
|
+
// ...then a retry mints a FRESH run (new id) for the SAME block and deletes the old one
|
|
86
|
+
// server-side. The post-retry snapshot carries only the new running run.
|
|
87
|
+
store.hydrate(
|
|
88
|
+
[{ id: 'e_new', blockId: 'b1', steps: [], status: 'running', rev: 1 } as never],
|
|
89
|
+
'ws1',
|
|
90
|
+
)
|
|
91
|
+
// The dead predecessor must not linger and shadow the running run in the by-block projection.
|
|
92
|
+
expect(store.getInstance('e_old')).toBeUndefined()
|
|
93
|
+
expect(store.getInstance('e_new')?.status).toBe('running')
|
|
94
|
+
expect(store.getByBlock('b1')?.id).toBe('e_new')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('keeps a live-added running run when a stale snapshot still lists its block predecessor', () => {
|
|
98
|
+
// A retry already minted e_new (running) for b1 — a live event added it to the cache...
|
|
99
|
+
store.hydrate(
|
|
100
|
+
[{ id: 'e_new', blockId: 'b1', steps: [], status: 'running', rev: 1 } as never],
|
|
101
|
+
'ws1',
|
|
102
|
+
)
|
|
103
|
+
// ...but a reconnect resync fetched BEFORE the retry resolves late (under load) and still
|
|
104
|
+
// carries the now-deleted predecessor e_old (failed) for the same block.
|
|
105
|
+
store.hydrate(
|
|
106
|
+
[{ id: 'e_old', blockId: 'b1', steps: [], status: 'failed', rev: 1 } as never],
|
|
107
|
+
'ws1',
|
|
108
|
+
)
|
|
109
|
+
// The live running run must survive — only a TERMINAL cached run is a superseded predecessor.
|
|
110
|
+
expect(store.getInstance('e_new')?.status).toBe('running')
|
|
111
|
+
expect(store.getByBlock('b1')?.id).toBe('e_new')
|
|
112
|
+
})
|
|
113
|
+
|
|
79
114
|
it('a workspace switch replaces the cache outright (no cross-board leak)', () => {
|
|
80
115
|
store.hydrate([run('e1', 1, 'running')], 'ws1')
|
|
81
116
|
store.upsert(run('e2', 1, 'running'))
|
package/app/stores/execution.ts
CHANGED
|
@@ -35,6 +35,11 @@ export const useExecutionStore = defineStore('execution', () => {
|
|
|
35
35
|
return e.rev ?? 0
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** A finished run — nothing further will execute or emit. Matches `runLive`/`runFailed`. */
|
|
39
|
+
function isTerminal(status: ExecutionInstance['status']): boolean {
|
|
40
|
+
return status === 'done' || status === 'failed'
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
/**
|
|
39
44
|
* Reconcile the cached executions with a server snapshot for `workspaceId`. A snapshot
|
|
40
45
|
* is authoritative EXCEPT where a live `execution` event already advanced (or ADDED) a
|
|
@@ -45,7 +50,24 @@ export const useExecutionStore = defineStore('execution', () => {
|
|
|
45
50
|
* can't revert a just-terminal run to `running`. A terminal run emits nothing
|
|
46
51
|
* further, so a regression here would strand the UI until an unrelated refresh.
|
|
47
52
|
* - DROP: a run a live event just ADDED that the (older) snapshot never saw — keep it
|
|
48
|
-
* rather than silently dropping it
|
|
53
|
+
* rather than silently dropping it, but ONLY when it is not the terminal predecessor a
|
|
54
|
+
* retry replaced (see below).
|
|
55
|
+
*
|
|
56
|
+
* The DROP caveat matters because a retry/restart REPLACES a block's run with a fresh one
|
|
57
|
+
* under a NEW id (the old run is deleted server-side), so the two attempts can't be
|
|
58
|
+
* reconciled by id or `rev`. Since there is exactly one run per block, a cached-only run
|
|
59
|
+
* whose block the snapshot already covers is that superseded predecessor — drop it.
|
|
60
|
+
* Preserving it would leave the dead `failed` run shadowing the running one in the by-block
|
|
61
|
+
* projection (`agentRuns.byBlock`, last-write-wins), keeping the failure banner up and its
|
|
62
|
+
* empty trail hiding the retry's carried-forward failure history.
|
|
63
|
+
*
|
|
64
|
+
* The drop is gated on the cached run being TERMINAL (`done`/`failed`): only a finished
|
|
65
|
+
* predecessor is ever superseded. A cached run still `running`/`blocked`/`paused` is a
|
|
66
|
+
* genuinely live-added run, so it must survive even when a stale reconnect snapshot (fetched
|
|
67
|
+
* before a retry, resolving late under load — see `useWorkspaceStream`) still lists its
|
|
68
|
+
* block's now-deleted predecessor. Dropping a live run there would strand the UI showing the
|
|
69
|
+
* dead attempt — the inverse of the bug this guard fixes — and `rev` can't catch it (the
|
|
70
|
+
* ids differ).
|
|
49
71
|
*/
|
|
50
72
|
function hydrate(next: ExecutionInstance[], workspaceId: string) {
|
|
51
73
|
const sameWorkspace = hydratedWorkspaceId === workspaceId
|
|
@@ -55,12 +77,19 @@ export const useExecutionStore = defineStore('execution', () => {
|
|
|
55
77
|
return
|
|
56
78
|
}
|
|
57
79
|
const incomingIds = new Set(next.map((e) => e.id))
|
|
80
|
+
const incomingBlocks = new Set(next.map((e) => e.blockId))
|
|
58
81
|
const held = new Map(instances.value.map((e) => [e.id, e]))
|
|
59
82
|
const reconciled = next.map((incoming) => {
|
|
60
83
|
const current = held.get(incoming.id)
|
|
61
84
|
return current && revOf(current) > revOf(incoming) ? current : incoming
|
|
62
85
|
})
|
|
63
|
-
|
|
86
|
+
// Preserve a cached-only run UNLESS it is the terminal predecessor a retry replaced: a
|
|
87
|
+
// finished (`done`/`failed`) run whose block the snapshot now covers under a fresh id.
|
|
88
|
+
// Gating on the CACHED run being terminal keeps a live `running`/`blocked`/`paused` run
|
|
89
|
+
// that a stale snapshot happens to omit.
|
|
90
|
+
const preserved = [...held.values()].filter(
|
|
91
|
+
(e) => !incomingIds.has(e.id) && !(isTerminal(e.status) && incomingBlocks.has(e.blockId)),
|
|
92
|
+
)
|
|
64
93
|
instances.value = [...reconciled, ...preserved]
|
|
65
94
|
}
|
|
66
95
|
|
|
@@ -87,7 +116,13 @@ export const useExecutionStore = defineStore('execution', () => {
|
|
|
87
116
|
}
|
|
88
117
|
|
|
89
118
|
function getByBlock(blockId: string) {
|
|
90
|
-
|
|
119
|
+
const runs = instances.value.filter((e) => e.blockId === blockId)
|
|
120
|
+
if (runs.length <= 1) return runs[0]
|
|
121
|
+
// A block only holds several runs transiently: a stale reconnect snapshot re-listing a
|
|
122
|
+
// retry's now-deleted terminal predecessor alongside the live successor. Prefer the live
|
|
123
|
+
// one so this projection agrees with `agentRuns.byBlock` (whose last-write-wins already
|
|
124
|
+
// resolves to it) — the failed predecessor is dead and about to fall out on the next read.
|
|
125
|
+
return runs.find((e) => !isTerminal(e.status)) ?? runs.at(-1)
|
|
91
126
|
}
|
|
92
127
|
|
|
93
128
|
/** How many decisions anywhere are awaiting a human. */
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { ref } from 'vue'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
AgentContextSnapshot,
|
|
5
|
+
AgentSearchQuery,
|
|
6
|
+
LlmCallActivity,
|
|
7
|
+
LlmCallMetric,
|
|
8
|
+
} from '~/types/execution'
|
|
4
9
|
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
10
|
|
|
6
11
|
/**
|
|
@@ -23,6 +28,10 @@ export const useObservabilityStore = defineStore('observability', () => {
|
|
|
23
28
|
const contextByExecution = ref<Record<string, AgentContextSnapshot[]>>({})
|
|
24
29
|
/** Execution ids whose context is currently loading. */
|
|
25
30
|
const contextLoading = ref<Set<string>>(new Set())
|
|
31
|
+
/** Per-execution-id performed-search-query list (newest first). */
|
|
32
|
+
const searchQueriesByExecution = ref<Record<string, AgentSearchQuery[]>>({})
|
|
33
|
+
/** Execution ids whose search queries are currently loading. */
|
|
34
|
+
const searchQueriesLoading = ref<Set<string>>(new Set())
|
|
26
35
|
/** Execution ids currently loading. */
|
|
27
36
|
const loading = ref<Set<string>>(new Set())
|
|
28
37
|
/** Execution ids currently exporting. */
|
|
@@ -134,6 +143,30 @@ export const useObservabilityStore = defineStore('observability', () => {
|
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
|
|
146
|
+
function searchQueriesFor(executionId: string): AgentSearchQuery[] {
|
|
147
|
+
return searchQueriesByExecution.value[executionId] ?? []
|
|
148
|
+
}
|
|
149
|
+
function isSearchQueriesLoading(executionId: string): boolean {
|
|
150
|
+
return searchQueriesLoading.value.has(executionId)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Load (or refresh) the performed web-search queries for a run. */
|
|
154
|
+
async function loadSearchQueries(executionId: string) {
|
|
155
|
+
if (!workspace.workspaceId) return
|
|
156
|
+
withFlag(searchQueriesLoading, executionId, true)
|
|
157
|
+
try {
|
|
158
|
+
const { searchQueries } = await api.getSearchQueries(workspace.requireId(), executionId)
|
|
159
|
+
searchQueriesByExecution.value = {
|
|
160
|
+
...searchQueriesByExecution.value,
|
|
161
|
+
[executionId]: searchQueries,
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// Best-effort: the panel shows an empty state; nothing is persisted client-side.
|
|
165
|
+
} finally {
|
|
166
|
+
withFlag(searchQueriesLoading, executionId, false)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
137
170
|
/**
|
|
138
171
|
* Fetch the LLM-friendly export bundle and trigger a client-side download. The
|
|
139
172
|
* events socket auths via a Bearer header (a plain `<a download>` can't), so we
|
|
@@ -169,5 +202,9 @@ export const useObservabilityStore = defineStore('observability', () => {
|
|
|
169
202
|
contextFor,
|
|
170
203
|
isContextLoading,
|
|
171
204
|
loadContext,
|
|
205
|
+
searchQueriesByExecution,
|
|
206
|
+
searchQueriesFor,
|
|
207
|
+
isSearchQueriesLoading,
|
|
208
|
+
loadSearchQueries,
|
|
172
209
|
}
|
|
173
210
|
})
|
package/app/types/execution.ts
CHANGED
package/i18n/locales/en.json
CHANGED
|
@@ -1006,6 +1006,8 @@
|
|
|
1006
1006
|
"companionCapDetail": "Do one more automatic rework round, proceed to the next step accepting the current output, or stop and reset the task so you can edit the inputs and resubmit.",
|
|
1007
1007
|
"infraAttempts": "Infrastructure attempts",
|
|
1008
1008
|
"hideInfraAttempts": "Hide infrastructure attempts",
|
|
1009
|
+
"executionHistory": "Execution history",
|
|
1010
|
+
"hideExecutionHistory": "Hide execution history",
|
|
1009
1011
|
"editingConclusions": "Editing the conclusions",
|
|
1010
1012
|
"editConclusionsPlaceholder": "Edit the agent's conclusions; your edits are saved when you approve…",
|
|
1011
1013
|
"noProseOutput": "This agent produced no prose output.",
|
|
@@ -1139,6 +1141,16 @@
|
|
|
1139
1141
|
"systemPrompt": "System prompt",
|
|
1140
1142
|
"userPrompt": "User prompt",
|
|
1141
1143
|
"details": "Details"
|
|
1144
|
+
},
|
|
1145
|
+
"webSearch": "Web search",
|
|
1146
|
+
"loadingSearch": "Loading web searches…",
|
|
1147
|
+
"noSearch": "No web searches recorded for this run. Queries are captured when web search runs and the workspace has 'Store full agent context' enabled.",
|
|
1148
|
+
"search": {
|
|
1149
|
+
"available": "Available",
|
|
1150
|
+
"unavailable": "Unavailable",
|
|
1151
|
+
"provider": "Provider",
|
|
1152
|
+
"resultsCount": "{count} result | {count} results",
|
|
1153
|
+
"queriesTitle": "Performed searches"
|
|
1142
1154
|
}
|
|
1143
1155
|
},
|
|
1144
1156
|
"auth": {
|
|
@@ -3325,8 +3337,6 @@
|
|
|
3325
3337
|
"description": "Noun label heading the recorded reply to a finding (the answer that was given), not the verb."
|
|
3326
3338
|
},
|
|
3327
3339
|
"answerPlaceholder": "Answer this finding…",
|
|
3328
|
-
"refineAnswerPlaceholder": "Refine your answer…",
|
|
3329
|
-
"saveAnswer": "Save answer",
|
|
3330
3340
|
"dismissIrrelevant": "Dismiss as irrelevant",
|
|
3331
3341
|
"reopen": "Reopen",
|
|
3332
3342
|
"docHeading": "Clarified bug report",
|
package/i18n/locales/es.json
CHANGED
|
@@ -963,6 +963,8 @@
|
|
|
963
963
|
"companionCapDetail": "Haz una ronda más de reelaboración automática, avanza al siguiente paso aceptando la salida actual, o detén y restablece la tarea para que puedas editar las entradas y reenviarla.",
|
|
964
964
|
"infraAttempts": "Intentos de infraestructura",
|
|
965
965
|
"hideInfraAttempts": "Ocultar intentos de infraestructura",
|
|
966
|
+
"executionHistory": "Historial de ejecución",
|
|
967
|
+
"hideExecutionHistory": "Ocultar historial de ejecución",
|
|
966
968
|
"editingConclusions": "Editando las conclusiones",
|
|
967
969
|
"editConclusionsPlaceholder": "Edita las conclusiones del agente; tus cambios se guardan cuando apruebas…",
|
|
968
970
|
"noProseOutput": "Este agente no produjo salida en prosa.",
|
|
@@ -1096,6 +1098,16 @@
|
|
|
1096
1098
|
"systemPrompt": "Prompt del sistema",
|
|
1097
1099
|
"userPrompt": "Prompt del usuario",
|
|
1098
1100
|
"details": "Detalles"
|
|
1101
|
+
},
|
|
1102
|
+
"webSearch": "Búsqueda web",
|
|
1103
|
+
"loadingSearch": "Cargando búsquedas web…",
|
|
1104
|
+
"noSearch": "No se registraron búsquedas web para esta ejecución. Las consultas se capturan cuando se ejecuta la búsqueda web y el espacio de trabajo tiene activado 'Almacenar contexto completo del agente'.",
|
|
1105
|
+
"search": {
|
|
1106
|
+
"available": "Disponible",
|
|
1107
|
+
"unavailable": "No disponible",
|
|
1108
|
+
"provider": "Proveedor",
|
|
1109
|
+
"resultsCount": "{count} resultado | {count} resultados",
|
|
1110
|
+
"queriesTitle": "Búsquedas realizadas"
|
|
1099
1111
|
}
|
|
1100
1112
|
},
|
|
1101
1113
|
"auth": {
|
|
@@ -3218,8 +3230,6 @@
|
|
|
3218
3230
|
"reReviewingStage": "Volviendo a revisar el informe de error actualizado… Puedes cerrar esto, te avisaremos solo si se necesita más información.",
|
|
3219
3231
|
"answerLabel": "Respuesta",
|
|
3220
3232
|
"answerPlaceholder": "Responde a este hallazgo…",
|
|
3221
|
-
"refineAnswerPlaceholder": "Refina tu respuesta…",
|
|
3222
|
-
"saveAnswer": "Guardar respuesta",
|
|
3223
3233
|
"dismissIrrelevant": "Descartar por irrelevante",
|
|
3224
3234
|
"reopen": "Reabrir",
|
|
3225
3235
|
"docHeading": "Informe de error aclarado",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -963,6 +963,8 @@
|
|
|
963
963
|
"companionCapDetail": "Effectuer un tour de retravail automatique supplémentaire, passer à l'étape suivante en acceptant la sortie actuelle, ou arrêter et réinitialiser la tâche pour modifier les entrées et la resoumettre.",
|
|
964
964
|
"infraAttempts": "Tentatives d'infrastructure",
|
|
965
965
|
"hideInfraAttempts": "Masquer les tentatives d'infrastructure",
|
|
966
|
+
"executionHistory": "Historique d'exécution",
|
|
967
|
+
"hideExecutionHistory": "Masquer l'historique d'exécution",
|
|
966
968
|
"editingConclusions": "Modification des conclusions",
|
|
967
969
|
"editConclusionsPlaceholder": "Modifiez les conclusions de l'agent ; vos modifications sont enregistrées lorsque vous approuvez…",
|
|
968
970
|
"noProseOutput": "Cet agent n'a produit aucune sortie en texte libre.",
|
|
@@ -1096,6 +1098,16 @@
|
|
|
1096
1098
|
"systemPrompt": "Prompt système",
|
|
1097
1099
|
"userPrompt": "Prompt utilisateur",
|
|
1098
1100
|
"details": "Détails"
|
|
1101
|
+
},
|
|
1102
|
+
"webSearch": "Recherche web",
|
|
1103
|
+
"loadingSearch": "Chargement des recherches web…",
|
|
1104
|
+
"noSearch": "Aucune recherche web enregistrée pour cette exécution. Les requêtes sont capturées lorsque la recherche web s'exécute et que l'espace de travail a activé « Stocker le contexte complet de l'agent ».",
|
|
1105
|
+
"search": {
|
|
1106
|
+
"available": "Disponible",
|
|
1107
|
+
"unavailable": "Indisponible",
|
|
1108
|
+
"provider": "Fournisseur",
|
|
1109
|
+
"resultsCount": "{count} résultat | {count} résultats",
|
|
1110
|
+
"queriesTitle": "Recherches effectuées"
|
|
1099
1111
|
}
|
|
1100
1112
|
},
|
|
1101
1113
|
"auth": {
|
|
@@ -3218,8 +3230,6 @@
|
|
|
3218
3230
|
"reReviewingStage": "Nouvelle relecture du rapport de bogue mis à jour… Vous pouvez fermer ceci, nous vous avertirons seulement si plus d'informations sont nécessaires.",
|
|
3219
3231
|
"answerLabel": "Réponse",
|
|
3220
3232
|
"answerPlaceholder": "Répondez à ce constat…",
|
|
3221
|
-
"refineAnswerPlaceholder": "Affinez votre réponse…",
|
|
3222
|
-
"saveAnswer": "Enregistrer la réponse",
|
|
3223
3233
|
"dismissIrrelevant": "Écarter comme non pertinent",
|
|
3224
3234
|
"reopen": "Rouvrir",
|
|
3225
3235
|
"docHeading": "Rapport de bogue clarifié",
|
package/i18n/locales/he.json
CHANGED
|
@@ -963,6 +963,8 @@
|
|
|
963
963
|
"companionCapDetail": "בצע סבב עיבוד אוטומטי נוסף, המשך לשלב הבא תוך קבלת הפלט הנוכחי, או עצור ואפס את המשימה כדי לערוך את הקלטים ולשלוח מחדש.",
|
|
964
964
|
"infraAttempts": "ניסיונות תשתית",
|
|
965
965
|
"hideInfraAttempts": "הסתר ניסיונות תשתית",
|
|
966
|
+
"executionHistory": "היסטוריית הרצה",
|
|
967
|
+
"hideExecutionHistory": "הסתר היסטוריית הרצה",
|
|
966
968
|
"editingConclusions": "עריכת המסקנות",
|
|
967
969
|
"editConclusionsPlaceholder": "ערוך את מסקנות הסוכן; העריכות שלך נשמרות כשתאשר…",
|
|
968
970
|
"noProseOutput": "סוכן זה לא הפיק פלט טקסטואלי.",
|
|
@@ -1096,6 +1098,16 @@
|
|
|
1096
1098
|
"systemPrompt": "פרומפט מערכת",
|
|
1097
1099
|
"userPrompt": "פרומפט משתמש",
|
|
1098
1100
|
"details": "פרטים"
|
|
1101
|
+
},
|
|
1102
|
+
"webSearch": "חיפוש באינטרנט",
|
|
1103
|
+
"loadingSearch": "טוען חיפושי אינטרנט…",
|
|
1104
|
+
"noSearch": "לא נרשמו חיפושי אינטרנט עבור הרצה זו. שאילתות נלכדות כאשר חיפוש האינטרנט פועל וכאשר במרחב העבודה מופעל 'אחסון הקשר סוכן מלא'.",
|
|
1105
|
+
"search": {
|
|
1106
|
+
"available": "זמין",
|
|
1107
|
+
"unavailable": "לא זמין",
|
|
1108
|
+
"provider": "ספק",
|
|
1109
|
+
"resultsCount": "תוצאה {count} | {count} תוצאות",
|
|
1110
|
+
"queriesTitle": "חיפושים שבוצעו"
|
|
1099
1111
|
}
|
|
1100
1112
|
},
|
|
1101
1113
|
"auth": {
|
|
@@ -3229,8 +3241,6 @@
|
|
|
3229
3241
|
"reReviewingStage": "בודק מחדש את דוח הבאג המעודכן… תוכל לסגור את זה, נודיע לך רק אם נדרש קלט נוסף.",
|
|
3230
3242
|
"answerLabel": "תשובה",
|
|
3231
3243
|
"answerPlaceholder": "ענה על ממצא זה…",
|
|
3232
|
-
"refineAnswerPlaceholder": "חדד את התשובה שלך…",
|
|
3233
|
-
"saveAnswer": "שמור תשובה",
|
|
3234
3244
|
"dismissIrrelevant": "דחה כלא רלוונטי",
|
|
3235
3245
|
"reopen": "פתח מחדש",
|
|
3236
3246
|
"docHeading": "דוח באג מובהר",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -963,6 +963,8 @@
|
|
|
963
963
|
"companionCapDetail": "自動の再作業をもう1回実行するか、現在の出力を受け入れて次のステップに進むか、タスクを停止してリセットし、入力を編集して再送信してください。",
|
|
964
964
|
"infraAttempts": "インフラの試行",
|
|
965
965
|
"hideInfraAttempts": "インフラの試行を非表示",
|
|
966
|
+
"executionHistory": "実行履歴",
|
|
967
|
+
"hideExecutionHistory": "実行履歴を非表示",
|
|
966
968
|
"editingConclusions": "結論を編集中",
|
|
967
969
|
"editConclusionsPlaceholder": "エージェントの結論を編集してください。編集内容は承認時に保存されます…",
|
|
968
970
|
"noProseOutput": "このエージェントは文章出力を生成しませんでした。",
|
|
@@ -1096,6 +1098,16 @@
|
|
|
1096
1098
|
"systemPrompt": "システムプロンプト",
|
|
1097
1099
|
"userPrompt": "ユーザープロンプト",
|
|
1098
1100
|
"details": "詳細"
|
|
1101
|
+
},
|
|
1102
|
+
"webSearch": "ウェブ検索",
|
|
1103
|
+
"loadingSearch": "ウェブ検索を読み込み中…",
|
|
1104
|
+
"noSearch": "この実行のウェブ検索は記録されていません。クエリはウェブ検索の実行時、かつワークスペースで「エージェントコンテキスト全体を保存」が有効な場合に記録されます。",
|
|
1105
|
+
"search": {
|
|
1106
|
+
"available": "利用可能",
|
|
1107
|
+
"unavailable": "利用不可",
|
|
1108
|
+
"provider": "プロバイダー",
|
|
1109
|
+
"resultsCount": "{count} 件の結果 | {count} 件の結果",
|
|
1110
|
+
"queriesTitle": "実行した検索"
|
|
1099
1111
|
}
|
|
1100
1112
|
},
|
|
1101
1113
|
"auth": {
|
|
@@ -3230,8 +3242,6 @@
|
|
|
3230
3242
|
"reReviewingStage": "更新されたバグ報告を再レビュー中… これを閉じてもかまいません。追加の入力が必要な場合のみ通知します。",
|
|
3231
3243
|
"answerLabel": "回答",
|
|
3232
3244
|
"answerPlaceholder": "この指摘に回答…",
|
|
3233
|
-
"refineAnswerPlaceholder": "回答を改善…",
|
|
3234
|
-
"saveAnswer": "回答を保存",
|
|
3235
3245
|
"dismissIrrelevant": "無関係として却下",
|
|
3236
3246
|
"reopen": "再オープン",
|
|
3237
3247
|
"docHeading": "明確化されたバグ報告",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -963,6 +963,8 @@
|
|
|
963
963
|
"companionCapDetail": "Wykonaj jeszcze jedną automatyczną rundę przeróbki, przejdź do następnego kroku akceptując bieżący wynik, albo zatrzymaj i zresetuj zadanie, aby edytować dane wejściowe i przesłać ponownie.",
|
|
964
964
|
"infraAttempts": "Próby infrastrukturalne",
|
|
965
965
|
"hideInfraAttempts": "Ukryj próby infrastrukturalne",
|
|
966
|
+
"executionHistory": "Historia wykonania",
|
|
967
|
+
"hideExecutionHistory": "Ukryj historię wykonania",
|
|
966
968
|
"editingConclusions": "Edytowanie wniosków",
|
|
967
969
|
"editConclusionsPlaceholder": "Edytuj wnioski agenta; Twoje zmiany zostaną zapisane po zatwierdzeniu…",
|
|
968
970
|
"noProseOutput": "Ten agent nie wytworzył wyniku tekstowego.",
|
|
@@ -1096,6 +1098,16 @@
|
|
|
1096
1098
|
"systemPrompt": "Prompt systemowy",
|
|
1097
1099
|
"userPrompt": "Prompt użytkownika",
|
|
1098
1100
|
"details": "Szczegóły"
|
|
1101
|
+
},
|
|
1102
|
+
"webSearch": "Wyszukiwanie w sieci",
|
|
1103
|
+
"loadingSearch": "Ładowanie wyszukań w sieci…",
|
|
1104
|
+
"noSearch": "Nie zarejestrowano wyszukań w sieci dla tego uruchomienia. Zapytania są rejestrowane, gdy wyszukiwanie w sieci jest uruchomione, a przestrzeń robocza ma włączone „Przechowuj pełny kontekst agenta”.",
|
|
1105
|
+
"search": {
|
|
1106
|
+
"available": "Dostępne",
|
|
1107
|
+
"unavailable": "Niedostępne",
|
|
1108
|
+
"provider": "Dostawca",
|
|
1109
|
+
"resultsCount": "{count} wynik | {count} wyniki | {count} wyników",
|
|
1110
|
+
"queriesTitle": "Wykonane wyszukiwania"
|
|
1099
1111
|
}
|
|
1100
1112
|
},
|
|
1101
1113
|
"auth": {
|
|
@@ -3218,8 +3230,6 @@
|
|
|
3218
3230
|
"reReviewingStage": "Ponowna recenzja zaktualizowanego zgłoszenia błędu… Możesz to zamknąć, powiadomimy Cię tylko, jeśli potrzebne będą dodatkowe informacje.",
|
|
3219
3231
|
"answerLabel": "Odpowiedź",
|
|
3220
3232
|
"answerPlaceholder": "Odpowiedz na tę uwagę…",
|
|
3221
|
-
"refineAnswerPlaceholder": "Dopracuj swoją odpowiedź…",
|
|
3222
|
-
"saveAnswer": "Zapisz odpowiedź",
|
|
3223
3233
|
"dismissIrrelevant": "Odrzuć jako nieistotne",
|
|
3224
3234
|
"reopen": "Otwórz ponownie",
|
|
3225
3235
|
"docHeading": "Doprecyzowane zgłoszenie błędu",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -963,6 +963,8 @@
|
|
|
963
963
|
"companionCapDetail": "Bir otomatik yeniden çalışma turu daha yapın, mevcut çıktıyı kabul ederek sonraki adıma geçin ya da durup görevi sıfırlayarak girdileri düzenleyip yeniden gönderin.",
|
|
964
964
|
"infraAttempts": "Altyapı denemeleri",
|
|
965
965
|
"hideInfraAttempts": "Altyapı denemelerini gizle",
|
|
966
|
+
"executionHistory": "Yürütme geçmişi",
|
|
967
|
+
"hideExecutionHistory": "Yürütme geçmişini gizle",
|
|
966
968
|
"editingConclusions": "Sonuçlar düzenleniyor",
|
|
967
969
|
"editConclusionsPlaceholder": "Aracının sonuçlarını düzenleyin; düzenlemeleriniz onayladığınızda kaydedilir…",
|
|
968
970
|
"noProseOutput": "Bu aracı herhangi bir metin çıktısı üretmedi.",
|
|
@@ -1096,6 +1098,16 @@
|
|
|
1096
1098
|
"systemPrompt": "Sistem istemi",
|
|
1097
1099
|
"userPrompt": "Kullanıcı istemi",
|
|
1098
1100
|
"details": "Ayrıntılar"
|
|
1101
|
+
},
|
|
1102
|
+
"webSearch": "Web araması",
|
|
1103
|
+
"loadingSearch": "Web aramaları yükleniyor…",
|
|
1104
|
+
"noSearch": "Bu çalıştırma için web araması kaydedilmedi. Sorgular, web araması çalıştığında ve çalışma alanında 'Tam aracı bağlamını depola' etkinleştirildiğinde yakalanır.",
|
|
1105
|
+
"search": {
|
|
1106
|
+
"available": "Kullanılabilir",
|
|
1107
|
+
"unavailable": "Kullanılamıyor",
|
|
1108
|
+
"provider": "Sağlayıcı",
|
|
1109
|
+
"resultsCount": "{count} sonuç | {count} sonuç",
|
|
1110
|
+
"queriesTitle": "Yapılan aramalar"
|
|
1099
1111
|
}
|
|
1100
1112
|
},
|
|
1101
1113
|
"auth": {
|
|
@@ -3230,8 +3242,6 @@
|
|
|
3230
3242
|
"reReviewingStage": "Güncellenen hata raporu yeniden inceleniyor… Bunu kapatabilirsiniz, yalnızca daha fazla girdi gerekirse sizi bilgilendireceğiz.",
|
|
3231
3243
|
"answerLabel": "Yanıt",
|
|
3232
3244
|
"answerPlaceholder": "Bu bulguyu yanıtlayın…",
|
|
3233
|
-
"refineAnswerPlaceholder": "Yanıtınızı iyileştirin…",
|
|
3234
|
-
"saveAnswer": "Yanıtı kaydet",
|
|
3235
3245
|
"dismissIrrelevant": "İlgisiz olarak göz ardı et",
|
|
3236
3246
|
"reopen": "Yeniden aç",
|
|
3237
3247
|
"docHeading": "Netleştirilmiş hata raporu",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -963,6 +963,8 @@
|
|
|
963
963
|
"companionCapDetail": "Виконайте ще один автоматичний раунд переробки, перейдіть до наступного кроку, прийнявши поточний вивід, або зупиніться та скиньте завдання, щоб відредагувати вхідні дані й надіслати повторно.",
|
|
964
964
|
"infraAttempts": "Спроби інфраструктури",
|
|
965
965
|
"hideInfraAttempts": "Приховати спроби інфраструктури",
|
|
966
|
+
"executionHistory": "Історія виконання",
|
|
967
|
+
"hideExecutionHistory": "Приховати історію виконання",
|
|
966
968
|
"editingConclusions": "Редагування висновків",
|
|
967
969
|
"editConclusionsPlaceholder": "Відредагуйте висновки агента; ваші зміни зберігаються після затвердження…",
|
|
968
970
|
"noProseOutput": "Цей агент не створив текстового виводу.",
|
|
@@ -1096,6 +1098,16 @@
|
|
|
1096
1098
|
"systemPrompt": "Системний запит",
|
|
1097
1099
|
"userPrompt": "Запит користувача",
|
|
1098
1100
|
"details": "Деталі"
|
|
1101
|
+
},
|
|
1102
|
+
"webSearch": "Веб-пошук",
|
|
1103
|
+
"loadingSearch": "Завантаження веб-пошуків…",
|
|
1104
|
+
"noSearch": "Для цього запуску не зафіксовано веб-пошуків. Запити фіксуються, коли виконується веб-пошук і в робочому просторі увімкнено «Зберігати повний контекст агента».",
|
|
1105
|
+
"search": {
|
|
1106
|
+
"available": "Доступно",
|
|
1107
|
+
"unavailable": "Недоступно",
|
|
1108
|
+
"provider": "Провайдер",
|
|
1109
|
+
"resultsCount": "{count} результат | {count} результати | {count} результатів",
|
|
1110
|
+
"queriesTitle": "Виконані пошуки"
|
|
1099
1111
|
}
|
|
1100
1112
|
},
|
|
1101
1113
|
"auth": {
|
|
@@ -3218,8 +3230,6 @@
|
|
|
3218
3230
|
"reReviewingStage": "Повторне рецензування оновленого звіту про помилку… Ви можете це закрити, ми сповістимо вас лише якщо знадобиться більше інформації.",
|
|
3219
3231
|
"answerLabel": "Відповідь",
|
|
3220
3232
|
"answerPlaceholder": "Дайте відповідь на це зауваження…",
|
|
3221
|
-
"refineAnswerPlaceholder": "Уточніть свою відповідь…",
|
|
3222
|
-
"saveAnswer": "Зберегти відповідь",
|
|
3223
3233
|
"dismissIrrelevant": "Відхилити як недоречне",
|
|
3224
3234
|
"reopen": "Відкрити знову",
|
|
3225
3235
|
"docHeading": "Уточнений звіт про помилку",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.96.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.105.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|