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
6 changes: 0 additions & 6 deletions FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,6 @@ This repository is a fork of `pingdotgg/t3code`. Keep this file focused on fork

- The fork adds thread goal support, goal activity rendering, and goal sidebar/panel UI.

### Subagent Activity

- Parent timelines keep subagent commands, file changes, tool calls, web searches, image views, and diffs.
- Subagent messages, reasoning, goals, plans, token usage, and thread/turn state stay out of the parent timeline. Codex child relationships are recognized from both `collabAgentToolCall` and `subAgentActivity` items.
- Root-agent activity remains visible. Filtering applies only to provider thread IDs explicitly discovered through those child relationship items.

### Provider Launch Environment

- Provider sessions use a shared launch environment pipeline instead of ad hoc environment assembly.
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/cli/pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ExecutionEnvironmentDescriptor,
PortSchema,
} from "@t3tools/contracts";
import { ROOT_BASE_PATH } from "@t3tools/shared/basePath";
import { resolveWorktreeT3Home } from "@t3tools/shared/devHome";
import {
buildTailscaleHttpsBaseUrl,
Expand Down Expand Up @@ -337,6 +338,7 @@ const makePairServerConfig = Effect.fn(function* (input: {
host: state.host,
cwd: process.cwd(),
baseDir,
basePath: ROOT_BASE_PATH,
...derivedPaths,
staticDir: undefined,
devUrl,
Expand Down
92 changes: 40 additions & 52 deletions apps/server/src/provider/Layers/CodexSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,60 +615,47 @@ function readRouteFields(notification: CodexServerNotification): {
}
}

function rememberChildThreadTurns(
childThreadTurns: Map<string, TurnId>,
function rememberCollabReceiverTurns(
collabReceiverTurns: Map<string, TurnId>,
notification: CodexServerNotification,
parentTurnId: TurnId | undefined,
): void {
if (parentTurnId === undefined) {
if (!parentTurnId) {
return;
}

if (notification.method !== "item/started" && notification.method !== "item/completed") {
return;
}

const item = notification.params.item;

if (item.type === "collabAgentToolCall") {
for (const receiverThreadId of item.receiverThreadIds) {
childThreadTurns.set(receiverThreadId, parentTurnId);
}
if (notification.params.item.type !== "collabAgentToolCall") {
return;
}

if (item.type === "subAgentActivity") {
childThreadTurns.set(item.agentThreadId, parentTurnId);
for (const receiverThreadId of notification.params.item.receiverThreadIds) {
collabReceiverTurns.set(receiverThreadId, parentTurnId);
}
}

function shouldKeepChildNotification(notification: CodexServerNotification): boolean {
switch (notification.method) {
case "item/started":
case "item/completed":
switch (notification.params.item.type) {
case "commandExecution":
case "fileChange":
case "mcpToolCall":
case "dynamicToolCall":
case "collabAgentToolCall":
case "webSearch":
case "imageView":
return true;
default:
return false;
}
case "item/commandExecution/outputDelta":
case "item/commandExecution/terminalInteraction":
case "item/fileChange/outputDelta":
case "item/fileChange/patchUpdated":
case "item/mcpToolCall/progress":
case "serverRequest/resolved":
case "turn/diff/updated":
return true;
default:
return false;
}
function shouldSuppressChildConversationNotification(
method: CodexRpc.ServerNotificationMethod,
): boolean {
return (
method === "thread/started" ||
method === "thread/status/changed" ||
method === "thread/archived" ||
method === "thread/unarchived" ||
method === "thread/closed" ||
method === "thread/compacted" ||
method === "thread/name/updated" ||
method === "thread/tokenUsage/updated" ||
method === "thread/goal/updated" ||
method === "thread/goal/cleared" ||
method === "turn/started" ||
method === "turn/completed" ||
method === "turn/plan/updated" ||
method === "item/plan/delta"
);
}

function toCodexUserInputAnswer(
Expand Down Expand Up @@ -752,7 +739,7 @@ export const makeCodexSessionRuntime = (
const pendingApprovalsRef = yield* Ref.make(new Map<ApprovalRequestId, PendingApproval>());
const approvalCorrelationsRef = yield* Ref.make(new Map<string, ApprovalCorrelation>());
const pendingUserInputsRef = yield* Ref.make(new Map<ApprovalRequestId, PendingUserInput>());
const childThreadTurnsRef = yield* Ref.make(new Map<string, TurnId>());
const collabReceiverTurnsRef = yield* Ref.make(new Map<string, TurnId>());
const closedRef = yield* Ref.make(false);

// `~` is not shell-expanded when env vars are set via
Expand Down Expand Up @@ -869,28 +856,27 @@ export const makeCodexSessionRuntime = (
),
);

const currentSessionProviderThreadId = Effect.map(Ref.get(sessionRef), currentProviderThreadId);

const handleRawNotification = (notification: CodexServerNotification) =>
Effect.gen(function* () {
const payload = notification.params;
const route = readRouteFields(notification);
const providerThreadId = readNotificationThreadId(notification);
const childThreadTurns = yield* Ref.get(childThreadTurnsRef);
const childParentTurnId =
providerThreadId === undefined ? undefined : childThreadTurns.get(providerThreadId);
const parentTurnId = childParentTurnId ?? route.turnId;

rememberChildThreadTurns(childThreadTurns, notification, parentTurnId);
const collabReceiverTurns = yield* Ref.get(collabReceiverTurnsRef);
const childParentTurnId = (() => {
const providerConversationId = readNotificationThreadId(notification);
return providerConversationId
? collabReceiverTurns.get(providerConversationId)
: undefined;
})();

if (childParentTurnId !== undefined && !shouldKeepChildNotification(notification)) {
yield* Ref.set(childThreadTurnsRef, childThreadTurns);
rememberCollabReceiverTurns(collabReceiverTurns, notification, route.turnId);
if (childParentTurnId && shouldSuppressChildConversationNotification(notification.method)) {
yield* Ref.set(collabReceiverTurnsRef, collabReceiverTurns);
return;
}

let requestId: ApprovalRequestId | undefined;
let requestKind: ProviderRequestKind | undefined;
let turnId = parentTurnId;
let turnId = childParentTurnId ?? route.turnId;
let itemId = route.itemId;

if (notification.method === "serverRequest/resolved") {
Expand All @@ -914,7 +900,7 @@ export const makeCodexSessionRuntime = (
}
}

yield* Ref.set(childThreadTurnsRef, childThreadTurns);
yield* Ref.set(collabReceiverTurnsRef, collabReceiverTurns);
yield* emitEvent({
kind: "notification",
threadId: options.threadId,
Expand All @@ -930,6 +916,8 @@ export const makeCodexSessionRuntime = (
});
});

const currentSessionProviderThreadId = Effect.map(Ref.get(sessionRef), currentProviderThreadId);

yield* client.handleServerNotification("thread/started", (payload) =>
currentSessionProviderThreadId.pipe(
Effect.flatMap((providerThreadId) => {
Expand Down
Loading