Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/mcp-default-user-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

Send a default User-Agent on remote MCP requests so servers that reject requests without one accept the connection. A configured User-Agent header still wins.
5 changes: 5 additions & 0 deletions .changeset/mcp-probe-user-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

Send the default User-Agent on the MCP endpoint probe and its OAuth metadata request too, so adding a server that rejects requests without one no longer fails. A configured User-Agent still wins.
5 changes: 5 additions & 0 deletions .changeset/v2-import-sidebar-link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@executor-js/cloud": patch
---

Link eligible single-member free organizations to Executor v2's workspace import from the sidebar.
5 changes: 5 additions & 0 deletions .changeset/v2-moved-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@executor-js/cloud": patch
---

Tell users when their organization has moved to Executor v2, with a link to it in v2 and a way to say why they are staying on v1.
1 change: 1 addition & 0 deletions apps/cloud/src/account/org-api-key-revoke.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ const stubDirectory = Layer.succeed(MemberDirectory)({
const stubAutumn = Layer.succeed(AutumnService)({
use: () => Effect.die("revoke does not touch billing"),
ensureCustomer: () => Effect.die("revoke does not touch billing"),
findCustomer: () => Effect.die("revoke does not touch billing"),
checkExecutionBalance: () => Effect.die("revoke does not touch billing"),
trackExecution: () => Effect.void,
setMemberSeats: () => Effect.void,
Expand Down
24 changes: 24 additions & 0 deletions apps/cloud/src/auth/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ const AuthOrganizationsResponse = Schema.Struct({
activeOrganizationId: Schema.NullOr(Schema.String),
});

// Executor v2's workspace import for the selected org: its page URL when v2
// would let the signed-in user import the org, otherwise null. Nothing else
// about the membership or billing reads behind it is returned.
//
// `moved` is set instead when the org has already been imported (v2 records
// the move on the WorkOS organization): where it lives in v2 and when it
// moved. It drives a notice only; the org keeps working in v1.
const V2MovedResponse = Schema.Struct({
v2Url: Schema.String,
v2Slug: Schema.String,
movedAt: Schema.String,
});

const V2ImportResponse = Schema.Struct({
url: Schema.NullOr(Schema.String),
moved: Schema.NullOr(V2MovedResponse),
});

const CreateOrganizationBody = Schema.Struct({
name: Schema.String,
});
Expand Down Expand Up @@ -231,6 +249,12 @@ export class CloudAuthApi extends HttpApiGroup.make("cloudAuth")
error: [WorkOSError, UserStoreError, MemberDirectoryError],
}),
)
.add(
HttpApiEndpoint.get("v2Import", "/auth/v2-import", {
success: V2ImportResponse,
error: NoOrganization,
}),
)
.add(
HttpApiEndpoint.post("createOrganization", "/auth/create-organization", {
payload: CreateOrganizationBody,
Expand Down
14 changes: 14 additions & 0 deletions apps/cloud/src/auth/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {
type AuthorizeOrganizationOptions,
} from "./organization";
import { mcpSessionStub } from "@executor-js/cloudflare/mcp/session-stub";
import { liveV2ImportSource, v2ImportAnswer } from "./v2-import";
import { liveV2MovedSource } from "./v2-moved";

const COOKIE_OPTIONS = {
path: "/",
Expand Down Expand Up @@ -439,6 +441,18 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group(
};
}),
)
.handle("v2Import", () =>
Effect.gen(function* () {
const session = yield* requireSelectedOrganization;
return yield* v2ImportAnswer({
env,
importSource: liveV2ImportSource,
movedSource: liveV2MovedSource,
userId: session.accountId,
organizationId: session.organizationId,
});
}),
)
.handle("createOrganization", ({ payload }) =>
Effect.gen(function* () {
const workos = yield* WorkOSClient;
Expand Down
6 changes: 6 additions & 0 deletions apps/cloud/src/auth/mirror-feeders.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ const seedOrganization = (id: string) =>
const stubAutumn = Layer.succeed(AutumnService)({
use: () => Effect.die("feeders do not read billing"),
ensureCustomer: () => Effect.void,
findCustomer: () => Effect.die("feeders do not read billing"),
checkExecutionBalance: () => Effect.die("feeders do not check balances"),
trackExecution: () => Effect.void,
setMemberSeats: () => Effect.void,
Expand Down Expand Up @@ -673,6 +674,7 @@ describe("session handlers read membership from the mirror", () => {
const deletingAutumn = Layer.succeed(AutumnService)({
use: () => Effect.succeed({} as never),
ensureCustomer: () => Effect.void,
findCustomer: () => Effect.die("feeders do not read billing"),
checkExecutionBalance: () => Effect.die("deletion does not check balances"),
trackExecution: () => Effect.void,
setMemberSeats: () => Effect.void,
Expand Down Expand Up @@ -866,6 +868,7 @@ describe("session handlers read membership from the mirror", () => {
return Effect.fail(failure);
}),
ensureCustomer: () => Effect.void,
findCustomer: () => Effect.die("feeders do not read billing"),
checkExecutionBalance: () => Effect.die("deletion does not check balances"),
trackExecution: () => Effect.void,
setMemberSeats: () => Effect.void,
Expand Down Expand Up @@ -1234,6 +1237,7 @@ describe("account service writes through to the mirror", () => {
subscriptions: [{ planId: "team", status: "active" }],
} as never),
ensureCustomer: () => Effect.void,
findCustomer: () => Effect.die("feeders do not read billing"),
checkExecutionBalance: () => Effect.die("invite does not check balances"),
trackExecution: () => Effect.void,
setMemberSeats: () => Effect.void,
Expand Down Expand Up @@ -1314,6 +1318,7 @@ describe("account service writes through to the mirror", () => {
const freeAutumn = Layer.succeed(AutumnService)({
use: () => Effect.succeed({ subscriptions: [] } as never),
ensureCustomer: () => Effect.void,
findCustomer: () => Effect.die("feeders do not read billing"),
checkExecutionBalance: () => Effect.die("invite does not check balances"),
trackExecution: () => Effect.void,
setMemberSeats: () => Effect.void,
Expand Down Expand Up @@ -1541,6 +1546,7 @@ describe("seat reporter", () => {
const recording = Layer.succeed(AutumnService)({
use: () => Effect.die("the seat reporter sets seats, it does not read"),
ensureCustomer: () => Effect.void,
findCustomer: () => Effect.die("feeders do not read billing"),
checkExecutionBalance: () => Effect.die("the seat reporter does not check balances"),
trackExecution: () => Effect.void,
setMemberSeats: (organizationId, seats) =>
Expand Down
Loading
Loading