universal-agent-memory 2.7.2 → 2.8.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/dist/bin/cli.js +28 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/agent.js +104 -58
- package/dist/cli/agent.js.map +1 -1
- package/dist/cli/coord.d.ts.map +1 -1
- package/dist/cli/coord.js +35 -42
- package/dist/cli/coord.js.map +1 -1
- package/dist/cli/dashboard.d.ts +8 -0
- package/dist/cli/dashboard.d.ts.map +1 -0
- package/dist/cli/dashboard.js +654 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/cli/memory.d.ts.map +1 -1
- package/dist/cli/memory.js +53 -41
- package/dist/cli/memory.js.map +1 -1
- package/dist/cli/task.d.ts.map +1 -1
- package/dist/cli/task.js +68 -22
- package/dist/cli/task.js.map +1 -1
- package/dist/cli/visualize.d.ts +77 -0
- package/dist/cli/visualize.d.ts.map +1 -0
- package/dist/cli/visualize.js +287 -0
- package/dist/cli/visualize.js.map +1 -0
- package/package.json +1 -1
- package/templates/CLAUDE.template.md +62 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { existsSync, statSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import { TaskService } from '../tasks/service.js';
|
|
7
|
+
import { CoordinationService } from '../coordination/service.js';
|
|
8
|
+
import { SQLiteShortTermMemory } from '../memory/short-term/sqlite.js';
|
|
9
|
+
import { progressBar, stackedBar, stackedBarLegend, horizontalBarChart, table, tree, box, sectionHeader, keyValue, miniGauge, statusBadge, divider, bulletList, columns, } from './visualize.js';
|
|
10
|
+
import { STATUS_ICONS, TYPE_ICONS, PRIORITY_LABELS } from '../tasks/types.js';
|
|
11
|
+
export async function dashboardCommand(action, options = {}) {
|
|
12
|
+
switch (action) {
|
|
13
|
+
case 'overview':
|
|
14
|
+
await showOverview(options);
|
|
15
|
+
break;
|
|
16
|
+
case 'tasks':
|
|
17
|
+
await showTaskDashboard(options);
|
|
18
|
+
break;
|
|
19
|
+
case 'agents':
|
|
20
|
+
await showAgentDashboard(options);
|
|
21
|
+
break;
|
|
22
|
+
case 'memory':
|
|
23
|
+
await showMemoryDashboard(options);
|
|
24
|
+
break;
|
|
25
|
+
case 'progress':
|
|
26
|
+
await showProgressDashboard(options);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function showOverview(_options) {
|
|
31
|
+
const spinner = ora('Loading dashboard...').start();
|
|
32
|
+
try {
|
|
33
|
+
const taskService = new TaskService();
|
|
34
|
+
const coordService = new CoordinationService();
|
|
35
|
+
const stats = taskService.getStats();
|
|
36
|
+
const coordStatus = coordService.getStatus();
|
|
37
|
+
spinner.stop();
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(chalk.bold.cyan(' UAM Dashboard'));
|
|
40
|
+
console.log(divider(60));
|
|
41
|
+
console.log('');
|
|
42
|
+
// Task completion progress
|
|
43
|
+
const completedTasks = stats.byStatus.done + stats.byStatus.wont_do;
|
|
44
|
+
const activeTasks = stats.total - completedTasks;
|
|
45
|
+
console.log(sectionHeader('Task Progress'));
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(` ${progressBar(completedTasks, stats.total, 40, {
|
|
48
|
+
label: 'Completion',
|
|
49
|
+
filled: chalk.green,
|
|
50
|
+
})}`);
|
|
51
|
+
console.log('');
|
|
52
|
+
// Status breakdown bar
|
|
53
|
+
const statusSegments = [
|
|
54
|
+
{ value: stats.byStatus.done, color: chalk.green, label: `Done ${STATUS_ICONS.done}` },
|
|
55
|
+
{ value: stats.byStatus.in_progress, color: chalk.cyan, label: `In Progress ${STATUS_ICONS.in_progress}` },
|
|
56
|
+
{ value: stats.byStatus.open, color: chalk.white, label: `Open ${STATUS_ICONS.open}` },
|
|
57
|
+
{ value: stats.byStatus.blocked, color: chalk.red, label: `Blocked ${STATUS_ICONS.blocked}` },
|
|
58
|
+
{ value: stats.byStatus.wont_do, color: chalk.dim, label: `Won't Do ${STATUS_ICONS.wont_do}` },
|
|
59
|
+
];
|
|
60
|
+
console.log(` ${stackedBar(statusSegments, stats.total, 50)}`);
|
|
61
|
+
console.log(` ${stackedBarLegend(statusSegments)}`);
|
|
62
|
+
console.log('');
|
|
63
|
+
// Two-column layout: Priority vs Type
|
|
64
|
+
const priorityLines = [
|
|
65
|
+
chalk.bold(' By Priority'),
|
|
66
|
+
...horizontalBarChart([
|
|
67
|
+
{ label: 'P0 Critical', value: stats.byPriority[0], color: chalk.red },
|
|
68
|
+
{ label: 'P1 High', value: stats.byPriority[1], color: chalk.yellow },
|
|
69
|
+
{ label: 'P2 Medium', value: stats.byPriority[2], color: chalk.blue },
|
|
70
|
+
{ label: 'P3 Low', value: stats.byPriority[3], color: chalk.dim },
|
|
71
|
+
{ label: 'P4 Backlog', value: stats.byPriority[4], color: chalk.dim },
|
|
72
|
+
], { maxWidth: 20, maxLabelWidth: 14 }),
|
|
73
|
+
];
|
|
74
|
+
const typeData = Object.entries(stats.byType)
|
|
75
|
+
.filter(([, count]) => count > 0);
|
|
76
|
+
const typeLines = [
|
|
77
|
+
chalk.bold(' By Type'),
|
|
78
|
+
...horizontalBarChart(typeData.map(([type, count]) => ({
|
|
79
|
+
label: `${TYPE_ICONS[type]} ${type}`,
|
|
80
|
+
value: count,
|
|
81
|
+
color: chalk.magenta,
|
|
82
|
+
})), { maxWidth: 20, maxLabelWidth: 14 }),
|
|
83
|
+
];
|
|
84
|
+
const combined = columns(priorityLines, typeLines, { gap: 6, leftWidth: 42 });
|
|
85
|
+
for (const line of combined)
|
|
86
|
+
console.log(line);
|
|
87
|
+
console.log('');
|
|
88
|
+
// Agent Status
|
|
89
|
+
console.log(sectionHeader('Agents & Coordination'));
|
|
90
|
+
console.log('');
|
|
91
|
+
const agentItems = coordStatus.activeAgents.map(a => ({
|
|
92
|
+
text: `${chalk.cyan(a.name)} ${statusBadge(a.status)}${a.currentTask ? chalk.dim(` working on ${a.currentTask}`) : ''}`,
|
|
93
|
+
status: a.status === 'active' ? 'ok' : 'warn',
|
|
94
|
+
}));
|
|
95
|
+
if (agentItems.length > 0) {
|
|
96
|
+
for (const line of bulletList(agentItems))
|
|
97
|
+
console.log(line);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(chalk.dim(' No active agents'));
|
|
101
|
+
}
|
|
102
|
+
console.log('');
|
|
103
|
+
for (const line of keyValue([
|
|
104
|
+
['Active Agents', coordStatus.activeAgents.length],
|
|
105
|
+
['Resource Claims', coordStatus.activeClaims.length],
|
|
106
|
+
['Pending Deploys', coordStatus.pendingDeploys.length],
|
|
107
|
+
['Unread Messages', coordStatus.pendingMessages],
|
|
108
|
+
]))
|
|
109
|
+
console.log(line);
|
|
110
|
+
// Memory summary
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(sectionHeader('Memory'));
|
|
113
|
+
console.log('');
|
|
114
|
+
const cwd = process.cwd();
|
|
115
|
+
const dbPath = join(cwd, 'agents/data/memory/short_term.db');
|
|
116
|
+
const memoryItems = [];
|
|
117
|
+
if (existsSync(dbPath)) {
|
|
118
|
+
const dbStats = statSync(dbPath);
|
|
119
|
+
const sizeKB = Math.round(dbStats.size / 1024);
|
|
120
|
+
memoryItems.push({
|
|
121
|
+
text: `Short-term: ${chalk.bold(sizeKB + ' KB')} ${chalk.dim(`(modified ${dbStats.mtime.toLocaleDateString()})`)}`,
|
|
122
|
+
status: 'ok',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
memoryItems.push({ text: 'Short-term: Not initialized', status: 'warn' });
|
|
127
|
+
}
|
|
128
|
+
let qdrantRunning = false;
|
|
129
|
+
try {
|
|
130
|
+
const dockerStatus = execSync('docker ps --filter name=qdrant --format "{{.Status}}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
131
|
+
if (dockerStatus) {
|
|
132
|
+
memoryItems.push({ text: `Qdrant: ${chalk.bold('Running')} ${chalk.dim(dockerStatus)}`, status: 'ok' });
|
|
133
|
+
qdrantRunning = true;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
memoryItems.push({ text: 'Qdrant: Stopped', status: 'warn' });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
memoryItems.push({ text: 'Qdrant: Not available', status: 'warn' });
|
|
141
|
+
}
|
|
142
|
+
for (const line of bulletList(memoryItems))
|
|
143
|
+
console.log(line);
|
|
144
|
+
// Summary box
|
|
145
|
+
console.log('');
|
|
146
|
+
const summaryContent = [
|
|
147
|
+
`Tasks: ${chalk.bold(stats.total)} total, ${chalk.green(completedTasks + ' done')}, ${chalk.yellow(activeTasks + ' active')}`,
|
|
148
|
+
`Agents: ${chalk.bold(coordStatus.activeAgents.length)} active`,
|
|
149
|
+
`Memory: ${existsSync(dbPath) ? chalk.green('SQLite') : chalk.dim('None')} / ${qdrantRunning ? chalk.green('Qdrant') : chalk.dim('No Qdrant')}`,
|
|
150
|
+
];
|
|
151
|
+
for (const line of box('Summary', summaryContent, { borderColor: chalk.cyan })) {
|
|
152
|
+
console.log(` ${line}`);
|
|
153
|
+
}
|
|
154
|
+
console.log('');
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
spinner.fail('Failed to load dashboard');
|
|
158
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function showTaskDashboard(options) {
|
|
162
|
+
const spinner = ora('Loading task dashboard...').start();
|
|
163
|
+
try {
|
|
164
|
+
const service = new TaskService();
|
|
165
|
+
const stats = service.getStats();
|
|
166
|
+
const allTasks = service.list({});
|
|
167
|
+
const readyTasks = service.ready();
|
|
168
|
+
const blockedTasks = service.blocked();
|
|
169
|
+
spinner.stop();
|
|
170
|
+
console.log('');
|
|
171
|
+
console.log(chalk.bold.cyan(' Task Dashboard'));
|
|
172
|
+
console.log(divider(60));
|
|
173
|
+
console.log('');
|
|
174
|
+
// Completion gauge
|
|
175
|
+
const done = stats.byStatus.done + stats.byStatus.wont_do;
|
|
176
|
+
console.log(` ${chalk.bold('Completion')} ${miniGauge(done, stats.total, 20)} ${chalk.bold(Math.round(done / Math.max(stats.total, 1) * 100) + '%')} ${chalk.dim(`(${done}/${stats.total})`)}`);
|
|
177
|
+
console.log(` ${chalk.bold('In Flight ')} ${miniGauge(stats.byStatus.in_progress, stats.total, 20)} ${chalk.dim(`${stats.byStatus.in_progress} tasks`)}`);
|
|
178
|
+
console.log(` ${chalk.bold('Blocked ')} ${miniGauge(stats.byStatus.blocked, stats.total, 20)} ${chalk.dim(`${stats.byStatus.blocked} tasks`)}`);
|
|
179
|
+
console.log('');
|
|
180
|
+
// Status stacked bar
|
|
181
|
+
console.log(sectionHeader('Status Distribution'));
|
|
182
|
+
console.log('');
|
|
183
|
+
const segments = [
|
|
184
|
+
{ value: stats.byStatus.done, color: chalk.green, label: 'Done' },
|
|
185
|
+
{ value: stats.byStatus.in_progress, color: chalk.cyan, label: 'In Progress' },
|
|
186
|
+
{ value: stats.byStatus.open, color: chalk.white, label: 'Open' },
|
|
187
|
+
{ value: stats.byStatus.blocked, color: chalk.red, label: 'Blocked' },
|
|
188
|
+
{ value: stats.byStatus.wont_do, color: chalk.dim, label: "Won't Do" },
|
|
189
|
+
];
|
|
190
|
+
console.log(` ${stackedBar(segments, stats.total, 50)}`);
|
|
191
|
+
console.log(` ${stackedBarLegend(segments)}`);
|
|
192
|
+
console.log('');
|
|
193
|
+
// Priority chart
|
|
194
|
+
console.log(sectionHeader('Priority Breakdown'));
|
|
195
|
+
console.log('');
|
|
196
|
+
for (const line of horizontalBarChart([
|
|
197
|
+
{ label: 'P0 Critical', value: stats.byPriority[0], color: chalk.red },
|
|
198
|
+
{ label: 'P1 High', value: stats.byPriority[1], color: chalk.yellow },
|
|
199
|
+
{ label: 'P2 Medium', value: stats.byPriority[2], color: chalk.blue },
|
|
200
|
+
{ label: 'P3 Low', value: stats.byPriority[3], color: chalk.dim },
|
|
201
|
+
{ label: 'P4 Backlog', value: stats.byPriority[4], color: chalk.dim },
|
|
202
|
+
], { maxWidth: 35, maxLabelWidth: 14 })) {
|
|
203
|
+
console.log(line);
|
|
204
|
+
}
|
|
205
|
+
console.log('');
|
|
206
|
+
// Type chart
|
|
207
|
+
const typeData = Object.entries(stats.byType)
|
|
208
|
+
.filter(([, count]) => count > 0);
|
|
209
|
+
if (typeData.length > 0) {
|
|
210
|
+
console.log(sectionHeader('Type Breakdown'));
|
|
211
|
+
console.log('');
|
|
212
|
+
for (const line of horizontalBarChart(typeData.map(([type, count]) => ({
|
|
213
|
+
label: `${TYPE_ICONS[type]} ${type}`,
|
|
214
|
+
value: count,
|
|
215
|
+
color: chalk.magenta,
|
|
216
|
+
})), { maxWidth: 35, maxLabelWidth: 14 })) {
|
|
217
|
+
console.log(line);
|
|
218
|
+
}
|
|
219
|
+
console.log('');
|
|
220
|
+
}
|
|
221
|
+
// Ready tasks table
|
|
222
|
+
if (readyTasks.length > 0) {
|
|
223
|
+
console.log(sectionHeader('Ready to Work'));
|
|
224
|
+
console.log('');
|
|
225
|
+
const readyRows = readyTasks.slice(0, 10).map(t => ({
|
|
226
|
+
id: t.id,
|
|
227
|
+
priority: `P${t.priority}`,
|
|
228
|
+
type: TYPE_ICONS[t.type],
|
|
229
|
+
title: t.title.slice(0, 40) + (t.title.length > 40 ? '...' : ''),
|
|
230
|
+
}));
|
|
231
|
+
for (const line of table(readyRows, [
|
|
232
|
+
{ key: 'id', header: 'ID', width: 10, color: chalk.cyan },
|
|
233
|
+
{ key: 'priority', header: 'Pri', width: 5 },
|
|
234
|
+
{ key: 'type', header: 'T', width: 3 },
|
|
235
|
+
{ key: 'title', header: 'Title', width: 42 },
|
|
236
|
+
])) {
|
|
237
|
+
console.log(line);
|
|
238
|
+
}
|
|
239
|
+
if (readyTasks.length > 10) {
|
|
240
|
+
console.log(chalk.dim(` ... and ${readyTasks.length - 10} more`));
|
|
241
|
+
}
|
|
242
|
+
console.log('');
|
|
243
|
+
}
|
|
244
|
+
// Blocked tasks
|
|
245
|
+
if (blockedTasks.length > 0) {
|
|
246
|
+
console.log(sectionHeader('Blocked Tasks'));
|
|
247
|
+
console.log('');
|
|
248
|
+
for (const t of blockedTasks.slice(0, 5)) {
|
|
249
|
+
console.log(` ${chalk.red(STATUS_ICONS.blocked)} ${chalk.cyan(t.id)} ${t.title}`);
|
|
250
|
+
if (t.blockedBy.length > 0) {
|
|
251
|
+
console.log(chalk.red(` Blocked by: ${t.blockedBy.join(', ')}`));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
console.log('');
|
|
255
|
+
}
|
|
256
|
+
// In-progress tasks
|
|
257
|
+
const inProgress = allTasks.filter(t => t.status === 'in_progress');
|
|
258
|
+
if (inProgress.length > 0) {
|
|
259
|
+
console.log(sectionHeader('In Progress'));
|
|
260
|
+
console.log('');
|
|
261
|
+
for (const t of inProgress) {
|
|
262
|
+
console.log(` ${chalk.cyan(STATUS_ICONS.in_progress)} ${chalk.cyan(t.id)} ${t.title}`);
|
|
263
|
+
if (t.assignee)
|
|
264
|
+
console.log(chalk.dim(` Assigned: ${t.assignee}`));
|
|
265
|
+
}
|
|
266
|
+
console.log('');
|
|
267
|
+
}
|
|
268
|
+
// Task hierarchy tree (epics with children)
|
|
269
|
+
const epics = allTasks.filter(t => t.type === 'epic' && t.status !== 'done' && t.status !== 'wont_do');
|
|
270
|
+
if (epics.length > 0 && !options.compact) {
|
|
271
|
+
console.log(sectionHeader('Task Hierarchy'));
|
|
272
|
+
console.log('');
|
|
273
|
+
for (const epic of epics.slice(0, 3)) {
|
|
274
|
+
const children = allTasks.filter(t => t.parentId === epic.id);
|
|
275
|
+
const epicNode = {
|
|
276
|
+
label: `${chalk.bold(epic.title)} ${chalk.dim(epic.id)}`,
|
|
277
|
+
status: STATUS_ICONS[epic.status],
|
|
278
|
+
children: children.map(c => ({
|
|
279
|
+
label: `${c.title} ${chalk.dim(c.id)}`,
|
|
280
|
+
status: STATUS_ICONS[c.status],
|
|
281
|
+
meta: `P${c.priority} ${c.type}`,
|
|
282
|
+
})),
|
|
283
|
+
};
|
|
284
|
+
for (const line of tree(epicNode))
|
|
285
|
+
console.log(line);
|
|
286
|
+
}
|
|
287
|
+
console.log('');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
spinner.fail('Failed to load task dashboard');
|
|
292
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function showAgentDashboard(_options) {
|
|
296
|
+
const spinner = ora('Loading agent dashboard...').start();
|
|
297
|
+
try {
|
|
298
|
+
const coordService = new CoordinationService();
|
|
299
|
+
const status = coordService.getStatus();
|
|
300
|
+
const activeWork = coordService.getActiveWork();
|
|
301
|
+
spinner.stop();
|
|
302
|
+
console.log('');
|
|
303
|
+
console.log(chalk.bold.cyan(' Agent Dashboard'));
|
|
304
|
+
console.log(divider(60));
|
|
305
|
+
console.log('');
|
|
306
|
+
// Agent count and status
|
|
307
|
+
console.log(sectionHeader('Active Agents'));
|
|
308
|
+
console.log('');
|
|
309
|
+
if (status.activeAgents.length === 0) {
|
|
310
|
+
console.log(chalk.dim(' No active agents registered'));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
const agentRows = status.activeAgents.map(a => ({
|
|
314
|
+
name: a.name,
|
|
315
|
+
status: statusBadge(a.status),
|
|
316
|
+
task: a.currentTask || chalk.dim('idle'),
|
|
317
|
+
heartbeat: chalk.dim(a.lastHeartbeat.slice(11, 19)),
|
|
318
|
+
}));
|
|
319
|
+
for (const line of table(agentRows, [
|
|
320
|
+
{ key: 'name', header: 'Agent', width: 18, color: chalk.cyan },
|
|
321
|
+
{ key: 'status', header: 'Status', width: 16 },
|
|
322
|
+
{ key: 'task', header: 'Current Task', width: 20 },
|
|
323
|
+
{ key: 'heartbeat', header: 'Last Beat', width: 10 },
|
|
324
|
+
])) {
|
|
325
|
+
console.log(line);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
console.log('');
|
|
329
|
+
// Resource claims
|
|
330
|
+
console.log(sectionHeader('Resource Claims'));
|
|
331
|
+
console.log('');
|
|
332
|
+
if (status.activeClaims.length === 0) {
|
|
333
|
+
console.log(chalk.dim(' No active resource claims'));
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
for (const claim of status.activeClaims) {
|
|
337
|
+
const lockIcon = claim.claimType === 'exclusive' ? chalk.red('EXCL') : chalk.green('SHARED');
|
|
338
|
+
console.log(` ${lockIcon} ${chalk.yellow(claim.resource)}`);
|
|
339
|
+
console.log(chalk.dim(` Agent: ${claim.agentId.slice(0, 8)}...`));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
console.log('');
|
|
343
|
+
// Active work visualization
|
|
344
|
+
if (activeWork.length > 0) {
|
|
345
|
+
console.log(sectionHeader('Active Work'));
|
|
346
|
+
console.log('');
|
|
347
|
+
const grouped = new Map();
|
|
348
|
+
for (const work of activeWork) {
|
|
349
|
+
const existing = grouped.get(work.resource) || [];
|
|
350
|
+
existing.push(work);
|
|
351
|
+
grouped.set(work.resource, existing);
|
|
352
|
+
}
|
|
353
|
+
for (const [resource, works] of grouped) {
|
|
354
|
+
const hasConflict = works.length > 1;
|
|
355
|
+
const icon = hasConflict ? chalk.red('!!') : chalk.green('OK');
|
|
356
|
+
console.log(` ${icon} ${chalk.bold(resource)}`);
|
|
357
|
+
for (const w of works) {
|
|
358
|
+
console.log(` ${chalk.cyan(w.agentName || w.agentId.slice(0, 8))} ${chalk.dim(w.intentType)}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
console.log('');
|
|
362
|
+
}
|
|
363
|
+
// Deploy queue
|
|
364
|
+
console.log(sectionHeader('Deploy Queue'));
|
|
365
|
+
console.log('');
|
|
366
|
+
if (status.pendingDeploys.length === 0) {
|
|
367
|
+
console.log(chalk.dim(' No pending deploys'));
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
console.log(` ${chalk.bold(String(status.pendingDeploys.length))} pending deploy action(s)`);
|
|
371
|
+
const grouped = new Map();
|
|
372
|
+
for (const d of status.pendingDeploys) {
|
|
373
|
+
grouped.set(d.actionType, (grouped.get(d.actionType) || 0) + 1);
|
|
374
|
+
}
|
|
375
|
+
for (const line of horizontalBarChart([...grouped.entries()].map(([type, count]) => ({
|
|
376
|
+
label: type,
|
|
377
|
+
value: count,
|
|
378
|
+
color: chalk.yellow,
|
|
379
|
+
})), { maxWidth: 20, maxLabelWidth: 12 })) {
|
|
380
|
+
console.log(line);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
console.log('');
|
|
384
|
+
// Summary
|
|
385
|
+
for (const line of keyValue([
|
|
386
|
+
['Total Agents', status.activeAgents.length],
|
|
387
|
+
['Resource Claims', status.activeClaims.length],
|
|
388
|
+
['Active Work Items', activeWork.length],
|
|
389
|
+
['Pending Deploys', status.pendingDeploys.length],
|
|
390
|
+
['Unread Messages', status.pendingMessages],
|
|
391
|
+
]))
|
|
392
|
+
console.log(line);
|
|
393
|
+
console.log('');
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
spinner.fail('Failed to load agent dashboard');
|
|
397
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function showMemoryDashboard(_options) {
|
|
401
|
+
const spinner = ora('Loading memory dashboard...').start();
|
|
402
|
+
try {
|
|
403
|
+
const cwd = process.cwd();
|
|
404
|
+
const dbPath = join(cwd, 'agents/data/memory/short_term.db');
|
|
405
|
+
spinner.stop();
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log(chalk.bold.cyan(' Memory Dashboard'));
|
|
408
|
+
console.log(divider(60));
|
|
409
|
+
console.log('');
|
|
410
|
+
// Short-term memory
|
|
411
|
+
console.log(sectionHeader('Short-Term Memory (SQLite)'));
|
|
412
|
+
console.log('');
|
|
413
|
+
if (existsSync(dbPath)) {
|
|
414
|
+
const dbStats = statSync(dbPath);
|
|
415
|
+
const sizeKB = Math.round(dbStats.size / 1024);
|
|
416
|
+
try {
|
|
417
|
+
const shortTermDb = new SQLiteShortTermMemory({
|
|
418
|
+
dbPath,
|
|
419
|
+
projectId: 'dashboard',
|
|
420
|
+
maxEntries: 9999,
|
|
421
|
+
});
|
|
422
|
+
const count = await shortTermDb.count();
|
|
423
|
+
await shortTermDb.close();
|
|
424
|
+
for (const line of keyValue([
|
|
425
|
+
['Status', 'Active'],
|
|
426
|
+
['Entries', count],
|
|
427
|
+
['Size', `${sizeKB} KB`],
|
|
428
|
+
['Last Modified', dbStats.mtime.toLocaleDateString()],
|
|
429
|
+
['Path', dbPath],
|
|
430
|
+
]))
|
|
431
|
+
console.log(line);
|
|
432
|
+
console.log('');
|
|
433
|
+
console.log(` ${chalk.bold('Capacity')} ${miniGauge(count, 50, 20)} ${chalk.dim(`${count}/50 entries`)}`);
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
console.log(` ${statusBadge('active')} ${chalk.dim(`${sizeKB} KB`)}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
console.log(` ${statusBadge('not_available')} Not initialized`);
|
|
441
|
+
}
|
|
442
|
+
console.log('');
|
|
443
|
+
// Qdrant status
|
|
444
|
+
console.log(sectionHeader('Long-Term Memory (Qdrant)'));
|
|
445
|
+
console.log('');
|
|
446
|
+
try {
|
|
447
|
+
const dockerStatus = execSync('docker ps --filter name=qdrant --format "{{.Status}}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
448
|
+
if (dockerStatus) {
|
|
449
|
+
console.log(` ${statusBadge('running')} ${chalk.dim(dockerStatus)}`);
|
|
450
|
+
try {
|
|
451
|
+
const dockerInspect = execSync('docker inspect --format "{{.Config.Image}}" uam-qdrant 2>/dev/null || docker inspect --format "{{.Config.Image}}" qdrant 2>/dev/null', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
452
|
+
if (dockerInspect) {
|
|
453
|
+
console.log(chalk.dim(` Image: ${dockerInspect}`));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch { /* ignore */ }
|
|
457
|
+
console.log(chalk.dim(' Endpoint: http://localhost:6333'));
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
console.log(` ${statusBadge('stopped')} Container not running`);
|
|
461
|
+
console.log(chalk.dim(' Start with: uam memory start'));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
console.log(` ${statusBadge('not_available')} Docker not available`);
|
|
466
|
+
}
|
|
467
|
+
console.log('');
|
|
468
|
+
// Embeddings
|
|
469
|
+
console.log(sectionHeader('Embeddings (Ollama)'));
|
|
470
|
+
console.log('');
|
|
471
|
+
try {
|
|
472
|
+
const ollamaResponse = await fetch('http://localhost:11434/api/tags', {
|
|
473
|
+
method: 'GET',
|
|
474
|
+
signal: AbortSignal.timeout(2000),
|
|
475
|
+
});
|
|
476
|
+
if (ollamaResponse.ok) {
|
|
477
|
+
const ollamaData = await ollamaResponse.json();
|
|
478
|
+
const embedModels = ollamaData.models?.filter(m => m.name.includes('embed') || m.name.includes('nomic')) || [];
|
|
479
|
+
if (embedModels.length > 0) {
|
|
480
|
+
console.log(` ${statusBadge('active')}`);
|
|
481
|
+
for (const model of embedModels) {
|
|
482
|
+
const sizeMB = Math.round((model.size || 0) / 1024 / 1024);
|
|
483
|
+
console.log(` ${chalk.cyan(model.name)} ${chalk.dim(`${sizeMB} MB`)}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
console.log(` ${statusBadge('not_available')} No embedding models found`);
|
|
488
|
+
console.log(chalk.dim(' Install: ollama pull nomic-embed-text'));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
console.log(` ${statusBadge('stopped')} Not responding`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
console.log(` ${statusBadge('not_available')} Ollama not running`);
|
|
497
|
+
console.log(chalk.dim(' Install from https://ollama.ai'));
|
|
498
|
+
}
|
|
499
|
+
// Memory layers summary
|
|
500
|
+
console.log('');
|
|
501
|
+
console.log(sectionHeader('Memory Layer Architecture'));
|
|
502
|
+
console.log('');
|
|
503
|
+
const layers = {
|
|
504
|
+
label: chalk.bold('UAM Memory System'),
|
|
505
|
+
children: [
|
|
506
|
+
{
|
|
507
|
+
label: 'L1 Working Memory',
|
|
508
|
+
status: existsSync(dbPath) ? chalk.green('ON') : chalk.red('OFF'),
|
|
509
|
+
meta: 'SQLite, <1ms',
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
label: 'L2 Session Memory',
|
|
513
|
+
status: existsSync(dbPath) ? chalk.green('ON') : chalk.red('OFF'),
|
|
514
|
+
meta: 'SQLite, <5ms',
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
label: 'L3 Semantic Memory',
|
|
518
|
+
status: chalk.yellow('?'),
|
|
519
|
+
meta: 'Qdrant, ~50ms',
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
label: 'L4 Knowledge Graph',
|
|
523
|
+
status: existsSync(dbPath) ? chalk.green('ON') : chalk.red('OFF'),
|
|
524
|
+
meta: 'SQLite entities/rels',
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
for (const line of tree(layers))
|
|
529
|
+
console.log(line);
|
|
530
|
+
console.log('');
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
spinner.fail('Failed to load memory dashboard');
|
|
534
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async function showProgressDashboard(_options) {
|
|
538
|
+
const spinner = ora('Loading progress dashboard...').start();
|
|
539
|
+
try {
|
|
540
|
+
const service = new TaskService();
|
|
541
|
+
const stats = service.getStats();
|
|
542
|
+
const allTasks = service.list({});
|
|
543
|
+
spinner.stop();
|
|
544
|
+
console.log('');
|
|
545
|
+
console.log(chalk.bold.cyan(' Progress Dashboard'));
|
|
546
|
+
console.log(divider(60));
|
|
547
|
+
console.log('');
|
|
548
|
+
const total = stats.total;
|
|
549
|
+
const done = stats.byStatus.done;
|
|
550
|
+
const wontDo = stats.byStatus.wont_do;
|
|
551
|
+
const inProgress = stats.byStatus.in_progress;
|
|
552
|
+
const blocked = stats.byStatus.blocked;
|
|
553
|
+
const open = stats.byStatus.open;
|
|
554
|
+
const completed = done + wontDo;
|
|
555
|
+
// Big completion percentage
|
|
556
|
+
const pct = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
557
|
+
const bigNum = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
|
|
558
|
+
console.log(` ${bigNum(chalk.bold(`${pct}%`))} ${chalk.dim('complete')}`);
|
|
559
|
+
console.log('');
|
|
560
|
+
console.log(` ${progressBar(completed, total, 50, {
|
|
561
|
+
showPercent: false,
|
|
562
|
+
showCount: false,
|
|
563
|
+
filled: pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red,
|
|
564
|
+
})}`);
|
|
565
|
+
console.log('');
|
|
566
|
+
// Flow breakdown
|
|
567
|
+
console.log(sectionHeader('Task Flow'));
|
|
568
|
+
console.log('');
|
|
569
|
+
console.log(` ${chalk.white('Open')} ${progressBar(open, total, 30, { filled: chalk.white, showPercent: true, showCount: true })}`);
|
|
570
|
+
console.log(` ${chalk.cyan('In Progress')} ${progressBar(inProgress, total, 30, { filled: chalk.cyan, showPercent: true, showCount: true })}`);
|
|
571
|
+
console.log(` ${chalk.red('Blocked')} ${progressBar(blocked, total, 30, { filled: chalk.red, showPercent: true, showCount: true })}`);
|
|
572
|
+
console.log(` ${chalk.green('Done')} ${progressBar(done, total, 30, { filled: chalk.green, showPercent: true, showCount: true })}`);
|
|
573
|
+
if (wontDo > 0) {
|
|
574
|
+
console.log(` ${chalk.dim("Won't Do")} ${progressBar(wontDo, total, 30, { filled: chalk.dim, showPercent: true, showCount: true })}`);
|
|
575
|
+
}
|
|
576
|
+
console.log('');
|
|
577
|
+
// Per-priority progress
|
|
578
|
+
console.log(sectionHeader('Progress by Priority'));
|
|
579
|
+
console.log('');
|
|
580
|
+
for (let p = 0; p <= 4; p++) {
|
|
581
|
+
const priority = p;
|
|
582
|
+
const priorityTasks = allTasks.filter(t => t.priority === priority);
|
|
583
|
+
const priorityDone = priorityTasks.filter(t => t.status === 'done' || t.status === 'wont_do').length;
|
|
584
|
+
const priorityTotal = priorityTasks.length;
|
|
585
|
+
if (priorityTotal > 0) {
|
|
586
|
+
const color = p === 0 ? chalk.red : p === 1 ? chalk.yellow : p === 2 ? chalk.blue : chalk.dim;
|
|
587
|
+
const label = PRIORITY_LABELS[priority].padEnd(14);
|
|
588
|
+
console.log(` ${color(label)} ${progressBar(priorityDone, priorityTotal, 25, {
|
|
589
|
+
filled: color,
|
|
590
|
+
showPercent: true,
|
|
591
|
+
showCount: true,
|
|
592
|
+
})}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
console.log('');
|
|
596
|
+
// Per-type progress
|
|
597
|
+
const typeData = Object.entries(stats.byType)
|
|
598
|
+
.filter(([, count]) => count > 0);
|
|
599
|
+
if (typeData.length > 0) {
|
|
600
|
+
console.log(sectionHeader('Progress by Type'));
|
|
601
|
+
console.log('');
|
|
602
|
+
for (const [type, typeTotal] of typeData) {
|
|
603
|
+
const typeDone = allTasks.filter(t => t.type === type && (t.status === 'done' || t.status === 'wont_do')).length;
|
|
604
|
+
const label = `${TYPE_ICONS[type]} ${type}`.padEnd(14);
|
|
605
|
+
console.log(` ${label} ${progressBar(typeDone, typeTotal, 25, {
|
|
606
|
+
filled: chalk.magenta,
|
|
607
|
+
showPercent: true,
|
|
608
|
+
showCount: true,
|
|
609
|
+
})}`);
|
|
610
|
+
}
|
|
611
|
+
console.log('');
|
|
612
|
+
}
|
|
613
|
+
// Velocity indicator (recent completions)
|
|
614
|
+
const now = new Date();
|
|
615
|
+
const recentDone = allTasks.filter(t => {
|
|
616
|
+
if (t.status !== 'done' || !t.closedAt)
|
|
617
|
+
return false;
|
|
618
|
+
const closedDate = new Date(t.closedAt);
|
|
619
|
+
const daysDiff = (now.getTime() - closedDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
620
|
+
return daysDiff <= 7;
|
|
621
|
+
});
|
|
622
|
+
const recentCreated = allTasks.filter(t => {
|
|
623
|
+
const createdDate = new Date(t.createdAt);
|
|
624
|
+
const daysDiff = (now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
625
|
+
return daysDiff <= 7;
|
|
626
|
+
});
|
|
627
|
+
console.log(sectionHeader('Velocity (Last 7 Days)'));
|
|
628
|
+
console.log('');
|
|
629
|
+
for (const line of keyValue([
|
|
630
|
+
['Completed', `${recentDone.length} tasks`],
|
|
631
|
+
['Created', `${recentCreated.length} tasks`],
|
|
632
|
+
['Net Progress', `${recentDone.length - recentCreated.length > 0 ? '+' : ''}${recentDone.length - recentCreated.length}`],
|
|
633
|
+
]))
|
|
634
|
+
console.log(line);
|
|
635
|
+
console.log('');
|
|
636
|
+
// Summary box
|
|
637
|
+
const summaryLines = [
|
|
638
|
+
`${chalk.bold(String(total))} total tasks`,
|
|
639
|
+
`${chalk.green(String(completed))} completed ${chalk.dim(`(${pct}%)`)}`,
|
|
640
|
+
`${chalk.cyan(String(inProgress))} in progress`,
|
|
641
|
+
`${blocked > 0 ? chalk.red(String(blocked) + ' blocked') : chalk.dim('0 blocked')}`,
|
|
642
|
+
`${chalk.dim(String(open))} open / awaiting`,
|
|
643
|
+
];
|
|
644
|
+
for (const line of box('Summary', summaryLines, { borderColor: chalk.cyan })) {
|
|
645
|
+
console.log(` ${line}`);
|
|
646
|
+
}
|
|
647
|
+
console.log('');
|
|
648
|
+
}
|
|
649
|
+
catch (error) {
|
|
650
|
+
spinner.fail('Failed to load progress dashboard');
|
|
651
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
//# sourceMappingURL=dashboard.js.map
|