Skip to content
Draft
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
1 change: 1 addition & 0 deletions apps/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@
"react-hotkeys-hook": "^4.4.4",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^3.0.6",
"react-scan": "^0.5.6",
"reflect-metadata": "^0.2.2",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
Expand Down
27 changes: 27 additions & 0 deletions apps/code/src/main/di/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { WorktreeRepository } from "../db/repositories/worktree-repository";
import { DatabaseService } from "../db/service";
import { ElectronAppLifecycle } from "../platform-adapters/electron-app-lifecycle";
import { ElectronAppMeta } from "../platform-adapters/electron-app-meta";
import { ElectronAppMetrics } from "../platform-adapters/electron-app-metrics";
import { ElectronBundledResources } from "../platform-adapters/electron-bundled-resources";
import { ElectronClipboard } from "../platform-adapters/electron-clipboard";
import { ElectronContextMenu } from "../platform-adapters/electron-context-menu";
import { ElectronDevHostActions } from "../platform-adapters/electron-dev-host-actions";
import { ElectronDialog } from "../platform-adapters/electron-dialog";
import { ElectronFileIcon } from "../platform-adapters/electron-file-icon";
import { ElectronImageProcessor } from "../platform-adapters/electron-image-processor";
Expand All @@ -34,6 +36,11 @@ import { CloudTaskService } from "../services/cloud-task/service";
import { ConnectivityService } from "../services/connectivity/service";
import { ContextMenuService } from "../services/context-menu/service";
import { DeepLinkService } from "../services/deep-link/service";
import { DevActionsService } from "../services/dev-actions/service";
import { DevFlagsService } from "../services/dev-flags/service";
import { DevLogsService } from "../services/dev-logs/service";
import { DevMetricsService } from "../services/dev-metrics/service";
import { DevNetworkService } from "../services/dev-network/service";
import { EnrichmentService } from "../services/enrichment/service";
import { EnvironmentService } from "../services/environment/service";
import { ExternalAppsService } from "../services/external-apps/service";
Expand Down Expand Up @@ -71,6 +78,18 @@ export const container = new Container({
defaultScope: "Singleton",
});

export function getService<T>(token: symbol): T {
return container.get<T>(token);
}

export function tryGetService<T>(token: symbol): T | null {
try {
return container.get<T>(token);
} catch {
return null;
}
}

container.bind(MAIN_TOKENS.UrlLauncher).to(ElectronUrlLauncher);
container.bind(MAIN_TOKENS.StoragePaths).to(ElectronStoragePaths);
container.bind(MAIN_TOKENS.AppMeta).to(ElectronAppMeta);
Expand All @@ -86,6 +105,8 @@ container.bind(MAIN_TOKENS.Notifier).to(ElectronNotifier);
container.bind(MAIN_TOKENS.ContextMenu).to(ElectronContextMenu);
container.bind(MAIN_TOKENS.BundledResources).to(ElectronBundledResources);
container.bind(MAIN_TOKENS.ImageProcessor).to(ElectronImageProcessor);
container.bind(MAIN_TOKENS.AppMetrics).to(ElectronAppMetrics);
container.bind(MAIN_TOKENS.DevHostActions).to(ElectronDevHostActions);

container.bind(MAIN_TOKENS.DatabaseService).to(DatabaseService);
container
Expand Down Expand Up @@ -144,3 +165,9 @@ container.bind(MAIN_TOKENS.WatcherRegistryService).to(WatcherRegistryService);
container.bind(MAIN_TOKENS.WorkspaceService).to(WorkspaceService);

container.bind(MAIN_TOKENS.SettingsStore).toConstantValue(settingsStore);

container.bind(MAIN_TOKENS.DevFlagsService).to(DevFlagsService);
container.bind(MAIN_TOKENS.DevMetricsService).to(DevMetricsService);
container.bind(MAIN_TOKENS.DevNetworkService).to(DevNetworkService);
container.bind(MAIN_TOKENS.DevLogsService).to(DevLogsService);
container.bind(MAIN_TOKENS.DevActionsService).to(DevActionsService);
7 changes: 7 additions & 0 deletions apps/code/src/main/di/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const MAIN_TOKENS = Object.freeze({
ContextMenu: Symbol.for("Platform.ContextMenu"),
BundledResources: Symbol.for("Platform.BundledResources"),
ImageProcessor: Symbol.for("Platform.ImageProcessor"),
AppMetrics: Symbol.for("Platform.AppMetrics"),
DevHostActions: Symbol.for("Platform.DevHostActions"),

// Stores
SettingsStore: Symbol.for("Main.SettingsStore"),
Expand Down Expand Up @@ -77,4 +79,9 @@ export const MAIN_TOKENS = Object.freeze({
ProvisioningService: Symbol.for("Main.ProvisioningService"),
WorkspaceService: Symbol.for("Main.WorkspaceService"),
EnrichmentService: Symbol.for("Main.EnrichmentService"),
DevFlagsService: Symbol.for("Main.DevFlagsService"),
DevMetricsService: Symbol.for("Main.DevMetricsService"),
DevNetworkService: Symbol.for("Main.DevNetworkService"),
DevLogsService: Symbol.for("Main.DevLogsService"),
DevActionsService: Symbol.for("Main.DevActionsService"),
});
3 changes: 3 additions & 0 deletions apps/code/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MAIN_TOKENS } from "./di/tokens";
import { registerMcpSandboxProtocol } from "./protocols/mcp-sandbox";
import type { AppLifecycleService } from "./services/app-lifecycle/service";
import type { AuthService } from "./services/auth/service";
import { initDevToolbar } from "./services/dev-toolbar";
import type { ExternalAppsService } from "./services/external-apps/service";
import type { GitHubIntegrationService } from "./services/github-integration/service";
import type { InboxLinkService } from "./services/inbox-link/service";
Expand Down Expand Up @@ -142,6 +143,8 @@ app.on("child-process-gone", (_event, details) => {
});

async function initializeServices(): Promise<void> {
initDevToolbar(container);

container.get<DatabaseService>(MAIN_TOKENS.DatabaseService);
container.get<OAuthService>(MAIN_TOKENS.OAuthService);
const authService = container.get<AuthService>(MAIN_TOKENS.AuthService);
Expand Down
21 changes: 21 additions & 0 deletions apps/code/src/main/platform-adapters/electron-app-metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type {
AppProcessMetric,
IAppMetrics,
} from "@posthog/platform/app-metrics";
import { app } from "electron";
import { injectable } from "inversify";

@injectable()
export class ElectronAppMetrics implements IAppMetrics {
public getAppMetrics(): AppProcessMetric[] {
return app.getAppMetrics().map((m) => ({
pid: m.pid,
type: m.type,
name: m.name,
cpu: m.cpu ? { percentCPUUsage: m.cpu.percentCPUUsage } : undefined,
memory: m.memory
? { workingSetSize: m.memory.workingSetSize }
: undefined,
}));
}
}
25 changes: 25 additions & 0 deletions apps/code/src/main/platform-adapters/electron-dev-host-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { IDevHostActions } from "@posthog/platform/dev-host-actions";
import { app, BrowserWindow, shell } from "electron";
import { injectable } from "inversify";

@injectable()
export class ElectronDevHostActions implements IDevHostActions {
public async openPath(path: string): Promise<void> {
await shell.openPath(path);
}

public reloadAllWindows(): void {
for (const window of BrowserWindow.getAllWindows()) {
window.webContents.reload();
}
}

public relaunch(): void {
app.relaunch();
app.exit(0);
}

public crash(): void {
process.crash();
}
}
18 changes: 18 additions & 0 deletions apps/code/src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@ import { exposeElectronTRPC } from "@posthog/electron-trpc/main";
import { contextBridge, webUtils } from "electron";
import "electron-log/preload";

const DEV_FLAGS_CLI_PREFIX = "--posthog-code-flags=";

function readDevFlags(): { devMode: boolean } {
const arg = process.argv.find((a) => a.startsWith(DEV_FLAGS_CLI_PREFIX));
if (!arg) return { devMode: false };
try {
const payload = decodeURIComponent(arg.slice(DEV_FLAGS_CLI_PREFIX.length));
const parsed = JSON.parse(payload);
return { devMode: parsed?.devMode === true };
} catch {
return { devMode: false };
}
}

const devFlags = readDevFlags();

contextBridge.exposeInMainWorld("electronUtils", {
getPathForFile: (file: File) => webUtils.getPathForFile(file),
});

contextBridge.exposeInMainWorld("__posthogCodeDevFlags", devFlags);

if (process.argv.includes("--posthog-code-dev")) {
contextBridge.exposeInMainWorld("__posthogCodeTest", {
crash: () => {
Expand Down
43 changes: 43 additions & 0 deletions apps/code/src/main/services/agent/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,49 @@ When creating pull requests, add the following footer at the end of the PR descr
return this.sessions.get(taskRunId);
}

getDebugSnapshot(): {
sessions: Array<{
taskRunId: string;
taskId: string;
repoPath: string;
adapter: string;
model: string | null;
sessionId: string | null;
channel: string;
createdAt: number;
lastActivityAt: number;
promptPending: boolean;
inFlightToolCalls: number;
idleDeadline: number | null;
}>;
pendingPermissions: Array<{
taskRunId: string;
toolCallId: string;
}>;
} {
const sessions = [...this.sessions.values()].map((session) => ({
taskRunId: session.taskRunId,
taskId: session.taskId,
repoPath: session.repoPath,
adapter: session.config.adapter ?? "claude",
model: session.config.model ?? null,
sessionId: session.config.sessionId ?? null,
channel: session.channel,
createdAt: session.createdAt,
lastActivityAt: session.lastActivityAt,
promptPending: session.promptPending,
inFlightToolCalls: session.inFlightMcpToolCalls.size,
idleDeadline: this.idleTimeouts.get(session.taskRunId)?.deadline ?? null,
}));
const pendingPermissions = [...this.pendingPermissions.values()].map(
(perm) => ({
taskRunId: perm.taskRunId,
toolCallId: perm.toolCallId,
}),
);
return { sessions, pendingPermissions };
}

async setSessionConfigOption(
sessionId: string,
configId: string,
Expand Down
22 changes: 22 additions & 0 deletions apps/code/src/main/services/dev-actions/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { z } from "zod";

export const devToastInput = z.object({
variant: z.enum(["info", "error"]),
message: z.string(),
});

export const devToastSchema = z.object({
id: z.number(),
variant: z.enum(["info", "error"]),
message: z.string(),
});

export type DevToast = z.infer<typeof devToastSchema>;

export const DevActionsEvent = {
Toast: "toast",
} as const;

export interface DevActionsEvents {
[DevActionsEvent.Toast]: DevToast;
}
68 changes: 68 additions & 0 deletions apps/code/src/main/services/dev-actions/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { IDevHostActions } from "@posthog/platform/dev-host-actions";
import { inject, injectable } from "inversify";
import { MAIN_TOKENS } from "../../di/tokens";
import { getUserDataDir } from "../../utils/env";
import { getLogFilePath, logger } from "../../utils/logger";
import { TypedEventEmitter } from "../../utils/typed-event-emitter";
import type { DevNetworkService } from "../dev-network/service";
import {
DevActionsEvent,
type DevActionsEvents,
type DevToast,
} from "./schemas";

const log = logger.scope("dev-actions");

@injectable()
export class DevActionsService extends TypedEventEmitter<DevActionsEvents> {
private nextToastId = 1;

constructor(
@inject(MAIN_TOKENS.DevNetworkService)
private readonly network: DevNetworkService,
@inject(MAIN_TOKENS.DevHostActions)
private readonly host: IDevHostActions,
) {
super();
}

async openUserDataDir(): Promise<void> {
await this.host.openPath(getUserDataDir());
}

async openLogFile(): Promise<void> {
await this.host.openPath(getLogFilePath());
}

reloadRenderer(): void {
this.host.reloadAllWindows();
}

restartMain(): void {
log.warn("Restarting main process from dev toolbar");
this.host.relaunch();
}

crashMain(): void {
log.warn("Crashing main process from dev toolbar");
this.host.crash();
}

triggerToast(variant: "info" | "error", message: string): DevToast {
const toast: DevToast = {
id: this.nextToastId++,
variant,
message,
};
this.emit(DevActionsEvent.Toast, toast);
return toast;
}

setOffline(offline: boolean): void {
this.network.setSim({ offline });
}

setSlowDelay(slowDelayMs: number): void {
this.network.setSim({ slowDelayMs });
}
}
19 changes: 19 additions & 0 deletions apps/code/src/main/services/dev-flags/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from "zod";

export const devFlagsSchema = z.object({
devMode: z.boolean(),
});

export type DevFlags = z.infer<typeof devFlagsSchema>;

export const DEFAULT_DEV_FLAGS: DevFlags = {
devMode: false,
};

export const DevFlagsEvent = {
Changed: "changed",
} as const;

export interface DevFlagsEvents {
[DevFlagsEvent.Changed]: DevFlags;
}
Loading
Loading