arkaos 3.48.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 +1 -1
- package/dashboard/app/components/TextDiff.vue +110 -0
- package/dashboard/app/pages/agents/compare.vue +21 -1
- package/dashboard/app/pages/personas/compare-with-agent.vue +21 -1
- package/dashboard/app/pages/workflows.vue +89 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/dashboard-api.py +56 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.50.0
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// PR94c v3.49.0 — minimal side-by-side line diff.
|
|
3
|
+
//
|
|
4
|
+
// Uses LCS (longest common subsequence) on lines to mark equal / added /
|
|
5
|
+
// removed rows. Empty inputs render an italic "—" placeholder. Diff is
|
|
6
|
+
// pure client-side — no deps.
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
left: string
|
|
10
|
+
right: string
|
|
11
|
+
leftLabel?: string
|
|
12
|
+
rightLabel?: string
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
interface DiffRow {
|
|
16
|
+
kind: 'eq' | 'add' | 'del'
|
|
17
|
+
left: string
|
|
18
|
+
right: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function lineDiff(a: string, b: string): DiffRow[] {
|
|
22
|
+
const left = (a ?? '').split(/\r?\n/)
|
|
23
|
+
const right = (b ?? '').split(/\r?\n/)
|
|
24
|
+
const m = left.length
|
|
25
|
+
const n = right.length
|
|
26
|
+
// Build LCS table.
|
|
27
|
+
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0))
|
|
28
|
+
for (let i = m - 1; i >= 0; i -= 1) {
|
|
29
|
+
for (let j = n - 1; j >= 0; j -= 1) {
|
|
30
|
+
if (left[i] === right[j]) dp[i][j] = dp[i + 1][j + 1] + 1
|
|
31
|
+
else dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1])
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Walk back, emitting rows.
|
|
35
|
+
const rows: DiffRow[] = []
|
|
36
|
+
let i = 0
|
|
37
|
+
let j = 0
|
|
38
|
+
while (i < m && j < n) {
|
|
39
|
+
if (left[i] === right[j]) {
|
|
40
|
+
rows.push({ kind: 'eq', left: left[i], right: right[j] })
|
|
41
|
+
i += 1
|
|
42
|
+
j += 1
|
|
43
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
44
|
+
rows.push({ kind: 'del', left: left[i], right: '' })
|
|
45
|
+
i += 1
|
|
46
|
+
} else {
|
|
47
|
+
rows.push({ kind: 'add', left: '', right: right[j] })
|
|
48
|
+
j += 1
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
while (i < m) {
|
|
52
|
+
rows.push({ kind: 'del', left: left[i], right: '' })
|
|
53
|
+
i += 1
|
|
54
|
+
}
|
|
55
|
+
while (j < n) {
|
|
56
|
+
rows.push({ kind: 'add', left: '', right: right[j] })
|
|
57
|
+
j += 1
|
|
58
|
+
}
|
|
59
|
+
return rows
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rows = computed(() => lineDiff(props.left || '', props.right || ''))
|
|
63
|
+
|
|
64
|
+
const summary = computed(() => {
|
|
65
|
+
const adds = rows.value.filter((r) => r.kind === 'add').length
|
|
66
|
+
const dels = rows.value.filter((r) => r.kind === 'del').length
|
|
67
|
+
return { adds, dels }
|
|
68
|
+
})
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<div class="rounded-lg border border-default overflow-hidden">
|
|
73
|
+
<div class="px-3 py-2 border-b border-default bg-elevated/30 flex items-center justify-between gap-3 text-xs">
|
|
74
|
+
<div class="flex items-center gap-3">
|
|
75
|
+
<span class="font-mono">{{ leftLabel ?? 'left' }}</span>
|
|
76
|
+
<UIcon name="i-lucide-arrow-right" class="size-3 text-muted" />
|
|
77
|
+
<span class="font-mono">{{ rightLabel ?? 'right' }}</span>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="flex items-center gap-2 font-mono text-muted">
|
|
80
|
+
<span v-if="summary.adds > 0" class="text-emerald-500">+{{ summary.adds }}</span>
|
|
81
|
+
<span v-if="summary.dels > 0" class="text-rose-500">-{{ summary.dels }}</span>
|
|
82
|
+
<span v-if="summary.adds === 0 && summary.dels === 0">identical</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="grid grid-cols-2 text-xs font-mono overflow-x-auto">
|
|
86
|
+
<div>
|
|
87
|
+
<div
|
|
88
|
+
v-for="(row, idx) in rows"
|
|
89
|
+
:key="`l-${idx}`"
|
|
90
|
+
:class="[
|
|
91
|
+
'px-3 py-1 whitespace-pre-wrap break-words border-b border-default/50 min-h-[1.5rem]',
|
|
92
|
+
row.kind === 'del' ? 'bg-rose-500/10 text-rose-600 dark:text-rose-400' : '',
|
|
93
|
+
row.kind === 'add' ? 'bg-muted/10' : '',
|
|
94
|
+
]"
|
|
95
|
+
>{{ row.left || '·' }}</div>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="border-l border-default">
|
|
98
|
+
<div
|
|
99
|
+
v-for="(row, idx) in rows"
|
|
100
|
+
:key="`r-${idx}`"
|
|
101
|
+
:class="[
|
|
102
|
+
'px-3 py-1 whitespace-pre-wrap break-words border-b border-default/50 min-h-[1.5rem]',
|
|
103
|
+
row.kind === 'add' ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400' : '',
|
|
104
|
+
row.kind === 'del' ? 'bg-muted/10' : '',
|
|
105
|
+
]"
|
|
106
|
+
>{{ row.right || '·' }}</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
@@ -40,6 +40,8 @@ interface AgentDetail {
|
|
|
40
40
|
preferred_format?: string
|
|
41
41
|
language?: string
|
|
42
42
|
}
|
|
43
|
+
// PR94c v3.49.0 — diffable free-text field
|
|
44
|
+
bio_md?: string
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const { data: a, status: aStatus } = fetchApi<AgentDetail>(
|
|
@@ -216,8 +218,26 @@ const bigFiveKeys = ['openness', 'conscientiousness', 'extraversion', 'agreeable
|
|
|
216
218
|
</div>
|
|
217
219
|
</div>
|
|
218
220
|
|
|
221
|
+
<!-- PR94c v3.49.0 — text diff blocks for the free-text fields -->
|
|
222
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted pt-2">Bio (Markdown)</h3>
|
|
223
|
+
<TextDiff
|
|
224
|
+
:left="a.bio_md || ''"
|
|
225
|
+
:right="b.bio_md || ''"
|
|
226
|
+
:left-label="a.name || a.id"
|
|
227
|
+
:right-label="b.name || b.id"
|
|
228
|
+
/>
|
|
229
|
+
|
|
230
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted pt-2">Communication tone</h3>
|
|
231
|
+
<TextDiff
|
|
232
|
+
:left="a.communication?.tone || ''"
|
|
233
|
+
:right="b.communication?.tone || ''"
|
|
234
|
+
:left-label="a.name || a.id"
|
|
235
|
+
:right-label="b.name || b.id"
|
|
236
|
+
/>
|
|
237
|
+
|
|
219
238
|
<p class="text-xs text-muted pt-4 italic">
|
|
220
|
-
Cells with a yellow tint differ between the two agents.
|
|
239
|
+
Cells with a yellow tint differ between the two agents. Red lines were
|
|
240
|
+
removed; green lines were added.
|
|
221
241
|
</p>
|
|
222
242
|
</div>
|
|
223
243
|
</template>
|
|
@@ -29,6 +29,7 @@ interface PersonaDetail {
|
|
|
29
29
|
expertise_domains?: string[]
|
|
30
30
|
frameworks?: string[]
|
|
31
31
|
communication?: { tone?: string, vocabulary_level?: string, avoid?: string[] }
|
|
32
|
+
bio_md?: string // PR94c v3.49.0
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
interface AgentDetail {
|
|
@@ -43,6 +44,7 @@ interface AgentDetail {
|
|
|
43
44
|
mental_models?: { primary?: string[], secondary?: string[] }
|
|
44
45
|
expertise?: { domains?: string[], frameworks?: string[] }
|
|
45
46
|
communication?: { tone?: string, vocabulary_level?: string, avoid?: string[] }
|
|
47
|
+
bio_md?: string // PR94c v3.49.0
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
const { data: persona, status: pStatus } = fetchApi<PersonaDetail>(
|
|
@@ -209,8 +211,26 @@ const bigFiveKeys = ['openness', 'conscientiousness', 'extraversion', 'agreeable
|
|
|
209
211
|
</div>
|
|
210
212
|
</div>
|
|
211
213
|
|
|
214
|
+
<!-- PR94c v3.49.0 — free-text diff blocks -->
|
|
215
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted pt-2">Bio (Markdown)</h3>
|
|
216
|
+
<TextDiff
|
|
217
|
+
:left="persona.bio_md || ''"
|
|
218
|
+
:right="agent.bio_md || ''"
|
|
219
|
+
:left-label="persona.name || persona.id"
|
|
220
|
+
:right-label="agent.name || agent.id"
|
|
221
|
+
/>
|
|
222
|
+
|
|
223
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted pt-2">Communication tone</h3>
|
|
224
|
+
<TextDiff
|
|
225
|
+
:left="persona.communication?.tone || ''"
|
|
226
|
+
:right="agent.communication?.tone || ''"
|
|
227
|
+
:left-label="persona.name || persona.id"
|
|
228
|
+
:right-label="agent.name || agent.id"
|
|
229
|
+
/>
|
|
230
|
+
|
|
212
231
|
<p class="text-xs text-muted pt-4 italic">
|
|
213
|
-
Cells with a yellow tint differ between persona and agent.
|
|
232
|
+
Cells with a yellow tint differ between persona and agent. Red lines
|
|
233
|
+
were removed; green lines were added.
|
|
214
234
|
</p>
|
|
215
235
|
</div>
|
|
216
236
|
</template>
|
|
@@ -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="
|
|
296
|
-
<
|
|
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
package/pyproject.toml
CHANGED
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -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."""
|