Skip to content

Staging#513

Merged
equationalapplications merged 9 commits into
mainfrom
staging
Jul 2, 2026
Merged

Staging#513
equationalapplications merged 9 commits into
mainfrom
staging

Conversation

@equationalapplications

Copy link
Copy Markdown
Owner

Description

Ships the July 2026 credit economy repricing: metered agent-turn billing on the primary WebSocket path and HTTP fallback, live voice at 5 credits/tick with a connect gate of 5, updated Firebase callable costs, and refreshed billing/docs copy.

Design spec: docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test update
  • Build/CI update

Related Issue

Fixes #

Changes Made

  • cloud-agent: multi-row FIFO credit allocator; consumeAgentEvents for per-loop billing (max 5); WebSocket /agent/stream uses shared metering; HTTP POST /agent/run pre-flight 402 when balance is zero; live voice 5 credits/tick and connect gate ≥ 5
  • functions: generateReply grounded (3) vs standard (1); generateImage/convertDocumentText → 2 credits; bill summarizeText (1) and generateEmbedding (Math.ceil(chars/50_000))
  • client: MIN_CREDITS_FOR_CALL raised to 5; landing/meta copy updated to 5 credits/minute
  • docs: updated billing-and-credits.md consumption table; added repricing design spec; removed completed ephemeral plan files

Testing

Platforms Tested

  • iOS
  • Android
  • Web

Test Steps

  1. cd cloud-agent && npm run typecheck && npm test
  2. cd functions && npm run typecheck && npm test
  3. npm test -- __tests__/useLiveVoiceChat.test.tsx

Screenshots

Before

N/A — billing/pricing change

After

N/A — billing/pricing change

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

Hard cutover — no rate versioning. LOW_CREDIT_THRESHOLD on the talk screen stays at 5 (less runway at the new live-voice rate; accepted per spec).

Made with Cursor

equationalapplications and others added 6 commits July 1, 2026 22:06
Brainstormed spec for the finalized per-action credit pricing table —
metered live-voice/agent-loop billing, new embedding/summarize costs,
and the multi-row FIFO port needed in cloud-agent to support >1-credit
spends.
- generateReply: spend lives in chargeForReply(), which has no access to
  tools; grounded/standard split needs a signature plumb, not a one-line
  change at the spend site.
- cloud-agent spendCredit ripple: correct the caller inventory —
  tools/browserAction.ts is the real browser_action spend site (was
  omitted); wsBrowserAgentHandler.ts is not a caller (was wrongly listed).
11 tasks, dependency-ordered: cloud-agent multi-row credit allocator +
ripple, per-loop agent-turn billing with hard stop at 5, live-voice
5-credit tick + gate, client gate, functions callable cost bumps and
two new billing sites (summarizeText, generateEmbedding), generateReply
grounded/standard split, docs, and consumer copy.
Meter agent turns per internal tool-loop on HTTP and WebSocket with a
zero-balance pre-flight gate, raise live voice to 5 credits/tick, port the
multi-row FIFO allocator, bill summarize/embedding, and split generateReply
into grounded (3) vs standard (1) pricing.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Guard WebSocket sends so a failed send (client already disconnected)
  can no longer be mistaken for an ADK processing error and trigger an
  unwarranted credit refund.
- Emit onToolEnd for the active tool before breaking the loop on abort,
  insufficient credits, or hitting MAX_LOOP_ITERATIONS, so consumers
  don't get stuck showing a tool as still running.
- Run the zero-credit preflight check before queryWikiContext so 402
  responses skip the embedding/DB work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat(billing): July 2026 credit economy repricing
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6698f4d8-90de-42e9-b71c-ad407d2c7b61

📥 Commits

Reviewing files that changed from the base of the PR and between f9c6da5 and 1dfc042.

📒 Files selected for processing (3)
  • cloud-agent/src/services/agentEventLoop.test.ts
  • cloud-agent/src/services/agentEventLoop.ts
  • docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md
📝 Walkthrough

Walkthrough

Reworks credit handling to use allocation arrays and shared agent billing helpers, updates live-voice gating to 5 credits, changes per-action pricing in Functions, and refreshes related tests, client copy, and billing docs.

Changes

Credit economy repricing

