Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Empty file added .ade/kv.sqlite
Empty file.
19 changes: 2 additions & 17 deletions apps/desktop/src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import { createPrService } from "./services/prs/prService";
import { createPrPollingService } from "./services/prs/prPollingService";
import { createQueueLandingService } from "./services/prs/queueLandingService";
import { createQueueRehearsalService } from "./services/prs/queueRehearsalService";
import { detectDefaultBaseRef, resolveRepoRoot, toProjectInfo, upsertProjectRow } from "./services/projects/projectService";
import { createAdeProjectService } from "./services/projects/adeProjectService";
import { createConfigReloadService } from "./services/projects/configReloadService";
Expand Down Expand Up @@ -495,7 +494,7 @@
const loadPty = () => {
// node-pty is a native dependency; keep the require inside the main process runtime.
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require("node-pty") as typeof import("node-pty");

Check warning on line 497 in apps/desktop/src/main/main.ts

View workflow job for this annotation

GitHub Actions / lint-desktop

`import()` type annotations are forbidden
};

const normalizeProjectRoot = (projectRoot: string) => path.resolve(projectRoot);
Expand Down Expand Up @@ -650,7 +649,7 @@
logger,
broadcastEvent: (ev) => emitProjectEvent(projectRoot, IPC.lanesPortEvent, ev),
persistLeases: (leases) => db.setJson("port_leases", leases),
loadLeases: () => db.getJson<import("../shared/types").PortLease[]>("port_leases") ?? [],

Check warning on line 652 in apps/desktop/src/main/main.ts

View workflow job for this annotation

GitHub Actions / lint-desktop

`import()` type annotations are forbidden
});
portAllocationService.restore();

Expand Down Expand Up @@ -728,6 +727,7 @@
db,
logger,
projectId,
projectRoot,
laneService,
onEvent: (event) => emitProjectEvent(projectRoot, IPC.lanesRebaseSuggestionsEvent, event)
});
Expand Down Expand Up @@ -796,6 +796,7 @@
aiIntegrationService,
projectConfigService,
conflictService,
rebaseSuggestionService,
openExternal: async (url) => {
await shell.openExternal(url);
}
Expand Down Expand Up @@ -845,19 +846,6 @@
}
});
queueLandingService.init();
const queueRehearsalService = createQueueRehearsalService({
db,
logger,
projectId,
prService,
laneService,
conflictService,
emitEvent: (event) => emitProjectEvent(projectRoot, IPC.prsEvent, event),
onStateChanged: (state) => {
aiOrchestratorServiceRef?.onQueueRehearsalStateChanged?.(state);
}
});
queueRehearsalService.init();

const fileService = createFileService({
laneService,
Expand Down Expand Up @@ -1541,7 +1529,6 @@
prService,
conflictService,
queueLandingService,
queueRehearsalService,
projectRoot,
missionBudgetService,
humanWorkDigestService,
Expand Down Expand Up @@ -2117,7 +2104,6 @@
prPollingService,
computerUseArtifactBrokerService,
queueLandingService,
queueRehearsalService,
jobEngine,
automationService,
automationPlannerService,
Expand Down Expand Up @@ -2210,7 +2196,6 @@
prService: null,
prPollingService: null,
queueLandingService: null,
queueRehearsalService: null,
jobEngine: null,
automationService: null,
automationPlannerService: null,
Expand Down
105 changes: 105 additions & 0 deletions apps/desktop/src/main/services/ai/tools/workflowTools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { describe, expect, it, vi } from "vitest";
import { createWorkflowTools } from "./workflowTools";

function makeTools(prServiceOverrides: Record<string, unknown> = {}) {
const prService = {
getChecks: vi.fn(async () => []),
getActionRuns: vi.fn(async () => []),
getReviewThreads: vi.fn(async () => []),
getComments: vi.fn(async () => []),
rerunChecks: vi.fn(async () => undefined),
replyToReviewThread: vi.fn(async () => ({ id: "reply-1", author: "you", authorAvatarUrl: null, body: "Fixed.", url: null, createdAt: null, updatedAt: null })),
resolveReviewThread: vi.fn(async () => undefined),
...prServiceOverrides,
} as any;

const tools = createWorkflowTools({
laneService: {} as any,
prService,
sessionId: "session-1",
laneId: "lane-1",
});

return { prService, tools };
}

describe("createWorkflowTools", () => {
it("refreshes PR issue inventory with actionable review threads and failing checks", async () => {
const { tools } = makeTools({
getChecks: vi.fn(async () => [
{ name: "ci / unit", status: "completed", conclusion: "failure", detailsUrl: "https://example.com/check", startedAt: null, completedAt: null },
]),
getActionRuns: vi.fn(async () => [
{
id: 17,
name: "CI",
status: "completed",
conclusion: "failure",
headSha: "abc123",
htmlUrl: "https://example.com/run/17",
createdAt: "2026-03-23T12:00:00.000Z",
updatedAt: "2026-03-23T12:00:00.000Z",
jobs: [
{
id: 28,
name: "test",
status: "completed",
conclusion: "failure",
startedAt: null,
completedAt: null,
steps: [
{ name: "vitest", status: "completed", conclusion: "failure", number: 1, startedAt: null, completedAt: null },
],
},
],
},
]),
getReviewThreads: vi.fn(async () => [
{
id: "thread-1",
isResolved: false,
isOutdated: false,
path: "src/prs.ts",
line: 18,
originalLine: 18,
startLine: null,
originalStartLine: null,
diffSide: "RIGHT",
url: "https://example.com/thread/1",
createdAt: "2026-03-23T12:00:00.000Z",
updatedAt: "2026-03-23T12:00:00.000Z",
comments: [
{ id: "comment-1", author: "reviewer", authorAvatarUrl: null, body: "Please tighten this logic.", url: null, createdAt: null, updatedAt: null },
],
},
]),
getComments: vi.fn(async () => [
{ id: "issue-1", author: "bot", authorAvatarUrl: null, body: "Heads up", source: "issue", url: null, path: null, line: null, createdAt: null, updatedAt: null },
]),
});

const result = await (tools.prRefreshIssueInventory as any).execute({ prId: "pr-80" });

expect(result.success).toBe(true);
expect(result.summary).toMatchObject({
hasActionableChecks: true,
hasActionableComments: true,
failingCheckCount: 1,
actionableReviewThreadCount: 1,
});
expect(result.reviewThreads).toHaveLength(1);
expect(result.failingWorkflowRuns[0]).toMatchObject({ name: "CI" });
});

it("routes review-thread reply, resolve, and rerun actions through prService", async () => {
const { prService, tools } = makeTools();

await (tools.prRerunFailedChecks as any).execute({ prId: "pr-80" });
await (tools.prReplyToReviewThread as any).execute({ prId: "pr-80", threadId: "thread-1", body: "Fixed." });
await (tools.prResolveReviewThread as any).execute({ prId: "pr-80", threadId: "thread-1" });

expect(prService.rerunChecks).toHaveBeenCalledWith({ prId: "pr-80" });
expect(prService.replyToReviewThread).toHaveBeenCalledWith({ prId: "pr-80", threadId: "thread-1", body: "Fixed." });
expect(prService.resolveReviewThread).toHaveBeenCalledWith({ prId: "pr-80", threadId: "thread-1" });
});
});
Loading
Loading