arkaos 3.38.0 → 3.40.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.38.0
1
+ 3.40.0
@@ -69,9 +69,13 @@ async function refreshAll() {
69
69
  await Promise.all([refresh(), refreshActivity()])
70
70
  }
71
71
 
72
- const search = ref('')
73
- const departmentFilter = ref('all')
74
- const tierFilter = ref('all')
72
+ // PR92b v3.40.0 — initial values come from ?q=...&dept=...&tier=...
73
+ // so /agents links can deep-link to a filtered view.
74
+ const route = useRoute()
75
+ const router = useRouter()
76
+ const search = ref(String(route.query.q ?? ''))
77
+ const departmentFilter = ref(String(route.query.dept ?? 'all'))
78
+ const tierFilter = ref(String(route.query.tier ?? 'all'))
75
79
  const page = ref(1)
76
80
  const pageSize = 15
77
81
 
@@ -92,8 +96,13 @@ const tierOptions = [
92
96
  ]
93
97
 
94
98
  // 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')
99
+ // PR92b v3.40.0 seed from URL query.
100
+ const discFilter = ref<'all' | 'D' | 'I' | 'S' | 'C'>(
101
+ (route.query.disc as any) ?? 'all',
102
+ )
103
+ const mbtiGroupFilter = ref<'all' | 'analysts' | 'diplomats' | 'sentinels' | 'explorers'>(
104
+ (route.query.mbti as any) ?? 'all',
105
+ )
97
106
 
