Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion apps/server/src/orchestration/ActivityPayloadProjection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,62 @@ export function projectActivityPayload(
};
}

/**
* Matches the validity rule in the web client's
* `deriveLatestContextWindowSnapshot`: rows without a finite, non-negative
* `usedTokens` are skipped during its backward walk, so they must not shadow
* an earlier resolvable row here.
*/
function isResolvableContextWindowActivity(activity: OrchestrationThreadActivity): boolean {
if (activity.kind !== "context-window.updated") {
return false;
}
const payload = asRecord(activity.payload);
const usedTokens = payload?.usedTokens;
return typeof usedTokens === "number" && Number.isFinite(usedTokens) && usedTokens >= 0;
}

/**
* Drops all but the last resolvable context-window activity per turn from a
* snapshot. Clients only ever read the latest usage value (walking the array
* backwards), so shipping the full history — often thousands of rows on long
* threads — buys nothing. Retention is per turn rather than per thread because
* a live `thread.reverted` makes the client discard whole turns; keeping each
* turn's latest row means the meter can still resolve a value from the turns
* that survive. Malformed rows pass through untouched rather than shadowing a
* valid earlier row. Live `thread.activity-appended` events are untouched:
* newer updates still stream through and supersede the retained rows on the
* client.
*/
function dropStaleContextWindowActivities(
activities: ReadonlyArray<OrchestrationThreadActivity>,
): ReadonlyArray<OrchestrationThreadActivity> {
const latestIndexByTurn = new Map<string | null, number>();
for (let index = 0; index < activities.length; index += 1) {
if (isResolvableContextWindowActivity(activities[index]!)) {
latestIndexByTurn.set(activities[index]!.turnId, index);
}
}
if (latestIndexByTurn.size === 0) {
return activities;
}
return activities.filter(
(activity, index) =>
!isResolvableContextWindowActivity(activity) ||
latestIndexByTurn.get(activity.turnId) === index,
);
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
}
Comment thread
cursor[bot] marked this conversation as resolved.

export function projectThreadDetailSnapshot(
snapshot: OrchestrationThreadDetailSnapshot,
): OrchestrationThreadDetailSnapshot {
return {
...snapshot,
thread: {
...snapshot.thread,
activities: snapshot.thread.activities.map(projectActivityPayload),
activities: dropStaleContextWindowActivities(snapshot.thread.activities).map(
projectActivityPayload,
),
},
};
}
Expand Down
76 changes: 0 additions & 76 deletions apps/server/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5690,15 +5690,6 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
),
);
assert.equal(fullDiffResult.diff, "full-diff");

const replayResult = yield* Effect.scoped(
withWsRpcClient(wsUrl, (client) =>
client[ORCHESTRATION_WS_METHODS.replayEvents]({
fromSequenceExclusive: 0,
}),
),
);
assert.deepEqual(replayResult, []);
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

Expand Down Expand Up @@ -6361,73 +6352,6 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

