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.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/VERSION +1 -1
  3. package/arka/SKILL.md +2 -1
  4. package/config/claude-agents/marta-cqo.md +9 -0
  5. package/config/standards/{claude-md-overlays → stack-rules}/laravel.md +11 -0
  6. package/config/standards/{claude-md-overlays → stack-rules}/node.md +10 -1
  7. package/config/standards/{claude-md-overlays → stack-rules}/nuxt.md +12 -1
  8. package/config/standards/stack-rules/php.md +13 -0
  9. package/config/standards/{claude-md-overlays → stack-rules}/python.md +5 -0
  10. package/config/standards/stack-rules/react.md +14 -0
  11. package/config/standards/stack-rules/vue.md +13 -0
  12. package/core/cognition/evolve.py +299 -0
  13. package/core/cognition/evolve_cli.py +55 -0
  14. package/core/governance/stop_lint.py +266 -0
  15. package/core/governance/stop_lint_telemetry.py +132 -0
  16. package/core/governance/tool_loop_check.py +111 -0
  17. package/core/hooks/stop.py +73 -22
  18. package/core/runtime/pricing.py +53 -19
  19. package/core/synapse/layers.py +49 -476
  20. package/core/synapse/layers_base.py +99 -0
  21. package/core/synapse/layers_kb.py +395 -0
  22. package/core/sync/content_syncer.py +79 -10
  23. package/dashboard/app/components/AgentDependencyGraph.vue +5 -5
  24. package/dashboard/app/components/AgentEditDrawer.vue +120 -52
  25. package/dashboard/app/components/AgentSuggestionsCard.vue +4 -2
  26. package/dashboard/app/components/ConfirmDialog.vue +2 -2
  27. package/dashboard/app/components/DashboardState.vue +5 -3
  28. package/dashboard/app/components/GlobalSearch.vue +11 -7
  29. package/dashboard/app/components/KeyboardShortcutsHelp.vue +8 -6
  30. package/dashboard/app/components/KnowledgeSourcesList.vue +13 -9
  31. package/dashboard/app/components/MarkdownEditor.vue +1 -1
  32. package/dashboard/app/components/NotificationsBell.vue +8 -4
  33. package/dashboard/app/components/OnboardingTour.vue +24 -11
  34. package/dashboard/app/components/PersonaCloneDialog.vue +15 -8
  35. package/dashboard/app/components/PersonaWizard.vue +105 -72
  36. package/dashboard/app/components/SidebarFavoritesWidget.vue +5 -5
  37. package/dashboard/app/components/SidebarStatsWidget.vue +3 -1
  38. package/dashboard/app/components/Terminal.vue +9 -8
  39. package/dashboard/app/components/TextDiff.vue +27 -15
  40. package/dashboard/app/composables/useActivityFeed.ts +3 -3
  41. package/dashboard/app/composables/useApi.ts +4 -2
  42. package/dashboard/app/composables/useConfirmDialog.ts +1 -1
  43. package/dashboard/app/composables/useFavorites.ts +8 -8
  44. package/dashboard/app/composables/useTerminalThemes.ts +13 -13
  45. package/dashboard/app/composables/useThemeColor.ts +7 -4
  46. package/dashboard/app/pages/agents/[id].vue +319 -159
  47. package/dashboard/app/pages/agents/compare.vue +152 -52
  48. package/dashboard/app/pages/agents/index.vue +68 -55
  49. package/dashboard/app/pages/agents/new.vue +125 -58
  50. package/dashboard/app/pages/audit.vue +13 -6
  51. package/dashboard/app/pages/budget.vue +20 -18
  52. package/dashboard/app/pages/commands.vue +26 -22
  53. package/dashboard/app/pages/departments/[dept].vue +67 -31
  54. package/dashboard/app/pages/departments/compare.vue +85 -27
  55. package/dashboard/app/pages/departments/index.vue +31 -7
  56. package/dashboard/app/pages/health.vue +19 -12
  57. package/dashboard/app/pages/knowledge/index.vue +190 -78
  58. package/dashboard/app/pages/models.vue +74 -28
  59. package/dashboard/app/pages/personas/[id].vue +322 -219
  60. package/dashboard/app/pages/personas/archetypes.vue +20 -6
  61. package/dashboard/app/pages/personas/compare-with-agent.vue +109 -39
  62. package/dashboard/app/pages/personas/compare.vue +196 -124
  63. package/dashboard/app/pages/personas/index.vue +67 -60
  64. package/dashboard/app/pages/personas/new.vue +1 -1
  65. package/dashboard/app/pages/settings.vue +1 -0
  66. package/dashboard/app/pages/tasks.vue +80 -38
  67. package/dashboard/app/pages/trash.vue +11 -9
  68. package/dashboard/app/pages/workflows.vue +29 -19
  69. package/dashboard/nuxt.config.ts +2 -1
  70. package/dashboard/package.json +5 -0
  71. package/departments/kb/skills/doc-extraction/SKILL.md +99 -0
  72. package/departments/kb/skills/doc-redaction/SKILL.md +103 -0
  73. package/departments/kb/skills/knowledge-ops/SKILL.md +88 -0
  74. package/departments/ops/skills/github-ops/SKILL.md +89 -0
  75. package/departments/ops/skills/terminal-ops/SKILL.md +83 -0
  76. package/installer/index.js +1 -1
  77. package/knowledge/commands-registry.json +16 -3
  78. package/knowledge/skills-manifest.json +66 -1
  79. package/package.json +1 -1
  80. 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: any
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((p) => ({
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[]; secondary: 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((s) => s.trim()).filter(Boolean)
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: any
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: any) {
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
- (s) => !current.some((c) => c.toLowerCase() === s.toLowerCase()),
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">Edit agent</h2>
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">Identity</h3>
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">Mental models</h3>
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">Expertise</h3>
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">Communication</h3>
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 v-model="draft.communication.language" placeholder="en, pt" class="w-full" @update:model-value="markDirty" />
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 label="Cancel" variant="ghost" :disabled="saving" @click="tryClose" />
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">What's missing?</h3>
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
- const props = withDefaults(defineProps<Props>(), {
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">{{ emptyTitle }}</h3>
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: { 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' },
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">{{ r.label }}</p>
126
- <p class="text-xs text-muted truncate">{{ r.sublabel }}</p>
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">Keyboard shortcuts</h2>
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((s) => s.source.toLowerCase().includes(q))
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, () => { page.value = 1 })
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">Indexed sources</h3>
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>
@@ -20,7 +20,7 @@ const tab = ref<'edit' | 'preview'>('edit')
20
20
 
21
21
  const value = computed({
22
22
  get: () => props.modelValue ?? '',
23
- set: (v: string) => emit('update:modelValue', v),
23
+ set: (v: string) => emit('update:modelValue', v)
24
24
  })
25
25
 
26
26
  const html = computed(() => {