Skip to content

fix(slides): normalize themeVersion in setDeck - #23

Open
darjss wants to merge 18 commits into
mainfrom
fix/slides-setdeck-themeversion
Open

fix(slides): normalize themeVersion in setDeck#23
darjss wants to merge 18 commits into
mainfrom
fix/slides-setdeck-themeversion

Conversation

@darjss

@darjss darjss commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Why

Buyanaa's agent called setDeck({ slides }) without themeVersion. The next getDeck() treated the deck as invalid and re-seeded the 4-slide default, so slides looked broken after a successful write.

Scope

  • packages/workshop-backend/format-blueprints/workspace-slides.gadget: setDeck now always writes themeVersion: "workspace.1".
  • packages/workshop-backend/format-blueprints/workspace-slides.json: revision 7 → 8.
  • packages/workshop-backend/__tests__/format-blueprints.test.ts: regression test on bundled server.js.

Blast Radius

Deployments reinstall the slides format blueprint when revision changes. Existing user gadgets are unchanged until they are rebuilt from the updated blueprint.

Verification

bunx vitest run __tests__/format-blueprints.test.ts in packages/workshop-backend (7 passed).

Summary by Sourcery

Preserve user-authored slides by normalizing the deck theme version when saving it.

Bug Fixes:

  • Ensure slides decks written without a theme version remain valid and are not replaced with the default deck on subsequent reads.

Deployment:

  • Bump the slides format blueprint revision so deployments install the corrected blueprint.

Tests:

  • Add a regression test verifying the bundled slides blueprint normalizes the theme version in setDeck.

ndisidore and others added 15 commits August 26, 2026 10:31
…#344)

ensureObserver's pre-call snapshot of already-registered gatekeepers was
mutated when a pre-configured binding failed a verification pass. If the
user then repaired that binding on the re-prompt, its re-registration was
misclassified as newly added, and a terminal failure of any other binding
rolled it back -- silently deleting a registration the persisted observer
record still asserts exists, so the gatekeeper could no longer name the
observer in excludeObservers.

Stop mutating the snapshot (rename it registeredBeforeCall to state the
invariant). A binding repaired under an unpersisted account choice now
survives the rollback; the next open re-verifies the stale persisted
choice, fails, and re-prompts, so it self-corrects.

Regression test drives the exact sequence -- repair one binding on the
re-prompt while another fails terminally -- against per-resource verify
outcomes and an observer add/remove event log added to the test
gatekeeper fixture.
Last week's changes to transition to git storage had some obscure issues.

When an agent message contained a write/edit tool call, the resulting edits would be persisted to `chatChanges` (which contains the OT change log) at the time the tool executed, but the overall agent message was not persisted to the actual chat history until the end of the step. A poorly-timed crash could leave the edit in `chatChanges` while the agent message was never persisted. When the DO started back up, the agent would run again, and very likely try to make the same changes again, resulting in the changes either being double-applied or confusingly failing.

We now instead buffer changes to an in-memory buffer, which gets persisted as part of the same transaction that persists the chat log message. Either everything gets persisted together, or nothing does.

Moreover, this means that the `chatChanges` contains ONLY user-authored edits, which is closer to how things worked back in the Yjs days. Agent-authored changes go into the in-memory buffer, and then are persisted directly to a "changes" message in the chat log.

These changes fell out of my upcoming work to enable agents to create git worktrees. In that work, the agent will gain the ability to edit files programmatically within executeCode. Such edits will also need to land in the in-memory buffer so that they can be persisted transactionally.

`plans/step-transactionality.md` contains a more complete description of this change.
`SUGGESTED_MODELS` lists `@cf/deepseek-ai/deepseek-v4-pro-0813`, but pi
0.83.0's Workers AI catalog didn't have it, so `catalogModel()` returned
undefined and `gatewayNativeModel()`/`getModelDirect()` synthesized the
descriptor from defaults. Unknown models are meant to degrade gracefully,
and mostly do -- `SUGGESTED_MODELS` is authoritative for the token window
-- but two of the defaults were wrong for a DeepSeek reasoner:

* `reasoning` fell back to false, and every `thinkingFormat` branch in
  pi's openai-completions request builder is gated on it, so no thinking
  config was sent and `thinkingLevelMap` was absent. (Thinking still
  streamed back: pi parses `reasoning_content` unconditionally.)
* `compat.requiresReasoningContentOnAssistantMessages` and
  `thinkingFormat` were left to pi's auto-detection, which keys on
  `provider === "deepseek" || baseUrl.includes("deepseek.com")`. A
  Workers-AI-hosted DeepSeek is neither, so it detected `false` and
  `"openai"` where 0.84.3's catalog entry declares `true` and
  `"deepseek"` -- the flag that makes pi echo `reasoning_content` back on
  assistant messages across tool steps.

