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
|
@@ -21,7 +21,7 @@ function kindIcon(kind: string): string {
|
|
|
21
21
|
success: 'i-lucide-check-circle',
|
|
22
22
|
warning: 'i-lucide-alert-triangle',
|
|
23
23
|
error: 'i-lucide-x-circle',
|
|
24
|
-
info: 'i-lucide-info'
|
|
24
|
+
info: 'i-lucide-info'
|
|
25
25
|
}[kind] ?? 'i-lucide-circle'
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ function kindColor(kind: string): string {
|
|
|
30
30
|
success: 'text-emerald-500',
|
|
31
31
|
warning: 'text-amber-500',
|
|
32
32
|
error: 'text-rose-500',
|
|
33
|
-
info: 'text-blue-500'
|
|
33
|
+
info: 'text-blue-500'
|
|
34
34
|
}[kind] ?? 'text-muted'
|
|
35
35
|
}
|
|
36
36
|
</script>
|
|
@@ -56,7 +56,9 @@ function kindColor(kind: string): string {
|
|
|
56
56
|
<template #content>
|
|
57
57
|
<div class="p-2 border-b border-default flex items-center justify-between gap-2">
|
|
58
58
|
<div>
|
|
59
|
-
<p class="text-sm font-semibold">
|
|
59
|
+
<p class="text-sm font-semibold">
|
|
60
|
+
Recent activity
|
|
61
|
+
</p>
|
|
60
62
|
<p class="text-xs text-muted">
|
|
61
63
|
{{ feed.unreadCount.value }} unread ·
|
|
62
64
|
{{ feed.events.value.length }} total
|
|
@@ -118,7 +120,9 @@ function kindColor(kind: string): string {
|
|
|
118
120
|
<p v-if="ev.description" class="text-xs text-muted truncate">
|
|
119
121
|
{{ ev.description }}
|
|
120
122
|
</p>
|
|
121
|
-
<p class="text-xs text-muted/70 mt-0.5">
|
|
123
|
+
<p class="text-xs text-muted/70 mt-0.5">
|
|
124
|
+
{{ formatRelative(ev.ts) }}
|
|
125
|
+
</p>
|
|
122
126
|
</div>
|
|
123
127
|
<UButton
|
|
124
128
|
icon="i-lucide-x"
|
|
@@ -23,37 +23,37 @@ const steps: TourStep[] = [
|
|
|
23
23
|
{
|
|
24
24
|
icon: 'i-lucide-sparkles',
|
|
25
25
|
title: 'Welcome to ArkaOS',
|
|
26
|
-
body: 'A 4-minute tour of where to start. Press Esc anytime to skip — you can replay this from Settings.'
|
|
26
|
+
body: 'A 4-minute tour of where to start. Press Esc anytime to skip — you can replay this from Settings.'
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
icon: 'i-lucide-users',
|
|
30
30
|
title: 'Agents',
|
|
31
31
|
body: 'Your specialist team. Browse the table, click a row for full detail (DNA, expertise, history). Create new ones with the AI draft on /agents/new.',
|
|
32
|
-
cta: { label: 'Open /agents', to: '/agents' }
|
|
32
|
+
cta: { label: 'Open /agents', to: '/agents' }
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
icon: 'i-lucide-user-plus',
|
|
36
36
|
title: 'Personas',
|
|
37
37
|
body: 'Behavioural profiles of real people (or archetypes) that seed your agents. Import from .md files / URLs, export as ZIP, clone to agents.',
|
|
38
|
-
cta: { label: 'Open /personas', to: '/personas' }
|
|
38
|
+
cta: { label: 'Open /personas', to: '/personas' }
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
icon: 'i-lucide-workflow',
|
|
42
42
|
title: 'Workflows',
|
|
43
43
|
body: 'YAML-defined orchestrations under departments/. Click a workflow for its phase flow, raw YAML, and recent runs.',
|
|
44
|
-
cta: { label: 'Open /workflows', to: '/workflows' }
|
|
44
|
+
cta: { label: 'Open /workflows', to: '/workflows' }
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
icon: 'i-lucide-wallet',
|
|
48
48
|
title: 'Budget',
|
|
49
49
|
body: 'Real LLM spend by provider/model/category, daily trend chart (7/14/30d), CSV export. Powered by PR47 telemetry.',
|
|
50
|
-
cta: { label: 'Open /budget', to: '/budget' }
|
|
50
|
+
cta: { label: 'Open /budget', to: '/budget' }
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
icon: 'i-lucide-keyboard',
|
|
54
54
|
title: 'Power shortcuts',
|
|
55
|
-
body: 'Press ? anywhere for the full keymap. Try / for search, g a for agents, g p for personas.'
|
|
56
|
-
}
|
|
55
|
+
body: 'Press ? anywhere for the full keymap. Try / for search, g a for agents, g p for personas.'
|
|
56
|
+
}
|
|
57
57
|
]
|
|
58
58
|
|
|
59
59
|
onMounted(() => {
|
|
@@ -100,24 +100,37 @@ const progress = computed(() => Math.round(((step.value + 1) / steps.length) * 1
|
|
|
100
100
|
title="Welcome"
|
|
101
101
|
>
|
|
102
102
|
<template #content>
|
|
103
|
-
|
|
103
|
+
<!-- step is clamped to [0, steps.length-1] by next()/back(), so
|
|
104
|
+
current is always defined — the v-if only narrows for the type
|
|
105
|
+
checker and never hides the card at runtime. -->
|
|
106
|
+
<UCard v-if="current">
|
|
104
107
|
<template #header>
|
|
105
108
|
<div class="flex items-center justify-between gap-3">
|
|
106
109
|
<div class="flex items-center gap-3">
|
|
107
110
|
<UIcon :name="current.icon" class="size-6 text-primary" />
|
|
108
111
|
<div>
|
|
109
|
-
<h2 class="text-lg font-bold">
|
|
112
|
+
<h2 class="text-lg font-bold">
|
|
113
|
+
{{ current.title }}
|
|
114
|
+
</h2>
|
|
110
115
|
<p class="text-xs text-muted mt-0.5">
|
|
111
116
|
Step {{ step + 1 }} of {{ steps.length }}
|
|
112
117
|
</p>
|
|
113
118
|
</div>
|
|
114
119
|
</div>
|
|
115
|
-
<UButton
|
|
120
|
+
<UButton
|
|
121
|
+
icon="i-lucide-x"
|
|
122
|
+
variant="ghost"
|
|
123
|
+
size="sm"
|
|
124
|
+
aria-label="Skip"
|
|
125
|
+
@click="dismiss"
|
|
126
|
+
/>
|
|
116
127
|
</div>
|
|
117
128
|
</template>
|
|
118
129
|
|
|
119
130
|
<div class="space-y-4">
|
|
120
|
-
<p class="text-sm">
|
|
131
|
+
<p class="text-sm">
|
|
132
|
+
{{ current.body }}
|
|
133
|
+
</p>
|
|
121
134
|
<UButton
|
|
122
135
|
v-if="current.cta"
|
|
123
136
|
:label="current.cta.label"
|
|
@@ -25,13 +25,13 @@ const cloning = ref(false)
|
|
|
25
25
|
|
|
26
26
|
const departmentOptions = [
|
|
27
27
|
'dev', 'marketing', 'brand', 'finance', 'strategy', 'ecom', 'kb', 'ops',
|
|
28
|
-
'pm', 'saas', 'landing', 'content', 'community', 'sales', 'leadership', 'org'
|
|
29
|
-
].map(
|
|
28
|
+
'pm', 'saas', 'landing', 'content', 'community', 'sales', 'leadership', 'org'
|
|
29
|
+
].map(d => ({ label: d, value: d }))
|
|
30
30
|
|
|
31
31
|
const tierOptions = [
|
|
32
32
|
{ label: 'Tier 1 — Squad Lead', value: 1 },
|
|
33
33
|
{ label: 'Tier 2 — Specialist', value: 2 },
|
|
34
|
-
{ label: 'Tier 3 — Support', value: 3 }
|
|
34
|
+
{ label: 'Tier 3 — Support', value: 3 }
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
async function clone() {
|
|
@@ -44,14 +44,14 @@ async function clone() {
|
|
|
44
44
|
error?: string
|
|
45
45
|
}>(`${apiBase}/api/personas/${props.personaId}/clone`, {
|
|
46
46
|
method: 'POST',
|
|
47
|
-
body: { department: department.value, tier: tier.value }
|
|
47
|
+
body: { department: department.value, tier: tier.value }
|
|
48
48
|
})
|
|
49
49
|
if (res.error || !res.agent_id) throw new Error(res.error ?? 'unknown error')
|
|
50
50
|
toast.add({
|
|
51
51
|
title: 'Cloned to agent',
|
|
52
52
|
description: `${props.personaName} → ${res.agent_id} (${res.department})`,
|
|
53
53
|
color: 'success',
|
|
54
|
-
icon: 'i-lucide-copy-plus'
|
|
54
|
+
icon: 'i-lucide-copy-plus'
|
|
55
55
|
})
|
|
56
56
|
emit('cloned', res.agent_id)
|
|
57
57
|
emit('update:modelValue', false)
|
|
@@ -59,7 +59,7 @@ async function clone() {
|
|
|
59
59
|
toast.add({
|
|
60
60
|
title: 'Clone failed',
|
|
61
61
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
62
|
-
color: 'error'
|
|
62
|
+
color: 'error'
|
|
63
63
|
})
|
|
64
64
|
} finally {
|
|
65
65
|
cloning.value = false
|
|
@@ -83,7 +83,9 @@ function close() {
|
|
|
83
83
|
<template #header>
|
|
84
84
|
<div class="flex items-center justify-between gap-3">
|
|
85
85
|
<div>
|
|
86
|
-
<h2 class="text-lg font-bold">
|
|
86
|
+
<h2 class="text-lg font-bold">
|
|
87
|
+
Clone to Agent
|
|
88
|
+
</h2>
|
|
87
89
|
<p class="text-sm text-muted mt-0.5">
|
|
88
90
|
Create a new agent based on <strong>{{ personaName }}</strong>.
|
|
89
91
|
</p>
|
|
@@ -116,7 +118,12 @@ function close() {
|
|
|
116
118
|
|
|
117
119
|
<template #footer>
|
|
118
120
|
<div class="flex items-center justify-end gap-2">
|
|
119
|
-
<UButton
|
|
121
|
+
<UButton
|
|
122
|
+
label="Cancel"
|
|
123
|
+
variant="ghost"
|
|
124
|
+
:disabled="cloning"
|
|
125
|
+
@click="close"
|
|
126
|
+
/>
|
|
120
127
|
<UButton
|
|
121
128
|
label="Clone"
|
|
122
129
|
icon="i-lucide-copy-plus"
|
|
@@ -32,9 +32,9 @@ const skipIngest = ref(false)
|
|
|
32
32
|
const sourceLineCount = computed(() =>
|
|
33
33
|
sources.value
|
|
34
34
|
.split('\n')
|
|
35
|
-
.map(
|
|
36
|
-
.filter(
|
|
37
|
-
.length
|
|
35
|
+
.map(s => s.trim())
|
|
36
|
+
.filter(s => s.length > 0)
|
|
37
|
+
.length
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
// PR83a v3.3.0 — Mode 3: build from a free-text description (no chunks).
|
|
@@ -52,7 +52,7 @@ interface Archetype {
|
|
|
52
52
|
description: string
|
|
53
53
|
}
|
|
54
54
|
const { data: archetypeData } = fetchApi<{ archetypes: Archetype[] }>(
|
|
55
|
-
'/api/personas/archetypes'
|
|
55
|
+
'/api/personas/archetypes'
|
|
56
56
|
)
|
|
57
57
|
const archetypes = computed<Archetype[]>(() => archetypeData.value?.archetypes ?? [])
|
|
58
58
|
|
|
@@ -69,7 +69,7 @@ const route = useRoute()
|
|
|
69
69
|
watch(archetypes, (list) => {
|
|
70
70
|
const slug = String(route.query.archetype ?? '')
|
|
71
71
|
if (!slug || list.length === 0) return
|
|
72
|
-
const match = list.find(
|
|
72
|
+
const match = list.find(a => a.id === slug)
|
|
73
73
|
if (match) applyArchetype(match)
|
|
74
74
|
}, { immediate: true })
|
|
75
75
|
|
|
@@ -84,10 +84,10 @@ const ingestJobs = ref<Array<{
|
|
|
84
84
|
let ws: WebSocket | null = null
|
|
85
85
|
const allIngestComplete = computed(() =>
|
|
86
86
|
ingestJobs.value.length > 0
|
|
87
|
-
&& ingestJobs.value.every(
|
|
87
|
+
&& ingestJobs.value.every(j => j.status === 'completed' || j.status === 'failed')
|
|
88
88
|
)
|
|
89
89
|
const ingestCompletedCount = computed(() =>
|
|
90
|
-
ingestJobs.value.filter(
|
|
90
|
+
ingestJobs.value.filter(j => j.status === 'completed').length
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
// ─── Step 3 state ────────────────────────────────────────────────────────
|
|
@@ -104,18 +104,17 @@ const cloneTier = ref<'1' | '2' | '3'>('2')
|
|
|
104
104
|
|
|
105
105
|
const departmentOptions = [
|
|
106
106
|
'dev', 'marketing', 'brand', 'finance', 'strategy', 'ecom', 'kb', 'ops',
|
|
107
|
-
'pm', 'saas', 'landing', 'content', 'community', 'sales', 'leadership', 'org'
|
|
108
|
-
].map(
|
|
107
|
+
'pm', 'saas', 'landing', 'content', 'community', 'sales', 'leadership', 'org'
|
|
108
|
+
].map(d => ({ label: d, value: d }))
|
|
109
109
|
|
|
110
110
|
const tierOptions = [
|
|
111
111
|
{ label: 'Tier 1 — Squad Lead', value: '1' },
|
|
112
112
|
{ label: 'Tier 2 — Specialist', value: '2' },
|
|
113
|
-
{ label: 'Tier 3 — Support', value: '3' }
|
|
113
|
+
{ label: 'Tier 3 — Support', value: '3' }
|
|
114
114
|
]
|
|
115
115
|
|
|
116
116
|
// ─── Step 1 → 2 transition ───────────────────────────────────────────────
|
|
117
117
|
|
|
118
|
-
|
|
119
118
|
async function startIngest() {
|
|
120
119
|
if (mode.value === 'description') {
|
|
121
120
|
// PR83a — no ingest, no chunks. Build directly from description.
|
|
@@ -132,22 +131,22 @@ async function startIngest() {
|
|
|
132
131
|
}
|
|
133
132
|
const cleaned = sources.value
|
|
134
133
|
.split('\n')
|
|
135
|
-
.map(
|
|
136
|
-
.filter(
|
|
134
|
+
.map(s => s.trim())
|
|
135
|
+
.filter(s => s.length > 0)
|
|
137
136
|
if (cleaned.length === 0 || !name.value.trim()) return
|
|
138
137
|
step.value = 2
|
|
139
|
-
ingestJobs.value = cleaned.map(
|
|
138
|
+
ingestJobs.value = cleaned.map(source => ({
|
|
140
139
|
source,
|
|
141
140
|
status: 'queued',
|
|
142
|
-
progress: 0
|
|
141
|
+
progress: 0
|
|
143
142
|
}))
|
|
144
143
|
try {
|
|
145
144
|
const res = await $fetch<{ jobs: Array<{ source: string, job_id?: string, error?: string }>, count: number }>(
|
|
146
145
|
`${apiBase}/api/knowledge/ingest-bulk`,
|
|
147
|
-
{ method: 'POST', body: { sources: cleaned } }
|
|
146
|
+
{ method: 'POST', body: { sources: cleaned } }
|
|
148
147
|
)
|
|
149
148
|
res.jobs.forEach((j) => {
|
|
150
|
-
const row = ingestJobs.value.find(
|
|
149
|
+
const row = ingestJobs.value.find(r => r.source === j.source)
|
|
151
150
|
if (!row) return
|
|
152
151
|
if (j.error) {
|
|
153
152
|
row.status = 'failed'
|
|
@@ -162,13 +161,12 @@ async function startIngest() {
|
|
|
162
161
|
toast.add({
|
|
163
162
|
title: 'Ingest failed',
|
|
164
163
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
165
|
-
color: 'error'
|
|
164
|
+
color: 'error'
|
|
166
165
|
})
|
|
167
166
|
step.value = 1
|
|
168
167
|
}
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
|
|
172
170
|
function connectWebSocket() {
|
|
173
171
|
if (ws && ws.readyState === WebSocket.OPEN) return
|
|
174
172
|
const wsUrl = apiBase.replace('http://', 'ws://').replace('https://', 'wss://') + '/ws/tasks'
|
|
@@ -176,7 +174,7 @@ function connectWebSocket() {
|
|
|
176
174
|
ws.onmessage = (event) => {
|
|
177
175
|
try {
|
|
178
176
|
const data = JSON.parse(event.data)
|
|
179
|
-
const row = ingestJobs.value.find(
|
|
177
|
+
const row = ingestJobs.value.find(j => j.job_id === data.job_id)
|
|
180
178
|
if (!row) return
|
|
181
179
|
if (data.type === 'job_progress') {
|
|
182
180
|
row.progress = data.progress
|
|
@@ -192,23 +190,21 @@ function connectWebSocket() {
|
|
|
192
190
|
}
|
|
193
191
|
}
|
|
194
192
|
|
|
195
|
-
|
|
196
193
|
function disconnectWebSocket() {
|
|
197
194
|
if (ws) {
|
|
198
|
-
try {
|
|
195
|
+
try {
|
|
196
|
+
ws.close()
|
|
197
|
+
} catch { /* already closed */ }
|
|
199
198
|
ws = null
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
201
|
|
|
203
|
-
|
|
204
202
|
onBeforeUnmount(() => {
|
|
205
203
|
disconnectWebSocket()
|
|
206
204
|
})
|
|
207
205
|
|
|
208
|
-
|
|
209
206
|
// ─── Step 3: build the persona draft ────────────────────────────────────
|
|
210
207
|
|
|
211
|
-
|
|
212
208
|
async function runDescriptionBuild() {
|
|
213
209
|
building.value = true
|
|
214
210
|
buildError.value = null
|
|
@@ -221,12 +217,12 @@ async function runDescriptionBuild() {
|
|
|
221
217
|
body: {
|
|
222
218
|
name: name.value.trim(),
|
|
223
219
|
description: description.value.trim(),
|
|
224
|
-
source_label: sourceLabel.value.trim() || name.value.trim()
|
|
225
|
-
}
|
|
226
|
-
}
|
|
220
|
+
source_label: sourceLabel.value.trim() || name.value.trim()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
227
223
|
)
|
|
228
|
-
if (
|
|
229
|
-
throw new Error(
|
|
224
|
+
if (typeof res.error === 'string') {
|
|
225
|
+
throw new Error(res.error)
|
|
230
226
|
}
|
|
231
227
|
draft.value = res.persona
|
|
232
228
|
chunksUsed.value = 0
|
|
@@ -238,24 +234,23 @@ async function runDescriptionBuild() {
|
|
|
238
234
|
}
|
|
239
235
|
}
|
|
240
236
|
|
|
241
|
-
|
|
242
237
|
async function runBuild() {
|
|
243
238
|
building.value = true
|
|
244
239
|
buildError.value = null
|
|
245
240
|
draft.value = null
|
|
246
241
|
try {
|
|
247
|
-
const res = await $fetch<{ persona: Persona, chunks_used: number, provider_name: string }>(
|
|
242
|
+
const res = await $fetch<{ persona: Persona, chunks_used: number, provider_name: string, error?: string }>(
|
|
248
243
|
`${apiBase}/api/personas/build`,
|
|
249
244
|
{
|
|
250
245
|
method: 'POST',
|
|
251
246
|
body: {
|
|
252
247
|
name: name.value.trim(),
|
|
253
|
-
source_label: sourceLabel.value.trim() || name.value.trim()
|
|
254
|
-
}
|
|
255
|
-
}
|
|
248
|
+
source_label: sourceLabel.value.trim() || name.value.trim()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
256
251
|
)
|
|
257
|
-
if (
|
|
258
|
-
throw new Error(
|
|
252
|
+
if (typeof res.error === 'string') {
|
|
253
|
+
throw new Error(res.error)
|
|
259
254
|
}
|
|
260
255
|
draft.value = res.persona
|
|
261
256
|
chunksUsed.value = res.chunks_used
|
|
@@ -267,45 +262,41 @@ async function runBuild() {
|
|
|
267
262
|
}
|
|
268
263
|
}
|
|
269
264
|
|
|
270
|
-
|
|
271
265
|
// ─── Step 4: save + optional clone ──────────────────────────────────────
|
|
272
266
|
|
|
273
|
-
|
|
274
267
|
async function savePersona() {
|
|
275
268
|
if (!draft.value) return
|
|
276
269
|
saving.value = true
|
|
277
270
|
try {
|
|
278
271
|
const created = await $fetch<Persona>(`${apiBase}/api/personas`, {
|
|
279
272
|
method: 'POST',
|
|
280
|
-
body: draft.value
|
|
273
|
+
body: draft.value
|
|
281
274
|
})
|
|
282
275
|
if (saveAndClone.value && cloneDept.value && cloneTier.value) {
|
|
283
276
|
await $fetch(`${apiBase}/api/personas/${created.id}/clone`, {
|
|
284
277
|
method: 'POST',
|
|
285
|
-
body: { department: cloneDept.value, tier: Number(cloneTier.value) }
|
|
278
|
+
body: { department: cloneDept.value, tier: Number(cloneTier.value) }
|
|
286
279
|
})
|
|
287
280
|
}
|
|
288
281
|
toast.add({
|
|
289
282
|
title: saveAndClone.value ? 'Persona saved + agent cloned' : 'Persona saved',
|
|
290
283
|
description: `${created.name} is now in your board.`,
|
|
291
|
-
color: 'success'
|
|
284
|
+
color: 'success'
|
|
292
285
|
})
|
|
293
286
|
emit('completed', created)
|
|
294
287
|
} catch (err) {
|
|
295
288
|
toast.add({
|
|
296
289
|
title: 'Save failed',
|
|
297
290
|
description: err instanceof Error ? err.message : 'unknown error',
|
|
298
|
-
color: 'error'
|
|
291
|
+
color: 'error'
|
|
299
292
|
})
|
|
300
293
|
} finally {
|
|
301
294
|
saving.value = false
|
|
302
295
|
}
|
|
303
296
|
}
|
|
304
297
|
|
|
305
|
-
|
|
306
298
|
// ─── Auto-advance when ingest completes ─────────────────────────────────
|
|
307
299
|
|
|
308
|
-
|
|
309
300
|
watch(allIngestComplete, async (done) => {
|
|
310
301
|
if (done && step.value === 2 && ingestCompletedCount.value > 0) {
|
|
311
302
|
step.value = 3
|
|
@@ -313,13 +304,11 @@ watch(allIngestComplete, async (done) => {
|
|
|
313
304
|
}
|
|
314
305
|
})
|
|
315
306
|
|
|
316
|
-
|
|
317
307
|
function cancel() {
|
|
318
308
|
disconnectWebSocket()
|
|
319
309
|
emit('cancelled')
|
|
320
310
|
}
|
|
321
311
|
|
|
322
|
-
|
|
323
312
|
function backToStep1() {
|
|
324
313
|
disconnectWebSocket()
|
|
325
314
|
step.value = 1
|
|
@@ -332,13 +321,15 @@ function backToStep1() {
|
|
|
332
321
|
<template #header>
|
|
333
322
|
<div class="flex items-center justify-between">
|
|
334
323
|
<div>
|
|
335
|
-
<h3 class="text-lg font-semibold">
|
|
324
|
+
<h3 class="text-lg font-semibold">
|
|
325
|
+
AI Persona Builder
|
|
326
|
+
</h3>
|
|
336
327
|
<p class="text-sm text-muted mt-1">
|
|
337
328
|
Step {{ step }} of 4 — {{ {
|
|
338
329
|
1: 'Sources',
|
|
339
330
|
2: 'Indexing',
|
|
340
331
|
3: 'Generating DNA',
|
|
341
|
-
4: 'Review & save'
|
|
332
|
+
4: 'Review & save'
|
|
342
333
|
}[step] }}
|
|
343
334
|
</p>
|
|
344
335
|
</div>
|
|
@@ -385,9 +376,9 @@ function backToStep1() {
|
|
|
385
376
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-2">
|
|
386
377
|
<button
|
|
387
378
|
v-for="m in ([
|
|
388
|
-
{ key: 'sources',
|
|
389
|
-
{ key: 'existing',
|
|
390
|
-
{ key: 'description', title: 'From description', desc: 'No sources — pure description' }
|
|
379
|
+
{ key: 'sources', title: 'Ingest sources', desc: 'YouTube, articles, PDFs — best fidelity' },
|
|
380
|
+
{ key: 'existing', title: 'Existing chunks', desc: 'Use what is already indexed' },
|
|
381
|
+
{ key: 'description', title: 'From description', desc: 'No sources — pure description' }
|
|
391
382
|
] as const)"
|
|
392
383
|
:key="m.key"
|
|
393
384
|
type="button"
|
|
@@ -395,8 +386,12 @@ function backToStep1() {
|
|
|
395
386
|
:class="mode === m.key ? 'border-primary bg-primary/5' : 'border-default hover:border-primary/40'"
|
|
396
387
|
@click="mode = m.key"
|
|
397
388
|
>
|
|
398
|
-
<p class="text-sm font-semibold">
|
|
399
|
-
|
|
389
|
+
<p class="text-sm font-semibold">
|
|
390
|
+
{{ m.title }}
|
|
391
|
+
</p>
|
|
392
|
+
<p class="text-xs text-muted mt-1">
|
|
393
|
+
{{ m.desc }}
|
|
394
|
+
</p>
|
|
400
395
|
</button>
|
|
401
396
|
</div>
|
|
402
397
|
</UFormField>
|
|
@@ -425,7 +420,7 @@ function backToStep1() {
|
|
|
425
420
|
:items="archetypes.map((a) => ({
|
|
426
421
|
label: `${a.name} — ${a.title}`,
|
|
427
422
|
icon: 'i-lucide-sparkles',
|
|
428
|
-
onSelect: () => applyArchetype(a)
|
|
423
|
+
onSelect: () => applyArchetype(a)
|
|
429
424
|
}))"
|
|
430
425
|
>
|
|
431
426
|
<UButton
|
|
@@ -466,7 +461,7 @@ function backToStep1() {
|
|
|
466
461
|
:label="(
|
|
467
462
|
mode === 'sources' ? `Index ${sourceLineCount} source${sourceLineCount === 1 ? '' : 's'} & build`
|
|
468
463
|
: mode === 'existing' ? 'Generate from existing knowledge'
|
|
469
|
-
|
|
464
|
+
: 'Generate from description'
|
|
470
465
|
)"
|
|
471
466
|
icon="i-lucide-arrow-right"
|
|
472
467
|
:disabled="(
|
|
@@ -501,18 +496,20 @@ function backToStep1() {
|
|
|
501
496
|
queued: 'i-lucide-clock',
|
|
502
497
|
processing: 'i-lucide-loader-2 animate-spin',
|
|
503
498
|
completed: 'i-lucide-check-circle',
|
|
504
|
-
failed: 'i-lucide-x-circle'
|
|
499
|
+
failed: 'i-lucide-x-circle'
|
|
505
500
|
}[job.status]"
|
|
506
501
|
:class="{
|
|
507
502
|
queued: 'text-muted',
|
|
508
503
|
processing: 'text-primary',
|
|
509
504
|
completed: 'text-green-500',
|
|
510
|
-
failed: 'text-red-500'
|
|
505
|
+
failed: 'text-red-500'
|
|
511
506
|
}[job.status]"
|
|
512
507
|
class="size-4 shrink-0"
|
|
513
508
|
/>
|
|
514
509
|
<div class="flex-1 min-w-0">
|
|
515
|
-
<p class="text-sm font-mono truncate">
|
|
510
|
+
<p class="text-sm font-mono truncate">
|
|
511
|
+
{{ job.source }}
|
|
512
|
+
</p>
|
|
516
513
|
<UProgress
|
|
517
514
|
v-if="job.status === 'processing' || job.status === 'queued'"
|
|
518
515
|
:value="job.progress"
|
|
@@ -520,7 +517,9 @@ function backToStep1() {
|
|
|
520
517
|
size="xs"
|
|
521
518
|
class="mt-1"
|
|
522
519
|
/>
|
|
523
|
-
<p v-if="job.error" class="text-xs text-red-400 mt-1">
|
|
520
|
+
<p v-if="job.error" class="text-xs text-red-400 mt-1">
|
|
521
|
+
{{ job.error }}
|
|
522
|
+
</p>
|
|
524
523
|
</div>
|
|
525
524
|
<span class="text-xs text-muted">{{ job.progress }}%</span>
|
|
526
525
|
</div>
|
|
@@ -543,13 +542,27 @@ function backToStep1() {
|
|
|
543
542
|
<div class="flex items-start gap-3">
|
|
544
543
|
<UIcon name="i-lucide-alert-circle" class="size-5 text-red-500 mt-0.5 shrink-0" />
|
|
545
544
|
<div class="flex-1">
|
|
546
|
-
<p class="text-sm font-medium text-red-400">
|
|
547
|
-
|
|
545
|
+
<p class="text-sm font-medium text-red-400">
|
|
546
|
+
Build failed
|
|
547
|
+
</p>
|
|
548
|
+
<p class="text-xs text-muted mt-1">
|
|
549
|
+
{{ buildError }}
|
|
550
|
+
</p>
|
|
548
551
|
</div>
|
|
549
552
|
</div>
|
|
550
553
|
<div class="flex gap-2 mt-3">
|
|
551
|
-
<UButton
|
|
552
|
-
|
|
554
|
+
<UButton
|
|
555
|
+
label="Retry"
|
|
556
|
+
variant="outline"
|
|
557
|
+
size="sm"
|
|
558
|
+
@click="runBuild"
|
|
559
|
+
/>
|
|
560
|
+
<UButton
|
|
561
|
+
label="Back to sources"
|
|
562
|
+
variant="ghost"
|
|
563
|
+
size="sm"
|
|
564
|
+
@click="backToStep1"
|
|
565
|
+
/>
|
|
553
566
|
</div>
|
|
554
567
|
</div>
|
|
555
568
|
</div>
|
|
@@ -564,7 +577,9 @@ function backToStep1() {
|
|
|
564
577
|
</div>
|
|
565
578
|
|
|
566
579
|
<fieldset class="space-y-3">
|
|
567
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
580
|
+
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
581
|
+
Identity
|
|
582
|
+
</legend>
|
|
568
583
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
569
584
|
<UFormField label="Name">
|
|
570
585
|
<UInput v-model="draft.name" class="w-full" />
|
|
@@ -579,7 +594,9 @@ function backToStep1() {
|
|
|
579
594
|
</fieldset>
|
|
580
595
|
|
|
581
596
|
<fieldset class="space-y-3">
|
|
582
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
597
|
+
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
598
|
+
Behavioural DNA
|
|
599
|
+
</legend>
|
|
583
600
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
584
601
|
<UFormField label="MBTI">
|
|
585
602
|
<UInput v-model="draft.mbti" class="w-full" />
|
|
@@ -588,34 +605,50 @@ function backToStep1() {
|
|
|
588
605
|
<UInput v-model="draft.disc.primary" class="w-full" />
|
|
589
606
|
</UFormField>
|
|
590
607
|
<UFormField label="Enneagram type">
|
|
591
|
-
<UInput
|
|
608
|
+
<UInput
|
|
609
|
+
v-model.number="draft.enneagram.type"
|
|
610
|
+
type="number"
|
|
611
|
+
:min="1"
|
|
612
|
+
:max="9"
|
|
613
|
+
class="w-full"
|
|
614
|
+
/>
|
|
592
615
|
</UFormField>
|
|
593
616
|
<UFormField label="Enneagram wing">
|
|
594
|
-
<UInput
|
|
617
|
+
<UInput
|
|
618
|
+
v-model.number="draft.enneagram.wing"
|
|
619
|
+
type="number"
|
|
620
|
+
:min="1"
|
|
621
|
+
:max="9"
|
|
622
|
+
class="w-full"
|
|
623
|
+
/>
|
|
595
624
|
</UFormField>
|
|
596
625
|
</div>
|
|
597
626
|
</fieldset>
|
|
598
627
|
|
|
599
628
|
<fieldset class="space-y-3">
|
|
600
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
629
|
+
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
630
|
+
Knowledge
|
|
631
|
+
</legend>
|
|
601
632
|
<UFormField label="Mental models" help="comma-separated">
|
|
602
633
|
<UInput
|
|
603
634
|
:model-value="draft.mental_models.join(', ')"
|
|
604
|
-
@update:model-value="(v: string) => draft && (draft.mental_models = v.split(',').map(s => s.trim()).filter(Boolean))"
|
|
605
635
|
class="w-full"
|
|
636
|
+
@update:model-value="(v: string) => draft && (draft.mental_models = v.split(',').map(s => s.trim()).filter(Boolean))"
|
|
606
637
|
/>
|
|
607
638
|
</UFormField>
|
|
608
639
|
<UFormField label="Expertise domains" help="comma-separated">
|
|
609
640
|
<UInput
|
|
610
641
|
:model-value="draft.expertise_domains.join(', ')"
|
|
611
|
-
@update:model-value="(v: string) => draft && (draft.expertise_domains = v.split(',').map(s => s.trim()).filter(Boolean))"
|
|
612
642
|
class="w-full"
|
|
643
|
+
@update:model-value="(v: string) => draft && (draft.expertise_domains = v.split(',').map(s => s.trim()).filter(Boolean))"
|
|
613
644
|
/>
|
|
614
645
|
</UFormField>
|
|
615
646
|
</fieldset>
|
|
616
647
|
|
|
617
648
|
<fieldset class="space-y-3">
|
|
618
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
649
|
+
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-2">
|
|
650
|
+
Save options
|
|
651
|
+
</legend>
|
|
619
652
|
<UCheckbox
|
|
620
653
|
v-model="saveAndClone"
|
|
621
654
|
label="Also clone to an agent immediately"
|