tlc-claude-code 1.4.9 → 1.5.2
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/CLAUDE.md +23 -0
- package/CODING-STANDARDS.md +408 -0
- package/bin/install.js +2 -0
- package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
- package/dashboard/dist/components/QualityGatePane.js +31 -0
- package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
- package/dashboard/dist/components/QualityGatePane.test.js +147 -0
- package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
- package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
- package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
- package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
- package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
- package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
- package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
- package/dashboard/dist/components/orchestration/AgentList.js +47 -0
- package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
- package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
- package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
- package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
- package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
- package/dashboard/dist/components/orchestration/index.d.ts +8 -0
- package/dashboard/dist/components/orchestration/index.js +8 -0
- package/package.json +1 -1
- package/server/lib/access-control.js +352 -0
- package/server/lib/access-control.test.js +322 -0
- package/server/lib/agents-cancel-command.js +139 -0
- package/server/lib/agents-cancel-command.test.js +180 -0
- package/server/lib/agents-get-command.js +159 -0
- package/server/lib/agents-get-command.test.js +167 -0
- package/server/lib/agents-list-command.js +150 -0
- package/server/lib/agents-list-command.test.js +149 -0
- package/server/lib/agents-logs-command.js +126 -0
- package/server/lib/agents-logs-command.test.js +198 -0
- package/server/lib/agents-retry-command.js +117 -0
- package/server/lib/agents-retry-command.test.js +192 -0
- package/server/lib/budget-limits.js +222 -0
- package/server/lib/budget-limits.test.js +214 -0
- package/server/lib/code-generator.js +291 -0
- package/server/lib/code-generator.test.js +307 -0
- package/server/lib/cost-command.js +290 -0
- package/server/lib/cost-command.test.js +202 -0
- package/server/lib/cost-optimizer.js +404 -0
- package/server/lib/cost-optimizer.test.js +232 -0
- package/server/lib/cost-projections.js +302 -0
- package/server/lib/cost-projections.test.js +217 -0
- package/server/lib/cost-reports.js +277 -0
- package/server/lib/cost-reports.test.js +254 -0
- package/server/lib/cost-tracker.js +216 -0
- package/server/lib/cost-tracker.test.js +302 -0
- package/server/lib/crypto-patterns.js +433 -0
- package/server/lib/crypto-patterns.test.js +346 -0
- package/server/lib/design-command.js +385 -0
- package/server/lib/design-command.test.js +249 -0
- package/server/lib/design-parser.js +237 -0
- package/server/lib/design-parser.test.js +290 -0
- package/server/lib/gemini-vision.js +377 -0
- package/server/lib/gemini-vision.test.js +282 -0
- package/server/lib/input-validator.js +360 -0
- package/server/lib/input-validator.test.js +295 -0
- package/server/lib/litellm-client.js +232 -0
- package/server/lib/litellm-client.test.js +267 -0
- package/server/lib/litellm-command.js +291 -0
- package/server/lib/litellm-command.test.js +260 -0
- package/server/lib/litellm-config.js +273 -0
- package/server/lib/litellm-config.test.js +212 -0
- package/server/lib/model-pricing.js +189 -0
- package/server/lib/model-pricing.test.js +178 -0
- package/server/lib/models-command.js +223 -0
- package/server/lib/models-command.test.js +193 -0
- package/server/lib/optimize-command.js +197 -0
- package/server/lib/optimize-command.test.js +193 -0
- package/server/lib/orchestration-integration.js +206 -0
- package/server/lib/orchestration-integration.test.js +235 -0
- package/server/lib/output-encoder.js +308 -0
- package/server/lib/output-encoder.test.js +312 -0
- package/server/lib/quality-evaluator.js +396 -0
- package/server/lib/quality-evaluator.test.js +337 -0
- package/server/lib/quality-gate-command.js +340 -0
- package/server/lib/quality-gate-command.test.js +321 -0
- package/server/lib/quality-gate-scorer.js +378 -0
- package/server/lib/quality-gate-scorer.test.js +376 -0
- package/server/lib/quality-history.js +265 -0
- package/server/lib/quality-history.test.js +359 -0
- package/server/lib/quality-presets.js +288 -0
- package/server/lib/quality-presets.test.js +269 -0
- package/server/lib/quality-retry.js +323 -0
- package/server/lib/quality-retry.test.js +325 -0
- package/server/lib/quality-thresholds.js +255 -0
- package/server/lib/quality-thresholds.test.js +237 -0
- package/server/lib/secure-auth.js +333 -0
- package/server/lib/secure-auth.test.js +288 -0
- package/server/lib/secure-code-command.js +540 -0
- package/server/lib/secure-code-command.test.js +309 -0
- package/server/lib/secure-errors.js +521 -0
- package/server/lib/secure-errors.test.js +298 -0
- package/server/lib/vision-command.js +372 -0
- package/server/lib/vision-command.test.js +255 -0
- package/server/lib/visual-command.js +350 -0
- package/server/lib/visual-command.test.js +256 -0
- package/server/lib/visual-testing.js +315 -0
- package/server/lib/visual-testing.test.js +357 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents Cancel Command
|
|
3
|
+
* Cancel running agents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if confirmation is required
|
|
8
|
+
* @param {object} options - Command options
|
|
9
|
+
* @returns {boolean} True if confirmation needed
|
|
10
|
+
*/
|
|
11
|
+
function requiresConfirmation(options) {
|
|
12
|
+
return !options.force;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cancel a single agent
|
|
17
|
+
* @param {object} agent - Agent to cancel
|
|
18
|
+
* @param {Function} updateAgent - Function to update agent
|
|
19
|
+
* @returns {object} Cancelled agent
|
|
20
|
+
*/
|
|
21
|
+
function cancelAgent(agent, updateAgent) {
|
|
22
|
+
return updateAgent(agent.id, {
|
|
23
|
+
status: 'cancelled',
|
|
24
|
+
endTime: new Date(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Cancel all running agents
|
|
30
|
+
* @param {Array} agents - All agents
|
|
31
|
+
* @param {Function} updateAgent - Function to update agent
|
|
32
|
+
* @returns {number} Count of cancelled agents
|
|
33
|
+
*/
|
|
34
|
+
function cancelAllRunning(agents, updateAgent) {
|
|
35
|
+
const running = agents.filter(a => a.status === 'running' || a.status === 'queued');
|
|
36
|
+
for (const agent of running) {
|
|
37
|
+
cancelAgent(agent, updateAgent);
|
|
38
|
+
}
|
|
39
|
+
return running.length;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if agent can be cancelled
|
|
44
|
+
* @param {object} agent - Agent to check
|
|
45
|
+
* @returns {object} Result with canCancel and reason
|
|
46
|
+
*/
|
|
47
|
+
function canCancel(agent) {
|
|
48
|
+
if (!agent) {
|
|
49
|
+
return { canCancel: false, reason: 'Agent not found' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (agent.status === 'cancelled') {
|
|
53
|
+
return { canCancel: false, reason: 'Agent is already cancelled' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (agent.status === 'completed') {
|
|
57
|
+
return { canCancel: false, reason: 'Agent is already completed' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (agent.status === 'failed') {
|
|
61
|
+
return { canCancel: false, reason: 'Agent has already failed' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { canCancel: true };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute agents cancel command
|
|
69
|
+
* @param {object} context - Execution context
|
|
70
|
+
* @returns {Promise<object>} Command result
|
|
71
|
+
*/
|
|
72
|
+
async function execute(context) {
|
|
73
|
+
const { registry, agentId, options = {}, onCleanup } = context;
|
|
74
|
+
|
|
75
|
+
// Handle --all flag
|
|
76
|
+
if (options.all) {
|
|
77
|
+
if (requiresConfirmation(options)) {
|
|
78
|
+
const running = registry.listAgents().filter(a =>
|
|
79
|
+
a.status === 'running' || a.status === 'queued'
|
|
80
|
+
);
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
needsConfirmation: true,
|
|
84
|
+
message: `Cancel ${running.length} running agents?`,
|
|
85
|
+
count: running.length,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const agents = registry.listAgents();
|
|
90
|
+
const count = cancelAllRunning(agents, registry.updateAgent.bind(registry));
|
|
91
|
+
|
|
92
|
+
if (onCleanup) onCleanup();
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
cancelled: count,
|
|
97
|
+
message: `Cancelled ${count} agents`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Single agent cancel
|
|
102
|
+
const agent = registry.getAgent(agentId);
|
|
103
|
+
const { canCancel: allowed, reason } = canCancel(agent);
|
|
104
|
+
|
|
105
|
+
if (!allowed) {
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: reason,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (requiresConfirmation(options)) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
needsConfirmation: true,
|
|
116
|
+
message: `Cancel agent ${agentId}?`,
|
|
117
|
+
agent,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cancelAgent(agent, registry.updateAgent.bind(registry));
|
|
122
|
+
|
|
123
|
+
if (onCleanup) onCleanup();
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
cancelled: 1,
|
|
128
|
+
message: `Cancelled agent ${agentId}`,
|
|
129
|
+
agent,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
execute,
|
|
135
|
+
cancelAgent,
|
|
136
|
+
cancelAllRunning,
|
|
137
|
+
requiresConfirmation,
|
|
138
|
+
canCancel,
|
|
139
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const { describe, it, beforeEach, mock } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const {
|
|
4
|
+
execute,
|
|
5
|
+
cancelAgent,
|
|
6
|
+
cancelAllRunning,
|
|
7
|
+
requiresConfirmation,
|
|
8
|
+
} = require('./agents-cancel-command.js');
|
|
9
|
+
|
|
10
|
+
describe('agents-cancel-command', () => {
|
|
11
|
+
describe('execute', () => {
|
|
12
|
+
const createMockRegistry = (agents = []) => ({
|
|
13
|
+
getAgent: (id) => agents.find(a => a.id === id),
|
|
14
|
+
listAgents: () => agents,
|
|
15
|
+
updateAgent: (id, updates) => {
|
|
16
|
+
const agent = agents.find(a => a.id === id);
|
|
17
|
+
if (agent) Object.assign(agent, updates);
|
|
18
|
+
return agent;
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('cancels agent', async () => {
|
|
23
|
+
const agents = [
|
|
24
|
+
{ id: 'agent-1', status: 'running' },
|
|
25
|
+
];
|
|
26
|
+
const result = await execute({
|
|
27
|
+
registry: createMockRegistry(agents),
|
|
28
|
+
agentId: 'agent-1',
|
|
29
|
+
options: { force: true },
|
|
30
|
+
});
|
|
31
|
+
assert.ok(result.success);
|
|
32
|
+
assert.strictEqual(agents[0].status, 'cancelled');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('shows confirmation prompt', async () => {
|
|
36
|
+
const agents = [{ id: 'agent-1', status: 'running' }];
|
|
37
|
+
const result = await execute({
|
|
38
|
+
registry: createMockRegistry(agents),
|
|
39
|
+
agentId: 'agent-1',
|
|
40
|
+
options: {},
|
|
41
|
+
});
|
|
42
|
+
assert.ok(result.needsConfirmation);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('with --force skips prompt', async () => {
|
|
46
|
+
const agents = [{ id: 'agent-1', status: 'running' }];
|
|
47
|
+
const result = await execute({
|
|
48
|
+
registry: createMockRegistry(agents),
|
|
49
|
+
agentId: 'agent-1',
|
|
50
|
+
options: { force: true },
|
|
51
|
+
});
|
|
52
|
+
assert.ok(result.success);
|
|
53
|
+
assert.ok(!result.needsConfirmation);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('--all cancels all running', async () => {
|
|
57
|
+
const agents = [
|
|
58
|
+
{ id: 'agent-1', status: 'running' },
|
|
59
|
+
{ id: 'agent-2', status: 'running' },
|
|
60
|
+
{ id: 'agent-3', status: 'completed' },
|
|
61
|
+
];
|
|
62
|
+
const result = await execute({
|
|
63
|
+
registry: createMockRegistry(agents),
|
|
64
|
+
options: { all: true, force: true },
|
|
65
|
+
});
|
|
66
|
+
assert.ok(result.success);
|
|
67
|
+
assert.strictEqual(result.cancelled, 2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('handles unknown agent ID', async () => {
|
|
71
|
+
const result = await execute({
|
|
72
|
+
registry: createMockRegistry([]),
|
|
73
|
+
agentId: 'unknown',
|
|
74
|
+
options: { force: true },
|
|
75
|
+
});
|
|
76
|
+
assert.strictEqual(result.success, false);
|
|
77
|
+
assert.ok(result.error.includes('not found'));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('handles already cancelled', async () => {
|
|
81
|
+
const agents = [{ id: 'agent-1', status: 'cancelled' }];
|
|
82
|
+
const result = await execute({
|
|
83
|
+
registry: createMockRegistry(agents),
|
|
84
|
+
agentId: 'agent-1',
|
|
85
|
+
options: { force: true },
|
|
86
|
+
});
|
|
87
|
+
assert.strictEqual(result.success, false);
|
|
88
|
+
assert.ok(result.error.includes('already'));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('handles already completed', async () => {
|
|
92
|
+
const agents = [{ id: 'agent-1', status: 'completed' }];
|
|
93
|
+
const result = await execute({
|
|
94
|
+
registry: createMockRegistry(agents),
|
|
95
|
+
agentId: 'agent-1',
|
|
96
|
+
options: { force: true },
|
|
97
|
+
});
|
|
98
|
+
assert.strictEqual(result.success, false);
|
|
99
|
+
assert.ok(result.error.includes('already') || result.error.includes('completed'));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('shows result message', async () => {
|
|
103
|
+
const agents = [{ id: 'agent-1', status: 'running' }];
|
|
104
|
+
const result = await execute({
|
|
105
|
+
registry: createMockRegistry(agents),
|
|
106
|
+
agentId: 'agent-1',
|
|
107
|
+
options: { force: true },
|
|
108
|
+
});
|
|
109
|
+
assert.ok(result.message);
|
|
110
|
+
assert.ok(result.message.includes('cancelled') || result.message.includes('Cancelled'));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('updates agent state', async () => {
|
|
114
|
+
const agents = [{ id: 'agent-1', status: 'running' }];
|
|
115
|
+
await execute({
|
|
116
|
+
registry: createMockRegistry(agents),
|
|
117
|
+
agentId: 'agent-1',
|
|
118
|
+
options: { force: true },
|
|
119
|
+
});
|
|
120
|
+
assert.strictEqual(agents[0].status, 'cancelled');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('triggers cleanup', async () => {
|
|
124
|
+
let cleanupCalled = false;
|
|
125
|
+
const agents = [{ id: 'agent-1', status: 'running' }];
|
|
126
|
+
await execute({
|
|
127
|
+
registry: createMockRegistry(agents),
|
|
128
|
+
agentId: 'agent-1',
|
|
129
|
+
options: { force: true },
|
|
130
|
+
onCleanup: () => { cleanupCalled = true; },
|
|
131
|
+
});
|
|
132
|
+
assert.ok(cleanupCalled);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('cancelAgent', () => {
|
|
137
|
+
it('sets status to cancelled', () => {
|
|
138
|
+
const agent = { id: 'agent-1', status: 'running' };
|
|
139
|
+
const updateAgent = (id, updates) => Object.assign(agent, updates);
|
|
140
|
+
cancelAgent(agent, updateAgent);
|
|
141
|
+
assert.strictEqual(agent.status, 'cancelled');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('sets end time', () => {
|
|
145
|
+
const agent = { id: 'agent-1', status: 'running' };
|
|
146
|
+
const updateAgent = (id, updates) => Object.assign(agent, updates);
|
|
147
|
+
cancelAgent(agent, updateAgent);
|
|
148
|
+
assert.ok(agent.endTime);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('cancelAllRunning', () => {
|
|
153
|
+
it('cancels only running agents', () => {
|
|
154
|
+
const agents = [
|
|
155
|
+
{ id: '1', status: 'running' },
|
|
156
|
+
{ id: '2', status: 'completed' },
|
|
157
|
+
{ id: '3', status: 'running' },
|
|
158
|
+
];
|
|
159
|
+
const updateAgent = (id, updates) => {
|
|
160
|
+
const agent = agents.find(a => a.id === id);
|
|
161
|
+
Object.assign(agent, updates);
|
|
162
|
+
};
|
|
163
|
+
const count = cancelAllRunning(agents, updateAgent);
|
|
164
|
+
assert.strictEqual(count, 2);
|
|
165
|
+
assert.strictEqual(agents[0].status, 'cancelled');
|
|
166
|
+
assert.strictEqual(agents[1].status, 'completed');
|
|
167
|
+
assert.strictEqual(agents[2].status, 'cancelled');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('requiresConfirmation', () => {
|
|
172
|
+
it('returns true without force', () => {
|
|
173
|
+
assert.strictEqual(requiresConfirmation({}), true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('returns false with force', () => {
|
|
177
|
+
assert.strictEqual(requiresConfirmation({ force: true }), false);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents Get Command
|
|
3
|
+
* Show detailed agent information
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format timestamp
|
|
8
|
+
* @param {Date} date - Date to format
|
|
9
|
+
* @returns {string} Formatted timestamp
|
|
10
|
+
*/
|
|
11
|
+
function formatTimestamp(date) {
|
|
12
|
+
if (!date) return '-';
|
|
13
|
+
return new Date(date).toLocaleString();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format state history
|
|
18
|
+
* @param {Array} history - State history entries
|
|
19
|
+
* @returns {string} Formatted history
|
|
20
|
+
*/
|
|
21
|
+
function formatStateHistory(history) {
|
|
22
|
+
if (!history || history.length === 0) {
|
|
23
|
+
return 'No state history';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return history.map(entry =>
|
|
27
|
+
` ${formatTimestamp(entry.timestamp)} → ${entry.state}`
|
|
28
|
+
).join('\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Format token breakdown
|
|
33
|
+
* @param {object} tokens - Token counts
|
|
34
|
+
* @returns {string} Formatted breakdown
|
|
35
|
+
*/
|
|
36
|
+
function formatTokenBreakdown(tokens) {
|
|
37
|
+
if (!tokens) return 'No token data';
|
|
38
|
+
|
|
39
|
+
const input = tokens.input || 0;
|
|
40
|
+
const output = tokens.output || 0;
|
|
41
|
+
const total = input + output;
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
` Input: ${input.toLocaleString()}`,
|
|
45
|
+
` Output: ${output.toLocaleString()}`,
|
|
46
|
+
` Total: ${total.toLocaleString()}`,
|
|
47
|
+
].join('\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format cost breakdown
|
|
52
|
+
* @param {number} cost - Total cost
|
|
53
|
+
* @returns {string} Formatted cost
|
|
54
|
+
*/
|
|
55
|
+
function formatCostBreakdown(cost) {
|
|
56
|
+
if (cost === undefined || cost === null) return 'No cost data';
|
|
57
|
+
return ` Total: $${cost.toFixed(4)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Format quality scores
|
|
62
|
+
* @param {object} quality - Quality data
|
|
63
|
+
* @returns {string} Formatted quality
|
|
64
|
+
*/
|
|
65
|
+
function formatQualityScores(quality) {
|
|
66
|
+
if (!quality) return 'No quality data';
|
|
67
|
+
|
|
68
|
+
const lines = [` Score: ${quality.score}%`];
|
|
69
|
+
|
|
70
|
+
if (quality.dimensions) {
|
|
71
|
+
for (const [dim, score] of Object.entries(quality.dimensions)) {
|
|
72
|
+
lines.push(` ${dim}: ${score}%`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Format full agent details
|
|
81
|
+
* @param {object} agent - Agent to format
|
|
82
|
+
* @returns {string} Formatted details
|
|
83
|
+
*/
|
|
84
|
+
function formatDetails(agent) {
|
|
85
|
+
const sections = [];
|
|
86
|
+
|
|
87
|
+
// Header
|
|
88
|
+
sections.push(`Agent: ${agent.id}`);
|
|
89
|
+
sections.push('='.repeat(50));
|
|
90
|
+
|
|
91
|
+
// Metadata
|
|
92
|
+
sections.push('\n📋 Metadata');
|
|
93
|
+
sections.push(` ID: ${agent.id}`);
|
|
94
|
+
sections.push(` Name: ${agent.name || '-'}`);
|
|
95
|
+
sections.push(` Model: ${agent.model}`);
|
|
96
|
+
sections.push(` Status: ${agent.status}`);
|
|
97
|
+
sections.push(` Start: ${formatTimestamp(agent.startTime)}`);
|
|
98
|
+
if (agent.endTime) {
|
|
99
|
+
sections.push(` End: ${formatTimestamp(agent.endTime)}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// State History
|
|
103
|
+
sections.push('\n📜 State History');
|
|
104
|
+
sections.push(formatStateHistory(agent.stateHistory));
|
|
105
|
+
|
|
106
|
+
// Tokens
|
|
107
|
+
sections.push('\n🔢 Tokens');
|
|
108
|
+
sections.push(formatTokenBreakdown(agent.tokens));
|
|
109
|
+
|
|
110
|
+
// Cost
|
|
111
|
+
sections.push('\n💰 Cost');
|
|
112
|
+
sections.push(formatCostBreakdown(agent.cost));
|
|
113
|
+
|
|
114
|
+
// Quality
|
|
115
|
+
sections.push('\n📊 Quality');
|
|
116
|
+
sections.push(formatQualityScores(agent.quality));
|
|
117
|
+
|
|
118
|
+
return sections.join('\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Execute agents get command
|
|
123
|
+
* @param {object} context - Execution context
|
|
124
|
+
* @returns {Promise<object>} Command result
|
|
125
|
+
*/
|
|
126
|
+
async function execute(context) {
|
|
127
|
+
const { registry, agentId, options = {} } = context;
|
|
128
|
+
|
|
129
|
+
const agent = registry.getAgent(agentId);
|
|
130
|
+
|
|
131
|
+
if (!agent) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: `Agent not found: ${agentId}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let output;
|
|
139
|
+
if (options.json) {
|
|
140
|
+
output = JSON.stringify(agent, null, 2);
|
|
141
|
+
} else {
|
|
142
|
+
output = formatDetails(agent);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
agent,
|
|
148
|
+
output,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
execute,
|
|
154
|
+
formatDetails,
|
|
155
|
+
formatStateHistory,
|
|
156
|
+
formatTokenBreakdown,
|
|
157
|
+
formatCostBreakdown,
|
|
158
|
+
formatQualityScores,
|
|
159
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const { describe, it, beforeEach, mock } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const {
|
|
4
|
+
execute,
|
|
5
|
+
formatDetails,
|
|
6
|
+
formatStateHistory,
|
|
7
|
+
formatTokenBreakdown,
|
|
8
|
+
formatCostBreakdown,
|
|
9
|
+
formatQualityScores,
|
|
10
|
+
} = require('./agents-get-command.js');
|
|
11
|
+
|
|
12
|
+
describe('agents-get-command', () => {
|
|
13
|
+
const mockAgent = {
|
|
14
|
+
id: 'agent-123',
|
|
15
|
+
name: 'Code Builder',
|
|
16
|
+
model: 'claude-3-opus',
|
|
17
|
+
status: 'completed',
|
|
18
|
+
startTime: new Date(Date.now() - 120000),
|
|
19
|
+
endTime: new Date(),
|
|
20
|
+
tokens: { input: 5000, output: 2500 },
|
|
21
|
+
cost: 0.25,
|
|
22
|
+
quality: { score: 85, dimensions: { correctness: 90, completeness: 80 } },
|
|
23
|
+
stateHistory: [
|
|
24
|
+
{ state: 'queued', timestamp: new Date(Date.now() - 180000) },
|
|
25
|
+
{ state: 'running', timestamp: new Date(Date.now() - 120000) },
|
|
26
|
+
{ state: 'completed', timestamp: new Date() },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('execute', () => {
|
|
31
|
+
it('shows agent details', async () => {
|
|
32
|
+
const result = await execute({
|
|
33
|
+
registry: { getAgent: () => mockAgent },
|
|
34
|
+
agentId: 'agent-123',
|
|
35
|
+
});
|
|
36
|
+
assert.ok(result.output);
|
|
37
|
+
assert.ok(result.output.includes('agent-123'));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('shows metadata section', async () => {
|
|
41
|
+
const result = await execute({
|
|
42
|
+
registry: { getAgent: () => mockAgent },
|
|
43
|
+
agentId: 'agent-123',
|
|
44
|
+
});
|
|
45
|
+
assert.ok(result.output.includes('claude-3-opus'));
|
|
46
|
+
assert.ok(result.output.includes('completed'));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('shows state history', async () => {
|
|
50
|
+
const result = await execute({
|
|
51
|
+
registry: { getAgent: () => mockAgent },
|
|
52
|
+
agentId: 'agent-123',
|
|
53
|
+
});
|
|
54
|
+
assert.ok(result.output.includes('queued') || result.output.includes('running'));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('shows token breakdown', async () => {
|
|
58
|
+
const result = await execute({
|
|
59
|
+
registry: { getAgent: () => mockAgent },
|
|
60
|
+
agentId: 'agent-123',
|
|
61
|
+
});
|
|
62
|
+
assert.ok(result.output.includes('5000') || result.output.includes('5,000') || result.output.includes('Input'));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('shows cost breakdown', async () => {
|
|
66
|
+
const result = await execute({
|
|
67
|
+
registry: { getAgent: () => mockAgent },
|
|
68
|
+
agentId: 'agent-123',
|
|
69
|
+
});
|
|
70
|
+
assert.ok(result.output.includes('0.25') || result.output.includes('$'));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('shows quality scores', async () => {
|
|
74
|
+
const result = await execute({
|
|
75
|
+
registry: { getAgent: () => mockAgent },
|
|
76
|
+
agentId: 'agent-123',
|
|
77
|
+
});
|
|
78
|
+
assert.ok(result.output.includes('85') || result.output.includes('Quality'));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('handles unknown agent ID', async () => {
|
|
82
|
+
const result = await execute({
|
|
83
|
+
registry: { getAgent: () => null },
|
|
84
|
+
agentId: 'unknown',
|
|
85
|
+
});
|
|
86
|
+
assert.strictEqual(result.success, false);
|
|
87
|
+
assert.ok(result.error.includes('not found') || result.error.includes('Unknown'));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('includes timestamps', async () => {
|
|
91
|
+
const result = await execute({
|
|
92
|
+
registry: { getAgent: () => mockAgent },
|
|
93
|
+
agentId: 'agent-123',
|
|
94
|
+
});
|
|
95
|
+
assert.ok(result.output.includes(':') || result.output.includes('Time'));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('JSON output option works', async () => {
|
|
99
|
+
const result = await execute({
|
|
100
|
+
registry: { getAgent: () => mockAgent },
|
|
101
|
+
agentId: 'agent-123',
|
|
102
|
+
options: { json: true },
|
|
103
|
+
});
|
|
104
|
+
const parsed = JSON.parse(result.output);
|
|
105
|
+
assert.strictEqual(parsed.id, 'agent-123');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('formatDetails', () => {
|
|
110
|
+
it('creates sections', () => {
|
|
111
|
+
const result = formatDetails(mockAgent);
|
|
112
|
+
assert.ok(result.includes('Agent:') || result.includes('ID:'));
|
|
113
|
+
assert.ok(result.includes('Model:') || result.includes('claude'));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('formatStateHistory', () => {
|
|
118
|
+
it('shows state transitions', () => {
|
|
119
|
+
const result = formatStateHistory(mockAgent.stateHistory);
|
|
120
|
+
assert.ok(result.includes('queued'));
|
|
121
|
+
assert.ok(result.includes('running'));
|
|
122
|
+
assert.ok(result.includes('completed'));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('handles empty history', () => {
|
|
126
|
+
const result = formatStateHistory([]);
|
|
127
|
+
assert.ok(result.includes('No') || result === '');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('formatTokenBreakdown', () => {
|
|
132
|
+
it('shows input and output', () => {
|
|
133
|
+
const result = formatTokenBreakdown({ input: 5000, output: 2500 });
|
|
134
|
+
assert.ok(result.includes('5000') || result.includes('5,000'));
|
|
135
|
+
assert.ok(result.includes('2500') || result.includes('2,500'));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('shows total', () => {
|
|
139
|
+
const result = formatTokenBreakdown({ input: 5000, output: 2500 });
|
|
140
|
+
assert.ok(result.includes('7500') || result.includes('7,500') || result.includes('Total'));
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('formatCostBreakdown', () => {
|
|
145
|
+
it('shows cost', () => {
|
|
146
|
+
const result = formatCostBreakdown(0.25);
|
|
147
|
+
assert.ok(result.includes('0.25') || result.includes('$'));
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('formatQualityScores', () => {
|
|
152
|
+
it('shows main score', () => {
|
|
153
|
+
const result = formatQualityScores({ score: 85, dimensions: {} });
|
|
154
|
+
assert.ok(result.includes('85'));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('shows dimensions', () => {
|
|
158
|
+
const result = formatQualityScores({ score: 85, dimensions: { correctness: 90 } });
|
|
159
|
+
assert.ok(result.includes('90') || result.includes('correctness'));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('handles no quality data', () => {
|
|
163
|
+
const result = formatQualityScores(null);
|
|
164
|
+
assert.ok(result.includes('No') || result === '');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|