it.effect("enriches replayed project events with repository identity metadata", () =>
Effect.gen(function* () {
const repositoryIdentity = {
canonicalKey: "github.com/t3tools/t3code",
locator: {
source: "git-remote" as const,
remoteName: "origin",
remoteUrl: "git@github.com:T3Tools/t3code.git",
},
displayName: "T3Tools/t3code",
provider: "github",
owner: "T3Tools",
name: "t3code",
};

yield* buildAppUnderTest({
layers: {
orchestrationEngine: {
readEvents: (_fromSequenceExclusive) =>
Stream.make({
sequence: 1,
eventId: EventId.make("event-1"),
aggregateKind: "project",
aggregateId: defaultProjectId,
occurredAt: "2026-04-05T00:00:00.000Z",
commandId: null,
causationEventId: null,
correlationId: null,
metadata: {},
type: "project.created",
payload: {
projectId: defaultProjectId,
title: "Default Project",
workspaceRoot: "/tmp/default-project",
defaultModelSelection,
scripts: [],
createdAt: "2026-04-05T00:00:00.000Z",
updatedAt: "2026-04-05T00:00:00.000Z",
},
} satisfies Extract<OrchestrationEvent, { type: "project.created" }>),
},
repositoryIdentityResolver: {
resolve: () => Effect.succeed(repositoryIdentity),
},
},
});

const wsUrl = yield* getWsServerUrl("/ws");
const replayResult = yield* Effect.scoped(
withWsRpcClient(wsUrl, (client) =>
client[ORCHESTRATION_WS_METHODS.replayEvents]({
fromSequenceExclusive: 0,
}),
),
);

const replayedEvent = replayResult[0];
assert.equal(replayedEvent?.type, "project.created");
assert.deepEqual(
replayedEvent && replayedEvent.type === "project.created"
? replayedEvent.payload.repositoryIdentity
: null,
repositoryIdentity,
);
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

it.effect("stops the provider session and closes thread terminals after archive", () =>
Effect.gen(function* () {
const threadId = ThreadId.make("thread-archive");
Expand Down
77 changes: 0 additions & 77 deletions apps/server/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import {
ProjectWriteFileError,
RelayClientInstallFailedError,
type RelayClientInstallProgressEvent,
OrchestrationReplayEventsError,
type FilesystemBrowseFailure,
FilesystemBrowseError,
AssetWorkspaceContextNotFoundError,
Expand All @@ -60,7 +59,6 @@ import {
WS_METHODS,
WsRpcGroup,
} from "@t3tools/contracts";
import { clamp } from "effect/Number";
import { HttpRouter, HttpServerRequest, HttpServerRespondable } from "effect/unstable/http";
import { RpcSerialization, RpcServer } from "effect/unstable/rpc";

Expand Down Expand Up @@ -99,7 +97,6 @@ import * as VcsProvisioningService from "./vcs/VcsProvisioningService.ts";
import * as GitWorkflowService from "./git/GitWorkflowService.ts";
import * as ReviewService from "./review/ReviewService.ts";
import * as ProjectSetupScriptRunner from "./project/ProjectSetupScriptRunner.ts";
import * as RepositoryIdentityResolver from "./project/RepositoryIdentityResolver.ts";
import * as ServerEnvironment from "./environment/ServerEnvironment.ts";
import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";
Expand Down Expand Up @@ -302,7 +299,6 @@ const RPC_REQUIRED_SCOPE = new Map<string, AuthEnvironmentScope>([
[ORCHESTRATION_WS_METHODS.dispatchCommand, AuthOrchestrationOperateScope],
[ORCHESTRATION_WS_METHODS.getTurnDiff, AuthOrchestrationReadScope],
[ORCHESTRATION_WS_METHODS.getFullThreadDiff, AuthOrchestrationReadScope],
[ORCHESTRATION_WS_METHODS.replayEvents, AuthOrchestrationReadScope],
[ORCHESTRATION_WS_METHODS.subscribeShell, AuthOrchestrationReadScope],
[ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot, AuthOrchestrationReadScope],
[ORCHESTRATION_WS_METHODS.subscribeThread, AuthOrchestrationReadScope],
Expand Down Expand Up @@ -441,8 +437,6 @@ const makeWsRpcLayer = (
const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;
const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem;
const projectSetupScriptRunner = yield* ProjectSetupScriptRunner.ProjectSetupScriptRunner;
const repositoryIdentityResolver =
yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
const serverAuth = yield* EnvironmentAuth.EnvironmentAuth;
const sourceControlDiscovery = yield* SourceControlDiscovery.SourceControlDiscovery;
Expand Down Expand Up @@ -592,53 +586,6 @@ const makeWsRpcLayer = (
});
};

const enrichProjectEvent = (
event: OrchestrationEvent,
): Effect.Effect<OrchestrationEvent, never, never> => {
switch (event.type) {
case "project.created":
return repositoryIdentityResolver.resolve(event.payload.workspaceRoot).pipe(
Effect.map((repositoryIdentity) => ({
...event,
payload: {
...event.payload,
repositoryIdentity,
},
})),
);
case "project.meta-updated":
return Effect.gen(function* () {
const workspaceRoot =
event.payload.workspaceRoot ??
Option.match(
yield* projectionSnapshotQuery.getProjectShellById(event.payload.projectId),
{
onNone: () => null,
onSome: (project) => project.workspaceRoot,
},
) ??
null;
if (workspaceRoot === null) {
return event;
}

const repositoryIdentity = yield* repositoryIdentityResolver.resolve(workspaceRoot);
return {
...event,
payload: {
...event.payload,
repositoryIdentity,
},
} satisfies OrchestrationEvent;
}).pipe(Effect.orElseSucceed(() => event));
default:
return Effect.succeed(event);
}
};

const enrichOrchestrationEvents = (events: ReadonlyArray<OrchestrationEvent>) =>
Effect.forEach(events, enrichProjectEvent, { concurrency: 4 });

const toShellStreamEvent = (
event: OrchestrationEvent,
): Effect.Effect<Option.Option<OrchestrationShellStreamEvent>, never, never> => {
Expand Down Expand Up @@ -1215,30 +1162,6 @@ const makeWsRpcLayer = (
),
{ "rpc.aggregate": "orchestration" },
),
[ORCHESTRATION_WS_METHODS.replayEvents]: (input) =>
observeRpcEffect(
ORCHESTRATION_WS_METHODS.replayEvents,
Stream.runCollect(
orchestrationEngine.readEvents(
clamp(input.fromSequenceExclusive, {
maximum: Number.MAX_SAFE_INTEGER,
minimum: 0,
}),
),
).pipe(
Effect.map((events) => Array.from(events)),
Effect.flatMap(enrichOrchestrationEvents),
Effect.map((events) => events.map(projectActivityEvent)),
Effect.mapError(
(cause) =>
new OrchestrationReplayEventsError({
message: "Failed to replay orchestration events",
cause,
}),
),
),
{ "rpc.aggregate": "orchestration" },
),
[ORCHESTRATION_WS_METHODS.subscribeShell]: (input) =>
observeRpcStreamEffect(
ORCHESTRATION_WS_METHODS.subscribeShell,
Expand Down
Loading
Loading