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.52.0
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.52.0",
3
+ "version": "3.53.1",
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.52.0"
3
+ version = "3.53.1"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -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."""