@cat-factory/app 0.180.0 → 0.181.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/README.md +4 -1
- package/app/components/documents/TaskContextDocs.vue +20 -4
- package/app/components/initiative/InitiativePlanningWindow.vue +115 -71
- package/app/components/initiative/InitiativeTrackerWindow.vue +408 -366
- package/app/components/tasks/TaskContextIssues.vue +22 -4
- package/app/composables/useResultViewRunMeta.spec.ts +74 -0
- package/app/composables/useResultViewRunMeta.ts +98 -0
- package/app/docs/consumer-extensions.md +11 -10
- package/app/modular/panels/inspector.logic.spec.ts +12 -5
- package/app/modular/panels/inspector.logic.ts +14 -5
- package/i18n/locales/de.json +5 -1
- package/i18n/locales/en.json +5 -1
- package/i18n/locales/es.json +5 -1
- package/i18n/locales/fr.json +5 -1
- package/i18n/locales/he.json +5 -1
- package/i18n/locales/it.json +5 -1
- package/i18n/locales/ja.json +5 -1
- package/i18n/locales/pl.json +5 -1
- package/i18n/locales/tr.json +5 -1
- package/i18n/locales/uk.json +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,7 +148,10 @@ example ships in [`deploy/frontend`](../../deploy/frontend) (the `acme:security`
|
|
|
148
148
|
- **Context attachments** (`components/context`) — `ContextAttachmentFields`, the
|
|
149
149
|
shared staged-attachment form used by both the add-task and create-initiative
|
|
150
150
|
modals. Picks are held locally and import-and-linked once the block exists (see
|
|
151
|
-
`composables/useContextLinking`), because linking needs a block id.
|
|
151
|
+
`composables/useContextLinking`), because linking needs a block id. Both hosts
|
|
152
|
+
attach to the SAME per-block linkage, so the inspector's `TaskContextDocs` /
|
|
153
|
+
`TaskContextIssues` sections render for a task AND an initiative — an initiative's
|
|
154
|
+
attachments would otherwise be invisible the moment the create modal closed.
|
|
152
155
|
- **Integrations** — modals/panels for `github` (the source-control panel, shared
|
|
153
156
|
by every VCS provider), `vcs` (the GitLab personal-access-token connect),
|
|
154
157
|
`bootstrap`, `documents`, `tasks`, `requirements` (review), `scenarios`
|
|
@@ -4,8 +4,12 @@ import type { Block } from '~/types/domain'
|
|
|
4
4
|
import ContextDocumentPicker from '~/components/documents/ContextDocumentPicker.vue'
|
|
5
5
|
import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
|
|
6
6
|
|
|
7
|
-
// Documents (from any source) attached to a task
|
|
8
|
-
//
|
|
7
|
+
// Documents (from any source) attached to a task OR an initiative as agent
|
|
8
|
+
// context, shown inside the InspectorPanel. An initiative takes the same
|
|
9
|
+
// attachments (the create-initiative modal stages them exactly as the add-task one
|
|
10
|
+
// does) and its whole planning pipeline reads them, so it gets the same section —
|
|
11
|
+
// only the prose differs, which is why the hint/empty copy is level-keyed below.
|
|
12
|
+
// Attaching uses the SAME inline picker as task creation
|
|
9
13
|
// (source selector + repo→file browse + free-text search + paste-by-reference —
|
|
10
14
|
// ContextDocumentPicker), NOT the old dropdown that opened a second, page-level
|
|
11
15
|
// "Import a page…" modal on top of the inspector. Stacked page-level modals don't
|
|
@@ -27,6 +31,18 @@ onMounted(() => {
|
|
|
27
31
|
})
|
|
28
32
|
|
|
29
33
|
const linked = computed(() => documents.docsForBlock(props.block.id))
|
|
34
|
+
|
|
35
|
+
// Two STATIC literal keys per string, picked by level — the copy names what reads the
|
|
36
|
+
// document (the agents implementing a task vs the pipeline that plans an initiative), which
|
|
37
|
+
// is the whole point of the hint. Assembling one key from `block.level` would defeat the
|
|
38
|
+
// typed message-key check for a two-member choice that gains nothing from being dynamic.
|
|
39
|
+
const isInitiative = computed(() => props.block.level === 'initiative')
|
|
40
|
+
const hint = computed(() =>
|
|
41
|
+
isInitiative.value ? t('documents.taskDocs.hintInitiative') : t('documents.taskDocs.hint'),
|
|
42
|
+
)
|
|
43
|
+
const emptyHint = computed(() =>
|
|
44
|
+
isInitiative.value ? t('documents.taskDocs.emptyInitiative') : t('documents.taskDocs.empty'),
|
|
45
|
+
)
|
|
30
46
|
// Already-linked docs, so the inline picker filters them out / never re-offers them.
|
|
31
47
|
const chosenKeys = computed(() =>
|
|
32
48
|
linked.value.map((d) =>
|
|
@@ -71,7 +87,7 @@ async function attach(item: PendingContext) {
|
|
|
71
87
|
<InspectorSection
|
|
72
88
|
v-if="documents.available"
|
|
73
89
|
:title="t('documents.taskDocs.heading')"
|
|
74
|
-
:hint="
|
|
90
|
+
:hint="hint"
|
|
75
91
|
:count="linked.length"
|
|
76
92
|
>
|
|
77
93
|
<template #actions>
|
|
@@ -130,7 +146,7 @@ async function attach(item: PendingContext) {
|
|
|
130
146
|
</a>
|
|
131
147
|
</div>
|
|
132
148
|
<p v-else class="text-[11px] text-slate-500">
|
|
133
|
-
{{
|
|
149
|
+
{{ emptyHint }}
|
|
134
150
|
</p>
|
|
135
151
|
</InspectorSection>
|
|
136
152
|
</template>
|
|
@@ -31,19 +31,38 @@ import {
|
|
|
31
31
|
} from '~/utils/initiative'
|
|
32
32
|
import { interviewGatePhase } from '~/utils/interviewGate'
|
|
33
33
|
import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
|
|
34
|
+
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
34
35
|
|
|
35
36
|
const board = useBoardStore()
|
|
36
37
|
const initiatives = useInitiativesStore()
|
|
37
|
-
const execution = useExecutionStore()
|
|
38
38
|
const { t } = useI18n()
|
|
39
39
|
|
|
40
|
-
const { open, blockId, close } = useResultView('initiative-planning', {
|
|
40
|
+
const { open, blockId, instanceId, stepIndex, close } = useResultView('initiative-planning', {
|
|
41
41
|
onOpen: ({ blockId }) => void initiatives.load(blockId),
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
45
45
|
const initiative = computed(() => (blockId.value ? initiatives.forBlock(blockId.value) : null))
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The planning run + the interviewer step's run details, resolved through the shared seam so this
|
|
49
|
+
* window reports the same "which run is this / how did the model do" facts as every other agent
|
|
50
|
+
* window — including on the card/inspector entry point, which carries no step (see
|
|
51
|
+
* `useResultViewRunMeta`). `run` is the same instance the phase below reads.
|
|
52
|
+
*/
|
|
53
|
+
const {
|
|
54
|
+
instance: run,
|
|
55
|
+
step: metaStep,
|
|
56
|
+
instanceId: runId,
|
|
57
|
+
position,
|
|
58
|
+
totalSteps,
|
|
59
|
+
runFailed,
|
|
60
|
+
failureAt,
|
|
61
|
+
} = useResultViewRunMeta('initiative-planning', {
|
|
62
|
+
blockId: () => blockId.value,
|
|
63
|
+
instanceId: () => instanceId.value,
|
|
64
|
+
stepIndex: () => stepIndex.value,
|
|
65
|
+
})
|
|
47
66
|
|
|
48
67
|
/** Every interview exchange, with a stable key for the list + draft map. */
|
|
49
68
|
const questions = computed(() =>
|
|
@@ -184,7 +203,7 @@ async function onDiscard() {
|
|
|
184
203
|
icon-class="bg-indigo-500/15 text-indigo-300"
|
|
185
204
|
:title="initiative?.title ?? block?.title ?? t('initiative.planning.title')"
|
|
186
205
|
:subtitle="t('initiative.planning.subtitle')"
|
|
187
|
-
width="
|
|
206
|
+
width="4xl"
|
|
188
207
|
testid="initiative-planning-window"
|
|
189
208
|
@close="close"
|
|
190
209
|
>
|
|
@@ -194,79 +213,104 @@ async function onDiscard() {
|
|
|
194
213
|
</UBadge>
|
|
195
214
|
</template>
|
|
196
215
|
|
|
197
|
-
<div class="min-h-0 flex-1
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
<div class="flex min-h-0 flex-1">
|
|
217
|
+
<div class="min-w-0 flex-1 overflow-y-auto px-5 py-4">
|
|
218
|
+
<!-- No entity yet -->
|
|
219
|
+
<div
|
|
220
|
+
v-if="!initiative"
|
|
221
|
+
class="flex h-full flex-col items-center justify-center gap-2 text-center text-slate-400"
|
|
222
|
+
>
|
|
223
|
+
<UIcon name="i-lucide-messages-square" class="h-8 w-8 opacity-40" />
|
|
224
|
+
<p class="text-sm">{{ t('initiative.planning.empty') }}</p>
|
|
225
|
+
</div>
|
|
206
226
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
227
|
+
<template v-else>
|
|
228
|
+
<p class="mb-4 text-[13px] leading-relaxed text-slate-300">
|
|
229
|
+
{{ t('initiative.planning.intro') }}
|
|
230
|
+
</p>
|
|
211
231
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
232
|
+
<!-- A pass is running: the human is waiting on the planner. Without this the window is
|
|
233
|
+
byte-identical to the parked state and the submit reads as a no-op. -->
|
|
234
|
+
<InterviewGateNotice
|
|
235
|
+
v-if="phase === 'working'"
|
|
236
|
+
variant="working"
|
|
237
|
+
:title="t('initiative.planning.working')"
|
|
238
|
+
:hint="t('initiative.planning.workingHint')"
|
|
239
|
+
testid="initiative-planning-working"
|
|
240
|
+
/>
|
|
221
241
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
<!-- The planning run stopped before the interview settled — a dead end otherwise. -->
|
|
243
|
+
<InterviewGateNotice
|
|
244
|
+
v-else-if="phase === 'failed'"
|
|
245
|
+
variant="failed"
|
|
246
|
+
:title="t('initiative.planning.failed')"
|
|
247
|
+
:hint="t('initiative.planning.failedHint')"
|
|
248
|
+
testid="initiative-planning-failed"
|
|
249
|
+
/>
|
|
230
250
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
<!-- Planning was never started, so there is nothing to answer YET (distinct from
|
|
252
|
+
converged, which means the planner already has what it needs). -->
|
|
253
|
+
<div
|
|
254
|
+
v-else-if="phase === 'idle' && questions.length === 0"
|
|
255
|
+
class="rounded-lg border border-slate-800 bg-slate-950/40 p-4 text-center text-[13px] text-slate-400"
|
|
256
|
+
data-testid="initiative-planning-idle"
|
|
257
|
+
>
|
|
258
|
+
{{ t('initiative.planning.idle') }}
|
|
259
|
+
</div>
|
|
240
260
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
261
|
+
<!-- Converged / no pending questions -->
|
|
262
|
+
<div
|
|
263
|
+
v-else-if="phase === 'converged' || questions.length === 0"
|
|
264
|
+
class="rounded-lg border border-slate-800 bg-slate-950/40 p-4 text-center text-[13px] text-slate-400"
|
|
265
|
+
data-testid="initiative-planning-converged"
|
|
266
|
+
>
|
|
267
|
+
{{ t('initiative.planning.converged') }}
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<!-- Interview questions — the shared clarification surface (answer / not-relevant /
|
|
271
|
+
recommend), reused with the requirements-review window. -->
|
|
272
|
+
<ul v-else class="space-y-4">
|
|
273
|
+
<li
|
|
274
|
+
v-for="q in orderedQuestions"
|
|
275
|
+
:key="q.key"
|
|
276
|
+
data-testid="initiative-planning-question"
|
|
277
|
+
>
|
|
278
|
+
<ClarificationItem
|
|
279
|
+
v-model:answer="drafts[q.key]"
|
|
280
|
+
:prompt="q.question"
|
|
281
|
+
:dismissed="q.status === 'dismissed'"
|
|
282
|
+
:recommendation="q.recommendation"
|
|
283
|
+
:recommending="!!q.id && initiatives.recommending.has(q.id)"
|
|
284
|
+
:answer-placeholder="t('initiative.planning.answerPlaceholder')"
|
|
285
|
+
@persist="persist(q)"
|
|
286
|
+
@dismiss="setStatus(q, 'dismissed')"
|
|
287
|
+
@reopen="setStatus(q, 'open')"
|
|
288
|
+
@recommend="recommend(q)"
|
|
289
|
+
@use-recommendation="useRecommendation(q)"
|
|
290
|
+
/>
|
|
291
|
+
</li>
|
|
292
|
+
</ul>
|
|
293
|
+
</template>
|
|
294
|
+
</div>
|
|
249
295
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
</ul>
|
|
269
|
-
</template>
|
|
296
|
+
<!-- Run details: the shared run-metadata + LLM model-activity block every agent window
|
|
297
|
+
carries (step position, live duration, model, run id, calls + token usage). Resolved
|
|
298
|
+
through `useResultViewRunMeta`, so it is present on the card / inspector entry point
|
|
299
|
+
too — where this window carries no step index of its own. -->
|
|
300
|
+
<aside
|
|
301
|
+
v-if="metaStep"
|
|
302
|
+
data-testid="initiative-planning-run-meta"
|
|
303
|
+
class="hidden w-60 shrink-0 flex-col gap-4 overflow-y-auto border-s border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
|
|
304
|
+
>
|
|
305
|
+
<StepRunMeta
|
|
306
|
+
:step="metaStep"
|
|
307
|
+
:instance-id="runId"
|
|
308
|
+
:step-number="position"
|
|
309
|
+
:total-steps="totalSteps"
|
|
310
|
+
:run-failed="runFailed"
|
|
311
|
+
:failure-at="failureAt"
|
|
312
|
+
/>
|
|
313
|
+
</aside>
|
|
270
314
|
</div>
|
|
271
315
|
|
|
272
316
|
<!-- Action rail. The submit/plan-now pair shows only while the run is actually parked on the
|