Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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: 3 additions & 3 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
name: Publish canary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: main
fetch-depth: 0
Expand Down Expand Up @@ -79,9 +79,9 @@ jobs:
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
if: steps.check.outputs.skip != 'true'
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
if: steps.check.outputs.skip != 'true'
with:
node-version: 20
Expand Down
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand All @@ -33,9 +33,9 @@ jobs:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand All @@ -55,9 +55,9 @@ jobs:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand All @@ -80,9 +80,9 @@ jobs:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ jobs:
# Non-blocking: never prevent merging even if this job fails
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-vps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
env:
DEPLOY_SHA: ${{ steps.resolve_deploy.outputs.deploy_sha }}
FETCH_REF: ${{ steps.resolve_deploy.outputs.fetch_ref }}
uses: appleboy/ssh-action@v1.2.2
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f
with:
host: ${{ secrets.VPS_HOST }}
username: aoagent
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
timeout-minutes: 20

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/onboarding-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435

- name: Build test image
working-directory: tests/integration
Expand All @@ -47,7 +47,7 @@ jobs:

- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: onboarding-test-logs
path: |
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ jobs:
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand All @@ -65,7 +65,7 @@ jobs:
# `publish:` command. We deliberately omit `publish:` so the action
# never runs `changeset publish`. npm publishing is handled by a
# private cron that detects the GitHub release.
- uses: changesets/action@v1
- uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b
id: changesets
with:
version: pnpm changeset version
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0 # Full history to ensure base/head SHAs are available for PR scans

Expand Down Expand Up @@ -80,10 +80,10 @@ jobs:
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Dependency Review
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3
with:
fail-on-severity: moderate

Expand All @@ -92,13 +92,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
Expand Down
133 changes: 133 additions & 0 deletions packages/cli/__tests__/commands/start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { tmpdir } from "node:os";
import { parse as parseYaml } from "yaml";
import { EventEmitter } from "node:events";
import {
generateExternalId,
getDefaultRuntime,
recordActivityEvent,
type SessionManager,
Expand Down Expand Up @@ -2828,6 +2829,82 @@ describe("start command — already-running detection", () => {
expect(newKey).toMatch(/^my-app-/);
});

it("creates new orchestrator entry in the global registry when cwd config is flat", async () => {
mockIsAlreadyRunning.mockResolvedValue({
pid: 9999,
configPath: "/fake/config.yaml",
port: 3000,
startedAt: "2026-01-01T00:00:00Z",
projects: ["agent-orchestrator_5dce9e3fe8"],
});

mockPromptSelect.mockResolvedValue("new");

const repoDir = join(tmpDir, "agent-orchestrator");
createFakeRepo(repoDir, "https://github.com/org/agent-orchestrator.git");
const localConfigPath = join(repoDir, "agent-orchestrator.yaml");
writeFileSync(localConfigPath, "agent: claude-code\n");

const projectId = generateExternalId(
repoDir,
"https://github.com/org/agent-orchestrator.git",
);
const globalConfigPath = process.env["AO_GLOBAL_CONFIG"]!;
const { stringify: yamlStringify } = await import("yaml");
writeFileSync(
globalConfigPath,
yamlStringify(
{
defaults: {
runtime: "process",
agent: "claude-code",
workspace: "worktree",
notifiers: [],
},
projects: {
[projectId]: {
projectId,
path: repoDir,
defaultBranch: "main",
displayName: "Agent Orchestrator",
sessionPrefix: "app",
},
},
},
{ indent: 2 },
),
);

mockConfigRef.current = makeConfig({
[projectId]: makeProject({
name: "Agent Orchestrator",
path: repoDir,
sessionPrefix: "app",
}),
});
(mockConfigRef.current as Record<string, unknown>).configPath = localConfigPath;

try {
await program.parseAsync(["node", "test", "start", "--no-dashboard", "--no-orchestrator"]);
} catch {
// Startup may throw after the config mutation; this test only covers
// the flat-config new-orchestrator mutation path.
}

const updatedGlobal = parseYaml(readFileSync(globalConfigPath, "utf-8")) as {
projects: Record<string, Record<string, unknown>>;
};
const projectKeys = Object.keys(updatedGlobal.projects);
expect(projectKeys).toHaveLength(2);
expect(projectKeys).toContain(projectId);
const newKey = projectKeys.find((key) => key !== projectId);
expect(newKey).toMatch(new RegExp(`^${projectId}-`));
expect(updatedGlobal.projects[newKey!].path).toBe(repoDir);

const localConfig = readFileSync(localConfigPath, "utf-8");
expect(localConfig).not.toContain("projects:");
});

it("does not mutate YAML when non-TTY caller detects already running (path arg)", async () => {
mockIsAlreadyRunning.mockResolvedValue({
pid: 9999,
Expand Down Expand Up @@ -3208,4 +3285,60 @@ describe("start command — global registry mutations", () => {
else process.env["AO_GLOBAL_CONFIG"] = origGlobalEnv;
}
});

it("writes interactive agent overrides to a flat repo-local config", async () => {
const repoDir = join(tmpDir, "current");
createFakeRepo(repoDir, "https://github.com/org/current.git");

const localConfigPath = join(repoDir, "agent-orchestrator.yaml");
writeFileSync(localConfigPath, "agent: claude-code\n");

const projectId = generateExternalId(repoDir, "https://github.com/org/current.git");
mockConfigRef.current = makeConfig({
[projectId]: makeProject({
name: "Current",
path: repoDir,
sessionPrefix: "current",
}),
});
(mockConfigRef.current as Record<string, unknown>).configPath = localConfigPath;

const detectAgent = await import("../../src/lib/detect-agent.js");
vi.mocked(detectAgent.detectAvailableAgents).mockResolvedValue([
{ name: "claude-code", displayName: "Claude Code" },
{ name: "codex", displayName: "OpenAI Codex" },
]);
mockPromptSelect.mockResolvedValueOnce("claude-code").mockResolvedValueOnce("codex");
const originalStdinTty = process.stdin.isTTY;
const originalStdoutTty = process.stdout.isTTY;
Object.defineProperty(process.stdin, "isTTY", { value: true, configurable: true });
Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true });

try {
await program.parseAsync([
"node",
"test",
"start",
"--interactive",
"--no-dashboard",
"--no-orchestrator",
]);

const localConfig = readFileSync(localConfigPath, "utf-8");
expect(localConfig).toContain("agent: claude-code");
expect(localConfig).toContain("orchestrator:");
expect(localConfig).toContain("worker:");
expect(localConfig).toContain("agent: codex");
expect(localConfig).not.toContain("projects:");
} finally {
Object.defineProperty(process.stdin, "isTTY", {
value: originalStdinTty,
configurable: true,
});
Object.defineProperty(process.stdout, "isTTY", {
value: originalStdoutTty,
configurable: true,
});
}
});
});
Loading
Loading