Skip to content

Commit 8c983b2

Browse files
committed
Merge branch 'main' of github.com:PostHog/Array into fix/minor-issues
2 parents bd755be + f0d2800 commit 8c983b2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+494
-208
lines changed

apps/array/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,11 @@
116116
"@xterm/addon-web-links": "^0.11.0",
117117
"@xterm/xterm": "^5.5.0",
118118
"ai": "^5.0.75",
119+
"chokidar": "^5.0.0",
119120
"cmdk": "^1.1.1",
120121
"date-fns": "^3.3.1",
122+
"electron-log": "^5.4.3",
123+
"electron-store": "^11.0.0",
121124
"idb-keyval": "^6.2.2",
122125
"node-addon-api": "^8.5.0",
123126
"node-pty": "1.1.0-beta39",
@@ -133,7 +136,6 @@
133136
"sonner": "^2.0.7",
134137
"uuid": "^9.0.1",
135138
"zod": "^4.1.12",
136-
"zustand": "^4.5.0",
137-
"electron-store": "^11.0.0"
139+
"zustand": "^4.5.0"
138140
}
139141
}

apps/array/src/api/generated.ts

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19467,30 +19467,3 @@ export class ApiClient {
1946719467
export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
1946819468
return new ApiClient(fetcher).setBaseUrl(baseUrl ?? "");
1946919469
}
19470-
19471-
/**
19472-
Example usage:
19473-
const api = createApiClient((method, url, params) =>
19474-
fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
19475-
);
19476-
api.get("/users").then((users) => console.log(users));
19477-
api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
19478-
api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
19479-
19480-
// With error handling
19481-
const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true });
19482-
if (result.ok) {
19483-
// Access data directly
19484-
const user = result.data;
19485-
console.log(user);
19486-
19487-
// Or use the json() method for compatibility
19488-
const userFromJson = await result.json();
19489-
console.log(userFromJson);
19490-
} else {
19491-
const error = result.data;
19492-
console.error(`Error ${result.status}:`, error);
19493-
}
19494-
*/
19495-
19496-
// </ApiClient>

apps/array/src/api/posthogClient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { logger } from "@renderer/lib/logger";
12
import type { LogEntry, RepositoryConfig, Task, TaskRun } from "@shared/types";
23
import { buildApiFetcher } from "./fetcher";
34
import { createApiClient, type Schemas } from "./generated";
45

6+
const log = logger.scope("posthog-client");
7+
58
export class PostHogAPIClient {
69
private api: ReturnType<typeof createApiClient>;
710
private _teamId: number | null = null;
@@ -195,7 +198,7 @@ export class PostHogAPIClient {
195198
const response = await fetch(logUrl);
196199

197200
if (!response.ok) {
198-
console.warn(
201+
log.warn(
199202
`Failed to fetch logs: ${response.status} ${response.statusText}`,
200203
);
201204
return [];
@@ -211,7 +214,7 @@ export class PostHogAPIClient {
211214
.split("\n")
212215
.map((line) => JSON.parse(line) as LogEntry);
213216
} catch (err) {
214-
console.warn("Failed to fetch task logs from latest run", err);
217+
log.warn("Failed to fetch task logs from latest run", err);
215218
return [];
216219
}
217220
}

apps/array/src/main/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ import {
1010
type MenuItemConstructorOptions,
1111
shell,
1212
} from "electron";
13+
import "./lib/logger";
1314
import { ANALYTICS_EVENTS } from "../types/analytics.js";
1415
import { registerAgentIpc, type TaskController } from "./services/agent.js";
15-
import { registerFoldersIpc } from "./services/folders.js";
16-
import { registerWorktreeIpc } from "./services/worktree.js";
17-
import "./services/index.js";
16+
import { setupAgentHotReload } from "./services/dev-reload.js";
1817
import { registerFileWatcherIpc } from "./services/fileWatcher.js";
18+
import { registerFoldersIpc } from "./services/folders.js";
1919
import { registerFsIpc } from "./services/fs.js";
2020
import { registerGitIpc } from "./services/git.js";
21+
import "./services/index.js";
2122
import { registerOAuthHandlers } from "./services/oauth.js";
2223
import { registerOsIpc } from "./services/os.js";
2324
import { registerPosthogIpc } from "./services/posthog.js";
@@ -28,6 +29,7 @@ import {
2829
} from "./services/posthog-analytics.js";
2930
import { registerShellIpc } from "./services/shell.js";
3031
import { registerAutoUpdater } from "./services/updates.js";
32+
import { registerWorktreeIpc } from "./services/worktree.js";
3133

3234
const __filename = fileURLToPath(import.meta.url);
3335
const __dirname = path.dirname(__filename);
@@ -184,6 +186,11 @@ app.whenReady().then(() => {
184186
// Initialize PostHog analytics
185187
initializePostHog();
186188
trackAppEvent(ANALYTICS_EVENTS.APP_STARTED);
189+
190+
// Dev mode: Watch agent package and restart via mprocs
191+
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
192+
setupAgentHotReload();
193+
}
187194
});
188195

189196
app.on("window-all-closed", async () => {

apps/array/src/main/lib/logger.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import log from "electron-log/main";
2+
3+
// Initialize IPC transport to forward main process logs to renderer dev tools
4+
log.initialize();
5+
6+
log.transports.file.level = "info";
7+
log.transports.console.level = "info";
8+
9+
export const logger = {
10+
info: (message: string, ...args: unknown[]) => log.info(message, ...args),
11+
warn: (message: string, ...args: unknown[]) => log.warn(message, ...args),
12+
error: (message: string, ...args: unknown[]) => log.error(message, ...args),
13+
debug: (message: string, ...args: unknown[]) => log.debug(message, ...args),
14+
15+
scope: (name: string) => {
16+
const scoped = log.scope(name);
17+
return {
18+
info: (message: string, ...args: unknown[]) =>
19+
scoped.info(message, ...args),
20+
warn: (message: string, ...args: unknown[]) =>
21+
scoped.warn(message, ...args),
22+
error: (message: string, ...args: unknown[]) =>
23+
scoped.error(message, ...args),
24+
debug: (message: string, ...args: unknown[]) =>
25+
scoped.debug(message, ...args),
26+
};
27+
},
28+
};
29+
30+
export type Logger = typeof logger;
31+
export type ScopedLogger = ReturnType<typeof logger.scope>;

apps/array/src/main/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
TabContextMenuResult,
77
TaskContextMenuResult,
88
} from "./services/contextMenu.types.js";
9+
import "electron-log/preload";
910

1011
interface MessageBoxOptions {
1112
type?: "none" | "info" | "error" | "question" | "warning";

apps/array/src/main/services/agent.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,25 @@ import { randomUUID } from "node:crypto";
22
import { mkdirSync, rmSync, symlinkSync } from "node:fs";
33
import { tmpdir } from "node:os";
44
import { join } from "node:path";
5-
import { Agent, PermissionMode } from "@posthog/agent";
5+
import { Agent, type OnLogCallback, PermissionMode } from "@posthog/agent";
66
import {
77
app,
88
type BrowserWindow,
99
type IpcMainInvokeEvent,
1010
ipcMain,
1111
} from "electron";
12+
import { logger } from "../lib/logger";
13+
14+
const log = logger.scope("agent");
15+
16+
const onAgentLog: OnLogCallback = (level, scope, message, data) => {
17+
const scopedLog = logger.scope(scope);
18+
if (data !== undefined) {
19+
scopedLog[level](message, data);
20+
} else {
21+
scopedLog[level](message);
22+
}
23+
};
1224

1325
interface AgentStartParams {
1426
taskId: string;
@@ -96,12 +108,9 @@ export function registerAgentIpc(
96108
process.env.CLAUDE_CONFIG_DIR || join(app.getPath("home"), ".claude");
97109
const statsigPath = join(claudeConfigDir, "statsig");
98110
rmSync(statsigPath, { recursive: true, force: true });
99-
console.log(
100-
"[agent] Cleared statsig cache to work around input_examples bug",
101-
);
111+
log.info("Cleared statsig cache to work around input_examples bug");
102112
} catch (error) {
103-
// Ignore errors if the folder doesn't exist
104-
console.warn("[agent] Could not clear statsig cache:", error);
113+
log.warn("Could not clear statsig cache:", error);
105114
}
106115

107116
const taskId = randomUUID();
@@ -123,7 +132,7 @@ export function registerAgentIpc(
123132
if (stderrBuffer.length > 50) {
124133
stderrBuffer.shift();
125134
}
126-
console.error(`[agent][claude-stderr] ${text}`);
135+
log.error(`[claude-stderr] ${text}`);
127136
emitToRenderer({
128137
type: "status",
129138
ts: Date.now(),
@@ -146,6 +155,7 @@ export function registerAgentIpc(
146155
posthogApiUrl: apiHost,
147156
posthogProjectId: projectId,
148157
debug: true,
158+
onLog: onAgentLog,
149159
});
150160

151161
const controllerEntry: TaskController = {
@@ -181,7 +191,7 @@ export function registerAgentIpc(
181191
});
182192
}
183193
} catch (err) {
184-
console.warn("[agent] failed to fetch task progress", err);
194+
log.warn("Failed to fetch task progress", err);
185195
}
186196
};
187197

@@ -214,7 +224,7 @@ export function registerAgentIpc(
214224
} catch {}
215225
symlinkSync(process.execPath, nodeSymlinkPath);
216226
} catch (err) {
217-
console.warn("[agent] Failed to setup mock node environment", err);
227+
log.warn("Failed to setup mock node environment", err);
218228
}
219229

220230
const newPath = `${mockNodeDir}:${process.env.PATH || ""}`;
@@ -259,7 +269,7 @@ export function registerAgentIpc(
259269

260270
emitToRenderer({ type: "done", success: true, ts: Date.now() });
261271
} catch (err) {
262-
console.error("[agent] task execution failed", err);
272+
log.error("Task execution failed", err);
263273
let errorMessage = err instanceof Error ? err.message : String(err);
264274
const cause =
265275
err instanceof Error && "cause" in err && err.cause
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import path from "node:path";
2+
import { type FSWatcher, watch } from "chokidar";
3+
import { app } from "electron";
4+
import { logger } from "../lib/logger";
5+
6+
const log = logger.scope("dev-reload");
7+
8+
let watcher: FSWatcher | null = null;
9+
10+
export function setupAgentHotReload(): void {
11+
if (watcher) return;
12+
13+
const monorepoRoot = path.resolve(app.getAppPath(), "../..");
14+
const agentDistPath = path.join(monorepoRoot, "packages/agent/dist");
15+
16+
log.info(`Watching agent package: ${agentDistPath}`);
17+
18+
let debounceTimeout: NodeJS.Timeout | null = null;
19+
20+
watcher = watch(agentDistPath, {
21+
ignoreInitial: true,
22+
ignored: /node_modules/,
23+
});
24+
25+
watcher.on("change", (filePath) => {
26+
if (debounceTimeout) clearTimeout(debounceTimeout);
27+
debounceTimeout = setTimeout(() => {
28+
log.info(`Agent rebuilt: ${path.basename(filePath)}`);
29+
log.info("Exiting for mprocs to restart...");
30+
process.exit(0);
31+
}, 1000);
32+
});
33+
}

apps/array/src/main/services/fileWatcher.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
type IpcMainInvokeEvent,
88
ipcMain,
99
} from "electron";
10+
import { logger } from "../lib/logger";
11+
12+
const log = logger.scope("file-watcher");
1013

1114
const WATCHER_IGNORE_PATTERNS = ["**/node_modules/**", "**/.git/**"];
1215
const DEBOUNCE_MS = 100;
@@ -72,7 +75,7 @@ class FileService {
7275
return a.name.localeCompare(b.name);
7376
});
7477
} catch (error) {
75-
console.error("Failed to list directory:", error);
78+
log.error("Failed to list directory:", error);
7679
return [];
7780
}
7881
}
@@ -115,7 +118,7 @@ class FileService {
115118
repoPath,
116119
(err, events) => {
117120
if (err) {
118-
console.error("Watcher error:", err);
121+
log.error("Watcher error:", err);
119122
return;
120123
}
121124

@@ -162,7 +165,7 @@ class FileService {
162165
gitDirToWatch,
163166
(err, events) => {
164167
if (err) {
165-
console.error("Git watcher error:", err);
168+
log.error("Git watcher error:", err);
166169
return;
167170
}
168171
if (
@@ -175,7 +178,7 @@ class FileService {
175178
},
176179
);
177180
} catch (error) {
178-
console.warn("Failed to set up git watcher:", error);
181+
log.warn("Failed to set up git watcher:", error);
179182
}
180183

181184
state.subscription = subscription;
@@ -196,7 +199,7 @@ class FileService {
196199
ignore: WATCHER_IGNORE_PATTERNS,
197200
});
198201
} catch (error) {
199-
console.error("Failed to write snapshot:", error);
202+
log.error("Failed to write snapshot:", error);
200203
}
201204

202205
await state.subscription?.unsubscribe();

0 commit comments

Comments
 (0)