Sync foundation/main (21 commits) - #33
Conversation
…oudflare#267) * Load agent catalogs each turn instead of caching them on the chat A chat cached its agent catalogs and never loaded them again, so the agent could not see a skill added after the chat opened. A failed load was cached too, as null, which means "this gatekeeper has no catalog" -- so one failure read as an empty library for the rest of the chat. The cache was the cause of both. A catalog states what a session can reach now, which is not a fact a chat can hold. Loading it per turn removes the stale window and the cached failure together, and deletes the snapshot type, the completer, the chat field that stored it, and their tests. A connection blocked pending a scope-widening restart is still skipped. The cost is one call per ambient gatekeeper per runAgent invocation. An automatic compaction reruns the turn, so that turn loads twice. * Stop asking an ambient connection for a catalog once it answers null Loading catalogs each turn made every pass call getAgentCatalog() on every ambient connection, including Scheduler, which answers null on every call. The Workshop cannot tell which ambient gatekeepers have a catalog: the method is optional on Gatekeeper, nothing declares it, and over a stub every method looks callable. Sharpen the existing null return instead of adding a flag. null now means the gatekeeper has no catalog, and the Workshop remembers that per connection in memory for the life of the workspace's Durable Object. A gatekeeper that gains a catalog in a later version is asked again on the next activation, so every chat converges. An empty catalog is {entries: []} and is still asked for every turn. A failed load is not remembered. Context never returns null and Scheduler always does, so neither changes. The integration fixture's ambient gatekeeper had no getAgentCatalog at all and took the failure path on every pass; it now answers null like Scheduler. * Receiving the catalog is not an observation The Workshop loads the catalog into every chat's prompt on every turn, so it is expected and mandatory. Treating it as an observation recorded an action and a transcript entry per turn, and Context's excludeObservers check could refuse the whole catalog in a shared workspace once the owner added a private collection. The catalog is a discovery index of titles and descriptions; it must not hold anything that needs observer verification, or every workspace would be affected at once. Reading an item through the session remains an observation. getAgentCatalog() no longer takes an authorizer, and the Workshop no longer builds one for the load. Context drops the observation and observer tracking from its catalog; Scheduler and the integration fixture drop the unused parameter.
* add deployment-wide user directory for user search * add admin toggle for controlling user search access * add in-memory cache for user search admin policy
* Write the bundled format blueprints in TypeScript A blueprint's files/ may now be TypeScript: client.ts and server.ts are bundled with their lib/**/*.ts imports into the client.js / server.js the archive ships, readable rather than minified, so the installed gadget and the agent that edits it still see one JavaScript file per side. Three tsconfigs type-check the result under the globals each side really has: the client under the DOM lib, the server under the Workers types as part of this package's own program, and the blueprints' own tests under Node's. A lib/ module is checked under whichever side imports it.
The compaction check ran once, at the start of a turn. A turn whose tool results grew the prompt past the provider limit then failed mid-turn, the user saw an error, and compaction only ran after a manual retry. runAgent now owns the loop the Overseer used to run around it: it loads the durable history, runs one pass, publishes any checkpoint the pass produced, and goes again until a pass finishes. A pass ends early either because it compacted instead of prompting the model (as before) or, new here, because a persisted tool step left the next request over the compaction trigger: the step's measured prompt plus its tool results, or, when the provider reported no usage, a reload regardless so the turn-start estimator measures the whole prompt. Cancellation, connection requests and pending approvals still end the turn ahead of compaction. The 30-step cap on a turn is gone. It came from the ai-sdk's stepCountIs(30) and was carried over when the loop moved to pi-agent-core; nothing else needed it. GPT-5.6 Sol, Luna and Terra gain a `compactionInputBudget` of 272K, the point past which OpenAI prices their input higher. Compaction sizes against that instead of the 1.05M window; the window itself is unchanged.
…credential expiry (cloudflare#509) The Confluence Gatekeeper passed the Atlassian access token's one-hour expiry to GatekeeperConnectCallback.complete(), reconnectComplete() and credentialsRestored(). The Workshop treats that date as when the credentials stop being refreshable, so an hour after connecting (or after the last use) the account shows "Credentials expired" on the Gatekeepers page and the connect-resource account picker refuses it until the user runs a full Reconnect. The credentials are fine: any use refreshes the token from the rotating refresh token and the label clears for another hour. The callback contract says not to report a short-lived token-cache expiry when the gatekeeper refreshes transparently; GitHub and Google report none. Drop the three arguments and rely on the refresh path plus the explicit credentialsExpired() signal, which refreshCredentials() already sends on an auth failure. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Bumps the github-actions group with 2 updates: [voidzero-dev/setup-vp](https://github.com/voidzero-dev/setup-vp) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `voidzero-dev/setup-vp` from 1.17.0 to 1.19.0 - [Release notes](https://github.com/voidzero-dev/setup-vp/releases) - [Commits](voidzero-dev/setup-vp@v1.17.0...49c3e4e) Updates `actions/upload-artifact` from 4.6.2 to 7.0.1 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@ea165f8...043fb46) --- updated-dependencies: - dependency-name: voidzero-dev/setup-vp dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…flare#501) Bumps the remaining-npm group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) | `1.40.5` | `1.41.9` | | [zod](https://github.com/colinhacks/zod) | `4.4.3` | `4.5.4` | | [jose](https://github.com/panva/jose) | `6.2.8` | `6.2.12` | | [diff3](https://github.com/axosoft/diff3) | `0.0.3` | `0.0.4` | | [dompurify](https://github.com/cure53/DOMPurify) | `3.4.13` | `3.4.15` | Updates `isomorphic-git` from 1.40.5 to 1.41.9 - [Release notes](https://github.com/isomorphic-git/isomorphic-git/releases) - [Commits](isomorphic-git/isomorphic-git@v1.40.5...v1.41.9) Updates `zod` from 4.4.3 to 4.5.4 - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](colinhacks/zod@v4.4.3...v4.5.4) Updates `jose` from 6.2.8 to 6.2.12 - [Release notes](https://github.com/panva/jose/releases) - [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md) - [Commits](panva/jose@v6.2.8...v6.2.12) Updates `diff3` from 0.0.3 to 0.0.4 - [Commits](https://github.com/axosoft/diff3/commits) Updates `dompurify` from 3.4.13 to 3.4.15 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.4.13...3.4.15) --- updated-dependencies: - dependency-name: isomorphic-git dependency-version: 1.41.9 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: remaining-npm - dependency-name: zod dependency-version: 4.5.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: remaining-npm - dependency-name: jose dependency-version: 6.2.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: remaining-npm - dependency-name: diff3 dependency-version: 0.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: remaining-npm - dependency-name: dompurify dependency-version: 3.4.15 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: remaining-npm ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…loudflare#502) Bumps the editor-codemirror group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.4` | `6.11.0` | | [@codemirror/state](https://github.com/codemirror/state) | `6.7.1` | `6.7.4` | | [@codemirror/view](https://github.com/codemirror/view) | `6.43.8` | `6.43.11` | | [@codemirror/legacy-modes](https://github.com/codemirror/legacy-modes) | `6.5.3` | `6.5.4` | | [@codemirror/search](https://github.com/codemirror/search) | `6.7.1` | `6.7.2` | Updates `@codemirror/commands` from 6.10.4 to 6.11.0 - [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md) - [Commits](https://github.com/codemirror/commands/commits) Updates `@codemirror/state` from 6.7.1 to 6.7.4 - [Changelog](https://github.com/codemirror/state/blob/main/CHANGELOG.md) - [Commits](https://github.com/codemirror/state/commits) Updates `@codemirror/view` from 6.43.8 to 6.43.11 - [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md) - [Commits](https://github.com/codemirror/view/commits) Updates `@codemirror/legacy-modes` from 6.5.3 to 6.5.4 - [Changelog](https://github.com/codemirror/legacy-modes/blob/main/CHANGELOG.md) - [Commits](https://github.com/codemirror/legacy-modes/commits) Updates `@codemirror/search` from 6.7.1 to 6.7.2 - [Changelog](https://github.com/codemirror/search/blob/main/CHANGELOG.md) - [Commits](https://github.com/codemirror/search/commits) --- updated-dependencies: - dependency-name: "@codemirror/commands" dependency-version: 6.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: editor-codemirror - dependency-name: "@codemirror/state" dependency-version: 6.7.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: editor-codemirror - dependency-name: "@codemirror/view" dependency-version: 6.43.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: editor-codemirror - dependency-name: "@codemirror/legacy-modes" dependency-version: 6.5.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: editor-codemirror - dependency-name: "@codemirror/search" dependency-version: 6.7.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: editor-codemirror ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…lare#515) Bumps the react-and-ui group with 4 updates in the / directory: [@cloudflare/kumo](https://github.com/cloudflare/kumo/tree/HEAD/packages/kumo), [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom), [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router) and [@tanstack/router-plugin](https://github.com/TanStack/router/tree/HEAD/packages/router-plugin). Updates `@cloudflare/kumo` from 2.12.0 to 2.13.2 - [Release notes](https://github.com/cloudflare/kumo/releases) - [Changelog](https://github.com/cloudflare/kumo/blob/main/packages/kumo/CHANGELOG.md) - [Commits](https://github.com/cloudflare/kumo/commits/@cloudflare/kumo@2.13.2/packages/kumo) Updates `@types/react-dom` from 19.2.5 to 19.2.7 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `@tanstack/react-router` from 1.170.32 to 1.170.33 - [Release notes](https://github.com/TanStack/router/releases) - [Changelog](https://github.com/TanStack/router/blob/main/packages/react-router/CHANGELOG.md) - [Commits](https://github.com/TanStack/router/commits/@tanstack/react-router@1.170.33/packages/react-router) Updates `@tanstack/router-plugin` from 1.168.35 to 1.168.36 - [Release notes](https://github.com/TanStack/router/releases) - [Changelog](https://github.com/TanStack/router/blob/main/packages/router-plugin/CHANGELOG.md) - [Commits](https://github.com/TanStack/router/commits/@tanstack/router-plugin@1.168.36/packages/router-plugin) --- updated-dependencies: - dependency-name: "@cloudflare/kumo" dependency-version: 2.13.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: react-and-ui - dependency-name: "@tanstack/react-router" dependency-version: 1.170.33 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: react-and-ui - dependency-name: "@tanstack/router-plugin" dependency-version: 1.168.36 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: react-and-ui - dependency-name: "@types/react-dom" dependency-version: 19.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: react-and-ui ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Measured on the same tasks through the same gateway, two trials each: Luna finished the suite in 1m35s to GLM 5.3 Flash's 13m20s, used tools on every task, and had no provider errors. GLM spent the full 32,768 output-token budget on both harder tasks with nothing visible to show for it, so two of three cohorts were permanently 0% and measured a model failure rather than the agent. That failure is tracked separately. Ten trials per task instead of three: at three, one stochastic miss reads as a 33-point drop. A ten-trial Luna run took 3m35s and $0.40 per arm with no infrastructure failures, and showed project-doc at 8/10 where three trials had read 3/3. Luna is served only through an AI Gateway with an OpenAI key. A local run with direct Workers AI credentials now says so and points at WORKSHOP_EVAL_MODELS; any run that departs from the default model is told once, up front, that its results are not comparable to published baselines.
This means now when the agent has mounted a worktree, you can: * Browse the contents. * Review changes the agent has made. * Make your own edits directly, if you want. Of course, we already had a code editor UI, for gadget code. There are two big things we needed to do to make this work for worktrees: 1. Lazily load files. The code editor had been downloading the entire tree upfront. Now it loads one file at a time, on-demand when you click on it. Obviously, this is needed in order to support large trees. 2. Expand the file chooser sidebar into a full tree view, along with highlighting files with changes at the top: <img width="340" height="316" alt="Screenshot from 2026-09-15 23-05-27" src="https://github.com/user-attachments/assets/74c59689-a374-49eb-b3ed-f3c63166be38" />
…are#526) * Frontend: Invite several people at once from the share modal. With sharing restricted to per-user grants, owners add people in bulk. The composer now stages recipients as chips (Enter or a directory pick), applies one role, pipelines the addCollaborator calls and refetches the membership list once. Unknown accounts and policy errors stay on their chips for correction instead of being toasted.
…ive data (cloudflare#523) * Add an owner-invites-only latch for sensitive observations. A gatekeeper can now mark an observation `ownerInvitesOnly`. Once any such observation is authorized, the Overseer permanently latches the workspace: share links can no longer be created, copied, or redeemed by anyone new, and only the owner can add collaborators directly. People who already joined keep access and are still re-verified on every open; the owner can still list, rename, and revoke links.
* Add shared hierarchical list primitive * Add Kumo hierarchical list adapter * Add shared frontend conventions * fix(ui): remove stale Workshop lock entry * fix(ui): address hierarchical list review findings * fix(ui): harden hierarchical list interactions * fix(ui): ignore hierarchical list no-op drops * fix(ui): resolve hierarchical list interactions * test(ui): satisfy hierarchical list lint * fix(ui): harden hierarchical list drag state * fix(ui): bound hierarchical list actions * fix(ui): preserve hierarchical list semantics * fix(ui): close hierarchical list interaction gaps * fix(ui): use default long-press delay * fix(ui): compose hierarchical row interactions * fix(ui): align hierarchical move feedback * chore: fix lockfile after rebase
…ries (cloudflare#512) The Workshop aborts an API session on purpose when it loses its workspace DO (server.ts, #openGadgetInternal) and the browser reconnects (main.tsx) while the DO resumes the interrupted turn (#resumeInterruptedAgents). The agent session had no reconnect and only watched chat events, so after such an abort it sat on the dead socket for the whole turn budget: both arms of the first eval run lost a 28-minute trial with zero model turns to a turn the product may well have completed. The session now reconnects like the browser does, with the chat subscription replaying everything since the session opened, and the observer finishes a turn from that replay when it never saw the agent active. Only when no session can be re-established does the turn fail, with the reason. Trials in an eval file now run concurrently, each in its own Workshop, so a file takes as long as its slowest trial rather than the sum of them. The egress filter moved from per-target to per-file for that: globalThis.fetch is process-wide. Case titles are built directly so Vitest no longer truncates the trial number out of them. Merges to main now measure their own commit and cache it under the same commit key pull requests use, so the first PR on that base runs one arm and every relevant main commit gets a score. Trajectories are rendered to Markdown for the reviewer. Pretty-printed JSON still put each file the agent wrote on one line, past the 2,000 characters the reviewer's read tool shows; fenced, no line is longer than its longest source line.
Covers fork-added modules, tests, harnesses and docs that grew without boundary entries (mcp-shared openapi-publisher, backend helpers, linear workerd harness, discovery policy, ai-executor fixtures). Undeclared adds sit outside the survivors scope, so declaring them extends the removed-name grep to files it previously skipped.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7fb967a498
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
| shouldStopAfterTurn: ({message, toolResults}) => { | ||
| // The stop reasons that end the turn come first: a compaction reload must not resume work | ||
| // that one of them ended. | ||
| if ( |
There was a problem hiding this comment.
Preserve the per-turn model-step limit across compaction
Restore a turn-wide model-step cap here. When a model continually returns tool calls, this callback now stops only for cancellation, connection/approval waits, or compaction; the previous 30-step guard was removed. Compaction can reload runAgentPass() and continue the same activation, so it does not bound the total number of model calls or side-effecting tools, allowing a degenerate turn to consume substantial inference budget and keep acting until the provider eventually rejects the oversized current turn. Keep the counter outside individual passes so a compaction reload cannot reset it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch on the removed guard, with one scope correction: the old turnCount lived inside runAgent() (base agent.ts:3280), which the overseer invoked once per pass — so it capped model steps at 30 per pass, and a compaction reload already reset it. Upstream cloudflare#493 removed that per-pass cap as part of moving the compaction loop inside runAgent; this sync adopts that refactor as-is (the fork's resolution only threads its turn options through the new signature).
Whether passes need a bound back is an upstream design question — their trajectory evals (cloudflare#510–cloudflare#512) exercise long turns, so re-adding a cap here would be a new fork behavioral divergence that could skew those. Not taking that on in the sync; worth raising upstream.
| Return to the last accepted version. Any gadgets or worktrees created by these | ||
| changes will be permanently deleted. Pending changes can't be restored. |
There was a problem hiding this comment.
Correct the discard warning for retained worktrees
Remove the claim that created worktrees are permanently deleted. If a chat creates and then modifies a worktree, this discard-all confirmation is shown, but the backend deliberately keeps worktrees across reverts and only rolls back their content and head (proposedChangeWorkpieceIds() documents that no revert deletes one). Users can therefore confirm this destructive-looking action expecting the checkout to disappear, yet it remains in the conversation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This string is upstream cloudflare#513's, adopted unchanged — the fork's ChatInterface changes in this sync are only the composer effort/prompt seam. Whether "created by these changes" accurately covers creation-discard versus modification-revert is an upstream UX-copy question, so leaving it as-is rather than creating a standing fork diff on a line upstream owns.
| check.commit(); | ||
| } | ||
| return catalog; | ||
| return buildContextCatalog(collections, await this.#loadSkills(collections)); |
There was a problem hiding this comment.
Keep private Context metadata out of automatic catalogs
When an owner has an enabled private Context collection and shares a workspace, this now puts that collection's title, description, and skill metadata into every chat prompt without the former observer authorization or exclusion check. A collaborator who does not own that collection can therefore induce the agent to reveal its catalog metadata even though session reads would reject them; either omit private entries from the automatic catalog or restore a recipient-aware authorization boundary.
AGENTS.md reference: AGENTS.md:L33-L33
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Mechanism confirmed: loadEnabledContextCollections returns the account's owned private titles/descriptions, and cloudflare#267 dropped the authorizer, so that metadata now reaches every chat prompt unauthenticated. Two scoping notes: (1) this file is untouched by the fork — the sync adopts upstream cloudflare#267's contract verbatim, including its doc comment stating catalogs "may not need observer verification"; (2) the exposure is catalog metadata only — document reads still go through the session with per-collection observer verification (addObserver), and skill invocation still authorizes.
A fork fix would need recipient-aware catalogs, i.e. threading back the authorizer cloudflare#267 removed — a significant divergence from the new architecture, not something to take on inside a sync. Treating this as adopted-upstream-behavior; if we want a fork-side boundary here it deserves its own proposal.
| this.ctx.exports.UserDirectoryDurableObject.getByName("") | ||
| .syncUser({ id: profile.id, name: profile.name }, rev) | ||
| .then(() => { |
There was a problem hiding this comment.
Keep directory sync alive after the RPC returns
Register this background synchronization with this.ctx.waitUntil(). Both authentication and setOwnDisplayName() return without awaiting this promise, so the Durable Object event can finish while the cross-DO syncUser() call or its acknowledgement is still pending; the runtime may then cancel that work. In particular, a rename on a long-lived session can remain absent from the directory indefinitely because no later authentication occurs to retry it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The failure mode is real but already designed for: this is upstream cloudflare#474's code, adopted unchanged, and its comment documents the choice — directoryRev advances only on success, so a cancelled sync "leaves it behind so the next authentication retries". A rename therefore goes stale only until that user's next authentication, not indefinitely. Upgrading fire-and-forget to waitUntil would be a (small) fork divergence from upstream's deliberate best-effort design; leaving as-is.
Upstream
2e37d67f..87e09feb(21 commits), merged perdocs/fork-maintenance.md.Resolution highlights
runRevocationCleanupinstead of upstream's ungatedscheduleAccessRestart(new optional reason param). Upstream's latch test re-stubbed to the fork seam, same intent.RunAgentOptionsonrunAgent/runAgentPass; blindfold re-applied to the new replay loop;knitli-*harnesses migrated toloadChatHistory.enabledResources, preserving per-path case behavior (KV parse lowercases, AdminSettings read preserves).wrangler.jsonctakes the v3 migration, formatting normalized to upstream.GatekeeperModal.test.tsx: upstream's file kept byte-identical, fork tests moved toGatekeeperModal.fork.test.tsx(new Tier-1 entry).Fork tooling
fork-boundary.json: +35 Tier-1 entries for previously undeclared fork files;reviewedSurvivorspruned (22 stale) + 9 new acks, all false-positive class.Verification
fork:sync --verify: exit 0 (survivors, uncached typecheck, audit clean — no dropped hunks, no format drift, no collisions)pnpm lint: green; full uncached suite on Node 24: 43/43 tasks green