Skip to content

feat(cloud-agents): pear-side cloud-agent feature (manager + UI scaffolding)#8

Merged
khaliqgant merged 2 commits into
mainfrom
ricky/wave-pear-cloud-agents/01-cloud-agents
May 21, 2026
Merged

feat(cloud-agents): pear-side cloud-agent feature (manager + UI scaffolding)#8
khaliqgant merged 2 commits into
mainfrom
ricky/wave-pear-cloud-agents/01-cloud-agents

Conversation

@khaliqgant

Copy link
Copy Markdown
Member

Spec 01 scaffolding. Builds on #7 (shared scaffold); review after #7 lands.

  • src/main/cloud-agent.ts + types: CloudAgentManager (attach/detach/restore/list/create/delete, warm-box polling, relayfile mount, conflict-policy launcher, git sync-back).
  • components/agents/: AddAgentDialog (local vs cloud picker), CloudAgentDialog, CloudAgentPicker, CloudAuthRequired.
  • hooks/use-cloud-agent.ts + stores/cloud-agent-store.ts.

Remaining gaps (restore-on-launch wiring + relayfile --conflict-policy CLI flag) tracked in slim spec specs/01-cloud-agents.md from #5. The cloud-side box endpoints are remediation spec 04 (cloud worktree).

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@khaliqgant has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 10 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 68a01b7c-960f-49da-ac1d-948f8b098d13

📥 Commits

Reviewing files that changed from the base of the PR and between d66ef8b and 66bdd6f.

📒 Files selected for processing (8)
  • src/main/cloud-agent.ts
  • src/main/cloud-agent.types.ts
  • src/renderer/src/components/agents/AddAgentDialog.tsx
  • src/renderer/src/components/agents/CloudAgentDialog.tsx
  • src/renderer/src/components/agents/CloudAgentPicker.tsx
  • src/renderer/src/components/agents/CloudAuthRequired.tsx
  • src/renderer/src/hooks/use-cloud-agent.ts
  • src/renderer/src/stores/cloud-agent-store.ts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

function isAuthRequiredError(error: unknown): boolean {
const code = getErrorCode(error)
if (code === 'AUTH_REQUIRED') return true
return /auth|required|sign in|login/i.test(getErrorMessage(error))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Overly broad regex in isAuthRequiredError causes false-positive auth redirects

The regex /auth|required|sign in|login/i at CloudAgentPicker.tsx:34 matches any error message containing the word "required" or "auth", not just actual authentication errors. This means API errors like "subscription required", "field X is required", "authorization scope insufficient", or even the main process validation errors ("Cloud agent name is required" from src/main/cloud-agent.ts:143) would be misidentified as auth failures. When triggered, the user is redirected to the sign-in flow (onAuthRequired?.()) instead of seeing the actual error message, causing confusing UX.

Suggested change
return /auth|required|sign in|login/i.test(getErrorMessage(error))
return /cloud-auth-required|sign in|login required/i.test(getErrorMessage(error))
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/main/cloud-agent.ts Outdated
const workspaceId = await this.requireAccountWorkspaceId()
const setup = new RelayfileSetup({
cloudApiUrl: auth.apiUrl,
accessToken: () => auth.accessToken

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Stale access token closure in startMount defeats SDK's token refresh mechanism

In startMount, the accessToken parameter is passed as a function () => auth.accessToken, but auth is a local variable captured from a single requireCloudAuth() call. The RelayfileSetup SDK accepts a function (not a plain string) precisely so it can call it repeatedly to obtain fresh tokens during the mount's lifetime. This implementation always returns the same token value. For long-running mounts (e.g., hours without a conflict policy change that would trigger startMount again), the token will expire and the mount will fail to authenticate its requests silently.

Prompt for agents
The issue is in startMount at line 734 in src/main/cloud-agent.ts. The accessToken parameter passed to RelayfileSetup captures a single resolved token value in a closure. Since the SDK accepts a function callback (rather than a plain string), it likely calls this function repeatedly to get fresh tokens during the mount lifecycle.

The fix should make the accessToken callback resolve a fresh token each time it's called. One approach: change the callback to call resolveCloudAuth() (or a similar method) each time. However, you need to check whether the SDK's accessToken callback supports async functions or only sync. If sync-only, you might need to maintain a cached token that gets refreshed periodically in the background. If async is supported, the fix could be:

accessToken: async () => {
  const freshAuth = await resolveCloudAuth()
  return freshAuth?.accessToken ?? auth.accessToken
}

The relevant function is requireCloudAuth (line 633) which calls resolveCloudAuth from src/main/auth.ts.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

return () => {
cancelled = true
}
}, [onAuthRequired])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Inline onAuthRequired arrow function causes useEffect to re-run on every parent re-render

In CloudAgentPicker.tsx:139, the useEffect depends on [onAuthRequired]. In the parent CloudAgentDialog.tsx:100, this prop is passed as an inline arrow function () => setState('auth-required'), which creates a new function reference on every render of CloudAgentDialog. Whenever CloudAgentDialog re-renders (e.g., due to store changes from useUIStore or useProjectStore), the effect re-runs, causing the agent list to be re-fetched from the API unnecessarily.

Prompt for agents
The problem is that the useEffect in CloudAgentPicker (line 106-139) has [onAuthRequired] as a dependency, but onAuthRequired is passed as an inline arrow function from CloudAgentDialog.tsx:100 which creates a new reference on each render.

Two approaches to fix:
1. In CloudAgentDialog.tsx, wrap the onAuthRequired callback in useCallback: const handleAuthRequired = useCallback(() => setState('auth-required'), [])
2. In CloudAgentPicker.tsx, use a ref to track onAuthRequired and remove it from the useEffect dependency array, so the effect only runs on mount. The ref approach: store onAuthRequired in a useRef and update it on each render, then call the ref.current in the effect's async function.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant khaliqgant changed the base branch from wave/pear-shared-scaffold to main May 21, 2026 14:08
khaliqgant and others added 2 commits May 21, 2026 16:09
…olding)

