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
88 changes: 88 additions & 0 deletions src/cli/commands/status/__tests__/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ vi.mock('../../../aws/agentcore-control', () => ({
getOnlineEvaluationConfig: (...args: unknown[]) => mockGetOnlineEvaluationConfig(...args),
}));

const mockIsPreviewEnabled = vi.fn(() => true);
vi.mock('../../../feature-flags', () => ({
isPreviewEnabled: () => mockIsPreviewEnabled(),
}));
Comment thread
jesseturner21 marked this conversation as resolved.

vi.mock('../../../logging', () => {
return {
ExecLogger: class {
Expand Down Expand Up @@ -409,6 +414,89 @@ describe('computeResourceStatuses', () => {
expect(configEntry!.deploymentState).toBe('pending-removal');
});

it('marks harness as deployed when in both local and deployed state', () => {
const project = {
...baseProject,
harnesses: [{ name: 'my-harness', path: 'harnesses/my-harness' }],
} as unknown as AgentCoreProjectSpec;

const resources: DeployedResourceState = {
harnesses: {
'my-harness': {
harnessId: 'h-123',
harnessArn: 'arn:aws:bedrock:us-east-1:123456789:harness/h-123',
roleArn: 'arn:aws:iam::123456789:role/test',
status: 'ACTIVE',
},
},
};

const result = computeResourceStatuses(project, resources);
const harnessEntry = result.find(r => r.resourceType === 'harness' && r.name === 'my-harness');

expect(harnessEntry).toBeDefined();
expect(harnessEntry!.deploymentState).toBe('deployed');
expect(harnessEntry!.identifier).toBe('arn:aws:bedrock:us-east-1:123456789:harness/h-123');
});

it('marks harness as local-only when not in deployed state', () => {
const project = {
...baseProject,
harnesses: [{ name: 'my-harness', path: 'harnesses/my-harness' }],
} as unknown as AgentCoreProjectSpec;

const result = computeResourceStatuses(project, undefined);
const harnessEntry = result.find(r => r.resourceType === 'harness' && r.name === 'my-harness');

expect(harnessEntry).toBeDefined();
expect(harnessEntry!.deploymentState).toBe('local-only');
});

it('marks harness as pending-removal when in deployed state but not in local schema', () => {
const resources: DeployedResourceState = {
harnesses: {
'removed-harness': {
harnessId: 'h-456',
harnessArn: 'arn:aws:bedrock:us-east-1:123456789:harness/h-456',
roleArn: 'arn:aws:iam::123456789:role/test',
status: 'ACTIVE',
},
},
};

const result = computeResourceStatuses(baseProject, resources);
const harnessEntry = result.find(r => r.resourceType === 'harness' && r.name === 'removed-harness');

expect(harnessEntry).toBeDefined();
expect(harnessEntry!.deploymentState).toBe('pending-removal');
expect(harnessEntry!.identifier).toBe('arn:aws:bedrock:us-east-1:123456789:harness/h-456');
});

it('does not include harnesses when preview is disabled', () => {
mockIsPreviewEnabled.mockReturnValueOnce(false);

const project = {
...baseProject,
harnesses: [{ name: 'my-harness', path: 'harnesses/my-harness' }],
} as unknown as AgentCoreProjectSpec;

const resources: DeployedResourceState = {
harnesses: {
'my-harness': {
harnessId: 'h-123',
harnessArn: 'arn:aws:bedrock:us-east-1:123456789:harness/h-123',
roleArn: 'arn:aws:iam::123456789:role/test',
status: 'ACTIVE',
},
},
};

const result = computeResourceStatuses(project, resources);
const harnessEntries = result.filter(r => r.resourceType === 'harness');

expect(harnessEntries).toHaveLength(0);
});

it('handles mixed deployed and local-only resources', () => {
const project = {
...baseProject,
Expand Down
13 changes: 13 additions & 0 deletions src/cli/commands/status/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getAgentRuntimeStatus } from '../../aws';
import { getEvaluator, getOnlineEvaluationConfig } from '../../aws/agentcore-control';
import { dnsSuffix } from '../../aws/partition';
import { getErrorMessage } from '../../errors';
import { isPreviewEnabled } from '../../feature-flags';
import { ExecLogger } from '../../logging';
import type { ResourceDeploymentState } from './constants';
import { buildRuntimeInvocationUrl } from './constants';
Expand All @@ -24,6 +25,7 @@ export interface ResourceStatusEntry {
| 'config-bundle'
| 'ab-test'
| 'dataset'
| 'harness'
| 'runtime-endpoint';
name: string;
deploymentState: ResourceDeploymentState;
Expand Down Expand Up @@ -296,6 +298,16 @@ export function computeResourceStatuses(
getParentName: item => item.agentName,
});

const harnesses = isPreviewEnabled()
? diffResourceSet({
resourceType: 'harness',
localItems: project.harnesses ?? [],
deployedRecord: resources?.harnesses ?? {},
getIdentifier: deployed => deployed.harnessArn,
getLocalDetail: () => undefined,
})
: [];

return [
...agents,
...runtimeEndpoints,
Expand All @@ -309,6 +321,7 @@ export function computeResourceStatuses(
...datasets,
...configBundles,
...abTests,
...harnesses,
];
}

Expand Down
17 changes: 13 additions & 4 deletions src/cli/commands/status/command.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ValidationError, serializeResult } from '../../../lib';
import { getErrorMessage } from '../../errors';
import { isPreviewEnabled } from '../../feature-flags';
import { getDatasetStatus } from '../../operations/dataset';
import type { DatasetStatusResult } from '../../operations/dataset';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
Expand All @@ -25,6 +26,7 @@
'config-bundle',
'ab-test',
'dataset',
...(isPreviewEnabled() ? (['harness'] as const) : []),
Comment thread
jesseturner21 marked this conversation as resolved.
] as const;
const VALID_STATES = ['deployed', 'local-only', 'pending-removal'] as const;

Expand Down Expand Up @@ -65,10 +67,7 @@
.description(COMMAND_DESCRIPTIONS.status)
.option('--runtime-id <id>', 'Look up a specific runtime by ID')
.option('--target <name>', 'Select deployment target')
.option(
'--type <type>',
'Filter by resource type (agent, runtime-endpoint, memory, credential, gateway, evaluator, online-eval, policy-engine, policy, config-bundle, ab-test, dataset)'
)
.option('--type <type>', `Filter by resource type (${VALID_RESOURCE_TYPES.join(', ')})`)
.option('--state <state>', 'Filter by deployment state (deployed, local-only, pending-removal)')
.option('--runtime <name>', 'Filter to a specific runtime')
.option('--json', 'Output as JSON')
Expand Down Expand Up @@ -170,6 +169,7 @@
const configBundles = filtered.filter(r => r.resourceType === 'config-bundle');
const abTests = filtered.filter(r => r.resourceType === 'ab-test');
const datasets = filtered.filter(r => r.resourceType === 'dataset');
const harnesses = filtered.filter(r => r.resourceType === 'harness');
// TODO: Add http-gateway resource type when diffResourceSet for HTTP gateways is added to action.ts

// Fetch enriched dataset info when --type dataset is specified
Expand Down Expand Up @@ -381,6 +381,15 @@

{/* TODO: Add HTTP Gateways render section when diffResourceSet is added to action.ts */}

{harnesses.length > 0 && (
<Box flexDirection="column" marginTop={1}>
<Text bold>Harnesses</Text>
{harnesses.map(entry => (
<ResourceEntry key={`${entry.resourceType}-${entry.name}`} entry={entry} />
))}
</Box>
)}

{filtered.length === 0 && <Text dimColor>No resources match the given filters.</Text>}
</Box>
);
Expand All @@ -391,7 +400,7 @@
});
};

function ResourceEntry({ entry, showRuntime }: { entry: ResourceStatusEntry; showRuntime?: boolean }) {

Check warning on line 403 in src/cli/commands/status/command.tsx

View workflow job for this annotation

GitHub Actions / lint

Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `extraHOCs` option
return (
<Text>
{' '}
Expand Down
2 changes: 2 additions & 0 deletions src/cli/telemetry/schemas/common-shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export const FilterType = z.enum([
'policy',
'config-bundle',
'ab-test',
'dataset',
'harness',
'none',
]);
export const AgentFramework = z.enum(['strands', 'langchain_langgraph', 'googleadk', 'openaiagents']);
Expand Down
1 change: 1 addition & 0 deletions src/cli/tui/components/ResourceGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
'config-bundle': '⬡',
'ab-test': '⚗',
dataset: '▤',
harness: '⬢',
'runtime-endpoint': '◉',
} as const;

Expand Down Expand Up @@ -113,7 +114,7 @@
);
}

export function getTargetDisplayText(target: AgentCoreGatewayTarget): string {

Check warning on line 117 in src/cli/tui/components/ResourceGraph.tsx

View workflow job for this annotation

GitHub Actions / lint

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
if (target.targetType === 'mcpServer' && target.endpoint) return target.endpoint;
if (target.targetType === 'apiGateway' && target.apiGateway)
return `${target.apiGateway.restApiId}/${target.apiGateway.stage}`;
Expand Down
Loading