From 5b3d4e64df64af671594b4f9451683774632df29 Mon Sep 17 00:00:00 2001 From: Jesse Turner Date: Wed, 27 May 2026 15:15:55 -0400 Subject: [PATCH 1/2] fix(status): show harnesses in agentcore status output computeResourceStatuses was missing harness diffing, so deployed harnesses never appeared in status output. Add harness support gated behind __PREVIEW__ compile-time flag. Constraint: harness features must be gated behind isPreviewEnabled() Rejected: Always show harnesses | GA builds must not expose harness concepts Confidence: high Scope-risk: narrow --- .../commands/status/__tests__/action.test.ts | 62 +++++++++++++++++++ src/cli/commands/status/action.ts | 13 ++++ src/cli/commands/status/command.tsx | 13 ++++ src/cli/telemetry/schemas/common-shapes.ts | 2 + src/cli/tui/components/ResourceGraph.tsx | 1 + 5 files changed, 91 insertions(+) diff --git a/src/cli/commands/status/__tests__/action.test.ts b/src/cli/commands/status/__tests__/action.test.ts index f8c94c6bc..fa83f0106 100644 --- a/src/cli/commands/status/__tests__/action.test.ts +++ b/src/cli/commands/status/__tests__/action.test.ts @@ -18,6 +18,10 @@ vi.mock('../../../aws/agentcore-control', () => ({ getOnlineEvaluationConfig: (...args: unknown[]) => mockGetOnlineEvaluationConfig(...args), })); +vi.mock('../../../feature-flags', () => ({ + isPreviewEnabled: () => true, +})); + vi.mock('../../../logging', () => { return { ExecLogger: class { @@ -409,6 +413,64 @@ 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('handles mixed deployed and local-only resources', () => { const project = { ...baseProject, diff --git a/src/cli/commands/status/action.ts b/src/cli/commands/status/action.ts index cece9c03d..1432e2d1a 100644 --- a/src/cli/commands/status/action.ts +++ b/src/cli/commands/status/action.ts @@ -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'; @@ -24,6 +25,7 @@ export interface ResourceStatusEntry { | 'config-bundle' | 'ab-test' | 'dataset' + | 'harness' | 'runtime-endpoint'; name: string; deploymentState: ResourceDeploymentState; @@ -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, @@ -309,6 +321,7 @@ export function computeResourceStatuses( ...datasets, ...configBundles, ...abTests, + ...harnesses, ]; } diff --git a/src/cli/commands/status/command.tsx b/src/cli/commands/status/command.tsx index eacc21943..348d96174 100644 --- a/src/cli/commands/status/command.tsx +++ b/src/cli/commands/status/command.tsx @@ -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'; @@ -25,6 +26,7 @@ const VALID_RESOURCE_TYPES = [ 'config-bundle', 'ab-test', 'dataset', + ...(isPreviewEnabled() ? (['harness'] as const) : []), ] as const; const VALID_STATES = ['deployed', 'local-only', 'pending-removal'] as const; @@ -381,6 +383,17 @@ export const registerStatus = (program: Command) => { {/* TODO: Add HTTP Gateways render section when diffResourceSet is added to action.ts */} + {filtered.filter(r => r.resourceType === 'harness').length > 0 && ( + + Harnesses + {filtered + .filter(r => r.resourceType === 'harness') + .map(entry => ( + + ))} + + )} + {filtered.length === 0 && No resources match the given filters.} ); diff --git a/src/cli/telemetry/schemas/common-shapes.ts b/src/cli/telemetry/schemas/common-shapes.ts index e3aa86bad..552abfd5d 100644 --- a/src/cli/telemetry/schemas/common-shapes.ts +++ b/src/cli/telemetry/schemas/common-shapes.ts @@ -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']); diff --git a/src/cli/tui/components/ResourceGraph.tsx b/src/cli/tui/components/ResourceGraph.tsx index 1295624f7..d6e6a3d04 100644 --- a/src/cli/tui/components/ResourceGraph.tsx +++ b/src/cli/tui/components/ResourceGraph.tsx @@ -23,6 +23,7 @@ const ICONS = { 'config-bundle': '⬡', 'ab-test': '⚗', dataset: '▤', + harness: '⬢', 'runtime-endpoint': '◉', } as const; From 8702812b843fa12d38462103b33b5e0940d59682 Mon Sep 17 00:00:00 2001 From: Jesse Turner Date: Wed, 27 May 2026 15:22:52 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20review=20comments=20?= =?UTF-8?q?=E2=80=94=20add=20GA=20test,=20extract=20harnesses=20var,=20dyn?= =?UTF-8?q?amic=20--type=20description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commands/status/__tests__/action.test.ts | 28 ++++++++++++++++++- src/cli/commands/status/command.tsx | 16 ++++------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/cli/commands/status/__tests__/action.test.ts b/src/cli/commands/status/__tests__/action.test.ts index fa83f0106..25520d9ce 100644 --- a/src/cli/commands/status/__tests__/action.test.ts +++ b/src/cli/commands/status/__tests__/action.test.ts @@ -18,8 +18,9 @@ vi.mock('../../../aws/agentcore-control', () => ({ getOnlineEvaluationConfig: (...args: unknown[]) => mockGetOnlineEvaluationConfig(...args), })); +const mockIsPreviewEnabled = vi.fn(() => true); vi.mock('../../../feature-flags', () => ({ - isPreviewEnabled: () => true, + isPreviewEnabled: () => mockIsPreviewEnabled(), })); vi.mock('../../../logging', () => { @@ -471,6 +472,31 @@ describe('computeResourceStatuses', () => { 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, diff --git a/src/cli/commands/status/command.tsx b/src/cli/commands/status/command.tsx index 348d96174..94ed5328a 100644 --- a/src/cli/commands/status/command.tsx +++ b/src/cli/commands/status/command.tsx @@ -67,10 +67,7 @@ export const registerStatus = (program: Command) => { .description(COMMAND_DESCRIPTIONS.status) .option('--runtime-id ', 'Look up a specific runtime by ID') .option('--target ', 'Select deployment target') - .option( - '--type ', - 'Filter by resource type (agent, runtime-endpoint, memory, credential, gateway, evaluator, online-eval, policy-engine, policy, config-bundle, ab-test, dataset)' - ) + .option('--type ', `Filter by resource type (${VALID_RESOURCE_TYPES.join(', ')})`) .option('--state ', 'Filter by deployment state (deployed, local-only, pending-removal)') .option('--runtime ', 'Filter to a specific runtime') .option('--json', 'Output as JSON') @@ -172,6 +169,7 @@ export const registerStatus = (program: Command) => { 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 @@ -383,14 +381,12 @@ export const registerStatus = (program: Command) => { {/* TODO: Add HTTP Gateways render section when diffResourceSet is added to action.ts */} - {filtered.filter(r => r.resourceType === 'harness').length > 0 && ( + {harnesses.length > 0 && ( Harnesses - {filtered - .filter(r => r.resourceType === 'harness') - .map(entry => ( - - ))} + {harnesses.map(entry => ( + + ))} )}