arkaos 3.49.0 → 3.50.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.49.0
1
+ 3.50.0
@@ -30,8 +30,55 @@ interface Workflow {
30
30
  }
31
31
 
32
32
  const { fetchApi, apiBase } = useApi()
33
+ const toast = useToast()
33
34
  const { data, status, error, refresh } = await fetchApi<{ workflows: Workflow[] }>('/api/workflows')
34
35
 
36
+ // PR94d v3.50.0 — inline YAML editor state.
37
+ const editingYaml = ref(false)
38
+ const yamlDraft = ref('')
39
+ const savingYaml = ref(false)
40
+
41
+ function startEditYaml() {
42
+ if (!selected.value) return
43
+ yamlDraft.value = selected.value.content
44
+ editingYaml.value = true
45
+ }
46
+
47
+ function cancelEditYaml() {
48
+ yamlDraft.value = ''
49
+ editingYaml.value = false
50
+ }
51
+
52
+ async function saveYaml() {
53
+ if (!selected.value) return
54
+ savingYaml.value = true
55
+ try {
56
+ const res = await $fetch<{ updated?: boolean, error?: string }>(
57
+ `${apiBase}/api/workflows/${selected.value.id}/yaml`,
58
+ { method: 'PUT', body: { content: yamlDraft.value } },
59
+ )
60
+ if (res.error) throw new Error(res.error)
61
+ toast.add({
62
+ title: 'YAML updated',
63
+ description: selected.value.file,
64
+ color: 'success',
65
+ icon: 'i-lucide-check',
66
+ })
67
+ // Patch local content so the preview shows the new YAML immediately.
68
+ selected.value = { ...selected.value, content: yamlDraft.value }
69
+ editingYaml.value = false
70
+ await refresh()
71
+ } catch (err) {
72
+ toast.add({
73
+ title: 'Save failed',
74
+ description: err instanceof Error ? err.message : 'unknown error',
75
+ color: 'error',
76
+ })
77
+ } finally {
78
+ savingYaml.value = false
79
+ }
80
+ }
81
+
35
82
  // PR89b v3.28.0 — recent runs for the selected workflow.
36
83
  interface WorkflowRun {
37
84
  session_id: string
@@ -171,7 +218,7 @@ const columns: TableColumn<Workflow>[] = [
171
218
  th: 'py-2 first:rounded-l-lg last:rounded-r-lg border-y border-default first:border-l last:border-r',
172
219
  td: 'border-b border-default',
173
220
  }"
174
- @select="(row: { original: Workflow }) => { selected = row.original; sidePanelTab = 'flow'; runs = []; loadRuns(row.original.id) }"
221
+ @select="(row: { original: Workflow }) => { selected = row.original; sidePanelTab = 'flow'; runs = []; editingYaml = false; loadRuns(row.original.id) }"
175
222
  >
176
223
  <template #name-cell="{ row }">
177
224
  <div class="min-w-0">
@@ -292,8 +339,47 @@ const columns: TableColumn<Workflow>[] = [
292
339
  No phases defined.
293
340
  </div>
294
341
  </div>
295
- <div v-else-if="sidePanelTab === 'yaml'" class="overflow-x-auto">
296
- <pre class="p-4 text-xs font-mono whitespace-pre">{{ selected.content }}</pre>
342
+ <div v-else-if="sidePanelTab === 'yaml'" class="flex flex-col">
343
+ <div class="px-4 py-2 border-b border-default flex items-center justify-between text-xs">
344
+ <span class="text-muted font-mono">{{ selected.file }}</span>
345
+ <div class="flex items-center gap-2">
346
+ <template v-if="!editingYaml">
347
+ <UButton
348
+ label="Edit"
349
+ icon="i-lucide-pencil"
350
+ size="xs"
351
+ variant="soft"
352
+ @click="startEditYaml"
353
+ />
354
+ </template>
355
+ <template v-else>
356
+ <UButton
357
+ label="Cancel"
358
+ variant="ghost"
359
+ size="xs"
360
+ :disabled="savingYaml"
361
+ @click="cancelEditYaml"
362
+ />
363
+ <UButton
364
+ label="Save"
365
+ icon="i-lucide-check"
366
+ color="primary"
367
+ size="xs"
368
+ :loading="savingYaml"
369
+ @click="saveYaml"
370
+ />
371
+ </template>
372
+ </div>
373
+ </div>
374
+ <div v-if="!editingYaml" class="overflow-x-auto">
375
+ <pre class="p-4 text-xs font-mono whitespace-pre">{{ selected.content }}</pre>
376
+ </div>
377
+ <UTextarea
378
+ v-else
379
+ v-model="yamlDraft"
380
+ :rows="20"
381
+ class="m-2 font-mono text-xs"
382
+ />
297
383
  </div>
298
384
  <div v-else class="p-4">
299
385
  <div v-if="runsLoading" class="py-6 text-center text-sm text-muted">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.49.0",
3
+ "version": "3.50.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.49.0"
3
+ version = "3.50.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"}
@@ -1894,6 +1894,62 @@ def _iso_duration_s(start_iso: str, end_iso: str) -> Optional[int]:
1894
1894
  return None
1895
1895
 
1896
1896
 
1897
+ @app.put("/api/workflows/{workflow_id}/yaml")
1898
+ def workflow_update_yaml(workflow_id: str, body: dict):
1899
+ """PR94d v3.50.0 — overwrite a workflow's YAML file in place.
1900
+
1901
+ Body: ``{"content": "<full YAML body>"}``. Validates that the
1902
+ content parses to a dict with a non-empty ``id`` key. Refuses to
1903
+ move the file (operator can't rename via this endpoint — that
1904
+ would invalidate cached references).
1905
+ """
1906
+ if not isinstance(body, dict):
1907
+ return {"error": "body must be an object"}
1908
+ content = str(body.get("content") or "")
1909
+ if not content.strip():
1910
+ return {"error": "content is required"}
1911
+ target = _resolve_workflow_yaml(workflow_id)
1912
+ if target is None:
1913
+ return {"error": "workflow not found"}
1914
+ try:
1915
+ import yaml as _yaml
1916
+ parsed = _yaml.safe_load(content)
1917
+ except Exception as exc: # noqa: BLE001
1918
+ return {"error": f"YAML parse failed: {exc}"}
1919
+ if not isinstance(parsed, dict):
1920
+ return {"error": "YAML root must be a mapping"}
1921
+ if not parsed.get("id"):
1922
+ return {"error": "YAML must include a non-empty 'id' field"}
1923
+ try:
1924
+ tmp = target.with_suffix(target.suffix + ".tmp")
1925
+ tmp.write_text(content, encoding="utf-8")
1926
+ tmp.replace(target)
1927
+ except OSError as exc:
1928
+ return {"error": f"write failed: {exc}"}
1929
+ return {"updated": True, "id": workflow_id, "file": str(target)}
1930
+
1931
+
1932
+ def _resolve_workflow_yaml(workflow_id: str):
1933
+ """Return the YAML path for a workflow id, or None when missing."""
1934
+ try:
1935
+ import yaml as _yaml
1936
+ except ImportError:
1937
+ return None
1938
+ dept_root = ARKAOS_ROOT / "departments"
1939
+ if not dept_root.exists():
1940
+ return None
1941
+ for path in dept_root.glob("*/workflows/*.yaml"):
1942
+ try:
1943
+ raw = _yaml.safe_load(path.read_text(encoding="utf-8")) or {}
1944
+ except Exception: # noqa: BLE001
1945
+ continue
1946
+ if not isinstance(raw, dict):
1947
+ continue
1948
+ if str(raw.get("id") or path.stem) == workflow_id:
1949
+ return path
1950
+ return None
1951
+
1952
+
1897
1953
  @app.get("/api/workflows")
1898
1954
  def workflows_list():
1899
1955
  """Scan departments/*/workflows/*.yaml and return metadata + content."""