Layer / File(s) Summary
Credit service multi-row allocation core
cloud-agent/src/services/creditService.ts, cloud-agent/src/services/creditService.test.ts
Adds CreditSpendAllocation, multi-row FIFO spend/refund behavior, and matching allocation-based tests.
Agent event loop credit gating and per-iteration billing
cloud-agent/src/services/agentEventLoop.ts, cloud-agent/src/services/agentEventLoop.test.ts
Adds pre-flight balance gating, per-iteration spending with a max loop cap, degraded fallback handling, and refund-on-error logic.
HTTP /agent/run wiring to shared credit helpers
cloud-agent/src/index.ts, cloud-agent/src/index.test.ts, cloud-agent/src/agent.live.test.ts, cloud-agent/src/integration.test.ts
Threads creditService through RunAgentParams, delegates agent execution to the shared event-loop helper, and updates endpoint tests to the new balance-gate flow.
WebSocket agent handler credit gating and safeSend refactor
cloud-agent/src/handlers/wsAgentHandler.ts, cloud-agent/src/handlers/wsAgentHandler.test.ts
Switches agent WebSocket flow to shared helpers, adds safe socket sends, and removes the old refund-on-close path.
Live voice threshold and per-tick billing
cloud-agent/src/handlers/wsLiveAgentHandler.ts, cloud-agent/src/handlers/wsLiveAgentHandler.test.ts
Raises the live-voice start gate to 5 credits and bills 5 credits per interval.
Scheduler trigger allocation refund
cloud-agent/src/handlers/schedulerTriggerHandler.ts, cloud-agent/src/handlers/schedulerTriggerHandler.test.ts
Stores and refunds allocation arrays instead of transaction ids for scheduled runs.
Browser action tool allocation refund
cloud-agent/src/tools/browserAction.ts, cloud-agent/src/tools/browserAction.test.ts
Stores and conditionally refunds allocation arrays in browser-action billing paths.
Functions per-action repricing
functions/src/generateImage.ts, functions/src/convertDocumentText.ts, functions/src/generateEmbedding.ts, functions/src/generateReply.ts, functions/src/summarizeText.ts, and matching *.test.ts
Updates image/document costs to 2 credits, adds embedding/summarization credit gating, and makes reply cost depend on tools presence.
Client threshold and docs
src/hooks/useLiveVoiceChat.ts, __tests__/useLiveVoiceChat.test.tsx, src/components/LandingPage/FeaturesSection.tsx, app/index.web.tsx, docs/billing-and-credits.md, docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md, docs/superpowers/plans/*
Raises client live-voice gating to 5 and updates billing, marketing, and design-doc text to match the new pricing.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Index
  participant assertAgentTurnCredits
  participant runAgentReal
  participant consumeAgentEvents
  participant CreditService

  Client->>Index: POST /agent/run
  Index->>assertAgentTurnCredits: verify balance
  assertAgentTurnCredits-->>Index: ok or insufficient
  Index->>runAgentReal: creditService + ADK events
  runAgentReal->>consumeAgentEvents: consume stream
  consumeAgentEvents->>CreditService: spendCredit/refundCredit
  consumeAgentEvents-->>runAgentReal: reply/toolCalls/metadata
  runAgentReal-->>Client: response payload
Loading

Possibly related PRs

Poem

A bunny nibbled credits, five by five,
Then watched the agent loop hop and jive.
Arrays went thump through FIFO lanes,
And docs all sang the pricing changes.
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too generic to convey the pull request’s actual change. Replace it with a concise title that names the main change, such as credit economy repricing or live voice billing updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly matches the pricing, billing, docs, and test updates in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

🧹 Nitpick comments (6)
cloud-agent/src/tools/browserAction.test.ts (1)

29-29: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Capture and assert refund allocations in these mocks.

These tests now model allocation-based spends, but the refund mocks still accept no args. The refund-path tests should assert the exact { transactionId, amount }[] is passed through.

Also applies to: 158-158, 196-196

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-agent/src/tools/browserAction.test.ts` at line 29, The refund mocks in
browserAction.test.ts still ignore the allocation payload, so update the
creditService.refundCredit mocks to accept and assert the exact { transactionId,
amount }[] argument passed through by the refund flow. Adjust the affected test
cases around the browserAction refund paths to verify the expected allocation
array in the mock implementation, using the creditService object and its
spendCredit/refundCredit methods as the main symbols to locate the changes.
cloud-agent/src/handlers/schedulerTriggerHandler.test.ts (1)

27-28: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the refund allocation payload, not only the call count.

The updated mock returns allocations, but refundCredit still ignores its args. Existing timeout/setup refund tests would pass even if the handler refunded [] or the wrong transaction.

Also applies to: 67-68

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-agent/src/handlers/schedulerTriggerHandler.test.ts` around lines 27 -
28, The refund tests in schedulerTriggerHandler.test.ts only verify that
refundCredit was called, so they can miss incorrect refund payloads. Update the
affected tests to assert the actual arguments passed to refundCredit, especially
the allocations returned by the updated spendCredit mock, using the relevant
handler/test setup symbols so the test fails if the handler refunds an empty or
wrong transaction list.
cloud-agent/src/handlers/wsLiveAgentHandler.ts (1)

315-320: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a shared constant for the live-voice credit rate.

The connect-gate threshold (balance < 5, Line 316) and the per-tick spend amount (cs.spendCredit(userId!, 5), Line 339) are both hardcoded literals that happen to match today. They express related-but-distinct concepts (minimum balance to start vs. cost per tick) and could silently diverge in a future edit since nothing ties them together.

♻️ Suggested refactor
+const LIVE_VOICE_MIN_CREDITS_TO_CONNECT = 5
+const LIVE_VOICE_CREDITS_PER_TICK = 5
+
 ...
-      if (balance < 5) {
+      if (balance < LIVE_VOICE_MIN_CREDITS_TO_CONNECT) {
 ...
-            await cs.spendCredit(userId!, 5)
+            await cs.spendCredit(userId!, LIVE_VOICE_CREDITS_PER_TICK)

Also applies to: 339-339

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-agent/src/handlers/wsLiveAgentHandler.ts` around lines 315 - 320, The
live-voice credit threshold and per-tick spend are hardcoded separately in
wsLiveAgentHandler, which can drift apart. Introduce shared named constants for
the minimum balance required in the connect gate and the credit cost consumed in
the tick loop, and use them in both the balance check and cs.spendCredit call so
the relationship is explicit and maintained in one place.
docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md (1)

55-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Escape the unescaped || inside the generateReply table row.

The literal `!tools || tools.length === 0` inside this table cell contains an unescaped double-pipe, which Markdown table parsers split on regardless of the surrounding backticks — this is what's producing the "Actual: 5" column-count warning from markdownlint and will render as a broken table.

📝 Suggested fix
-computed at the call site as `!tools || tools.length === 0` — and spend `isGrounded ? 3 : 1`.
+computed at the call site as `!tools \|\| tools.length === 0` — and spend `isGrounded ? 3 : 1`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md` at line
55, The generateReply spec table row contains an unescaped double-pipe in the
inline expression used to describe the grounded check, which breaks Markdown
table parsing and causes the column-count warning. Update the text in the
generateReply row so the `||` inside the `chargeForReply`/`isGrounded`
description is escaped or rewritten in a table-safe form, while keeping the
existing references to `generateReply.ts`, `buildToolsForRequest`, and
`chargeForReply` intact.

Source: Linters/SAST tools

functions/src/generateImage.ts (1)

123-132: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Avoid duplicating the credit cost literal across spend, log, and response.

2 is hardcoded independently in chargeForImage (Line 127), the success log (Line 351), and the returned creditsSpent (Line 366). If the price changes again, these can drift out of sync and misreport the actual amount charged to the user.

Derive the displayed/logged value from spendAllocations (sum of amount) or a single named constant, rather than repeating the literal.

♻️ Proposed fix using a single source of truth
+const IMAGE_GENERATION_CREDIT_COST = 2;
+
 async function chargeForImage(
   userId: string,
   credits: Pick<typeof creditService, 'spendCredits'>
 ): Promise<CreditSpendAllocation[]> {
-  const spendAllocations = await credits.spendCredits(userId, 2);
+  const spendAllocations = await credits.spendCredits(userId, IMAGE_GENERATION_CREDIT_COST);
   if (spendAllocations === null) {
     throw new HttpsError("failed-precondition", "Insufficient credits.");
   }
   return spendAllocations;
 }
+    const creditsSpent = spendAllocations.reduce((sum, a) => sum + a.amount, 0);
     logger.info("generateImage succeeded", {
       firebaseUid: request.auth.uid,
       userId: user.id,
-      creditsSpent: 2,
+      creditsSpent,
       ...
     });
     return {
       imageBase64: imageResult.imageBase64,
       mimeType: normalizedMimeType,
-      creditsSpent: 2,
+      creditsSpent,
       ...
     };

Also applies to: 348-351, 363-366

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@functions/src/generateImage.ts` around lines 123 - 132, The image credit
charge amount is hardcoded in multiple places, so the spend call, success log,
and returned creditsSpent can drift out of sync; update chargeForImage and the
related success/response handling to use a single source of truth from the
spendAllocations result (sum of amount) or a shared constant, and reuse that
derived value in the log and returned payload instead of repeating the literal.
functions/src/summarizeText.test.ts (1)

127-162: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the refund receives the original allocation.

These tests would still pass if the handler refunded the wrong user or an empty allocation. Capture userId and allocations so the new allocation refund contract is covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@functions/src/summarizeText.test.ts` around lines 127 - 162, The refund-path
tests in summarizeTextHandler only verify that refundCreditImpl is called, so
they miss whether the original allocation is actually passed through. Update the
tests around summarizeTextHandler to capture the userId and allocations from the
initial credit allocation and assert refundCreditImpl receives those same values
when generateSummary fails or returns an empty result. Use the existing
summarizeTextHandler, buildAuth, and makeOptions helpers to verify the refund
contract end-to-end.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cloud-agent/src/handlers/schedulerTriggerHandler.ts`:
- Around line 215-216: The refund rollback path in schedulerTriggerHandler is
swallowing failures, so update the refund handling around refundCredit in the
scheduler trigger flow to surface errors instead of relying on the inaccurate
“/* logged */” comment. In the try/catch blocks near the allocation rollback
logic, catch the error, log it with enough context (such as userId and
allocations), and make sure the failure is recorded consistently in the same
rollback path used by the other occurrence in this handler.

