Skip to content

Commit 720fc68

Browse files
arul28claude
andauthored
Ios Sim Fixes Again (#247)
* ship: checkpoint before automate/finalize iOS simulator stability fixes plus targeted code review/perf fixes: - ios-sim refresh and chat panel updates - diff service hardening with new tests - lane git actions and commit timeline tweaks - file/project icon resolver work - CSP refactored to rendererCsp helper - ADE CLI guidance restructured - Greptile P1 fixes: switchTab same-tab guard, navigate inspect-overlay desync - Copilot fix: ChatBuiltInBrowserPanel default-tab effect dep churn - Perf: cap resume-command scan window in ptyService to prevent forever-running regex on long sessions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ship: finalize gates + automate coverage - Add rendererCsp test for built-in browser frame-src contract - Add ptyService test asserting RESUME_SCAN_WINDOW_MS bounds resume scanning - Type cast on mockReturnValue to satisfy declared type - Lockfile regen sync from npm install during finalize Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ship: iter 1 — address Greptile + CodeRabbit review Greptile P2 + CodeRabbit findings (deduped where overlapping): - selectPoint: emit drawer-open-requested only after the async select resolves - registerIpc: align diff truncation notice with diffService; preserve runGit error context instead of folding all failures into "file missing" - fileService: treat volatile ADE paths as forbidden (not just ignored); surface directory truncation to the renderer - AgentChatPane: reset iosSimulatorDrawerModeRequest after consumption; close other right-side panels on auto-open simulator - LaneDiffPane: keep current diff mounted across same-file refreshes; expose files past the 500-cap - LaneGitActionsPane: expose files past the 300-cap so they remain reachable - FilesPage: do not cache dirty tabs of any size - adeCliGuidance: use fully qualified --socket examples - ChatIosSimulatorPanel, CommitTimeline, ctoStateService, diffService, projectIconResolver, types/files: corollary fixes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d33c23e commit 720fc68

40 files changed

Lines changed: 2034 additions & 370 deletions

apps/ade-cli/package-lock.json

Lines changed: 6 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3012,6 +3012,33 @@ describe("adeRpcServer", () => {
30123012
});
30133013
});
30143014

