Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Preserve deployment run failures and integration-watch health in JSON output,
and surface them clearly in the human-readable deployments list.

## [4.1.29] - 2026-07-17

### Fixed
Expand Down
288 changes: 287 additions & 1 deletion packages/cli/src/list-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import assert from 'node:assert/strict';
import {
formatDeploymentLogEntries,
formatDeploymentsTable,
parseDeploymentAgents,
parseDeploymentListArgs,
parseDeploymentLogsArgs,
runDeploymentList,
tailLogEntriesFromNewestFiles
} from './list-command.js';

Expand Down Expand Up @@ -43,16 +45,31 @@ test('formatDeploymentsTable renders agent rows', () => {
createdAt: '2026-05-13T09:11:00.000Z',
updatedAt: '2026-07-17T08:45:00.000Z',
lastUsedAt: null,
lastRunStatus: 'succeeded',
lastError: null,
integrationWatchHealth: {
status: 'healthy',
reason: null,
lastSuccessfulDeliveryAt: '2026-07-17T08:44:00.000Z',
lastDeliveryAt: '2026-07-17T08:44:00.000Z',
lastFailedDeliveryAt: null,
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 0,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
},
scheduleIds: ['sched-1'],
deployedByUserId: 'user-1'
}
]);
assert.match(out, /name\s+status\s+deployed\s+lastUsed\s+agentId/);
assert.match(out, /name\s+deployment\s+lastRun\s+watch\s+deployed\s+lastUsed\s+agentId/);
assert.match(out, /b2f1\.\.\.e8c2/);
assert.match(out, /Weekly Digest/);
assert.doesNotMatch(out, /7133e815/);
assert.match(out, /2026-07-17 08:45 UTC/);
assert.doesNotMatch(out, /2026-05-13 09:11 UTC/);
assert.match(out, /Weekly Digest\s+active\s+succeeded\s+healthy/);
assert.doesNotMatch(out, /Errors:/);
});

