fix(delegation): handle undefined totalCost in timeout error formatting

Add defensive checks in result-formatter, headless-executor, and session-manager
to prevent TypeError when delegated sessions timeout without emitting a result.
Includes 4 comprehensive unit tests for undefined/null totalCost scenarios.

Fixes formatting error that prevented timeout messages from displaying.
This commit is contained in:
kaitranntt
2025-11-16 06:40:31 -05:00
parent 4df5a7d357
commit fb3deeff38
4 changed files with 87 additions and 3 deletions
+2 -1
View File
@@ -88,7 +88,8 @@ class HeadlessExecutor {
if (lastSession) {
args.push('--resume', lastSession.sessionId);
if (process.env.CCS_DEBUG) {
console.error(`[i] Resuming session: ${lastSession.sessionId} (${lastSession.turns} turns, $${lastSession.totalCost.toFixed(4)})`);
const cost = lastSession.totalCost !== undefined && lastSession.totalCost !== null ? lastSession.totalCost.toFixed(4) : '0.0000';
console.error(`[i] Resuming session: ${lastSession.sessionId} (${lastSession.turns} turns, $${cost})`);
}
} else if (sessionId) {
args.push('--resume', sessionId);
+3 -1
View File
@@ -437,7 +437,9 @@ class ResultFormatter {
// Abbreviate session ID (Git-style first 8 chars)
const shortId = sessionId && sessionId.length > 8 ? sessionId.substring(0, 8) : sessionId;
output += `[i] Session persisted with ID: ${shortId}\n`;
output += `[i] Cost: $${totalCost.toFixed(4)}\n`;
if (totalCost !== undefined && totalCost !== null) {
output += `[i] Cost: $${totalCost.toFixed(4)}\n`;
}
return output;
}
+2 -1
View File
@@ -60,7 +60,8 @@ class SessionManager {
this._saveSessions(sessions);
if (process.env.CCS_DEBUG) {
console.error(`[i] Updated session: ${sessionId}, total: $${sessions[key].totalCost.toFixed(4)}, turns: ${sessions[key].turns}`);
const cost = sessions[key].totalCost !== undefined && sessions[key].totalCost !== null ? sessions[key].totalCost.toFixed(4) : '0.0000';
console.error(`[i] Updated session: ${sessionId}, total: $${cost}, turns: ${sessions[key].turns}`);
}
}
}
@@ -284,5 +284,85 @@ runner.test('Should show file counts in info box', () => {
assertIncludes(formatted, 'Files Modified: 1', 'Should show modified count');
});
// Test 15: Handle undefined totalCost in timeout error
runner.test('Should handle undefined totalCost in timeout error', () => {
const result = {
profile: 'glm',
cwd: '/test',
duration: 120000,
sessionId: 'test-session-123',
totalCost: undefined,
numTurns: 5,
timedOut: true
};
// Should not throw TypeError
const formatted = ResultFormatter.format(result);
assertIncludes(formatted, 'Execution timed out', 'Should show timeout message');
assertIncludes(formatted, 'test-ses', 'Should show abbreviated session ID');
// Cost line should be omitted when undefined
assert(!formatted.includes('Cost: $'), 'Should not show cost when undefined');
});
// Test 16: Handle null totalCost in timeout error
runner.test('Should handle null totalCost in timeout error', () => {
const result = {
profile: 'kimi',
cwd: '/test',
duration: 60000,
sessionId: 'test-session-456',
totalCost: null,
numTurns: 3,
timedOut: true
};
// Should not throw TypeError
const formatted = ResultFormatter.format(result);
assertIncludes(formatted, 'Execution timed out', 'Should show timeout message');
assert(!formatted.includes('Cost: $'), 'Should not show cost when null');
});
// Test 17: Show totalCost when defined in timeout error
runner.test('Should show totalCost when defined in timeout error', () => {
const result = {
profile: 'glm',
cwd: '/test',
duration: 90000,
sessionId: 'test-session-789',
totalCost: 0.1234,
numTurns: 4,
timedOut: true
};
const formatted = ResultFormatter.format(result);
assertIncludes(formatted, 'Cost: $0.1234', 'Should show formatted cost');
});
// Test 18: Handle undefined totalCost in normal result
runner.test('Should handle undefined totalCost in normal result', () => {
const result = {
profile: 'kimi',
cwd: '/test',
exitCode: 0,
stdout: 'Task completed',
stderr: '',
duration: 5000,
success: true,
sessionId: 'session-abc',
totalCost: undefined,
numTurns: 2
};
// Should not throw TypeError
const formatted = ResultFormatter.format(result);
assertIncludes(formatted, '[OK]', 'Should show success');
// Cost line should be omitted in info box when undefined
assert(!formatted.includes('Cost: $'), 'Should not show cost when undefined');
});
// Run all tests
runner.run();