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
30 changes: 17 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
# passes only when every other job does.
lint:
name: Lint
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand Down Expand Up @@ -57,7 +57,7 @@ jobs:

typecheck:
name: Typecheck
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand All @@ -83,7 +83,7 @@ jobs:

build:
name: Build
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand All @@ -99,7 +99,9 @@ jobs:
# Runs in the background while Vite+ installs; the step before the build waits for it.
- name: Start installing browser secret helper build libraries
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
for mirror_file in /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources; do
if test -f "$mirror_file"; then sudo sed -i 's|http://|https://|g' "$mirror_file"; fi
done
(
set +e
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
Expand Down Expand Up @@ -137,7 +139,7 @@ jobs:
# per runner is unchanged.
test:
name: Test
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand All @@ -154,7 +156,9 @@ jobs:
# the background while Vite+ installs; the step before the tests waits for it.
- name: Start installing browser secret helper build libraries
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
for mirror_file in /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources; do
if test -f "$mirror_file"; then sudo sed -i 's|http://|https://|g' "$mirror_file"; fi
done
(
set +e
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
Expand Down Expand Up @@ -191,7 +195,7 @@ jobs:
# started last and ran alone for about 40s after everything else finished.
test_web:
name: Test Web
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand Down Expand Up @@ -220,7 +224,7 @@ jobs:
# split files by recorded duration (apps/server/src/testUtils/weightedShardSequencer.ts).
test_server:
name: Test Server ${{ matrix.shard }}
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
strategy:
fail-fast: false
Expand Down Expand Up @@ -287,7 +291,7 @@ jobs:
# for checks that take under 3s, on the critical path of every PR.
rust:
name: Rust
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand Down Expand Up @@ -321,7 +325,7 @@ jobs:
# the diff cannot be resolved, the lint runs.
mobile_native_changes:
name: Mobile Native Changes
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 5
permissions:
contents: read
Expand Down Expand Up @@ -399,7 +403,7 @@ jobs:
# Skip only on an explicit "no": a gate job that failed or errored leaves the
# output empty, and that must run the lint rather than silently skip it.
if: ${{ !cancelled() && needs.mobile_native_changes.outputs.changed != 'false' }}
runs-on: blacksmith-6vcpu-macos-26
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-6vcpu-macos-26' || 'macos-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand Down Expand Up @@ -427,7 +431,7 @@ jobs:

release_smoke:
name: Release Smoke
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
Expand Down Expand Up @@ -471,7 +475,7 @@ jobs:
mobile_native_static_analysis,
release_smoke,
]
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 5
steps:
- name: Require every job to pass
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.0.44",
"version": "0.0.45-preview.20261008.1",
"private": true,
"type": "module",
"main": "dist-electron/boot.cjs",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.0.44",
"version": "0.0.45-preview.20261008.1",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
85 changes: 36 additions & 49 deletions apps/server/src/provider/Layers/CodexSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import * as EffectCodexSchema from "effect-codex-app-server/schema";

import { buildCodexInitializeParams } from "./CodexProvider.ts";
import { codexSessionAppServerArgs } from "./codexLaunchArgs.ts";
import * as CodexCapacityRetry from "./codexCapacityRetry.ts";
import { expandHomePath } from "../../pathExpansion.ts";
import {
buildCodexAdditionalContext,
Expand Down Expand Up @@ -1393,6 +1394,7 @@ export const makeCodexSessionRuntime = (
const sessionRef = yield* Ref.make<ProviderSession>(initialSession);
const offerEvent = (event: ProviderEvent) => Queue.offer(events, event).pipe(Effect.asVoid);

const capacityTurnAliases = new Map<string, string>();
const emitEvent = (event: Omit<ProviderEvent, "id" | "provider" | "createdAt">) =>
Effect.gen(function* () {
const id = yield* randomUUIDv4("provider-event");
Expand All @@ -1402,6 +1404,9 @@ export const makeCodexSessionRuntime = (
...(options.providerInstanceId ? { providerInstanceId: options.providerInstanceId } : {}),
createdAt: yield* nowIso,
...event,
...(event.turnId
? { turnId: TurnId.make(capacityTurnAliases.get(event.turnId) ?? event.turnId) }
: {}),
});
});
const emitSessionEvent = (method: string, message: string) =>
Expand Down Expand Up @@ -1901,12 +1906,18 @@ export const makeCodexSessionRuntime = (
),
);

const capacityRetry = yield* CodexCapacityRetry.makeCapacityRetry({
client,
aliases: capacityTurnAliases,
update: (patch) => updateSession(sessionRef, patch),
terminal: CodexCapacityRetry.makeTerminalEmitter(emitEvent, options.threadId),
});
const handleRawNotification = (notification: CodexServerNotification) =>
Effect.gen(function* () {
const isMemoryConsolidationNotification =
suppressMemoryConsolidationNotification(notification);

const payload = notification.params;
let payload = notification.params;
const route = readRouteFields(notification);
const collabReceiverTurns = yield* Ref.get(collabReceiverTurnsRef);
const childParentTurnId = (() => {
Expand Down Expand Up @@ -1983,6 +1994,20 @@ export const makeCodexSessionRuntime = (
return;
}

if (
notification.method === "error" &&
(!notification.params.threadId || notification.params.threadId === suppressRootId) &&
(yield* capacityRetry.waiting)
) {
payload = { ...notification.params, willRetry: true };
}
if (
notification.method === "turn/completed" &&
notification.params.turn.status === "failed" &&
(yield* capacityRetry.waiting)
)
return;

if (isMemoryConsolidationNotification) {
return;
}
Expand Down Expand Up @@ -2052,54 +2077,11 @@ export const makeCodexSessionRuntime = (
),
);

yield* client.handleServerNotification("turn/started", (payload) =>
currentSessionProviderThreadId.pipe(
Effect.flatMap((providerThreadId) => {
if (providerThreadId && payload.threadId !== providerThreadId) {
return Effect.void;
}
return updateSession(sessionRef, {
status: "running",
activeTurnId: TurnId.make(payload.turn.id),
});
}),
),
);

yield* client.handleServerNotification("turn/completed", (payload) =>
currentSessionProviderThreadId.pipe(
Effect.flatMap((providerThreadId) => {
if (providerThreadId && payload.threadId !== providerThreadId) {
return Effect.void;
}
const lastError =
payload.turn.status === "failed" && "error" in payload.turn && payload.turn.error
? payload.turn.error.message
: undefined;
return updateSession(sessionRef, {
status: payload.turn.status === "failed" ? "error" : "ready",
activeTurnId: undefined,
...(lastError ? { lastError } : {}),
});
}),
),
);

yield* client.handleServerNotification("error", (payload) =>
currentSessionProviderThreadId.pipe(
Effect.flatMap((providerThreadId) => {
const payloadThreadId = payload.threadId;
if (providerThreadId && payloadThreadId && payloadThreadId !== providerThreadId) {
return Effect.void;
}
const errorMessage = payload.error.message;
const willRetry = payload.willRetry;
return updateSession(sessionRef, {
status: willRetry ? "running" : "error",
...(errorMessage ? { lastError: errorMessage } : {}),
});
}),
),
yield* CodexCapacityRetry.registerCodexLifecycle(
client,
currentSessionProviderThreadId,
(patch) => updateSession(sessionRef, patch),
capacityRetry,
);

yield* client.handleServerRequest("item/commandExecution/requestApproval", (payload) =>
Expand Down Expand Up @@ -2520,6 +2502,7 @@ export const makeCodexSessionRuntime = (
});

const close = Effect.gen(function* () {
yield* capacityRetry.cancel;
const alreadyClosed = yield* Ref.getAndSet(closedRef, true);
if (alreadyClosed) {
return;
Expand Down Expand Up @@ -2582,6 +2565,7 @@ export const makeCodexSessionRuntime = (
options.mcpCapabilities,
),
});
yield* capacityRetry.begin(params);
yield* Ref.set(lastAdditionalContextRef, params.additionalContext);
const rawResponse = yield* client.raw.request("turn/start", params);
const response = yield* decodeV2TurnStartResponse(rawResponse).pipe(
Expand All @@ -2593,6 +2577,7 @@ export const makeCodexSessionRuntime = (
),
),
);
yield* capacityRetry.accept(response.turn.id);
const turnId = TurnId.make(response.turn.id);
yield* updateSession(sessionRef, (session) => ({
status: "running",
Expand All @@ -2613,6 +2598,7 @@ export const makeCodexSessionRuntime = (
}),
interruptTurn: (turnId) =>
Effect.gen(function* () {
const cancelledCapacityRetry = yield* capacityRetry.cancel;
const providerThreadId = yield* readProviderThreadId;
const session = yield* Ref.get(sessionRef);
// Settle parked approvals FIRST. The transport answers server
Expand Down Expand Up @@ -2645,6 +2631,7 @@ export const makeCodexSessionRuntime = (
.pipe(Effect.timeoutOption("3 seconds"), Effect.ignore),
{ concurrency: 8, discard: true },
).pipe(Effect.timeoutOption("10 seconds"), Effect.ignore);
if (cancelledCapacityRetry) return;
const effectiveTurnId = turnId ?? session.activeTurnId;
if (!effectiveTurnId) {
return;
Expand Down
Loading
Loading