chore: change default dashboard port from 3000 to 4100

Port 3000 conflicts with Next.js, React, Express, and Rails defaults —
all common in agent sessions. 4100 sits in a gap between framework
defaults (3000 zone) and Angular (4200), minimizing conflicts.

Updated: config schema default, ao init, terminal-web plugin,
example config, README, and all test fixtures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-02-19 07:56:36 +05:30
parent 549da360ab
commit ffef4a6114
20 changed files with 33 additions and 33 deletions

View File

@ -8,7 +8,7 @@ dataDir: ~/.agent-orchestrator
worktreeDir: ~/.worktrees
# Web dashboard port
port: 3000
port: 4100
# Terminal server ports (defaults: 14800/14801 — chosen to avoid conflicts with dev tools)
# Override when running multiple dashboards to avoid EADDRINUSE

View File

@ -37,7 +37,7 @@ describe("init command", () => {
expect(existsSync(outputPath)).toBe(true);
});
it("auto mode uses port 3000 when it is available", async () => {
it("auto mode uses port 4100 when it is available", async () => {
tmpDir = mkdtempSync(join(tmpdir(), "ao-init-test-"));
const outputPath = join(tmpDir, "agent-orchestrator.yaml");
@ -50,17 +50,17 @@ describe("init command", () => {
await program.parseAsync(["node", "test", "init", "--auto", "--output", outputPath]);
const content = readFileSync(outputPath, "utf-8");
expect(content).toContain("port: 3000");
expect(content).toContain("port: 4100");
});
it("auto mode picks next free port when 3000 is occupied", async () => {
it("auto mode picks next free port when 4100 is occupied", async () => {
tmpDir = mkdtempSync(join(tmpdir(), "ao-init-test-"));
const outputPath = join(tmpDir, "agent-orchestrator.yaml");
// Occupy port 3000
// Occupy port 4100
const blocker = createServer();
await new Promise<void>((resolve) => {
blocker.listen(3000, "127.0.0.1", () => resolve());
blocker.listen(4100, "127.0.0.1", () => resolve());
});
try {
@ -73,14 +73,14 @@ describe("init command", () => {
await program.parseAsync(["node", "test", "init", "--auto", "--output", outputPath]);
const content = readFileSync(outputPath, "utf-8");
// Should NOT be 3000 since we're occupying it
expect(content).not.toContain("port: 3000");
// Should pick 3001 (or higher if 3001 is also taken)
// Should NOT be 4100 since we're occupying it
expect(content).not.toContain("port: 4100");
// Should pick 4101 (or higher if 4101 is also taken)
const portMatch = content.match(/port: (\d+)/);
expect(portMatch).toBeTruthy();
const port = parseInt(portMatch![1], 10);
expect(port).toBeGreaterThan(3000);
expect(port).toBeLessThan(3100);
expect(port).toBeGreaterThan(4100);
expect(port).toBeLessThan(4200);
} finally {
await new Promise<void>((resolve) => blocker.close(() => resolve()));
}

View File

@ -34,7 +34,7 @@ beforeEach(() => {
mockConfigRef.current = {
dataDir: "/tmp/ao",
worktreeDir: "/tmp/wt",
port: 3000,
port: 4100,
defaults: {
runtime: "tmux",
agent: "claude-code",

View File

@ -113,7 +113,7 @@ beforeEach(() => {
mockConfigRef.current = {
configPath,
port: 3000,
port: 4100,
defaults: {
runtime: "tmux",
agent: "claude-code",

View File

@ -124,7 +124,7 @@ beforeEach(() => {
mockConfigRef.current = {
configPath,
port: 3000,
port: 4100,
defaults: {
runtime: "tmux",
agent: "claude-code",

View File

@ -72,7 +72,7 @@ beforeEach(() => {
mockConfigRef.current = {
configPath,
port: 3000,
port: 4100,
defaults: {
runtime: "tmux",
agent: "claude-code",

View File

@ -170,7 +170,7 @@ beforeEach(() => {
mockConfigRef.current = {
configPath,
port: 3000,
port: 4100,
readyThresholdMs: 300_000,
defaults: {
runtime: "tmux",

View File

@ -9,7 +9,7 @@ function makeConfig(
return {
dataDir: "/tmp",
worktreeDir: "/tmp/wt",
port: 3000,
port: 4100,
defaults: { runtime: "tmux", agent: defaultAgent, workspace: "worktree", notifiers: [] },
projects: Object.fromEntries(
Object.entries(projects ?? { app: {} }).map(([id, p]) => [

View File

@ -60,7 +60,7 @@ describe("findProjectForSession", () => {
({
dataDir: "/tmp",
worktreeDir: "/tmp/wt",
port: 3000,
port: 4100,
defaults: { runtime: "tmux", agent: "claude-code", workspace: "worktree", notifiers: [] },
projects: Object.fromEntries(
Object.entries(projects).map(([id, p]) => [

View File

@ -13,7 +13,7 @@ import {
formatProjectTypeForDisplay,
} from "../lib/project-detection.js";
const DEFAULT_PORT = 3000;
const DEFAULT_PORT = 4100;
const MAX_PORT_SCAN = 100;
/** Find the first available port starting from `start`, scanning upward. */

View File

@ -114,7 +114,7 @@ beforeEach(() => {
config = {
configPath,
port: 3000,
port: 4100,
defaults: {
runtime: "mock",
agent: "mock-agent",

View File

@ -160,7 +160,7 @@ beforeEach(() => {
config = {
configPath,
port: 3000,
port: 4100,
defaults: {
runtime: "mock",
agent: "mock-agent",

View File

@ -87,7 +87,7 @@ beforeEach(() => {
config = {
configPath,
port: 3000,
port: 4100,
defaults: {
runtime: "mock",
agent: "mock-agent",

View File

@ -89,7 +89,7 @@ const DefaultPluginsSchema = z.object({
});
const OrchestratorConfigSchema = z.object({
port: z.number().default(3000),
port: z.number().default(4100),
terminalPort: z.number().optional(),
directTerminalPort: z.number().optional(),
readyThresholdMs: z.number().nonnegative().default(300_000),

View File

@ -59,7 +59,7 @@ describe.skipIf(!tmuxOk)("CLI-Core integration (hash-based architecture)", () =>
// Create config WITHOUT dataDir/worktreeDir (new architecture)
const config = {
port: 3000,
port: 4100,
defaults: {
runtime: "tmux",
agent: "claude-code",
@ -155,7 +155,7 @@ describe.skipIf(!tmuxOk)("CLI-Core integration (hash-based architecture)", () =>
// Create session-manager with configPath
const config: OrchestratorConfig = {
configPath, // This enables hash-based architecture
port: 3000,
port: 4100,
readyThresholdMs: 300_000,
defaults: {
runtime: "tmux",
@ -215,7 +215,7 @@ describe.skipIf(!tmuxOk)("CLI-Core integration (hash-based architecture)", () =>
const config: OrchestratorConfig = {
configPath,
port: 3000,
port: 4100,
readyThresholdMs: 300_000,
defaults: {
runtime: "tmux",

View File

@ -36,7 +36,7 @@ describe("config → metadata service integration (real filesystem)", () => {
// Create a minimal config file
const config = {
port: 3000,
port: 4100,
defaults: {
runtime: "tmux",
agent: "claude-code",

View File

@ -56,13 +56,13 @@ describe("terminal-web integration", () => {
});
describe("URL construction", () => {
it("default dashboard URL uses port 3000", async () => {
it("default dashboard URL uses port 4100", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const terminal = webPlugin.create();
await terminal.openSession(makeSession({ id: "url-test" }));
expect(logSpy).toHaveBeenCalledWith(
expect.stringContaining("http://localhost:3000/sessions/url-test/terminal"),
expect.stringContaining("http://localhost:4100/sessions/url-test/terminal"),
);
});

View File

@ -72,7 +72,7 @@ describe("terminal-web", () => {
const terminal = create();
await terminal.openSession(makeSession());
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("http://localhost:3000"));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("http://localhost:4100"));
});
it("uses custom dashboard URL", async () => {

View File

@ -16,7 +16,7 @@ export const manifest = {
* that the dashboard needs.
*/
export function create(config?: Record<string, unknown>): Terminal {
const dashboardUrl = (config?.dashboardUrl as string) ?? "http://localhost:3000";
const dashboardUrl = (config?.dashboardUrl as string) ?? "http://localhost:4100";
// Track which sessions have been "opened" (URL generated for the dashboard)
const openSessions = new Set<string>();

View File

@ -127,7 +127,7 @@ const mockRegistry: PluginRegistry = {
const mockConfig: OrchestratorConfig = {
configPath: "/tmp/ao-test/agent-orchestrator.yaml",
port: 3000,
port: 4100,
readyThresholdMs: 300_000,
defaults: { runtime: "tmux", agent: "claude-code", workspace: "worktree", notifiers: [] },
projects: {
@ -166,7 +166,7 @@ import { GET as eventsGET } from "@/app/api/events/route";
function makeRequest(url: string, init?: RequestInit): NextRequest {
return new NextRequest(
new URL(url, "http://localhost:3000"),
new URL(url, "http://localhost:4100"),
init as ConstructorParameters<typeof NextRequest>[1],
);
}