Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
37f9d3f
feat: implement compile-time feature flag for preview/GA branch conso…
jesseturner21 May 21, 2026
5f909ee
fix: remove unnecessary quotes around __PREVIEW__ key in esbuild config
jesseturner21 May 21, 2026
9808478
fix: skip harness integration and e2e tests in GA builds
jesseturner21 May 21, 2026
dad4319
fix: gate harness options in invoke/create commands and fix remove me…
jesseturner21 May 21, 2026
8341521
fix: auto-deploy harness in TUI dev mode before invoking
jesseturner21 May 21, 2026
cd64794
fix: use queueMicrotask for deploy-to-harness transition in TUI dev
jesseturner21 May 21, 2026
9c6d334
fix: match preview branch deploy UI and persist deployHash
jesseturner21 May 21, 2026
05aa114
fix: align harness UX with preview branch
jesseturner21 May 21, 2026
9a6dab1
fix: hint message render order and harness/runtime disambiguation
jesseturner21 May 21, 2026
b50b195
fix: route harness invocations and show deploy box in dev mode
jesseturner21 May 22, 2026
022ce41
fix: serve harness info in status API and fix deploy message flow
jesseturner21 May 22, 2026
6a62cd4
fix: include harnesses in /api/resources response for browser UI
jesseturner21 May 22, 2026
93dc6fd
test: add unit tests for harness invocation and status handlers
jesseturner21 May 25, 2026
1db7ef3
test: add unit tests for resolve-agent, change-detection, and harness…
jesseturner21 May 25, 2026
5b43cbd
merge: bring in main (datasets, feedback, version 0.15.0)
jesseturner21 May 25, 2026
ac6d36b
fix: adjust preview-flag DCE test for keepNames compatibility
jesseturner21 May 25, 2026
bcb1020
fix: pass structured DeployMessage through to TUI instead of plain st…
jesseturner21 May 26, 2026
d04a2d6
fix(harness): populate retrievalConfig from memory strategy namespaces
jesseturner21 May 26, 2026
59dba1c
Merge branch 'main' into feature/preview-feature-flag
jesseturner21 May 26, 2026
0576a80
fix: rewrite dev command to resolve merge conflict with telemetry ref…
jesseturner21 May 26, 2026
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
3 changes: 3 additions & 0 deletions e2e-tests/harness-bedrock.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createHarnessE2ESuite } from './harness-e2e-helper.js';

createHarnessE2ESuite({ modelProvider: 'bedrock' });
165 changes: 165 additions & 0 deletions e2e-tests/harness-e2e-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { hasAwsCredentials, parseJsonOutput, prereqs, retry, spawnAndCollect } from '../src/test-utils/index.js';
import {
cleanupStaleCredentialProviders,
installCdkTarball,
runAgentCoreCLI,
teardownE2EProject,
writeAwsTargets,
} from './e2e-helper.js';
import { randomUUID } from 'node:crypto';
import { mkdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const hasAws = hasAwsCredentials();
// Harness features are only available in preview builds (BUILD_PREVIEW=1).
const isPreviewBuild = process.env.BUILD_PREVIEW === '1';
const baseCanRun = prereqs.npm && prereqs.git && hasAws && isPreviewBuild;

interface HarnessE2EConfig {
modelProvider: 'bedrock' | 'open_ai' | 'gemini';
requiredEnvVar?: string;
skipMemory?: boolean;
}

export function createHarnessE2ESuite(cfg: HarnessE2EConfig) {
const hasRequiredVar = !cfg.requiredEnvVar || !!process.env[cfg.requiredEnvVar];
const canRun = baseCanRun && hasRequiredVar;

const providerLabel =
cfg.modelProvider === 'open_ai' ? 'OpenAI' : cfg.modelProvider === 'gemini' ? 'Gemini' : 'Bedrock';

describe.sequential(`e2e: harness/${providerLabel} — create → deploy → invoke`, () => {
let testDir: string;
let projectPath: string;
let harnessName: string;

beforeAll(async () => {
if (!canRun) return;

await cleanupStaleCredentialProviders();

testDir = join(tmpdir(), `agentcore-e2e-harness-${randomUUID()}`);
await mkdir(testDir, { recursive: true });

const providerSlug = cfg.modelProvider.replace('_', '').slice(0, 4);
harnessName = `E2eHrns${providerSlug}${String(Date.now()).slice(-8)}`;

const createArgs = [
'create',
'--name',
harnessName,
'--model-provider',
cfg.modelProvider,
'--json',
'--skip-git',
];

if (cfg.requiredEnvVar && process.env[cfg.requiredEnvVar]) {
createArgs.push('--api-key-arn', process.env[cfg.requiredEnvVar]!);
}

if (cfg.skipMemory) {
createArgs.push('--no-harness-memory');
}

const result = await runAgentCoreCLI(createArgs, testDir);

expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
const json = parseJsonOutput(result.stdout) as { projectPath: string };
projectPath = json.projectPath;

await writeAwsTargets(projectPath);
installCdkTarball(projectPath);
}, 300000);

afterAll(async () => {
if (projectPath && hasAws) {
await teardownE2EProject(projectPath, harnessName, cfg.modelProvider);
}
if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 });
}, 600000);

it.skipIf(!canRun)(
'deploys to AWS successfully',
async () => {
expect(projectPath, 'Project should have been created').toBeTruthy();

await retry(
async () => {
const result = await runAgentCoreCLI(['deploy', '--yes', '--json'], projectPath);

if (result.exitCode !== 0) {
console.log('Deploy stdout:', result.stdout);
console.log('Deploy stderr:', result.stderr);
}

expect(result.exitCode, `Deploy failed (stderr: ${result.stderr}, stdout: ${result.stdout})`).toBe(0);

const json = parseJsonOutput(result.stdout) as { success: boolean };
expect(json.success, 'Deploy should report success').toBe(true);
},
1,
30000
);
},
600000
);

it.skipIf(!canRun)(
'invokes the deployed harness',
async () => {
expect(projectPath, 'Project should have been created').toBeTruthy();

await retry(
async () => {
const result = await runAgentCoreCLI(
['invoke', '--harness', harnessName, '--prompt', 'Say hello', '--json'],
projectPath
);

if (result.exitCode !== 0) {
console.log('Invoke stdout:', result.stdout);
console.log('Invoke stderr:', result.stderr);
}

expect(result.exitCode, `Invoke failed: ${result.stderr}`).toBe(0);

const json = parseJsonOutput(result.stdout) as { success: boolean };
expect(json.success, 'Invoke should report success').toBe(true);
},
3,
15000
);
},
180000
);

it.skipIf(!canRun)(
'status shows the deployed harness',
async () => {
const statusResult = await spawnAndCollect('agentcore', ['status', '--json'], projectPath);

expect(statusResult.exitCode, `Status failed: ${statusResult.stderr}`).toBe(0);

const json = parseJsonOutput(statusResult.stdout) as {
success: boolean;
resources: {
resourceType: string;
name: string;
deploymentState: string;
identifier?: string;
}[];
};
expect(json.success).toBe(true);

const harness = json.resources.find(r => r.resourceType === 'harness' && r.name === harnessName);
expect(harness, `Harness "${harnessName}" should appear in status`).toBeDefined();
expect(harness!.deploymentState).toBe('deployed');
expect(harness!.identifier, 'Deployed harness should have a harnessArn').toBeTruthy();
},
120000
);
});
}
3 changes: 3 additions & 0 deletions e2e-tests/harness-gemini.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createHarnessE2ESuite } from './harness-e2e-helper.js';

createHarnessE2ESuite({ modelProvider: 'gemini', requiredEnvVar: 'GEMINI_API_KEY_ARN', skipMemory: true });
3 changes: 3 additions & 0 deletions e2e-tests/harness-openai.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createHarnessE2ESuite } from './harness-e2e-helper.js';

createHarnessE2ESuite({ modelProvider: 'open_ai', requiredEnvVar: 'OPENAI_API_KEY_ARN', skipMemory: true });
11 changes: 8 additions & 3 deletions esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,20 @@ const textLoaderPlugin = {
},
};

const outfile = process.env.ESBUILD_OUTFILE || './dist/cli/index.mjs';

await esbuild.build({
entryPoints: ['./src/cli/index.ts'],
outfile: './dist/cli/index.mjs',
outfile,
bundle: true,
platform: 'node',
format: 'esm',
minify: true,
keepNames: true,
jsx: 'automatic',
define: {
__PREVIEW__: process.env.BUILD_PREVIEW === '1' ? 'true' : 'false',
},
// Inject require shim for ESM compatibility with CommonJS dependencies
banner: {
js: `import { createRequire } from 'module'; import { fileURLToPath as __ef } from 'url'; import { dirname as __ed } from 'path'; const require = createRequire(import.meta.url); const __filename = __ef(import.meta.url); const __dirname = __ed(__filename);`,
Expand All @@ -59,9 +64,9 @@ await esbuild.build({
});

// Make executable
fs.chmodSync('./dist/cli/index.mjs', '755');
fs.chmodSync(outfile, '755');

console.log('CLI build complete: dist/cli/index.mjs');
console.log(`CLI build complete: ${outfile}`);

// ---------------------------------------------------------------------------
// MCP harness build — opt-in via BUILD_HARNESS=1
Expand Down
Loading
Loading