authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-25 18:01:07-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-26 13:20:36-08:00
log5a755ada28fa8da9d435f2e1f14697e83f4aa6db
tree4839da3b79b19f57e1d3cde38994131cbcb79199
parentb211d059d2b88e3ffce38b5e9b59cdc7b2e0259a

nits


3 files changed, 33 insertions(+), 29 deletions(-)

apps/server/src/wsServer.ts+30-27
...@@ -183,23 +183,22 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E...@@ -183,23 +183,22 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E
183 });183 });
184 }184 }
185185
186 const onTerminalEvent = (event: TerminalEvent) =>186 const onTerminalEvent = Effect.fnUntraced(function* (event: TerminalEvent) {
187 Effect.gen(function* () {187 const push: WsPush = {
188 const push: WsPush = {188 type: "push",
189 type: "push",189 channel: WS_CHANNELS.terminalEvent,
190 channel: WS_CHANNELS.terminalEvent,190 data: event,
191 data: event,191 };
192 };192 const message = yield* Schema.encodeEffect(Schema.fromJsonString(WsPush))(push);
193 const message = yield* Schema.encodeEffect(Schema.fromJsonString(WsPush))(push);193 let recipients = 0;
194 let recipients = 0;194 for (const client of yield* Ref.get(clients)) {
195 for (const client of yield* Ref.get(clients)) {195 if (client.readyState === client.OPEN) {
196 if (client.readyState === client.OPEN) {196 client.send(message);
197 client.send(message);197 recipients += 1;
198 recipients += 1;
199 }
200 }198 }
201 logOutgoingPush(push, recipients);199 }
202 }).pipe(Effect.runPromise);200 logOutgoingPush(push, recipients);
201 });
203202
204 // HTTP server — serves static files or redirects to Vite dev server203 // HTTP server — serves static files or redirects to Vite dev server
205 const httpServer = http.createServer((req, res) => {204 const httpServer = http.createServer((req, res) => {
...@@ -289,9 +288,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E...@@ -289,9 +288,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E
289 const { openInEditor } = yield* Open;288 const { openInEditor } = yield* Open;
290289
291 const subscriptionsScope = yield* Scope.make("sequential");290 const subscriptionsScope = yield* Scope.make("sequential");
292 yield* Effect.addFinalizer(() =>291 yield* Effect.addFinalizer(() => Scope.close(subscriptionsScope, Exit.void));
293 Scope.close(subscriptionsScope, Exit.void).pipe(Effect.orElseSucceed(() => undefined)),
294 );
295292
296 yield* Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) =>293 yield* Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) =>
297 Effect.gen(function* () {294 Effect.gen(function* () {
...@@ -312,7 +309,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E...@@ -312,7 +309,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E
312 }),309 }),
313 ).pipe(Effect.forkIn(subscriptionsScope));310 ).pipe(Effect.forkIn(subscriptionsScope));
314311
315 yield* orchestrationReactor.start.pipe(Scope.provide(subscriptionsScope));312 yield* Scope.provide(orchestrationReactor.start, subscriptionsScope);
316313
317 if (autoBootstrapProjectFromCwd) {314 if (autoBootstrapProjectFromCwd) {
318 const snapshot = yield* projectionReadModelQuery.getSnapshot();315 const snapshot = yield* projectionReadModelQuery.getSnapshot();
...@@ -332,7 +329,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E...@@ -332,7 +329,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E
332 }329 }
333 }330 }
334331
335 const routeRequest = Effect.fn(function* (request: WebSocketRequest) {332 const routeRequest = Effect.fnUntraced(function* (request: WebSocketRequest) {
336 switch (request.body._tag) {333 switch (request.body._tag) {
337 case ORCHESTRATION_WS_METHODS.getSnapshot:334 case ORCHESTRATION_WS_METHODS.getSnapshot:
338 return yield* projectionReadModelQuery.getSnapshot();335 return yield* projectionReadModelQuery.getSnapshot();
...@@ -573,18 +570,24 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E...@@ -573,18 +570,24 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E
573 ),570 ),
574 );571 );
575572
576 const unsubscribeTerminalEvents = yield* terminalManager.subscribe(onTerminalEvent);573 const unsubscribeTerminalEvents = yield* terminalManager.subscribe(
574 (event) => void Effect.runPromise(onTerminalEvent(event)),
575 );
577 yield* Effect.addFinalizer(() =>576 yield* Effect.addFinalizer(() =>
578 Effect.sync(() => {577 Effect.all([Effect.sync(() => unsubscribeTerminalEvents()), terminalManager.dispose()]),
579 unsubscribeTerminalEvents();
580 }),
581 );578 );
582 yield* Effect.addFinalizer(terminalManager.dispose);
583579
584 yield* NodeHttpServer.make(() => httpServer, listenOptions);580 yield* NodeHttpServer.make(() => httpServer, listenOptions);
585581
586 yield* Effect.addFinalizer(() =>582 yield* Effect.addFinalizer(() =>
587 Effect.all([closeAllClients, closeWebSocketServer.pipe(Effect.orElseSucceed(() => undefined))]),583 Effect.all([
584 closeAllClients,
585 closeWebSocketServer.pipe(
586 Effect.catch((error) =>
587 Effect.logWarning("failed to close web socket server", { cause: error }),
588 ),
589 ),
590 ]),
588 );591 );
589592
590 return httpServer;593 return httpServer;
apps/web/src/session-logic.test.ts+2-2
...@@ -69,7 +69,7 @@ describe("derivePendingApprovals", () => {...@@ -69,7 +69,7 @@ describe("derivePendingApprovals", () => {
69});69});
7070
71describe("deriveWorkLogEntries", () => {71describe("deriveWorkLogEntries", () => {
72 it("returns entries in chronological order", () => {72 it("omits tool started entries and keeps completed entries", () => {
73 const activities: OrchestrationThreadActivity[] = [73 const activities: OrchestrationThreadActivity[] = [
74 makeActivity({74 makeActivity({
75 id: "tool-complete",75 id: "tool-complete",
...@@ -86,7 +86,7 @@ describe("deriveWorkLogEntries", () => {...@@ -86,7 +86,7 @@ describe("deriveWorkLogEntries", () => {
86 ];86 ];
8787
88 const entries = deriveWorkLogEntries(activities, undefined);88 const entries = deriveWorkLogEntries(activities, undefined);
89 expect(entries.map((entry) => entry.id)).toEqual(["tool-start", "tool-complete"]);89 expect(entries.map((entry) => entry.id)).toEqual(["tool-complete"]);
90 });90 });
9191
92 it("filters by turn id when provided", () => {92 it("filters by turn id when provided", () => {
apps/web/src/session-logic.ts+1
...@@ -127,6 +127,7 @@ export function deriveWorkLogEntries(...@@ -127,6 +127,7 @@ export function deriveWorkLogEntries(
127 );127 );
128 return ordered128 return ordered
129 .filter((activity) => (latestTurnId ? activity.turnId === latestTurnId : true))129 .filter((activity) => (latestTurnId ? activity.turnId === latestTurnId : true))
130 .filter((activity) => activity.kind !== "tool.started")
130 .filter((activity) => activity.summary !== "Checkpoint captured")131 .filter((activity) => activity.summary !== "Checkpoint captured")
131 .map((activity) => {132 .map((activity) => {
132 const payload =133 const payload =