Skip to content

Commit b942224

Browse files
committed
Let clean checkouts reset diff stats and steady preview browsing
- Rebase thread diff totals after clean checkout observations - Improve preview approvals, URL defaults, tab favicons, and tab scrolling - Prevent snapshot/live event gaps and retry failed subscriptions
1 parent 04cc9f9 commit b942224

67 files changed

Lines changed: 1953 additions & 184 deletions

File tree

Some content is hidden

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

apps/desktop/src/preview/PreviewAutomation.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ export const make = Effect.sync(function PreviewAutomationMake() {
262262
webContentsId: contents.id,
263263
host: blockedHost,
264264
url,
265+
// Who was navigating. The agent's own navigations are refused in the
266+
// renderer before they start, so anything blocked here came from a page
267+
// -- a redirect or a link -- and the prompt should say which one.
268+
fromHost: navigationHost(contents.getURL()),
265269
};
266270
const embedder = contents.hostWebContents;
267271
if (embedder !== null && !embedder.isDestroyed()) {

apps/server/integration/OrchestrationEngineHarness.integration.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
type OrchestrationEngineShape,
6262
} from "../src/orchestration/Services/OrchestrationEngine.ts";
6363
import { ThreadDeletionReactor } from "../src/orchestration/Services/ThreadDeletionReactor.ts";
64+
import { ThreadDiffStatBaselineReactor } from "../src/orchestration/Services/ThreadDiffStatBaselineReactor.ts";
6465
import { OrchestrationReactor } from "../src/orchestration/Services/OrchestrationReactor.ts";
6566
import { ProjectionSnapshotQuery } from "../src/orchestration/Services/ProjectionSnapshotQuery.ts";
6667
import {
@@ -348,6 +349,7 @@ export const makeOrchestrationIntegrationHarness = (
348349
}),
349350
refreshStatus: () => Effect.die("refreshStatus should not be called in this test"),
350351
streamStatus: () => Stream.empty,
352+
observeLocalStatus: () => Stream.empty,
351353
}),
352354
),
353355
Layer.provideMerge(
@@ -370,6 +372,12 @@ export const makeOrchestrationIntegrationHarness = (
370372
drain: Effect.void,
371373
}),
372374
),
375+
Layer.provideMerge(
376+
Layer.succeed(ThreadDiffStatBaselineReactor, {
377+
start: () => Effect.void,
378+
drain: Effect.void,
379+
}),
380+
),
373381
);
374382
const layer = Layer.empty.pipe(
375383
Layer.provideMerge(runtimeServicesLayer),

apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ describe("CheckpointDiffQueryLive", () => {
107107
toCheckpointRef,
108108
});
109109
}),
110+
listThreadDiffStatBaselines: () => Effect.die("unused"),
110111
getThreadShellById: () => Effect.succeed(Option.none()),
111112
getThreadDetailById: () => Effect.succeed(Option.none()),
112113
}),
@@ -194,6 +195,7 @@ describe("CheckpointDiffQueryLive", () => {
194195
getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
195196
getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)),
196197
getFullThreadDiffContext: () => Effect.die("unused"),
198+
listThreadDiffStatBaselines: () => Effect.die("unused"),
197199
getThreadShellById: () => Effect.succeed(Option.none()),
198200
getThreadDetailById: () => Effect.succeed(Option.none()),
199201
}),
@@ -308,6 +310,7 @@ describe("CheckpointDiffQueryLive", () => {
308310
getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
309311
getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)),
310312
getFullThreadDiffContext: () => Effect.die("unused"),
313+
listThreadDiffStatBaselines: () => Effect.die("unused"),
311314
getThreadShellById: () => Effect.succeed(Option.none()),
312315
getThreadDetailById: () => Effect.succeed(Option.none()),
313316
}),
@@ -380,6 +383,7 @@ describe("CheckpointDiffQueryLive", () => {
380383
getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
381384
getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)),
382385
getFullThreadDiffContext: () => Effect.die("unused"),
386+
listThreadDiffStatBaselines: () => Effect.die("unused"),
383387
getThreadShellById: () => Effect.succeed(Option.none()),
384388
getThreadDetailById: () => Effect.succeed(Option.none()),
385389
}),
@@ -448,6 +452,7 @@ describe("CheckpointDiffQueryLive", () => {
448452
getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
449453
getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)),
450454
getFullThreadDiffContext: () => Effect.die("unused"),
455+
listThreadDiffStatBaselines: () => Effect.die("unused"),
451456
getThreadShellById: () => Effect.succeed(Option.none()),
452457
getThreadDetailById: () => Effect.succeed(Option.none()),
453458
}),
@@ -499,6 +504,7 @@ describe("CheckpointDiffQueryLive", () => {
499504
getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
500505
getThreadCheckpointContext: () => Effect.succeed(Option.none()),
501506
getFullThreadDiffContext: () => Effect.succeed(Option.none()),
507+
listThreadDiffStatBaselines: () => Effect.die("unused"),
502508
getThreadShellById: () => Effect.succeed(Option.none()),
503509
getThreadDetailById: () => Effect.succeed(Option.none()),
504510
}),

apps/server/src/orchestration/Layers/CheckpointReactor.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ describe("CheckpointReactor", () => {
399399
),
400400
refreshStatus: () => Effect.die("refreshStatus should not be called in this test"),
401401
streamStatus: () => Stream.empty,
402+
observeLocalStatus: () => Stream.empty,
402403
});
403404

404405
const layer = CheckpointReactorLive.pipe(

apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ describe("OrchestrationEngine", () => {
155155
proposedPlans: [],
156156
activities: [],
157157
checkpoints: [],
158+
diffStatBaselineTurnCount: 0,
158159
session: null,
159160
},
160161
],
@@ -202,6 +203,7 @@ describe("OrchestrationEngine", () => {
202203
getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),
203204
getThreadCheckpointContext: () => Effect.succeed(Option.none()),
204205
getFullThreadDiffContext: () => Effect.succeed(Option.none()),
206+
listThreadDiffStatBaselines: () => Effect.succeed([]),
205207
getThreadShellById: () => Effect.succeed(Option.none()),
206208
getThreadDetailById: () => Effect.succeed(Option.none()),
207209
}),

apps/server/src/orchestration/Layers/OrchestrationEngine.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ const makeOrchestrationEngine = Effect.gen(function* () {
316316
get streamDomainEvents(): OrchestrationEngineShape["streamDomainEvents"] {
317317
return Stream.fromPubSub(eventPubSub);
318318
},
319+
subscribeDomainEvents: Effect.map(PubSub.subscribe(eventPubSub), Stream.fromSubscription),
319320
} satisfies OrchestrationEngineShape;
320321
});
321322

apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CheckpointReactor } from "../Services/CheckpointReactor.ts";
99
import { ProviderCommandReactor } from "../Services/ProviderCommandReactor.ts";
1010
import { ProviderRuntimeIngestionService } from "../Services/ProviderRuntimeIngestion.ts";
1111
import { ThreadDeletionReactor } from "../Services/ThreadDeletionReactor.ts";
12+
import { ThreadDiffStatBaselineReactor } from "../Services/ThreadDiffStatBaselineReactor.ts";
1213
import { OrchestrationReactor } from "../Services/OrchestrationReactor.ts";
1314
import { makeOrchestrationReactor } from "./OrchestrationReactor.ts";
1415

@@ -22,7 +23,7 @@ describe("OrchestrationReactor", () => {
2223
runtime = null;
2324
});
2425

25-
it("starts provider ingestion, provider command, checkpoint, and thread deletion reactors", async () => {
26+
it("starts provider ingestion, provider command, checkpoint, thread deletion, and diff-stat baseline reactors", async () => {
2627
const started: string[] = [];
2728

2829
runtime = ManagedRuntime.make(
@@ -63,6 +64,15 @@ describe("OrchestrationReactor", () => {
6364
drain: Effect.void,
6465
}),
6566
),
67+
Layer.provideMerge(
68+
Layer.succeed(ThreadDiffStatBaselineReactor, {
69+
start: () => {
70+
started.push("thread-diffstat-baseline-reactor");
71+
return Effect.void;
72+
},
73+
drain: Effect.void,
74+
}),
75+
),
6676
),
6777
);
6878

@@ -75,6 +85,7 @@ describe("OrchestrationReactor", () => {
7585
"provider-command-reactor",
7686
"checkpoint-reactor",
7787
"thread-deletion-reactor",
88+
"thread-diffstat-baseline-reactor",
7889
]);
7990

8091
await Effect.runPromise(Scope.close(scope, Exit.void));

apps/server/src/orchestration/Layers/OrchestrationReactor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ import { CheckpointReactor } from "../Services/CheckpointReactor.ts";
99
import { ProviderCommandReactor } from "../Services/ProviderCommandReactor.ts";
1010
import { ProviderRuntimeIngestionService } from "../Services/ProviderRuntimeIngestion.ts";
1111
import { ThreadDeletionReactor } from "../Services/ThreadDeletionReactor.ts";
12+
import { ThreadDiffStatBaselineReactor } from "../Services/ThreadDiffStatBaselineReactor.ts";
1213

1314
export const makeOrchestrationReactor = Effect.gen(function* () {
1415
const providerRuntimeIngestion = yield* ProviderRuntimeIngestionService;
1516
const providerCommandReactor = yield* ProviderCommandReactor;
1617
const checkpointReactor = yield* CheckpointReactor;
1718
const threadDeletionReactor = yield* ThreadDeletionReactor;
19+
const threadDiffStatBaselineReactor = yield* ThreadDiffStatBaselineReactor;
1820

1921
const start: OrchestrationReactorShape["start"] = Effect.fn("start")(function* () {
2022
yield* providerRuntimeIngestion.start();
2123
yield* providerCommandReactor.start();
2224
yield* checkpointReactor.start();
2325
yield* threadDeletionReactor.start();
26+
yield* threadDiffStatBaselineReactor.start();
2427
});
2528

2629
return {

apps/server/src/orchestration/Layers/ProjectionPipeline.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
559559
effectiveCwd: null,
560560
goal: null,
561561
voiceActive: 0,
562+
diffStatBaselineTurnCount: 0,
562563
latestTurnId: null,
563564
createdAt: event.payload.createdAt,
564565
updatedAt: event.payload.updatedAt,
@@ -820,6 +821,27 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
820821
return;
821822
}
822823

824+
case "thread.diffstat-rebased": {
825+
const existingRow = yield* projectionThreadRepository.getById({
826+
threadId: event.payload.threadId,
827+
});
828+
if (Option.isNone(existingRow)) {
829+
return;
830+
}
831+
// The decider only emits this event when the baseline moves forward,
832+
// but replay order is the projector's problem alone -- clamp so a
833+
// re-applied event can never walk the rollup backwards.
834+
yield* projectionThreadRepository.upsert({
835+
...existingRow.value,
836+
diffStatBaselineTurnCount: Math.max(
837+
existingRow.value.diffStatBaselineTurnCount ?? 0,
838+
event.payload.baselineTurnCount,
839+
),
840+
updatedAt: event.payload.occurredAt,
841+
});
842+
return;
843+
}
844+
823845
case "thread.reverted": {
824846
const existingRow = yield* projectionThreadRepository.getById({
825847
threadId: event.payload.threadId,

0 commit comments

Comments
 (0)