arkaos 3.5.0 → 3.6.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 +1 -1
- package/core/agents/__pycache__/string_suggester.cpython-313.pyc +0 -0
- package/dashboard/app/pages/agents/[id].vue +75 -0
- package/dashboard/app/pages/personas/index.vue +1 -1
- 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 +84 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.6.1
|
|
Binary file
|
|
@@ -27,6 +27,38 @@ const deptActivity = computed<ActivityRow | null>(() =>
|
|
|
27
27
|
(activityData.value?.by_department?.[agent.value?.department ?? ''] ?? null),
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
// PR83d v3.6.0 — activity strip (30d, dept-level + last_used + rank)
|
|
31
|
+
interface ActivityStrip {
|
|
32
|
+
period: string
|
|
33
|
+
department: string
|
|
34
|
+
calls: number
|
|
35
|
+
cost_usd: number | null
|
|
36
|
+
tokens_in: number
|
|
37
|
+
tokens_out: number
|
|
38
|
+
last_used: string | null
|
|
39
|
+
dept_rank: number | null
|
|
40
|
+
dept_count: number
|
|
41
|
+
}
|
|
42
|
+
const { data: activityStrip } = fetchApi<ActivityStrip>(
|
|
43
|
+
`/api/agents/${agentId}/activity-strip?period=month`,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
function formatRelative(iso: string | null): string {
|
|
47
|
+
if (!iso) return 'never'
|
|
48
|
+
const ts = Date.parse(iso)
|
|
49
|
+
if (Number.isNaN(ts)) return 'never'
|
|
50
|
+
const diff = Date.now() - ts
|
|
51
|
+
const minutes = Math.floor(diff / 60_000)
|
|
52
|
+
if (minutes < 1) return 'just now'
|
|
53
|
+
if (minutes < 60) return `${minutes}m ago`
|
|
54
|
+
const hours = Math.floor(minutes / 60)
|
|
55
|
+
if (hours < 24) return `${hours}h ago`
|
|
56
|
+
const days = Math.floor(hours / 24)
|
|
57
|
+
if (days < 30) return `${days}d ago`
|
|
58
|
+
const months = Math.floor(days / 30)
|
|
59
|
+
return `${months}mo ago`
|
|
60
|
+
}
|
|
61
|
+
|
|
30
62
|
// PR76 — edit drawer state
|
|
31
63
|
const editOpen = ref(false)
|
|
32
64
|
|
|
@@ -291,6 +323,49 @@ function formatTokens(n: number): string {
|
|
|
291
323
|
</div>
|
|
292
324
|
</section>
|
|
293
325
|
|
|
326
|
+
<!-- ===== ACTIVITY STRIP (PR83d) ===== -->
|
|
327
|
+
<section
|
|
328
|
+
v-if="activityStrip"
|
|
329
|
+
class="rounded-xl border border-default bg-elevated/10 p-4"
|
|
330
|
+
>
|
|
331
|
+
<div class="flex flex-wrap items-center gap-x-6 gap-y-3 text-sm">
|
|
332
|
+
<div class="flex items-center gap-2">
|
|
333
|
+
<UIcon name="i-lucide-activity" class="size-4 text-primary" />
|
|
334
|
+
<span class="font-semibold uppercase tracking-wide text-muted text-xs">
|
|
335
|
+
30d activity (dept)
|
|
336
|
+
</span>
|
|
337
|
+
</div>
|
|
338
|
+
<div class="flex items-center gap-2">
|
|
339
|
+
<span class="text-muted">Calls</span>
|
|
340
|
+
<span class="font-mono font-semibold">{{ activityStrip.calls }}</span>
|
|
341
|
+
</div>
|
|
342
|
+
<div class="flex items-center gap-2">
|
|
343
|
+
<span class="text-muted">Cost</span>
|
|
344
|
+
<span class="font-mono font-semibold">{{ formatCost(activityStrip.cost_usd) }}</span>
|
|
345
|
+
</div>
|
|
346
|
+
<div class="flex items-center gap-2">
|
|
347
|
+
<span class="text-muted">Tokens</span>
|
|
348
|
+
<span class="font-mono">
|
|
349
|
+
{{ formatTokens(activityStrip.tokens_in) }} /
|
|
350
|
+
{{ formatTokens(activityStrip.tokens_out) }}
|
|
351
|
+
</span>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="flex items-center gap-2">
|
|
354
|
+
<span class="text-muted">Last used</span>
|
|
355
|
+
<span class="font-mono">{{ formatRelative(activityStrip.last_used) }}</span>
|
|
356
|
+
</div>
|
|
357
|
+
<div v-if="activityStrip.dept_rank" class="flex items-center gap-2">
|
|
358
|
+
<span class="text-muted">Dept rank</span>
|
|
359
|
+
<UBadge
|
|
360
|
+
:label="`#${activityStrip.dept_rank} of ${activityStrip.dept_count}`"
|
|
361
|
+
:color="activityStrip.dept_rank <= 3 ? 'primary' : 'neutral'"
|
|
362
|
+
variant="subtle"
|
|
363
|
+
size="sm"
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
</section>
|
|
368
|
+
|
|
294
369
|
<AgentEditDrawer
|
|
295
370
|
v-model="editOpen"
|
|
296
371
|
:agent="agent"
|
|
@@ -289,7 +289,7 @@ async function bulkDelete() {
|
|
|
289
289
|
th: 'py-2 first:rounded-l-lg last:rounded-r-lg border-y border-default first:border-l last:border-r',
|
|
290
290
|
td: 'border-b border-default',
|
|
291
291
|
}"
|
|
292
|
-
@select="(row: Persona) => goToPersona(row.id)"
|
|
292
|
+
@select="(row: { original: Persona }) => goToPersona(row.original.id)"
|
|
293
293
|
>
|
|
294
294
|
<template #select-header>
|
|
295
295
|
<UCheckbox
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -218,6 +218,90 @@ def agents_activity(period: str = "week"):
|
|
|
218
218
|
return {"by_department": out, "period": period}
|
|
219
219
|
|
|
220
220
|
|
|
221
|
+
@app.get("/api/agents/{agent_id}/activity-strip")
|
|
222
|
+
def agent_activity_strip(agent_id: str, period: str = "month"):
|
|
223
|
+
"""PR83d v3.6.0 — compact activity payload for the agent hero strip.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
{
|
|
227
|
+
"period": "month",
|
|
228
|
+
"department": "<dept>",
|
|
229
|
+
"calls": <int>,
|
|
230
|
+
"cost_usd": <float|null>,
|
|
231
|
+
"tokens_in": <int>, "tokens_out": <int>,
|
|
232
|
+
"last_used": "<ISO ts>"|null,
|
|
233
|
+
"dept_rank": <1-based int>|null,
|
|
234
|
+
"dept_count": <int>
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
All values reflect the agent's DEPARTMENT (per-agent attribution
|
|
238
|
+
isn't tracked yet — see PR47 telemetry).
|
|
239
|
+
"""
|
|
240
|
+
agents = _load_agents()
|
|
241
|
+
base = None
|
|
242
|
+
for a in agents:
|
|
243
|
+
if a.get("id") == agent_id:
|
|
244
|
+
base = dict(a)
|
|
245
|
+
break
|
|
246
|
+
if not base:
|
|
247
|
+
return {"error": "Agent not found"}
|
|
248
|
+
dept = base.get("department") or ""
|
|
249
|
+
try:
|
|
250
|
+
from core.runtime.llm_cost_telemetry import (
|
|
251
|
+
VALID_PERIODS,
|
|
252
|
+
_load_slice,
|
|
253
|
+
_period_cutoff,
|
|
254
|
+
summarise,
|
|
255
|
+
)
|
|
256
|
+
except Exception:
|
|
257
|
+
return {"error": "telemetry unavailable"}
|
|
258
|
+
if period not in VALID_PERIODS:
|
|
259
|
+
period = "month"
|
|
260
|
+
|
|
261
|
+
summary = summarise(period=period)
|
|
262
|
+
dept_costs: list[tuple[str, float]] = []
|
|
263
|
+
target_row: dict | None = None
|
|
264
|
+
for category, row in (summary.by_category or {}).items():
|
|
265
|
+
if not isinstance(category, str) or not category.startswith("subagent:"):
|
|
266
|
+
continue
|
|
267
|
+
cat_dept = category.split(":", 1)[1] or "unknown"
|
|
268
|
+
cost = row.get("total_cost_usd")
|
|
269
|
+
dept_costs.append((cat_dept, float(cost) if isinstance(cost, (int, float)) else 0.0))
|
|
270
|
+
if cat_dept == dept:
|
|
271
|
+
target_row = row
|
|
272
|
+
|
|
273
|
+
dept_costs.sort(key=lambda t: t[1], reverse=True)
|
|
274
|
+
dept_rank: Optional[int] = None
|
|
275
|
+
for idx, (d, _) in enumerate(dept_costs, start=1):
|
|
276
|
+
if d == dept:
|
|
277
|
+
dept_rank = idx
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
entries, _ = _load_slice(None, _period_cutoff(period, now=None))
|
|
281
|
+
last_used: Optional[str] = None
|
|
282
|
+
for entry in reversed(entries):
|
|
283
|
+
cat = entry.get("category") or ""
|
|
284
|
+
if isinstance(cat, str) and cat == f"subagent:{dept}":
|
|
285
|
+
last_used = entry.get("ts")
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
"period": period,
|
|
290
|
+
"department": dept,
|
|
291
|
+
"calls": int(target_row.get("call_count", 0)) if target_row else 0,
|
|
292
|
+
"cost_usd": (
|
|
293
|
+
float(target_row.get("total_cost_usd"))
|
|
294
|
+
if target_row and isinstance(target_row.get("total_cost_usd"), (int, float))
|
|
295
|
+
else None
|
|
296
|
+
),
|
|
297
|
+
"tokens_in": int(target_row.get("total_tokens_in", 0)) if target_row else 0,
|
|
298
|
+
"tokens_out": int(target_row.get("total_tokens_out", 0)) if target_row else 0,
|
|
299
|
+
"last_used": last_used,
|
|
300
|
+
"dept_rank": dept_rank,
|
|
301
|
+
"dept_count": len(dept_costs),
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
221
305
|
@app.get("/api/agents/{agent_id}")
|
|
222
306
|
def agent_detail(agent_id: str):
|
|
223
307
|
"""Get full agent detail including YAML data."""
|