98
107
  const discOptions = [
99
108
  { label: 'All DISC', value: 'all' },
@@ -164,6 +173,23 @@ const paginatedAgents = computed(() => {
164
173
 
165
174
  const totalPages = computed(() => Math.max(1, Math.ceil(totalFiltered.value / pageSize)))
166
175
 
176
+ // PR92b v3.40.0 — push filter state to URL so deep-links survive reload
177
+ // and the browser back/forward buttons work as expected.
178
+ watch(
179
+ [search, departmentFilter, tierFilter, discFilter, mbtiGroupFilter, favoritesOnly],
180
+ () => {
181
+ const query: Record<string, string> = {}
182
+ if (search.value.trim()) query.q = search.value.trim()
183
+ if (departmentFilter.value !== 'all') query.dept = departmentFilter.value
184
+ if (tierFilter.value !== 'all') query.tier = tierFilter.value
185
+ if (discFilter.value !== 'all') query.disc = discFilter.value
186
+ if (mbtiGroupFilter.value !== 'all') query.mbti = mbtiGroupFilter.value
187
+ if (favoritesOnly.value) query.fav = '1'
188
+ router.replace({ query })
189
+ },
190
+ { flush: 'post' },
191
+ )
192
+
167
193
  watch([search, departmentFilter, tierFilter, discFilter, mbtiGroupFilter], () => {
168
194
  page.value = 1
169
195
  })
@@ -200,9 +226,10 @@ function goToAgent(id: string) {
200
226
  }
201
227
 
202
228
  // PR86a v3.15.0 — favorites.
229
+ // PR92b v3.40.0 — favoritesOnly persists in URL (`?fav=1`).
203
230
  const favs = useFavorites()
204
231
  await favs.load()
205
- const favoritesOnly = ref(false)
232
+ const favoritesOnly = ref(route.query.fav === '1')
206
233
 
207
234
  // PR83b v3.4.0 — bulk selection + delete.
208
235
  // PR84b v3.8.0 — bulk move department.
@@ -142,6 +142,40 @@ const favs = useFavorites()
142
142
  await favs.load()
143
143
  const favoritesOnly = ref(false)
144
144
 
145
+ // PR92a v3.39.0 — bulk export every persona as a zip.
146
+ const exportingZip = ref(false)
147
+ async function exportAllAsZip() {
148
+ exportingZip.value = true
149
+ try {
150
+ const blob = await $fetch<Blob>(
151
+ `${apiBase}/api/personas/export-all.zip`,
152
+ { responseType: 'blob' },
153
+ )
154
+ const url = URL.createObjectURL(blob)
155
+ const a = document.createElement('a')
156
+ a.href = url
157
+ a.download = 'arkaos-personas.zip'
158
+ document.body.appendChild(a)
159
+ a.click()
160
+ a.remove()
161
+ URL.revokeObjectURL(url)
162
+ toast.add({
163
+ title: 'ZIP downloaded',
164
+ description: 'arkaos-personas.zip',
165
+ color: 'success',
166
+ icon: 'i-lucide-archive',
167
+ })
168
+ } catch (err) {
169
+ toast.add({
170
+ title: 'Export failed',
171
+ description: err instanceof Error ? err.message : 'unknown error',
172
+ color: 'error',
173
+ })
174
+ } finally {
175
+ exportingZip.value = false
176
+ }
177
+ }
178
+
145
179
  // PR87b v3.20.0 — import .md persona files.
146
180
  // PR91b v3.36.0 — extended with URL import.
147
181
  const importInput = ref<HTMLInputElement | null>(null)
@@ -354,6 +388,14 @@ async function undoTrashIds(ids: string[]) {
354
388
  />
355
389
  </template>
356
390
  <template #right>
391
+ <UButton
392
+ label="Export ZIP"
393
+ icon="i-lucide-archive"
394
+ variant="ghost"
395
+ size="sm"
396
+ :loading="exportingZip"
397
+ @click="exportAllAsZip"
398
+ />
357
399
  <UDropdownMenu
358
400
  :items="[
359
401
  { label: 'Pick .md files…', icon: 'i-lucide-file-up', onSelect: triggerImport },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.38.0",
3
+ "version": "3.40.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.38.0"
3
+ version = "3.40.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"}
@@ -322,6 +322,68 @@ def agent_activity_strip(agent_id: str, period: str = "month"):
322
322
  }
323
323
 
324
324
 
325
+ @app.get("/api/personas/export-all.zip")
326
+ def personas_export_all():
327
+ """PR92a v3.39.0 — stream every persona as Markdown inside a ZIP.
328
+
329
+ Iterates `PersonaManager.list_all()` plus any Obsidian-vault entries
330
+ surfaced via `persona_detail`, renders each through
331
+ `ObsidianPersonaStore._render`, and zips them. Filename uses the
332
+ persona name (sanitised), falling back to id.
333
+ """
334
+ mgr = _get_persona_manager()
335
+ if not mgr:
336
+ return {"error": "Persona manager unavailable"}
337
+ try:
338
+ items = list(mgr.list_all() or [])
339
+ except Exception as exc: # noqa: BLE001
340
+ return {"error": f"list failed: {exc}"}
341
+
342
+ from core.personas.obsidian_store import ObsidianPersonaStore
343
+
344
+ import io
345
+ import zipfile
346
+
347
+ buffer = io.BytesIO()
348
+ seen: set[str] = set()
349
+ written = 0
350
+ with zipfile.ZipFile(buffer, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
351
+ for p in items:
352
+ persona = p if hasattr(p, "model_dump") else None
353
+ if persona is None:
354
+ continue
355
+ slug = _zip_persona_slug(persona.name or persona.id)
356
+ if slug in seen:
357
+ slug = f"{slug}-{persona.id[:6]}"
358
+ seen.add(slug)
359
+ try:
360
+ body = ObsidianPersonaStore._render(persona)
361
+ except Exception: # noqa: BLE001
362
+ continue
363
+ zf.writestr(f"{slug}.md", body)
364
+ written += 1
365
+
366
+ if written == 0:
367
+ return {"error": "no personas to export"}
368
+
369
+ from fastapi import Response
370
+ return Response(
371
+ content=buffer.getvalue(),
372
+ media_type="application/zip",
373
+ headers={
374
+ "Content-Disposition": 'attachment; filename="arkaos-personas.zip"',
375
+ },
376
+ )
377
+
378
+
379
+ def _zip_persona_slug(name: str) -> str:
380
+ """Sanitise a name for use as a zip member filename."""
381
+ import re
382
+ cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", str(name or ""))
383
+ cleaned = cleaned.strip("-") or "persona"
384
+ return cleaned[:80]
385
+
386
+
325
387
  @app.get("/api/personas/{persona_id}/markdown")
326
388
  def persona_download_markdown(persona_id: str):
327
389
  """PR90a v3.31.0 — return the persona as a Markdown file.