arkaos 3.52.0 → 3.53.1
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.
|
|
1
|
+
3.53.1
|
|
@@ -52,6 +52,67 @@ const compareOptions = computed(() =>
|
|
|
52
52
|
})),
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
+
// PR95c v3.53.0 — Merge this department's agents into another.
|
|
56
|
+
const { apiBase } = useApi()
|
|
57
|
+
const toast = useToast()
|
|
58
|
+
const confirmDialog = useConfirmDialog()
|
|
59
|
+
const merging = ref(false)
|
|
60
|
+
|
|
61
|
+
const mergeOptions = computed(() =>
|
|
62
|
+
(deptListData.value?.departments ?? [])
|
|
63
|
+
.filter((d) => d.department !== deptId.value)
|
|
64
|
+
.map((d) => ({
|
|
65
|
+
label: `Move all into ${d.department}`,
|
|
66
|
+
icon: 'i-lucide-folder-input',
|
|
67
|
+
onSelect: () => mergeInto(d.department),
|
|
68
|
+
})),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async function mergeInto(target: string) {
|
|
72
|
+
const ok = await confirmDialog({
|
|
73
|
+
title: `Merge ${deptId.value} → ${target}?`,
|
|
74
|
+
description: `Every agent in '${deptId.value}' will be moved to '${target}'. Tier 0 agents are skipped. This is reversible per-agent from /trash.`,
|
|
75
|
+
confirmLabel: `Move all into ${target}`,
|
|
76
|
+
cancelLabel: 'Cancel',
|
|
77
|
+
variant: 'danger',
|
|
78
|
+
})
|
|
79
|
+
if (!ok) return
|
|
80
|
+
merging.value = true
|
|
81
|
+
try {
|
|
82
|
+
const res = await $fetch<{
|
|
83
|
+
moved: number
|
|
84
|
+
skipped: number
|
|
85
|
+
failed: number
|
|
86
|
+
error?: string
|
|
87
|
+
}>(`${apiBase}/api/departments/${deptId.value}/merge-into/${target}`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
})
|
|
90
|
+
if (res.error) throw new Error(res.error)
|
|
91
|
+
toast.add({
|
|
92
|
+
title: res.moved > 0
|
|
93
|
+
? `Merged ${res.moved} agent${res.moved === 1 ? '' : 's'} into ${target}`
|
|
94
|
+
: 'Nothing moved',
|
|
95
|
+
description: [
|
|
96
|
+
res.skipped > 0 ? `${res.skipped} skipped (Tier 0)` : '',
|
|
97
|
+
res.failed > 0 ? `${res.failed} failed` : '',
|
|
98
|
+
].filter(Boolean).join(' · ') || undefined,
|
|
99
|
+
color: res.failed > 0
|
|
100
|
+
? 'warning'
|
|
101
|
+
: res.moved > 0 ? 'success' : 'info',
|
|
102
|
+
icon: 'i-lucide-folder-input',
|
|
103
|
+
})
|
|
104
|
+
if (res.moved > 0) navigateTo('/departments')
|
|
105
|
+
} catch (err) {
|
|
106
|
+
toast.add({
|
|
107
|
+
title: 'Merge failed',
|
|
108
|
+
description: err instanceof Error ? err.message : 'unknown error',
|
|
109
|
+
color: 'error',
|
|
110
|
+
})
|
|
111
|
+
} finally {
|
|
112
|
+
merging.value = false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
55
116
|
const errorMsg = computed(() => data.value?.error || error.value?.message || null)
|
|
56
117
|
const detail = computed<DeptDetail | null>(() => {
|
|
57
118
|
if (!data.value || data.value.error) return null
|
|
@@ -90,6 +151,17 @@ const tierColor = (tier: number | undefined) => {
|
|
|
90
151
|
trailing-icon="i-lucide-chevron-down"
|
|
91
152
|
/>
|
|
92
153
|
</UDropdownMenu>
|
|
154
|
+
<UDropdownMenu v-if="mergeOptions.length > 0" :items="mergeOptions">
|
|
155
|
+
<UButton
|
|
156
|
+
label="Merge"
|
|
157
|
+
icon="i-lucide-folder-input"
|
|
158
|
+
variant="soft"
|
|
159
|
+
color="warning"
|
|
160
|
+
size="sm"
|
|
161
|
+
:loading="merging"
|
|
162
|
+
trailing-icon="i-lucide-chevron-down"
|
|
163
|
+
/>
|
|
164
|
+
</UDropdownMenu>
|
|
93
165
|
</template>
|
|
94
166
|
</UDashboardNavbar>
|
|
95
167
|
</template>
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -1801,6 +1801,70 @@ def departments_list():
|
|
|
1801
1801
|
return {"departments": out, "total": len(out)}
|
|
1802
1802
|
|
|
1803
1803
|
|
|
1804
|
+
@app.post("/api/departments/{src}/merge-into/{dst}")
|
|
1805
|
+
def department_merge(src: str, dst: str):
|
|
1806
|
+
"""PR95c v3.53.0 — move every agent from `src` department to `dst`.
|
|
1807
|
+
|
|
1808
|
+
Returns ``{src, dst, moved, skipped, failed, results}``. Tier 0
|
|
1809
|
+
agents are skipped (governance fixtures). Refuses self-merge,
|
|
1810
|
+
unknown source / destination, or empty source.
|
|
1811
|
+
"""
|
|
1812
|
+
src = src.strip().lower()
|
|
1813
|
+
dst = dst.strip().lower()
|
|
1814
|
+
if not src or not dst:
|
|
1815
|
+
return {"error": "src and dst are required"}
|
|
1816
|
+
if src == dst:
|
|
1817
|
+
return {"error": "src and dst must differ"}
|
|
1818
|
+
src_dir = ARKAOS_ROOT / "departments" / src / "agents"
|
|
1819
|
+
dst_dir = ARKAOS_ROOT / "departments" / dst / "agents"
|
|
1820
|
+
if not src_dir.exists():
|
|
1821
|
+
return {"error": f"department '{src}' not found"}
|
|
1822
|
+
if not dst_dir.exists():
|
|
1823
|
+
return {"error": f"department '{dst}' not found"}
|
|
1824
|
+
# Walk the filesystem directly so freshly-created (not-yet-registered)
|
|
1825
|
+
# agents are also picked up. _load_agents() reads a cached registry
|
|
1826
|
+
# that doesn't refresh during the FastAPI process.
|
|
1827
|
+
try:
|
|
1828
|
+
import yaml as _yaml
|
|
1829
|
+
except ImportError:
|
|
1830
|
+
return {"error": "PyYAML unavailable"}
|
|
1831
|
+
source_ids: list[str] = []
|
|
1832
|
+
for path in sorted(src_dir.glob("*.yaml")):
|
|
1833
|
+
try:
|
|
1834
|
+
raw = _yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
1835
|
+
except Exception: # noqa: BLE001
|
|
1836
|
+
continue
|
|
1837
|
+
if isinstance(raw, dict) and raw.get("id"):
|
|
1838
|
+
source_ids.append(str(raw["id"]))
|
|
1839
|
+
if not source_ids:
|
|
1840
|
+
return {"error": f"department '{src}' has no agents"}
|
|
1841
|
+
|
|
1842
|
+
moved = 0
|
|
1843
|
+
skipped = 0
|
|
1844
|
+
failed = 0
|
|
1845
|
+
results: list[dict] = []
|
|
1846
|
+
for aid in source_ids:
|
|
1847
|
+
res = agent_move(aid, {"department": dst})
|
|
1848
|
+
if res.get("moved"):
|
|
1849
|
+
moved += 1
|
|
1850
|
+
results.append({"id": aid, "status": "moved"})
|
|
1851
|
+
elif "Tier 0" in (res.get("error") or ""):
|
|
1852
|
+
skipped += 1
|
|
1853
|
+
results.append({"id": aid, "status": "skipped", "reason": "Tier 0"})
|
|
1854
|
+
else:
|
|
1855
|
+
failed += 1
|
|
1856
|
+
results.append({"id": aid, "status": "failed", "error": res.get("error", "")})
|
|
1857
|
+
|
|
1858
|
+
return {
|
|
1859
|
+
"src": src,
|
|
1860
|
+
"dst": dst,
|
|
1861
|
+
"moved": moved,
|
|
1862
|
+
"skipped": skipped,
|
|
1863
|
+
"failed": failed,
|
|
1864
|
+
"results": results,
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
|
|
1804
1868
|
@app.get("/api/departments/{dept_id}")
|
|
1805
1869
|
def department_detail(dept_id: str):
|
|
1806
1870
|
"""Full department detail: agents, workflows, 30d cost."""
|