arkaos 3.18.0 → 3.20.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/VERSION CHANGED
@@ -1 +1 @@
1
- 3.18.0
1
+ 3.20.0
@@ -91,6 +91,33 @@ const tierOptions = [
91
91
  { label: 'Tier 3 — Support', value: '3' }
92
92
  ]
93
93
 
94
+ // PR87a v3.19.0 — DNA filters (DISC primary + MBTI group).
95
+ const discFilter = ref<'all' | 'D' | 'I' | 'S' | 'C'>('all')
96
+ const mbtiGroupFilter = ref<'all' | 'analysts' | 'diplomats' | 'sentinels' | 'explorers'>('all')
97
+
98
+ const discOptions = [
99
+ { label: 'All DISC', value: 'all' },
100
+ { label: 'D — Dominance', value: 'D' },
101
+ { label: 'I — Influence', value: 'I' },
102
+ { label: 'S — Steadiness', value: 'S' },
103
+ { label: 'C — Conscientiousness', value: 'C' },
104
+ ]
105
+
106
+ const mbtiGroupOptions = [
107
+ { label: 'All MBTI groups', value: 'all' },
108
+ { label: 'Analysts (NT)', value: 'analysts' },
109
+ { label: 'Diplomats (NF)', value: 'diplomats' },
110
+ { label: 'Sentinels (S__J)', value: 'sentinels' },
111
+ { label: 'Explorers (S__P)', value: 'explorers' },
112
+ ]
113
+
114
+ const MBTI_GROUPS: Record<string, string> = {
115
+ INTJ: 'analysts', INTP: 'analysts', ENTJ: 'analysts', ENTP: 'analysts',
116
+ INFJ: 'diplomats', INFP: 'diplomats', ENFJ: 'diplomats', ENFP: 'diplomats',
117
+ ISTJ: 'sentinels', ISFJ: 'sentinels', ESTJ: 'sentinels', ESFJ: 'sentinels',
118
+ ISTP: 'explorers', ISFP: 'explorers', ESTP: 'explorers', ESFP: 'explorers',
119
+ }
120
+
94
121
  const filteredAgents = computed(() => {
95
122
  let result = agents.value
96
123
  const query = search.value.toLowerCase()
@@ -111,6 +138,16 @@ const filteredAgents = computed(() => {
111
138
  result = result.filter(agent => String(agent.tier) === tierFilter.value)
112
139
  }
113
140
 
141
+ if (discFilter.value !== 'all') {
142
+ result = result.filter(agent => agent.disc?.primary === discFilter.value)
143
+ }
144
+
145
+ if (mbtiGroupFilter.value !== 'all') {
146
+ result = result.filter(
147
+ agent => MBTI_GROUPS[(agent.mbti ?? '').toUpperCase()] === mbtiGroupFilter.value,
148
+ )
149
+ }
150
+
114
151
  if (favoritesOnly.value) {
115
152
  result = result.filter(agent => favs.isAgentFavorite(agent.id))
116
153
  }
@@ -127,7 +164,7 @@ const paginatedAgents = computed(() => {
127
164
 
128
165
  const totalPages = computed(() => Math.max(1, Math.ceil(totalFiltered.value / pageSize)))
129
166
 
130
- watch([search, departmentFilter, tierFilter], () => {
167
+ watch([search, departmentFilter, tierFilter, discFilter, mbtiGroupFilter], () => {
131
168
  page.value = 1
132
169
  })
133
170
 
@@ -381,6 +418,22 @@ async function undoTrashIds(ids: string[]) {
381
418
  aria-label="Filter by tier"
382
419
  />
383
420
 
421
+ <USelect
422
+ v-model="discFilter"
423
+ :items="discOptions"
424
+ placeholder="DISC"
425
+ class="min-w-36"
426
+ aria-label="Filter by DISC primary"
427
+ />
428
+
429
+ <USelect
430
+ v-model="mbtiGroupFilter"
431
+ :items="mbtiGroupOptions"
432
+ placeholder="MBTI group"
433
+ class="min-w-44"
434
+ aria-label="Filter by MBTI group"
435
+ />
436
+
384
437
  <UButton
385
438
  :label="favoritesOnly ? 'All' : 'Favorites'"
386
439
  :icon="favoritesOnly ? 'i-lucide-star' : 'i-lucide-star'"
@@ -142,6 +142,56 @@ const favs = useFavorites()
142
142
  await favs.load()
143
143
  const favoritesOnly = ref(false)
144
144
 
145
+ // PR87b v3.20.0 — import .md persona files.
146
+ const importInput = ref<HTMLInputElement | null>(null)
147
+ const importing = ref(false)
148
+
149
+ function triggerImport() {
150
+ importInput.value?.click()
151
+ }
152
+
153
+ async function onImportFiles(event: Event) {
154
+ const target = event.target as HTMLInputElement
155
+ const files = Array.from(target.files ?? [])
156
+ target.value = ''
157
+ if (files.length === 0) return
158
+ importing.value = true
159
+ try {
160
+ const payload = await Promise.all(
161
+ files.map(async (f) => ({
162
+ name: f.name,
163
+ content: await f.text(),
164
+ })),
165
+ )
166
+ const res = await $fetch<{
167
+ imported: number
168
+ failed: number
169
+ results: Array<{ filename: string, status: string, id?: string, error?: string }>
170
+ error?: string
171
+ }>(`${apiBase}/api/personas/import`, { method: 'POST', body: { files: payload } })
172
+ if (res.error) throw new Error(res.error)
173
+ toast.add({
174
+ title: res.imported > 0
175
+ ? `Imported ${res.imported} persona${res.imported === 1 ? '' : 's'}`
176
+ : 'Nothing imported',
177
+ description: res.failed > 0 ? `${res.failed} failed` : undefined,
178
+ color: res.imported > 0 && res.failed === 0
179
+ ? 'success'
180
+ : res.imported > 0 ? 'warning' : 'error',
181
+ icon: 'i-lucide-file-down',
182
+ })
183
+ await refreshAll()
184
+ } catch (err) {
185
+ toast.add({
186
+ title: 'Import failed',
187
+ description: err instanceof Error ? err.message : 'unknown error',
188
+ color: 'error',
189
+ })
190
+ } finally {
191
+ importing.value = false
192
+ }
193
+ }
194
+
145
195
  // PR83b v3.4.0 — bulk selection + delete.
146
196
  const toast = useToast()
147
197
  const confirmDialog = useConfirmDialog()
@@ -262,6 +312,22 @@ async function undoTrashIds(ids: string[]) {
262
312
  />
263
313
  </template>
264
314
  <template #right>
315
+ <UButton
316
+ label="Import .md"
317
+ icon="i-lucide-file-up"
318
+ variant="soft"
319
+ size="sm"
320
+ :loading="importing"
321
+ @click="triggerImport"
322
+ />
323
+ <input
324
+ ref="importInput"
325
+ type="file"
326
+ accept=".md,text/markdown"
327
+ multiple
328
+ class="hidden"
329
+ @change="onImportFiles"
330
+ />
265
331
  <UButton
266
332
  label="New Persona"
267
333
  icon="i-lucide-plus"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.18.0",
3
+ "version": "3.20.0",
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.18.0"
3
+ version = "3.20.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1494,6 +1494,66 @@ def global_search(q: str = "", limit: int = 20):
1494
1494
  return {"results": results[: max(0, int(limit))]}
1495
1495
 
1496
1496
 
1497
+ # --- Persona import from Markdown (PR87b v3.20.0) ---
1498
+
1499
+ @app.post("/api/personas/import")
1500
+ def personas_import(body: dict):
1501
+ """Import persona Markdown files (frontmatter + body) into the store.
1502
+
1503
+ Body: {"files": [{"name": "Alex.md", "content": "<full file body>"}]}
1504
+ Returns: {imported, failed, results: [{filename, status, id?, error?}]}
1505
+
1506
+ Each file MUST have YAML frontmatter with ``type: persona``. Files
1507
+ lacking the frontmatter are flagged as failed without partial
1508
+ side-effects.
1509
+ """
1510
+ files = body.get("files") or []
1511
+ if not isinstance(files, list):
1512
+ return {"error": "files must be a list"}
1513
+ mgr = _get_persona_manager()
1514
+ if not mgr:
1515
+ return {"error": "Persona manager unavailable"}
1516
+
1517
+ from pathlib import Path as _Path
1518
+
1519
+ from core.personas.obsidian_store import ObsidianPersonaStore
1520
+
1521
+ imported = 0
1522
+ failed = 0
1523
+ results: list[dict] = []
1524
+ for entry in files:
1525
+ if not isinstance(entry, dict):
1526
+ failed += 1
1527
+ results.append({"filename": "(invalid)", "status": "failed", "error": "not an object"})
1528
+ continue
1529
+ filename = str(entry.get("name") or "")
1530
+ content = str(entry.get("content") or "")
1531
+ if not content.strip():
1532
+ failed += 1
1533
+ results.append({"filename": filename, "status": "failed", "error": "empty content"})
1534
+ continue
1535
+ fm = ObsidianPersonaStore._parse_frontmatter(content)
1536
+ if not fm or str(fm.get("type", "")).lower() != "persona":
1537
+ failed += 1
1538
+ results.append({
1539
+ "filename": filename, "status": "failed",
1540
+ "error": "missing or invalid frontmatter (type: persona required)",
1541
+ })
1542
+ continue
1543
+ try:
1544
+ persona = ObsidianPersonaStore._frontmatter_to_persona(
1545
+ fm, _Path(filename or "imported.md"),
1546
+ )
1547
+ mgr.create(persona)
1548
+ imported += 1
1549
+ results.append({"filename": filename, "status": "ok", "id": persona.id})
1550
+ except Exception as exc: # noqa: BLE001
1551
+ failed += 1
1552
+ results.append({"filename": filename, "status": "failed", "error": str(exc)})
1553
+
1554
+ return {"imported": imported, "failed": failed, "results": results}
1555
+
1556
+
1497
1557
  # --- Trash / Undo (PR85b v3.12.0) ---
1498
1558
 
1499
1559
  @app.get("/api/trash")