In `@cloud-agent/src/handlers/wsAgentHandler.ts`:
- Around line 64-73: The auth flow in wsAgentHandler still bypasses the new
safeSend wrapper, so disconnected clients can trigger raw ws.send exceptions
during token verification or DB lookup. Update the auth failure/success response
paths in the handler to use safeSend instead of ws.send, especially in the
branches covered by the auth logic around the WebSocket session setup, so close
handling can continue normally without throwing.

In `@cloud-agent/src/services/agentEventLoop.ts`:
- Around line 116-119: The tool lifecycle handling in agentEventLoop is missing
an onToolEnd for the previously active tool when a new function call with a
different name arrives, which leaves earlier tools active in WebSocket clients.
Update the logic around the consecutive function-call handling in agentEventLoop
so that before calling hooks.onToolStart for a new fc.name, you first end the
currently tracked lastToolName via hooks.onToolEnd and then reset/update
lastToolName; apply the same transition handling in the other function-call path
referenced by the diff so every started tool is always ended before switching to
the next one.

In `@cloud-agent/src/services/creditService.ts`:
- Around line 17-18: Validate credit amounts before any balance mutation in
CreditService: add input checks in spendCredit and refundCredit to reject zero,
negative, fractional, NaN, or non-finite amounts, and ensure allocation.amount
values used by refundCredit are sane before updating credits. Also harden the
internal allocation handling path in CreditService methods (including the
allocation creation and refund logic around the referenced blocks) so duplicate
or malformed allocations cannot be applied twice or corrupt balances, returning
a clear validation error instead of proceeding.