Spec 01 scaffolding for cloud agents in pear:
- src/main/cloud-agent.ts + cloud-agent.types.ts: CloudAgentManager
  (attach/detach/restore/list/create/delete, warm box polling, mount session
  via @relayfile/sdk, conflict-policy launcher, git sync-back).
- components/agents/: AddAgentDialog (local vs cloud picker),
  CloudAgentDialog, CloudAgentPicker, CloudAuthRequired.
- hooks/use-cloud-agent.ts + stores/cloud-agent-store.ts: renderer-side
  binding + catalog state.

Builds on the shared scaffolding (#7). The remaining gaps —
restore-on-launch in index.ts and the relayfile --conflict-policy CLI flag — are
covered in the slimmed spec at specs/01-cloud-agents.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…memoize callbacks

- CloudAgentPicker.isAuthRequiredError: match only explicit auth signals
  (cloud-auth-required / sign in to / login required / not logged in) instead
  of /auth|required|sign in|login/ which fired on innocuous messages like
  'Cloud agent name is required' or 'authorization scope insufficient' and
  misrouted users to the sign-in screen.
- cloud-agent.startMount: pass an async accessToken callback that resolves
  fresh credentials via resolveCloudAuth on each call. The previous closure
  captured a single token value, defeating RelayfileSetup's refresh mechanism
  and breaking long-running mounts when the token expired.
- CloudAgentDialog: wrap onAuthenticated/onAuthRequired in useCallback so the
  picker's useEffect doesn't refire on every parent re-render and re-fetch
  the agent list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@khaliqgant khaliqgant force-pushed the ricky/wave-pear-cloud-agents/01-cloud-agents branch from e8081e4 to 66bdd6f Compare May 21, 2026 14:09
@khaliqgant khaliqgant merged commit e2ac40f into main May 21, 2026
1 check passed
khaliqgant added a commit that referenced this pull request May 21, 2026
Spec 03 remaining work — the unentangled portion. Adds the standalone
artifacts; UI mounting (App.tsx/ui-store.ts/ProjectSettings.tsx) is
deliberately left for a follow-up PR because those files have edits
from other specs mixed in on the working branch.

- scripts/sync-runtime-types.mjs: writes vendor/agentworkforce-runtime-types.d.ts
  from @agentworkforce/persona-kit at build time; wired via prebuild script.
- vendor/agentworkforce-runtime-types.d.ts: vendored persona/runtime types
  that the editor type-checks the user's handler source against.
- package.json: + @monaco-editor/react ^4.7.0, + prebuild + sync:runtime-types
  scripts.
- test/proactive-agent.smoke.ts: vitest smoke that mocks @agentworkforce/deploy
  + cloud HTTP and walks create → deploy → simulated change event → run row
  succeeded, per spec acceptance gate #8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
khaliqgant added a commit that referenced this pull request May 21, 2026
Per the v1 consolidation discussion on AgentWorkforce/cloud#912AgentWorkforce/cloud#919 (draft), the parallel cloud proactive-runtime
modules the smoke test imported (proactive-trigger-router,
proactive-runner) have been dropped in favour of reusing the existing
agents + deployments + integration-watch-dispatcher pipeline.

The smoke test was tightly coupled to those modules (relative imports
from ../../cloud/packages/core/src/runtime/*) and to the
/proactive-personas/* route surface that no longer exists. Retargeting
it to the v1 surface would require a different test harness (the v1
dispatcher imports from cloud's web package, which pulls in Daytona +
relayauth infrastructure not easily smoke-testable from pear).

Pear's part of spec-03 acceptance gate #8 (deploy → trigger →
succeeded) is more honestly covered by an end-to-end test in workforce
(packages/deploy/src/modes/cloud.test.ts now asserts proactive personas
flow through /deployments correctly) plus cloud's existing
integration-watch-dispatcher tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
miyaontherelay added a commit that referenced this pull request Jun 8, 2026
Fix #8: previously listeners received the full (post-trim) buffer and
sliced from their captured writtenChunks. At the 10k cap the trim shifts
the window, so writtenChunks > buffer.length and the slice drops the
freshly-added chunks.

Listeners now receive only the newly-queued chunks ("tail"). The
terminal runtime does the snapshot-aware initial replay manually against
getPtyChunks before subscribing, and AgentNode re-pulls the canonical
buffer on each notification so its preview honours the trim cap.
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.

1 participant