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: 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