@agent-live/cli 0.0.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/bin.mjs +41 -0
- package/console-dist/assets/components-0p8SghrX.js +1 -0
- package/console-dist/assets/index-49jCgePm.js +1 -0
- package/console-dist/assets/index-BnLKN4xZ.css +1 -0
- package/console-dist/assets/index-jfHKGDWM.js +76 -0
- package/console-dist/assets/page-BRDi_757.js +1 -0
- package/console-dist/assets/page-BpIZP_o-.js +1 -0
- package/console-dist/assets/page-Byx3HD88.js +1 -0
- package/console-dist/assets/page-CEUp9Rfn.js +1 -0
- package/console-dist/assets/page-CmST3snz.js +1 -0
- package/console-dist/assets/page-DMYOtzYN.js +1 -0
- package/console-dist/index.html +4 -0
- package/package.json +19 -0
- package/server/demo/data.mjs +524 -0
- package/server/http.mjs +102 -0
- package/server/real/data.mjs +170 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { openDb, createSqliteStore, subscribeAll } from '@agent-live/sdk'
|
|
2
|
+
|
|
3
|
+
const CHANNELS = new Set(['whatsapp', 'email', 'web', 'api'])
|
|
4
|
+
const DEGRADED_FAILURE_RATE = 0.2
|
|
5
|
+
const STALE_BUSY_WINDOW_MS = 5 * 60_000
|
|
6
|
+
|
|
7
|
+
function coerceChannel(channel) {
|
|
8
|
+
return CHANNELS.has(channel) ? channel : 'api'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function coerceStatus(status) {
|
|
12
|
+
// The contract has no 'stale' status — a run orphaned by a server
|
|
13
|
+
// restart reads most truthfully as cancelled, not completed/failed.
|
|
14
|
+
return status === 'stale' ? 'cancelled' : status
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function customerAliasFor(sessionKey) {
|
|
18
|
+
return sessionKey ? `Session ${sessionKey.slice(0, 8)}` : 'Unknown session'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toRun(row) {
|
|
22
|
+
return {
|
|
23
|
+
id: row.id,
|
|
24
|
+
agentId: row.agentId || 'unassigned',
|
|
25
|
+
customerId: row.sessionKey,
|
|
26
|
+
channel: coerceChannel(row.channel),
|
|
27
|
+
status: coerceStatus(row.status),
|
|
28
|
+
startedAt: row.startedAt,
|
|
29
|
+
completedAt: row.completedAt,
|
|
30
|
+
durationMs: row.durationMs,
|
|
31
|
+
summary: row.outputPreview || row.inputPreview || '',
|
|
32
|
+
agentName: row.agentName || row.agentId || 'Unassigned agent',
|
|
33
|
+
customerAlias: customerAliasFor(row.sessionKey),
|
|
34
|
+
customerHint: coerceChannel(row.channel),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isRunningRecently(run) {
|
|
39
|
+
return run.status === 'running' && Date.now() - Date.parse(run.startedAt) < STALE_BUSY_WINDOW_MS
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Opens the SDK's SQLite store and exposes the console's `/api/*` shapes from real recorded data. */
|
|
43
|
+
export function createRealData({ dbPath }) {
|
|
44
|
+
const db = openDb(dbPath)
|
|
45
|
+
const store = createSqliteStore(db)
|
|
46
|
+
store.markStaleRunsOnStartup()
|
|
47
|
+
|
|
48
|
+
function allRuns() {
|
|
49
|
+
return store.listRunsAll({ limit: 500 })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function agentGroups() {
|
|
53
|
+
const groups = new Map()
|
|
54
|
+
for (const run of allRuns()) {
|
|
55
|
+
const agentId = run.agentId || 'unassigned'
|
|
56
|
+
if (!groups.has(agentId)) groups.set(agentId, [])
|
|
57
|
+
groups.get(agentId).push(run)
|
|
58
|
+
}
|
|
59
|
+
return groups
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function agentSummary(agentId, runs) {
|
|
63
|
+
const today = Date.now() - 24 * 60 * 60_000
|
|
64
|
+
const runsToday = runs.filter((r) => Date.parse(r.startedAt) >= today)
|
|
65
|
+
const terminal = runs.filter((r) => r.status === 'completed' || r.status === 'failed')
|
|
66
|
+
const failureRate = terminal.length ? terminal.filter((r) => r.status === 'failed').length / terminal.length : 0
|
|
67
|
+
const busy = runs.some(isRunningRecently)
|
|
68
|
+
const status = busy ? 'busy' : failureRate >= DEGRADED_FAILURE_RATE ? 'degraded' : 'idle'
|
|
69
|
+
const latest = runs.slice().sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt))[0]
|
|
70
|
+
const name = latest?.agentName || agentId
|
|
71
|
+
const channels = [...new Set(runs.map((r) => coerceChannel(r.channel)))]
|
|
72
|
+
return {
|
|
73
|
+
id: agentId,
|
|
74
|
+
name,
|
|
75
|
+
description: '',
|
|
76
|
+
status,
|
|
77
|
+
channels,
|
|
78
|
+
runsToday: runsToday.length,
|
|
79
|
+
failureRate,
|
|
80
|
+
lastActivityAt: latest?.startedAt ?? new Date(0).toISOString(),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function listAgents() {
|
|
85
|
+
const groups = agentGroups()
|
|
86
|
+
const agents = [...groups.entries()]
|
|
87
|
+
.filter(([agentId]) => agentId && agentId !== 'unassigned')
|
|
88
|
+
.map(([agentId, runs]) => agentSummary(agentId, runs))
|
|
89
|
+
return { agents }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function channelBreakdown() {
|
|
93
|
+
const runs = allRuns()
|
|
94
|
+
const counts = {}
|
|
95
|
+
for (const run of runs) {
|
|
96
|
+
const channel = coerceChannel(run.channel)
|
|
97
|
+
counts[channel] = (counts[channel] || 0) + 1
|
|
98
|
+
}
|
|
99
|
+
const total = runs.length || 1
|
|
100
|
+
return Object.entries(counts)
|
|
101
|
+
.map(([channel, count]) => ({ channel, count, pct: Math.round((count / total) * 100) }))
|
|
102
|
+
.sort((a, b) => b.count - a.count)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function listTouchpoints() {
|
|
106
|
+
// Not tracked by the recorder yet — see plan decision on empty vs. derived state.
|
|
107
|
+
return { breakdown: channelBreakdown(), recent: [] }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function listRuns(agentId) {
|
|
111
|
+
return { runs: allRuns().filter((r) => !agentId || r.agentId === agentId).map(toRun) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function listApprovals() {
|
|
115
|
+
return { approvals: [] }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function listDeliveries() {
|
|
119
|
+
return { deliveries: [] }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildOverview() {
|
|
123
|
+
const runs = allRuns()
|
|
124
|
+
const { agents } = listAgents()
|
|
125
|
+
return {
|
|
126
|
+
org: { id: 'self-hosted', name: 'Self-hosted', mode: 'self-hosted' },
|
|
127
|
+
stats: {
|
|
128
|
+
activeAgents: agents.filter((a) => a.status === 'busy' || a.status === 'degraded').length,
|
|
129
|
+
agentCount: agents.length,
|
|
130
|
+
openApprovals: 0,
|
|
131
|
+
failedDeliveries: 0,
|
|
132
|
+
touchesToday: runs.filter((r) => Date.now() - Date.parse(r.startedAt) < 24 * 60 * 60_000).length,
|
|
133
|
+
runningRuns: runs.filter((r) => r.status === 'running').length,
|
|
134
|
+
},
|
|
135
|
+
recentRuns: runs
|
|
136
|
+
.slice()
|
|
137
|
+
.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt))
|
|
138
|
+
.slice(0, 6)
|
|
139
|
+
.map(toRun),
|
|
140
|
+
channelBreakdown: channelBreakdown(),
|
|
141
|
+
attention: [],
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Bridges the SDK's cross-tenant event bus into the console's SSE `run-event` shape. */
|
|
146
|
+
function subscribe(broadcast) {
|
|
147
|
+
return subscribeAll((event) => {
|
|
148
|
+
const run = store.getRun(event.runId)
|
|
149
|
+
broadcast('run-event', {
|
|
150
|
+
...event,
|
|
151
|
+
agentId: run?.agentId ?? null,
|
|
152
|
+
agentName: run?.agentName ?? run?.agentId ?? null,
|
|
153
|
+
customerAlias: run ? customerAliasFor(run.sessionKey) : null,
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
buildOverview,
|
|
160
|
+
listAgents,
|
|
161
|
+
listTouchpoints,
|
|
162
|
+
listRuns,
|
|
163
|
+
listApprovals,
|
|
164
|
+
listDeliveries,
|
|
165
|
+
subscribe,
|
|
166
|
+
dispose() {
|
|
167
|
+
db.close()
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|