Skip to content

Fix Factory usage metrics#561

Open
abyssbugg wants to merge 1 commit into
robinebers:mainfrom
abyssbugg:fix/factory-usage-metrics-surgical
Open

Fix Factory usage metrics#561
abyssbugg wants to merge 1 commit into
robinebers:mainfrom
abyssbugg:fix/factory-usage-metrics-surgical

Conversation

@abyssbugg

@abyssbugg abyssbugg commented Jun 5, 2026

Copy link
Copy Markdown

Summary

  • Add Factory supplemental usage calls for billing limits and compute usage.
  • Show current Factory/Droid metrics: extra usage, 5-hour, weekly, monthly, Droid Core, managed computers, and legacy token usage.
  • Update Factory manifest, docs, README provider summary, and regression tests.

Scope

Surgical Factory-only change based on latest upstream/main. No version bump, build workflow changes, or unrelated provider/app changes.

Validation

  • bun run test --run plugins/factory/plugin.test.js — 44 tests passed
  • bun run test --run — 64 files / 1093 tests passed
  • bun run build — passed

Screenshots

Draft PR: screenshots are pending because they require authenticated Factory account data. No UI layout/components changed; this updates provider data lines displayed by the existing UI.

Conversation: https://app.warp.dev/conversation/0133ae31-1784-4e10-87f5-baf8d6c69bca

Co-Authored-By: Oz oz-agent@warp.dev


Summary by cubic

Show full Factory/Droid usage: 5-hour, weekly, monthly, extra usage balance, Droid Core, and managed computer hours. Adds supplemental API calls and smarter parsing so data matches the Factory app.

  • New Features
    • Fetches GET /api/billing/limits and GET /api/organization/compute-usage when subscription usage lacks these fields; sends browser-like headers and userId; falls back to GET if POST returns 405.
    • Adds lines: Extra Usage, 5-hour usage, Weekly usage, Monthly usage, Droid Core (badge), and Managed Computers; preserves Standard and Premium tokens.
    • Normalizes legacy/new response shapes (percent vs ratio, start/end, durations) and shows reset timing.
    • Infers plan from allowance and appends "+ Droid Core" when enabled.
    • Updates provider docs with new endpoints and plan detection; refreshes README summary and regression tests.

Written for commit cfc2041. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Documentation

    • Updated Factory/Droid provider documentation with new plan types and API reference.
  • New Features

    • Added support for tracking Extra usage, Droid Core status, and Managed Computers usage.
    • Introduced new billing limits and compute usage tracking endpoints with improved plan detection.

Co-Authored-By: Oz <oz-agent@warp.dev>
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The Factory/Droid plugin gains support for extra usage tracking, Droid Core status, managed computers metrics via supplemental billing and compute-usage endpoints, and enhanced plan inference. Documentation, configuration, request handling, parsing utilities, and data integration work together to surface extended usage information in the UI.

Changes

Factory Plugin Extended Usage & Droid Core Support

Layer / File(s) Summary
API Documentation & Configuration
README.md, docs/providers/factory.md, plugins/factory/plugin.json
Updated README provider description and plugin UI line config to cover 5h/weekly/monthly usage, extra usage, Droid Core, and managed computers. Added Factory API reference docs for GET /api/billing/limits and GET /api/organization/compute-usage endpoints with request/response examples. Plan detection now accounts for Droid Core enablement.
Request & Token Handling
plugins/factory/plugin.js
Adds endpoint constants (APP_URL, usage/billing/compute paths); introduces JWT user ID extraction (from sub/user_id/userId) and request builders for consistent headers/payload/query formatting. Usage fetch now posts by default and retries with GET on HTTP 405. Retry handler tracks effective token and updates access token.
Usage Parsing & Normalization
plugins/factory/plugin.js
Implements extensive parsing utilities: extracting numbers and objects from varied response shapes, fetching JSON from billing/compute endpoints, detecting Droid Core and extended-usage configs, merging supplemental data, normalizing metrics with percent/count formats and time windows, and inferring plans from token allowances and Droid Core status.
Data Integration & Rendering
plugins/factory/plugin.js
Merges supplemental billing/compute data into usage object; refactors line construction to render extra usage text, percent-based quotas (5-hour/weekly/monthly progress), Droid Core badge, and managed-computers count progress. Plan now derives from inferPlan helper.
Test Coverage
plugins/factory/plugin.test.js
Enhanced JWT helper to merge claim objects into payload. Added three tests: usage-limit formatting with extra usage, Droid Core badge, and Managed Computers progress; fetching billing/compute endpoints for legacy subscriptions; POST request with userId in JWT sub and browser-like headers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix Factory usage metrics' is concise and directly reflects the main objective of the pull request—enhancing Factory/Droid usage metric reporting.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@abyssbugg abyssbugg marked this pull request as ready for review June 5, 2026 11:47
Copilot AI review requested due to automatic review settings June 5, 2026 11:47
@abyssbugg abyssbugg requested a review from davidarny as a code owner June 5, 2026 11:47
@abyssbugg

