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:
parent
549da360ab
commit
ffef4a6114
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ beforeEach(() => {
|
|||
mockConfigRef.current = {
|
||||
dataDir: "/tmp/ao",
|
||||
worktreeDir: "/tmp/wt",
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
defaults: {
|
||||
runtime: "tmux",
|
||||
agent: "claude-code",
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ beforeEach(() => {
|
|||
|
||||
mockConfigRef.current = {
|
||||
configPath,
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
defaults: {
|
||||
runtime: "tmux",
|
||||
agent: "claude-code",
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ beforeEach(() => {
|
|||
|
||||
mockConfigRef.current = {
|
||||
configPath,
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
defaults: {
|
||||
runtime: "tmux",
|
||||
agent: "claude-code",
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ beforeEach(() => {
|
|||
|
||||
mockConfigRef.current = {
|
||||
configPath,
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
defaults: {
|
||||
runtime: "tmux",
|
||||
agent: "claude-code",
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ beforeEach(() => {
|
|||
|
||||
mockConfigRef.current = {
|
||||
configPath,
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
readyThresholdMs: 300_000,
|
||||
defaults: {
|
||||
runtime: "tmux",
|
||||
|
|
|
|||
|
|
@ -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]) => [
|
||||
|
|
|
|||
|
|
@ -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]) => [
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ beforeEach(() => {
|
|||
|
||||
config = {
|
||||
configPath,
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
defaults: {
|
||||
runtime: "mock",
|
||||
agent: "mock-agent",
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ beforeEach(() => {
|
|||
|
||||
config = {
|
||||
configPath,
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
defaults: {
|
||||
runtime: "mock",
|
||||
agent: "mock-agent",
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ beforeEach(() => {
|
|||
|
||||
config = {
|
||||
configPath,
|
||||
port: 3000,
|
||||
port: 4100,
|
||||
defaults: {
|
||||
runtime: "mock",
|
||||
agent: "mock-agent",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue