arkaos 3.23.0 → 3.24.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.23.0
1
+ 3.24.0
@@ -48,6 +48,13 @@ const links = [[{
48
48
  onSelect: () => {
49
49
  open.value = false
50
50
  }
51
+ }, {
52
+ label: 'Workflows',
53
+ icon: 'i-lucide-workflow',
54
+ to: '/workflows',
55
+ onSelect: () => {
56
+ open.value = false
57
+ }
51
58
  }, {
52
59
  label: 'Knowledge',
53
60
  icon: 'i-lucide-brain',
@@ -0,0 +1,191 @@
1
+ <script setup lang="ts">
2
+ // PR88b v3.24.0 — Workflows browser.
3
+ //
4
+ // Lists every YAML workflow under departments/*/workflows/*.yaml with
5
+ // metadata pulled from the file's frontmatter-ish top keys. Selecting
6
+ // a row opens a side panel with the raw YAML for inspection.
7
+
8
+ import type { TableColumn } from '@nuxt/ui'
9
+
10
+ interface Workflow {
11
+ id: string
12
+ name: string
13
+ description: string
14
+ department: string
15
+ tier: string
16
+ command: string
17
+ phases_count: number
18
+ file: string
19
+ content: string
20
+ }
21
+
22
+ const { fetchApi } = useApi()
23
+ const { data, status, error, refresh } = await fetchApi<{ workflows: Workflow[] }>('/api/workflows')
24
+
25
+ const workflows = computed(() => data.value?.workflows ?? [])
26
+ const search = ref('')
27
+ const deptFilter = ref<'all' | string>('all')
28
+ const selected = ref<Workflow | null>(null)
29
+
30
+ const departments = computed(() => {
31
+ const set = new Set<string>()
32
+ for (const w of workflows.value) if (w.department) set.add(w.department)
33
+ return [
34
+ { label: 'All departments', value: 'all' },
35
+ ...Array.from(set).sort().map((d) => ({ label: d, value: d })),
36
+ ]
37
+ })
38
+
39
+ const filtered = computed(() => {
40
+ let result = workflows.value
41
+ if (deptFilter.value !== 'all') {
42
+ result = result.filter((w) => w.department === deptFilter.value)
43
+ }
44
+ const q = search.value.toLowerCase().trim()
45
+ if (q) {
46
+ result = result.filter((w) =>
47
+ w.name.toLowerCase().includes(q)
48
+ || w.description.toLowerCase().includes(q)
49
+ || w.command.toLowerCase().includes(q)
50
+ || w.id.toLowerCase().includes(q),
51
+ )
52
+ }
53
+ return result
54
+ })
55
+
56
+ const tierColor = (tier: string) => {
57
+ const m: Record<string, 'primary' | 'success' | 'warning' | 'neutral'> = {
58
+ enterprise: 'primary',
59
+ focused: 'success',
60
+ specialist: 'warning',
61
+ }
62
+ return m[tier] ?? 'neutral'
63
+ }
64
+
65
+ const columns: TableColumn<Workflow>[] = [
66
+ { accessorKey: 'name', header: 'Name' },
67
+ { accessorKey: 'department', header: 'Department' },
68
+ { accessorKey: 'tier', header: 'Tier' },
69
+ { accessorKey: 'command', header: 'Command' },
70
+ { accessorKey: 'phases_count', header: 'Phases' },
71
+ ]
72
+ </script>
73
+
74
+ <template>
75
+ <UDashboardPanel id="workflows">
76
+ <template #header>
77
+ <UDashboardNavbar title="Workflows">
78
+ <template #leading>
79
+ <UDashboardSidebarCollapse />
80
+ </template>
81
+ <template #trailing>
82
+ <UBadge v-if="workflows.length" :label="String(workflows.length)" variant="subtle" />
83
+ </template>
84
+ </UDashboardNavbar>
85
+ </template>
86
+
87
+ <template #body>
88
+ <DashboardState
89
+ :status="status"
90
+ :error="error"
91
+ :empty="!workflows.length"
92
+ empty-title="No workflows found"
93
+ empty-description="YAML workflows live under departments/*/workflows/."
94
+ empty-icon="i-lucide-workflow"
95
+ loading-label="Scanning workflows"
96
+ :on-retry="() => refresh()"
97
+ >
98
+ <div class="flex flex-wrap items-center gap-3 mb-4">
99
+ <UInput
100
+ v-model="search"
101
+ class="max-w-sm"
102
+ icon="i-lucide-search"
103
+ placeholder="Search name, command, description…"
104
+ />
105
+ <USelect
106
+ v-model="deptFilter"
107
+ :items="departments"
108
+ placeholder="Department"
109
+ class="min-w-44"
110
+ />
111
+ <span class="ml-auto text-xs text-muted">
112
+ {{ filtered.length }} workflow{{ filtered.length === 1 ? '' : 's' }}
113
+ </span>
114
+ </div>
115
+
116
+ <div class="grid grid-cols-1 xl:grid-cols-[1.4fr_1fr] gap-4">
117
+ <UTable
118
+ :data="filtered"
119
+ :columns="columns"
120
+ :loading="status === 'pending'"
121
+ :ui="{
122
+ tbody: '[&>tr]:cursor-pointer [&>tr]:hover:bg-elevated/50 [&>tr]:transition-colors',
123
+ th: 'py-2 first:rounded-l-lg last:rounded-r-lg border-y border-default first:border-l last:border-r',
124
+ td: 'border-b border-default',
125
+ }"
126
+ @select="(row: { original: Workflow }) => selected = row.original"
127
+ >
128
+ <template #name-cell="{ row }">
129
+ <div class="min-w-0">
130
+ <p class="font-medium truncate">{{ row.original.name }}</p>
131
+ <p class="text-xs text-muted font-mono truncate">{{ row.original.id }}</p>
132
+ </div>
133
+ </template>
134
+ <template #department-cell="{ row }">
135
+ <UBadge :label="row.original.department" variant="subtle" size="sm" />
136
+ </template>
137
+ <template #tier-cell="{ row }">
138
+ <UBadge
139
+ v-if="row.original.tier"
140
+ :label="row.original.tier"
141
+ :color="tierColor(row.original.tier)"
142
+ variant="subtle"
143
+ size="sm"
144
+ />
145
+ <span v-else class="text-xs text-muted">—</span>
146
+ </template>
147
+ <template #command-cell="{ row }">
148
+ <code v-if="row.original.command" class="text-xs font-mono">{{ row.original.command }}</code>
149
+ <span v-else class="text-xs text-muted">—</span>
150
+ </template>
151
+ <template #phases_count-cell="{ row }">
152
+ <span class="font-mono text-sm">{{ row.original.phases_count }}</span>
153
+ </template>
154
+ </UTable>
155
+
156
+ <div
157
+ v-if="selected"
158
+ class="rounded-lg border border-default overflow-hidden flex flex-col"
159
+ >
160
+ <div class="px-4 py-3 border-b border-default bg-elevated/30">
161
+ <div class="flex items-start justify-between gap-3">
162
+ <div class="min-w-0">
163
+ <p class="text-xs text-muted uppercase tracking-wide">YAML preview</p>
164
+ <p class="font-semibold truncate">{{ selected.name }}</p>
165
+ <p class="text-xs text-muted font-mono truncate">{{ selected.file }}</p>
166
+ </div>
167
+ <UButton
168
+ icon="i-lucide-x"
169
+ variant="ghost"
170
+ size="xs"
171
+ aria-label="Close preview"
172
+ @click="selected = null"
173
+ />
174
+ </div>
175
+ <p v-if="selected.description" class="text-xs text-muted mt-2">
176
+ {{ selected.description }}
177
+ </p>
178
+ </div>
179
+ <pre class="p-4 text-xs font-mono overflow-x-auto whitespace-pre">{{ selected.content }}</pre>
180
+ </div>
181
+ <div
182
+ v-else
183
+ class="rounded-lg border border-dashed border-default p-8 text-center text-sm text-muted self-start"
184
+ >
185
+ Click a row to preview its YAML.
186
+ </div>
187
+ </div>
188
+ </DashboardState>
189
+ </template>
190
+ </UDashboardPanel>
191
+ </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.23.0",
3
+ "version": "3.24.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.23.0"
3
+ version = "3.24.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"}
@@ -1387,6 +1387,46 @@ def agent_export_to_vault(agent_id: str):
1387
1387
  return {"exported": True, "path": str(res.path), "vault_path": str(res.vault_path)}
1388
1388
 
1389
1389
 
1390
+ # --- Workflows (PR88b v3.24.0) ---
1391
+
1392
+ @app.get("/api/workflows")
1393
+ def workflows_list():
1394
+ """Scan departments/*/workflows/*.yaml and return metadata + content."""
1395
+ out: list[dict] = []
1396
+ dept_root = ARKAOS_ROOT / "departments"
1397
+ if not dept_root.exists():
1398
+ return {"workflows": []}
1399
+ try:
1400
+ import yaml as _yaml
1401
+ except ImportError:
1402
+ return {"workflows": [], "error": "PyYAML unavailable"}
1403
+ for path in sorted(dept_root.glob("*/workflows/*.yaml")):
1404
+ try:
1405
+ content = path.read_text(encoding="utf-8")
1406
+ except OSError:
1407
+ continue
1408
+ try:
1409
+ raw = _yaml.safe_load(content) or {}
1410
+ except Exception: # noqa: BLE001
1411
+ raw = {}
1412
+ if not isinstance(raw, dict):
1413
+ continue
1414
+ phases = raw.get("phases") if isinstance(raw.get("phases"), list) else []
1415
+ rel = path.relative_to(ARKAOS_ROOT).as_posix()
1416
+ out.append({
1417
+ "id": str(raw.get("id") or path.stem),
1418
+ "name": str(raw.get("name") or path.stem),
1419
+ "description": str(raw.get("description") or ""),
1420
+ "department": str(raw.get("department") or path.parent.parent.name),
1421
+ "tier": str(raw.get("tier") or ""),
1422
+ "command": str(raw.get("command") or ""),
1423
+ "phases_count": len(phases),
1424
+ "file": rel,
1425
+ "content": content,
1426
+ })
1427
+ return {"workflows": out}
1428
+
1429
+
1390
1430
  # --- Sidebar stats widget (PR87d v3.22.0) ---
1391
1431
 
1392
1432
  @app.get("/api/sidebar-stats")