Copy link
Copy Markdown
Author

@robinebers this surgical Factory-only PR is ready for review.

Validation completed:

  • bun run test --run plugins/factory/plugin.test.js — 44 tests passed
  • bun run test --run — 64 files / 1093 tests passed
  • bun run build — passed

Screenshots are not attached because they require authenticated Factory account data. No UI layout/components changed; this only updates the provider data lines rendered by the existing UI.

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

Note

Copilot was unable to run its full agentic suite in this review.

Extends the Factory/Droid provider to surface additional quota/usage signals (rate-limit windows, extra usage balance, Droid Core, managed compute) and adds fallback API calls when the primary subscription usage response is legacy-only.

Changes:

  • Add supplemental fetch/normalization logic (billing limits + compute usage) and enrich displayed lines/plan labeling.
  • Update plugin manifest to display new lines (Extra Usage, 5-hour/weekly/monthly usage, Droid Core, Managed Computers).
  • Add/expand tests and documentation to cover the new endpoints and behaviors.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
plugins/factory/plugin.js Adds header/payload helpers, supplemental endpoint fetch/merge, new line builders, and updated plan inference.
plugins/factory/plugin.json Registers new UI lines for extra usage, rate windows, Droid Core, and managed compute.
plugins/factory/plugin.test.js Adds tests for new formatting behavior, legacy-only fallback fetching, and request headers/userId.
docs/providers/factory.md Documents the additional endpoints and updated plan detection notes.
README.md Updates provider capability summary to reflect the new metrics.

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

