Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ee80eb6
Add assistant response copy action
leonardoxr Mar 28, 2026
d493b44
Merge branch 'main' into issue-1455-assistant-response-copy
leonardoxr Mar 28, 2026
00f34de
Fix ordered list copy indentation
leonardoxr Mar 28, 2026
45a53c2
Refine assistant copy footer behavior
leonardoxr Mar 28, 2026
3f2e028
Merge branch 'main' into issue-1455-assistant-response-copy
leonardoxr Mar 28, 2026
484fcc5
Address assistant copy review feedback
leonardoxr Mar 28, 2026
65c6a83
Re-measure timeline on copy format change
leonardoxr Mar 28, 2026
4803895
Use LRU cache for assistant plain text
leonardoxr Mar 28, 2026
0897f66
Preserve leading indentation in plain text copy
leonardoxr Mar 28, 2026
05ec87d
Merge branch 'main' into issue-1455-assistant-response-copy
leonardoxr Mar 28, 2026
059279b
Collapse soft line breaks in plain text copy
leonardoxr Mar 28, 2026
11ec9d7
feat(web): add queue and steer follow-up behavior
leonardoxr Mar 28, 2026
8f0600a
Merge branch 'main' into feat/follow-up-behavior-1462
leonardoxr Mar 28, 2026
3a033b0
feat: move queued follow-ups into orchestration
leonardoxr Mar 28, 2026
81ba2c4
Merge origin/main into feat/follow-up-behavior-1462
leonardoxr Mar 28, 2026
91df7c2
fix: address queued follow-up review feedback
leonardoxr Mar 28, 2026
b263589
fix: revoke queued follow-up preview urls on clear
leonardoxr Mar 28, 2026
775ee42
fix: harden queued follow-up composer flow
leonardoxr Mar 28, 2026
72d67ca
Add in-thread search for chat conversations
leonardoxr Mar 28, 2026
7828e85
Address thread search review feedback
leonardoxr Mar 28, 2026
cace8fc
fix: address queued follow-up review regressions
leonardoxr Mar 28, 2026
2fd077b
Merge branch 'main' into feat/thread-search-1486
leonardoxr Mar 28, 2026
f4587c9
Merge branch 'main' into feat/follow-up-behavior-1462
leonardoxr Mar 28, 2026
340789c
Address follow-up thread search review feedback
leonardoxr Mar 28, 2026
511b760
Merge branch 'main' into feat/thread-search-1486
juliusmarminge Mar 28, 2026
06d68cd
fix: harden queued follow-up dispatch
leonardoxr Mar 28, 2026
776dfcf
Align thread search with rendered content
leonardoxr Mar 28, 2026
46f95da
fix: resolve queued steer review feedback
leonardoxr Mar 28, 2026
50affe9
Merge origin/main into feat/thread-search-1486
leonardoxr Mar 29, 2026
d92e28b
Merge origin/main into feat/follow-up-behavior-1462
leonardoxr Mar 29, 2026
a652fd5
Align thread search with rendered markdown text
leonardoxr Mar 29, 2026
1ad44cc
fix: resolve remaining queued follow-up review comments
leonardoxr Mar 29, 2026
f049043
Merge branch 'main' into feat/follow-up-behavior-1462
leonardoxr Mar 29, 2026
314644c
Merge origin/main into issue-1455-assistant-response-copy
leonardoxr Mar 29, 2026
f9bfd72
Merge branch 'main' into feat/thread-search-1486
leonardoxr Mar 29, 2026
4aa8819
Merge remote-tracking branch 'origin/feat/follow-up-behavior-1462' in…
leonardoxr Mar 29, 2026
1ef1344
Merge remote-tracking branch 'origin/issue-1455-assistant-response-co…
leonardoxr Mar 29, 2026
88bc525
Merge remote-tracking branch 'origin/feat/thread-search-1486' into ma…
leonardoxr Mar 29, 2026
42bdfa9
Fix post-merge typecheck regressions
leonardoxr Mar 29, 2026
a7584d2
Support branch-based fork releases
leonardoxr Mar 29, 2026
7edd703
Allow prerelease desktop auto-updates
leonardoxr Mar 29, 2026
da5d142
Merge remote-tracking branch 'origin/main' into main-xavier
leonardoxr Apr 1, 2026
65c71eb
Fix post-merge typecheck and fixture regressions
leonardoxr Apr 1, 2026
b060bb0
Merge remote-tracking branch 'origin/main' into main-xavier
leonardoxr Apr 6, 2026
434634e
Merge remote-tracking branch 'origin/main' into main-xavier
leonardoxr Apr 8, 2026
01a42b9
Merge upstream main into main-xavier
leonardoxr May 11, 2026
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
2 changes: 1 addition & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"**/routeTree.gen.ts"
],
"plugins": ["eslint", "oxc", "react", "unicorn", "typescript"],
"jsPlugins": ["./oxlint-plugin-t3code/index.ts"],
"jsPlugins": ["./oxlint-plugin-t3code/index.js"],
"categories": {
"correctness": "warn",
"suspicious": "warn",
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ brew install --cask t3-code
yay -S t3code-bin
```

## Fork Releases

If you want desktop releases and auto-updates from a fork or branch such as `main-xavier`, use the `Release Desktop` GitHub Actions workflow with:

- `target_branch`: the branch you want to release from, for example `main-xavier`
- `publish_cli`: `false` for fork/private desktop-only releases
- `finalize_version_bump`: `false` unless you want the workflow to push the version bump commit back to that branch

The packaged desktop app will target the current GitHub repository for updater assets during CI builds, so fork releases will update from that fork's GitHub Releases.

## Some notes

We are very very early in this project. Expect bugs.
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as DesktopClientSettings from "./DesktopClientSettings.ts";

const clientSettings: ClientSettings = {
autoOpenPlanSidebar: false,
assistantResponseCopyFormat: "markdown",
confirmThreadArchive: true,
confirmThreadDelete: false,
dismissedProviderUpdateNotificationKeys: [],
Expand Down
17 changes: 13 additions & 4 deletions apps/server/integration/OrchestrationEngineHarness.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,15 @@ export interface OrchestrationIntegrationHarness {
timeoutMs?: number,
): Effect.Effect<Receipt, never>;
};
readonly startReactor: Effect.Effect<void, never>;
readonly dispose: Effect.Effect<void, never>;
}

interface MakeOrchestrationIntegrationHarnessOptions {
readonly provider?: ProviderDriverKind;
readonly realCodex?: boolean;
readonly rootDir?: string;
readonly autoStartReactor?: boolean;
}

export const makeOrchestrationIntegrationHarness = (
Expand All @@ -244,9 +247,11 @@ export const makeOrchestrationIntegrationHarness = (
makeAdapterRegistryMock({ [adapterHarness.provider]: adapterHarness.adapter }),
)
: null;
const rootDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-orchestration-integration-",
});
const rootDir =
options?.rootDir ??
(yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-orchestration-integration-",
}));
const workspaceDir = path.join(rootDir, "workspace");
const { stateDir, dbPath } = yield* deriveServerPaths(rootDir, undefined).pipe(
Effect.provideService(Path.Path, path),
Expand Down Expand Up @@ -404,9 +409,12 @@ export const makeOrchestrationIntegrationHarness = (
).pipe(Effect.orDie);

const scope = yield* Scope.make("sequential");
yield* tryRuntimePromise("start OrchestrationReactor", () =>
const startReactor = tryRuntimePromise("start OrchestrationReactor", () =>
runtime.runPromise(reactor.start().pipe(Scope.provide(scope))),
).pipe(Effect.orDie);
if (options?.autoStartReactor !== false) {
yield* startReactor;
}
const receiptHistory = yield* Ref.make<ReadonlyArray<OrchestrationRuntimeReceipt>>([]);
yield* Stream.runForEach(runtimeReceiptBus.streamEventsForTest, (receipt) =>
Ref.update(receiptHistory, (history) => [...history, receipt]).pipe(Effect.asVoid),
Expand Down Expand Up @@ -546,6 +554,7 @@ export const makeOrchestrationIntegrationHarness = (
waitForDomainEvent,
waitForPendingApproval,
waitForReceipt,
startReactor,
dispose,
} satisfies OrchestrationIntegrationHarness;
});
96 changes: 96 additions & 0 deletions apps/server/integration/orchestrationEngine.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @effect-diagnostics nodeBuiltinImport:off
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import {
Expand Down Expand Up @@ -106,6 +107,17 @@ function withHarness<A, E>(
).pipe(Effect.provide(NodeServices.layer));
}

function withHarnessOptions<A, E>(
options: Parameters<typeof makeOrchestrationIntegrationHarness>[0],
use: (harness: OrchestrationIntegrationHarness) => Effect.Effect<A, E>,
) {
return Effect.acquireUseRelease(
makeOrchestrationIntegrationHarness(options),
use,
(harness) => harness.dispose,
).pipe(Effect.provide(NodeServices.layer));
}

function withRealCodexHarness<A, E>(
use: (harness: OrchestrationIntegrationHarness) => Effect.Effect<A, E>,
) {
Expand Down Expand Up @@ -266,6 +278,90 @@ it.live("runs a single turn end-to-end and persists checkpoint state in sqlite +
),
);

it.live("replays queued follow-ups after orchestration restarts", () =>
Effect.gen(function* () {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-orchestration-queue-restart-"));

yield* withHarnessOptions({ rootDir }, (harness) =>
Effect.gen(function* () {
yield* seedProjectAndThread(harness);

yield* harness.engine.dispatch({
type: "thread.queued-follow-up.enqueue",
commandId: CommandId.makeUnsafe("cmd-queued-follow-up-restart-enqueue"),
threadId: THREAD_ID,
followUp: {
id: "follow-up-restart-1",
createdAt: nowIso(),
prompt: "Resume this after restart",
attachments: [],
terminalContexts: [],
modelSelection: {
instanceId: ProviderInstanceId.make("codex"),
provider: "codex",
model: DEFAULT_MODEL_BY_PROVIDER[ProviderDriverKind.make("codex")] ?? DEFAULT_MODEL,
},
runtimeMode: "approval-required",
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
lastSendError: null,
},
createdAt: nowIso(),
});

const queuedThread = yield* harness.waitForThread(
THREAD_ID,
(thread) => thread.queuedFollowUps.length === 1,
);
assert.equal(queuedThread.queuedFollowUps[0]?.prompt, "Resume this after restart");
}),
);

yield* withHarnessOptions({ rootDir, autoStartReactor: false }, (harness) =>
Effect.gen(function* () {
yield* harness.adapterHarness!.queueTurnResponseForNextSession({
events: [
{
type: "turn.started",
...runtimeBase("evt-queued-restart-1", "2026-03-28T12:05:00.000Z"),
threadId: THREAD_ID,
turnId: FIXTURE_TURN_ID,
},
{
type: "message.delta",
...runtimeBase("evt-queued-restart-2", "2026-03-28T12:05:00.050Z"),
threadId: THREAD_ID,
turnId: FIXTURE_TURN_ID,
delta: "Recovered queued follow-up output.\n",
},
{
type: "turn.completed",
...runtimeBase("evt-queued-restart-3", "2026-03-28T12:05:00.100Z"),
threadId: THREAD_ID,
turnId: FIXTURE_TURN_ID,
status: "completed",
},
],
});

yield* harness.startReactor;

const recoveredThread = yield* harness.waitForThread(
THREAD_ID,
(thread) =>
thread.queuedFollowUps.length === 0 &&
thread.messages.some(
(message) =>
message.role === "assistant" &&
message.text.includes("Recovered queued follow-up output."),
),
);

assert.equal(recoveredThread.queuedFollowUps.length, 0);
}),
);
}).pipe(Effect.provide(NodeServices.layer)),
);

it.live.skipIf(!process.env.CODEX_BINARY_PATH)(
"keeps the same Codex provider thread across runtime mode switches",
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ describe("OrchestrationEngine", () => {
archivedAt: null,
deletedAt: null,
messages: [],
queuedFollowUps: [],
proposedPlans: [],
activities: [],
checkpoints: [],
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/orchestration/Layers/OrchestrationEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,13 @@ const makeOrchestrationEngine = Effect.gen(function* () {
return yield* Deferred.await(result);
});

const getReadModel: OrchestrationEngineShape["getReadModel"] = () =>
Effect.sync(() => commandReadModel);

return {
readEvents,
dispatch,
getReadModel,
// Each access creates a fresh PubSub subscription so that multiple
// consumers (wsServer, ProviderRuntimeIngestion, CheckpointReactor, etc.)
// each independently receive all domain events.
Expand Down
Loading
Loading