Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2a6caaf
feat: wire telemetry withCommandRun into all add.* commands
Hweinstock Apr 29, 2026
a0eb4ec
refactor: extract cliCommandRun helper, apply to all add.* primitives
Hweinstock Apr 29, 2026
b102b94
test: add audit file assertions for all add.* telemetry
Hweinstock Apr 29, 2026
72ab9d3
test: add telemetry audit assertions to existing add integ tests
Hweinstock Apr 29, 2026
e521dbc
refactor: extract shared audit test utils into src/test-utils/audit.ts
Hweinstock Apr 29, 2026
f152b0c
fix: address review feedback — guard telemetry init, replaceAll, unkn…
Hweinstock Apr 29, 2026
f7003ca
fix: AgentPrimitive TUI try/catch, standardize uses safeParse
Hweinstock Apr 29, 2026
091264c
refactor: extract standalone assertTelemetry helper
Hweinstock Apr 30, 2026
8aecebf
refactor: rename audit.ts to telemetry-helper.ts, clarify method names
Hweinstock Apr 30, 2026
8e4a229
refactor: move assertTelemetry into TelemetryHelper as assertMetricEm…
Hweinstock Apr 30, 2026
445a448
feat: add telemetry to TUI add paths via withAddTelemetry
Hweinstock Apr 30, 2026
c0af1ea
fix: review feedback — withAddTelemetry safety, standardize handles u…
Hweinstock Apr 30, 2026
db7300b
fix: remove unnecessary type assertion
Hweinstock Apr 30, 2026
de7c6df
fix: address review — document standardize cast, add policy-engine + …
Hweinstock Apr 30, 2026
9087af0
refactor: centralize gateway target type mapping in common-shapes
Hweinstock Apr 30, 2026
35a0531
fix: preserve original function error with telemetry wrapper
Hweinstock Apr 30, 2026
0d09e63
refactor: extract telemetryAttrs into a single line
Hweinstock Apr 30, 2026
3d948c4
feat: wire up telemetry for addAgent
Hweinstock Apr 30, 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
2 changes: 1 addition & 1 deletion e2e-tests/byo-custom-jwt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const region = process.env.AWS_REGION ?? 'us-east-1';
* Run the local CLI build without skipping install (needed for deploy).
*/
function runLocalCLI(args: string[], cwd: string): Promise<RunResult> {
return runCLI(args, cwd, /* skipInstall */ false);
return runCLI(args, cwd, { skipInstall: false });
}

