arkaos 4.20.0 → 4.22.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 +1 -1
- package/VERSION +1 -1
- package/arka/SKILL.md +2 -1
- package/config/claude-agents/marta-cqo.md +9 -0
- package/config/standards/{claude-md-overlays → stack-rules}/laravel.md +11 -0
- package/config/standards/{claude-md-overlays → stack-rules}/node.md +10 -1
- package/config/standards/{claude-md-overlays → stack-rules}/nuxt.md +12 -1
- package/config/standards/stack-rules/php.md +13 -0
- package/config/standards/{claude-md-overlays → stack-rules}/python.md +5 -0
- package/config/standards/stack-rules/react.md +14 -0
- package/config/standards/stack-rules/vue.md +13 -0
- package/core/cognition/evolve.py +299 -0
- package/core/cognition/evolve_cli.py +55 -0
- package/core/governance/stop_lint.py +266 -0
- package/core/governance/stop_lint_telemetry.py +132 -0
- package/core/governance/tool_loop_check.py +111 -0
- package/core/hooks/stop.py +73 -22
- package/core/runtime/pricing.py +53 -19
- package/core/synapse/layers.py +49 -476
- package/core/synapse/layers_base.py +99 -0
- package/core/synapse/layers_kb.py +395 -0
- package/core/sync/content_syncer.py +79 -10
- package/dashboard/app/components/AgentDependencyGraph.vue +5 -5
- package/dashboard/app/components/AgentEditDrawer.vue +120 -52
- package/dashboard/app/components/AgentSuggestionsCard.vue +4 -2
- package/dashboard/app/components/ConfirmDialog.vue +2 -2
- package/dashboard/app/components/DashboardState.vue +5 -3
- package/dashboard/app/components/GlobalSearch.vue +11 -7
- package/dashboard/app/components/KeyboardShortcutsHelp.vue +8 -6
- package/dashboard/app/components/KnowledgeSourcesList.vue +13 -9
- package/dashboard/app/components/MarkdownEditor.vue +1 -1
- package/dashboard/app/components/NotificationsBell.vue +8 -4
- package/dashboard/app/components/OnboardingTour.vue +24 -11
- package/dashboard/app/components/PersonaCloneDialog.vue +15 -8
- package/dashboard/app/components/PersonaWizard.vue +105 -72
- package/dashboard/app/components/SidebarFavoritesWidget.vue +5 -5
- package/dashboard/app/components/SidebarStatsWidget.vue +3 -1
- package/dashboard/app/components/Terminal.vue +9 -8
- package/dashboard/app/components/TextDiff.vue +27 -15
- package/dashboard/app/composables/useActivityFeed.ts +3 -3
- package/dashboard/app/composables/useApi.ts +4 -2
- package/dashboard/app/composables/useConfirmDialog.ts +1 -1
- package/dashboard/app/composables/useFavorites.ts +8 -8
- package/dashboard/app/composables/useTerminalThemes.ts +13 -13
- package/dashboard/app/composables/useThemeColor.ts +7 -4
- package/dashboard/app/pages/agents/[id].vue +319 -159
- package/dashboard/app/pages/agents/compare.vue +152 -52
- package/dashboard/app/pages/agents/index.vue +68 -55
- package/dashboard/app/pages/agents/new.vue +125 -58
- package/dashboard/app/pages/audit.vue +13 -6
- package/dashboard/app/pages/budget.vue +20 -18
- package/dashboard/app/pages/commands.vue +26 -22
- package/dashboard/app/pages/departments/[dept].vue +67 -31
- package/dashboard/app/pages/departments/compare.vue +85 -27
- package/dashboard/app/pages/departments/index.vue +31 -7
- package/dashboard/app/pages/health.vue +19 -12
- package/dashboard/app/pages/knowledge/index.vue +190 -78
- package/dashboard/app/pages/models.vue +74 -28
- package/dashboard/app/pages/personas/[id].vue +322 -219
- package/dashboard/app/pages/personas/archetypes.vue +20 -6
- package/dashboard/app/pages/personas/compare-with-agent.vue +109 -39
- package/dashboard/app/pages/personas/compare.vue +196 -124
- package/dashboard/app/pages/personas/index.vue +67 -60
- package/dashboard/app/pages/personas/new.vue +1 -1
- package/dashboard/app/pages/settings.vue +1 -0
- package/dashboard/app/pages/tasks.vue +80 -38
- package/dashboard/app/pages/trash.vue +11 -9
- package/dashboard/app/pages/workflows.vue +29 -19
- package/dashboard/nuxt.config.ts +2 -1
- package/dashboard/package.json +5 -0
- package/departments/kb/skills/doc-extraction/SKILL.md +99 -0
- package/departments/kb/skills/doc-redaction/SKILL.md +103 -0
- package/departments/kb/skills/knowledge-ops/SKILL.md +88 -0
- package/departments/ops/skills/github-ops/SKILL.md +89 -0
- package/departments/ops/skills/terminal-ops/SKILL.md +83 -0
- package/installer/index.js +1 -1
- package/knowledge/commands-registry.json +16 -3
- package/knowledge/skills-manifest.json +66 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
|
@@ -18,9 +18,57 @@
|
|
|
18
18
|
|
|
19
19
|
import type { Persona } from '~/types'
|
|
20
20
|
|
|
21
|
+
// Shape of the agent payload this drawer edits. Mirrors the safe-to-mutate
|
|
22
|
+
// fields returned by GET /api/agents/{id} — everything optional except id,
|
|
23
|
+
// because the backend only includes populated keys.
|
|
24
|
+
interface AgentEditable {
|
|
25
|
+
id: string
|
|
26
|
+
name?: string
|
|
27
|
+
role?: string
|
|
28
|
+
department?: string
|
|
29
|
+
tier?: number
|
|
30
|
+
mental_models?: { primary?: string[], secondary?: string[] }
|
|
31
|
+
frameworks?: string[]
|
|
32
|
+
expertise_domains?: string[]
|
|
33
|
+
expertise_depth?: string
|
|
34
|
+
expertise_years?: number
|
|
35
|
+
communication?: {
|
|
36
|
+
tone?: string
|
|
37
|
+
vocabulary_level?: string
|
|
38
|
+
preferred_format?: string
|
|
39
|
+
language?: string
|
|
40
|
+
avoid?: string[]
|
|
41
|
+
}
|
|
42
|
+
linked_personas?: string[]
|
|
43
|
+
bio_md?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Shape of the AI-generated draft returned by POST /api/agents/draft.
|
|
47
|
+
// Values are unknown at the boundary — applyRewrite validates each one
|
|
48
|
+
// (Array.isArray / typeof) before assigning into the typed draft.
|
|
49
|
+
interface RewriteDraft {
|
|
50
|
+
expertise?: {
|
|
51
|
+
domains?: unknown
|
|
52
|
+
frameworks?: unknown
|
|
53
|
+
depth?: unknown
|
|
54
|
+
years_equivalent?: unknown
|
|
55
|
+
}
|
|
56
|
+
mental_models?: {
|
|
57
|
+
primary?: unknown
|
|
58
|
+
secondary?: unknown
|
|
59
|
+
}
|
|
60
|
+
communication?: {
|
|
61
|
+
tone?: unknown
|
|
62
|
+
vocabulary_level?: unknown
|
|
63
|
+
preferred_format?: unknown
|
|
64
|
+
language?: unknown
|
|
65
|
+
avoid?: unknown
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
21
69
|
const props = defineProps<{
|
|
22
70
|
modelValue: boolean
|
|
23
|
-
agent:
|
|
71
|
+
agent: AgentEditable | null
|
|
24
72
|
}>()
|
|
25
73
|
|
|
26
74
|
const emit = defineEmits<{
|
|
@@ -35,17 +83,17 @@ const confirmDialog = useConfirmDialog()
|
|
|
35
83
|
// Persona list — for the linked_personas multi-select.
|
|
36
84
|
const { data: personasData } = fetchApi<{ personas: Persona[] }>('/api/personas')
|
|
37
85
|
const personaOptions = computed(() =>
|
|
38
|
-
(personasData.value?.personas ?? []).map(
|
|
86
|
+
(personasData.value?.personas ?? []).map(p => ({
|
|
39
87
|
label: p.name + (p.title ? ` — ${p.title}` : ''),
|
|
40
|
-
value: p.id
|
|
41
|
-
}))
|
|
88
|
+
value: p.id
|
|
89
|
+
}))
|
|
42
90
|
)
|
|
43
91
|
|
|
44
92
|
interface AgentDraft {
|
|
45
93
|
name: string
|
|
46
94
|
role: string
|
|
47
95
|
tier: number
|
|
48
|
-
mental_models: { primary: string[]
|
|
96
|
+
mental_models: { primary: string[], secondary: string[] }
|
|
49
97
|
frameworks: string[]
|
|
50
98
|
expertise_domains: string[]
|
|
51
99
|
expertise_depth: string
|
|
@@ -75,7 +123,7 @@ watch(
|
|
|
75
123
|
tier: agent.tier ?? 2,
|
|
76
124
|
mental_models: {
|
|
77
125
|
primary: agent.mental_models?.primary ?? [],
|
|
78
|
-
secondary: agent.mental_models?.secondary ?? []
|
|
126
|
+
secondary: agent.mental_models?.secondary ?? []
|
|
79
127
|
},
|
|
80
128
|
frameworks: agent.frameworks ?? [],
|
|
81
129
|
expertise_domains: agent.expertise_domains ?? [],
|
|
@@ -86,10 +134,10 @@ watch(
|
|
|
86
134
|
vocabulary_level: agent.communication?.vocabulary_level ?? '',
|
|
87
135
|
preferred_format: agent.communication?.preferred_format ?? '',
|
|
88
136
|
language: agent.communication?.language ?? '',
|
|
89
|
-
avoid: agent.communication?.avoid ?? []
|
|
137
|
+
avoid: agent.communication?.avoid ?? []
|
|
90
138
|
},
|
|
91
139
|
linked_personas: agent.linked_personas ?? [],
|
|
92
|
-
bio_md: agent.bio_md ?? ''
|
|
140
|
+
bio_md: agent.bio_md ?? ''
|
|
93
141
|
}
|
|
94
142
|
dirty.value = false
|
|
95
143
|
} else if (!open) {
|
|
@@ -97,7 +145,7 @@ watch(
|
|
|
97
145
|
dirty.value = false
|
|
98
146
|
}
|
|
99
147
|
},
|
|
100
|
-
{ immediate: true }
|
|
148
|
+
{ immediate: true }
|
|
101
149
|
)
|
|
102
150
|
|
|
103
151
|
function markDirty() {
|
|
@@ -109,7 +157,7 @@ function listToCsv(list: string[]): string {
|
|
|
109
157
|
}
|
|
110
158
|
|
|
111
159
|
function csvToList(value: string): string[] {
|
|
112
|
-
return value.split(',').map(
|
|
160
|
+
return value.split(',').map(s => s.trim()).filter(Boolean)
|
|
113
161
|
}
|
|
114
162
|
|
|
115
163
|
// PR81 v2.99.0 — AI list-field suggester.
|
|
@@ -129,14 +177,14 @@ async function rewriteFromDescription() {
|
|
|
129
177
|
toast.add({
|
|
130
178
|
title: 'Add more detail',
|
|
131
179
|
description: 'Describe the agent in at least a sentence or two.',
|
|
132
|
-
color: 'warning'
|
|
180
|
+
color: 'warning'
|
|
133
181
|
})
|
|
134
182
|
return
|
|
135
183
|
}
|
|
136
184
|
rewriting.value = true
|
|
137
185
|
try {
|
|
138
186
|
const res = await $fetch<{
|
|
139
|
-
draft:
|
|
187
|
+
draft: RewriteDraft
|
|
140
188
|
provider_name: string
|
|
141
189
|
error?: string
|
|
142
190
|
}>(`${apiBase}/api/agents/draft`, {
|
|
@@ -146,8 +194,8 @@ async function rewriteFromDescription() {
|
|
|
146
194
|
name: draft.value.name,
|
|
147
195
|
role: draft.value.role,
|
|
148
196
|
department: props.agent.department,
|
|
149
|
-
tier: draft.value.tier
|
|
150
|
-
}
|
|
197
|
+
tier: draft.value.tier
|
|
198
|
+
}
|
|
151
199
|
})
|
|
152
200
|
if (res.error) throw new Error(res.error)
|
|
153
201
|
applyRewrite(res.draft)
|
|
@@ -156,7 +204,7 @@ async function rewriteFromDescription() {
|
|
|
156
204
|
title: 'Rewritten',
|
|
157
205
|
description: `via ${res.provider_name} — review and Save when ready.`,
|
|
158
206
|
color: 'success',
|
|
159
|
-
icon: 'i-lucide-sparkles'
|
|
207
|
+
icon: 'i-lucide-sparkles'
|
|
160
208
|
})
|
|
161
209
|
rewriteOpen.value = false
|
|
162
210
|
rewriteDescription.value = ''
|
|
@@ -164,14 +212,14 @@ async function rewriteFromDescription() {
|
|
|
164
212
|
toast.add({
|
|
165
213
|
title: 'Rewrite failed',
|
|
166
214
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
167
|
-
color: 'error'
|
|
215
|
+
color: 'error'
|
|
168
216
|
})
|
|
169
217
|
} finally {
|
|
170
218
|
rewriting.value = false
|
|
171
219
|
}
|
|
172
220
|
}
|
|
173
221
|
|
|
174
|
-
function applyRewrite(d:
|
|
222
|
+
function applyRewrite(d: RewriteDraft) {
|
|
175
223
|
if (!draft.value) return
|
|
176
224
|
// NOTE: identity (id, department) stays. Behavioural DNA is intentionally
|
|
177
225
|
// not editable here, so we do not touch it. We rewrite the SAFE fields
|
|
@@ -212,10 +260,10 @@ async function suggestString(field: StringField) {
|
|
|
212
260
|
name: props.agent.name,
|
|
213
261
|
role: props.agent.role,
|
|
214
262
|
department: props.agent.department,
|
|
215
|
-
current
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
263
|
+
current
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
219
267
|
)
|
|
220
268
|
if (res.error) throw new Error(res.error)
|
|
221
269
|
if (field === 'tone') draft.value.communication.tone = res.value
|
|
@@ -225,13 +273,13 @@ async function suggestString(field: StringField) {
|
|
|
225
273
|
title: 'Generated',
|
|
226
274
|
description: `via ${res.provider_name}`,
|
|
227
275
|
color: 'success',
|
|
228
|
-
icon: 'i-lucide-sparkles'
|
|
276
|
+
icon: 'i-lucide-sparkles'
|
|
229
277
|
})
|
|
230
278
|
} catch (err) {
|
|
231
279
|
toast.add({
|
|
232
280
|
title: 'Generate failed',
|
|
233
281
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
234
|
-
color: 'error'
|
|
282
|
+
color: 'error'
|
|
235
283
|
})
|
|
236
284
|
} finally {
|
|
237
285
|
suggestingString.value = null
|
|
@@ -264,19 +312,19 @@ async function suggest(field: SuggestField) {
|
|
|
264
312
|
name: props.agent.name,
|
|
265
313
|
role: props.agent.role,
|
|
266
314
|
department: props.agent.department,
|
|
267
|
-
current
|
|
268
|
-
}
|
|
269
|
-
}
|
|
315
|
+
current
|
|
316
|
+
}
|
|
317
|
+
}
|
|
270
318
|
})
|
|
271
319
|
if (res.error) throw new Error(res.error)
|
|
272
320
|
const additions = (res.suggestions ?? []).filter(
|
|
273
|
-
|
|
321
|
+
s => !current.some(c => c.toLowerCase() === s.toLowerCase())
|
|
274
322
|
)
|
|
275
323
|
if (additions.length === 0) {
|
|
276
324
|
toast.add({
|
|
277
325
|
title: 'No new suggestions',
|
|
278
326
|
description: 'The model returned only items you already have.',
|
|
279
|
-
color: 'info'
|
|
327
|
+
color: 'info'
|
|
280
328
|
})
|
|
281
329
|
return
|
|
282
330
|
}
|
|
@@ -295,13 +343,13 @@ async function suggest(field: SuggestField) {
|
|
|
295
343
|
title: `Added ${additions.length} suggestion${additions.length === 1 ? '' : 's'}`,
|
|
296
344
|
description: `via ${res.provider_name}`,
|
|
297
345
|
color: 'success',
|
|
298
|
-
icon: 'i-lucide-sparkles'
|
|
346
|
+
icon: 'i-lucide-sparkles'
|
|
299
347
|
})
|
|
300
348
|
} catch (err) {
|
|
301
349
|
toast.add({
|
|
302
350
|
title: 'Suggestion failed',
|
|
303
351
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
304
|
-
color: 'error'
|
|
352
|
+
color: 'error'
|
|
305
353
|
})
|
|
306
354
|
} finally {
|
|
307
355
|
suggestingField.value = null
|
|
@@ -321,11 +369,11 @@ async function save() {
|
|
|
321
369
|
expertise_domains: draft.value.expertise_domains,
|
|
322
370
|
expertise: {
|
|
323
371
|
depth: draft.value.expertise_depth,
|
|
324
|
-
years_equivalent: draft.value.expertise_years
|
|
372
|
+
years_equivalent: draft.value.expertise_years
|
|
325
373
|
},
|
|
326
374
|
communication: draft.value.communication,
|
|
327
375
|
linked_personas: draft.value.linked_personas,
|
|
328
|
-
bio_md: draft.value.bio_md
|
|
376
|
+
bio_md: draft.value.bio_md
|
|
329
377
|
}
|
|
330
378
|
const res = await $fetch<{
|
|
331
379
|
id: string
|
|
@@ -334,7 +382,7 @@ async function save() {
|
|
|
334
382
|
error?: string
|
|
335
383
|
}>(`${apiBase}/api/agents/${props.agent.id}`, {
|
|
336
384
|
method: 'PUT',
|
|
337
|
-
body: payload
|
|
385
|
+
body: payload
|
|
338
386
|
})
|
|
339
387
|
if (res.error) throw new Error(res.error)
|
|
340
388
|
toast.add({
|
|
@@ -342,7 +390,7 @@ async function save() {
|
|
|
342
390
|
description: res.yaml_path
|
|
343
391
|
? `Wrote ${res.yaml_path.split('/').slice(-3).join('/')}`
|
|
344
392
|
: 'YAML updated',
|
|
345
|
-
color: 'success'
|
|
393
|
+
color: 'success'
|
|
346
394
|
})
|
|
347
395
|
dirty.value = false
|
|
348
396
|
emit('saved')
|
|
@@ -351,7 +399,7 @@ async function save() {
|
|
|
351
399
|
toast.add({
|
|
352
400
|
title: 'Save failed',
|
|
353
401
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
354
|
-
color: 'error'
|
|
402
|
+
color: 'error'
|
|
355
403
|
})
|
|
356
404
|
} finally {
|
|
357
405
|
saving.value = false
|
|
@@ -365,7 +413,7 @@ async function tryClose() {
|
|
|
365
413
|
description: 'Any changes you made to this agent will be lost.',
|
|
366
414
|
confirmLabel: 'Discard',
|
|
367
415
|
cancelLabel: 'Keep editing',
|
|
368
|
-
variant: 'danger'
|
|
416
|
+
variant: 'danger'
|
|
369
417
|
})
|
|
370
418
|
if (!ok) return
|
|
371
419
|
}
|
|
@@ -376,18 +424,18 @@ const tierOptions = [
|
|
|
376
424
|
{ label: 'Tier 0 — C-Suite', value: 0 },
|
|
377
425
|
{ label: 'Tier 1 — Squad Lead', value: 1 },
|
|
378
426
|
{ label: 'Tier 2 — Specialist', value: 2 },
|
|
379
|
-
{ label: 'Tier 3 — Support', value: 3 }
|
|
427
|
+
{ label: 'Tier 3 — Support', value: 3 }
|
|
380
428
|
]
|
|
381
429
|
const depthOptions = [
|
|
382
430
|
{ label: 'Intermediate', value: 'intermediate' },
|
|
383
431
|
{ label: 'Advanced', value: 'advanced' },
|
|
384
432
|
{ label: 'Expert', value: 'expert' },
|
|
385
|
-
{ label: 'Master', value: 'master' }
|
|
433
|
+
{ label: 'Master', value: 'master' }
|
|
386
434
|
]
|
|
387
435
|
const vocabOptions = [
|
|
388
436
|
{ label: 'Lay (no jargon)', value: 'lay' },
|
|
389
437
|
{ label: 'Specialist (industry terms)', value: 'specialist' },
|
|
390
|
-
{ label: 'Expert (research-level)', value: 'expert' }
|
|
438
|
+
{ label: 'Expert (research-level)', value: 'expert' }
|
|
391
439
|
]
|
|
392
440
|
</script>
|
|
393
441
|
|
|
@@ -401,13 +449,15 @@ const vocabOptions = [
|
|
|
401
449
|
<UCard
|
|
402
450
|
:ui="{
|
|
403
451
|
root: 'h-full flex flex-col rounded-none',
|
|
404
|
-
body: 'flex-1 overflow-y-auto'
|
|
452
|
+
body: 'flex-1 overflow-y-auto'
|
|
405
453
|
}"
|
|
406
454
|
>
|
|
407
455
|
<template #header>
|
|
408
456
|
<div class="flex items-center justify-between gap-3">
|
|
409
457
|
<div>
|
|
410
|
-
<h2 class="text-xl font-bold">
|
|
458
|
+
<h2 class="text-xl font-bold">
|
|
459
|
+
Edit agent
|
|
460
|
+
</h2>
|
|
411
461
|
<p class="text-sm text-muted mt-0.5">
|
|
412
462
|
{{ props.agent?.name }}
|
|
413
463
|
<span class="text-xs text-muted/60 ml-1">— {{ props.agent?.id }}</span>
|
|
@@ -479,7 +529,9 @@ const vocabOptions = [
|
|
|
479
529
|
</p>
|
|
480
530
|
|
|
481
531
|
<section class="space-y-3">
|
|
482
|
-
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
532
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
533
|
+
Identity
|
|
534
|
+
</h3>
|
|
483
535
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
484
536
|
<UFormField label="Name">
|
|
485
537
|
<UInput v-model="draft.name" class="w-full" @update:model-value="markDirty" />
|
|
@@ -500,7 +552,9 @@ const vocabOptions = [
|
|
|
500
552
|
|
|
501
553
|
<section class="space-y-3">
|
|
502
554
|
<div class="flex items-center justify-between">
|
|
503
|
-
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
555
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
556
|
+
Mental models
|
|
557
|
+
</h3>
|
|
504
558
|
<UButton
|
|
505
559
|
label="Suggest with AI"
|
|
506
560
|
icon="i-lucide-sparkles"
|
|
@@ -515,21 +569,23 @@ const vocabOptions = [
|
|
|
515
569
|
<UFormField label="Primary" help="comma-separated">
|
|
516
570
|
<UInput
|
|
517
571
|
:model-value="listToCsv(draft.mental_models.primary)"
|
|
518
|
-
@update:model-value="(v: string) => { if (draft) { draft.mental_models.primary = csvToList(v); markDirty() } }"
|
|
519
572
|
class="w-full"
|
|
573
|
+
@update:model-value="(v: string) => { if (draft) { draft.mental_models.primary = csvToList(v); markDirty() } }"
|
|
520
574
|
/>
|
|
521
575
|
</UFormField>
|
|
522
576
|
<UFormField label="Secondary" help="comma-separated">
|
|
523
577
|
<UInput
|
|
524
578
|
:model-value="listToCsv(draft.mental_models.secondary)"
|
|
525
|
-
@update:model-value="(v: string) => { if (draft) { draft.mental_models.secondary = csvToList(v); markDirty() } }"
|
|
526
579
|
class="w-full"
|
|
580
|
+
@update:model-value="(v: string) => { if (draft) { draft.mental_models.secondary = csvToList(v); markDirty() } }"
|
|
527
581
|
/>
|
|
528
582
|
</UFormField>
|
|
529
583
|
</section>
|
|
530
584
|
|
|
531
585
|
<section class="space-y-3">
|
|
532
|
-
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
586
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
587
|
+
Expertise
|
|
588
|
+
</h3>
|
|
533
589
|
<UFormField label="Domains" help="comma-separated">
|
|
534
590
|
<template #hint>
|
|
535
591
|
<UButton
|
|
@@ -545,8 +601,8 @@ const vocabOptions = [
|
|
|
545
601
|
</template>
|
|
546
602
|
<UInput
|
|
547
603
|
:model-value="listToCsv(draft.expertise_domains)"
|
|
548
|
-
@update:model-value="(v: string) => { if (draft) { draft.expertise_domains = csvToList(v); markDirty() } }"
|
|
549
604
|
class="w-full"
|
|
605
|
+
@update:model-value="(v: string) => { if (draft) { draft.expertise_domains = csvToList(v); markDirty() } }"
|
|
550
606
|
/>
|
|
551
607
|
</UFormField>
|
|
552
608
|
<UFormField label="Frameworks" help="comma-separated">
|
|
@@ -564,8 +620,8 @@ const vocabOptions = [
|
|
|
564
620
|
</template>
|
|
565
621
|
<UInput
|
|
566
622
|
:model-value="listToCsv(draft.frameworks)"
|
|
567
|
-
@update:model-value="(v: string) => { if (draft) { draft.frameworks = csvToList(v); markDirty() } }"
|
|
568
623
|
class="w-full"
|
|
624
|
+
@update:model-value="(v: string) => { if (draft) { draft.frameworks = csvToList(v); markDirty() } }"
|
|
569
625
|
/>
|
|
570
626
|
</UFormField>
|
|
571
627
|
<div class="grid grid-cols-2 gap-3">
|
|
@@ -591,7 +647,9 @@ const vocabOptions = [
|
|
|
591
647
|
</section>
|
|
592
648
|
|
|
593
649
|
<section class="space-y-3">
|
|
594
|
-
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
650
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted">
|
|
651
|
+
Communication
|
|
652
|
+
</h3>
|
|
595
653
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
596
654
|
<UFormField label="Tone">
|
|
597
655
|
<template #hint>
|
|
@@ -632,7 +690,12 @@ const vocabOptions = [
|
|
|
632
690
|
<UInput v-model="draft.communication.preferred_format" class="w-full" @update:model-value="markDirty" />
|
|
633
691
|
</UFormField>
|
|
634
692
|
<UFormField label="Language">
|
|
635
|
-
<UInput
|
|
693
|
+
<UInput
|
|
694
|
+
v-model="draft.communication.language"
|
|
695
|
+
placeholder="en, pt"
|
|
696
|
+
class="w-full"
|
|
697
|
+
@update:model-value="markDirty"
|
|
698
|
+
/>
|
|
636
699
|
</UFormField>
|
|
637
700
|
</div>
|
|
638
701
|
<UFormField label="Avoid (phrases)" help="comma-separated">
|
|
@@ -650,8 +713,8 @@ const vocabOptions = [
|
|
|
650
713
|
</template>
|
|
651
714
|
<UInput
|
|
652
715
|
:model-value="listToCsv(draft.communication.avoid)"
|
|
653
|
-
@update:model-value="(v: string) => { if (draft) { draft.communication.avoid = csvToList(v); markDirty() } }"
|
|
654
716
|
class="w-full"
|
|
717
|
+
@update:model-value="(v: string) => { if (draft) { draft.communication.avoid = csvToList(v); markDirty() } }"
|
|
655
718
|
/>
|
|
656
719
|
</UFormField>
|
|
657
720
|
</section>
|
|
@@ -700,7 +763,12 @@ const vocabOptions = [
|
|
|
700
763
|
</span>
|
|
701
764
|
<span v-else class="text-xs text-muted">No changes</span>
|
|
702
765
|
<div class="flex gap-2">
|
|
703
|
-
<UButton
|
|
766
|
+
<UButton
|
|
767
|
+
label="Cancel"
|
|
768
|
+
variant="ghost"
|
|
769
|
+
:disabled="saving"
|
|
770
|
+
@click="tryClose"
|
|
771
|
+
/>
|
|
704
772
|
<UButton
|
|
705
773
|
label="Save"
|
|
706
774
|
icon="i-lucide-check"
|
|
@@ -13,7 +13,7 @@ interface Suggestion {
|
|
|
13
13
|
|
|
14
14
|
const { fetchApi } = useApi()
|
|
15
15
|
const { data, status } = fetchApi<{ suggestions: Suggestion[], total_gaps: number }>(
|
|
16
|
-
'/api/agents/suggestions?limit=6'
|
|
16
|
+
'/api/agents/suggestions?limit=6'
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
const suggestions = computed<Suggestion[]>(() => data.value?.suggestions ?? [])
|
|
@@ -28,7 +28,9 @@ function severityColor(s: string): 'error' | 'warning' | 'neutral' {
|
|
|
28
28
|
<template #header>
|
|
29
29
|
<div class="flex items-center justify-between">
|
|
30
30
|
<div>
|
|
31
|
-
<h3 class="text-lg font-semibold">
|
|
31
|
+
<h3 class="text-lg font-semibold">
|
|
32
|
+
What's missing?
|
|
33
|
+
</h3>
|
|
32
34
|
<p class="text-xs text-muted mt-0.5">
|
|
33
35
|
{{ data?.total_gaps }} gap{{ data?.total_gaps === 1 ? '' : 's' }} across departments. Showing top {{ suggestions.length }}.
|
|
34
36
|
</p>
|
|
@@ -25,7 +25,7 @@ const props = withDefaults(defineProps<ConfirmDialogProps>(), {
|
|
|
25
25
|
description: '',
|
|
26
26
|
confirmLabel: 'Confirm',
|
|
27
27
|
cancelLabel: 'Cancel',
|
|
28
|
-
variant: 'default'
|
|
28
|
+
variant: 'default'
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
const emits = defineEmits<{
|
|
@@ -33,7 +33,7 @@ const emits = defineEmits<{
|
|
|
33
33
|
}>()
|
|
34
34
|
|
|
35
35
|
const confirmColor = computed(() =>
|
|
36
|
-
props.variant === 'danger' ? 'error' : 'primary'
|
|
36
|
+
props.variant === 'danger' ? 'error' : 'primary'
|
|
37
37
|
)
|
|
38
38
|
</script>
|
|
39
39
|
|
|
@@ -45,13 +45,13 @@ interface Props {
|
|
|
45
45
|
loadingLabel?: string
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
withDefaults(defineProps<Props>(), {
|
|
49
49
|
error: null,
|
|
50
50
|
empty: false,
|
|
51
51
|
emptyTitle: 'No data',
|
|
52
52
|
emptyDescription: '',
|
|
53
53
|
emptyIcon: 'i-lucide-inbox',
|
|
54
|
-
loadingLabel: 'Loading'
|
|
54
|
+
loadingLabel: 'Loading'
|
|
55
55
|
})
|
|
56
56
|
</script>
|
|
57
57
|
|
|
@@ -97,7 +97,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
97
97
|
>
|
|
98
98
|
<slot name="empty">
|
|
99
99
|
<UIcon :name="emptyIcon" class="size-16 text-muted" />
|
|
100
|
-
<h3 class="text-lg font-semibold text-highlighted">
|
|
100
|
+
<h3 class="text-lg font-semibold text-highlighted">
|
|
101
|
+
{{ emptyTitle }}
|
|
102
|
+
</h3>
|
|
101
103
|
<p
|
|
102
104
|
v-if="emptyDescription"
|
|
103
105
|
class="text-sm text-muted text-center max-w-md"
|
|
@@ -36,7 +36,7 @@ watch(query, (q) => {
|
|
|
36
36
|
try {
|
|
37
37
|
const res = await $fetch<{ results: SearchResult[] }>(
|
|
38
38
|
`${apiBase}/api/search`,
|
|
39
|
-
{ query: { q, limit: 20 }, signal: abortCtl.signal }
|
|
39
|
+
{ query: { q, limit: 20 }, signal: abortCtl.signal }
|
|
40
40
|
)
|
|
41
41
|
results.value = res.results ?? []
|
|
42
42
|
} catch {
|
|
@@ -60,10 +60,10 @@ function pickResult(r: SearchResult) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const kindMeta: Record<SearchResult['kind'], { icon: string, color: string }> = {
|
|
63
|
-
agent:
|
|
64
|
-
persona:
|
|
65
|
-
department: { icon: 'i-lucide-folder-tree',
|
|
66
|
-
command:
|
|
63
|
+
agent: { icon: 'i-lucide-users', color: 'text-primary' },
|
|
64
|
+
persona: { icon: 'i-lucide-user-plus', color: 'text-emerald-500' },
|
|
65
|
+
department: { icon: 'i-lucide-folder-tree', color: 'text-amber-500' },
|
|
66
|
+
command: { icon: 'i-lucide-terminal', color: 'text-blue-500' }
|
|
67
67
|
}
|
|
68
68
|
</script>
|
|
69
69
|
|
|
@@ -122,8 +122,12 @@ const kindMeta: Record<SearchResult['kind'], { icon: string, color: string }> =
|
|
|
122
122
|
:class="kindMeta[r.kind].color"
|
|
123
123
|
/>
|
|
124
124
|
<div class="min-w-0 flex-1">
|
|
125
|
-
<p class="text-sm font-semibold truncate">
|
|
126
|
-
|
|
125
|
+
<p class="text-sm font-semibold truncate">
|
|
126
|
+
{{ r.label }}
|
|
127
|
+
</p>
|
|
128
|
+
<p class="text-xs text-muted truncate">
|
|
129
|
+
{{ r.sublabel }}
|
|
130
|
+
</p>
|
|
127
131
|
</div>
|
|
128
132
|
<UBadge
|
|
129
133
|
:label="r.kind"
|
|
@@ -19,17 +19,17 @@ const groups = [
|
|
|
19
19
|
{ keys: ['g', 'k'], label: 'Knowledge' },
|
|
20
20
|
{ keys: ['g', 'e'], label: 'Health' },
|
|
21
21
|
{ keys: ['g', 'r'], label: 'Trash' },
|
|
22
|
-
{ keys: ['g', 's'], label: 'Settings' }
|
|
23
|
-
]
|
|
22
|
+
{ keys: ['g', 's'], label: 'Settings' }
|
|
23
|
+
]
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
title: 'Actions',
|
|
27
27
|
items: [
|
|
28
28
|
{ keys: ['/'], label: 'Open global search' },
|
|
29
29
|
{ keys: ['n'], label: 'New (context-aware — agent / persona)' },
|
|
30
|
-
{ keys: ['?'], label: 'Toggle this help' }
|
|
31
|
-
]
|
|
32
|
-
}
|
|
30
|
+
{ keys: ['?'], label: 'Toggle this help' }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
33
|
]
|
|
34
34
|
</script>
|
|
35
35
|
|
|
@@ -44,7 +44,9 @@ const groups = [
|
|
|
44
44
|
<template #header>
|
|
45
45
|
<div class="flex items-center justify-between gap-3">
|
|
46
46
|
<div>
|
|
47
|
-
<h2 class="text-lg font-bold">
|
|
47
|
+
<h2 class="text-lg font-bold">
|
|
48
|
+
Keyboard shortcuts
|
|
49
|
+
</h2>
|
|
48
50
|
<p class="text-xs text-muted mt-0.5">
|
|
49
51
|
Press <kbd class="px-1.5 py-0.5 rounded bg-elevated/50 text-xs font-mono">?</kbd> anywhere to toggle this.
|
|
50
52
|
</p>
|
|
@@ -34,14 +34,16 @@ const pageSize = 15
|
|
|
34
34
|
const filtered = computed(() => {
|
|
35
35
|
const q = search.value.toLowerCase().trim()
|
|
36
36
|
if (!q) return sources.value
|
|
37
|
-
return sources.value.filter(
|
|
37
|
+
return sources.value.filter(s => s.source.toLowerCase().includes(q))
|
|
38
38
|
})
|
|
39
39
|
const totalPages = computed(() => Math.max(1, Math.ceil(filtered.value.length / pageSize)))
|
|
40
40
|
const paged = computed(() =>
|
|
41
|
-
filtered.value.slice((page.value - 1) * pageSize, page.value * pageSize)
|
|
41
|
+
filtered.value.slice((page.value - 1) * pageSize, page.value * pageSize)
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
watch(search, () => {
|
|
44
|
+
watch(search, () => {
|
|
45
|
+
page.value = 1
|
|
46
|
+
})
|
|
45
47
|
|
|
46
48
|
async function remove(row: SourceRow) {
|
|
47
49
|
const ok = await confirmDialog({
|
|
@@ -49,26 +51,26 @@ async function remove(row: SourceRow) {
|
|
|
49
51
|
description: `Removes ${row.chunks} chunk${row.chunks === 1 ? '' : 's'} from the vector store. This cannot be undone.`,
|
|
50
52
|
confirmLabel: 'Delete',
|
|
51
53
|
cancelLabel: 'Cancel',
|
|
52
|
-
variant: 'danger'
|
|
54
|
+
variant: 'danger'
|
|
53
55
|
})
|
|
54
56
|
if (!ok) return
|
|
55
57
|
try {
|
|
56
58
|
const res = await $fetch<{ deleted?: number, error?: string }>(
|
|
57
59
|
`${apiBase}/api/knowledge/sources`,
|
|
58
|
-
{ method: 'DELETE', query: { source: row.source } }
|
|
60
|
+
{ method: 'DELETE', query: { source: row.source } }
|
|
59
61
|
)
|
|
60
62
|
if (res.error) throw new Error(res.error)
|
|
61
63
|
toast.add({
|
|
62
64
|
title: `Removed ${res.deleted ?? 0} chunks`,
|
|
63
65
|
description: row.source,
|
|
64
|
-
color: 'success'
|
|
66
|
+
color: 'success'
|
|
65
67
|
})
|
|
66
68
|
await refresh()
|
|
67
69
|
} catch (err) {
|
|
68
70
|
toast.add({
|
|
69
71
|
title: 'Delete failed',
|
|
70
72
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
71
|
-
color: 'error'
|
|
73
|
+
color: 'error'
|
|
72
74
|
})
|
|
73
75
|
}
|
|
74
76
|
}
|
|
@@ -100,7 +102,9 @@ const typeColorMap: Record<string, 'error' | 'primary' | 'warning' | 'success' |
|
|
|
100
102
|
<template #header>
|
|
101
103
|
<div class="flex items-center justify-between gap-3">
|
|
102
104
|
<div>
|
|
103
|
-
<h3 class="text-lg font-bold">
|
|
105
|
+
<h3 class="text-lg font-bold">
|
|
106
|
+
Indexed sources
|
|
107
|
+
</h3>
|
|
104
108
|
<p class="text-xs text-muted mt-0.5">
|
|
105
109
|
Click any source to view its transcript, video, and knowledge.
|
|
106
110
|
<span v-if="data?.total">{{ data.total }} total.</span>
|
|
@@ -112,7 +116,7 @@ const typeColorMap: Record<string, 'error' | 'primary' | 'warning' | 'success' |
|
|
|
112
116
|
size="sm"
|
|
113
117
|
aria-label="Refresh"
|
|
114
118
|
:loading="status === 'pending'"
|
|
115
|
-
@click="refresh"
|
|
119
|
+
@click="() => refresh()"
|
|
116
120
|
/>
|
|
117
121
|
</div>
|
|
118
122
|
</template>
|