arkaos 3.29.0 → 3.31.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.29.0
1
+ 3.31.0
@@ -105,6 +105,41 @@ function markedHtml(src: string): string {
105
105
  }
106
106
  }
107
107
 
108
+ // PR89d v3.30.0 — download YAML.
109
+ const downloadingYaml = ref(false)
110
+ async function downloadYaml() {
111
+ if (!agent.value) return
112
+ downloadingYaml.value = true
113
+ try {
114
+ const blob = await $fetch<Blob>(
115
+ `${apiBase}/api/agents/${agentId}/yaml`,
116
+ { responseType: 'blob' },
117
+ )
118
+ const url = URL.createObjectURL(blob)
119
+ const a = document.createElement('a')
120
+ a.href = url
121
+ a.download = `${agentId}.yaml`
122
+ document.body.appendChild(a)
123
+ a.click()
124
+ a.remove()
125
+ URL.revokeObjectURL(url)
126
+ toast.add({
127
+ title: 'YAML downloaded',
128
+ description: `${agentId}.yaml`,
129
+ color: 'success',
130
+ icon: 'i-lucide-download',
131
+ })
132
+ } catch (err) {
133
+ toast.add({
134
+ title: 'Download failed',
135
+ description: err instanceof Error ? err.message : 'unknown error',
136
+ color: 'error',
137
+ })
138
+ } finally {
139
+ downloadingYaml.value = false
140
+ }
141
+ }
142
+
108
143
  // PR86c v3.17.0 — export to Obsidian.
109
144
  const exporting = ref(false)
110
145
  async function exportToVault() {
@@ -359,6 +394,14 @@ function formatTokens(n: number): string {
359
394
  :loading="exporting"
360
395
  @click="exportToVault"
361
396
  />
397
+ <UButton
398
+ label="YAML"
399
+ icon="i-lucide-download"
400
+ variant="ghost"
401
+ size="sm"
402
+ :loading="downloadingYaml"
403
+ @click="downloadYaml"
404
+ />
362
405
  <UButton
363
406
  label="Edit"
364
407
  icon="i-lucide-pencil"
@@ -241,6 +241,41 @@ function markedHtml(src: string): string {
241
241
  }
242
242
  }
243
243
 
244
+ // PR90a v3.31.0 — download persona as Markdown.
245
+ const downloadingMd = ref(false)
246
+ async function downloadMarkdown() {
247
+ if (!detail.value) return
248
+ downloadingMd.value = true
249
+ try {
250
+ const blob = await $fetch<Blob>(
251
+ `${apiBase}/api/personas/${personaId}/markdown`,
252
+ { responseType: 'blob' },
253
+ )
254
+ const url = URL.createObjectURL(blob)
255
+ const a = document.createElement('a')
256
+ a.href = url
257
+ a.download = `${detail.value.name || personaId}.md`
258
+ document.body.appendChild(a)
259
+ a.click()
260
+ a.remove()
261
+ URL.revokeObjectURL(url)
262
+ toast.add({
263
+ title: 'Markdown downloaded',
264
+ description: `${detail.value.name || personaId}.md`,
265
+ color: 'success',
266
+ icon: 'i-lucide-download',
267
+ })
268
+ } catch (err) {
269
+ toast.add({
270
+ title: 'Download failed',
271
+ description: err instanceof Error ? err.message : 'unknown error',
272
+ color: 'error',
273
+ })
274
+ } finally {
275
+ downloadingMd.value = false
276
+ }
277
+ }
278
+
244
279
  // PR85a v3.11.0 — Clone to Agent dialog.
245
280
  const cloneOpen = ref(false)
246
281
  function onCloned(agentId: string) {
@@ -526,6 +561,14 @@ const vocabOptions = [
526
561
  size="sm"
527
562
  @click="cloneOpen = true"
528
563
  />
564
+ <UButton
565
+ label="MD"
566
+ icon="i-lucide-download"
567
+ variant="ghost"
568
+ size="sm"
569
+ :loading="downloadingMd"
570
+ @click="downloadMarkdown"
571
+ />
529
572
  <UButton label="Edit" icon="i-lucide-pencil" size="sm" @click="startEdit" />
530
573
  <UButton
531
574
  icon="i-lucide-trash-2"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.29.0",
3
+ "version": "3.31.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.29.0"
3
+ version = "3.31.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,79 @@ def agent_activity_strip(agent_id: str, period: str = "month"):
322
322
  }
323
323
 
324
324
 
325
+ @app.get("/api/personas/{persona_id}/markdown")
326
+ def persona_download_markdown(persona_id: str):
327
+ """PR90a v3.31.0 — return the persona as a Markdown file.
328
+
329
+ Renders via ObsidianPersonaStore._render so the output matches
330
+ exactly what gets written when the operator clicks Save with a
331
+ configured vault. Responds with ``text/markdown`` and an
332
+ attachment Content-Disposition.
333
+ """
334
+ detail = persona_detail(persona_id)
335
+ if "error" in detail:
336
+ return detail
337
+ from core.personas.obsidian_store import ObsidianPersonaStore
338
+ from core.personas.schema import (
339
+ Persona, PersonaDISC, PersonaEnneagram, PersonaBigFive, PersonaCommunication,
340
+ )
341
+ try:
342
+ persona = Persona(
343
+ id=detail.get("id", ""),
344
+ name=detail.get("name", ""),
345
+ title=detail.get("title", ""),
346
+ tagline=detail.get("tagline", ""),
347
+ source=detail.get("source", ""),
348
+ disc=PersonaDISC(**(detail.get("disc") or {})),
349
+ enneagram=PersonaEnneagram(**(detail.get("enneagram") or {})),
350
+ big_five=PersonaBigFive(**(detail.get("big_five") or {})),
351
+ mbti=detail.get("mbti", "INTJ"),
352
+ mental_models=detail.get("mental_models") or [],
353
+ expertise_domains=detail.get("expertise_domains") or [],
354
+ frameworks=detail.get("frameworks") or [],
355
+ key_quotes=detail.get("key_quotes") or [],
356
+ communication=PersonaCommunication(**(detail.get("communication") or {})),
357
+ bio_md=detail.get("bio_md", "") or "",
358
+ created_at=detail.get("created_at", ""),
359
+ )
360
+ except (TypeError, ValueError) as exc:
361
+ return {"error": f"persona schema mismatch: {exc}"}
362
+ content = ObsidianPersonaStore._render(persona)
363
+ filename = f"{persona.name or persona.id}.md".replace("/", "-")
364
+ from fastapi import Response
365
+ return Response(
366
+ content=content,
367
+ media_type="text/markdown",
368
+ headers={
369
+ "Content-Disposition": f'attachment; filename="{filename}"',
370
+ },
371
+ )
372
+
373
+
374
+ @app.get("/api/agents/{agent_id}/yaml")
375
+ def agent_download_yaml(agent_id: str):
376
+ """PR89d v3.30.0 — return the raw YAML for the agent.
377
+
378
+ Responds with ``application/x-yaml`` and an attachment Content-
379
+ Disposition so browsers prompt a Save As. Refuses unknown agents.
380
+ """
381
+ yaml_file = _resolve_agent_yaml(agent_id)
382
+ if yaml_file is None:
383
+ return {"error": "Agent not found"}
384
+ try:
385
+ content = yaml_file.read_text(encoding="utf-8")
386
+ except OSError as exc:
387
+ return {"error": f"read failed: {exc}"}
388
+ from fastapi import Response
389
+ return Response(
390
+ content=content,
391
+ media_type="application/x-yaml",
392
+ headers={
393
+ "Content-Disposition": f'attachment; filename="{yaml_file.name}"',
394
+ },
395
+ )
396
+
397
+
325
398
  @app.get("/api/agents/{agent_id}/history")
326
399
  def agent_history(agent_id: str, limit: int = 20):
327
400
  """PR88d v3.26.0 — combined history for an agent.