In `@cloud-agent/src/tools/browserAction.ts`:
- Around line 111-112: The refundCredit calls in browserAction’s allocation
cleanup are swallowing failures without any operational trail. Update the refund
paths in browserAction to catch the thrown error and log a clear failure message
with the relevant allocation/user context before continuing, using the existing
deps.creditService.refundCredit flow so reconciliation can identify stranded
refunds. Apply the same fix to both refund locations in this file, keeping the
logging consistent and tied to the refund attempt.

In `@functions/src/summarizeText.ts`:
- Around line 166-176: Move the `getSummaryGenerator()` initialization into the
same refund-protected `try` block in `summarizeText` so any synchronous setup
failure is also covered by the refund path. Keep the `spendCredits` call before
the block, but wrap both generator creation and the subsequent
`generateSummary(buildPrompt(...))` call inside the existing `try/catch` so
`spendAllocations` can be refunded if initialization throws.
- Around line 160-164: The user bootstrap in summarizeText.ts is passing an
empty string into getOrCreateUserByFirebaseIdentity when decoded.email is
missing, which should be rejected earlier. Update the logic around
request.auth.uid and users.getOrCreateUserByFirebaseIdentity to require a
normalized, non-empty email from decoded.email before calling the repository,
and fail fast with a clear error or return path when the Firebase token has no
usable email. Keep the guard close to the existing email normalization so the
user creation flow only proceeds with valid identity data.

---

Nitpick comments:
In `@cloud-agent/src/handlers/schedulerTriggerHandler.test.ts`:
- Around line 27-28: The refund tests in schedulerTriggerHandler.test.ts only
verify that refundCredit was called, so they can miss incorrect refund payloads.
Update the affected tests to assert the actual arguments passed to refundCredit,
especially the allocations returned by the updated spendCredit mock, using the
relevant handler/test setup symbols so the test fails if the handler refunds an
empty or wrong transaction list.

In `@cloud-agent/src/handlers/wsLiveAgentHandler.ts`:
- Around line 315-320: The live-voice credit threshold and per-tick spend are
hardcoded separately in wsLiveAgentHandler, which can drift apart. Introduce
shared named constants for the minimum balance required in the connect gate and
the credit cost consumed in the tick loop, and use them in both the balance
check and cs.spendCredit call so the relationship is explicit and maintained in
one place.

In `@cloud-agent/src/tools/browserAction.test.ts`:
- Line 29: The refund mocks in browserAction.test.ts still ignore the allocation
payload, so update the creditService.refundCredit mocks to accept and assert the
exact { transactionId, amount }[] argument passed through by the refund flow.
Adjust the affected test cases around the browserAction refund paths to verify
the expected allocation array in the mock implementation, using the
creditService object and its spendCredit/refundCredit methods as the main
symbols to locate the changes.

In `@docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md`:
- Line 55: The generateReply spec table row contains an unescaped double-pipe in
the inline expression used to describe the grounded check, which breaks Markdown
table parsing and causes the column-count warning. Update the text in the
generateReply row so the `||` inside the `chargeForReply`/`isGrounded`
description is escaped or rewritten in a table-safe form, while keeping the
existing references to `generateReply.ts`, `buildToolsForRequest`, and
`chargeForReply` intact.

In `@functions/src/generateImage.ts`:
- Around line 123-132: The image credit charge amount is hardcoded in multiple
places, so the spend call, success log, and returned creditsSpent can drift out
of sync; update chargeForImage and the related success/response handling to use
a single source of truth from the spendAllocations result (sum of amount) or a
shared constant, and reuse that derived value in the log and returned payload
instead of repeating the literal.

In `@functions/src/summarizeText.test.ts`:
- Around line 127-162: The refund-path tests in summarizeTextHandler only verify
that refundCreditImpl is called, so they miss whether the original allocation is
actually passed through. Update the tests around summarizeTextHandler to capture
the userId and allocations from the initial credit allocation and assert
refundCreditImpl receives those same values when generateSummary fails or
returns an empty result. Use the existing summarizeTextHandler, buildAuth, and
makeOptions helpers to verify the refund contract end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f705a29-fe8d-4eaf-821a-71e3c7c1521c

📥 Commits

Reviewing files that changed from the base of the PR and between 075d60e and 2105889.

📒 Files selected for processing (38)
  • __tests__/useLiveVoiceChat.test.tsx
  • app/index.web.tsx
  • cloud-agent/src/agent.live.test.ts
  • cloud-agent/src/handlers/schedulerTriggerHandler.test.ts
  • cloud-agent/src/handlers/schedulerTriggerHandler.ts
  • cloud-agent/src/handlers/wsAgentHandler.test.ts
  • cloud-agent/src/handlers/wsAgentHandler.ts
  • cloud-agent/src/handlers/wsLiveAgentHandler.test.ts
  • cloud-agent/src/handlers/wsLiveAgentHandler.ts
  • cloud-agent/src/index.test.ts
  • cloud-agent/src/index.ts
  • cloud-agent/src/integration.test.ts
  • cloud-agent/src/services/agentEventLoop.test.ts
  • cloud-agent/src/services/agentEventLoop.ts
  • cloud-agent/src/services/creditService.test.ts
  • cloud-agent/src/services/creditService.ts
  • cloud-agent/src/tools/browserAction.test.ts
  • cloud-agent/src/tools/browserAction.ts
  • docs/billing-and-credits.md
  • docs/superpowers/plans/2026-06-29-mv3-bridge-phase1.md
  • docs/superpowers/plans/2026-06-29-phase2-browser-bridge-stateful-actions.md
  • docs/superpowers/plans/2026-06-29-phase3-proactive-scheduler.md
  • docs/superpowers/plans/2026-06-30-cws-extension-readiness.md
  • docs/superpowers/plans/2026-07-01-billing-hardening.md
  • docs/superpowers/plans/2026-07-01-credit-improvements.md
  • docs/superpowers/plans/2026-07-01-live-voice-credit-reconciliation.md
  • docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md
  • functions/src/convertDocumentText.ts
  • functions/src/generateEmbedding.test.ts
  • functions/src/generateEmbedding.ts
  • functions/src/generateImage.test.ts
  • functions/src/generateImage.ts
  • functions/src/generateReply.test.ts
  • functions/src/generateReply.ts
  • functions/src/summarizeText.test.ts
  • functions/src/summarizeText.ts
  • src/components/LandingPage/FeaturesSection.tsx
  • src/hooks/useLiveVoiceChat.ts
💤 Files with no reviewable changes (5)
  • docs/superpowers/plans/2026-06-29-phase2-browser-bridge-stateful-actions.md
  • docs/superpowers/plans/2026-07-01-live-voice-credit-reconciliation.md
  • docs/superpowers/plans/2026-06-30-cws-extension-readiness.md
  • docs/superpowers/plans/2026-07-01-credit-improvements.md
  • docs/superpowers/plans/2026-06-29-phase3-proactive-scheduler.md

