Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dd8a278
feat: harden Codex workplace runtime
SlowGreekMSFT Jul 18, 2026
02665b7
feat: enforce workplace security boundaries
SlowGreekMSFT Jul 18, 2026
a1e6bfb
feat: synchronize Codex lifecycle and worktree recovery
SlowGreekMSFT Jul 18, 2026
d472bb9
feat: preserve worktree state during handoff
SlowGreekMSFT Jul 18, 2026
4017f4a
feat: add native manual thread compaction
SlowGreekMSFT Jul 18, 2026
9e7e5f1
test: align desktop checks with disabled telemetry and updates
SlowGreekMSFT Jul 18, 2026
59f6569
test: permit explicit bare remotes in fixtures
SlowGreekMSFT Jul 18, 2026
7cf1131
feat: wire native Codex review modes
SlowGreekMSFT Jul 18, 2026
8b4e000
feat: discover Codex collaboration modes
SlowGreekMSFT Jul 18, 2026
d796739
feat: preserve unknown Codex protocol events
SlowGreekMSFT Jul 18, 2026
8412dff
feat: support complete Codex input prompts
SlowGreekMSFT Jul 18, 2026
1dcda40
feat: expose native Codex review targets
SlowGreekMSFT Jul 18, 2026
1ef1193
feat: add native Codex MCP management
SlowGreekMSFT Jul 18, 2026
04a0877
docs: record native Codex fork surfaces
SlowGreekMSFT Jul 18, 2026
2dd48d6
test: isolate server router layers
SlowGreekMSFT Jul 18, 2026
805bf6b
feat: persist native Codex thread forks
SlowGreekMSFT Jul 18, 2026
8884b94
test: wait for interrupted transcript state
SlowGreekMSFT Jul 18, 2026
09efd4c
feat: link native Codex thread history
SlowGreekMSFT Jul 18, 2026
4e38cfa
feat: distinguish managed worktrees
SlowGreekMSFT Jul 18, 2026
5c3e42a
docs: record unsupported native thread mutations
SlowGreekMSFT Jul 18, 2026
f43b482
feat: send structured Codex skills and mentions
SlowGreekMSFT Jul 18, 2026
2ef2414
feat: manage inactive Codex threads
SlowGreekMSFT Jul 18, 2026
be34a80
test: serialize stateful router effects
SlowGreekMSFT Jul 18, 2026
ddcfdfb
test: prove Codex structured and select semantics
SlowGreekMSFT Jul 18, 2026
89170b4
fix: preserve composer structured inputs
SlowGreekMSFT Jul 18, 2026
3cdf77d
test: isolate Effect HTTP router suites
SlowGreekMSFT Jul 18, 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
22 changes: 22 additions & 0 deletions .github/workflows/workplace-target-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Workplace target guard

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read

jobs:
require-workplace-target:
if: github.repository == 'SlowGreek/t3code'
runs-on: ubuntu-latest
steps:
- name: Require codex-workplace base
env:
BASE_REF: ${{ github.base_ref }}
run: |
if [ "$BASE_REF" != "codex-workplace" ]; then
echo "::error::Fork product changes must target codex-workplace, not $BASE_REF."
exit 1
fi
3 changes: 0 additions & 3 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
"smoke-test": "node scripts/smoke-test.mjs"
},
"dependencies": {
"@clerk/electron": "catalog:",
"@clerk/electron-passkeys": "catalog:",
"@effect/platform-node": "catalog:",
"@t3tools/client-runtime": "workspace:*",
"@t3tools/contracts": "workspace:*",
Expand All @@ -23,7 +21,6 @@
"effect": "catalog:",
"electron": "41.5.0",
"electron-store": "^8.2.0",
"electron-updater": "^6.6.2",
"playwright-core": "1.60.0",
"react-grab": "^0.1.32"
},
Expand Down
143 changes: 3 additions & 140 deletions apps/desktop/src/app/DesktopClerk.test.ts
Original file line number Diff line number Diff line change
@@ -1,149 +1,12 @@
import { assert, describe, it } from "@effect/vitest";
import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { beforeEach, vi } from "vite-plus/test";

const { createClerkBridgeMock, storageAdapter, storageMock } = vi.hoisted(() => ({
createClerkBridgeMock: vi.fn(),
storageAdapter: {
getItem: vi.fn(),
setItem: vi.fn(),
removeItem: vi.fn(),
},
storageMock: vi.fn(),
}));

vi.mock("@clerk/electron", () => ({
createClerkBridge: createClerkBridgeMock,
}));

vi.mock("@clerk/electron/storage", () => ({
storage: storageMock,
}));

import * as DesktopClerk from "./DesktopClerk.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

const makeDesktopClerkLayer = (isDevelopment = true) => {
const environment = DesktopEnvironment.DesktopEnvironment.of({
stateDir: "/tmp/t3-state",
isDevelopment,
} as unknown as DesktopEnvironment.DesktopEnvironment["Service"]);

return DesktopClerk.layer.pipe(
Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)),
);
};

describe("DesktopClerk", () => {
beforeEach(() => {
createClerkBridgeMock.mockReset();
storageMock.mockReset();
});

it("derives the Clerk Frontend API hostname used by the desktop CSP", () => {
it("permanently excludes Clerk hosts from the desktop CSP", () => {
const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`;

assert.equal(
DesktopClerk.resolveDesktopClerkFrontendApiHostname(publishableKey),
"clerk.t3.codes",
);
assert.equal(DesktopClerk.resolveDesktopClerkFrontendApiHostname(""), undefined);
assert.equal(DesktopClerk.resolveDesktopClerkFrontendApiHostname("invalid"), undefined);
});

it.effect("acquires and releases the SDK bridge with the layer", () => {
const cleanup = vi.fn();
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({ cleanup });

return Effect.gen(function* () {
yield* Effect.scoped(Layer.build(makeDesktopClerkLayer()));

assert.deepEqual(createClerkBridgeMock.mock.calls, [
[
{
storage: storageAdapter,
passkeys: true,
renderer: { scheme: "t3code-dev", host: "app" },
},
],
]);
assert.equal(cleanup.mock.calls.length, 1);
storageMock.mockClear();
createClerkBridgeMock.mockClear();
});
});

it.effect("preserves bridge initialization failures", () => {
const cause = new Error("bridge initialization failed");
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockImplementationOnce(() => {
throw cause;
});

return Effect.gen(function* () {
const error = yield* Effect.scoped(Layer.build(makeDesktopClerkLayer())).pipe(Effect.flip);

assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeInitializationError);
assert.equal(error.stateDir, "/tmp/t3-state");
assert.equal(error.isDevelopment, true);
assert.strictEqual(error.cause, cause);
assert.equal(
error.message,
'Failed to initialize the desktop Clerk bridge for state directory "/tmp/t3-state" (development: true).',
);
});
});

it.effect("preserves bridge cleanup failures", () => {
const cause = new Error("bridge cleanup failed");
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({
cleanup: () => {
throw cause;
},
});

return Effect.gen(function* () {
const exit = yield* Effect.exit(Effect.scoped(Layer.build(makeDesktopClerkLayer(false))));

assert.equal(exit._tag, "Failure");
if (exit._tag === "Failure") {
const error = Cause.squash(exit.cause);
assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeCleanupError);
assert.equal(error.stateDir, "/tmp/t3-state");
assert.equal(error.isDevelopment, false);
assert.strictEqual(error.cause, cause);
assert.equal(
error.message,
'Failed to clean up the desktop Clerk bridge for state directory "/tmp/t3-state" (development: false).',
);
}
});
});

it.each([
{ isDevelopment: true, scheme: "t3code-dev" },
{ isDevelopment: false, scheme: "t3code" },
])("configures the SDK with the $scheme renderer origin", ({ isDevelopment, scheme }) => {
const bridge = { cleanup: vi.fn() };
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue(bridge);

assert.equal(DesktopClerk.createDesktopClerkBridge("/tmp/t3-state", isDevelopment), bridge);
assert.deepEqual(storageMock.mock.calls, [[{ path: "/tmp/t3-state" }]]);
assert.deepEqual(createClerkBridgeMock.mock.calls, [
[
{
storage: storageAdapter,
passkeys: true,
renderer: { scheme, host: "app" },
},
],
]);
storageMock.mockClear();
createClerkBridgeMock.mockClear();
assert.equal(DesktopClerk.resolveDesktopClerkFrontendApiHostname(publishableKey), undefined);
assert.equal(DesktopClerk.desktopClerkFrontendApiHostname, undefined);
});
});
101 changes: 13 additions & 88 deletions apps/desktop/src/app/DesktopClerk.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,16 @@
import { createClerkBridge } from "@clerk/electron";
import { storage } from "@clerk/electron/storage";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as Scope from "effect/Scope";

import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
import * as ElectronWindow from "../electron/ElectronWindow.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined;

export class DesktopClerkBridgeInitializationError extends Schema.TaggedErrorClass<DesktopClerkBridgeInitializationError>()(
"DesktopClerkBridgeInitializationError",
{
stateDir: Schema.String,
isDevelopment: Schema.Boolean,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to initialize the desktop Clerk bridge for state directory "${this.stateDir}" (development: ${this.isDevelopment}).`;
}
}

export class DesktopClerkBridgeCleanupError extends Schema.TaggedErrorClass<DesktopClerkBridgeCleanupError>()(
"DesktopClerkBridgeCleanupError",
{
stateDir: Schema.String,
isDevelopment: Schema.Boolean,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to clean up the desktop Clerk bridge for state directory "${this.stateDir}" (development: ${this.isDevelopment}).`;
}
}

/**
* Compatibility service retaining desktop single-instance behavior after the
* workplace distribution removed the Clerk/T3 Connect SDK bridge.
*/
export class DesktopClerk extends Context.Service<
DesktopClerk,
{
Expand All @@ -53,60 +23,15 @@ export class DesktopClerk extends Context.Service<
>()("@t3tools/desktop/app/DesktopClerk") {}

export function resolveDesktopClerkFrontendApiHostname(
publishableKey: string | undefined,
): string | undefined {
const normalizedKey = publishableKey?.trim();
if (!normalizedKey) return undefined;

try {
return clerkFrontendApiHostnameFromPublishableKey(normalizedKey);
} catch {
return undefined;
}
}

export const desktopClerkFrontendApiHostname = resolveDesktopClerkFrontendApiHostname(
typeof __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__ === "undefined"
? undefined
: __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__,
);

export function createDesktopClerkBridge(stateDir: string, isDevelopment: boolean) {
return createClerkBridge({
storage: storage({ path: stateDir }),
passkeys: true,
renderer: {
scheme: ElectronProtocol.getDesktopScheme(isDevelopment),
host: ElectronProtocol.DESKTOP_HOST,
},
});
_publishableKey: string | undefined,
): undefined {
return undefined;
}

export const make = Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
yield* Effect.acquireRelease(
Effect.try({
try: () => createDesktopClerkBridge(environment.stateDir, environment.isDevelopment),
catch: (cause) =>
new DesktopClerkBridgeInitializationError({
stateDir: environment.stateDir,
isDevelopment: environment.isDevelopment,
cause,
}),
}),
(bridge) =>
Effect.try({
try: () => bridge.cleanup(),
catch: (cause) =>
new DesktopClerkBridgeCleanupError({
stateDir: environment.stateDir,
isDevelopment: environment.isDevelopment,
cause,
}),
}).pipe(Effect.orDie),
);
export const desktopClerkFrontendApiHostname = undefined;

return DesktopClerk.of({
export const make = Effect.succeed(
DesktopClerk.of({
configure: Effect.gen(function* () {
const electronApp = yield* ElectronApp.ElectronApp;
const electronWindow = yield* ElectronWindow.ElectronWindow;
Expand All @@ -128,8 +53,8 @@ export const make = Effect.gen(function* () {
}),
);
});
}).pipe(Effect.withSpan("desktop.clerk.configure")),
});
});
}).pipe(Effect.withSpan("desktop.singleInstance.configure")),
}),
);

export const layer = Layer.effect(DesktopClerk, make);
23 changes: 5 additions & 18 deletions apps/desktop/src/app/DesktopConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,6 @@ const trimmedString = (name: string) =>
const optionalBoolean = (name: string) =>
Config.boolean(name).pipe(Config.option, Config.map(Option.getOrElse(() => false)));

const commaSeparatedStrings = (name: string) =>
trimmedString(name).pipe(
Config.map(
Option.match({
onNone: () => [],
onSome: (value) =>
value
.split(",")
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0),
}),
),
);

const compactEnv = (env: Readonly<Record<string, string | undefined>>): Record<string, string> =>
Object.fromEntries(
Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined),
Expand All @@ -41,14 +27,15 @@ export const DesktopConfig = Config.all({
devRemoteT3ServerEntryPath: trimmedString("T3CODE_DEV_REMOTE_T3_SERVER_ENTRY_PATH"),
configuredBackendPort: Config.port("T3CODE_PORT").pipe(Config.option),
commitHashOverride: trimmedString("T3CODE_COMMIT_HASH"),
desktopLanHostOverride: trimmedString("T3CODE_DESKTOP_LAN_HOST"),
desktopHttpsEndpointUrls: commaSeparatedStrings("T3CODE_DESKTOP_HTTPS_ENDPOINTS"),
otlpTracesUrl: trimmedString("T3CODE_OTLP_TRACES_URL"),
// Workplace builds cannot be configured into a network-exposed or OTLP-exporting mode.
desktopLanHostOverride: Config.succeed(Option.none<string>()),
desktopHttpsEndpointUrls: Config.succeed<ReadonlyArray<string>>([]),
otlpTracesUrl: Config.succeed(Option.none<string>()),
otlpExportIntervalMs: Config.int("T3CODE_OTLP_EXPORT_INTERVAL_MS").pipe(
Config.withDefault(10_000),
),
appImagePath: trimmedString("APPIMAGE"),
disableAutoUpdate: optionalBoolean("T3CODE_DISABLE_AUTO_UPDATE"),
disableAutoUpdate: Config.succeed(true),
mockUpdates: optionalBoolean("T3CODE_DESKTOP_MOCK_UPDATES"),
mockUpdateServerPort: Config.port("T3CODE_DESKTOP_MOCK_UPDATE_SERVER_PORT").pipe(
Config.withDefault(3000),
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/app/DesktopEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe("DesktopEnvironment", () => {
assert.deepEqual(environment.devRemoteT3ServerEntryPath, Option.some("/remote/server.mjs"));
assert.deepEqual(environment.configuredBackendPort, Option.some(4949));
assert.deepEqual(environment.commitHashOverride, Option.some("0123456789abcdef"));
assert.deepEqual(environment.otlpTracesUrl, Option.some("http://127.0.0.1:4318/v1/traces"));
assert.deepEqual(environment.otlpTracesUrl, Option.none());
assert.equal(environment.otlpExportIntervalMs, 2500);
}),
);
Expand Down
Loading
Loading