describe.sequential('e2e: BYO agent with CUSTOM_JWT auth', () => {
Expand Down
58 changes: 55 additions & 3 deletions integ-tests/add-remove-resources.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { createTestProject, readProjectConfig, runCLI } from '../src/test-utils/index.js';
import type { TestProject } from '../src/test-utils/index.js';
import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const telemetry = createTelemetryHelper();

describe('integration: add and remove resources', () => {
let project: TestProject;

Expand All @@ -16,13 +19,16 @@ describe('integration: add and remove resources', () => {

afterAll(async () => {
await project.cleanup();
telemetry.destroy();
});

describe('memory lifecycle', () => {
const memoryName = `IntegMem${Date.now().toString().slice(-6)}`;

it('adds a memory resource', async () => {
const result = await runCLI(['add', 'memory', '--name', memoryName, '--json'], project.projectPath);
const result = await runCLI(['add', 'memory', '--name', memoryName, '--json'], project.projectPath, {
env: telemetry.env,
});

expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
const json = JSON.parse(result.stdout);
Expand All @@ -34,13 +40,17 @@ describe('integration: add and remove resources', () => {
expect(memories, 'memories should exist').toBeDefined();
const found = memories!.some((m: Record<string, unknown>) => m.name === memoryName);
expect(found, `Memory "${memoryName}" should be in config`).toBe(true);

// Verify telemetry
telemetry.assertMetricEmitted({ command: 'add.memory', exit_reason: 'success' });
});

it('adds a memory with EPISODIC strategy and verifies reflectionNamespaces', async () => {
const episodicMemName = `EpiMem${Date.now().toString().slice(-6)}`;
const result = await runCLI(
['add', 'memory', '--name', episodicMemName, '--strategies', 'EPISODIC', '--json'],
project.projectPath
project.projectPath,
{ env: telemetry.env }
);

expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
Expand All @@ -61,6 +71,14 @@ describe('integration: add and remove resources', () => {
expect(episodic!.reflectionNamespaces, 'Should have reflectionNamespaces').toBeDefined();
expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0);

// Verify telemetry
telemetry.assertMetricEmitted({
command: 'add.memory',
exit_reason: 'success',
strategy_count: '1',
strategy_episodic: 'true',
});

// Clean up
await runCLI(['remove', 'memory', '--name', episodicMemName, '--json'], project.projectPath);
});
Expand All @@ -86,7 +104,8 @@ describe('integration: add and remove resources', () => {
it('adds a credential resource', async () => {
const result = await runCLI(
['add', 'credential', '--name', credentialName, '--api-key', 'test-key-integ-123', '--json'],
project.projectPath
project.projectPath,
{ env: telemetry.env }
);

expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
Expand All @@ -99,6 +118,13 @@ describe('integration: add and remove resources', () => {
expect(credentials, 'credentials should exist').toBeDefined();
const found = credentials!.some((c: Record<string, unknown>) => c.name === credentialName);
expect(found, `Credential "${credentialName}" should be in config`).toBe(true);

// Verify telemetry
telemetry.assertMetricEmitted({
command: 'add.credential',
exit_reason: 'success',
credential_type: 'api-key',
});
});

it('removes the credential resource', async () => {
Expand All @@ -115,4 +141,30 @@ describe('integration: add and remove resources', () => {
expect(found, `Credential "${credentialName}" should be removed from config`).toBe(false);
});
});

describe('policy-engine', () => {
const engineName = `TestEngine${Date.now().toString().slice(-6)}`;

it('adds a policy engine resource', async () => {
const result = await runCLI(['add', 'policy-engine', '--name', engineName, '--json'], project.projectPath, {
env: telemetry.env,
});

expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

telemetry.assertMetricEmitted({
command: 'add.policy-engine',
exit_reason: 'success',
attach_gateway_count: '0',
});
});

it('removes the policy engine resource', async () => {
const result = await runCLI(['remove', 'policy-engine', '--name', engineName, '--json'], project.projectPath);

expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
});
});
});
2 changes: 1 addition & 1 deletion integ-tests/create-no-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('integration: create without agent', () => {

it.skipIf(!hasNpm || !hasGit)('creates project with real npm install and git init', async () => {
const name = `NoAgent${Date.now().toString().slice(-6)}`;
const result = await runCLI(['create', '--name', name, '--no-agent', '--json'], testDir, false);
const result = await runCLI(['create', '--name', name, '--no-agent', '--json'], testDir, { skipInstall: false });

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

Expand Down
2 changes: 1 addition & 1 deletion integ-tests/create-with-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('integration: create with Python agent', () => {
'--json',
],
testDir,
false
{ skipInstall: false }
);

expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
Expand Down
2 changes: 1 addition & 1 deletion integ-tests/dev-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('integration: dev server', () => {
'--json',
],
testDir,
false
{ skipInstall: false }
);

if (result.exitCode === 0) {
Expand Down
47 changes: 20 additions & 27 deletions integ-tests/help.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { spawnAndCollect } from '../src/test-utils/cli-runner.js';
import { runCLI } from '../src/test-utils/index.js';
import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js';
import { readdirSync } from 'node:fs';
import { mkdir, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { afterAll, describe, expect, it } from 'vitest';

const COMMANDS = [
'create',
Expand Down Expand Up @@ -45,52 +44,46 @@ describe('CLI help', () => {
});

describe('help modes telemetry', () => {
let testConfigDir: string;
const telemetry = createTelemetryHelper();
const cliPath = join(__dirname, '..', 'dist', 'cli', 'index.mjs');

beforeAll(async () => {
testConfigDir = join(tmpdir(), `agentcore-help-telemetry-${Date.now()}`);
await mkdir(testConfigDir, { recursive: true });
});
afterAll(() => rm(testConfigDir, { recursive: true, force: true }));
afterAll(() => telemetry.destroy());

function run(args: string[], extraEnv: Record<string, string> = {}) {
return spawnAndCollect('node', [cliPath, ...args], tmpdir(), {
return spawnAndCollect('node', [cliPath, ...args], process.cwd(), {
AGENTCORE_SKIP_INSTALL: '1',
AGENTCORE_CONFIG_DIR: testConfigDir,
...telemetry.env,
...extraEnv,
});
}

it('writes JSONL audit file when audit is enabled via env var', async () => {
const result = await run(['help', 'modes'], { AGENTCORE_TELEMETRY_AUDIT: '1' });
const result = await run(['help', 'modes']);
expect(result.exitCode).toBe(0);

const telemetryDir = join(testConfigDir, 'telemetry');
const files = readdirSync(telemetryDir).filter(f => f.startsWith('help-'));
expect(files).toHaveLength(1);

const content = await readFile(join(telemetryDir, files[0]!), 'utf-8');
const entry = JSON.parse(content.trim());
expect(entry.attrs).toMatchObject({
'service.name': 'agentcore-cli',
'agentcore-cli.mode': 'cli',
const entries = telemetry.readEntries();
expect(entries).toHaveLength(1);
telemetry.assertMetricEmitted({
command_group: 'help',
command: 'help.modes',
exit_reason: 'success',
});
expect(entry.attrs['agentcore-cli.session_id']).toBeDefined();
expect(entry.attrs['os.type']).toBeDefined();
expect(entry.value).toBeGreaterThanOrEqual(0);
expect(entries[0]!.attrs['agentcore-cli.session_id']).toBeDefined();
expect(entries[0]!.attrs['os.type']).toBeDefined();
expect(entries[0]!.value).toBeGreaterThanOrEqual(0);
});

it('does not write audit file when audit is not enabled', async () => {
const telemetryDir = join(testConfigDir, 'telemetry');
await rm(telemetryDir, { recursive: true, force: true });
telemetry.clearEntries();

const result = await run(['help', 'modes']);
const noAuditCliPath = join(__dirname, '..', 'dist', 'cli', 'index.mjs');
const result = await spawnAndCollect('node', [noAuditCliPath, 'help', 'modes'], process.cwd(), {
AGENTCORE_SKIP_INSTALL: '1',
AGENTCORE_CONFIG_DIR: telemetry.dir,
});
expect(result.exitCode).toBe(0);

const telemetryDir = join(telemetry.dir, 'telemetry');
try {
const files = readdirSync(telemetryDir);
expect(files).toHaveLength(0);
Expand Down
Loading
Loading