Comment on lines +11 to +16
function makeJwt(expSecondsOrClaims) {
const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT" }))
const payload = btoa(JSON.stringify({ exp: expSeconds, org_id: "org_123", email: "test@example.com" }))
const claims = typeof expSecondsOrClaims === "object"
? { org_id: "org_123", email: "test@example.com", ...expSecondsOrClaims }
: { exp: expSecondsOrClaims, org_id: "org_123", email: "test@example.com" }
const payload = btoa(JSON.stringify(claims))
Comment thread plugins/factory/plugin.js
Comment on lines +261 to +268
function getUserIdFromAccessToken(ctx, accessToken) {
const payload = ctx.jwt.decodePayload(accessToken)
if (!payload || typeof payload !== "object") return null
const rawUserId = payload.sub || payload.user_id || payload.userId
if (typeof rawUserId !== "string") return null
const userId = rawUserId.trim()
return userId || null
}
Comment thread plugins/factory/plugin.js
Comment on lines +537 to +545
const compute = requestGetJson(ctx, accessToken, COMPUTE_USAGE_URL, "compute usage")
if (isObject(compute) && !firstObject(
merged.managedComputers,
merged.managedComputerUsage,
merged.computers,
merged.compute,
merged.managedCompute,
)) {
const limitMs = firstNumber(compute, ["limitMs", "includedMs", "allowanceMs", "totalMs", "limit"])
Comment thread plugins/factory/plugin.js
Comment on lines +679 to +687
const managedUsed = firstNumber(managed, ["usedHours", "usageHours", "hoursUsed", "used", "current"])
const used = managedUsed === null ? 0 : managedUsed
const limit = firstNumber(managed, ["includedHours", "limitHours", "allowanceHours", "totalHours", "limit"])
if (limit === null || limit <= 0) return false
lines.push(ctx.line.progress({
label: "Managed Computers",
used,
limit,
format: { kind: "count", suffix: "h" },
Comment thread docs/providers/factory.md
Comment on lines +85 to +87
"fiveHour": { "usedPercent": 0.12, "windowEnd": 1770626926000, "secondsRemaining": 1200 },
"weekly": { "usedPercent": 0.34, "windowEnd": 1771228126000, "secondsRemaining": 604800 },
"monthly": { "usedPercent": 0.56, "windowEnd": 1772956800000, "secondsRemaining": 2333474 }

@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: 1

🧹 Nitpick comments (2)
plugins/factory/plugin.js (1)

261-711: 🏗️ Heavy lift

Split this plugin module to stay within the repository size guideline.

This file is now far beyond the ~400 LOC target; the newly added parsing/fetch/line-formatting logic is a strong extraction seam into smaller modules.

As per coding guidelines: **/*.{js,ts,tsx,jsx,rs,py,java,go,cpp,c,rb} should keep files under ~400 lines of code and split/refactor as needed.

🤖 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 `@plugins/factory/plugin.js` around lines 261 - 711, This file exceeds the repo
size guideline; split the large plugin.js into smaller modules by extracting
cohesive seams: move HTTP/usage helpers (buildUsageHeaders, buildUsagePayload,
buildUsageGetUrl, fetchUsage, refreshToken, requestGetJson) into a usage-api
module, move generic utilities (isObject, asNumber, firstValue, firstNumber,
firstObject, normalizePercent, metricStartValue, metricEndValue, metricResetsAt,
metricPeriodDurationMs, percentFromMetric) into a utils module, and move
presentation/line-formatting logic (windowMetricWithDuration,
mergeSupplementalUsage, addPercentUsageLine, addExtraUsageLine,
addDroidCoreLine, addManagedComputersLine, inferPlan, droidCoreConfig,
hasExtendedUsageFields, isDroidCoreEnabled) into a usage-ui module;
export/import these functions where needed and update plugin.js to orchestrate
them (retain getUserIdFromAccessToken and top-level exports), ensuring
tests/imports are updated and no logic changes are made during the refactor.
plugins/factory/plugin.test.js (1)

508-754: 🏗️ Heavy lift

Split this test suite; it’s well beyond the size guideline.

These additions are good, but this file is already very large. Consider splitting by behavior area (auth loading, refresh flow, usage formatting) to keep tests maintainable.

As per coding guidelines: **/*.{js,ts,tsx,jsx,rs,py,java,go,cpp,c,rb} should keep files under ~400 lines of code and split/refactor as needed.

🤖 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 `@plugins/factory/plugin.test.js` around lines 508 - 754, This file exceeds the
size guideline; split the large test suite into smaller focused files (e.g. auth
tests, refresh-flow tests, usage-formatting tests) by moving related it() blocks
into new files and keeping shared helpers (makeCtx, loadPlugin, makeJwt)
imported from the existing test helpers so you don't duplicate setup;
specifically extract the tests that exercise authentication/loading (those using
makeCtx + auth.json writes and makeJwt), the refresh flow cases, and the
usage/formatting assertions (the tests asserting result.plan and result.lines
such as the ones checking "Extra Usage", "5-hour usage", "Droid Core", "Managed
Computers", and "Standard"), ensure each new file references loadPlugin and
plugin.probe the same way, update imports to use the shared helpers, and keep
each test file under ~400 lines.
🤖 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 `@plugins/factory/plugin.js`:
- Around line 531-534: The merge currently forces merged.droidCore.enabled =
true whenever a coreLimits object exists; change it to respect explicit flags
from coreLimits: if coreLimits exists and coreLimits.enabled === false, set
merged.droidCore = { enabled: false }; otherwise if coreLimits exists and merged
has no droid core config (use droidCoreConfig(merged)), set merged.droidCore = {
enabled: true }. Reference the coreLimits variable, firstObject-derived input,
droidCoreConfig(merged) check, and merged.droidCore assignment when making the
change.

---

Nitpick comments:
In `@plugins/factory/plugin.js`:
- Around line 261-711: This file exceeds the repo size guideline; split the
large plugin.js into smaller modules by extracting cohesive seams: move
HTTP/usage helpers (buildUsageHeaders, buildUsagePayload, buildUsageGetUrl,
fetchUsage, refreshToken, requestGetJson) into a usage-api module, move generic
utilities (isObject, asNumber, firstValue, firstNumber, firstObject,
normalizePercent, metricStartValue, metricEndValue, metricResetsAt,
metricPeriodDurationMs, percentFromMetric) into a utils module, and move
presentation/line-formatting logic (windowMetricWithDuration,
mergeSupplementalUsage, addPercentUsageLine, addExtraUsageLine,
addDroidCoreLine, addManagedComputersLine, inferPlan, droidCoreConfig,
hasExtendedUsageFields, isDroidCoreEnabled) into a usage-ui module;
export/import these functions where needed and update plugin.js to orchestrate
them (retain getUserIdFromAccessToken and top-level exports), ensuring
tests/imports are updated and no logic changes are made during the refactor.

In `@plugins/factory/plugin.test.js`:
- Around line 508-754: This file exceeds the size guideline; split the large
test suite into smaller focused files (e.g. auth tests, refresh-flow tests,
usage-formatting tests) by moving related it() blocks into new files and keeping
shared helpers (makeCtx, loadPlugin, makeJwt) imported from the existing test
helpers so you don't duplicate setup; specifically extract the tests that
exercise authentication/loading (those using makeCtx + auth.json writes and
makeJwt), the refresh flow cases, and the usage/formatting assertions (the tests
asserting result.plan and result.lines such as the ones checking "Extra Usage",
"5-hour usage", "Droid Core", "Managed Computers", and "Standard"), ensure each
new file references loadPlugin and plugin.probe the same way, update imports to
use the shared helpers, and keep each test file under ~400 lines.
🪄 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 Plus

Run ID: 86c2557a-c49d-4d27-bbfe-ba37baa88734

📥 Commits

Reviewing files that changed from the base of the PR and between 8a5b337 and cfc2041.

📒 Files selected for processing (5)
  • README.md
  • docs/providers/factory.md
  • plugins/factory/plugin.js
  • plugins/factory/plugin.json
  • plugins/factory/plugin.test.js

Comment thread plugins/factory/plugin.js
Comment on lines +531 to +534
const coreLimits = limits && firstObject(limits.core, limits.droidCore, limits.droid_core)
if (coreLimits && !droidCoreConfig(merged)) {
merged.droidCore = { enabled: true }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Respect explicit Droid Core disable flags when merging supplemental limits.

Line 533 forces enabled: true whenever a core object exists. If the API returns enabled: false, UI/plan output will still show Droid Core as enabled.

Suggested fix
-      const coreLimits = limits && firstObject(limits.core, limits.droidCore, limits.droid_core)
-      if (coreLimits && !droidCoreConfig(merged)) {
-        merged.droidCore = { enabled: true }
-      }
+      const coreLimits = limits && firstObject(limits.core, limits.droidCore, limits.droid_core)
+      if (coreLimits && !droidCoreConfig(merged)) {
+        const hasExplicitFlag =
+          typeof coreLimits.enabled === "boolean" ||
+          typeof coreLimits.available === "boolean" ||
+          typeof coreLimits.included === "boolean"
+        const coreEnabled =
+          coreLimits.enabled === true ||
+          coreLimits.available === true ||
+          coreLimits.included === true ||
+          (!hasExplicitFlag && Object.keys(coreLimits).length > 0)
+        if (coreEnabled) merged.droidCore = { enabled: true }
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const coreLimits = limits && firstObject(limits.core, limits.droidCore, limits.droid_core)
if (coreLimits && !droidCoreConfig(merged)) {
merged.droidCore = { enabled: true }
}
const coreLimits = limits && firstObject(limits.core, limits.droidCore, limits.droid_core)
if (coreLimits && !droidCoreConfig(merged)) {
const hasExplicitFlag =
typeof coreLimits.enabled === "boolean" ||
typeof coreLimits.available === "boolean" ||
typeof coreLimits.included === "boolean"
const coreEnabled =
coreLimits.enabled === true ||
coreLimits.available === true ||
coreLimits.included === true ||
(!hasExplicitFlag && Object.keys(coreLimits).length > 0)
if (coreEnabled) merged.droidCore = { enabled: true }
}
🤖 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 `@plugins/factory/plugin.js` around lines 531 - 534, The merge currently forces
merged.droidCore.enabled = true whenever a coreLimits object exists; change it
to respect explicit flags from coreLimits: if coreLimits exists and
coreLimits.enabled === false, set merged.droidCore = { enabled: false };
otherwise if coreLimits exists and merged has no droid core config (use
droidCoreConfig(merged)), set merged.droidCore = { enabled: true }. Reference
the coreLimits variable, firstObject-derived input, droidCoreConfig(merged)
check, and merged.droidCore assignment when making the change.

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="plugins/factory/plugin.js">

<violation number="1" location="plugins/factory/plugin.js:533">
P2: This unconditionally sets `enabled: true` whenever a `coreLimits` object exists in the API response. If the API returns `{ enabled: false }` (e.g., a disabled or suspended Droid Core add-on), the UI will still show Droid Core as active and append `+ Droid Core` to the plan label. Check the explicit flags (`enabled`, `available`, `included`) from `coreLimits` before setting this.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread plugins/factory/plugin.js

const coreLimits = limits && firstObject(limits.core, limits.droidCore, limits.droid_core)
if (coreLimits && !droidCoreConfig(merged)) {
merged.droidCore = { enabled: true }

@cubic-dev-ai cubic-dev-ai Bot Jun 5, 2026

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.

P2: This unconditionally sets enabled: true whenever a coreLimits object exists in the API response. If the API returns { enabled: false } (e.g., a disabled or suspended Droid Core add-on), the UI will still show Droid Core as active and append + Droid Core to the plan label. Check the explicit flags (enabled, available, included) from coreLimits before setting this.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/factory/plugin.js, line 533:

<comment>This unconditionally sets `enabled: true` whenever a `coreLimits` object exists in the API response. If the API returns `{ enabled: false }` (e.g., a disabled or suspended Droid Core add-on), the UI will still show Droid Core as active and append `+ Droid Core` to the plan label. Check the explicit flags (`enabled`, `available`, `included`) from `coreLimits` before setting this.</comment>

<file context>
@@ -314,34 +353,363 @@
+
+      const coreLimits = limits && firstObject(limits.core, limits.droidCore, limits.droid_core)
+      if (coreLimits && !droidCoreConfig(merged)) {
+        merged.droidCore = { enabled: true }
+      }
+    }
</file context>
Suggested change
merged.droidCore = { enabled: true }
const hasExplicitFlag =
typeof coreLimits.enabled === "boolean" ||
typeof coreLimits.available === "boolean" ||
typeof coreLimits.included === "boolean"
const coreEnabled =
coreLimits.enabled === true ||
coreLimits.available === true ||
coreLimits.included === true ||
(!hasExplicitFlag && Object.keys(coreLimits).length > 0)
if (coreEnabled) merged.droidCore = { enabled: true }
Fix with cubic

@robinebers robinebers left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hey! This is Rob's AI reviewer. Thanks for the contribution.

The Factory expansion is useful and the coverage is strong. One thing needs fixing before merge:

  1. Droid Core can be shown as enabled when it is disabled - when /api/billing/limits returns a limits.core object, the current merge sets droidCore.enabled = true just because that object exists. Please preserve an explicit disabled value, for example when coreLimits.enabled === false, and add a regression test so a disabled Droid Core response does not show the Droid Core badge or append + Droid Core to the plan.

Everything else I checked looks aligned with the plugin shape, docs, and tests.

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.

3 participants