Comment thread cloud-agent/src/handlers/schedulerTriggerHandler.ts Outdated
Comment thread cloud-agent/src/handlers/wsAgentHandler.ts
Comment thread cloud-agent/src/services/agentEventLoop.ts
Comment thread cloud-agent/src/services/creditService.ts
Comment thread cloud-agent/src/tools/browserAction.ts Outdated
Comment thread functions/src/summarizeText.ts
Comment thread functions/src/summarizeText.ts
- Log (don't silently swallow) refund rollback failures in
  schedulerTriggerHandler and browserAction
- Use safeSend in the WS auth path so a closed socket can't throw
- End the previously active tool before starting a differently-named
  one in the ADK event loop
- Validate credit spend/refund amounts are positive safe integers
- Guard summarizeText against a missing Firebase email and move
  generator init inside the refund-protected try block

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@equationalapplications

Copy link
Copy Markdown
Owner Author

/fix-pr follow-up

Commit: 153bb7bf

Review resolution

  • schedulerTriggerHandler.ts:216, 262-263 — refund rollback failures silently swallowed — Fixed: log refund errors with allocations/context on both setup and offline rollback paths.
  • wsAgentHandler.ts:73, 257-279 — auth path used raw ws.send (can throw on closed socket) — Fixed: switched to the existing safeSend wrapper for all auth-path responses.
  • agentEventLoop.ts:119 — switching to a different tool name skipped ending the previous tool, leaving it stuck active client-side — Fixed: call endActiveTool() before starting the new tool.
  • creditService.ts:18spendCredit/refundCredit accepted any number, risking corrupt balances from zero/negative/fractional/NaN amounts — Fixed: added assertPositiveCreditAmount guard on both entry points.
  • browserAction.ts:112, 131 — refund failures after dispatch error / offline abort were silently swallowed — Fixed: log refund errors with allocations/context in both spots.
  • summarizeText.ts:164 — empty-string email passed straight into user bootstrap for email-less tokens — Fixed: guard and throw failed-precondition before bootstrapping.
  • summarizeText.ts:176 — generator initialization happened outside the refund-protected try block, so a synchronous init throw would strand spent credits — Fixed: moved getSummaryGenerator() call inside the try.

Verification

  • typecheck (cloud-agent) — pass (npm run typecheck)
  • typecheck (functions) — pass (npm run typecheck)
  • lint (functions) — pass, 1 pre-existing unrelated warning (npm run lint)
  • tests (cloud-agent) — pass, 210/210 (npm test)
  • tests (functions) — pass, 329/329 (npm test)

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Implements the July 2026 credit-economy repricing across the client, Firebase Functions, and cloud-agent so that billed actions align with the new per-action costs and the cloud-agent’s text/voice paths use metered billing with updated connect gates and documentation.

Changes:

  • Updated client gates/copy for live voice to require ≥ 5 credits and reflect 5 credits/minute pricing.
  • Added/adjusted Functions-side billing for summarizeText, embeddings, grounded vs standard generateReply, image generation, and document conversion (with test updates).
  • Refactored cloud-agent billing to support multi-row spends, per-loop metering (max 5) for agent turns, and 5-credits/tick live-voice billing (with new event-loop utility + tests), plus refreshed billing docs and added the repricing design spec.

Reviewed changes

Copilot reviewed 36 out of 38 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/hooks/useLiveVoiceChat.ts Raises live-voice minimum credits gate to 5.
src/components/LandingPage/FeaturesSection.tsx Updates landing-page voice pricing copy to 5 credits/minute.
functions/src/summarizeText.ts Adds 1-credit billing with refund-on-failure for summarizeText.
functions/src/summarizeText.test.ts Adds spend/refund test coverage for summarizeText billing.
functions/src/generateReply.ts Charges 3 credits for grounded default vs 1 for explicit-tools path.
functions/src/generateReply.test.ts Updates/expands tests to cover grounded vs standard costs.
functions/src/generateImage.ts Raises image generation cost to 2 credits and updates reporting.
functions/src/generateImage.test.ts Updates tests to assert 2-credit image billing and refunds.
functions/src/generateEmbedding.ts Adds embedding billing (Math.ceil(chars/50k)) + refund-on-failure.
functions/src/generateEmbedding.test.ts Adds billing/refund unit tests and cost function tests.
functions/src/convertDocumentText.ts Raises document conversion cost from 1 to 2 credits.
docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md Adds the repricing design spec documenting the new costs and rollout.
docs/superpowers/plans/2026-07-01-live-voice-credit-reconciliation.md Removes completed/obsolete implementation plan doc.
docs/superpowers/plans/2026-07-01-credit-improvements.md Removes completed/obsolete implementation plan doc.
docs/superpowers/plans/2026-06-30-cws-extension-readiness.md Removes completed/obsolete implementation plan doc.
docs/superpowers/plans/2026-06-29-phase3-proactive-scheduler.md Removes completed/obsolete implementation plan doc.
docs/billing-and-credits.md Updates consumption table for new per-action pricing and gates.
cloud-agent/src/tools/browserAction.ts Updates browser_action billing/refund to allocation-array spend API.
cloud-agent/src/tools/browserAction.test.ts Updates mocks/assertions for allocation-based spend/refund.
cloud-agent/src/services/creditService.ts Implements multi-row FIFO spending with variable-amount support.
cloud-agent/src/services/creditService.test.ts Adds tests for multi-row spend/refund and default-amount behavior.
cloud-agent/src/services/agentEventLoop.ts Adds shared per-loop credit metering + graceful degrade logic (max 5).
cloud-agent/src/services/agentEventLoop.test.ts Adds unit tests for per-loop billing, caps, refunds, and preflight gating.
cloud-agent/src/integration.test.ts Updates creditService mocks for allocation-array spend/refund API.
cloud-agent/src/index.ts Switches HTTP /agent/run to preflight gate + per-loop billing via shared event loop.
cloud-agent/src/index.test.ts Updates tests for new preflight behavior and allocation-based credit mocks.
cloud-agent/src/handlers/wsLiveAgentHandler.ts Raises live-voice connect gate to ≥ 5 and charges 5 credits/tick.
cloud-agent/src/handlers/wsLiveAgentHandler.test.ts Adds/updates tests for new ≥ 5 gate and updated spend behavior.
cloud-agent/src/handlers/wsAgentHandler.ts Switches WS agent path to preflight gate + per-loop billing + safeSend.
cloud-agent/src/handlers/wsAgentHandler.test.ts Updates insufficient-credit test to reflect preflight balance gating.
cloud-agent/src/handlers/schedulerTriggerHandler.ts Updates scheduler trigger billing to allocation-array spend/refund API.
cloud-agent/src/handlers/schedulerTriggerHandler.test.ts Updates scheduler-trigger credit mocks to allocation-array shape.
cloud-agent/src/agent.live.test.ts Updates live agent test wiring to supply creditService spend/refund hooks.
app/index.web.tsx Updates web meta description voice pricing to 5 credits/minute.
tests/useLiveVoiceChat.test.tsx Adds test ensuring client gate blocks starting call below 5 credits.
Comments suppressed due to low confidence (1)

cloud-agent/src/index.ts:231

  • The HTTP /agent/run handler performs bulkInsertUnsynced writes before the pre-flight 402 gate (assertAgentTurnCredits). This means a user with 0 credits can still trigger DB writes before being rejected, which is inconsistent with the WS path (wsAgentHandler) and undermines the intent of the pre-flight credit gate.

Move assertAgentTurnCredits above the bulkInsertUnsynced block so insufficient-credit requests fail fast before any non-essential writes.

      if (unsyncedHistory.length > 0) {
        try {
          await bulkInsertUnsynced(db, userId, characterId, unsyncedHistory, embedText)
        } catch (err) {
          // Swallow sync errors so the agent can still respond (matches Firebase generateReply behavior)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread functions/src/generateEmbedding.ts
Empty-string email was passed straight into getOrCreateUserByFirebaseIdentity,
which throws an opaque internal error for email-less auth tokens instead of a
clear failed-precondition. Same guard already applied to summarizeText.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026
@equationalapplications

Copy link
Copy Markdown
Owner Author

Pushed f9c6da5. Fixed missing-email guard in generateEmbedding.ts; other two findings
retracted after checking spec — both intentional per design doc.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cloud-agent/src/services/agentEventLoop.ts (2)

185-193: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

End the active tool when the stream errors.

If an error is thrown after onToolStart and before a non-function event, the catch path refunds and rethrows without emitting onToolEnd, leaving WebSocket clients with stale active-tool state.

🧹 Proposed fix
   } catch (err) {
+    try {
+      endActiveTool()
+    } catch (hookErr) {
+      console.error('onToolEnd cleanup failed:', hookErr)
+    }
     if (spentAllocations.length > 0) {
       try {
         await creditService.refundCredit(userId, spentAllocations)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-agent/src/services/agentEventLoop.ts` around lines 185 - 193, The error
path in agentEventLoop’s catch block refunds credits and rethrows, but never
closes the currently active tool after onToolStart. Update the catch handling
around the stream processing in agentEventLoop so that any started tool is ended
before rethrowing when a stream error occurs, using the existing onToolEnd
flow/state that is paired with onToolStart. Make sure this cleanup runs even
when refundCredit fails, so WebSocket clients don’t retain stale active-tool
state.

110-128: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Bill every function-call part, not just the containing event.

This loop records each fc.name, but Line 127 spends only once for the whole event. If an event contains multiple functionCall parts, those tool calls are undercharged despite being tracked individually.

💳 Proposed fix
       if (eventHasFunctionCall(event)) {
+        let billableFunctionCalls = 0
         for (const part of event.content!.parts!) {
           if ('functionCall' in part) {
             const fc = (part as { functionCall?: { name?: string } }).functionCall
             if (fc?.name) {
+              billableFunctionCalls += 1
               toolCalls.push(fc.name)
               if (fc.name !== lastToolName) {
                 endActiveTool()
                 hooks?.onToolStart?.(fc.name)
                 lastToolName = fc.name
@@
         loopCount += 1
         try {
-          const allocations = await creditService.spendCredit(userId)
-          spentAllocations.push(...allocations)
+          if (billableFunctionCalls > 0) {
+            const allocations = await creditService.spendCredit(userId, billableFunctionCalls)
+            spentAllocations.push(...allocations)
+          }
         } catch (creditErr) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-agent/src/services/agentEventLoop.ts` around lines 110 - 128, The
billing in agentEventLoop’s function-call handling only charges once per event
even though multiple functionCall parts can be recorded in toolCalls. Update the
event processing loop in eventHasFunctionCall so each part with a functionCall
and valid fc.name is billed individually, while still preserving the existing
onToolStart/endActiveTool behavior and lastToolName tracking. Use the existing
creditService.spendCredit and spentAllocations flow, but move the charge inside
the per-part handling so every function-call part is accounted for.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cloud-agent/src/services/agentEventLoop.ts`:
- Around line 185-193: The error path in agentEventLoop’s catch block refunds
credits and rethrows, but never closes the currently active tool after
onToolStart. Update the catch handling around the stream processing in
agentEventLoop so that any started tool is ended before rethrowing when a stream
error occurs, using the existing onToolEnd flow/state that is paired with
onToolStart. Make sure this cleanup runs even when refundCredit fails, so
WebSocket clients don’t retain stale active-tool state.
- Around line 110-128: The billing in agentEventLoop’s function-call handling
only charges once per event even though multiple functionCall parts can be
recorded in toolCalls. Update the event processing loop in eventHasFunctionCall
so each part with a functionCall and valid fc.name is billed individually, while
still preserving the existing onToolStart/endActiveTool behavior and
lastToolName tracking. Use the existing creditService.spendCredit and
spentAllocations flow, but move the charge inside the per-part handling so every
function-call part is accounted for.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 368486d3-dc9c-4b6f-8a1c-b425e204fb14

📥 Commits

Reviewing files that changed from the base of the PR and between 2105889 and f9c6da5.

📒 Files selected for processing (7)
  • cloud-agent/src/handlers/schedulerTriggerHandler.ts
  • cloud-agent/src/handlers/wsAgentHandler.ts
  • cloud-agent/src/services/agentEventLoop.ts
  • cloud-agent/src/services/creditService.ts
  • cloud-agent/src/tools/browserAction.ts
  • functions/src/generateEmbedding.ts
  • functions/src/summarizeText.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • cloud-agent/src/tools/browserAction.ts
  • functions/src/summarizeText.ts
  • cloud-agent/src/handlers/schedulerTriggerHandler.ts
  • functions/src/generateEmbedding.ts
  • cloud-agent/src/handlers/wsAgentHandler.ts
  • cloud-agent/src/services/creditService.ts

consumeAgentEvents only spent a credit inside the functionCall branch, so a
plain conversational reply with no tool call cost 0 credits — a real billing
gap, not the per-loop metering the spec intended. Extract the spend+cap logic
into chargeLoopIteration and call it for both the tool-call turn and the
no-tool-call/final-synthesis turn, so every completed model turn bills 1
credit (still capped at MAX_LOOP_ITERATIONS total). Update the design spec to
document the corrected behavior and add test coverage for the previously-free
no-tool-call reply.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@equationalapplications

Copy link
Copy Markdown
Owner Author

/fix-pr follow-up

Commit: 1dfc0427

Review resolution

No new unresolved review threads found (all 8 prior threads — 7 CodeRabbit + 1 Copilot — were already fixed and resolved in earlier passes). This pass addresses a billing gap found during manual re-review of the diff, at the user's direction:

  • cloud-agent/src/services/agentEventLoop.tsconsumeAgentEvents only spent a credit inside the functionCall branch, so a plain conversational reply with no tool call cost 0 credits (a real gap, not the intended per-loop metering) — Fixed: extracted a shared chargeLoopIteration helper and bill 1 credit per completed model turn — the tool-call turn and the no-tool-call/final-synthesis turn each bill independently, still capped at MAX_LOOP_ITERATIONS total.
  • docs/superpowers/specs/2026-07-01-credit-economy-repricing-design.md — updated to document the corrected per-turn billing behavior (a bare reply = 1 credit; a tool call + its synthesis reply = 2 credits).
  • Added test coverage in agentEventLoop.test.ts for the previously-free no-tool-call reply, and updated the existing tool-call test to expect 2 spends (tool call + synthesis).

Verification

  • typecheck (cloud-agent) — pass (npm run typecheck)
  • typecheck (functions) — pass (npm run typecheck)
  • tests (cloud-agent) — pass, 211/212 (1 pre-existing skip) (npm test)
  • tests (functions) — pass, 329/329 (npm test)

@equationalapplications
equationalapplications merged commit d3d5334 into main Jul 2, 2026
6 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants