Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
430 changes: 428 additions & 2 deletions apps/desktop/src/main/services/chat/agentChatService.test.ts

Large diffs are not rendered by default.

541 changes: 436 additions & 105 deletions apps/desktop/src/main/services/chat/agentChatService.ts

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions apps/desktop/src/main/services/context/contextDocBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,7 @@ export async function runContextDocGeneration(
const lastRunRaw = deps.db.getJson<PersistedContextDocRun>(CONTEXT_DOC_LAST_RUN_KEY);
const lastGeneratedAt = typeof lastRunRaw?.generatedAt === "string" ? lastRunRaw.generatedAt : null;
const persistedDocResults = readPersistedDocResults(lastRunRaw);
const generationStartedAt = nowIso();
const existingPrdFile = readContextDocFile(deps.projectRoot, ADE_DOC_PRD_REL);
const existingArchFile = readContextDocFile(deps.projectRoot, ADE_DOC_ARCH_REL);
const bundle = await buildHybridSourceBundle(deps.projectRoot, lastGeneratedAt);
Expand Down Expand Up @@ -1179,9 +1180,8 @@ export async function runContextDocGeneration(
});
}

const generatedAt = nowIso();
deps.db.setJson(CONTEXT_DOC_LAST_RUN_KEY, {
generatedAt,
generatedAt: generationStartedAt,
provider,
trigger,
modelId,
Expand All @@ -1208,7 +1208,7 @@ export async function runContextDocGeneration(

return {
provider,
generatedAt,
generatedAt: generationStartedAt,
prdPath: prdWrite.writtenPath,
architecturePath: archWrite.writtenPath,
usedFallbackPath: prdWrite.usedFallback || archWrite.usedFallback,
Expand Down
10 changes: 8 additions & 2 deletions apps/desktop/src/main/services/ipc/registerIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3596,7 +3596,10 @@ export function registerIpc({
if (typeof record.sessionId !== "string" || !record.sessionId.trim()) {
throw new Error("Agent chat cancel steer sessionId must be a non-empty string");
}
return { sessionId: record.sessionId };
if (typeof record.steerId !== "string" || !record.steerId.trim()) {
throw new Error("Agent chat cancel steer steerId must be a non-empty string");
}
return { sessionId: record.sessionId.trim(), steerId: record.steerId.trim() };
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const parseAgentChatEditSteerArgs = (
Expand All @@ -3606,10 +3609,13 @@ export function registerIpc({
if (typeof record.sessionId !== "string" || !record.sessionId.trim()) {
throw new Error("Agent chat edit steer sessionId must be a non-empty string");
}
if (typeof record.steerId !== "string" || !record.steerId.trim()) {
throw new Error("Agent chat edit steer steerId must be a non-empty string");
}
if (typeof record.text !== "string") {
throw new Error("Agent chat edit steer text must be a string");
}
return { sessionId: record.sessionId, text: record.text };
return { sessionId: record.sessionId.trim(), steerId: record.steerId.trim(), text: record.text };
};

ipcMain.handle(IPC.lanesOAuthGetStatus, async () => {
Expand Down
183 changes: 183 additions & 0 deletions apps/desktop/src/main/services/lanes/laneTemplateService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,187 @@ describe("laneTemplateService", () => {
});
});
});

// ---------------------------------------------------------------
// 4. Setup script resolution tests
// ---------------------------------------------------------------

describe("resolveSetupScript", () => {
function makeService() {
const snapshot = makeSnapshot();
const configService = makeProjectConfigService(snapshot);
return createLaneTemplateService({
projectConfigService: configService,
logger,
});
}

it("returns null when template has no setupScript", () => {
const service = makeService();
const template = makeTemplate({ id: "tpl-no-script", name: "No Script" });

const result = service.resolveSetupScript(template);

expect(result).toBeNull();
});

it("returns null when setupScript has empty commands and no scriptPath", () => {
const service = makeService();
const template = makeTemplate({
id: "tpl-empty-script",
name: "Empty Script",
setupScript: { commands: [] },
});

const result = service.resolveSetupScript(template);

expect(result).toBeNull();
});

it("uses unixCommands on non-Windows", () => {
const service = makeService();
const template = makeTemplate({
id: "tpl-unix",
name: "Unix Commands",
setupScript: {
commands: ["generic-cmd"],
unixCommands: ["bash setup.sh"],
windowsCommands: ["powershell setup.ps1"],
},
});

const result = service.resolveSetupScript(template);

expect(result).not.toBeNull();
expect(result!.commands).toEqual(["bash setup.sh"]);
});

it("falls back to generic commands when no unixCommands provided", () => {
const service = makeService();
const template = makeTemplate({
id: "tpl-generic",
name: "Generic Commands",
setupScript: {
commands: ["npm run setup"],
},
});

const result = service.resolveSetupScript(template);

expect(result).not.toBeNull();
expect(result!.commands).toEqual(["npm run setup"]);
});

it("uses unixScriptPath on non-Windows, falls back to generic scriptPath", () => {
const service = makeService();

const templateWithUnix = makeTemplate({
id: "tpl-unix-path",
name: "Unix Script Path",
setupScript: {
scriptPath: "setup.sh",
unixScriptPath: "scripts/unix-setup.sh",
windowsScriptPath: "scripts/win-setup.ps1",
},
});

const resultWithUnix = service.resolveSetupScript(templateWithUnix);
expect(resultWithUnix).not.toBeNull();
expect(resultWithUnix!.scriptPath).toBe("scripts/unix-setup.sh");

const templateFallback = makeTemplate({
id: "tpl-fallback-path",
name: "Fallback Script Path",
setupScript: {
scriptPath: "setup.sh",
},
});

const resultFallback = service.resolveSetupScript(templateFallback);
expect(resultFallback).not.toBeNull();
expect(resultFallback!.scriptPath).toBe("setup.sh");
});

it("defaults injectPrimaryPath to false when not set", () => {
const service = makeService();
const template = makeTemplate({
id: "tpl-no-inject",
name: "No Inject",
setupScript: {
commands: ["echo hello"],
},
});

const result = service.resolveSetupScript(template);

expect(result).not.toBeNull();
expect(result!.injectPrimaryPath).toBe(false);
});

it("returns injectPrimaryPath true when explicitly set", () => {
const service = makeService();
const template = makeTemplate({
id: "tpl-inject",
name: "With Inject",
setupScript: {
commands: ["echo hello"],
injectPrimaryPath: true,
},
});

const result = service.resolveSetupScript(template);

expect(result).not.toBeNull();
expect(result!.injectPrimaryPath).toBe(true);
});

it("returns commands and scriptPath together", () => {
const service = makeService();
const template = makeTemplate({
id: "tpl-both",
name: "Both",
setupScript: {
commands: ["npm install", "npm run build"],
scriptPath: "scripts/post-setup.sh",
injectPrimaryPath: true,
},
});

const result = service.resolveSetupScript(template);

expect(result).not.toBeNull();
expect(result!.commands).toEqual(["npm install", "npm run build"]);
expect(result!.scriptPath).toBe("scripts/post-setup.sh");
expect(result!.injectPrimaryPath).toBe(true);
});

it("uses windowsCommands and windowsScriptPath on win32", () => {
const originalPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "win32", writable: true });

try {
const service = makeService();
const template = makeTemplate({
id: "tpl-win",
name: "Windows",
setupScript: {
commands: ["generic-cmd"],
unixCommands: ["bash setup.sh"],
windowsCommands: ["powershell setup.ps1"],
scriptPath: "setup.sh",
unixScriptPath: "scripts/unix-setup.sh",
windowsScriptPath: "scripts/win-setup.ps1",
},
});

const result = service.resolveSetupScript(template);

expect(result).not.toBeNull();
expect(result!.commands).toEqual(["powershell setup.ps1"]);
expect(result!.scriptPath).toBe("scripts/win-setup.ps1");
} finally {
Object.defineProperty(process, "platform", { value: originalPlatform, writable: true });
}
});
});
});
35 changes: 35 additions & 0 deletions apps/desktop/src/main/services/lanes/laneTemplateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { NO_DEFAULT_LANE_TEMPLATE } from "../../../shared/types";
import type { Logger } from "../logging/logger";
import type { createProjectConfigService } from "../config/projectConfigService";

type ResolvedSetupScript = {
commands: string[];
scriptPath?: string;
injectPrimaryPath: boolean;
};

export function createLaneTemplateService({
projectConfigService,
logger,
Expand Down Expand Up @@ -58,6 +64,34 @@ export function createLaneTemplateService({
};
}

/**
* Resolves the platform-appropriate setup script commands from a template.
* Returns null if no setup script is configured.
*/
function resolveSetupScript(template: LaneTemplate): ResolvedSetupScript | null {
const cfg = template.setupScript;
if (!cfg) return null;

const isWindows = process.platform === "win32";

// Platform-specific commands take precedence
const commands = isWindows
? (cfg.windowsCommands ?? cfg.commands ?? [])
: (cfg.unixCommands ?? cfg.commands ?? []);

const scriptPath = isWindows
? (cfg.windowsScriptPath ?? cfg.scriptPath)
: (cfg.unixScriptPath ?? cfg.scriptPath);

if (commands.length === 0 && !scriptPath) return null;

return {
commands,
scriptPath,
injectPrimaryPath: cfg.injectPrimaryPath ?? false,
};
}

function saveTemplate(template: LaneTemplate): void {
const snapshot = projectConfigService.get();
const existing = [...(snapshot.local.laneTemplates ?? [])];
Expand Down Expand Up @@ -97,6 +131,7 @@ export function createLaneTemplateService({
getDefaultTemplateId,
setDefaultTemplateId,
resolveTemplateAsEnvInit,
resolveSetupScript,
saveTemplate,
deleteTemplate,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2926,6 +2926,7 @@ export function createMissionService({
...(args.recoveryLoop ? { recoveryLoop: args.recoveryLoop } : {}),
...(executionPolicyArg?.integrationPr ? { integrationPr: executionPolicyArg.integrationPr } : {}),
...(executionPolicyArg?.teamRuntime ? { teamRuntime: executionPolicyArg.teamRuntime } : {}),
prStrategy: executionPolicyArg?.prStrategy ?? { kind: "manual" },
finalizationPolicyKind: "result_lane",
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6954,8 +6954,10 @@ Check all worker statuses and continue managing the mission from here. Read work
}

startupStage = "run_activate";
const activatedRun = orchestratorService.activateRun(startedRunId);
// Persist the mission lane before activation so that any listeners
// of the run_activated event can already resolve the lane ID.
persistMissionLaneIdForRun(startedRunId, missionLaneId);
const activatedRun = orchestratorService.activateRun(startedRunId);
transitionMissionStatus(missionId, "in_progress");
if (initialPhase?.phaseKey.trim().toLowerCase() !== "planning") {
emitOrchestratorMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5961,7 +5961,7 @@ Format: Lead with the concrete rule or fact, then brief context for WHY. One act
if (!phaseCanLoop && policy.maxQuestions != null && priorInterventions.length >= policy.maxQuestions) {
return {
ok: false as const,
error: `This Planning phase already reached its Ask Questions limit (${policy.maxQuestions}). Continue with the best grounded assumptions you can.`,
error: `This phase already reached its Ask Questions limit (${policy.maxQuestions}). Continue with the best grounded assumptions you can.`,
};
}
const firstQuestion = questions[0].question.trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export function createIssueInventoryService(deps: { db: AdeDb }) {
};
}


function upsertItem(
prId: string,
externalId: string,
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/main/services/prs/prIssueResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ export function buildPrIssueResolutionPrompt(args: IssueResolutionPromptArgs): s
? formatIssueCommentsDetailed(args.issueComments)
: formatIssueCommentsSummary(args.issueComments);


promptSections.push(
"",
"Current failing checks",
Expand Down
Loading