Skip to content

Commit 70dce2c

Browse files
authored
refactor: sessions (#248)
Session store and all related types were an absolute mess. I refactored it so we now only use ACP types directly, so I can actually work on improving session rendering. Also updated our knip config.
1 parent 335268e commit 70dce2c

40 files changed

+1520
-1515
lines changed

CLAUDE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@
4242
- TypeScript strict mode enabled
4343
- Tailwind CSS classes should be sorted (biome `useSortedClasses` rule)
4444

45+
### Avoid Barrel Files
46+
47+
Barrel files:
48+
- Break tree-shaking
49+
- Create circular dependency risks
50+
- Hide the true source of imports
51+
- Make refactoring harder
52+
53+
Import directly from source files instead.
54+
4555
## Architecture
4656

4757
### Electron App (apps/array)

apps/array/knip.json

Lines changed: 0 additions & 19 deletions
This file was deleted.

apps/array/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"build-icons": "bash scripts/generate-icns.sh",
2121
"typecheck": "tsc -p tsconfig.node.json --noEmit && tsc -p tsconfig.web.json --noEmit",
2222
"generate-client": "tsx scripts/update-openapi-client.ts",
23-
"knip": "knip",
2423
"test": "vitest run",
2524
"postinstall": "cd ../.. && npx @electron/rebuild -f -m node_modules/node-pty || true"
2625
},
@@ -54,7 +53,6 @@
5453
"electron": "^30.0.0",
5554
"husky": "^9.1.7",
5655
"jsdom": "^26.0.0",
57-
"knip": "^5.66.3",
5856
"lint-staged": "^15.5.2",
5957
"postcss": "^8.4.33",
6058
"tailwindcss": "^3.4.1",
@@ -67,6 +65,7 @@
6765
"yaml": "^2.8.1"
6866
},
6967
"dependencies": {
68+
"@agentclientprotocol/sdk": "^0.9.0",
7069
"@ai-sdk/openai": "^2.0.52",
7170
"@codemirror/lang-angular": "^0.1.4",
7271
"@codemirror/lang-cpp": "^6.0.3",
@@ -126,6 +125,7 @@
126125
"electron-store": "^11.0.0",
127126
"file-icon": "^6.0.0",
128127
"idb-keyval": "^6.2.2",
128+
"immer": "^11.0.1",
129129
"is-glob": "^4.0.3",
130130
"micromatch": "^4.0.5",
131131
"node-addon-api": "^8.5.0",

apps/array/src/api/posthogClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { StoredLogEntry } from "@features/sessions/utils/parseSessionLogs";
21
import type { AgentEvent } from "@posthog/agent";
32
import { logger } from "@renderer/lib/logger";
43
import type { Task, TaskRun } from "@shared/types";
4+
import type { StoredLogEntry } from "@shared/types/session-events";
55
import { buildApiFetcher } from "./fetcher";
66
import { createApiClient, type Schemas } from "./generated";
77

apps/array/src/main/services/session-manager.ts

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,18 @@ import {
1818
type IpcMainInvokeEvent,
1919
ipcMain,
2020
} from "electron";
21+
import type { AcpMessage } from "../../shared/types/session-events";
2122
import { logger } from "../lib/logger";
2223

2324
const log = logger.scope("session-manager");
2425

25-
type MessageCallback = (
26-
message: unknown,
27-
direction: "client" | "agent",
28-
) => void;
29-
type MessageDirection = "client" | "agent";
26+
type MessageCallback = (message: unknown) => void;
3027

3128
class NdJsonTap {
3229
private decoder = new TextDecoder();
3330
private buffer = "";
3431

35-
constructor(
36-
private onMessage: MessageCallback,
37-
private direction: MessageDirection,
38-
) {}
32+
constructor(private onMessage: MessageCallback) {}
3933

4034
process(chunk: Uint8Array): void {
4135
this.buffer += this.decoder.decode(chunk, { stream: true });
@@ -45,7 +39,7 @@ class NdJsonTap {
4539
for (const line of lines) {
4640
if (!line.trim()) continue;
4741
try {
48-
this.onMessage(JSON.parse(line), this.direction);
42+
this.onMessage(JSON.parse(line));
4943
} catch {
5044
// Not valid JSON, skip
5145
}
@@ -56,10 +50,9 @@ class NdJsonTap {
5650
function createTappedReadableStream(
5751
underlying: ReadableStream<Uint8Array>,
5852
onMessage: MessageCallback,
59-
direction: MessageDirection,
6053
): ReadableStream<Uint8Array> {
6154
const reader = underlying.getReader();
62-
const tap = new NdJsonTap(onMessage, direction);
55+
const tap = new NdJsonTap(onMessage);
6356

6457
return new ReadableStream<Uint8Array>({
6558
async pull(controller) {
@@ -77,9 +70,8 @@ function createTappedReadableStream(
7770
function createTappedWritableStream(
7871
underlying: WritableStream<Uint8Array>,
7972
onMessage: MessageCallback,
80-
direction: MessageDirection,
8173
): WritableStream<Uint8Array> {
82-
const tap = new NdJsonTap(onMessage, direction);
74+
const tap = new NdJsonTap(onMessage);
8375

8476
return new WritableStream<Uint8Array>({
8577
async write(chunk) {
@@ -471,30 +463,31 @@ export class SessionManager {
471463
}
472464
};
473465

474-
// Emit all raw ACP messages (bidirectional) to the renderer
475-
const onAcpMessage = (message: unknown, direction: "client" | "agent") => {
476-
emitToRenderer({
466+
// Emit all raw ACP messages to the renderer
467+
const onAcpMessage = (message: unknown) => {
468+
const acpMessage: AcpMessage = {
477469
type: "acp_message",
478-
direction,
479470
ts: Date.now(),
480-
message,
481-
});
471+
message: message as AcpMessage["message"],
472+
};
473+
emitToRenderer(acpMessage);
482474
};
483475

484-
// Tap both streams to capture all messages bidirectionally
476+
// Tap both streams to capture all messages
485477
const tappedReadable = createTappedReadableStream(
486478
clientStreams.readable as ReadableStream<Uint8Array>,
487479
onAcpMessage,
488-
"agent",
489480
);
490481

491482
const tappedWritable = createTappedWritableStream(
492483
clientStreams.writable as WritableStream<Uint8Array>,
493484
onAcpMessage,
494-
"client",
495485
);
496486

497487
// Create Client implementation that forwards to renderer
488+
// Note: sessionUpdate is NOT implemented here because session/update
489+
// notifications are already captured by the stream tap and forwarded
490+
// as acp_message events. This avoids duplicate events.
498491
const client: Client = {
499492
async requestPermission(
500493
params: RequestPermissionRequest,
@@ -511,12 +504,9 @@ export class SessionManager {
511504
};
512505
},
513506

514-
async sessionUpdate(params: SessionNotification): Promise<void> {
515-
emitToRenderer({
516-
type: "session_update",
517-
ts: Date.now(),
518-
notification: params,
519-
});
507+
async sessionUpdate(_params: SessionNotification): Promise<void> {
508+
// No-op: session/update notifications are captured by the stream tap
509+
// and forwarded as acp_message events to avoid duplication
520510
},
521511
};
522512

0 commit comments

Comments
 (0)