Skip to content

Commit 5a85190

Browse files
committed
Merge branch 'main' into task-run-scope
2 parents ffbf472 + f0d2800 commit 5a85190

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

+459
-222
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
@@ -20067,30 +20067,3 @@ export class ApiClient {
2006720067
export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
2006820068
return new ApiClient(fetcher).setBaseUrl(baseUrl ?? "");
2006920069
}
20070-
20071-
/**
20072-
Example usage:
20073-
const api = createApiClient((method, url, params) =>
20074-
fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
20075-
);
20076-
api.get("/users").then((users) => console.log(users));
20077-
api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
20078-
api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
20079-
20080-
// With error handling
20081-
const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true });
20082-
if (result.ok) {
20083-
// Access data directly
20084-
const user = result.data;
20085-
console.log(user);
20086-
20087-
// Or use the json() method for compatibility
20088-
const userFromJson = await result.json();
20089-
console.log(userFromJson);
20090-
} else {
20091-
const error = result.data;
20092-
console.error(`Error ${result.status}:`, error);
20093-
}
20094-
*/
20095-
20096-
// </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, 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;
@@ -238,7 +241,7 @@ export class PostHogAPIClient {
238241
const response = await fetch(logUrl);
239242

240243
if (!response.ok) {
241-
console.warn(
244+
log.warn(
242245
`Failed to fetch logs: ${response.status} ${response.statusText}`,
243246
);
244247
return [];
@@ -254,7 +257,7 @@ export class PostHogAPIClient {
254257
.split("\n")
255258
.map((line) => JSON.parse(line) as LogEntry);
256259
} catch (err) {
257-
console.warn("Failed to fetch task logs from latest run", err);
260+
log.warn("Failed to fetch task logs from latest run", err);
258261
return [];
259262
}
260263
}

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);
@@ -183,6 +185,11 @@ app.whenReady().then(() => {
183185
// Initialize PostHog analytics
184186
initializePostHog();
185187
trackAppEvent(ANALYTICS_EVENTS.APP_STARTED);
188+
189+
// Dev mode: Watch agent package and restart via mprocs
190+
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
191+
setupAgentHotReload();
192+
}
186193
});
187194

188195
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 & 24 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;
@@ -35,18 +47,12 @@ export interface TaskController {
3547
}
3648

3749
function getClaudeCliPath(): string {
38-
const { existsSync } = require("node:fs");
3950
const appPath = app.getAppPath();
4051

4152
const claudeCliPath = app.isPackaged
4253
? join(`${appPath}.unpacked`, ".vite/build/claude-cli/cli.js")
4354
: join(appPath, ".vite/build/claude-cli/cli.js");
4455

45-
console.log("[agent] Claude CLI path:", claudeCliPath);
46-
console.log("[agent] Claude CLI exists:", existsSync(claudeCliPath));
47-
console.log("[agent] App path:", appPath);
48-
console.log("[agent] Is packaged:", app.isPackaged);
49-
5056
return claudeCliPath;
5157
}
5258

@@ -106,12 +112,9 @@ export function registerAgentIpc(
106112
process.env.CLAUDE_CONFIG_DIR || join(app.getPath("home"), ".claude");
107113
const statsigPath = join(claudeConfigDir, "statsig");
108114
rmSync(statsigPath, { recursive: true, force: true });
109-
console.log(
110-
"[agent] Cleared statsig cache to work around input_examples bug",
111-
);
115+
log.info("Cleared statsig cache to work around input_examples bug");
112116
} catch (error) {
113-
// Ignore errors if the folder doesn't exist
114-
console.warn("[agent] Could not clear statsig cache:", error);
117+
log.warn("Could not clear statsig cache:", error);
115118
}
116119

117120
const taskId = randomUUID();
@@ -133,7 +136,7 @@ export function registerAgentIpc(
133136
if (stderrBuffer.length > 50) {
134137
stderrBuffer.shift();
135138
}
136-
console.error(`[agent][claude-stderr] ${text}`);
139+
log.error(`[claude-stderr] ${text}`);
137140
emitToRenderer({
138141
type: "status",
139142
ts: Date.now(),
@@ -156,6 +159,7 @@ export function registerAgentIpc(
156159
posthogApiUrl: apiHost,
157160
posthogProjectId: projectId,
158161
debug: true,
162+
onLog: onAgentLog,
159163
});
160164

161165
const controllerEntry: TaskController = {
@@ -191,7 +195,7 @@ export function registerAgentIpc(
191195
});
192196
}
193197
} catch (err) {
194-
console.warn("[agent] failed to fetch task progress", err);
198+
log.warn("Failed to fetch task progress", err);
195199
}
196200
};
197201

@@ -224,7 +228,7 @@ export function registerAgentIpc(
224228
} catch {}
225229
symlinkSync(process.execPath, nodeSymlinkPath);
226230
} catch (err) {
227-
console.warn("[agent] Failed to setup mock node environment", err);
231+
log.warn("Failed to setup mock node environment", err);
228232
}
229233

230234
const newPath = `${mockNodeDir}:${process.env.PATH || ""}`;
@@ -245,14 +249,6 @@ export function registerAgentIpc(
245249
const mcpOverrides = {};
246250
const claudeCliPath = getClaudeCliPath();
247251

248-
console.log("[agent] Query overrides:", {
249-
model,
250-
pathToClaudeCodeExecutable: claudeCliPath,
251-
hasAbortController: !!abortController,
252-
hasStderr: !!forwardClaudeStderr,
253-
hasEnv: !!envOverrides,
254-
});
255-
256252
await agent.runTask(posthogTaskId, taskRunId, {
257253
repositoryPath: repoPath,
258254
permissionMode: resolvedPermission,
@@ -277,7 +273,7 @@ export function registerAgentIpc(
277273

278274
emitToRenderer({ type: "done", success: true, ts: Date.now() });
279275
} catch (err) {
280-
console.error("[agent] task execution failed", err);
276+
log.error("Task execution failed", err);
281277
let errorMessage = err instanceof Error ? err.message : String(err);
282278
const cause =
283279
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)