@cat-factory/app 0.178.2 → 0.180.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/initiative/InitiativePlanningWindow.vue +35 -6
- package/app/components/panels/ObservabilityPanel.vue +105 -1
- package/app/components/tasks/BugHuntModal.vue +91 -9
- package/app/components/tasks/ContextIssuePicker.vue +1 -1
- package/app/types/execution.ts +1 -0
- package/app/utils/initiative.spec.ts +81 -2
- package/app/utils/initiative.ts +32 -0
- package/app/utils/observability.spec.ts +60 -1
- package/app/utils/observability.ts +56 -1
- package/app/{components/tasks/ContextIssuePicker.logic.spec.ts → utils/taskSources.spec.ts} +16 -5
- package/app/{components/tasks/ContextIssuePicker.logic.ts → utils/taskSources.ts} +13 -11
- package/i18n/locales/de.json +15 -0
- package/i18n/locales/en.json +15 -0
- package/i18n/locales/es.json +15 -0
- package/i18n/locales/fr.json +15 -0
- package/i18n/locales/he.json +15 -0
- package/i18n/locales/it.json +15 -0
- package/i18n/locales/ja.json +15 -0
- package/i18n/locales/pl.json +15 -0
- package/i18n/locales/tr.json +15 -0
- package/i18n/locales/uk.json +15 -0
- package/package.json +2 -2
|
@@ -10,16 +10,25 @@
|
|
|
10
10
|
// (`ui.openInitiativePlanning`) or as the interviewer step's result view. Live `initiative`
|
|
11
11
|
// stream events patch the store, so an open window follows the interview as it progresses.
|
|
12
12
|
//
|
|
13
|
+
// An interview runs over MULTIPLE ROUNDS and the entity keeps the settled ones, so the list is a
|
|
14
|
+
// mix of what the human still owes an answer and what they already dealt with. It renders pending
|
|
15
|
+
// first (`orderInterviewQuestions`) — see the `order` snapshot below for why that is recomputed per
|
|
16
|
+
// round rather than live.
|
|
17
|
+
//
|
|
13
18
|
// CONTINUE/PROCEED ARE ASYNC. They only record the intent on the parked step and wake the durable
|
|
14
19
|
// driver; the interviewer LLM then runs for as long as it takes, and the response carries the
|
|
15
20
|
// PRE-resume entity. So the window must not key its body on the entity alone — that renders
|
|
16
21
|
// identically before and after the click, which reads as the button having done nothing. The
|
|
17
22
|
// phase below folds the planning RUN's status in, so the wait is visible and a failed pass says
|
|
18
23
|
// so instead of leaving the human staring at questions they already submitted.
|
|
19
|
-
import { computed, reactive, watch } from 'vue'
|
|
24
|
+
import { computed, reactive, ref, watch } from 'vue'
|
|
20
25
|
import ClarificationItem from '~/components/common/ClarificationItem.vue'
|
|
21
26
|
import InterviewGateNotice from '~/components/common/InterviewGateNotice.vue'
|
|
22
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
INITIATIVE_STATUS_LABEL_KEYS,
|
|
29
|
+
isPendingQuestion,
|
|
30
|
+
orderInterviewQuestions,
|
|
31
|
+
} from '~/utils/initiative'
|
|
23
32
|
import { interviewGatePhase } from '~/utils/interviewGate'
|
|
24
33
|
import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
|
|
25
34
|
|
|
@@ -41,9 +50,7 @@ const questions = computed(() =>
|
|
|
41
50
|
(initiative.value?.qa ?? []).map((q, i) => ({ ...q, key: q.id ?? `q-${i}` })),
|
|
42
51
|
)
|
|
43
52
|
/** Questions still needing an answer: not dismissed, and not yet answered (mirrors backend). */
|
|
44
|
-
const pending = computed(() =>
|
|
45
|
-
questions.value.filter((q) => q.status !== 'dismissed' && !(q.answer ?? '').trim()),
|
|
46
|
-
)
|
|
53
|
+
const pending = computed(() => questions.value.filter(isPendingQuestion))
|
|
47
54
|
|
|
48
55
|
// Per-question answer drafts, seeded from the entity and refreshed as new rounds arrive
|
|
49
56
|
// without clobbering an answer the human is mid-edit on.
|
|
@@ -58,6 +65,28 @@ watch(
|
|
|
58
65
|
{ immediate: true },
|
|
59
66
|
)
|
|
60
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Render order (pending first — see `orderInterviewQuestions`), re-snapshotted ONLY when the
|
|
70
|
+
* question SET changes, i.e. when a round lands. Deriving it live from the answers instead would
|
|
71
|
+
* yank a question out from under the human the moment they blurred its textarea and shuffle
|
|
72
|
+
* everything below it up, while they are reading down the list. Re-snapshotting per ROUND rather
|
|
73
|
+
* than per question is what keeps a window left open across rounds correct: a question answered in
|
|
74
|
+
* round one has to sink below round two's new ones, which a rank frozen at first sight never would.
|
|
75
|
+
*/
|
|
76
|
+
const order = ref<string[]>([])
|
|
77
|
+
watch(
|
|
78
|
+
() => questions.value.map((q) => q.key).join('|'),
|
|
79
|
+
() => {
|
|
80
|
+
order.value = orderInterviewQuestions(questions.value).map((q) => q.key)
|
|
81
|
+
},
|
|
82
|
+
{ immediate: true },
|
|
83
|
+
)
|
|
84
|
+
const orderedQuestions = computed(() => {
|
|
85
|
+
const rank = new Map(order.value.map((key, i) => [key, i]))
|
|
86
|
+
// A question the snapshot has not seen is by definition new, so it is pending and sorts first.
|
|
87
|
+
return [...questions.value].sort((a, b) => (rank.get(a.key) ?? -1) - (rank.get(b.key) ?? -1))
|
|
88
|
+
})
|
|
89
|
+
|
|
61
90
|
const resuming = computed(() => initiatives.resuming)
|
|
62
91
|
|
|
63
92
|
/**
|
|
@@ -221,7 +250,7 @@ async function onDiscard() {
|
|
|
221
250
|
<!-- Interview questions — the shared clarification surface (answer / not-relevant /
|
|
222
251
|
recommend), reused with the requirements-review window. -->
|
|
223
252
|
<ul v-else class="space-y-4">
|
|
224
|
-
<li v-for="q in
|
|
253
|
+
<li v-for="q in orderedQuestions" :key="q.key" data-testid="initiative-planning-question">
|
|
225
254
|
<ClarificationItem
|
|
226
255
|
v-model:answer="drafts[q.key]"
|
|
227
256
|
:prompt="q.question"
|
|
@@ -8,7 +8,13 @@ import type {
|
|
|
8
8
|
WebSearchProvider,
|
|
9
9
|
} from '~/types/execution'
|
|
10
10
|
import { agentKindMeta } from '~/utils/catalog'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
foldRunPhaseMetrics,
|
|
13
|
+
formatMs,
|
|
14
|
+
formatTokens,
|
|
15
|
+
pct,
|
|
16
|
+
totalInputTokens,
|
|
17
|
+
} from '~/utils/observability'
|
|
12
18
|
|
|
13
19
|
// Drill-down overlay for a run's LLM activity. Opened via
|
|
14
20
|
// `ui.openObservability(instanceId)` from a step surface; loads the full per-call
|
|
@@ -151,6 +157,26 @@ const totals = computed(() => {
|
|
|
151
157
|
function sum(items: LlmCallMetric[], pick: (m: LlmCallMetric) => number): number {
|
|
152
158
|
return items.reduce((acc, m) => acc + pick(m), 0)
|
|
153
159
|
}
|
|
160
|
+
|
|
161
|
+
// Where the run's tokens went, by PHASE. Unlike the totals above (derived from the capped call
|
|
162
|
+
// list), this reads the engine's SQL rollup off the steps, so it stays honest on a long run.
|
|
163
|
+
const phaseRows = computed(() => foldRunPhaseMetrics(instance.value?.steps ?? []))
|
|
164
|
+
const phaseCarryTotal = computed(() =>
|
|
165
|
+
phaseRows.value.reduce((acc, p) => acc + p.carryCostTokens, 0),
|
|
166
|
+
)
|
|
167
|
+
/** Share of the run's carry cost a phase accounts for (0..100), or null when nothing carried. */
|
|
168
|
+
function carryShare(carryCostTokens: number): number | null {
|
|
169
|
+
return phaseCarryTotal.value > 0 ? pct(carryCostTokens / phaseCarryTotal.value) : null
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The phase label as shown. The vocabulary belongs to the HARNESS — it is whatever its handlers
|
|
173
|
+
* pass to `onPhase`, so there is deliberately no closed union to translate against; a newer
|
|
174
|
+
* image's phase must render verbatim rather than disappear. The one label the platform owns is
|
|
175
|
+
* the empty string, which means "nothing could attribute this call" and needs saying in words.
|
|
176
|
+
*/
|
|
177
|
+
function phaseLabel(phase: string): string {
|
|
178
|
+
return phase || t('observability.phase.unattributed')
|
|
179
|
+
}
|
|
154
180
|
function isWarning(finishReason: string | null): boolean {
|
|
155
181
|
return finishReason === 'length' || finishReason === 'content_filter'
|
|
156
182
|
}
|
|
@@ -384,6 +410,84 @@ function exportJson() {
|
|
|
384
410
|
</div>
|
|
385
411
|
</section>
|
|
386
412
|
|
|
413
|
+
<!-- where the run's tokens went, by phase (the engine's SQL rollup, not the
|
|
414
|
+
capped call list) -->
|
|
415
|
+
<section
|
|
416
|
+
v-if="phaseRows.length"
|
|
417
|
+
class="rounded-xl border border-slate-800 bg-slate-900/50 p-4"
|
|
418
|
+
>
|
|
419
|
+
<div class="flex items-baseline gap-2">
|
|
420
|
+
<h2 class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
421
|
+
{{ t('observability.phase.title') }}
|
|
422
|
+
</h2>
|
|
423
|
+
<span class="text-[11px] text-slate-600">
|
|
424
|
+
{{ t('observability.phase.subtitle') }}
|
|
425
|
+
</span>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="mt-3 overflow-x-auto">
|
|
428
|
+
<table class="w-full min-w-[32rem] text-[12px]">
|
|
429
|
+
<thead>
|
|
430
|
+
<tr class="text-[11px] uppercase tracking-wide text-slate-500">
|
|
431
|
+
<th class="py-1 pe-3 text-start font-normal">
|
|
432
|
+
{{ t('observability.phase.columns.phase') }}
|
|
433
|
+
</th>
|
|
434
|
+
<th class="py-1 px-3 text-end font-normal">
|
|
435
|
+
{{ t('observability.phase.columns.turns') }}
|
|
436
|
+
</th>
|
|
437
|
+
<th class="py-1 px-3 text-end font-normal">
|
|
438
|
+
{{ t('observability.phase.columns.tokensInOut') }}
|
|
439
|
+
</th>
|
|
440
|
+
<!-- The sort key, MARKED as one. Rows lead with carry cost rather than
|
|
441
|
+
with tokens, and the two orders genuinely differ: a phase that runs
|
|
442
|
+
late carries almost nothing however much it spent (nothing after it
|
|
443
|
+
re-sends its context). Leaving that implicit invites reading row 1
|
|
444
|
+
as "the phase that burned the most", which is the neighbouring
|
|
445
|
+
column. -->
|
|
446
|
+
<th aria-sort="descending" class="py-1 ps-3 text-end font-normal">
|
|
447
|
+
<span :title="t('observability.phase.carryCostHint')">
|
|
448
|
+
{{ t('observability.phase.columns.carryCost') }} ↓
|
|
449
|
+
</span>
|
|
450
|
+
</th>
|
|
451
|
+
</tr>
|
|
452
|
+
</thead>
|
|
453
|
+
<tbody>
|
|
454
|
+
<tr v-for="p in phaseRows" :key="p.phase" class="border-t border-slate-800/70">
|
|
455
|
+
<td class="py-1.5 pe-3 text-slate-200">
|
|
456
|
+
<span :class="p.phase ? '' : 'text-slate-400 italic'">
|
|
457
|
+
{{ phaseLabel(p.phase) }}
|
|
458
|
+
</span>
|
|
459
|
+
<span v-if="!p.phase" class="ms-1.5 text-[11px] text-slate-600">
|
|
460
|
+
{{ t('observability.phase.unattributedHint') }}
|
|
461
|
+
</span>
|
|
462
|
+
<UBadge
|
|
463
|
+
v-if="p.errors"
|
|
464
|
+
color="error"
|
|
465
|
+
variant="subtle"
|
|
466
|
+
size="sm"
|
|
467
|
+
class="ms-2"
|
|
468
|
+
>
|
|
469
|
+
{{ t('observability.metricsBar.errors', { count: p.errors }, p.errors) }}
|
|
470
|
+
</UBadge>
|
|
471
|
+
</td>
|
|
472
|
+
<td class="py-1.5 px-3 text-end tabular-nums text-slate-300">
|
|
473
|
+
{{ p.calls }}
|
|
474
|
+
</td>
|
|
475
|
+
<td class="py-1.5 px-3 text-end tabular-nums text-slate-300">
|
|
476
|
+
{{ formatTokens(totalInputTokens(p)) }}↑
|
|
477
|
+
{{ formatTokens(p.completionTokens) }}↓
|
|
478
|
+
</td>
|
|
479
|
+
<td class="py-1.5 ps-3 text-end tabular-nums text-slate-300">
|
|
480
|
+
{{ formatTokens(p.carryCostTokens) }}
|
|
481
|
+
<span v-if="carryShare(p.carryCostTokens) !== null" class="text-slate-600">
|
|
482
|
+
· {{ carryShare(p.carryCostTokens) }}%
|
|
483
|
+
</span>
|
|
484
|
+
</td>
|
|
485
|
+
</tr>
|
|
486
|
+
</tbody>
|
|
487
|
+
</table>
|
|
488
|
+
</div>
|
|
489
|
+
</section>
|
|
490
|
+
|
|
387
491
|
<!-- states -->
|
|
388
492
|
<p
|
|
389
493
|
v-if="loading && !calls.length"
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
// unavailable or failed still shows its candidates (flagged as unassessed, since the scan is
|
|
11
11
|
// useful on its own), and a scan that hit its cap says so — a silently shortened list reads
|
|
12
12
|
// exactly like an exhaustive one.
|
|
13
|
+
//
|
|
14
|
+
// The tracker selector doubles as the "add a tracker" affordance (the same two-tier menu
|
|
15
|
+
// `<ContextIssuePicker>` renders, off the shared `buildSourceChoices`): a hunt is a common
|
|
16
|
+
// place to find out the tracker holding the bugs isn't connected here yet, and the answer
|
|
17
|
+
// has to be a route to that tracker's own connect screen rather than "go find the
|
|
18
|
+
// Integrations hub". The connect modal opens OVER the hunt, so nothing typed here is lost.
|
|
19
|
+
import type { DropdownMenuItem } from '@nuxt/ui'
|
|
13
20
|
import type { TaskSourceReadReason } from '@cat-factory/contracts'
|
|
14
21
|
import type {
|
|
15
22
|
BugHuntAnalysisStatus,
|
|
@@ -17,6 +24,7 @@ import type {
|
|
|
17
24
|
BugHuntConfidence,
|
|
18
25
|
TaskSourceKind,
|
|
19
26
|
} from '~/types/domain'
|
|
27
|
+
import { type SourceChoice, buildSourceChoices, reconcileSource } from '~/utils/taskSources'
|
|
20
28
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
21
29
|
|
|
22
30
|
const { t, d, n } = useI18n()
|
|
@@ -54,8 +62,64 @@ const containerItems = computed(() =>
|
|
|
54
62
|
})),
|
|
55
63
|
)
|
|
56
64
|
|
|
57
|
-
const
|
|
58
|
-
|
|
65
|
+
const descriptor = computed(() => (source.value ? tasks.descriptorFor(source.value) : undefined))
|
|
66
|
+
|
|
67
|
+
// Two-tier tracker menu: pick one the workspace already offers, or add one it doesn't.
|
|
68
|
+
const sourceChoices = computed(() => buildSourceChoices(tasks.sources, source.value))
|
|
69
|
+
const sourceMenu = computed<DropdownMenuItem[][]>(() =>
|
|
70
|
+
sourceChoices.value.map((group) =>
|
|
71
|
+
group.map((choice) =>
|
|
72
|
+
choice.action === 'select'
|
|
73
|
+
? {
|
|
74
|
+
label: choice.label,
|
|
75
|
+
icon: choice.icon,
|
|
76
|
+
trailingIcon: choice.active ? 'i-lucide-check' : undefined,
|
|
77
|
+
onSelect: () => {
|
|
78
|
+
source.value = choice.source
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
: {
|
|
82
|
+
label: addLabel(choice),
|
|
83
|
+
icon: 'i-lucide-plug',
|
|
84
|
+
onSelect: () => addSource(choice.source),
|
|
85
|
+
},
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
/** The trackers that can be added — the empty state's buttons, where none is offered yet. */
|
|
91
|
+
const addableSources = computed(() =>
|
|
92
|
+
sourceChoices.value.flat().filter((c) => c.action !== 'select'),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Wording for an addable tracker: `enable` is connected but toggled off for this workspace,
|
|
97
|
+
* so the user is never told to "connect" something they already connected.
|
|
98
|
+
*/
|
|
99
|
+
function addLabel(choice: SourceChoice): string {
|
|
100
|
+
return choice.action === 'enable'
|
|
101
|
+
? t('bugHunt.enableSource', { label: choice.label })
|
|
102
|
+
: t('bugHunt.connectSource', { label: choice.label })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The tracker the user left to add, so it becomes the selection the moment it turns up
|
|
107
|
+
* offered (the connect modal re-probes on success and this hunt stays open underneath it).
|
|
108
|
+
* Also the reconcile trigger for a source that STOPS being offered — disconnected, or
|
|
109
|
+
* toggled off in settings while the hunt sat open.
|
|
110
|
+
*/
|
|
111
|
+
const awaitingConnect = ref<TaskSourceKind | null>(null)
|
|
112
|
+
function addSource(s: TaskSourceKind) {
|
|
113
|
+
awaitingConnect.value = s
|
|
114
|
+
ui.openTaskConnect(s)
|
|
115
|
+
}
|
|
116
|
+
watch(
|
|
117
|
+
() => tasks.offeredSources.map((s) => s.source),
|
|
118
|
+
(offered) => {
|
|
119
|
+
const next = reconcileSource(offered, source.value, awaitingConnect.value)
|
|
120
|
+
if (next && next === awaitingConnect.value) awaitingConnect.value = null
|
|
121
|
+
if (next !== source.value) source.value = next
|
|
122
|
+
},
|
|
59
123
|
)
|
|
60
124
|
|
|
61
125
|
const boardItems = computed(() =>
|
|
@@ -100,6 +164,7 @@ watch(open, (isOpen) => {
|
|
|
100
164
|
boardId.value = ''
|
|
101
165
|
issueType.value = ''
|
|
102
166
|
labels.value = ''
|
|
167
|
+
awaitingConnect.value = null
|
|
103
168
|
source.value = ui.bugHunt?.source ?? tasks.offeredSources[0]?.source ?? undefined
|
|
104
169
|
containerId.value = ui.bugHunt?.containerId ?? containerItems.value[0]?.value
|
|
105
170
|
if (source.value) hunt.loadBoards(source.value)
|
|
@@ -193,16 +258,16 @@ const STATUS_KEYS: Record<BugHuntAnalysisStatus, string> = {
|
|
|
193
258
|
<div v-if="!tasks.anyOffered" class="space-y-3 text-center">
|
|
194
259
|
<UIcon name="i-lucide-plug" class="mx-auto h-8 w-8 text-slate-500" />
|
|
195
260
|
<p class="text-sm text-slate-400">{{ t('bugHunt.connectFirst') }}</p>
|
|
196
|
-
<div class="flex justify-center gap-2">
|
|
261
|
+
<div class="flex flex-wrap justify-center gap-2">
|
|
197
262
|
<UButton
|
|
198
|
-
v-for="
|
|
199
|
-
:key="
|
|
263
|
+
v-for="choice in addableSources"
|
|
264
|
+
:key="choice.source"
|
|
200
265
|
color="primary"
|
|
201
266
|
variant="soft"
|
|
202
|
-
:icon="
|
|
203
|
-
@click="
|
|
267
|
+
:icon="choice.icon"
|
|
268
|
+
@click="addSource(choice.source)"
|
|
204
269
|
>
|
|
205
|
-
{{
|
|
270
|
+
{{ addLabel(choice) }}
|
|
206
271
|
</UButton>
|
|
207
272
|
</div>
|
|
208
273
|
</div>
|
|
@@ -217,7 +282,24 @@ const STATUS_KEYS: Record<BugHuntAnalysisStatus, string> = {
|
|
|
217
282
|
|
|
218
283
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
219
284
|
<UFormField :label="t('bugHunt.tracker')">
|
|
220
|
-
|
|
285
|
+
<!-- The selector is also the way to ADD a tracker: each entry in the second group
|
|
286
|
+
opens that tracker's own connect screen over this modal, so the hunt (and
|
|
287
|
+
anything typed into it) is still here when the user comes back. -->
|
|
288
|
+
<UDropdownMenu
|
|
289
|
+
:items="sourceMenu"
|
|
290
|
+
:content="{ side: 'bottom', align: 'start' }"
|
|
291
|
+
class="w-full"
|
|
292
|
+
>
|
|
293
|
+
<UButton
|
|
294
|
+
color="neutral"
|
|
295
|
+
variant="soft"
|
|
296
|
+
:icon="descriptor?.icon"
|
|
297
|
+
trailing-icon="i-lucide-chevron-down"
|
|
298
|
+
class="w-full justify-between"
|
|
299
|
+
>
|
|
300
|
+
<span class="truncate">{{ descriptor?.label ?? t('bugHunt.pickTracker') }}</span>
|
|
301
|
+
</UButton>
|
|
302
|
+
</UDropdownMenu>
|
|
221
303
|
</UFormField>
|
|
222
304
|
|
|
223
305
|
<UFormField :label="t('bugHunt.board')">
|
|
@@ -21,7 +21,7 @@ import type { TaskSourceReadReason } from '@cat-factory/contracts'
|
|
|
21
21
|
import type { SourceTask, TaskSearchResult, TaskSourceKind } from '~/types/domain'
|
|
22
22
|
import { apiErrorReason } from '~/composables/api/errors'
|
|
23
23
|
import EmptyState from '~/components/common/EmptyState.vue'
|
|
24
|
-
import { buildSourceChoices, reconcileSource } from '~/
|
|
24
|
+
import { buildSourceChoices, reconcileSource } from '~/utils/taskSources'
|
|
25
25
|
|
|
26
26
|
const props = defineProps<{
|
|
27
27
|
/** contextKeys already staged by the caller, so they're filtered out / not re-offered. */
|
package/app/types/execution.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { INITIATIVE_ITEM_TERMINAL_STATUSES } from '@cat-factory/contracts'
|
|
2
2
|
import { describe, it, expect } from 'vitest'
|
|
3
|
-
import type { InitiativeItem, InitiativePhase } from '~/types/domain'
|
|
4
|
-
import { pendingCheckpointPhase } from './initiative'
|
|
3
|
+
import type { InitiativeItem, InitiativePhase, InitiativeQa } from '~/types/domain'
|
|
4
|
+
import { isPendingQuestion, orderInterviewQuestions, pendingCheckpointPhase } from './initiative'
|
|
5
5
|
|
|
6
6
|
// `pendingCheckpointPhase` mirrors the backend `pendingCheckpoint` (orchestration
|
|
7
7
|
// `initiative.logic.ts`); these pin the same ordering/edge cases the loop pauses on, so the
|
|
@@ -71,3 +71,82 @@ describe('pendingCheckpointPhase', () => {
|
|
|
71
71
|
expect(pendingCheckpointPhase(phases, [item('a', 'p1', status)])?.id).toBe('p1')
|
|
72
72
|
})
|
|
73
73
|
})
|
|
74
|
+
|
|
75
|
+
// `isPendingQuestion` mirrors the backend rule of the same name (orchestration
|
|
76
|
+
// `initiative.logic.ts`); `orderInterviewQuestions` is what the planning window renders by, so a
|
|
77
|
+
// multi-round interview puts what the human still owes an answer above what they already settled.
|
|
78
|
+
|
|
79
|
+
const qa = (over: Partial<InitiativeQa> & { id: string }): InitiativeQa => ({
|
|
80
|
+
question: over.id,
|
|
81
|
+
answer: '',
|
|
82
|
+
status: 'open',
|
|
83
|
+
...over,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('isPendingQuestion', () => {
|
|
87
|
+
it('is pending while unanswered and not dismissed', () => {
|
|
88
|
+
expect(isPendingQuestion(qa({ id: 'a' }))).toBe(true)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('is settled once answered', () => {
|
|
92
|
+
expect(isPendingQuestion(qa({ id: 'a', answer: 'yes' }))).toBe(false)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('treats a whitespace-only answer as unanswered', () => {
|
|
96
|
+
expect(isPendingQuestion(qa({ id: 'a', answer: ' \n' }))).toBe(true)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('is settled once dismissed, answered or not', () => {
|
|
100
|
+
expect(isPendingQuestion(qa({ id: 'a', status: 'dismissed' }))).toBe(false)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('treats an absent answer/status (a hand-authored exchange) as pending', () => {
|
|
104
|
+
expect(isPendingQuestion({})).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('orderInterviewQuestions', () => {
|
|
109
|
+
const ids = (list: InitiativeQa[]) => orderInterviewQuestions(list).map((q) => q.id)
|
|
110
|
+
|
|
111
|
+
it('floats a later round of unanswered questions above the settled digest', () => {
|
|
112
|
+
// The shape the backend's `[...retainedQa, ...pending]` append produces on round two.
|
|
113
|
+
const list = [
|
|
114
|
+
qa({ id: 'r1-answered', answer: 'yes' }),
|
|
115
|
+
qa({ id: 'r1-dismissed', status: 'dismissed' }),
|
|
116
|
+
qa({ id: 'r2-a' }),
|
|
117
|
+
qa({ id: 'r2-b' }),
|
|
118
|
+
]
|
|
119
|
+
expect(ids(list)).toEqual(['r2-a', 'r2-b', 'r1-answered', 'r1-dismissed'])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('keeps chronological order within each group', () => {
|
|
123
|
+
const list = [
|
|
124
|
+
qa({ id: 'p1' }),
|
|
125
|
+
qa({ id: 's1', answer: 'yes' }),
|
|
126
|
+
qa({ id: 'p2' }),
|
|
127
|
+
qa({ id: 's2', status: 'dismissed' }),
|
|
128
|
+
qa({ id: 'p3' }),
|
|
129
|
+
]
|
|
130
|
+
expect(ids(list)).toEqual(['p1', 'p2', 'p3', 's1', 's2'])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('leaves a first round (all pending) exactly as the interviewer asked it', () => {
|
|
134
|
+
const list = [qa({ id: 'a' }), qa({ id: 'b' }), qa({ id: 'c' })]
|
|
135
|
+
expect(ids(list)).toEqual(['a', 'b', 'c'])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('leaves a fully settled interview in its digest order', () => {
|
|
139
|
+
const list = [qa({ id: 'a', answer: 'x' }), qa({ id: 'b', status: 'dismissed' })]
|
|
140
|
+
expect(ids(list)).toEqual(['a', 'b'])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('does not mutate the stored order (the interviewer prompt + tracker digest read it)', () => {
|
|
144
|
+
const list = [qa({ id: 'answered', answer: 'x' }), qa({ id: 'pending' })]
|
|
145
|
+
orderInterviewQuestions(list)
|
|
146
|
+
expect(list.map((q) => q.id)).toEqual(['answered', 'pending'])
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('handles an empty interview', () => {
|
|
150
|
+
expect(orderInterviewQuestions([])).toEqual([])
|
|
151
|
+
})
|
|
152
|
+
})
|
package/app/utils/initiative.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
InitiativePhase,
|
|
7
7
|
InitiativePresetDescriptor,
|
|
8
8
|
InitiativePresetInputs,
|
|
9
|
+
InitiativeQa,
|
|
9
10
|
InitiativeStatus,
|
|
10
11
|
} from '~/types/domain'
|
|
11
12
|
|
|
@@ -111,6 +112,37 @@ export function initiativeProgress(
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Whether a planning-interview question still needs a human answer: not dismissed, and no answer
|
|
117
|
+
* yet. Mirrors the backend `isPendingQuestion` (orchestration `initiative.logic.ts`) — the rule the
|
|
118
|
+
* interviewer, the retained-across-rounds digest and the continue gate all key off — so the window's
|
|
119
|
+
* pending list, its unanswered counter and its render order can never disagree with the engine
|
|
120
|
+
* about what is still open.
|
|
121
|
+
*/
|
|
122
|
+
export function isPendingQuestion(q: Partial<Pick<InitiativeQa, 'answer' | 'status'>>): boolean {
|
|
123
|
+
return q.status !== 'dismissed' && (q.answer ?? '').trim().length === 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Interview questions in the order the planning window renders them: everything still pending
|
|
128
|
+
* first, everything already settled (answered, or dismissed as not relevant) after, each group
|
|
129
|
+
* keeping the interviewer's own chronological order.
|
|
130
|
+
*
|
|
131
|
+
* Each round APPENDS its new questions after the digest retained from the previous ones (backend
|
|
132
|
+
* `applyInterviewQuestions`: `[...retainedQa, ...pending]`), so from round two onwards the only
|
|
133
|
+
* questions the human still has to act on sit below a growing wall of ones they already settled —
|
|
134
|
+
* on a long interview, below the fold entirely. This reorders the RENDER only; the stored `qa`
|
|
135
|
+
* order, which the interviewer prompt and the in-repo tracker digest read, is untouched.
|
|
136
|
+
*/
|
|
137
|
+
export function orderInterviewQuestions<T extends Partial<Pick<InitiativeQa, 'answer' | 'status'>>>(
|
|
138
|
+
qa: readonly T[],
|
|
139
|
+
): T[] {
|
|
140
|
+
const pending: T[] = []
|
|
141
|
+
const settled: T[] = []
|
|
142
|
+
for (const q of qa) (isPendingQuestion(q) ? pending : settled).push(q)
|
|
143
|
+
return [...pending, ...settled]
|
|
144
|
+
}
|
|
145
|
+
|
|
114
146
|
/**
|
|
115
147
|
* The phase whose completed checkpoint (D2) is awaiting a human, or null. Mirrors the backend
|
|
116
148
|
* `pendingCheckpoint` (orchestration `initiative.logic.ts`) so the SPA recomputes the pending
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import type { PipelineStep, StepPhaseMetrics } from '~/types/execution'
|
|
3
|
+
import { foldRunPhaseMetrics, totalInputTokens } from './observability'
|
|
3
4
|
|
|
4
5
|
describe('totalInputTokens', () => {
|
|
5
6
|
it('sums all three input classes, so the headline matches Claude Code’s context gauge', () => {
|
|
@@ -26,3 +27,61 @@ describe('totalInputTokens', () => {
|
|
|
26
27
|
expect(totalInputTokens({ promptTokens: 500 })).toBe(500)
|
|
27
28
|
})
|
|
28
29
|
})
|
|
30
|
+
|
|
31
|
+
describe('foldRunPhaseMetrics', () => {
|
|
32
|
+
const phase = (over: Partial<StepPhaseMetrics> & Pick<StepPhaseMetrics, 'phase'>) => ({
|
|
33
|
+
calls: 1,
|
|
34
|
+
promptTokens: 10,
|
|
35
|
+
cacheReadTokens: 0,
|
|
36
|
+
cacheWriteTokens: 0,
|
|
37
|
+
completionTokens: 5,
|
|
38
|
+
carryCostTokens: 0,
|
|
39
|
+
errors: 0,
|
|
40
|
+
...over,
|
|
41
|
+
})
|
|
42
|
+
const step = (agentKind: string, byPhase: StepPhaseMetrics[]) =>
|
|
43
|
+
({ agentKind, metrics: { byPhase } }) as unknown as PipelineStep
|
|
44
|
+
|
|
45
|
+
it('does NOT double-count two steps that share an agent kind', () => {
|
|
46
|
+
// A step's rollup covers its agent KIND across the whole run (the proxy keys a conversation
|
|
47
|
+
// by `(execution, agentKind)`), so two tester steps carry identical numbers. Summing them
|
|
48
|
+
// would report twice the tokens the run actually spent.
|
|
49
|
+
const rows = [phase({ phase: 'agent', calls: 3, carryCostTokens: 90 })]
|
|
50
|
+
const folded = foldRunPhaseMetrics([step('tester', rows), step('tester', rows)])
|
|
51
|
+
expect(folded).toHaveLength(1)
|
|
52
|
+
expect(folded[0]).toMatchObject({ phase: 'agent', calls: 3, carryCostTokens: 90 })
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('merges a phase across different agent kinds and sorts costliest first', () => {
|
|
56
|
+
const folded = foldRunPhaseMetrics([
|
|
57
|
+
step('coder', [
|
|
58
|
+
phase({ phase: 'agent', calls: 2, carryCostTokens: 10 }),
|
|
59
|
+
phase({ phase: 'validation-repair', calls: 1, carryCostTokens: 500 }),
|
|
60
|
+
]),
|
|
61
|
+
step('reviewer', [phase({ phase: 'agent', calls: 4, carryCostTokens: 40 })]),
|
|
62
|
+
])
|
|
63
|
+
expect(folded.map((p) => [p.phase, p.calls, p.carryCostTokens])).toEqual([
|
|
64
|
+
['validation-repair', 1, 500],
|
|
65
|
+
['agent', 6, 50],
|
|
66
|
+
])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("keeps the unattributed '' phase rather than hiding it", () => {
|
|
70
|
+
const folded = foldRunPhaseMetrics([step('coder', [phase({ phase: '', calls: 7 })])])
|
|
71
|
+
expect(folded.map((p) => p.phase)).toEqual([''])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('is empty when no step carries a rollup, so the section simply does not render', () => {
|
|
75
|
+
expect(foldRunPhaseMetrics([{ agentKind: 'coder' } as unknown as PipelineStep])).toEqual([])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('returns fresh rows rather than aliasing the store objects it folded', () => {
|
|
79
|
+
// The single-kind case is the one that used to pass a `step.metrics.byPhase` row straight
|
|
80
|
+
// through: a caller mutating what a fold handed it would have written into the store.
|
|
81
|
+
const row = phase({ phase: 'agent', calls: 3, carryCostTokens: 90 })
|
|
82
|
+
const folded = foldRunPhaseMetrics([step('coder', [row])])
|
|
83
|
+
expect(folded[0]).not.toBe(row)
|
|
84
|
+
folded[0]!.calls = 999
|
|
85
|
+
expect(row.calls).toBe(3)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// rollups + the drill-down panel). Kept here so the components stay declarative and
|
|
3
3
|
// the number-crunching is unit-testable.
|
|
4
4
|
|
|
5
|
-
import type { StepMetrics } from '~/types/execution'
|
|
5
|
+
import type { PipelineStep, StepMetrics, StepPhaseMetrics } from '~/types/execution'
|
|
6
6
|
|
|
7
7
|
/** Compact token count: 1234 → "1.2k", 980 → "980", 2_500_000 → "2.5M". */
|
|
8
8
|
export function formatTokens(n: number): string {
|
|
@@ -69,6 +69,61 @@ export function transportRatio(m: Pick<StepMetrics, 'upstreamMs' | 'overheadMs'>
|
|
|
69
69
|
return total > 0 ? m.overheadMs / total : null
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** Zero cell, so the fold below has one accumulator shape and never aliases a store row. */
|
|
73
|
+
const EMPTY_PHASE: Omit<StepPhaseMetrics, 'phase'> = {
|
|
74
|
+
calls: 0,
|
|
75
|
+
promptTokens: 0,
|
|
76
|
+
cacheReadTokens: 0,
|
|
77
|
+
cacheWriteTokens: 0,
|
|
78
|
+
completionTokens: 0,
|
|
79
|
+
carryCostTokens: 0,
|
|
80
|
+
errors: 0,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The run's model spend split by the PHASE that spent it, folded from the per-step rollups the
|
|
85
|
+
* engine already pushes. Rows come back costliest-carry-cost first — the slice worth attacking.
|
|
86
|
+
*
|
|
87
|
+
* Two things make this correct rather than a naive sum over `steps`:
|
|
88
|
+
*
|
|
89
|
+
* 1. **Deduplicate by agent kind.** A step's `metrics` is the rollup for its AGENT KIND across
|
|
90
|
+
* the whole run (the proxy keys a conversation by `(execution, agentKind)`, not by step
|
|
91
|
+
* index), so two steps of the same kind carry the SAME numbers. Adding them would double
|
|
92
|
+
* every figure on any pipeline with, say, two tester steps.
|
|
93
|
+
* 2. **Read it off the rollup, not off the loaded calls.** The panel's call list is capped, so
|
|
94
|
+
* folding phases client-side from it would silently under-report exactly the long runs this
|
|
95
|
+
* breakdown exists for. The rollup is a SQL aggregate over every row.
|
|
96
|
+
*
|
|
97
|
+
* Every returned row is a FRESH object, never a row of `step.metrics.byPhase` passed through:
|
|
98
|
+
* those belong to the store, and a fold whose output aliases its input is a trap for the next
|
|
99
|
+
* caller that reasonably assumes it may mutate what a fold handed it.
|
|
100
|
+
*/
|
|
101
|
+
export function foldRunPhaseMetrics(steps: readonly PipelineStep[]): StepPhaseMetrics[] {
|
|
102
|
+
const seenKinds = new Set<string>()
|
|
103
|
+
const byPhase = new Map<string, StepPhaseMetrics>()
|
|
104
|
+
for (const step of steps) {
|
|
105
|
+
const rows = step.metrics?.byPhase
|
|
106
|
+
if (!rows?.length || seenKinds.has(step.agentKind)) continue
|
|
107
|
+
seenKinds.add(step.agentKind)
|
|
108
|
+
for (const row of rows) {
|
|
109
|
+
const prev = byPhase.get(row.phase) ?? EMPTY_PHASE
|
|
110
|
+
byPhase.set(row.phase, {
|
|
111
|
+
phase: row.phase,
|
|
112
|
+
calls: prev.calls + row.calls,
|
|
113
|
+
promptTokens: prev.promptTokens + row.promptTokens,
|
|
114
|
+
cacheReadTokens: prev.cacheReadTokens + row.cacheReadTokens,
|
|
115
|
+
cacheWriteTokens: prev.cacheWriteTokens + row.cacheWriteTokens,
|
|
116
|
+
completionTokens: prev.completionTokens + row.completionTokens,
|
|
117
|
+
carryCostTokens: prev.carryCostTokens + row.carryCostTokens,
|
|
118
|
+
errors: prev.errors + row.errors,
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return [...byPhase.values()].sort(
|
|
123
|
+
(a, b) => b.carryCostTokens - a.carryCostTokens || b.calls - a.calls,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
72
127
|
/** Tailwind text/bg colour for an output-headroom level (green → amber → red). */
|
|
73
128
|
export function headroomColor(ratio: number | null, truncated: boolean): string {
|
|
74
129
|
if (truncated || (ratio != null && ratio >= 0.98)) return 'text-rose-400'
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { buildSourceChoices, reconcileSource } from './
|
|
2
|
+
import { buildSourceChoices, reconcileSource } from './taskSources'
|
|
3
3
|
import type { TaskSourceState } from '~/types/domain'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* The pure
|
|
7
|
-
* selector promises: the tracker in use is named even when it is the only
|
|
8
|
-
* the workspace hasn't got yet is offered as something to ADD (worded for its
|
|
9
|
-
* state), and the selection stays valid as the offered set changes underneath it.
|
|
6
|
+
* The pure tracker-selection behind `<ContextIssuePicker>` and `<BugHuntModal>`. Pins what
|
|
7
|
+
* the always-visible selector promises: the tracker in use is named even when it is the only
|
|
8
|
+
* one, a tracker the workspace hasn't got yet is offered as something to ADD (worded for its
|
|
9
|
+
* actual state), and the selection stays valid as the offered set changes underneath it.
|
|
10
10
|
*/
|
|
11
11
|
const state = (source: string, { available = true, enabled = true } = {}): TaskSourceState =>
|
|
12
12
|
({
|
|
@@ -55,6 +55,17 @@ describe('buildSourceChoices', () => {
|
|
|
55
55
|
])
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
+
// What a surface's "nothing connected yet" state renders its add buttons from: it flattens
|
|
59
|
+
// the groups, so every choice there has to be addable. A `select` leaking through would
|
|
60
|
+
// offer to connect a tracker the workspace already has.
|
|
61
|
+
it('yields only addable choices when the workspace offers no tracker', () => {
|
|
62
|
+
const choices = buildSourceChoices(
|
|
63
|
+
[state('jira', { available: false }), state('linear', { enabled: false })],
|
|
64
|
+
undefined,
|
|
65
|
+
).flat()
|
|
66
|
+
expect(choices.map((c) => c.action)).toEqual(['connect', 'enable'])
|
|
67
|
+
})
|
|
68
|
+
|
|
58
69
|
it('drops empty groups so the menu renders no stray separator', () => {
|
|
59
70
|
expect(buildSourceChoices([state('github')], 'github')).toHaveLength(1)
|
|
60
71
|
expect(buildSourceChoices([state('jira', { available: false })], undefined)).toHaveLength(1)
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import type { TaskSourceKind, TaskSourceState } from '~/types/domain'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Pure
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Pure tracker-selection logic shared by every surface that picks a task source: which
|
|
5
|
+
* tracker is selected, and what its menu offers. Kept out of the components so their
|
|
6
|
+
* `computed`s and the unit spec call the same code.
|
|
7
7
|
*
|
|
8
8
|
* The menu is deliberately two-tier — pick an already-offered tracker, or go and add
|
|
9
|
-
* one — because
|
|
10
|
-
* missing, and sending them to the Integrations hub loses
|
|
9
|
+
* one — because a tracker-picking surface is exactly where a user discovers the tracker
|
|
10
|
+
* they want is missing, and sending them off to the Integrations hub loses whatever they
|
|
11
|
+
* had in progress. Today `<ContextIssuePicker>` (attach a context issue) and
|
|
12
|
+
* `<BugHuntModal>` (scan a board for bugs) both render it.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
|
-
/** One row of
|
|
15
|
+
/** One row of a tracker menu. */
|
|
14
16
|
export type SourceChoice =
|
|
15
|
-
/** An offered tracker the
|
|
17
|
+
/** An offered tracker the surface can use right now. */
|
|
16
18
|
| { action: 'select'; source: TaskSourceKind; label: string; icon: string; active: boolean }
|
|
17
19
|
/**
|
|
18
20
|
* A configured tracker that is not offered yet, so it can be added from here. `connect`
|
|
@@ -23,8 +25,8 @@ export type SourceChoice =
|
|
|
23
25
|
| { action: 'connect' | 'enable'; source: TaskSourceKind; label: string; icon: string }
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
+
* A tracker menu, as non-empty groups (the offered trackers, then the ones the user could
|
|
29
|
+
* add). Empty groups are dropped so the menu never renders a stray separator.
|
|
28
30
|
*/
|
|
29
31
|
export function buildSourceChoices(
|
|
30
32
|
sources: TaskSourceState[],
|
|
@@ -54,13 +56,13 @@ export function buildSourceChoices(
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
57
|
-
* The source
|
|
59
|
+
* The source a surface should hold once the offered set changes — after a connect, a
|
|
58
60
|
* disconnect, or the per-workspace toggle flipping elsewhere.
|
|
59
61
|
*
|
|
60
62
|
* `awaiting` is the tracker the user just left to connect: the moment it becomes offered
|
|
61
63
|
* it wins, so they land back on the source they went to add rather than on whatever was
|
|
62
64
|
* selected before. Otherwise a still-offered selection is kept, and a selection that
|
|
63
|
-
* stopped being offered falls back to the first one (
|
|
65
|
+
* stopped being offered falls back to the first one (reading a tracker the workspace no
|
|
64
66
|
* longer offers only yields errors).
|
|
65
67
|
*/
|
|
66
68
|
export function reconcileSource(
|
package/i18n/locales/de.json
CHANGED
|
@@ -3010,6 +3010,19 @@
|
|
|
3010
3010
|
"cacheWrite": "{tokens} in den Cache geschrieben",
|
|
3011
3011
|
"cacheWriteHint": "Eingabe-Tokens, die in den Cache des Providers geschrieben wurden (1,25x bis 2x der Preis frischer Eingabe-Tokens)"
|
|
3012
3012
|
},
|
|
3013
|
+
"phase": {
|
|
3014
|
+
"title": "Wohin die Tokens geflossen sind",
|
|
3015
|
+
"subtitle": "nach Phase des Laufs, sortiert nach Mitschleppkosten",
|
|
3016
|
+
"columns": {
|
|
3017
|
+
"phase": "Phase",
|
|
3018
|
+
"turns": "Runden",
|
|
3019
|
+
"tokensInOut": "Tokens (ein / aus)",
|
|
3020
|
+
"carryCost": "Mitschleppkosten"
|
|
3021
|
+
},
|
|
3022
|
+
"carryCostHint": "Wie stark jede Phase die nachfolgenden Runden belastet hat: ihr Kontext einmal für jede spätere Runde gezählt, die ihn erneut senden musste. Vergleichen Sie die Phasen eines Laufs miteinander; für sich allein sagt die Zahl nichts aus. Eine spät laufende Phase schleppt wenig mit, wie viel sie auch verbraucht hat; lesen Sie diese Spalte daher zusammen mit den Tokens daneben.",
|
|
3023
|
+
"unattributed": "Nicht zugeordnet",
|
|
3024
|
+
"unattributedHint": "von einem Kanal erfasst, der keine Phase meldet"
|
|
3025
|
+
},
|
|
3013
3026
|
"metricsBar": {
|
|
3014
3027
|
"calls": "{count} Aufruf | {count} Aufrufe",
|
|
3015
3028
|
"inputCompletionTokens": "Eingabe- / Ausgabe-Tokens insgesamt. Die Eingabe zählt auch gecachte Tokens mit: Sie belegen weiterhin das Kontextfenster, genau wie es die Kontextanzeige von Claude Code zählt.",
|
|
@@ -3431,6 +3444,8 @@
|
|
|
3431
3444
|
"intro": "Durchsuche ein Tracker-Board nach offenen, nicht zugewiesenen Fehlern und bewerte sie nach Auswirkung im Verhältnis zum geschätzten Aufwand. Wähle einen aus, und er wird zu einer Aufgabe, die die Fehlerbehebungs-Pipeline durchläuft.",
|
|
3432
3445
|
"connectFirst": "Verbinde oder aktiviere zuerst eine Aufgabenquelle.",
|
|
3433
3446
|
"connectSource": "{label} verbinden",
|
|
3447
|
+
"enableSource": "{label} aktivieren",
|
|
3448
|
+
"pickTracker": "Tracker auswählen",
|
|
3434
3449
|
"needFrameFirst": "Füge zuerst einen Service-Rahmen zum Board hinzu, damit ein übernommener Fehler irgendwo landen kann.",
|
|
3435
3450
|
"tracker": "Tracker",
|
|
3436
3451
|
"board": "Board",
|
package/i18n/locales/en.json
CHANGED
|
@@ -1474,6 +1474,19 @@
|
|
|
1474
1474
|
"cacheWrite": "{tokens} cache write",
|
|
1475
1475
|
"cacheWriteHint": "Input tokens written into the provider's cache (1.25x to 2x the price of fresh input)"
|
|
1476
1476
|
},
|
|
1477
|
+
"phase": {
|
|
1478
|
+
"title": "Where the tokens went",
|
|
1479
|
+
"subtitle": "by phase of the run, ordered by carry cost",
|
|
1480
|
+
"columns": {
|
|
1481
|
+
"phase": "Phase",
|
|
1482
|
+
"turns": "Turns",
|
|
1483
|
+
"tokensInOut": "Tokens (in / out)",
|
|
1484
|
+
"carryCost": "Carry cost"
|
|
1485
|
+
},
|
|
1486
|
+
"carryCostHint": "How much each phase burdened the turns that came after it: its context counted once for every later turn that had to re-send it. Compare a run's phases with each other; on its own the number means nothing. A phase that runs late carries little however much it spent, so read this column alongside the tokens beside it.",
|
|
1487
|
+
"unattributed": "Unattributed",
|
|
1488
|
+
"unattributedHint": "recorded by a channel that reports no phase"
|
|
1489
|
+
},
|
|
1477
1490
|
"metricsBar": {
|
|
1478
1491
|
"calls": "{count} call | {count} calls",
|
|
1479
1492
|
"inputCompletionTokens": "Total input / completion tokens. Input counts cached tokens too: they still occupy the context window, exactly as Claude Code's own context gauge counts them.",
|
|
@@ -3854,6 +3867,8 @@
|
|
|
3854
3867
|
"intro": "Scan a tracker board for open, unassigned bugs and rank them by impact against how hard each looks to fix. Pick one and it becomes a task running the bug-fix pipeline.",
|
|
3855
3868
|
"connectFirst": "Connect or enable a task source first.",
|
|
3856
3869
|
"connectSource": "Connect {label}",
|
|
3870
|
+
"enableSource": "Enable {label}",
|
|
3871
|
+
"pickTracker": "Pick a tracker",
|
|
3857
3872
|
"needFrameFirst": "Add a service frame to the board first, so a picked-up bug has somewhere to land.",
|
|
3858
3873
|
"tracker": "Tracker",
|
|
3859
3874
|
"board": "Board",
|
package/i18n/locales/es.json
CHANGED
|
@@ -1408,6 +1408,19 @@
|
|
|
1408
1408
|
"cacheWrite": "{tokens} escritos en caché",
|
|
1409
1409
|
"cacheWriteHint": "Tokens de entrada escritos en la caché del proveedor (de 1,25 a 2 veces el precio de la entrada fresca)"
|
|
1410
1410
|
},
|
|
1411
|
+
"phase": {
|
|
1412
|
+
"title": "Adónde fueron los tokens",
|
|
1413
|
+
"subtitle": "por fase de la ejecución, ordenado por coste de arrastre",
|
|
1414
|
+
"columns": {
|
|
1415
|
+
"phase": "Fase",
|
|
1416
|
+
"turns": "Turnos",
|
|
1417
|
+
"tokensInOut": "Tokens (entrada / salida)",
|
|
1418
|
+
"carryCost": "Coste de arrastre"
|
|
1419
|
+
},
|
|
1420
|
+
"carryCostHint": "Cuánto cargó cada fase sobre los turnos posteriores: su contexto contado una vez por cada turno que tuvo que reenviarlo. Compara entre sí las fases de una ejecución; por sí sola la cifra no significa nada. Una fase que se ejecuta al final arrastra poco por mucho que haya gastado, así que lee esta columna junto a los tokens de al lado.",
|
|
1421
|
+
"unattributed": "Sin atribuir",
|
|
1422
|
+
"unattributedHint": "registrado por un canal que no informa de la fase"
|
|
1423
|
+
},
|
|
1411
1424
|
"metricsBar": {
|
|
1412
1425
|
"calls": "{count} llamada | {count} llamadas",
|
|
1413
1426
|
"inputCompletionTokens": "Tokens totales de entrada / salida. La entrada tambien cuenta los tokens en caché: siguen ocupando la ventana de contexto, igual que los cuenta el medidor de contexto de Claude Code.",
|
|
@@ -3743,6 +3756,8 @@
|
|
|
3743
3756
|
"intro": "Explora un tablero del gestor de incidencias en busca de errores abiertos y sin asignar, y clasifícalos por impacto frente a lo difícil que parece arreglar cada uno. Elige uno y se convertirá en una tarea que ejecuta la canalización de corrección de errores.",
|
|
3744
3757
|
"connectFirst": "Conecta o activa primero una fuente de tareas.",
|
|
3745
3758
|
"connectSource": "Conectar {label}",
|
|
3759
|
+
"enableSource": "Habilitar {label}",
|
|
3760
|
+
"pickTracker": "Elige un gestor de incidencias",
|
|
3746
3761
|
"needFrameFirst": "Añade primero un marco de servicio al tablero para que el error elegido tenga dónde aterrizar.",
|
|
3747
3762
|
"tracker": "Gestor de incidencias",
|
|
3748
3763
|
"board": "Tablero",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -1408,6 +1408,19 @@
|
|
|
1408
1408
|
"cacheWrite": "{tokens} écrits dans le cache",
|
|
1409
1409
|
"cacheWriteHint": "Tokens d'entrée écrits dans le cache du fournisseur (1,25 à 2 fois le prix d'une entrée fraîche)"
|
|
1410
1410
|
},
|
|
1411
|
+
"phase": {
|
|
1412
|
+
"title": "Où sont passés les tokens",
|
|
1413
|
+
"subtitle": "par phase de l'exécution, trié par coût de report",
|
|
1414
|
+
"columns": {
|
|
1415
|
+
"phase": "Phase",
|
|
1416
|
+
"turns": "Tours",
|
|
1417
|
+
"tokensInOut": "Tokens (entrée / sortie)",
|
|
1418
|
+
"carryCost": "Coût de report"
|
|
1419
|
+
},
|
|
1420
|
+
"carryCostHint": "Ce que chaque phase a fait peser sur les tours suivants : son contexte compté une fois pour chaque tour ultérieur ayant dû le renvoyer. Comparez les phases d'une exécution entre elles ; isolée, la valeur ne signifie rien. Une phase qui s'exécute en dernier reporte peu, quoi qu'elle ait dépensé : lisez donc cette colonne avec les tokens voisins.",
|
|
1421
|
+
"unattributed": "Non attribué",
|
|
1422
|
+
"unattributedHint": "enregistré par un canal qui ne rapporte aucune phase"
|
|
1423
|
+
},
|
|
1411
1424
|
"metricsBar": {
|
|
1412
1425
|
"calls": "{count} appel | {count} appels",
|
|
1413
1426
|
"inputCompletionTokens": "Total des tokens d'entrée / de sortie. L'entree compte aussi les tokens en cache : ils occupent toujours la fenêtre de contexte, exactement comme les compte la jauge de contexte de Claude Code.",
|
|
@@ -3743,6 +3756,8 @@
|
|
|
3743
3756
|
"intro": "Parcourez un tableau du gestionnaire de tickets à la recherche de bugs ouverts et non assignés, puis classez-les selon leur impact face à la difficulté apparente du correctif. Choisissez-en un et il devient une tâche qui exécute le pipeline de correction.",
|
|
3744
3757
|
"connectFirst": "Connectez ou activez d'abord une source de tâches.",
|
|
3745
3758
|
"connectSource": "Connecter {label}",
|
|
3759
|
+
"enableSource": "Activer {label}",
|
|
3760
|
+
"pickTracker": "Choisir un gestionnaire de tickets",
|
|
3746
3761
|
"needFrameFirst": "Ajoutez d'abord un cadre de service au tableau, pour que le bug retenu ait un endroit où atterrir.",
|
|
3747
3762
|
"tracker": "Gestionnaire de tickets",
|
|
3748
3763
|
"board": "Tableau",
|
package/i18n/locales/he.json
CHANGED
|
@@ -1408,6 +1408,19 @@
|
|
|
1408
1408
|
"cacheWrite": "{tokens} נכתבו למטמון",
|
|
1409
1409
|
"cacheWriteHint": "טוקני קלט שנכתבו למטמון הספק (פי 1.25 עד 2 ממחיר קלט חדש)"
|
|
1410
1410
|
},
|
|
1411
|
+
"phase": {
|
|
1412
|
+
"title": "לאן הלכו הטוקנים",
|
|
1413
|
+
"subtitle": "לפי שלב בהרצה, ממוין לפי עלות גרירה",
|
|
1414
|
+
"columns": {
|
|
1415
|
+
"phase": "שלב",
|
|
1416
|
+
"turns": "תורים",
|
|
1417
|
+
"tokensInOut": "טוקנים (נכנס / יוצא)",
|
|
1418
|
+
"carryCost": "עלות גרירה"
|
|
1419
|
+
},
|
|
1420
|
+
"carryCostHint": "כמה כל שלב העמיס על התורים שבאו אחריו: ההקשר שלו נספר פעם אחת עבור כל תור מאוחר יותר שנאלץ לשלוח אותו שוב. השוו בין שלבי ההרצה זה לזה; בפני עצמו המספר אינו אומר דבר. שלב שרץ בסוף גורר מעט גם אם צרך הרבה, ולכן קראו את העמודה הזאת יחד עם הטוקנים שלצדה.",
|
|
1421
|
+
"unattributed": "ללא שיוך",
|
|
1422
|
+
"unattributedHint": "נרשם על ידי ערוץ שאינו מדווח על שלב"
|
|
1423
|
+
},
|
|
1411
1424
|
"metricsBar": {
|
|
1412
1425
|
"calls": "{count} קריאה | {count} קריאות",
|
|
1413
1426
|
"inputCompletionTokens": "סך טוקני הקלט / הפלט. הקלט סופר גם טוקנים מהמטמון: הם עדיין תופסים את חלון ההקשר, בדיוק כפי שמד ההקשר של Claude Code סופר אותם.",
|
|
@@ -3754,6 +3767,8 @@
|
|
|
3754
3767
|
"intro": "סרוק לוח של מערכת מעקב לאיתור באגים פתוחים שאינם משויכים לאיש, ודרג אותם לפי ההשפעה מול מידת הקושי המשוערת בתיקון. בחר אחד והוא יהפוך למשימה שמריצה את צינור תיקון הבאגים.",
|
|
3755
3768
|
"connectFirst": "חבר או הפעל תחילה מקור משימות.",
|
|
3756
3769
|
"connectSource": "חבר את {label}",
|
|
3770
|
+
"enableSource": "הפעל {label}",
|
|
3771
|
+
"pickTracker": "בחר מערכת מעקב",
|
|
3757
3772
|
"needFrameFirst": "הוסף תחילה מסגרת שירות ללוח, כדי שלבאג הנבחר יהיה לאן להגיע.",
|
|
3758
3773
|
"tracker": "מערכת מעקב",
|
|
3759
3774
|
"board": "לוח",
|
package/i18n/locales/it.json
CHANGED
|
@@ -3010,6 +3010,19 @@
|
|
|
3010
3010
|
"cacheWrite": "{tokens} scritti nella cache",
|
|
3011
3011
|
"cacheWriteHint": "Token di input scritti nella cache del provider (da 1,25 a 2 volte il prezzo dell'input fresco)"
|
|
3012
3012
|
},
|
|
3013
|
+
"phase": {
|
|
3014
|
+
"title": "Dove sono finiti i token",
|
|
3015
|
+
"subtitle": "per fase dell'esecuzione, ordinato per costo di trascinamento",
|
|
3016
|
+
"columns": {
|
|
3017
|
+
"phase": "Fase",
|
|
3018
|
+
"turns": "Turni",
|
|
3019
|
+
"tokensInOut": "Token (in / out)",
|
|
3020
|
+
"carryCost": "Costo di trascinamento"
|
|
3021
|
+
},
|
|
3022
|
+
"carryCostHint": "Quanto ogni fase ha gravato sui turni successivi: il suo contesto contato una volta per ogni turno che ha dovuto rinviarlo. Confronta tra loro le fasi di un'esecuzione; da solo il numero non significa nulla. Una fase che viene eseguita per ultima trascina poco per quanto abbia speso, quindi leggi questa colonna insieme ai token accanto.",
|
|
3023
|
+
"unattributed": "Non attribuito",
|
|
3024
|
+
"unattributedHint": "registrato da un canale che non riporta la fase"
|
|
3025
|
+
},
|
|
3013
3026
|
"metricsBar": {
|
|
3014
3027
|
"calls": "{count} chiamata | {count} chiamate",
|
|
3015
3028
|
"inputCompletionTokens": "Token totali di input / output. L'input conta anche i token in cache: occupano comunque la finestra di contesto, esattamente come li conta l'indicatore di contesto di Claude Code.",
|
|
@@ -3431,6 +3444,8 @@
|
|
|
3431
3444
|
"intro": "Esplora una bacheca del tracker alla ricerca di bug aperti e non assegnati e classificali per impatto rispetto a quanto sembra difficile risolverli. Scegline uno e diventerà un'attività che esegue la pipeline di correzione.",
|
|
3432
3445
|
"connectFirst": "Collega o abilita prima una sorgente di attività.",
|
|
3433
3446
|
"connectSource": "Collega {label}",
|
|
3447
|
+
"enableSource": "Abilita {label}",
|
|
3448
|
+
"pickTracker": "Scegli un tracker",
|
|
3434
3449
|
"needFrameFirst": "Aggiungi prima un frame di servizio alla bacheca, così il bug scelto ha dove atterrare.",
|
|
3435
3450
|
"tracker": "Tracker",
|
|
3436
3451
|
"board": "Bacheca",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -1408,6 +1408,19 @@
|
|
|
1408
1408
|
"cacheWrite": "{tokens} キャッシュ書き込み",
|
|
1409
1409
|
"cacheWriteHint": "プロバイダーのキャッシュに書き込まれた入力トークン (新規入力の 1.25 倍から 2 倍の価格)"
|
|
1410
1410
|
},
|
|
1411
|
+
"phase": {
|
|
1412
|
+
"title": "トークンの使われ先",
|
|
1413
|
+
"subtitle": "実行のフェーズ別、持ち越しコスト順",
|
|
1414
|
+
"columns": {
|
|
1415
|
+
"phase": "フェーズ",
|
|
1416
|
+
"turns": "ターン数",
|
|
1417
|
+
"tokensInOut": "トークン (入力 / 出力)",
|
|
1418
|
+
"carryCost": "持ち越しコスト"
|
|
1419
|
+
},
|
|
1420
|
+
"carryCostHint": "各フェーズが後続のターンにどれだけ負担をかけたか。そのコンテキストを、再送が必要になった後続ターンの数だけ数えた値です。1 回の実行内でフェーズ同士を比較してください。単独の数値には意味がありません。最後に実行されるフェーズは、どれだけ消費しても持ち越しはわずかです。この列は隣のトークン数と併せて読んでください。",
|
|
1421
|
+
"unattributed": "未特定",
|
|
1422
|
+
"unattributedHint": "フェーズを報告しないチャネルで記録"
|
|
1423
|
+
},
|
|
1411
1424
|
"metricsBar": {
|
|
1412
1425
|
"calls": "{count} 件の呼び出し | {count} 件の呼び出し",
|
|
1413
1426
|
"inputCompletionTokens": "入力 / 出力トークンの合計。入力にはキャッシュされたトークンも含まれます。それらも依然としてコンテキストウィンドウを占有するため、Claude Code のコンテキスト表示と同じ数え方です。",
|
|
@@ -3755,6 +3768,8 @@
|
|
|
3755
3768
|
"intro": "トラッカーのボードから未割り当ての未解決バグを探し、影響度と修正の難しさの比で並べ替えます。ひとつ選ぶと、バグ修正パイプラインを実行するタスクになります。",
|
|
3756
3769
|
"connectFirst": "先にタスクソースを接続または有効化してください。",
|
|
3757
3770
|
"connectSource": "{label} を接続",
|
|
3771
|
+
"enableSource": "{label} を有効化",
|
|
3772
|
+
"pickTracker": "トラッカーを選択",
|
|
3758
3773
|
"needFrameFirst": "選んだバグの受け入れ先が必要なので、先にサービスフレームをボードに追加してください。",
|
|
3759
3774
|
"tracker": "トラッカー",
|
|
3760
3775
|
"board": "ボード",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -1408,6 +1408,19 @@
|
|
|
1408
1408
|
"cacheWrite": "{tokens} zapisane do pamięci podręcznej",
|
|
1409
1409
|
"cacheWriteHint": "Tokeny wejściowe zapisane do pamięci podręcznej dostawcy (od 1,25 do 2 razy cena świeżego wejścia)"
|
|
1410
1410
|
},
|
|
1411
|
+
"phase": {
|
|
1412
|
+
"title": "Na co poszły tokeny",
|
|
1413
|
+
"subtitle": "według fazy przebiegu, posortowane według kosztu przenoszenia",
|
|
1414
|
+
"columns": {
|
|
1415
|
+
"phase": "Faza",
|
|
1416
|
+
"turns": "Tury",
|
|
1417
|
+
"tokensInOut": "Tokeny (we / wy)",
|
|
1418
|
+
"carryCost": "Koszt przenoszenia"
|
|
1419
|
+
},
|
|
1420
|
+
"carryCostHint": "Jak bardzo każda faza obciążyła kolejne tury: jej kontekst policzony raz za każdą późniejszą turę, która musiała go wysłać ponownie. Porównuj fazy jednego przebiegu ze sobą; sama liczba nic nie znaczy. Faza wykonywana na końcu przenosi niewiele, choć by zużyła dużo, dlatego czytaj tę kolumnę razem z tokenami obok.",
|
|
1421
|
+
"unattributed": "Bez przypisania",
|
|
1422
|
+
"unattributedHint": "zarejestrowane przez kanał, który nie podaje fazy"
|
|
1423
|
+
},
|
|
1411
1424
|
"metricsBar": {
|
|
1412
1425
|
"calls": "{count} wywołanie | {count} wywołania | {count} wywołań",
|
|
1413
1426
|
"inputCompletionTokens": "Łączna liczba tokenów wejścia / wyjscia. Wejście liczy takze tokeny z pamięci podręcznej: nadal zajmują okno kontekstu, dokładnie tak, jak liczy je wskaźnik kontekstu w Claude Code.",
|
|
@@ -3743,6 +3756,8 @@
|
|
|
3743
3756
|
"intro": "Przeszukaj tablicę systemu zgłoszeń w poszukiwaniu otwartych, nieprzypisanych błędów i uszereguj je według wpływu w stosunku do przewidywanej trudności naprawy. Wybierz jeden, a stanie się zadaniem uruchamiającym potok naprawy błędów.",
|
|
3744
3757
|
"connectFirst": "Najpierw połącz lub włącz źródło zadań.",
|
|
3745
3758
|
"connectSource": "Połącz {label}",
|
|
3759
|
+
"enableSource": "Włącz {label}",
|
|
3760
|
+
"pickTracker": "Wybierz system zgłoszeń",
|
|
3746
3761
|
"needFrameFirst": "Najpierw dodaj ramkę usługi do tablicy, aby wybrany błąd miał gdzie wylądować.",
|
|
3747
3762
|
"tracker": "System zgłoszeń",
|
|
3748
3763
|
"board": "Tablica",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -1408,6 +1408,19 @@
|
|
|
1408
1408
|
"cacheWrite": "{tokens} önbelleğe yazıldı",
|
|
1409
1409
|
"cacheWriteHint": "Sağlayıcının önbelleğine yazılan giriş token'ları (yeni girişin 1,25 ila 2 katı fiyat)"
|
|
1410
1410
|
},
|
|
1411
|
+
"phase": {
|
|
1412
|
+
"title": "Token'lar nereye gitti",
|
|
1413
|
+
"subtitle": "çalıştırmanın aşamasına göre, taşıma maliyetine göre sıralanır",
|
|
1414
|
+
"columns": {
|
|
1415
|
+
"phase": "Aşama",
|
|
1416
|
+
"turns": "Tur",
|
|
1417
|
+
"tokensInOut": "Token (giriş / çıkış)",
|
|
1418
|
+
"carryCost": "Taşıma maliyeti"
|
|
1419
|
+
},
|
|
1420
|
+
"carryCostHint": "Her aşamanın kendisinden sonraki turlara ne kadar yük bindirdiği: bağlamı, onu yeniden göndermek zorunda kalan her sonraki tur için bir kez sayılır. Bir çalıştırmanın aşamalarını birbiriyle karşılaştırın; tek başına bu sayı bir şey ifade etmez. En sonda çalışan bir aşama ne kadar harcarsa harcasın az taşır; bu sütunu yanındaki token sayılarıyla birlikte okuyun.",
|
|
1421
|
+
"unattributed": "Atanmamış",
|
|
1422
|
+
"unattributedHint": "aşama bildirmeyen bir kanal tarafından kaydedildi"
|
|
1423
|
+
},
|
|
1411
1424
|
"metricsBar": {
|
|
1412
1425
|
"calls": "{count} çağrı | {count} çağrı",
|
|
1413
1426
|
"inputCompletionTokens": "Toplam giriş / çıkış token'i. Giriş, önbelleğe alınmış token'ları da sayar: bunlar hâlâ bağlam penceresini kaplar, tıpkı Claude Code'un kendi bağlam göstergesinin saydığı gibi.",
|
|
@@ -3755,6 +3768,8 @@
|
|
|
3755
3768
|
"intro": "Bir takip panosunu açık ve kimseye atanmamış hatalar için tarayın ve bunları etkilerine karşı düzeltmenin ne kadar zor göründüğüne göre sıralayın. Birini seçin, hata düzeltme hattını çalıştıran bir göreve dönüşsün.",
|
|
3756
3769
|
"connectFirst": "Önce bir görev kaynağı bağlayın veya etkinleştirin.",
|
|
3757
3770
|
"connectSource": "{label} bağla",
|
|
3771
|
+
"enableSource": "{label} etkinleştir",
|
|
3772
|
+
"pickTracker": "Bir takip aracı seçin",
|
|
3758
3773
|
"needFrameFirst": "Seçilen hatanın ineceği bir yer olması için önce panoya bir servis çerçevesi ekleyin.",
|
|
3759
3774
|
"tracker": "Takip aracı",
|
|
3760
3775
|
"board": "Pano",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -1408,6 +1408,19 @@
|
|
|
1408
1408
|
"cacheWrite": "{tokens} записано в кеш",
|
|
1409
1409
|
"cacheWriteHint": "Вхідні токени, записані в кеш провайдера (від 1,25 до 2 разів ціна нових вхідних токенів)"
|
|
1410
1410
|
},
|
|
1411
|
+
"phase": {
|
|
1412
|
+
"title": "Куди пішли токени",
|
|
1413
|
+
"subtitle": "за фазою запуску, упорядковано за вартістю перенесення",
|
|
1414
|
+
"columns": {
|
|
1415
|
+
"phase": "Фаза",
|
|
1416
|
+
"turns": "Ходи",
|
|
1417
|
+
"tokensInOut": "Токени (вхід / вихід)",
|
|
1418
|
+
"carryCost": "Вартість перенесення"
|
|
1419
|
+
},
|
|
1420
|
+
"carryCostHint": "Наскільки кожна фаза обтяжила наступні ходи: її контекст пораховано один раз за кожен пізніший хід, який мусив надіслати його знову. Порівнюйте фази одного запуску між собою; сама по собі ця величина нічого не означає. Фаза, що виконується останньою, переносить мало, скільки б не витратила, тож читайте цей стовпець разом із токенами поруч.",
|
|
1421
|
+
"unattributed": "Без атрибуції",
|
|
1422
|
+
"unattributedHint": "записано каналом, який не повідомляє фазу"
|
|
1423
|
+
},
|
|
1411
1424
|
"metricsBar": {
|
|
1412
1425
|
"calls": "{count} виклик | {count} виклики | {count} викликів",
|
|
1413
1426
|
"inputCompletionTokens": "Загальна кількість токенів входу / виходу. Вхід враховує й токени з кешу: вони так само займають вікно контексту, точно як їх рахує індикатор контексту в Claude Code.",
|
|
@@ -3743,6 +3756,8 @@
|
|
|
3743
3756
|
"intro": "Перегляньте дошку трекера у пошуках відкритих і не призначених нікому помилок та впорядкуйте їх за впливом щодо того, наскільки складним видається виправлення. Оберіть одну, і вона стане завданням, що виконує конвеєр виправлення помилок.",
|
|
3744
3757
|
"connectFirst": "Спершу під’єднайте або увімкніть джерело завдань.",
|
|
3745
3758
|
"connectSource": "Під'єднати {label}",
|
|
3759
|
+
"enableSource": "Увімкнути {label}",
|
|
3760
|
+
"pickTracker": "Оберіть трекер",
|
|
3746
3761
|
"needFrameFirst": "Спершу додайте рамку сервісу на дошку, щоб обрана помилка мала куди потрапити.",
|
|
3747
3762
|
"tracker": "Трекер",
|
|
3748
3763
|
"board": "Дошка",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.180.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.40",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.189.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|