feat(sdk): add post-auth mount workspace helper#122
Conversation
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR adds the post-auth mount-session contract and docs, exposes Relayfile SDK APIs ( ChangesPost-Auth Mount-Session Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
docs/guides/post-auth-mount-session.md (2)
18-23: 💤 Low valueAdd language specifier to fenced code block.
The static analysis tool flagged this code block as missing a language specifier. Since this is an ASCII diagram, use
textor```with no language to suppress the warning consistently.📝 Suggested fix
-``` +```text [Cloud auth] → [Provider connect] → mountWorkspace() → local files on disk ↑ skipped if verifyProvider: false or no provider needed</details> <details> <summary>🤖 Prompt for AI Agents</summary>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/guides/post-auth-mount-session.mdaround lines 18 - 23, The fenced
ASCII diagram in docs/guides/post-auth-mount-session.md lacks a language
specifier; update the code fence to mark it as plain text (e.g., replace the
openingwithtext) so the static analyzer stops flagging it and the
diagram remains unchanged; locate the diagram block containing "[Cloud auth] →
[Provider connect] → mountWorkspace()" and add the language tag to the opening
fence.</details> --- `45-49`: _💤 Low value_ **Add language specifier to fenced code block.** This endpoint reference block was flagged by markdownlint for missing a language specifier. Use `http` or `text` for HTTP endpoint documentation. <details> <summary>📝 Suggested fix</summary> ```diff -``` +```http POST /api/v1/workspaces/{workspaceId}/relayfile/mount-session Authorization: Bearer <workspace-JWT or cloud-access-token> Content-Type: application/json ``` ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary>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/guides/post-auth-mount-session.mdaround lines 45 - 49, The fenced code
block showing the HTTP endpoint for "POST
/api/v1/workspaces/{workspaceId}/relayfile/mount-session" is missing a language
specifier; update the opening fence to include an HTTP language (e.g., change
the openingtohttp) so markdownlint recognizes it as an HTTP code block
and the request snippet (Authorization, Content-Type lines) is highlighted
properly.</details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.Nitpick comments:
In@docs/guides/post-auth-mount-session.md:
- Around line 18-23: The fenced ASCII diagram in
docs/guides/post-auth-mount-session.md lacks a language specifier; update the
code fence to mark it as plain text (e.g., replace the openingwithtext)
so the static analyzer stops flagging it and the diagram remains unchanged;
locate the diagram block containing "[Cloud auth] → [Provider connect] →
mountWorkspace()" and add the language tag to the opening fence.- Around line 45-49: The fenced code block showing the HTTP endpoint for "POST
/api/v1/workspaces/{workspaceId}/relayfile/mount-session" is missing a language
specifier; update the opening fence to include an HTTP language (e.g., change
the openingtohttp) so markdownlint recognizes it as an HTTP code block
and the request snippet (Authorization, Content-Type lines) is highlighted
properly.</details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Organization UI **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `b5a5e8b5-a20b-4c8f-aed3-499fafcdeaef` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 7b97759611da7ca5d039e4970f466d01bc7af66e and 4706f267d3233ed75f9ba0a3503d36c2ab8b668d. </details> <details> <summary>⛔ Files ignored due to path filters (2)</summary> * `docs/evidence/post-auth-mount-session-e2e.log` is excluded by `!**/*.log` * `package-lock.json` is excluded by `!**/package-lock.json` </details> <details> <summary>📒 Files selected for processing (20)</summary> * `docs/evidence/cloud-pr-498-view.json` * `docs/evidence/cloud-pr-498.patch` * `docs/evidence/post-auth-mount-session-final-evidence.md` * `docs/guides/post-auth-mount-session.md` * `docs/post-auth-mount-session-contract.md` * `docs/post-auth-mount-session-peer-review.md` * `docs/post-auth-mount-session-reflection.md` * `docs/post-auth-mount-session-self-review.md` * `packages/sdk/typescript/package.json` * `packages/sdk/typescript/scripts/post-auth-mount-session-e2e.mjs` * `packages/sdk/typescript/src/client.ts` * `packages/sdk/typescript/src/index.ts` * `packages/sdk/typescript/src/mount-launcher.test.ts` * `packages/sdk/typescript/src/mount-launcher.ts` * `packages/sdk/typescript/src/onWrite.test.ts` * `packages/sdk/typescript/src/onWrite.ts` * `packages/sdk/typescript/src/setup-errors.ts` * `packages/sdk/typescript/src/setup-types.ts` * `packages/sdk/typescript/src/setup.test.ts` * `packages/sdk/typescript/src/setup.ts` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| try { | ||
| await waitForMountReady(launcherInstance.ready, normalized.signal) | ||
| } catch (error) { | ||
| await safeStopLauncher(launcherInstance) | ||
| throw error |
There was a problem hiding this comment.
🟡 Unhandled promise rejection when abort signal fires before Promise.race attaches a handler to launcherInstance.ready
When signal.aborted is already true by the time waitForMountReady is called (e.g., the signal fires during launcher.start()), the function throws CloudAbortError at setup.ts:1446-1447 without ever calling Promise.race on the ready promise. This means no .then(_, reject) handler is attached to launcherInstance.ready. Later, when the real launcher's internal waitForReady (mount-launcher.ts:224-226) detects the abort and rejects, the rejection is unhandled. In Node.js 18+ (the minimum required version per package.json:"node": ">=18"), unhandled rejections terminate the process by default.
Triggering sequence
- Signal is aborted during or immediately after
launcher.start()resolves (setup.ts:291-296) waitForMountReadyis called — checkssignal.aborted→ true → throws immediately (setup.ts:1445-1447)- Catch block calls
safeStopLauncherwhich completes the stop - The launcher's
waitForReadyresumes, detects abort, throwsCloudAbortError→readyrejects - No handler is attached to
ready→unhandledRejectionevent → process exits
| try { | |
| await waitForMountReady(launcherInstance.ready, normalized.signal) | |
| } catch (error) { | |
| await safeStopLauncher(launcherInstance) | |
| throw error | |
| try { | |
| await waitForMountReady(launcherInstance.ready, normalized.signal) | |
| } catch (error) { | |
| launcherInstance.ready.catch(() => {}) | |
| await safeStopLauncher(launcherInstance) | |
| throw error | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
If the abort signal fires before waitForMountReady attaches its Promise.race handler, launcherInstance.ready will reject later from the launcher's own abort handling. Without a handler attached, Node 18+ surfaces an unhandledRejection and terminates the process by default. Attach a no-op .catch in the failure path so the rejection is observed.
…ce langs (#123) * docs: surface mountWorkspace SDK helper in README; fix guide code-fence langs Adds a "Mount a workspace from a sandbox (TypeScript SDK)" subsection under Hosted Agent Relay so the new SDK helper from #122 is discoverable from the project README and reads as the programmatic sibling of the existing `relayfile setup` CLI flow. Links the new guide from the Docs section. Also addresses CodeRabbit nits on docs/guides/post-auth-mount-session.md by adding language specifiers to two unspecified fenced code blocks (text for the ASCII flow diagram, http for the endpoint reference). * docs(readme): fix mount sample — use handle.expiresAt and workspaceId CodeRabbit / Devin Review on #123: - `handle.status()` returns a Promise; `handle.status().expiresAt` was reading off the unresolved Promise. The handle exposes `expiresAt` as a readonly synchronous property — use that. - `setup.ensureMountedWorkspace({ workspace: { id: ... } })` doesn't match `MountWorkspaceInput.workspace?: WorkspaceHandle`. Switch to the `workspaceId: string` overload, which the SDK accepts as a primitive. --------- Co-authored-by: Writeback Reliability Bot <agent@agent-relay.com>
Summary
Adds the Relayfile half of the post-auth sandbox mount contract: a one-call SDK helper that takes an authenticated user and a workspace and returns a live, supervised local mount of that workspace inside any sandbox.
RelayfileSetup.mountWorkspace({ workspaceId, localDir, remotePath, mode, background })andRelayfileSetup.ensureMountedWorkspace({ workspace, provider, verifyProvider, ... })topackages/sdk/typescript.POST /api/v1/workspaces/:workspaceId/relayfile/mount-session(Cloud companion: AgentWorkforce/cloud#502), maps the response into mount env, launches and supervisesrelayfile-mount, waits for first successful sync before resolving, and returns aMountedWorkspaceHandlewithready,expiresAt,suggestedRefreshAt,env(),status(), andstop().MountSessionInputError,MountSessionAuthError,MountSessionForbiddenError,MountSessionNotFoundError,InvalidMountModeError,InvalidLocalDirError,InvalidRemotePathError,ProviderNotConnectedError,ProviderNotReadyError,MountHarnessPermissionError. Supports both{ code }and{ error }shapes from Cloud so the typed mapping holds against the real route.mode: "poll"to the existingrelayfile-mount poll; rejectsmode: "stream"SDK-side with no HTTP traffic until the streaming mode is actually implemented (avoids a fake-stream contract).verifyProvider: trueeither (a) waits for connected+ready and proceeds, (b) throwsProviderNotConnectedError(provider)if not connected, or (c) throwsProviderNotReadyError({ provider, state, initialSyncState })on readiness timeout;verifyProvider: falseskips the probe and returns a handle without claiming provider readiness.onWritenow reads the base URL from an explicit client when one is provided, so a hostileRELAYFILE_BASE_URLcannot redirect the WebSocket while leaving HTTP correct.Relationship to AgentWorkforce/cloud#498 and AgentWorkforce/cloud#502
cloud#498 is the docs-only proposal for the post-auth sandbox mount contract. This PR and AgentWorkforce/cloud#502 implement that contract end-to-end across both repos and resolve the open review comments on #498:
expiresAtandsuggestedRefreshAtare now first-class fields on the SDK handle andstatus(), sourced from the Cloud route response.poll;streamis not silently faked.The two PRs are independently reviewable but only ship value together: cloud#502 owns the route/tokens, this PR owns the consumer surface and the sandbox-side supervision.
Files of interest
packages/sdk/typescript/src/setup.ts—mountWorkspaceandensureMountedWorkspaceplus typed error mapping.packages/sdk/typescript/src/mount-launcher.ts— local launcher and supervisor forrelayfile-mount, with readiness gating, log capture, pid tracking, and idempotentstop().packages/sdk/typescript/src/setup-errors.ts— typed error classes includingProviderNotReadyError({ provider, state, initialSyncState }).packages/sdk/typescript/src/setup-types.ts—MountSessionRequest,MountSessionResponse,MountedWorkspaceHandle.docs/post-auth-mount-session-contract.md— locked acceptance contract reconciling cloud#498 with the existing relayfile-mount surface.docs/guides/post-auth-mount-session.md— integrator guide for the post-auth flow with Daytona/E2B/local examples.Proof
npm run test --workspace=packages/sdk/typescript— 134/134 passing, includingsetup.test.ts(37),mount-launcher.test.ts(4),onWrite.test.ts(18), and the new typed-400 mapping regression.npm run typecheck --workspace=packages/sdk/typescript— clean.go test ./cmd/relayfile-mount ./internal/mountsync/... -count=1 -timeout 180s— passing.npm run post-auth-mount:e2e --workspace=packages/sdk/typescript— packaged E2E that builds andnpm packs@relayfile/coreand@relayfile/sdk, installs them into a temporary consumer, runs against a mock Cloud route + mock Relayfile, and asserts request path/body/auth, env mapping, readiness gating,status().expiresAt,env(),stop(), the seeded/notion/research/brief.mdsync, theverifyProvider: truesuccess and failure paths, theMountHarnessPermissionErrorpath, and the SDK-sidemode: "stream"rejection. EmitsPOST_AUTH_MOUNT_E2E_OK.docs/evidence/post-auth-mount-session-e2e.loganddocs/evidence/post-auth-mount-session-final-evidence.md.Process artifacts in this PR
This PR was produced by an agent-relay multi-agent workflow (
workflows/065-post-auth-mount-session.ts). The workflow's locked contract, reflection, self-review, peer-review, and final-evidence documents are committed underdocs/post-auth-mount-session-*.mdanddocs/evidence/so the contract and review trail travel with the change. Reviewers can read them in any order; the contract and the evidence log are the load-bearing artifacts, and the rest is process record.