Skip to content
Open
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ All notable changes to Agent Relay will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [Unreleased - Major]

### Removed

- Python workflows, the `agent-relay.py` node provider, and the `agent-relay-sdk` package. Local workflows are YAML, TypeScript, JavaScript, or shell. Cloud and worker workflows are YAML or TypeScript. Node definitions are TypeScript or JavaScript. SDKs are TypeScript and Swift.

### Fixed

- `agent-relay uninstall --dry-run` previews broker termination without sending a signal to the running process.

## [14.0.0] - 2026-10-13

### Changed
Expand Down
40 changes: 40 additions & 0 deletions packages/cli/src/cli/commands/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2833,6 +2833,46 @@ describe('registerCoreCommands', () => {
});
});

it.each([
['dry-run', true],
['normal uninstall', false],
])('uninstall %s handles a recorded broker process safely', async (_mode, dryRun) => {
const projectRoot = nodeFs.mkdtempSync(nodePath.join(os.tmpdir(), 'relay-uninstall-process-'));
const dataDir = nodePath.join(projectRoot, '.agentworkforce', 'relay');
const connectionPath = nodePath.join(dataDir, 'connection.json');
nodeFs.mkdirSync(dataDir, { recursive: true });
nodeFs.writeFileSync(connectionPath, connectionFile(54321));

try {
const { deps } = createHarness();
vi.mocked(deps.getProjectPaths).mockReturnValue({
projectRoot,
dataDir,
teamDir: nodePath.join(dataDir, 'teams'),
dbPath: nodePath.join(dataDir, 'messages.db'),
projectId: 'test-uninstall',
});
const program = new Command();
registerCoreMaintenance(program, deps);

const exitCode = await runCommand(program, ['uninstall', ...(dryRun ? ['--dry-run'] : [])]);

expect(exitCode).toBeUndefined();
if (dryRun) {
expect(deps.log).toHaveBeenCalledWith('[dry-run] Would send SIGTERM to broker (pid: 54321)');
expect(deps.killProcess).not.toHaveBeenCalled();
expect(deps.fs.rmSync).not.toHaveBeenCalled();
expect(deps.fs.unlinkSync).not.toHaveBeenCalled();
expect(deps.execCommand).not.toHaveBeenCalled();
expect(nodeFs.existsSync(connectionPath)).toBe(true);
} else {
expect(deps.killProcess).toHaveBeenCalledExactlyOnceWith(54321, 'SIGTERM');
}
} finally {
nodeFs.rmSync(projectRoot, { recursive: true, force: true });
}
});

it('uninstall dry-run covers renamed and legacy installer bin directories', async () => {
const { deps } = createHarness();
const program = new Command();
Expand Down
18 changes: 11 additions & 7 deletions packages/cli/src/cli/lib/core-maintenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,18 +223,22 @@ export async function runUninstallCommand(
const paths = deps.getProjectPaths();
const connectionPath = path.join(paths.dataDir, 'connection.json');

// Kill the broker if running
const isDryRun = options.dryRun === true;

// Preview broker shutdown without signalling a running process.
const conn = readBrokerConnection(paths.dataDir);
if (conn && conn.pid > 0) {
try {
deps.killProcess(conn.pid, 'SIGTERM');
} catch {
// Ignore dead processes.
if (isDryRun) {
deps.log(`[dry-run] Would send SIGTERM to broker (pid: ${conn.pid})`);
} else {
try {
deps.killProcess(conn.pid, 'SIGTERM');
} catch {
// Ignore dead processes.
}
}
}

const isDryRun = options.dryRun === true;

// --- Data directory cleanup ---
if (isDryRun) {
if (options.keepData) {
Expand Down