Skip to content
Merged
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
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 { createGithubService } from "./services/github/githubService";
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 @@ -728,6 +727,7 @@ app.whenReady().then(async () => {
db,
logger,
projectId,
projectRoot,
laneService,
onEvent: (event) => emitProjectEvent(projectRoot, IPC.lanesRebaseSuggestionsEvent, event)
});
Expand Down Expand Up @@ -796,6 +796,7 @@ app.whenReady().then(async () => {
aiIntegrationService,
projectConfigService,
conflictService,
rebaseSuggestionService,
openExternal: async (url) => {
await shell.openExternal(url);
}
Expand Down Expand Up @@ -845,19 +846,6 @@ app.whenReady().then(async () => {
}
});
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 @@ app.whenReady().then(async () => {
prService,
conflictService,
queueLandingService,
queueRehearsalService,
projectRoot,
missionBudgetService,
humanWorkDigestService,
Expand Down Expand Up @@ -2117,7 +2104,6 @@ app.whenReady().then(async () => {
prPollingService,
computerUseArtifactBrokerService,
queueLandingService,
queueRehearsalService,
jobEngine,
automationService,
automationPlannerService,
Expand Down Expand Up @@ -2210,7 +2196,6 @@ app.whenReady().then(async () => {
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