arkaos 2.85.0 → 2.86.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
- 2.85.0
1
+ 2.86.0
@@ -2,12 +2,73 @@
2
2
  import type { TableColumn } from '@nuxt/ui'
3
3
  import type { Agent } from '~/types'
4
4
 
5
- const { fetchApi } = useApi()
5
+ const { fetchApi, apiBase } = useApi()
6
+ const toast = useToast()
6
7
 
7
8
  const { data, status, error, refresh } = await fetchApi<{ agents: Agent[], total: number }>('/api/agents')
8
9
 
10
+ // PR69 v2.86.0 — per-department activity from PR47 telemetry.
11
+ // Used to badge agents whose department has run recently and to
12
+ // surface "no activity yet" hint when a department's never been
13
+ // invoked. Failure-tolerant — returns empty if telemetry unavailable.
14
+ interface ActivityRow {
15
+ call_count: number
16
+ total_cost_usd: number | null
17
+ total_tokens_in: number
18
+ total_tokens_out: number
19
+ }
20
+
21
+ const {
22
+ data: activityData,
23
+ refresh: refreshActivity,
24
+ } = fetchApi<{ by_department: Record<string, ActivityRow>, period: string }>(
25
+ '/api/agents/activity?period=week',
26
+ )
27
+
9
28
  const agents = computed(() => data.value?.agents ?? [])
10
29
 
30
+ function deptActivity(dept: string): ActivityRow | undefined {
31
+ return activityData.value?.by_department?.[dept]
32
+ }
33
+
34
+ const copied = ref<string | null>(null)
35
+ let copyTimer: ReturnType<typeof setTimeout> | null = null
36
+
37
+ async function copyAgentMention(agent: Agent) {
38
+ if (typeof navigator === 'undefined' || !navigator.clipboard) {
39
+ toast.add({ title: 'Clipboard unavailable', color: 'warning' })
40
+ return
41
+ }
42
+ // The most useful copy for an operator: a ready-to-paste mention
43
+ // that names the agent + their role so the orchestrator can dispatch.
44
+ const text = `Use ${agent.name} (${agent.role}, dept ${agent.department}, tier ${agent.tier}) for this task.`
45
+ try {
46
+ await navigator.clipboard.writeText(text)
47
+ copied.value = agent.id
48
+ if (copyTimer) clearTimeout(copyTimer)
49
+ copyTimer = setTimeout(() => { copied.value = null; copyTimer = null }, 1500)
50
+ toast.add({
51
+ title: 'Copied',
52
+ description: `${agent.name} mention ready to paste.`,
53
+ color: 'success',
54
+ })
55
+ } catch (err) {
56
+ toast.add({
57
+ title: 'Copy failed',
58
+ description: err instanceof Error ? err.message : 'unknown error',
59
+ color: 'error',
60
+ })
61
+ }
62
+ }
63
+
64
+ onBeforeUnmount(() => {
65
+ if (copyTimer) clearTimeout(copyTimer)
66
+ })
67
+
68
+ async function refreshAll() {
69
+ await Promise.all([refresh(), refreshActivity()])
70
+ }
71
+
11
72
  const search = ref('')
12
73
  const departmentFilter = ref('all')
13
74
  const tierFilter = ref('all')
@@ -77,35 +138,18 @@ const tierColor = (tier: number) => {
77
138
  }
78
139
 
79
140
  const columns: TableColumn<Agent>[] = [
80
- {
81
- accessorKey: 'name',
82
- header: 'Name'
83
- },
84
- {
85
- accessorKey: 'role',
86
- header: 'Role'
87
- },
88
- {
89
- accessorKey: 'department',
90
- header: 'Department'
91
- },
92
- {
93
- accessorKey: 'tier',
94
- header: 'Tier'
95
- },
141
+ { accessorKey: 'name', header: 'Name' },
142
+ { accessorKey: 'role', header: 'Role' },
143
+ { accessorKey: 'department', header: 'Department' },
144
+ { accessorKey: 'tier', header: 'Tier' },
96
145
  {
97
146
  accessorFn: (row: Agent) => row.disc?.primary ?? '-',
98
147
  id: 'disc',
99
- header: 'DISC'
100
- },
101
- {
102
- accessorKey: 'mbti',
103
- header: 'MBTI'
148
+ header: 'DISC',
104
149
  },
105
- {
106
- id: 'actions',
107
- header: ''
108
- }
150
+ { accessorKey: 'mbti', header: 'MBTI' },
151
+ { id: 'activity', header: 'Activity (7d)' },
152
+ { id: 'actions', header: '' },
109
153
  ]
110
154
 
111
155
  function goToAgent(id: string) {
@@ -134,7 +178,7 @@ function goToAgent(id: string) {
134
178
  empty-title="No agents found"
135
179
  empty-icon="i-lucide-users"
136
180
  loading-label="Loading agents"
137
- :on-retry="() => refresh()"
181
+ :on-retry="() => refreshAll()"
138
182
  >
139
183
  <div class="flex flex-wrap items-center gap-3 mb-4">
140
184
  <UInput
@@ -195,8 +239,33 @@ function goToAgent(id: string) {
195
239
  <template #mbti-cell="{ row }">
196
240
  <span class="font-mono text-sm">{{ row.original.mbti || '-' }}</span>
197
241
  </template>
242
+ <template #activity-cell="{ row }">
243
+ <template v-if="deptActivity(row.original.department)">
244
+ <div class="flex items-center gap-2">
245
+ <span class="inline-block size-2 rounded-full bg-green-500" />
246
+ <span class="text-xs font-mono">
247
+ {{ deptActivity(row.original.department)?.call_count ?? 0 }} calls
248
+ </span>
249
+ </div>
250
+ </template>
251
+ <span v-else class="text-xs text-muted">—</span>
252
+ </template>
198
253
  <template #actions-cell="{ row }">
199
- <UButton size="xs" variant="ghost" icon="i-lucide-arrow-right" @click="goToAgent(row.original.id)" />
254
+ <UButton
255
+ :icon="copied === row.original.id ? 'i-lucide-check' : 'i-lucide-copy'"
256
+ :color="copied === row.original.id ? 'success' : 'neutral'"
257
+ variant="ghost"
258
+ size="xs"
259
+ aria-label="Copy agent mention"
260
+ @click.stop="copyAgentMention(row.original)"
261
+ />
262
+ <UButton
263
+ size="xs"
264
+ variant="ghost"
265
+ icon="i-lucide-arrow-right"
266
+ aria-label="Open agent detail"
267
+ @click="goToAgent(row.original.id)"
268
+ />
200
269
  </template>
201
270
  </UTable>
202
271
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.85.0",
3
+ "version": "2.86.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 = "2.85.0"
3
+ version = "2.86.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"}
@@ -173,6 +173,51 @@ def agents(dept: Optional[str] = Query(None)):
173
173
  return {"agents": data, "total": len(data)}
174
174
 
175
175
 
176
+ @app.get("/api/agents/activity")
177
+ def agents_activity(period: str = "week"):
178
+ """Per-department activity from the PR47 LLM cost telemetry.
179
+
180
+ Returns ``{by_department: {dev: {call_count, total_cost_usd,
181
+ total_tokens_in, total_tokens_out}}}`` derived from rows whose
182
+ ``category`` field starts with ``subagent:``. Each agent's
183
+ dispatch is currently tagged at the department level — finer
184
+ per-agent attribution will land when orchestrators set
185
+ ``ARKA_CALL_CATEGORY=subagent:<dept>:<agent>``.
186
+ """
187
+ try:
188
+ from core.runtime.llm_cost_telemetry import summarise, VALID_PERIODS
189
+ except Exception: # pragma: no cover - import guard
190
+ return {"by_department": {}, "period": period}
191
+ if period not in VALID_PERIODS:
192
+ period = "week"
193
+ summary = summarise(period=period)
194
+ out: dict[str, dict] = {}
195
+ for category, row in (summary.by_category or {}).items():
196
+ if not isinstance(category, str) or not category.startswith("subagent:"):
197
+ continue
198
+ dept = category.split(":", 1)[1] or "unknown"
199
+ bucket = out.setdefault(dept, {
200
+ "call_count": 0,
201
+ "total_cost_usd": 0.0,
202
+ "any_cost_known": False,
203
+ "total_tokens_in": 0,
204
+ "total_tokens_out": 0,
205
+ })
206
+ bucket["call_count"] += row.get("call_count", 0)
207
+ bucket["total_tokens_in"] += row.get("total_tokens_in", 0)
208
+ bucket["total_tokens_out"] += row.get("total_tokens_out", 0)
209
+ cost = row.get("total_cost_usd")
210
+ if isinstance(cost, (int, float)):
211
+ bucket["total_cost_usd"] += float(cost)
212
+ bucket["any_cost_known"] = True
213
+ for dept, b in out.items():
214
+ if not b.pop("any_cost_known"):
215
+ b["total_cost_usd"] = None
216
+ else:
217
+ b["total_cost_usd"] = round(b["total_cost_usd"], 6)
218
+ return {"by_department": out, "period": period}
219
+
220
+
176
221
  @app.get("/api/agents/{agent_id}")
177
222
  def agent_detail(agent_id: str):
178
223
  """Get full agent detail including YAML data."""