3015+
it("uses initialized chat session identity when the server process has no ADE_CHAT_SESSION_ID", async () => {
3016+
await withEnv({ ADE_CHAT_SESSION_ID: undefined, ADE_DEFAULT_ROLE: "agent" }, async () => {
3017+
const fixture = createRuntime();
3018+
fixture.runtime.agentChatService.requestChatInput = vi.fn(async () => ({
3019+
decision: "decline",
3020+
answers: {},
3021+
responseText: null,
3022+
}));
3023+
const handler = createAdeRpcRequestHandler({ runtime: fixture.runtime, serverVersion: "test" });
3024+
3025+
await initialize(handler, {
3026+
callerId: "chat-session-identity",
3027+
role: "agent",
3028+
chatSessionId: "chat-session-identity",
3029+
});
3030+
const response = await callTool(handler, "ask_user", {
3031+
title: "Pick a flow",
3032+
body: "Which part should we test first?",
3033+
});
3034+
3035+
expect(response?.isError).toBeUndefined();
3036+
expect(fixture.runtime.agentChatService.requestChatInput).toHaveBeenCalledWith(expect.objectContaining({
3037+
chatSessionId: "chat-session-identity",
3038+
}));
3039+
});
3040+
});
3041+
30153042
it("returns explicit timed_out semantics for standalone ask_user when the user does not answer in time", async () => {
30163043
await withEnv({ ADE_CHAT_SESSION_ID: "chat-session-env" }, async () => {
30173044
const fixture = createRuntime();

apps/ade-cli/src/adeRpcServer.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2889,10 +2889,10 @@ function resolveCallerContext(session?: SessionState): CallerContext {
28892889
return {
28902890
callerId: asOptionalTrimmedString(session.identity.callerId),
28912891
role: session.identity.role ?? envContext.role,
2892-
chatSessionId: envContext.chatSessionId,
2892+
chatSessionId: session.identity.chatSessionId ?? envContext.chatSessionId,
28932893
standaloneChatSession: session.identity.standaloneChatSession,
28942894
missionId: session.identity.missionId ?? envContext.missionId,
2895-
runId: envContext.runId,
2895+
runId: session.identity.runId ?? envContext.runId,
28962896
stepId: session.identity.stepId ?? envContext.stepId,
28972897
attemptId: session.identity.attemptId ?? envContext.attemptId,
28982898
ownerId: session.identity.ownerId ?? envContext.ownerId,
@@ -2995,7 +2995,11 @@ function parseInitializeIdentity(runtime: AdeRuntime, params: unknown): SessionI
29952995
? identityRole
29962996
: null;
29972997
const validRole: SessionIdentity["role"] = envContext.role ?? "external";
2998-
const resolvedRunId = envContext.runId;
2998+
const requestedChatSessionId = asOptionalTrimmedString(identity.chatSessionId);
2999+
const resolvedChatSessionId = envContext.chatSessionId ?? requestedChatSessionId;
3000+
const resolvedRunId = envContext.runId ?? asOptionalTrimmedString(identity.runId);
3001+
const resolvedStepId = envContext.stepId ?? asOptionalTrimmedString(identity.stepId);
3002+
const resolvedAttemptId = envContext.attemptId ?? asOptionalTrimmedString(identity.attemptId);
29993003
const requestedMissionId = asOptionalTrimmedString(identity.missionId);
30003004
const resolvedMissionId =
30013005
envContext.missionId
@@ -3007,21 +3011,21 @@ function parseInitializeIdentity(runtime: AdeRuntime, params: unknown): SessionI
30073011
);
30083012
}
30093013

3010-
const standaloneChatSession = Boolean(envContext.chatSessionId)
3014+
const standaloneChatSession = Boolean(resolvedChatSessionId)
30113015
&& !envContext.missionId
3012-
&& !envContext.runId
3013-
&& !envContext.stepId
3014-
&& !envContext.attemptId;
3016+
&& !resolvedRunId
3017+
&& !resolvedStepId
3018+
&& !resolvedAttemptId;
30153019

30163020
return {
3017-
callerId: asOptionalTrimmedString(identity.callerId) ?? envContext.chatSessionId ?? envContext.attemptId ?? "unknown",
3021+
callerId: asOptionalTrimmedString(identity.callerId) ?? resolvedChatSessionId ?? envContext.attemptId ?? "unknown",
30183022
role: validRole,
3019-
chatSessionId: envContext.chatSessionId,
3023+
chatSessionId: resolvedChatSessionId,
30203024
standaloneChatSession,
30213025
missionId: resolvedMissionId ?? requestedMissionId ?? null,
30223026
runId: resolvedRunId,
3023-
stepId: asOptionalTrimmedString(identity.stepId) ?? envContext.stepId,
3024-
attemptId: asOptionalTrimmedString(identity.attemptId) ?? envContext.attemptId,
3027+
stepId: resolvedStepId,
3028+
attemptId: resolvedAttemptId,
30253029
ownerId: asOptionalTrimmedString(identity.ownerId) ?? envContext.ownerId,
30263030
};
30273031
}

apps/ade-cli/src/cli.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3790,12 +3790,24 @@ export function shouldAttemptDesktopSocketConnection(socketPath: string): boolea
37903790
}
37913791

37923792
async function initializeConnection(connection: CliConnection, options: GlobalOptions): Promise<void> {
3793+
const envChatSessionId = asString(process.env.ADE_CHAT_SESSION_ID);
3794+
const envMissionId = asString(process.env.ADE_MISSION_ID);
3795+
const envRunId = asString(process.env.ADE_RUN_ID);
3796+
const envStepId = asString(process.env.ADE_STEP_ID);
3797+
const envAttemptId = asString(process.env.ADE_ATTEMPT_ID);
3798+
const envOwnerId = asString(process.env.ADE_OWNER_ID);
37933799
await connection.request("ade/initialize", {
37943800
protocolVersion: PROTOCOL_VERSION,
37953801
clientInfo: { name: "ade-cli", version: VERSION },
37963802
identity: {
3797-
callerId: "ade-cli",
3803+
callerId: envChatSessionId ?? envAttemptId ?? "ade-cli",
37983804
role: options.role,
3805+
...(envChatSessionId ? { chatSessionId: envChatSessionId } : {}),
3806+
...(envMissionId ? { missionId: envMissionId } : {}),
3807+
...(envRunId ? { runId: envRunId } : {}),
3808+
...(envStepId ? { stepId: envStepId } : {}),
3809+
...(envAttemptId ? { attemptId: envAttemptId } : {}),
3810+
...(envOwnerId ? { ownerId: envOwnerId } : {}),
37993811
computerUsePolicy: {
38003812
mode: "auto",
38013813
allowLocalFallback: options.role !== "external",

apps/desktop/src/main/main.ts

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import type { PortLease, ProjectInfo, SyncMobileProjectSummary, SyncProjectConne
6161
import type { AutomationTriggerType } from "../shared/types/config";
6262
import type { AutomationTriggerLinearIssueContext } from "../shared/types/automations";
6363
import type { LinearIngressEventRecord } from "../shared/types/linearSync";
64+
import type { IosSimulatorDrawerMode } from "../shared/types/iosSimulator";
6465
import type { AppContext } from "./services/ipc/registerIpc";
6566
import fs from "node:fs";
6667
import net from "node:net";
@@ -118,6 +119,7 @@ import { createWorkerAdapterRuntimeService } from "./services/cto/workerAdapterR
118119
import { createWorkerTaskSessionService } from "./services/cto/workerTaskSessionService";
119120
import { createWorkerHeartbeatService } from "./services/cto/workerHeartbeatService";
120121
import { createLinearCredentialService } from "./services/cto/linearCredentialService";
122+
import { buildRendererCspPolicy } from "./rendererCsp";
121123
import { createLinearClient } from "./services/cto/linearClient";
122124
import { createLinearIssueTracker } from "./services/cto/linearIssueTracker";
123125
import { createLinearTemplateService } from "./services/cto/linearTemplateService";
@@ -468,26 +470,7 @@ async function createWindow(args: {
468470

469471
// Set CSP dynamically so it works with both http:// (dev) and file:// (production).
470472
const isDevMode = !!process.env.VITE_DEV_SERVER_URL;
471-
const cspSources = isDevMode
472-
? "'self' http://localhost:* http://127.0.0.1:*"
473-
: "'self' file: app:";
474-
const cspWsSources = isDevMode ? " ws://localhost:* ws://127.0.0.1:*" : "";
475-
const cspLocalSources = " http://localhost:* http://127.0.0.1:*";
476-
const cspImageSources = `${cspSources}${cspLocalSources} https://avatars.githubusercontent.com https://*.githubusercontent.com https://github.githubassets.com https://opengraph.githubassets.com https://github.com https://vercel.com https://*.vercel.com https://img.shields.io https://*.s3.amazonaws.com`;
477-
const cspPolicy = [
478-
`default-src ${cspSources}`,
479-
`base-uri 'self'`,
480-
`form-action 'self'`,
481-
`object-src 'none'`,
482-
`frame-src ${cspSources}${cspLocalSources} about:`,
483-
`script-src ${cspSources} 'unsafe-inline'`,
484-
`style-src ${cspSources} 'unsafe-inline'`,
485-
`img-src ${cspImageSources} ade-artifact: data: blob:`,
486-
`media-src ${cspSources}${cspLocalSources} ade-artifact: blob: data:`,
487-
`font-src ${cspSources} data:`,
488-
`connect-src ${cspSources}${cspWsSources} https:`,
489-
`worker-src 'self' blob:`,
490-
].join("; ");
473+
const cspPolicy = buildRendererCspPolicy(isDevMode);
491474

492475
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
493476
callback({
@@ -2767,6 +2750,94 @@ app.whenReady().then(async () => {
27672750
onEvent: (payload) =>
27682751
emitProjectEvent(projectRoot, IPC.iosSimulatorEvent, payload),
27692752
});
2753+
const iosSimulatorDrawerActionModes: Partial<Record<string, IosSimulatorDrawerMode>> = {
2754+
inspectPoint: "inspect",
2755+
launch: "interact",
2756+
openPreviewWorkspace: "preview",
2757+
renderPreview: "preview",
2758+
selectPoint: "inspect",
2759+
startStream: "interact",
2760+
tap: "interact",
2761+
typeText: "interact",
2762+
drag: "interact",
2763+
swipe: "interact",
2764+
};
2765+
const requestIosSimulatorDrawerOpen = (
2766+
action: keyof typeof iosSimulatorDrawerActionModes,
2767+
rawArgs: unknown,
2768+
result?: unknown,
2769+
): void => {
2770+
const mode = iosSimulatorDrawerActionModes[action];
2771+
if (!mode) return;
2772+
const argRecord = rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)
2773+
? rawArgs as Record<string, unknown>
2774+
: null;
2775+
const resultRecord = result && typeof result === "object" && !Array.isArray(result)
2776+
? result as Record<string, unknown>
2777+
: null;
2778+
const chatSessionId = readString(argRecord, "chatSessionId") ?? readString(resultRecord, "chatSessionId") ?? null;
2779+
const laneId = readString(argRecord, "laneId") ?? readString(resultRecord, "laneId") ?? null;
2780+
emitProjectEvent(projectRoot, IPC.iosSimulatorEvent, {
2781+
type: "drawer-open-requested",
2782+
action,
2783+
mode,
2784+
chatSessionId,
2785+
laneId,
2786+
});
2787+
};
2788+
const iosSimulatorRpcService = {
2789+
...iosSimulatorService,
2790+
inspectPoint: async (arg: Parameters<typeof iosSimulatorService.inspectPoint>[0]) => {
2791+
const result = await iosSimulatorService.inspectPoint(arg);
2792+
requestIosSimulatorDrawerOpen("inspectPoint", arg, result);
2793+
return result;
2794+
},
2795+
launch: async (arg?: Parameters<typeof iosSimulatorService.launch>[0]) => {
2796+
const result = await iosSimulatorService.launch(arg);
2797+
requestIosSimulatorDrawerOpen("launch", arg, result);
2798+
return result;
2799+
},
2800+
openPreviewWorkspace: async (arg?: Parameters<typeof iosSimulatorService.openPreviewWorkspace>[0]) => {
2801+
const result = await iosSimulatorService.openPreviewWorkspace(arg);
2802+
requestIosSimulatorDrawerOpen("openPreviewWorkspace", arg, result);
2803+
return result;
2804+
},
2805+
renderPreview: async (arg: Parameters<typeof iosSimulatorService.renderPreview>[0]) => {
2806+
const result = await iosSimulatorService.renderPreview(arg);
2807+
requestIosSimulatorDrawerOpen("renderPreview", arg, result);
2808+
return result;
2809+
},
2810+
selectPoint: async (arg: Parameters<typeof iosSimulatorService.selectPoint>[0]) => {
2811+
const result = await iosSimulatorService.selectPoint(arg);
2812+
requestIosSimulatorDrawerOpen("selectPoint", arg, result);
2813+
return result;
2814+
},
2815+
startStream: async (arg?: Parameters<typeof iosSimulatorService.startStream>[0]) => {
2816+
const result = await iosSimulatorService.startStream(arg);
2817+
requestIosSimulatorDrawerOpen("startStream", arg, result);
2818+
return result;
2819+
},
2820+
tap: async (arg: Parameters<typeof iosSimulatorService.tap>[0]) => {
2821+
const result = await iosSimulatorService.tap(arg);
2822+
requestIosSimulatorDrawerOpen("tap", arg, result);
2823+
return result;
2824+
},
2825+
typeText: async (arg: Parameters<typeof iosSimulatorService.typeText>[0]) => {
2826+
const result = await iosSimulatorService.typeText(arg);
2827+
requestIosSimulatorDrawerOpen("typeText", arg, result);
2828+
return result;
2829+
},
2830+
drag: async (arg: Parameters<typeof iosSimulatorService.drag>[0]) => {
2831+
const result = await iosSimulatorService.drag(arg);
2832+
requestIosSimulatorDrawerOpen("drag", arg, result);
2833+
return result;
2834+
},
2835+
swipe: async (arg: Parameters<typeof iosSimulatorService.swipe>[0]) => {
2836+
const result = await iosSimulatorService.swipe(arg);
2837+
requestIosSimulatorDrawerOpen("swipe", arg, result);
2838+
return result;
2839+
},
2840+
};
27702841
const appControlService = createAppControlService({
27712842
projectRoot,
27722843
logger,
@@ -3470,7 +3541,7 @@ app.whenReady().then(async () => {
34703541
automationService,
34713542
automationPlannerService,
34723543
computerUseArtifactBrokerService,
3473-
iosSimulatorService,
3544+
iosSimulatorService: iosSimulatorRpcService,
34743545
appControlService,
34753546
builtInBrowserService,
34763547
orchestratorService,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, expect, it } from "vitest";
2+
import { buildRendererCspPolicy } from "./rendererCsp";
3+
4+
describe("buildRendererCspPolicy", () => {
5+
it("allows packaged renderer fetches to local simulator stream URLs", () => {
6+
const policy = buildRendererCspPolicy(false);
7+
8+
expect(policy).toContain("connect-src 'self' file: app: http://localhost:* http://127.0.0.1:* https:");
9+
});
10+
11+
it("keeps dev websocket sources for Vite while allowing local fetches", () => {
12+
const policy = buildRendererCspPolicy(true);
13+
14+
expect(policy).toContain("connect-src 'self' http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* https:");
15+
});
16+
17+
it("frames built-in browser content from local servers and about:blank in packaged builds", () => {
18+
const policy = buildRendererCspPolicy(false);
19+
20+
expect(policy).toContain("frame-src 'self' file: app: http://localhost:* http://127.0.0.1:* about:");
21+
});
22+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export function buildRendererCspPolicy(isDevMode: boolean): string {
2+
const cspSources = isDevMode
3+
? "'self' http://localhost:* http://127.0.0.1:*"
4+
: "'self' file: app:";
5+
const cspWsSources = isDevMode ? " ws://localhost:* ws://127.0.0.1:*" : "";
6+
const cspLocalSources = " http://localhost:* http://127.0.0.1:*";
7+
const cspConnectLocalSources = isDevMode ? "" : cspLocalSources;
8+
const cspImageSources = `${cspSources}${cspLocalSources} https://avatars.githubusercontent.com https://*.githubusercontent.com https://github.githubassets.com https://opengraph.githubassets.com https://github.com https://vercel.com https://*.vercel.com https://img.shields.io https://*.s3.amazonaws.com`;
9+
return [
10+
`default-src ${cspSources}`,
11+
`base-uri 'self'`,
12+
`form-action 'self'`,
13+
`object-src 'none'`,
14+
`frame-src ${cspSources}${cspLocalSources} about:`,
15+
`script-src ${cspSources} 'unsafe-inline'`,
16+
`style-src ${cspSources} 'unsafe-inline'`,
17+
`img-src ${cspImageSources} ade-artifact: data: blob:`,
18+
`media-src ${cspSources}${cspLocalSources} ade-artifact: blob: data:`,
19+
`font-src ${cspSources} data:`,
20+
`connect-src ${cspSources}${cspConnectLocalSources}${cspWsSources} https:`,
21+
`worker-src 'self' blob:`,
22+
].join("; ");
23+
}

0 commit comments

Comments
 (0)