test('formatDeploymentsTable falls back to createdAt when updatedAt is absent', () => {
Expand All @@ -72,6 +89,275 @@ test('formatDeploymentsTable falls back to createdAt when updatedAt is absent',
assert.match(out, /2026-05-13 09:11 UTC/);
});

test('parseDeploymentAgents preserves run failures and integration watch health', () => {
const e2bigError =
'/home/daytona/.daytona/sessions/tick-1/cmd.sh:\r\n\u001b[31mline 3: /usr/bin/timeout: Argument list too long\u001b[0m';
const agents = parseDeploymentAgents({
agents: [
{
agentId: 'agent-e2big',
personaId: 'hoopsheet-maintainability',
deployedName: 'hoopsheet-maintainability',
status: 'active',
createdAt: '2026-07-17T12:55:00.000Z',
lastUsedAt: null,
lastRunStatus: 'failed',
lastError: e2bigError,
scheduleIds: [],
deployedByUserId: 'user-1',
integrationWatchHealth: {
status: 'unhealthy',
reason: 'delivery_failures',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: '2026-07-17T12:56:18.851Z',
lastFailedDeliveryAt: '2026-07-17T13:15:18.163Z',
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 1,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
}
},
{
agentId: 'agent-xai',
personaId: 'x-reply-radar',
deployedName: 'x-reply-radar',
status: 'active',
createdAt: '2026-07-17T07:54:00.000Z',
lastUsedAt: null,
lastRunStatus: 'failed',
lastError: 'Your xAI (Grok) credentials have expired and could not be refreshed automatically.',
scheduleIds: [],
deployedByUserId: 'user-2',
integrationWatchHealth: {
status: 'unknown',
reason: 'awaiting_first_successful_delivery',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: null,
lastFailedDeliveryAt: null,
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 0,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
}
}
]
});

assert.equal(agents[0]?.lastRunStatus, 'failed');
assert.equal(agents[0]?.lastError, e2bigError);
assert.deepEqual(agents[0]?.integrationWatchHealth, {
status: 'unhealthy',
reason: 'delivery_failures',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: '2026-07-17T12:56:18.851Z',
lastFailedDeliveryAt: '2026-07-17T13:15:18.163Z',
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 1,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
});
assert.equal(agents[1]?.lastRunStatus, 'failed');
assert.equal(
agents[1]?.lastError,
'Your xAI (Grok) credentials have expired and could not be refreshed automatically.'
);
assert.deepEqual(agents[1]?.integrationWatchHealth, {
status: 'unknown',
reason: 'awaiting_first_successful_delivery',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: null,
lastFailedDeliveryAt: null,
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 0,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
});
});

test('runDeploymentList --json preserves raw multiline and control-bearing failure fields', async () => {
const rawError =
'/home/daytona/.daytona/sessions/tick-1/cmd.sh:\r\n\u001b[31mline 3: /usr/bin/timeout: Argument list too long\u001b[0m';
const originalFetch = globalThis.fetch;
const originalExit = process.exit;
const originalStdout = process.stdout.write;
const originalStderr = process.stderr.write;
const originalToken = process.env.WORKFORCE_WORKSPACE_TOKEN;
let stdout = '';
let stderr = '';
const exits: number[] = [];

process.env.WORKFORCE_WORKSPACE_TOKEN = 'test-token';
globalThis.fetch = (async () =>
new Response(
JSON.stringify({
agents: [
{
agentId: 'agent-e2big',
personaId: 'hoopsheet-maintainability',
deployedName: 'hoopsheet-maintainability',
status: 'active',
createdAt: '2026-07-17T12:55:00.000Z',
lastUsedAt: null,
lastRunStatus: 'failed',
lastError: rawError,
scheduleIds: [],
deployedByUserId: 'user-1',
integrationWatchHealth: {
status: 'unhealthy',
reason: 'delivery_failures',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: '2026-07-17T12:56:18.851Z',
lastFailedDeliveryAt: '2026-07-17T13:15:18.163Z',
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 1,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
}
}
]
}),
{ status: 200, headers: { 'content-type': 'application/json' } }
)) as typeof globalThis.fetch;
process.exit = ((code?: number) => {
exits.push(code ?? 0);
return undefined as never;
}) as typeof process.exit;
process.stdout.write = ((chunk: string | Uint8Array) => {
stdout += typeof chunk === 'string' ? chunk : chunk.toString();
return true;
}) as typeof process.stdout.write;
process.stderr.write = ((chunk: string | Uint8Array) => {
stderr += typeof chunk === 'string' ? chunk : chunk.toString();
return true;
}) as typeof process.stderr.write;

try {
await runDeploymentList([
'--workspace',
'ws-test',
'--cloud-url',
'https://cloud.example.test',
'--json',
'--no-prompt'
]);

const output = JSON.parse(stdout) as { agents: Array<Record<string, unknown>> };
assert.deepEqual(exits, [0]);
assert.equal(stderr, '');
assert.equal(output.agents[0]?.lastRunStatus, 'failed');
assert.equal(output.agents[0]?.lastError, rawError);
assert.deepEqual(output.agents[0]?.integrationWatchHealth, {
status: 'unhealthy',
reason: 'delivery_failures',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: '2026-07-17T12:56:18.851Z',
lastFailedDeliveryAt: '2026-07-17T13:15:18.163Z',
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 1,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
});
} finally {
globalThis.fetch = originalFetch;
process.exit = originalExit;
process.stdout.write = originalStdout;
process.stderr.write = originalStderr;
if (originalToken === undefined) delete process.env.WORKFORCE_WORKSPACE_TOKEN;
else process.env.WORKFORCE_WORKSPACE_TOKEN = originalToken;
}
});

test('formatDeploymentsTable makes distinct active deployment failures unmistakable', () => {
const e2bigMultilineError =
'/home/daytona/.daytona/sessions/tick-1/cmd.sh:\r\nline 3: /usr/bin/timeout: Argument list too long';
const agents = parseDeploymentAgents({
agents: [
{
agentId: 'agent-e2big',
personaId: 'hoopsheet-maintainability',
deployedName: 'hoopsheet-maintainability',
status: 'active',
createdAt: '2026-07-17T12:55:00.000Z',
lastUsedAt: null,
lastRunStatus: 'failed',
lastError: e2bigMultilineError,
scheduleIds: [],
deployedByUserId: 'user-1',
integrationWatchHealth: {
status: 'unhealthy',
reason: 'delivery_failures',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: '2026-07-17T12:56:18.851Z',
lastFailedDeliveryAt: '2026-07-17T13:15:18.163Z',
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 1,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
}
},
{
agentId: 'agent-xai',
personaId: 'x-reply-radar',
deployedName: 'x-reply-radar',
status: 'active',
createdAt: '2026-07-17T07:54:00.000Z',
lastUsedAt: null,
lastRunStatus: 'failed',
lastError: 'Your xAI (Grok) credentials have expired and could not be refreshed automatically.',
scheduleIds: [],
deployedByUserId: 'user-2',
integrationWatchHealth: {
status: 'unknown',
reason: 'awaiting_first_successful_delivery',
lastSuccessfulDeliveryAt: null,
lastDeliveryAt: null,
lastFailedDeliveryAt: null,
pendingDeliveryCount: 0,
recentFailedDeliveryCount: 0,
recentWorkspaceDispatchFailureCount: 0,
latestWorkspaceDispatchFailureAt: null
}
}
]
});

const out = formatDeploymentsTable(agents);
assert.match(out, /name\s+deployment\s+lastRun\s+watch\s+deployed\s+lastUsed\s+agentId/);
assert.match(out, /hoopsheet-maintainability\s+active\s+failed\s+unhealthy/);
assert.match(out, /x-reply-radar\s+active\s+failed\s+unknown/);
assert.match(out, /Errors:/);
assert.match(
out,
/hoopsheet-maintainability: \/home\/daytona\/.* line 3: \/usr\/bin\/timeout: Argument list too long/
);
assert.match(out, /x-reply-radar: Your xAI \(Grok\) credentials have expired/);
assert.doesNotMatch(out, /\r/);
});

test('formatDeploymentsTable collapses and bounds multiline error details', () => {
const longMultilineError = `first line\r\nsecond line ${'x'.repeat(300)}`;
const out = formatDeploymentsTable([
{
agentId: 'agent-long-error',
personaId: 'long-error',
personaSlug: 'long-error',
deployedName: 'long-error',
status: 'active',
createdAt: '2026-07-17T12:55:00.000Z',
lastUsedAt: null,
lastRunStatus: 'failed',
lastError: longMultilineError,
scheduleIds: [],
deployedByUserId: 'user-1'
}
]);

assert.match(out, /long-error: first line second line x+/);
assert.doesNotMatch(out, /\r/);
assert.doesNotMatch(out, new RegExp(`x{300}`));
assert.match(out, /\.\.\./);
});

test('parseDeploymentLogsArgs accepts selector and log flags', () => {
assert.deepEqual(
parseDeploymentLogsArgs([
Expand Down
Loading
Loading