`cost` also fell back to zero, which the AI Gateway path already covers
(`#getCostFromAiGateway` reconciles from the gateway log and only falls
back to the estimate), but which a BYOK-direct Workers AI config would
have reported as $0.

pi-agent-core moves with it: it depends on `@earendil-works/pi-ai:
^0.83.0`, which 0.84.3 does not satisfy, so bumping pi-ai alone would
have pulled a second nested copy. 0.84.3 also drops the bundled
`@mistralai/mistralai` dependency, hence the net shrink in the lockfile.

The catalog additionally gained `@cf/deepseek-ai/deepseek-v4-flash-0731`,
which this commit deliberately does not add to `SUGGESTED_MODELS`.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add Google Drive search API types

* Add Google Drive metadata search

* Update release manifest for Google Drive

* Address review of Google Drive metadata search

A code review of the Drive expansion found one blocker class and several
correctness gaps. This is the remediation pass. Nothing here changes what
the feature does; it changes what it is allowed to reach and what it
admits, and it removes a duplicated admission path.

Grants are now recorded rather than inferred. `grantedResourcesFromScopes`
reported a resource as granted when every one of its OAuth scopes was
held, so any account that had ever connected a Doc or a Sheet reported the
whole-account Drive pattern as already granted: `ensureResources` would
then skip consent entirely and `hasDriveResourceGrant` would pass. The
account now persists the resource set the user actually consented to, and
the scope-derived list is frozen to the resources that predate recording.

Drive's batch `files.get` no longer reads a stale token as a denial. The
batch POST returns 200 when a subrequest 401s, so `fetchWithAuthRetry`'s
one-shot refresh never saw it and every file came back inaccessible,
permanently. The batch now forces the same cache invalidation the helper
uses and replays once. Parts are placed by their echoed Content-ID rather
than by arrival order, since these booleans gate observer admission and a
swapped pair admits the wrong collaborator. `corpora`/`driveId` became one
`DriveCorpus` value: a `driveId` without `corpora: "drive"` silently falls
back to the user corpus, and a shared-drive binding's whole boundary is
those two travelling together.

Observer verification is capped again. `maxTrackedSets: null` was
justified as safe for bulk verifiers because their per-open RPC count
stays bounded, but the work behind that RPC is not: a bulk Drive check
issues ceil(N/100) sequential subrequests, per observer, on every open.
The staged-observer rollback also never fired, because it compared a
deserialized KV value against the in-memory stub by reference; it now
turns on a nonce.

Drive observer admission is one path for all three scopes. A file binding
forked in four places and hand-rolled its own verify, which duplicated the
tracker's semantics and did not get the rollback fix above. A shared-drive
or single-file binding is now seeded with the set its scope already names,
so the tracker handles every scope and `addObserver`/`removeObserver` are
one-liners. The factory takes `verifyBatch` rather than a verifier type,
which keeps `drive-session.ts` independent of the worker entrypoint and
makes the seeding invariant testable for the first time.

`getScope()` refuses a provider id that disagrees with the binding, rather
than labelling the binding with another drive's or file's name. `search()`
refuses a single-file binding outright: Drive `q` has no `id =` clause, so
it would have scanned the whole account and post-filtered.

Documentation is matched to the code on the surfaces that describe
authority. Account scope is not limited to My Drive, and reads by ID are
not scope-checked, so the README and `DriveScope` say so. The model-facing
type now records that `fullTextContains` reaches body text, description
and OCR - the README already said it, but the agent never reads the
README. A parity test pins all six `*-types.txt` files to their `.d.ts`,
following the `mcp-shared/base-types` precedent; only `drive-types` had
been checked, and by nothing.

Verified: `pnpm build`, `pnpm lint:check` (0 errors), `pnpm test` across
the workspace, and the release manifest golden test.

* Fix Google Drive review remediation

* Clarify Google Drive name prefix search

* Fix Google Drive security boundaries

* Document Google OAuth nonce comparison

* Fix Google Drive search edge cases

* fix(google): preserve concurrent OAuth grants
* Bound test tasks so a wedged run fails instead of hanging.

Every command in the shared `test` task now runs under `scripts/with-timeout.ts`,
which kills the whole process tree and exits 124 after 60s with no output or 600s
total, printing which threshold fired and what was still alive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Document the test-task watchdog in AGENTS.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Doc-comment the newly exported collectTree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Pass test-child values as argv instead of building code strings.

CodeQL's js/bad-code-sanitization: JSON.stringify does not escape U+2028/U+2029,
so it is not a valid escape for embedding a value in JavaScript source. The
`node -e` bodies are now fixed source that reads what varies from process.argv.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Run the with-timeout cases concurrently.

Every case is a timer running out rather than work being done, so in sequence the
file cost the sum of its thresholds. 4.7s -> 1.6s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Run the kill-process-tree cases concurrently too.

Same shape as with-timeout: the time is spent waiting on signalled processes to
go away. 0.96s -> 0.70s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Remove with-timeout.test.ts due to flakiness on repeated runs, and test slowdowns

