Skip to content

Commit 6715100

Browse files
committed
Merge main into cloudflare-mcp-do-hibernation-standalone
Brings the branch up to date with main (78 commits). Non-trivial integration: main added toolkit-scoped MCP (`/mcp/toolkits/<slug>`, an `McpResource` threaded through the session flow) while this branch replaced the old session DO with the hibernatable Agent DO. Ported `resource` onto the new `McpAgentSessionDOBase` (McpSessionInit + SessionMeta) and threaded it through both agent handlers: host-cloudflare always serves the default resource (only `/mcp` routes to the bridge), cloud derives the toolkit resource from the request path. Kept the four old-impl files deleted (main had modified them; no new consumers).
2 parents 3d63ee9 + c85d12c commit 6715100

376 files changed

Lines changed: 22781 additions & 2237 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/core-tools-no-auth-connections.md

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"executor": patch
3+
---
4+
5+
Add a test seam to skip the first-run "keep Executor running in the background?" consent dialog under automation, matching the existing `confirmResetState` seam. Set `EXECUTOR_TEST_AUTO_CONFIRM_BACKGROUND_SERVICE=1` to keep the background service or any other value to decline. When the variable is unset the dialog is shown exactly as before. Native dialogs cannot be answered from CDP or Playwright, so a packaged first-run boot under automation previously blocked at this prompt with no way to proceed.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"executor": patch
3+
---
4+
5+
Fix the desktop app failing to start its local server when the generated auth token begins with a dash. The token is `randomBytes(32).toString("base64url")`, which can start with "-", and the packaged app passed it to the bundled CLI as a separate argument (`--auth-token`, then the token). The CLI then read the leading-dash token as an unknown flag, printed its help, and exited, so the desktop showed a fatal "local Executor server crashed during startup" dialog. This was persistent (the token is saved) and cross-platform, affecting roughly 1 in 64 fresh installs. The token is now passed in the combined `--auth-token=<value>` form so a leading dash is treated as the value.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@executor-js/plugin-graphql": patch
3+
---
4+
5+
Fix the GraphQL plugin generating invalid operations against large schemas, and make field selection caller-controlled instead of a baked-in guess.
6+
7+
Previously each tool froze a recursive, depth- and count-bounded selection at sync time. Against a rich schema (GitLab) this produced invalid GraphQL (composite fields with no sub-selection, nested fields missing required arguments) so every call over a rich return type failed, and the arbitrary bound silently truncated which fields came back.
8+
9+
Generated tools now default to selecting only scalar/enum leaf fields of the return type (always valid, always within a server's query-complexity budget), and expose an optional `select` input carrying a GraphQL selection set so a caller can request nested or list data per call (including supplying nested required arguments). Fixes #1146.

.changeset/openapi-file-emit-descriptions.md

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
name: self-contained-modals
3+
description: "Build modals/dialogs self-contained: form and in-flight state lives inside, closing unmounts it. Use when writing or reviewing a modal/dialog, especially one that owns async work (OAuth popups, timers, subscriptions, AbortControllers). Catches the stuck-on-Connecting class of lifecycle-leak bugs."
4+
---
5+
6+
# Self-contained modals
7+
8+
A modal's **form state and in-flight work live INSIDE the modal**, and **closing
9+
the modal UNMOUNTS that state** so it is destroyed, not hand-reset. Only the
10+
**open/route intent** belongs to the parent (deep links, programmatic open,
11+
reconnect handoffs need it).
12+
13+
## Why
14+
15+
A hand-written `reset()` has to enumerate every field, and it silently drifts out
16+
of sync with state owned by **child hooks the parent can't see**. Unmounting
17+
resets everything for free, including child-hook cleanup effects (cancelling a
18+
dangling server session, clearing a timer, aborting a fetch).
19+
20+
Concrete bug this prevents (executor, add-account-modal): the modal was mounted
21+
unconditionally, so `useOAuthPopupFlow`'s `busy` survived close. `reset()` zeroed
22+
its own booleans but never called `oauthPopup.cancel()`. Abandon the OAuth popup,
23+
close, reopen, and `oauthBusy = false || busy(true) = true`, so the footer is
24+
wedged on "Connecting…" with Close disabled. Unmounting would have cleared `busy`
25+
AND run the hook's cleanup that cancels the server OAuth session.
26+
27+
## How to apply
28+
29+
Default to **genuine conditional unmount**, state inside. When closed the
30+
component returns `null`, so React destroys all of it and runs every child
31+
hook's cleanup. This is the cleanest fix and the one to reach for first:
32+
33+
```tsx
34+
function Parent() {
35+
const [open, setOpen] = useState(false);
36+
return open ? <Modal onClose={() => setOpen(false)} /> : null;
37+
}
38+
```
39+
40+
A key bump (`<Body key={openCount} />`) is **still a manual reset**, just
41+
spelled as a remount. Prefer real unmount; only reach for keyed remount in the
42+
one case below.
43+
44+
That case: **Radix Dialog** (this repo's `components/dialog.tsx`) Content/Overlay
45+
use `data-[state=closed]:animate-out` exit animations, so unmounting the whole
46+
`Dialog` drops the close animation. If you must keep that animation, keep the
47+
`Dialog` + `DialogContent` shell mounted and remount only the state-bearing
48+
**body** per open:
49+
50+
```tsx
51+
function Parent() {
52+
const [open, setOpen] = useState(false);
53+
const [openCount, setOpenCount] = useState(0);
54+
return (
55+
<Dialog open={open} onOpenChange={setOpen}>
56+
<DialogContent>{open ? <ModalBody key={openCount} /> : null}</DialogContent>
57+
</Dialog>
58+
);
59+
}
60+
```
61+
62+
This only works when `DialogContent` is **independent of body state**. If the
63+
shell depends on the body (e.g. a width className driven by the body's current
64+
sub-view), the body must own `DialogContent`, so there is no stable shell to
65+
keep, and genuine unmount (losing the exit animation) is the right call. That is
66+
exactly the executor add-account-modal: it genuinely unmounts and accepts the
67+
lost animation rather than plumbing body state up to a shell.
68+
69+
## Reviewing: flag these smells
70+
71+
1. A hand-written `reset()` exists. Its presence means state outlives the modal;
72+
ask why the modal isn't just unmounted.
73+
2. An always-mounted dialog (rendered unconditionally with an `open` prop) that
74+
owns async/in-flight state: popups, timers, subscriptions, AbortControllers,
75+
server sessions.
76+
3. A busy/loading flag composed from a child hook (e.g. `ccBusy || someHook.busy`)
77+
where `reset()` clears only part of it.

.github/workflows/ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,44 @@ jobs:
7474

7575
- run: bun run test
7676

77+
e2e-local:
78+
name: E2E (stdio MCP)
79+
runs-on: ubuntu-latest
80+
steps:
81+
- uses: actions/checkout@v4
82+
83+
- uses: oven-sh/setup-bun@v2
84+
with:
85+
bun-version: 1.3.11
86+
87+
# The local scenarios boot a real `executor web` (which spawns a Node
88+
# sidecar) and some drive a browser, so pin Node 22 and install Chromium.
89+
- uses: actions/setup-node@v4
90+
with:
91+
node-version: 22
92+
93+
- run: bun install --frozen-lockfile
94+
95+
# `chromium` and the new `chromium-headless-shell` ship as separate
96+
# downloads; the browser-driven scenarios launch the headless shell.
97+
# Install from e2e so bunx resolves ITS pinned playwright (the version the
98+
# tests run against) rather than floating to the latest, which would fetch
99+
# a browser build the test runtime does not look for.
100+
- name: Install Playwright Chromium
101+
run: bunx playwright install --with-deps chromium chromium-headless-shell
102+
working-directory: e2e
103+
104+
# The `local` project is excluded from the default `test` chain (each
105+
# scenario boots its own `executor web`). Run just the stdio MCP scenario
106+
# here: it is the auto-connect / env-as-secret regression guard, and
107+
# running it alone avoids the boot-resource accumulation and the
108+
# pre-existing browser flakiness of the rest of the local suite. Expanding
109+
# to the full `local` project (bun run test:local) is a follow-up once
110+
# those are stabilized.
111+
- name: Run the stdio MCP scenario
112+
run: bunx vitest run --project local local/stdio-mcp.test.ts
113+
working-directory: e2e
114+
77115
desktop-smoke:
78116
name: Desktop smoke build
79117
runs-on: ubuntu-latest

.oxlintrc.jsonc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"vendor/",
154154
"emulators/",
155155
"e2e/runs/",
156+
"e2e/desktop-packaged/linux-vm/repro.mjs",
156157
"node_modules/",
157158
"packages/core/fumadb/",
158159
"packages/core/sdk/src/vendor/json-schema-to-typescript/",

RUNNING.md

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ working instance of X" — read them before inventing a boot path.
4848
screenshots, `session.mp4` + `trace.zip` for browser scenarios, and the
4949
scenario source as `test.ts`.
5050
- `cd e2e && bun run serve` builds the viewer and serves the scenario ×
51-
target matrix over HTTP, bound to all interfaces (reachable over the
52-
tailnet). It prefers port 8901 but walks forward to the next free port if
51+
target matrix over HTTP, bound to all interfaces. It prefers port 8901
52+
but walks forward to the next free port if
5353
that's taken (so concurrent worktrees, or a leaked previous viewer, never
5454
wedge each other) — read the printed `e2e viewer → …` URL for the actual
5555
port. `PORT=…` pins a port explicitly and fails loudly if it's busy. The
@@ -77,14 +77,14 @@ emulator ledger — develop interactively, then crystallize the journey into
7777
a scenario.
7878

7979
```sh
80-
bun run cli up selfhost --share # boot, reachable over the tailnet, stays up
81-
bun run cli up cloud --share # emulated WorkOS+Autumn, tailscale-HTTPS fronted
80+
bun run cli up selfhost --share # boot, shared, stays up
81+
bun run cli up cloud --share # emulated WorkOS+Autumn
8282
bun run cli status # what's running, URLs, creds
8383
bun run cli identity selfhost # fresh identity (headers / cookies / creds)
8484
bun run cli api selfhost tools.list
8585
bun run cli mcp selfhost call execute '{"code":"return 1+1;"}'
8686
bun run cli ledger cloud workos # what hit the emulator
87-
bun run cli down selfhost # tear down (also removes tailscale serves)
87+
bun run cli down selfhost # tear down
8888
```
8989

9090
Instances persist until `down``up --share` IS the "touch it" handoff
@@ -93,15 +93,6 @@ state (API/MCP/UI), hand across the URL. State files in `e2e/.dev/` mark
9393
deliberate long-lived instances (vs leaks); attach scenarios to a running
9494
instance with `E2E_<TARGET>_URL`.
9595

96-
Why cloud `--share` is more involved (encoded in the CLI, kept here for
97-
when you hit it manually): the cloud app sets `secure: true` auth cookies,
98-
so login breaks over plain http from any non-localhost origin ("Invalid
99-
login state"). Both the app AND the WorkOS emulator get fronted with
100-
`tailscale serve` HTTPS, the emulator advertises its public URL on both
101-
sides (its `baseUrl` and the app's `WORKOS_API_URL` — the browser-facing
102-
authorize URL derives from the latter), and Vite must allow the public
103-
hostname (`__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS`).
104-
10596
## Environment gotchas (learned the hard way)
10697

10798
- The shell is fish, and the working directory resets between Bash calls.
@@ -113,10 +104,6 @@ hostname (`__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS`).
113104
(symptom: behavior matching old code only in dev servers, while unit
114105
tests pass). Kill the server, clear `node_modules/.vite` /
115106
`.tanstack`-adjacent caches, reboot.
116-
- The real Tailscale CLI on this machine is
117-
`/opt/homebrew/opt/tailscale/bin/tailscale`; `/usr/local/bin/tailscale`
118-
is a broken shim pointing at a deleted app. The tailnet IP is on the
119-
`utun` interface (100.x.y.z) if the CLI fails.
120107
- `bun.lock` conflicts on rebase: take either side, re-run `bun install`,
121108
never hand-merge.
122109
- Long-lived demo servers you left up for the user look like leaks to

apps/cli/CHANGELOG.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,86 @@
11
# executor
22

3+
## 1.5.22
4+
5+
### Patch Changes
6+
7+
- [#1167](https://github.com/RhysSullivan/executor/pull/1167) [`add2e40`](https://github.com/RhysSullivan/executor/commit/add2e405fca8a5e20aea43d216bc8289c15e2187) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Fix the desktop app's main-area title-bar strip pushing page content down so page headers no longer lined up with the sidebar header. The drag strip now overlays the top of the main area (behind page content) instead of reserving its own row, and the Toolkits header uses a fixed title-bar height so its bottom border aligns with the sidebar header again.
8+
9+
- Updated dependencies []:
10+
- @executor-js/local@1.4.4
11+
- @executor-js/sdk@1.5.22
12+
- @executor-js/runtime-quickjs@1.5.22
13+
- @executor-js/api@1.4.42
14+
15+
## 1.5.21
16+
17+
### Patch Changes
18+
19+
- [#1134](https://github.com/RhysSullivan/executor/pull/1134) [`78aa871`](https://github.com/RhysSullivan/executor/commit/78aa8710d774d552d6030eca060c5e72f0899461) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Fix OAuth callbacks in cloud so they preserve the URL-selected organization when the session cookie points at another org.
20+
21+
- Updated dependencies []:
22+
- @executor-js/local@1.4.4
23+
- @executor-js/sdk@1.5.21
24+
- @executor-js/runtime-quickjs@1.5.21
25+
- @executor-js/api@1.4.41
26+
27+
## 1.5.20
28+
29+
### Patch Changes
30+
31+
- [#1132](https://github.com/RhysSullivan/executor/pull/1132) [`580fc7f`](https://github.com/RhysSullivan/executor/commit/580fc7f8b2615a0d7760b3a4daddf8d45673ef3f) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Fix the PostHog custom MCP OAuth setup flow so Add connection opens PostHog authorization instead of falling back to manual OAuth app registration.
32+
33+
- Updated dependencies []:
34+
- @executor-js/sdk@1.5.20
35+
- @executor-js/runtime-quickjs@1.5.20
36+
- @executor-js/local@1.4.4
37+
- @executor-js/api@1.4.40
38+
39+
## 1.5.19
40+
41+
### Patch Changes
42+
43+
- [#1115](https://github.com/RhysSullivan/executor/pull/1115) [`92bd86c`](https://github.com/RhysSullivan/executor/commit/92bd86cb975ce867b3002ae9bcb6bf60da67cc48) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Google media downloads (Drive file contents, exports, and other binary
44+
endpoints) are now returned as binary responses instead of being decoded as
45+
text, so files come back intact. Emit them with `emit(result.data)`.
46+
47+
- [#1115](https://github.com/RhysSullivan/executor/pull/1115) [`92bd86c`](https://github.com/RhysSullivan/executor/commit/92bd86cb975ce867b3002ae9bcb6bf60da67cc48) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - The CLI now validates that a URL is `http`/`https` before handing it to the
48+
operating system's browser opener, and on Windows opens it via
49+
`rundll32 url.dll,FileProtocolHandler` instead of `cmd /c start`. This removes a
50+
path where a crafted URL could be interpreted as a shell command. `executor
51+
login` and the "open in browser" prompts behave the same for normal URLs.
52+
53+
- [#1115](https://github.com/RhysSullivan/executor/pull/1115) [`92bd86c`](https://github.com/RhysSullivan/executor/commit/92bd86cb975ce867b3002ae9bcb6bf60da67cc48) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - Hardened the hosted egress guard. Outbound requests from OAuth token exchanges,
54+
MCP transports, and GraphQL/Google/Microsoft discovery now all route through the
55+
guard, and the guard resolves DNS before connecting so a hostname that points at
56+
a private or loopback address is blocked rather than only literal private IPs.
57+
This tightens SSRF protection for hosted and cloud execution.
58+
- Updated dependencies []:
59+
- @executor-js/sdk@1.5.19
60+
- @executor-js/runtime-quickjs@1.5.19
61+
- @executor-js/local@1.4.4
62+
- @executor-js/api@1.4.39
63+
64+
## 1.5.18
65+
66+
### Patch Changes
67+
68+
- [#1093](https://github.com/RhysSullivan/executor/pull/1093) [`bc24d1a`](https://github.com/RhysSullivan/executor/commit/bc24d1a4924ed8b3f09d64c639b0fe7fe02ed53d) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - `connections.create` now accepts no-auth connections (the `none` template with
69+
no credential), which previously failed validation with "Expected exactly one
70+
provider credential origin". Agents can wire up public, no-auth integrations
71+
(public MCP servers, public REST APIs) programmatically instead of bouncing
72+
through the web UI. Templates that take a credential still require exactly one.
73+
74+
- [#1093](https://github.com/RhysSullivan/executor/pull/1093) [`bc24d1a`](https://github.com/RhysSullivan/executor/commit/bc24d1a4924ed8b3f09d64c639b0fe7fe02ed53d) Thanks [@RhysSullivan](https://github.com/RhysSullivan)! - OpenAPI tools that return a file now spell out how to emit it directly in the
75+
tool's description, so an agent sees the `emit(result.data)` contract before its
76+
first call instead of only discovering it after a failed attempt or by reading
77+
`describe.tool`. Non-file tools are unchanged.
78+
- Updated dependencies []:
79+
- @executor-js/sdk@1.5.18
80+
- @executor-js/runtime-quickjs@1.5.18
81+
- @executor-js/local@1.4.4
82+
- @executor-js/api@1.4.38
83+
384
## 1.5.17
485

586
### Patch Changes

0 commit comments

Comments
 (0)