arkaos 3.65.0 → 3.66.1

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/VERSION CHANGED
@@ -1 +1 @@
1
- 3.65.0
1
+ 3.66.1
@@ -0,0 +1,192 @@
1
+ <script setup lang="ts">
2
+ // PR98d v3.66.0 — Agent dependency graph.
3
+ //
4
+ // Shows the current agent in the middle, its linked personas above,
5
+ // and other agents that link to those same personas (siblings) below.
6
+ // Pure SVG, no graph lib. Frontend-only — reuses existing endpoints.
7
+
8
+ interface Props {
9
+ agentId: string
10
+ agentName: string
11
+ linkedPersonas: string[]
12
+ }
13
+ const props = defineProps<Props>()
14
+
15
+ const { fetchApi } = useApi()
16
+
17
+ // /api/personas/usage returns the reverse lookup we need.
18
+ const { data: usageData } = fetchApi<{
19
+ by_persona: Record<string, { agent_count: number, agent_ids: string[] }>
20
+ }>('/api/personas/usage')
21
+
22
+ // /api/personas for resolving persona names.
23
+ const { data: personasData } = fetchApi<{
24
+ personas: Array<{ id: string, name: string }>
25
+ }>('/api/personas')
26
+
27
+ function personaName(id: string): string {
28
+ return personasData.value?.personas?.find((p) => p.id === id)?.name ?? id
29
+ }
30
+
31
+ const siblings = computed<string[]>(() => {
32
+ const ids = new Set<string>()
33
+ const usage = usageData.value?.by_persona ?? {}
34
+ for (const pid of props.linkedPersonas) {
35
+ for (const aid of (usage[pid]?.agent_ids ?? [])) {
36
+ if (aid && aid !== props.agentId) ids.add(aid)
37
+ }
38
+ }
39
+ return Array.from(ids).slice(0, 6)
40
+ })
41
+
42
+ // Layout constants — keep tight so the graph fits the hero card.
43
+ const WIDTH = 700
44
+ const HEIGHT = 220
45
+ const CENTER_X = WIDTH / 2
46
+ const CENTER_Y = HEIGHT / 2
47
+ const NODE_W = 110
48
+ const NODE_H = 32
49
+ const NODE_RX = 8
50
+
51
+ function distributeX(count: number, idx: number): number {
52
+ if (count === 1) return CENTER_X
53
+ const span = Math.min(WIDTH - 80, count * (NODE_W + 20))
54
+ const start = (WIDTH - span) / 2 + NODE_W / 2
55
+ const step = count > 1 ? (span - NODE_W) / (count - 1) : 0
56
+ return start + idx * step
57
+ }
58
+
59
+ const personaPositions = computed(() =>
60
+ props.linkedPersonas.map((id, i) => ({
61
+ id,
62
+ x: distributeX(props.linkedPersonas.length, i),
63
+ y: 32,
64
+ })),
65
+ )
66
+ const siblingPositions = computed(() =>
67
+ siblings.value.map((id, i) => ({
68
+ id,
69
+ x: distributeX(siblings.value.length, i),
70
+ y: HEIGHT - 32,
71
+ })),
72
+ )
73
+ </script>
74
+
75
+ <template>
76
+ <div
77
+ v-if="props.linkedPersonas.length > 0 || siblings.length > 0"
78
+ class="rounded-xl border border-default bg-elevated/10 p-5"
79
+ >
80
+ <h3 class="text-sm font-semibold uppercase tracking-wide text-muted mb-3">
81
+ Dependency graph
82
+ </h3>
83
+ <svg
84
+ :viewBox="`0 0 ${WIDTH} ${HEIGHT}`"
85
+ class="w-full"
86
+ preserveAspectRatio="xMidYMid meet"
87
+ >
88
+ <!-- Connector lines (personas → center) -->
89
+ <line
90
+ v-for="p in personaPositions"
91
+ :key="`pl-${p.id}`"
92
+ :x1="p.x"
93
+ :y1="p.y + NODE_H / 2"
94
+ :x2="CENTER_X"
95
+ :y2="CENTER_Y - NODE_H / 2"
96
+ class="stroke-default"
97
+ stroke-width="1"
98
+ />
99
+ <!-- Connector lines (center → siblings) -->
100
+ <line
101
+ v-for="s in siblingPositions"
102
+ :key="`sl-${s.id}`"
103
+ :x1="CENTER_X"
104
+ :y1="CENTER_Y + NODE_H / 2"
105
+ :x2="s.x"
106
+ :y2="s.y - NODE_H / 2"
107
+ class="stroke-default"
108
+ stroke-width="1"
109
+ stroke-dasharray="3,2"
110
+ />
111
+
112
+ <!-- Persona nodes (top) -->
113
+ <g
114
+ v-for="p in personaPositions"
115
+ :key="`p-${p.id}`"
116
+ >
117
+ <a :href="`/personas/${p.id}`">
118
+ <rect
119
+ :x="p.x - NODE_W / 2"
120
+ :y="p.y - NODE_H / 2"
121
+ :width="NODE_W"
122
+ :height="NODE_H"
123
+ :rx="NODE_RX"
124
+ class="fill-emerald-500/10 stroke-emerald-500/40"
125
+ stroke-width="1"
126
+ />
127
+ <text
128
+ :x="p.x"
129
+ :y="p.y + 4"
130
+ text-anchor="middle"
131
+ class="fill-emerald-700 dark:fill-emerald-300 text-xs"
132
+ >
133
+ {{ personaName(p.id).slice(0, 14) }}
134
+ </text>
135
+ <title>{{ personaName(p.id) }}</title>
136
+ </a>
137
+ </g>
138
+
139
+ <!-- Centre node: current agent -->
140
+ <g>
141
+ <rect
142
+ :x="CENTER_X - NODE_W / 2 - 10"
143
+ :y="CENTER_Y - NODE_H / 2"
144
+ :width="NODE_W + 20"
145
+ :height="NODE_H"
146
+ :rx="NODE_RX"
147
+ class="fill-primary/15 stroke-primary"
148
+ stroke-width="1.5"
149
+ />
150
+ <text
151
+ :x="CENTER_X"
152
+ :y="CENTER_Y + 4"
153
+ text-anchor="middle"
154
+ class="fill-primary text-sm font-semibold"
155
+ >
156
+ {{ props.agentName.slice(0, 16) }}
157
+ </text>
158
+ </g>
159
+
160
+ <!-- Sibling agent nodes (bottom) -->
161
+ <g
162
+ v-for="s in siblingPositions"
163
+ :key="`s-${s.id}`"
164
+ >
165
+ <a :href="`/agents/${s.id}`">
166
+ <rect
167
+ :x="s.x - NODE_W / 2"
168
+ :y="s.y - NODE_H / 2"
169
+ :width="NODE_W"
170
+ :height="NODE_H"
171
+ :rx="NODE_RX"
172
+ class="fill-blue-500/10 stroke-blue-500/40"
173
+ stroke-width="1"
174
+ />
175
+ <text
176
+ :x="s.x"
177
+ :y="s.y + 4"
178
+ text-anchor="middle"
179
+ class="fill-blue-700 dark:fill-blue-300 text-xs"
180
+ >
181
+ {{ s.id.slice(0, 14) }}
182
+ </text>
183
+ <title>{{ s.id }}</title>
184
+ </a>
185
+ </g>
186
+ </svg>
187
+ <p class="text-xs text-muted mt-2 italic">
188
+ Top: linked personas · Centre: this agent · Bottom: siblings (other
189
+ agents linking the same personas)
190
+ </p>
191
+ </div>
192
+ </template>
@@ -658,6 +658,14 @@ function formatTokens(n: number): string {
658
658
  </div>
659
659
  </section>
660
660
 
661
+ <!-- ===== DEPENDENCY GRAPH (PR98d) ===== -->
662
+ <AgentDependencyGraph
663
+ v-if="agent.linked_personas && agent.linked_personas.length > 0"
664
+ :agent-id="agent.id"
665
+ :agent-name="agent.name"
666
+ :linked-personas="agent.linked_personas"
667
+ />
668
+
661
669
  <!-- ===== BIO (PR86d) ===== -->
662
670
  <section
663
671
  v-if="(agent as any).bio_md"
@@ -79,6 +79,12 @@ const tierFilter = ref(String(route.query.tier ?? 'all'))
79
79
  const page = ref(1)
80
80
  const pageSize = 15
81
81
 
82
+ // PR86a + PR92b — favorites refs must exist BEFORE the filter watcher
83
+ // below references them (otherwise TDZ blows up the whole page).
84
+ const favs = useFavorites()
85
+ await favs.load()
86
+ const favoritesOnly = ref(route.query.fav === '1')
87
+
82
88
  const departments = computed(() => {
83
89
  const depts = new Set(agents.value.map(a => a.department))
84
90
  return [
@@ -262,12 +268,6 @@ defineShortcuts({
262
268
  enter: () => cursorOpen(),
263
269
  })
264
270
 
265
- // PR86a v3.15.0 — favorites.
266
- // PR92b v3.40.0 — favoritesOnly persists in URL (`?fav=1`).
267
- const favs = useFavorites()
268
- await favs.load()
269
- const favoritesOnly = ref(route.query.fav === '1')
270
-
271
271
  // PR83b v3.4.0 — bulk selection + delete.
272
272
  // PR84b v3.8.0 — bulk move department.
273
273
  const confirmDialog = useConfirmDialog()
@@ -0,0 +1,719 @@
1
+ <script setup lang="ts">
2
+ import type { Persona } from '~/types'
3
+
4
+ const { fetchApi, apiBase } = useApi()
5
+ const toast = useToast()
6
+
7
+ // --- Fetch personas (no await — non-blocking) ---
8
+ const { data, status, error, refresh } = fetchApi<{ personas: Persona[]; total: number }>('/api/personas')
9
+
10
+ const personas = computed(() => data.value?.personas ?? [])
11
+
12
+ // PR74 v2.92.0 — detail/edit drawer state
13
+ const detailOpen = ref(false)
14
+ const detailPersonaId = ref<string | null>(null)
15
+
16
+ // PR77 v2.95.0 — reverse-usage (which agents link to each persona).
17
+ const { data: usageData } = fetchApi<{
18
+ by_persona: Record<string, { agent_count: number, agent_ids: string[] }>
19
+ }>('/api/personas/usage')
20
+
21
+ function personaAgentCount(personaId: string): number {
22
+ return usageData.value?.by_persona?.[personaId]?.agent_count ?? 0
23
+ }
24
+
25
+ function mbtiGradient(mbti: string | undefined): string {
26
+ if (!mbti) return 'bg-gradient-to-br from-muted/20 to-muted/5'
27
+ const code = mbti.toUpperCase()
28
+ if (['INTJ', 'INTP', 'ENTJ', 'ENTP'].includes(code))
29
+ return 'bg-gradient-to-br from-blue-500/25 to-indigo-600/10'
30
+ if (['INFJ', 'INFP', 'ENFJ', 'ENFP'].includes(code))
31
+ return 'bg-gradient-to-br from-emerald-500/25 to-teal-600/10'
32
+ if (['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ'].includes(code))
33
+ return 'bg-gradient-to-br from-amber-500/25 to-orange-600/10'
34
+ if (['ISTP', 'ISFP', 'ESTP', 'ESFP'].includes(code))
35
+ return 'bg-gradient-to-br from-rose-500/25 to-pink-600/10'
36
+ return 'bg-gradient-to-br from-primary/20 to-primary/5'
37
+ }
38
+
39
+ function personaInitials(name: string): string {
40
+ if (!name) return '·'
41
+ const parts = name.trim().split(/\s+/)
42
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase()
43
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
44
+ }
45
+
46
+ function openDetail(persona: Persona) {
47
+ detailPersonaId.value = persona.id
48
+ detailOpen.value = true
49
+ }
50
+
51
+ async function onDetailSaved() {
52
+ await refresh()
53
+ }
54
+
55
+ async function onDetailDeleted(_id: string) {
56
+ await refresh()
57
+ }
58
+
59
+ // --- Creation mode ---
60
+ // PR62 v2.79.0 — three modes: list (default), wizard (AI builder), manual.
61
+ // The wizard is the new primary path; manual stays as fallback for
62
+ // operators who want to type every DNA field by hand.
63
+ type CreateMode = 'list' | 'wizard' | 'manual'
64
+ const createMode = ref<CreateMode>('list')
65
+ const showForm = computed(() => createMode.value === 'manual')
66
+
67
+ function startWizard() {
68
+ createMode.value = 'wizard'
69
+ }
70
+
71
+ function startManual() {
72
+ createMode.value = 'manual'
73
+ }
74
+
75
+ function cancelCreation() {
76
+ createMode.value = 'list'
77
+ }
78
+
79
+ async function onWizardComplete() {
80
+ createMode.value = 'list'
81
+ await refresh()
82
+ }
83
+
84
+ // --- Form state ---
85
+ function defaultForm() {
86
+ return {
87
+ name: '',
88
+ title: '',
89
+ source: '',
90
+ tagline: '',
91
+ mbti: '',
92
+ disc_primary: '',
93
+ disc_secondary: '',
94
+ enneagram_type: '',
95
+ enneagram_wing: '',
96
+ big_five_o: 50,
97
+ big_five_c: 50,
98
+ big_five_e: 50,
99
+ big_five_a: 50,
100
+ big_five_n: 50,
101
+ mental_models: '',
102
+ expertise_domains: '',
103
+ frameworks: '',
104
+ communication_tone: '',
105
+ }
106
+ }
107
+
108
+ const form = ref(defaultForm())
109
+ const creating = ref(false)
110
+
111
+ // --- Options ---
112
+ const mbtiTypes = [
113
+ 'INTJ', 'INTP', 'ENTJ', 'ENTP',
114
+ 'INFJ', 'INFP', 'ENFJ', 'ENFP',
115
+ 'ISTJ', 'ISFJ', 'ESTJ', 'ESFJ',
116
+ 'ISTP', 'ISFP', 'ESTP', 'ESFP',
117
+ ].map(t => ({ label: t, value: t }))
118
+
119
+ const discTypes = [
120
+ { label: 'D — Dominance', value: 'D' },
121
+ { label: 'I — Influence', value: 'I' },
122
+ { label: 'S — Steadiness', value: 'S' },
123
+ { label: 'C — Conscientiousness', value: 'C' },
124
+ ]
125
+
126
+ const enneagramTypes = Array.from({ length: 9 }, (_, i) => ({
127
+ label: `Type ${i + 1}`,
128
+ value: String(i + 1),
129
+ }))
130
+
131
+ const departmentOptions = [
132
+ 'dev', 'marketing', 'brand', 'finance', 'strategy',
133
+ 'ecom', 'kb', 'ops', 'pm', 'saas',
134
+ 'landing', 'content', 'community', 'sales', 'leadership', 'org',
135
+ ].map(d => ({ label: d, value: d }))
136
+
137
+ const tierOptions = [
138
+ { label: 'Tier 1 — Squad Leads', value: '1' },
139
+ { label: 'Tier 2 — Specialists', value: '2' },
140
+ { label: 'Tier 3 — Support', value: '3' },
141
+ ]
142
+
143
+ // --- Create persona ---
144
+ async function createPersona() {
145
+ if (!form.value.name.trim()) return
146
+
147
+ creating.value = true
148
+ try {
149
+ await $fetch(`${apiBase}/api/personas`, {
150
+ method: 'POST',
151
+ body: {
152
+ name: form.value.name,
153
+ title: form.value.title,
154
+ source: form.value.source,
155
+ tagline: form.value.tagline,
156
+ mbti: form.value.mbti,
157
+ disc: {
158
+ primary: form.value.disc_primary,
159
+ secondary: form.value.disc_secondary,
160
+ },
161
+ enneagram: {
162
+ type: form.value.enneagram_type ? Number(form.value.enneagram_type) : null,
163
+ wing: form.value.enneagram_wing ? Number(form.value.enneagram_wing) : null,
164
+ },
165
+ big_five: {
166
+ openness: form.value.big_five_o,
167
+ conscientiousness: form.value.big_five_c,
168
+ extraversion: form.value.big_five_e,
169
+ agreeableness: form.value.big_five_a,
170
+ neuroticism: form.value.big_five_n,
171
+ },
172
+ mental_models: form.value.mental_models
173
+ ? form.value.mental_models.split(',').map(s => s.trim()).filter(Boolean)
174
+ : [],
175
+ expertise_domains: form.value.expertise_domains
176
+ ? form.value.expertise_domains.split(',').map(s => s.trim()).filter(Boolean)
177
+ : [],
178
+ frameworks: form.value.frameworks
179
+ ? form.value.frameworks.split(',').map(s => s.trim()).filter(Boolean)
180
+ : [],
181
+ communication: {
182
+ tone: form.value.communication_tone,
183
+ },
184
+ },
185
+ })
186
+
187
+ toast.add({ title: 'Persona created', description: `${form.value.name} has been added.`, color: 'success' })
188
+ form.value = defaultForm()
189
+ showForm.value = false
190
+ await refresh()
191
+ } catch {
192
+ toast.add({ title: 'Error', description: 'Failed to create persona.', color: 'error' })
193
+ } finally {
194
+ creating.value = false
195
+ }
196
+ }
197
+
198
+ // --- Delete persona ---
199
+ const deleting = ref<string | null>(null)
200
+
201
+ async function deletePersona(persona: Persona) {
202
+ deleting.value = persona.id
203
+ try {
204
+ await $fetch(`${apiBase}/api/personas/${persona.id}`, { method: 'DELETE' })
205
+ toast.add({ title: 'Persona deleted', description: `${persona.name} has been removed.`, color: 'success' })
206
+ await refresh()
207
+ } catch {
208
+ toast.add({ title: 'Error', description: 'Failed to delete persona.', color: 'error' })
209
+ } finally {
210
+ deleting.value = null
211
+ }
212
+ }
213
+
214
+ // --- Clone to agent (inline expansion, no modal) ---
215
+ const cloneExpandedId = ref<string | null>(null)
216
+ const cloneDepartment = ref('')
217
+ const cloneTier = ref('')
218
+ const cloning = ref(false)
219
+
220
+ function toggleClone(persona: Persona) {
221
+ if (cloneExpandedId.value === persona.id) {
222
+ cloneExpandedId.value = null
223
+ } else {
224
+ cloneExpandedId.value = persona.id
225
+ cloneDepartment.value = ''
226
+ cloneTier.value = ''
227
+ }
228
+ }
229
+
230
+ async function cloneToAgent(persona: Persona) {
231
+ if (!cloneDepartment.value || !cloneTier.value) return
232
+
233
+ cloning.value = true
234
+ try {
235
+ await $fetch(`${apiBase}/api/personas/${persona.id}/clone`, {
236
+ method: 'POST',
237
+ body: {
238
+ department: cloneDepartment.value,
239
+ tier: Number(cloneTier.value),
240
+ },
241
+ })
242
+ toast.add({
243
+ title: 'Agent created',
244
+ description: `${persona.name} cloned to ${cloneDepartment.value} department.`,
245
+ color: 'success',
246
+ })
247
+ cloneExpandedId.value = null
248
+ await refresh()
249
+ } catch {
250
+ toast.add({ title: 'Error', description: 'Failed to clone persona to agent.', color: 'error' })
251
+ } finally {
252
+ cloning.value = false
253
+ }
254
+ }
255
+
256
+ // --- DNA badge colors ---
257
+ function mbtiColor(mbti: string): string {
258
+ if (!mbti) return 'neutral'
259
+ const analysts = ['INTJ', 'INTP', 'ENTJ', 'ENTP']
260
+ const diplomats = ['INFJ', 'INFP', 'ENFJ', 'ENFP']
261
+ const sentinels = ['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ']
262
+ if (analysts.includes(mbti)) return 'primary'
263
+ if (diplomats.includes(mbti)) return 'success'
264
+ if (sentinels.includes(mbti)) return 'warning'
265
+ return 'error'
266
+ }
267
+
268
+ function discColor(disc: string): string {
269
+ const colors: Record<string, string> = { D: 'error', I: 'warning', S: 'success', C: 'primary' }
270
+ return colors[disc] ?? 'neutral'
271
+ }
272
+ </script>
273
+
274
+ <template>
275
+ <UDashboardPanel id="personas">
276
+ <template #header>
277
+ <UDashboardNavbar title="Personas">
278
+ <template #leading>
279
+ <UDashboardSidebarCollapse />
280
+ </template>
281
+
282
+ <template #trailing>
283
+ <UBadge v-if="data?.total" :label="data.total" variant="subtle" />
284
+ </template>
285
+
286
+ <template #right>
287
+ <UButton
288
+ v-if="createMode === 'list'"
289
+ label="AI Builder"
290
+ icon="i-lucide-sparkles"
291
+ color="primary"
292
+ size="sm"
293
+ @click="startWizard"
294
+ />
295
+ <UButton
296
+ v-if="createMode === 'list'"
297
+ label="Manual"
298
+ icon="i-lucide-plus"
299
+ variant="outline"
300
+ size="sm"
301
+ class="ml-2"
302
+ @click="startManual"
303
+ />
304
+ <UButton
305
+ v-else
306
+ label="Back to list"
307
+ icon="i-lucide-arrow-left"
308
+ variant="ghost"
309
+ size="sm"
310
+ @click="cancelCreation"
311
+ />
312
+ </template>
313
+ </UDashboardNavbar>
314
+ </template>
315
+
316
+ <template #body>
317
+ <div class="overflow-y-auto h-[calc(100vh-4rem)]">
318
+ <!-- Loading -->
319
+ <div v-if="status === 'pending'" class="flex items-center justify-center py-12" aria-label="Loading personas">
320
+ <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
321
+ </div>
322
+
323
+ <!-- Error -->
324
+ <div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
325
+ <UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
326
+ <p class="text-sm text-muted">Failed to load personas.</p>
327
+ <UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
328
+ </div>
329
+
330
+ <!-- Content -->
331
+ <template v-else>
332
+ <!-- PR62: AI Persona Wizard -->
333
+ <PersonaWizard
334
+ v-if="createMode === 'wizard'"
335
+ class="mb-8"
336
+ @completed="onWizardComplete"
337
+ @cancelled="cancelCreation"
338
+ />
339
+
340
+ <!-- Manual create form (legacy / fallback) -->
341
+ <UCard v-if="showForm" class="mb-8">
342
+ <form @submit.prevent="createPersona" class="space-y-8 p-2">
343
+ <!-- Identity -->
344
+ <fieldset>
345
+ <legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Identity</legend>
346
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
347
+ <UFormField label="Name" required>
348
+ <UInput
349
+ v-model="form.name"
350
+ placeholder="e.g. Alex Hormozi"
351
+ aria-label="Persona name"
352
+ class="w-full"
353
+ required
354
+ />
355
+ </UFormField>
356
+ <UFormField label="Title">
357
+ <UInput
358
+ v-model="form.title"
359
+ placeholder="e.g. Business Strategy"
360
+ aria-label="Persona title"
361
+ class="w-full"
362
+ />
363
+ </UFormField>
364
+ <UFormField label="Source">
365
+ <UInput
366
+ v-model="form.source"
367
+ placeholder="e.g. Alex Hormozi"
368
+ aria-label="Persona source"
369
+ class="w-full"
370
+ />
371
+ </UFormField>
372
+ <UFormField label="Tagline">
373
+ <UInput
374
+ v-model="form.tagline"
375
+ placeholder="e.g. The Natural Commander"
376
+ aria-label="Persona tagline"
377
+ class="w-full"
378
+ />
379
+ </UFormField>
380
+ </div>
381
+ </fieldset>
382
+
383
+ <!-- Behavioral DNA -->
384
+ <fieldset>
385
+ <legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Behavioral DNA</legend>
386
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
387
+ <UFormField label="MBTI">
388
+ <USelect
389
+ v-model="form.mbti"
390
+ :items="mbtiTypes"
391
+ placeholder="Select MBTI"
392
+ aria-label="MBTI type"
393
+ class="w-full"
394
+ />
395
+ </UFormField>
396
+ <UFormField label="DISC Primary">
397
+ <USelect
398
+ v-model="form.disc_primary"
399
+ :items="discTypes"
400
+ placeholder="Primary"
401
+ aria-label="DISC primary type"
402
+ class="w-full"
403
+ />
404
+ </UFormField>
405
+ <UFormField label="DISC Secondary">
406
+ <USelect
407
+ v-model="form.disc_secondary"
408
+ :items="discTypes"
409
+ placeholder="Secondary"
410
+ aria-label="DISC secondary type"
411
+ class="w-full"
412
+ />
413
+ </UFormField>
414
+ <UFormField label="Enneagram Type">
415
+ <USelect
416
+ v-model="form.enneagram_type"
417
+ :items="enneagramTypes"
418
+ placeholder="Type"
419
+ aria-label="Enneagram type"
420
+ class="w-full"
421
+ />
422
+ </UFormField>
423
+ </div>
424
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
425
+ <UFormField label="Enneagram Wing (1-9)">
426
+ <UInput
427
+ v-model="form.enneagram_wing"
428
+ type="number"
429
+ :min="1"
430
+ :max="9"
431
+ placeholder="e.g. 4"
432
+ aria-label="Enneagram wing"
433
+ class="w-full"
434
+ />
435
+ </UFormField>
436
+ </div>
437
+ </fieldset>
438
+
439
+ <!-- Big Five -->
440
+ <fieldset>
441
+ <legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Big Five / OCEAN (0-100)</legend>
442
+ <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
443
+ <UFormField label="Openness">
444
+ <UInput
445
+ v-model.number="form.big_five_o"
446
+ type="number"
447
+ :min="0"
448
+ :max="100"
449
+ aria-label="Openness score"
450
+ class="w-full"
451
+ />
452
+ </UFormField>
453
+ <UFormField label="Conscientiousness">
454
+ <UInput
455
+ v-model.number="form.big_five_c"
456
+ type="number"
457
+ :min="0"
458
+ :max="100"
459
+ aria-label="Conscientiousness score"
460
+ class="w-full"
461
+ />
462
+ </UFormField>
463
+ <UFormField label="Extraversion">
464
+ <UInput
465
+ v-model.number="form.big_five_e"
466
+ type="number"
467
+ :min="0"
468
+ :max="100"
469
+ aria-label="Extraversion score"
470
+ class="w-full"
471
+ />
472
+ </UFormField>
473
+ <UFormField label="Agreeableness">
474
+ <UInput
475
+ v-model.number="form.big_five_a"
476
+ type="number"
477
+ :min="0"
478
+ :max="100"
479
+ aria-label="Agreeableness score"
480
+ class="w-full"
481
+ />
482
+ </UFormField>
483
+ <UFormField label="Neuroticism">
484
+ <UInput
485
+ v-model.number="form.big_five_n"
486
+ type="number"
487
+ :min="0"
488
+ :max="100"
489
+ aria-label="Neuroticism score"
490
+ class="w-full"
491
+ />
492
+ </UFormField>
493
+ </div>
494
+ </fieldset>
495
+
496
+ <!-- Knowledge & Communication -->
497
+ <fieldset>
498
+ <legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Knowledge & Communication</legend>
499
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
500
+ <UFormField label="Mental Models" hint="Comma-separated">
501
+ <UInput
502
+ v-model="form.mental_models"
503
+ placeholder="e.g. Grand Slam Offer, Value Equation"
504
+ aria-label="Mental models, comma-separated"
505
+ class="w-full"
506
+ />
507
+ </UFormField>
508
+ <UFormField label="Expertise Domains" hint="Comma-separated">
509
+ <UInput
510
+ v-model="form.expertise_domains"
511
+ placeholder="e.g. business strategy, offer creation"
512
+ aria-label="Expertise domains, comma-separated"
513
+ class="w-full"
514
+ />
515
+ </UFormField>
516
+ <UFormField label="Frameworks" hint="Comma-separated">
517
+ <UInput
518
+ v-model="form.frameworks"
519
+ placeholder="e.g. $100M Offers, Value Equation"
520
+ aria-label="Frameworks, comma-separated"
521
+ class="w-full"
522
+ />
523
+ </UFormField>
524
+ <UFormField label="Communication Tone">
525
+ <UInput
526
+ v-model="form.communication_tone"
527
+ placeholder="e.g. direct, high-energy"
528
+ aria-label="Communication tone"
529
+ class="w-full"
530
+ />
531
+ </UFormField>
532
+ </div>
533
+ </fieldset>
534
+
535
+ <!-- Submit -->
536
+ <UButton
537
+ type="submit"
538
+ label="Create Persona"
539
+ icon="i-lucide-sparkles"
540
+ size="lg"
541
+ block
542
+ :loading="creating"
543
+ :disabled="!form.name.trim()"
544
+ />
545
+ </form>
546
+ </UCard>
547
+
548
+ <!-- Empty state -->
549
+ <div v-if="!personas.length && !showForm" class="flex flex-col items-center justify-center gap-6 py-20">
550
+ <div class="rounded-full bg-muted/10 p-6">
551
+ <UIcon name="i-lucide-users" class="size-12 text-muted" />
552
+ </div>
553
+ <div class="text-center space-y-2">
554
+ <h3 class="text-base font-semibold">No personas yet</h3>
555
+ <p class="text-sm text-muted max-w-sm">
556
+ Personas define the behavioral DNA for your AI agents. Create one to get started.
557
+ </p>
558
+ </div>
559
+ <UButton
560
+ label="Create your first persona"
561
+ icon="i-lucide-plus"
562
+ size="lg"
563
+ @click="showForm = true"
564
+ />
565
+ </div>
566
+
567
+ <!-- PR74 v2.92.0 — detail/edit drawer -->
568
+ <PersonaDetailDrawer
569
+ v-model="detailOpen"
570
+ :persona-id="detailPersonaId"
571
+ @saved="onDetailSaved"
572
+ @deleted="onDetailDeleted"
573
+ />
574
+
575
+ <!-- Personas Grid -->
576
+ <div v-if="personas.length" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
577
+ <div
578
+ v-for="persona in personas"
579
+ :key="persona.id"
580
+ class="group flex flex-col rounded-2xl border border-default overflow-hidden cursor-pointer hover:border-primary/40 hover:shadow-lg transition-all"
581
+ role="button"
582
+ tabindex="0"
583
+ @click="openDetail(persona)"
584
+ @keydown.enter="openDetail(persona)"
585
+ >
586
+ <!-- Gradient header with avatar -->
587
+ <div
588
+ class="p-4 flex items-center gap-3"
589
+ :class="mbtiGradient(persona.mbti)"
590
+ >
591
+ <div class="shrink-0 size-12 rounded-xl bg-default/80 border border-default flex items-center justify-center shadow-sm backdrop-blur-sm">
592
+ <span class="text-sm font-bold tracking-tight text-highlighted">
593
+ {{ personaInitials(persona.name) }}
594
+ </span>
595
+ </div>
596
+ <div class="flex-1 min-w-0">
597
+ <h3 class="text-base font-bold truncate text-highlighted">{{ persona.name }}</h3>
598
+ <p v-if="persona.title" class="text-xs text-muted truncate mt-0.5">{{ persona.title }}</p>
599
+ </div>
600
+ <UBadge
601
+ v-if="personaAgentCount(persona.id) > 0"
602
+ :label="`${personaAgentCount(persona.id)} agents`"
603
+ variant="subtle"
604
+ color="primary"
605
+ size="xs"
606
+ class="shrink-0"
607
+ />
608
+ </div>
609
+
610
+ <!-- Body -->
611
+ <div class="flex flex-col gap-3 flex-1 p-4">
612
+ <p v-if="persona.tagline" class="text-sm text-muted italic leading-relaxed line-clamp-2">
613
+ "{{ persona.tagline }}"
614
+ </p>
615
+
616
+ <!-- DNA Badges -->
617
+ <div class="flex flex-wrap gap-1.5">
618
+ <UBadge
619
+ v-if="persona.mbti"
620
+ :label="persona.mbti"
621
+ :color="mbtiColor(persona.mbti) as any"
622
+ variant="subtle"
623
+ size="xs"
624
+ />
625
+ <UBadge
626
+ v-if="persona.disc?.primary"
627
+ :label="`DISC: ${persona.disc.primary}${persona.disc.secondary ? '/' + persona.disc.secondary : ''}`"
628
+ :color="discColor(persona.disc.primary) as any"
629
+ variant="subtle"
630
+ size="xs"
631
+ />
632
+ <UBadge
633
+ v-if="persona.enneagram?.type"
634
+ :label="`E${persona.enneagram.type}${persona.enneagram.wing ? 'w' + persona.enneagram.wing : ''}`"
635
+ variant="outline"
636
+ size="xs"
637
+ />
638
+ </div>
639
+
640
+ <!-- Expertise domains -->
641
+ <div v-if="persona.expertise_domains?.length" class="flex flex-wrap gap-1">
642
+ <UBadge
643
+ v-for="domain in persona.expertise_domains.slice(0, 3)"
644
+ :key="domain"
645
+ :label="domain"
646
+ variant="outline"
647
+ size="xs"
648
+ color="neutral"
649
+ />
650
+ <UBadge
651
+ v-if="persona.expertise_domains.length > 3"
652
+ :label="`+${persona.expertise_domains.length - 3}`"
653
+ variant="outline"
654
+ size="xs"
655
+ color="neutral"
656
+ />
657
+ </div>
658
+
659
+ <!-- Actions -->
660
+ <div class="pt-3 mt-auto border-t border-default space-y-3" @click.stop>
661
+ <div class="flex gap-2">
662
+ <UButton
663
+ label="Clone to Agent"
664
+ icon="i-lucide-copy"
665
+ size="sm"
666
+ variant="solid"
667
+ class="flex-1"
668
+ @click.stop="toggleClone(persona)"
669
+ />
670
+ <UButton
671
+ icon="i-lucide-trash-2"
672
+ size="sm"
673
+ variant="ghost"
674
+ color="error"
675
+ :loading="deleting === persona.id"
676
+ aria-label="Delete persona"
677
+ @click.stop="deletePersona(persona)"
678
+ />
679
+ </div>
680
+
681
+ <!-- Inline clone expansion -->
682
+ <div v-if="cloneExpandedId === persona.id" class="space-y-3 pt-2">
683
+ <UFormField label="Department" required>
684
+ <USelect
685
+ v-model="cloneDepartment"
686
+ :items="departmentOptions"
687
+ placeholder="Select department"
688
+ aria-label="Target department"
689
+ class="w-full"
690
+ />
691
+ </UFormField>
692
+ <UFormField label="Tier" required>
693
+ <USelect
694
+ v-model="cloneTier"
695
+ :items="tierOptions"
696
+ placeholder="Select tier"
697
+ aria-label="Agent tier"
698
+ class="w-full"
699
+ />
700
+ </UFormField>
701
+ <UButton
702
+ label="Confirm Clone"
703
+ icon="i-lucide-check"
704
+ size="sm"
705
+ block
706
+ :loading="cloning"
707
+ :disabled="!cloneDepartment || !cloneTier"
708
+ @click="cloneToAgent(persona)"
709
+ />
710
+ </div>
711
+ </div>
712
+ </div>
713
+ </div>
714
+ </div>
715
+ </template>
716
+ </div>
717
+ </template>
718
+ </UDashboardPanel>
719
+ </template>
@@ -4,18 +4,19 @@ role: Creative Director
4
4
  department: brand
5
5
  tier: 1
6
6
  model: sonnet
7
+
7
8
  behavioral_dna:
8
9
  disc:
9
10
  primary: S
10
11
  secondary: I
11
- communication_style: Thoughtful, visual, builds consensus before executing
12
- under_pressure: Protects creative quality, refuses to rush aesthetics
13
- motivator: Beautiful, meaningful brands that resonate emotionally
12
+ communication_style: "Thoughtful, visual, builds consensus before executing"
13
+ under_pressure: "Protects creative quality, refuses to rush aesthetics"
14
+ motivator: "Beautiful, meaningful brands that resonate emotionally"
14
15
  enneagram:
15
16
  type: 4
16
17
  wing: 3
17
- core_motivation: Creating unique, authentic brand identities
18
- core_fear: Producing generic, forgettable brand work
18
+ core_motivation: "Creating unique, authentic brand identities"
19
+ core_fear: "Producing generic, forgettable brand work"
19
20
  subtype: social
20
21
  big_five:
21
22
  openness: 92
@@ -25,52 +26,53 @@ behavioral_dna:
25
26
  neuroticism: 35
26
27
  mbti:
27
28
  type: INFP
29
+
28
30
  mental_models:
29
31
  primary:
30
- - Primal Branding 7 Elements (Hanlon)
31
- - Brand Identity Process (Wheeler)
32
- - 12 Archetypes (Jung)
32
+ - "Primal Branding 7 Elements (Hanlon)"
33
+ - "Brand Identity Process (Wheeler)"
34
+ - "12 Archetypes (Jung)"
33
35
  secondary:
34
- - StoryBrand SB7 (Miller)
35
- - Positioning (Ries/Trout)
36
- - Design Thinking (IDEO)
36
+ - "StoryBrand SB7 (Miller)"
37
+ - "Positioning (Ries/Trout)"
38
+ - "Design Thinking (IDEO)"
39
+
37
40
  authority:
38
41
  orchestrate: true
39
42
  approve_quality: true
40
43
  delegates_to:
41
- - visual-designer
42
- - ux-designer
43
- - brand-copywriter
44
- - brand-strategist
44
+ - visual-designer
45
+ - ux-designer
46
+ - brand-copywriter
47
+ - brand-strategist
45
48
  escalates_to: coo-sofia
49
+
46
50
  expertise:
47
51
  domains:
48
- - brand identity creation
49
- - visual design direction
50
- - UX/UI strategy
51
- - design systems
52
- - brand voice & tone
53
- - creative direction
52
+ - brand identity creation
53
+ - visual design direction
54
+ - UX/UI strategy
55
+ - design systems
56
+ - brand voice & tone
57
+ - creative direction
54
58
  frameworks:
55
- - Primal Branding (Hanlon)
56
- - StoryBrand (Miller)
57
- - Brand Archetypes (Jung)
58
- - Wheeler Process
59
- - Atomic Design (Frost)
60
- - Nielsen Heuristics
61
- - Dieter Rams 10 Principles
62
- - Double Diamond
59
+ - Primal Branding (Hanlon)
60
+ - StoryBrand (Miller)
61
+ - Brand Archetypes (Jung)
62
+ - Wheeler Process
63
+ - Atomic Design (Frost)
64
+ - Nielsen Heuristics
65
+ - Dieter Rams 10 Principles
66
+ - Double Diamond
63
67
  depth: master
64
- years_equivalent: 24
68
+ years_equivalent: 14
69
+
65
70
  communication:
66
71
  language: en
67
- tone: warm, visual, metaphor-rich
72
+ tone: "warm, visual, metaphor-rich"
68
73
  vocabulary_level: advanced
69
- preferred_format: mood boards, visual references, brand decks
74
+ preferred_format: "mood boards, visual references, brand decks"
70
75
  avoid:
71
- - design by committee
72
- - trendy without substance
73
- - skipping strategy for visuals
74
- frameworks: []
75
- expertise_domains: []
76
- linked_personas: []
76
+ - "design by committee"
77
+ - "trendy without substance"
78
+ - "skipping strategy for visuals"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.65.0",
3
+ "version": "3.66.1",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "3.65.0"
3
+ version = "3.66.1"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}