Skip to content

Commit a00fb19

Browse files
authored
Harden lane PR creation and remote status (#258)
1 parent 4eab46d commit a00fb19

61 files changed

Lines changed: 7159 additions & 212 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/ade-cli/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ ade --socket ios-sim launch --target target-id --text
7777
ade --socket ios-sim preview-render --source apps/ios/ADE/Views/Home.swift --index 0 --text
7878
ade --socket app-control launch --command "npm run dev" --text
7979
ade --socket browser open http://localhost:5173 --new-tab --text
80+
ade --socket macos-vm status --lane lane-id --text
81+
ade --socket macos-vm start --lane lane-id --create --no-display --text
82+
ade --socket macos-vm screenshot --lane lane-id --text
83+
ade --socket macos-vm click --lane lane-id --x 120 --y 420 --text
8084
ade --socket update status --text
8185
ade --socket update check --text
8286
ade --socket update install --text

apps/ade-cli/src/adeRpcServer.test.ts

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,85 @@ function createRuntime() {
727727
listArtifacts: vi.fn(() => []),
728728
ingest: vi.fn(() => ({ artifacts: [] })),
729729
} as any,
730+
macosVmService: {
731+
getStatus: vi.fn(async ({ laneId }: { laneId?: string | null } = {}) => ({
732+
supported: true,
733+
activeProvider: { kind: "lume", available: true },
734+
laneVm: laneId ? { laneId, name: "ade-lane-1", state: "running" } : null,
735+
vms: [],
736+
})),
737+
start: vi.fn(async ({ laneId }: { laneId: string }) => ({ laneId, name: "ade-lane-1", state: "running" })),
738+
getAgentGuide: vi.fn(async ({ laneId }: { laneId: string }) => ({
739+
laneId,
740+
vmName: "ade-lane-1",
741+
text: "Use macOS VM",
742+
target: { kind: "macos_vm_target", id: "target-1", laneId, vmName: "ade-lane-1" },
743+
})),
744+
focusWindow: vi.fn(async ({ laneId }: { laneId: string }) => ({
745+
laneId,
746+
vmName: "ade-lane-1",
747+
windowTitleQuery: "ade-lane-1",
748+
processName: "Lume",
749+
windowTitle: "ade-lane-1",
750+
frame: { x: 10, y: 20, width: 800, height: 600 },
751+
focusedAt: new Date().toISOString(),
752+
})),
753+
captureScreenshot: vi.fn(async ({ laneId }: { laneId: string }) => {
754+
const screenshotPath = path.join(projectRoot, ".ade", "artifacts", "macos-vms", laneId, "shot.png");
755+
fs.mkdirSync(path.dirname(screenshotPath), { recursive: true });
756+
fs.writeFileSync(screenshotPath, "png");
757+
return {
758+
ok: true,
759+
laneId,
760+
vmName: "ade-lane-1",
761+
path: screenshotPath,
762+
capturedAt: new Date().toISOString(),
763+
captureMode: "window-region",
764+
window: {
765+
laneId,
766+
vmName: "ade-lane-1",
767+
windowTitleQuery: "ade-lane-1",
768+
processName: "Lume",
769+
windowTitle: "ade-lane-1",
770+
frame: { x: 10, y: 20, width: 800, height: 600 },
771+
focusedAt: new Date().toISOString(),
772+
},
773+
};
774+
}),
775+
click: vi.fn(async ({ laneId, x, y }: { laneId: string; x: number; y: number }) => ({
776+
ok: true,
777+
laneId,
778+
x,
779+
y,
780+
})),
781+
selectPoint: vi.fn(async ({ laneId, x, y }: { laneId: string; x: number; y: number }) => {
782+
const screenshotPath = path.join(projectRoot, ".ade", "artifacts", "macos-vms", laneId, "selection.png");
783+
fs.mkdirSync(path.dirname(screenshotPath), { recursive: true });
784+
fs.writeFileSync(screenshotPath, "png");
785+
const screenshot = {
786+
ok: true,
787+
laneId,
788+
vmName: "ade-lane-1",
789+
path: screenshotPath,
790+
capturedAt: new Date().toISOString(),
791+
captureMode: "window-region",
792+
window: { laneId, vmName: "ade-lane-1" },
793+
};
794+
return {
795+
item: {
796+
kind: "macos_vm_target",
797+
id: "target-point-1",
798+
laneId,
799+
laneName: "Lane 1",
800+
vmName: "ade-lane-1",
801+
metadata: { selectedPoint: { x, y } },
802+
},
803+
source: "coordinate-fallback",
804+
screenshot,
805+
};
806+
}),
807+
typeText: vi.fn(async ({ laneId, text }: { laneId: string; text: string }) => ({ ok: true, laneId, textLength: text.length })),
808+
} as any,
730809
orchestratorService: {
731810
listRuns: vi.fn(() => []),
732811
pauseRun: vi.fn(({ runId }: any) => ({ id: runId, status: "paused" })),
@@ -1042,6 +1121,8 @@ describe("adeRpcServer", () => {
10421121
expect(names).not.toContain("interact_gui");
10431122
expect(names).not.toContain("screenshot_environment");
10441123
expect(names).not.toContain("record_environment");
1124+
expect(names).not.toContain("macos_vm_screenshot");
1125+
expect(names).not.toContain("macos_vm_click");
10451126

10461127
const denied = await callTool(handler, "screenshot_environment", {});
10471128
expect(denied.isError).toBe(true);
@@ -1182,6 +1263,149 @@ describe("adeRpcServer", () => {
11821263
);
11831264
});
11841265

1266+
it("exposes lane-tied macOS VM computer-use tools to agent callers", async () => {
1267+
const fixture = createRuntime();
1268+
const handler = createAdeRpcRequestHandler({ runtime: fixture.runtime, serverVersion: "test" });
1269+
1270+
await initialize(handler, {
1271+
callerId: "worker-1",
1272+
role: "agent",
1273+
missionId: "mission-1",
1274+
runId: "run-1",
1275+
stepId: "step-1",
1276+
attemptId: "attempt-1",
1277+
});
1278+
1279+
const result = (await handler({ jsonrpc: "2.0", id: 3, method: "ade/actions/list" })) as any;
1280+
const names = (result.actions ?? []).map((tool: any) => tool.name);
1281+
1282+
expect(names).toEqual(
1283+
expect.arrayContaining([
1284+
"macos_vm_status",
1285+
"macos_vm_start",
1286+
"macos_vm_guide",
1287+
"macos_vm_focus",
1288+
"macos_vm_screenshot",
1289+
"macos_vm_select",
1290+
"macos_vm_click",
1291+
"macos_vm_type",
1292+
]),
1293+
);
1294+
});
1295+
1296+
it("routes macOS VM computer-use tools and ingests screenshots as proof artifacts", async () => {
1297+
const fixture = createRuntime();
1298+
const handler = createAdeRpcRequestHandler({ runtime: fixture.runtime, serverVersion: "test" });
1299+
await initialize(handler, { callerId: "agent-1", role: "agent", chatSessionId: "chat-session-1" });
1300+
1301+
const screenshot = await callTool(handler, "macos_vm_screenshot", {
1302+
laneId: "lane-1",
1303+
name: "VM proof",
1304+
});
1305+
expect(screenshot?.isError).toBeUndefined();
1306+
expect(fixture.runtime.macosVmService.captureScreenshot).toHaveBeenCalledWith({
1307+
laneId: "lane-1",
1308+
windowTitleQuery: null,
1309+
});
1310+
expect(fixture.runtime.computerUseArtifactBrokerService.ingest).toHaveBeenCalledWith(
1311+
expect.objectContaining({
1312+
backend: { name: "macos-vm", toolName: "macos_vm_screenshot" },
1313+
owners: expect.arrayContaining([
1314+
expect.objectContaining({ kind: "lane", id: "lane-1" }),
1315+
expect.objectContaining({ kind: "chat_session", id: "chat-session-1" }),
1316+
]),
1317+
}),
1318+
);
1319+
1320+
const selected = await callTool(handler, "macos_vm_select", {
1321+
laneId: "lane-1",
1322+
x: 120,
1323+
y: 420,
1324+
});
1325+
expect(selected?.isError).toBeUndefined();
1326+
expect(fixture.runtime.macosVmService.selectPoint).toHaveBeenCalledWith({
1327+
laneId: "lane-1",
1328+
x: 120,
1329+
y: 420,
1330+
coordinateSpace: undefined,
1331+
windowTitleQuery: null,
1332+
});
1333+
expect(fixture.runtime.computerUseArtifactBrokerService.ingest).toHaveBeenCalledWith(
1334+
expect.objectContaining({
1335+
backend: { name: "macos-vm", toolName: "macos_vm_select" },
1336+
}),
1337+
);
1338+
1339+
const clicked = await callTool(handler, "macos_vm_click", { laneId: "lane-1", x: 12, y: 34 });
1340+
expect(clicked?.isError).toBeUndefined();
1341+
expect(fixture.runtime.macosVmService.click).toHaveBeenCalledWith({
1342+
laneId: "lane-1",
1343+
x: 12,
1344+
y: 34,
1345+
coordinateSpace: undefined,
1346+
windowTitleQuery: null,
1347+
});
1348+
1349+
const typed = await callTool(handler, "macos_vm_type", { laneId: "lane-1", text: "hello" });
1350+
expect(typed?.isError).toBeUndefined();
1351+
expect(fixture.runtime.macosVmService.typeText).toHaveBeenCalledWith({
1352+
laneId: "lane-1",
1353+
text: "hello",
1354+
windowTitleQuery: null,
1355+
});
1356+
});
1357+
1358+
it("routes standard computer-use tools to a lane-tied macOS VM target", async () => {
1359+
const fixture = createRuntime();
1360+
const handler = createAdeRpcRequestHandler({ runtime: fixture.runtime, serverVersion: "test" });
1361+
await initialize(handler, { callerId: "agent-1", role: "agent", chatSessionId: "chat-session-1" });
1362+
1363+
const screenshot = await callTool(handler, "screenshot_environment", {
1364+
target: "macos_vm",
1365+
laneId: "lane-1",
1366+
name: "standard VM proof",
1367+
});
1368+
expect(screenshot?.isError).toBeUndefined();
1369+
expect(fixture.runtime.macosVmService.captureScreenshot).toHaveBeenCalledWith({
1370+
laneId: "lane-1",
1371+
windowTitleQuery: null,
1372+
});
1373+
expect(fixture.runtime.computerUseArtifactBrokerService.ingest).toHaveBeenCalledWith(
1374+
expect.objectContaining({
1375+
backend: { name: "macos-vm", toolName: "screenshot_environment" },
1376+
}),
1377+
);
1378+
1379+
const clicked = await callTool(handler, "interact_gui", {
1380+
target: "macos_vm",
1381+
laneId: "lane-1",
1382+
action: "click",
1383+
x: 30,
1384+
y: 40,
1385+
});
1386+
expect(clicked?.isError).toBeUndefined();
1387+
expect(fixture.runtime.macosVmService.click).toHaveBeenCalledWith({
1388+
laneId: "lane-1",
1389+
x: 30,
1390+
y: 40,
1391+
coordinateSpace: undefined,
1392+
windowTitleQuery: null,
1393+
});
1394+
1395+
const typed = await callTool(handler, "interact_gui", {
1396+
target: "macos_vm",
1397+
laneId: "lane-1",
1398+
action: "type",
1399+
text: "hello",
1400+
});
1401+
expect(typed?.isError).toBeUndefined();
1402+
expect(fixture.runtime.macosVmService.typeText).toHaveBeenCalledWith({
1403+
laneId: "lane-1",
1404+
text: "hello",
1405+
windowTitleQuery: null,
1406+
});
1407+
});
1408+
11851409
it("hides ADE spawn and mission-worker tools from standalone chat callers", async () => {
11861410
await withEnv({ ADE_DEFAULT_ROLE: "agent", ADE_CHAT_SESSION_ID: "chat-1" }, async () => {
11871411
const { runtime } = createRuntime();

0 commit comments

Comments
 (0)