arkaos 3.64.0 → 3.66.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.
|
|
1
|
+
3.66.0
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// PR98d v3.66.0 — Agent dependency graph.
|
|
3
|
+
//
|
|
4
|
+
// Shows the current agent in the middle, its linked personas above,
|
|
5
|
+
// and other agents that link to those same personas (siblings) below.
|
|
6
|
+
// Pure SVG, no graph lib. Frontend-only — reuses existing endpoints.
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
agentId: string
|
|
10
|
+
agentName: string
|
|
11
|
+
linkedPersonas: string[]
|
|
12
|
+
}
|
|
13
|
+
const props = defineProps<Props>()
|
|
14
|
+
|
|
15
|
+
const { fetchApi } = useApi()
|
|
16
|
+
|
|
17
|
+
// /api/personas/usage returns the reverse lookup we need.
|
|
18
|
+
const { data: usageData } = fetchApi<{
|
|
19
|
+
by_persona: Record<string, { agent_count: number, agent_ids: string[] }>
|
|
20
|
+
}>('/api/personas/usage')
|
|
21
|
+
|
|
22
|
+
// /api/personas for resolving persona names.
|
|
23
|
+
const { data: personasData } = fetchApi<{
|
|
24
|
+
personas: Array<{ id: string, name: string }>
|
|
25
|
+
}>('/api/personas')
|
|
26
|
+
|
|
27
|
+
function personaName(id: string): string {
|
|
28
|
+
return personasData.value?.personas?.find((p) => p.id === id)?.name ?? id
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const siblings = computed<string[]>(() => {
|
|
32
|
+
const ids = new Set<string>()
|
|
33
|
+
const usage = usageData.value?.by_persona ?? {}
|
|
34
|
+
for (const pid of props.linkedPersonas) {
|
|
35
|
+
for (const aid of (usage[pid]?.agent_ids ?? [])) {
|
|
36
|
+
if (aid && aid !== props.agentId) ids.add(aid)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Array.from(ids).slice(0, 6)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Layout constants — keep tight so the graph fits the hero card.
|
|
43
|
+
const WIDTH = 700
|
|
44
|
+
const HEIGHT = 220
|
|
45
|
+
const CENTER_X = WIDTH / 2
|
|
46
|
+
const CENTER_Y = HEIGHT / 2
|
|
47
|
+
const NODE_W = 110
|
|
48
|
+
const NODE_H = 32
|
|
49
|
+
const NODE_RX = 8
|
|
50
|
+
|
|
51
|
+
function distributeX(count: number, idx: number): number {
|
|
52
|
+
if (count === 1) return CENTER_X
|
|
53
|
+
const span = Math.min(WIDTH - 80, count * (NODE_W + 20))
|
|
54
|
+
const start = (WIDTH - span) / 2 + NODE_W / 2
|
|
55
|
+
const step = count > 1 ? (span - NODE_W) / (count - 1) : 0
|
|
56
|
+
return start + idx * step
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const personaPositions = computed(() =>
|
|
60
|
+
props.linkedPersonas.map((id, i) => ({
|
|
61
|
+
id,
|
|
62
|
+
x: distributeX(props.linkedPersonas.length, i),
|
|
63
|
+
y: 32,
|
|
64
|
+
})),
|
|
65
|
+
)
|
|
66
|
+
const siblingPositions = computed(() =>
|
|
67
|
+
siblings.value.map((id, i) => ({
|
|
68
|
+
id,
|
|
69
|
+
x: distributeX(siblings.value.length, i),
|
|
70
|
+
y: HEIGHT - 32,
|
|
71
|
+
})),
|
|
72
|
+
)
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<div
|
|
77
|
+
v-if="props.linkedPersonas.length > 0 || siblings.length > 0"
|
|
78
|
+
class="rounded-xl border border-default bg-elevated/10 p-5"
|
|
79
|
+
>
|
|
80
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted mb-3">
|
|
81
|
+
Dependency graph
|
|
82
|
+
</h3>
|
|
83
|
+
<svg
|
|
84
|
+
:viewBox="`0 0 ${WIDTH} ${HEIGHT}`"
|
|
85
|
+
class="w-full"
|
|
86
|
+
preserveAspectRatio="xMidYMid meet"
|
|
87
|
+
>
|
|
88
|
+
<!-- Connector lines (personas → center) -->
|
|
89
|
+
<line
|
|
90
|
+
v-for="p in personaPositions"
|
|
91
|
+
:key="`pl-${p.id}`"
|
|
92
|
+
:x1="p.x"
|
|
93
|
+
:y1="p.y + NODE_H / 2"
|
|
94
|
+
:x2="CENTER_X"
|
|
95
|
+
:y2="CENTER_Y - NODE_H / 2"
|
|
96
|
+
class="stroke-default"
|
|
97
|
+
stroke-width="1"
|
|
98
|
+
/>
|
|
99
|
+
<!-- Connector lines (center → siblings) -->
|
|
100
|
+
<line
|
|
101
|
+
v-for="s in siblingPositions"
|
|
102
|
+
:key="`sl-${s.id}`"
|
|
103
|
+
:x1="CENTER_X"
|
|
104
|
+
:y1="CENTER_Y + NODE_H / 2"
|
|
105
|
+
:x2="s.x"
|
|
106
|
+
:y2="s.y - NODE_H / 2"
|
|
107
|
+
class="stroke-default"
|
|
108
|
+
stroke-width="1"
|
|
109
|
+
stroke-dasharray="3,2"
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
<!-- Persona nodes (top) -->
|
|
113
|
+
<g
|
|
114
|
+
v-for="p in personaPositions"
|
|
115
|
+
:key="`p-${p.id}`"
|
|
116
|
+
>
|
|
117
|
+
<a :href="`/personas/${p.id}`">
|
|
118
|
+
<rect
|
|
119
|
+
:x="p.x - NODE_W / 2"
|
|
120
|
+
:y="p.y - NODE_H / 2"
|
|
121
|
+
:width="NODE_W"
|
|
122
|
+
:height="NODE_H"
|
|
123
|
+
:rx="NODE_RX"
|
|
124
|
+
class="fill-emerald-500/10 stroke-emerald-500/40"
|
|
125
|
+
stroke-width="1"
|
|
126
|
+
/>
|
|
127
|
+
<text
|
|
128
|
+
:x="p.x"
|
|
129
|
+
:y="p.y + 4"
|
|
130
|
+
text-anchor="middle"
|
|
131
|
+
class="fill-emerald-700 dark:fill-emerald-300 text-xs"
|
|
132
|
+
>
|
|
133
|
+
{{ personaName(p.id).slice(0, 14) }}
|
|
134
|
+
</text>
|
|
135
|
+
<title>{{ personaName(p.id) }}</title>
|
|
136
|
+
</a>
|
|
137
|
+
</g>
|
|
138
|
+
|
|
139
|
+
<!-- Centre node: current agent -->
|
|
140
|
+
<g>
|
|
141
|
+
<rect
|
|
142
|
+
:x="CENTER_X - NODE_W / 2 - 10"
|
|
143
|
+
:y="CENTER_Y - NODE_H / 2"
|
|
144
|
+
:width="NODE_W + 20"
|
|
145
|
+
:height="NODE_H"
|
|
146
|
+
:rx="NODE_RX"
|
|
147
|
+
class="fill-primary/15 stroke-primary"
|
|
148
|
+
stroke-width="1.5"
|
|
149
|
+
/>
|
|
150
|
+
<text
|
|
151
|
+
:x="CENTER_X"
|
|
152
|
+
:y="CENTER_Y + 4"
|
|
153
|
+
text-anchor="middle"
|
|
154
|
+
class="fill-primary text-sm font-semibold"
|
|
155
|
+
>
|
|
156
|
+
{{ props.agentName.slice(0, 16) }}
|
|
157
|
+
</text>
|
|
158
|
+
</g>
|
|
159
|
+
|
|
160
|
+
<!-- Sibling agent nodes (bottom) -->
|
|
161
|
+
<g
|
|
162
|
+
v-for="s in siblingPositions"
|
|
163
|
+
:key="`s-${s.id}`"
|
|
164
|
+
>
|
|
165
|
+
<a :href="`/agents/${s.id}`">
|
|
166
|
+
<rect
|
|
167
|
+
:x="s.x - NODE_W / 2"
|
|
168
|
+
:y="s.y - NODE_H / 2"
|
|
169
|
+
:width="NODE_W"
|
|
170
|
+
:height="NODE_H"
|
|
171
|
+
:rx="NODE_RX"
|
|
172
|
+
class="fill-blue-500/10 stroke-blue-500/40"
|
|
173
|
+
stroke-width="1"
|
|
174
|
+
/>
|
|
175
|
+
<text
|
|
176
|
+
:x="s.x"
|
|
177
|
+
:y="s.y + 4"
|
|
178
|
+
text-anchor="middle"
|
|
179
|
+
class="fill-blue-700 dark:fill-blue-300 text-xs"
|
|
180
|
+
>
|
|
181
|
+
{{ s.id.slice(0, 14) }}
|
|
182
|
+
</text>
|
|
183
|
+
<title>{{ s.id }}</title>
|
|
184
|
+
</a>
|
|
185
|
+
</g>
|
|
186
|
+
</svg>
|
|
187
|
+
<p class="text-xs text-muted mt-2 italic">
|
|
188
|
+
Top: linked personas · Centre: this agent · Bottom: siblings (other
|
|
189
|
+
agents linking the same personas)
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
</template>
|
|
@@ -658,6 +658,14 @@ function formatTokens(n: number): string {
|
|
|
658
658
|
</div>
|
|
659
659
|
</section>
|
|
660
660
|
|
|
661
|
+
<!-- ===== DEPENDENCY GRAPH (PR98d) ===== -->
|
|
662
|
+
<AgentDependencyGraph
|
|
663
|
+
v-if="agent.linked_personas && agent.linked_personas.length > 0"
|
|
664
|
+
:agent-id="agent.id"
|
|
665
|
+
:agent-name="agent.name"
|
|
666
|
+
:linked-personas="agent.linked_personas"
|
|
667
|
+
/>
|
|
668
|
+
|
|
661
669
|
<!-- ===== BIO (PR86d) ===== -->
|
|
662
670
|
<section
|
|
663
671
|
v-if="(agent as any).bio_md"
|
|
@@ -103,6 +103,29 @@ function gateColor(gateType: string): 'primary' | 'warning' | 'error' | 'neutral
|
|
|
103
103
|
return m[gateType] ?? 'neutral'
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// PR98c v3.65.0 — copy a workflow's command to the clipboard so the
|
|
107
|
+
// operator can paste it into their runtime (Claude Code / Codex / Gemini).
|
|
108
|
+
// We can't run workflows from the dashboard — they orchestrate through
|
|
109
|
+
// the runtime's skill system, not via subprocess.
|
|
110
|
+
async function copyCommand(cmd: string) {
|
|
111
|
+
if (!cmd || typeof navigator === 'undefined' || !navigator.clipboard) return
|
|
112
|
+
try {
|
|
113
|
+
await navigator.clipboard.writeText(cmd)
|
|
114
|
+
toast.add({
|
|
115
|
+
title: 'Command copied',
|
|
116
|
+
description: `${cmd} — paste into your runtime to run`,
|
|
117
|
+
color: 'success',
|
|
118
|
+
icon: 'i-lucide-clipboard-check',
|
|
119
|
+
})
|
|
120
|
+
} catch {
|
|
121
|
+
toast.add({
|
|
122
|
+
title: 'Clipboard failed',
|
|
123
|
+
description: 'Browser blocked clipboard access',
|
|
124
|
+
color: 'error',
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
async function loadRuns(id: string) {
|
|
107
130
|
runsLoading.value = true
|
|
108
131
|
try {
|
|
@@ -259,13 +282,24 @@ const columns: TableColumn<Workflow>[] = [
|
|
|
259
282
|
<p class="font-semibold truncate">{{ selected.name }}</p>
|
|
260
283
|
<p class="text-xs text-muted font-mono truncate">{{ selected.file }}</p>
|
|
261
284
|
</div>
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
285
|
+
<div class="flex items-center gap-1">
|
|
286
|
+
<UButton
|
|
287
|
+
v-if="selected.command"
|
|
288
|
+
label="Copy command"
|
|
289
|
+
icon="i-lucide-clipboard-copy"
|
|
290
|
+
variant="soft"
|
|
291
|
+
color="primary"
|
|
292
|
+
size="xs"
|
|
293
|
+
@click="copyCommand(selected.command)"
|
|
294
|
+
/>
|
|
295
|
+
<UButton
|
|
296
|
+
icon="i-lucide-x"
|
|
297
|
+
variant="ghost"
|
|
298
|
+
size="xs"
|
|
299
|
+
aria-label="Close preview"
|
|
300
|
+
@click="selected = null"
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
269
303
|
</div>
|
|
270
304
|
<p v-if="selected.description" class="text-xs text-muted mt-2">
|
|
271
305
|
{{ selected.description }}
|
package/package.json
CHANGED