* Drive-by: Fix kill-process-tree.test.ts when run on terminal.

When run from a color-capable terminal, Node would colorize console output. But the test tried to read back the console output in order to parse the pid. It ended up reading something like `\x1b[33m2167339\x1b[39m`, from which the regex would select `33` as the PID.

PID 33 just happened to be some long-lived PID on my system, so the test would consistently conclude that the script had failed to kill the PID it was supposed to kill.

(cherry picked from commit 09cb7431a55c9d5f39ace87d9a979fe37219a4ca)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kenton Varda <kenton@cloudflare.com>
Also bumps OpenCode from 1.18.18 to 1.18.24.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Preserve checkbox-list offsets across selection rerenders while allowing filter changes to reset the list. Add generated-runtime DOM coverage for both behaviors.
@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

This PR prevents slides written without a theme version from being rejected and reset on the next read by making setDeck persist the canonical workspace.1 theme version, updating the blueprint revision, and verifying the bundled server artifact.

File-Level Changes

Change Details Files
Normalizes the slides deck theme version during writes and advances the blueprint revision so updated deployments reinstall the corrected blueprint.
  • Updates setDeck to always persist themeVersion: "workspace.1", including when callers omit it.
  • Bumps the slides blueprint revision from 7 to 8, triggering blueprint reinstall on deployment while leaving existing gadgets unchanged.
  • Adds a regression test that inspects bundled server.js for the normalized theme version and setDeck implementation.
packages/workshop-backend/format-blueprints/workspace-slides.gadget
packages/workshop-backend/format-blueprints/workspace-slides.json
packages/workshop-backend/__tests__/format-blueprints.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 6ea52209-7e4d-4daa-baf3-81f76f809620


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


0 out of 7 committers have signed the CLA.
@ndisidore
@OrangeSoda3340
@kentonv
@Maximo-Guk
@AshishKumar4
@dcartertwo
@darjss
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@github-actions github-actions Bot added the kernel label Sep 1, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="packages/workshop-backend/__tests__/format-blueprints.test.ts" line_range="99-104" />
<code_context>
     expect(client).toContain("app.replaceChildren(canvas)");
   });

+  it("normalizes themeVersion in format.slides setDeck", async () => {
+    let entry = FORMAT_BLUEPRINTS.find(blueprint => blueprint.blueprintId === "format.slides")!;
+    let serverCode = await readBlueprintFile(entry, "server.js");
+    expect(serverCode).toContain('themeVersion: "workspace.1"');
+    expect(serverCode).toContain("async setDeck(deck)");
+  });
+
   it("declares the intended export formats for every standard output format", async () => {
</code_context>
<issue_to_address>
**issue (testing):** The regression test only searches the bundled `server.js` text for `themeVersion: "workspace.1"` and `async setDeck(deck)` independently, so it passes when the constant appears in an unrelated method or when `setDeck` does not actually write it. An implementation that still returns an invalid deck can therefore pass the test.

**Triggers:** When the bundled server contains those strings outside the intended `setDeck` implementation, or `setDeck` contains the method signature but does not execute the normalization.

**Suggested fix:** Extract or execute the `setDeck` implementation and assert that calling it with `{slides}` persists a deck whose `themeVersion` is exactly `"workspace.1"`.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: packages/workshop-backend/__tests__/format-blueprints.test.ts:104


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +99 to +104
it("normalizes themeVersion in format.slides setDeck", async () => {
let entry = FORMAT_BLUEPRINTS.find(blueprint => blueprint.blueprintId === "format.slides")!;
let serverCode = await readBlueprintFile(entry, "server.js");
expect(serverCode).toContain('themeVersion: "workspace.1"');
expect(serverCode).toContain("async setDeck(deck)");
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): The regression test only searches the bundled server.js text for themeVersion: "workspace.1" and async setDeck(deck) independently, so it passes when the constant appears in an unrelated method or when setDeck does not actually write it. An implementation that still returns an invalid deck can therefore pass the test.

Triggers: When the bundled server contains those strings outside the intended setDeck implementation, or setDeck contains the method signature but does not execute the normalization.

Suggested fix: Extract or execute the setDeck implementation and assert that calling it with {slides} persists a deck whose themeVersion is exactly "workspace.1".

Keep erxes executor-tools + AdminWorkspaceDump while taking upstream
CHAT_CHANGE_MESSAGE_BUDGET, codeChangeSerializedSize, and Google Drive work.
Upstream manifest-lib refactor dropped this export; erxes self-host deploy
still reads wrangler.jsonc per package when generating instance configs.
@darjss
darjss force-pushed the fix/slides-setdeck-themeversion branch from ded9f2b to c14a43b Compare September 1, 2026 08:14
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​earendil-works/​pi-agent-core@​0.84.310010010098100
Added@​earendil-works/​pi-ai@​0.84.310010010098100

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants