Skip to content

feat(sdk): add post-auth mount workspace helper#122

Merged
khaliqgant merged 2 commits into
mainfrom
codex/post-auth-mount-session
May 9, 2026
Merged

feat(sdk): add post-auth mount workspace helper#122
khaliqgant merged 2 commits into
mainfrom
codex/post-auth-mount-session

Conversation

@khaliqgant

@khaliqgant khaliqgant commented May 9, 2026

Copy link
Copy Markdown
Member

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.

  • Adds RelayfileSetup.mountWorkspace({ workspaceId, localDir, remotePath, mode, background }) and RelayfileSetup.ensureMountedWorkspace({ workspace, provider, verifyProvider, ... }) to packages/sdk/typescript.
  • Calls POST /api/v1/workspaces/:workspaceId/relayfile/mount-session (Cloud companion: AgentWorkforce/cloud#502), maps the response into mount env, launches and supervises relayfile-mount, waits for first successful sync before resolving, and returns a MountedWorkspaceHandle with ready, expiresAt, suggestedRefreshAt, env(), status(), and stop().
  • Introduces typed errors so callers can branch deterministically: 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.
  • Maps mode: "poll" to the existing relayfile-mount poll; rejects mode: "stream" SDK-side with no HTTP traffic until the streaming mode is actually implemented (avoids a fake-stream contract).
  • verifyProvider: true either (a) waits for connected+ready and proceeds, (b) throws ProviderNotConnectedError(provider) if not connected, or (c) throws ProviderNotReadyError({ provider, state, initialSyncState }) on readiness timeout; verifyProvider: false skips the probe and returns a handle without claiming provider readiness.
  • onWrite now reads the base URL from an explicit client when one is provided, so a hostile RELAYFILE_BASE_URL cannot 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:

  • expiresAt and suggestedRefreshAt are now first-class fields on the SDK handle and status(), sourced from the Cloud route response.
  • Provider verification semantics are explicit and tested (connected/ready/timeout/skip) instead of being implied.
  • v1 mode mapping is locked to poll; stream is 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.tsmountWorkspace and ensureMountedWorkspace plus typed error mapping.
  • packages/sdk/typescript/src/mount-launcher.ts — local launcher and supervisor for relayfile-mount, with readiness gating, log capture, pid tracking, and idempotent stop().
  • packages/sdk/typescript/src/setup-errors.ts — typed error classes including ProviderNotReadyError({ provider, state, initialSyncState }).
  • packages/sdk/typescript/src/setup-types.tsMountSessionRequest, 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, including setup.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 and npm packs @relayfile/core and @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.md sync, the verifyProvider: true success and failure paths, the MountHarnessPermissionError path, and the SDK-side mode: "stream" rejection. Emits POST_AUTH_MOUNT_E2E_OK.
  • Evidence captured in docs/evidence/post-auth-mount-session-e2e.log and docs/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 under docs/post-auth-mount-session-*.md and docs/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.

@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 023ba05c-318b-4b54-8ead-94d8ded112f2

📥 Commits

Reviewing files that changed from the base of the PR and between 4706f26 and ceef135.

📒 Files selected for processing (1)
  • packages/sdk/typescript/src/setup.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/typescript/src/setup.ts

📝 Walkthrough

Walkthrough

This PR adds the post-auth mount-session contract and docs, exposes Relayfile SDK APIs (mountWorkspace, ensureMountedWorkspace) and types/errors, implements a default mount launcher and status reader, refactors client/onWrite baseUrl resolution, and provides unit and comprehensive E2E test harnesses with evidence artifacts.

Changes

Post-Auth Mount-Session Feature

Layer / File(s) Summary
Contract & Acceptance Criteria
docs/post-auth-mount-session-contract.md
Normative v1 contract specifying Cloud API (POST /api/v1/workspaces/{workspaceId}/relayfile/mount-session), SDK surface (mountWorkspace/ensureMountedWorkspace), MountedWorkspaceHandle semantics, provider verification, launcher requirements, test matrix, and cross-repo PR policy.
Design Documents & Evidence
docs/evidence/*, docs/guides/post-auth-mount-session.md, docs/post-auth-mount-session-reflection.md, docs/post-auth-mount-session-peer-review.md, docs/post-auth-mount-session-self-review.md
Architecture proposal, user guide, reflection, peer-review approval, self-review findings, and final evidence aggregation including cloud PR view JSON and patch.
Type Definitions & Error Classes
packages/sdk/typescript/src/setup-types.ts, packages/sdk/typescript/src/setup-errors.ts, packages/sdk/typescript/src/index.ts
Mount/session/launcher type exports (MountMode, MountSessionRequest/Response, MountedWorkspaceHandle, MountLauncher) and new error classes covering input validation, mode/provider readiness, and readiness timeouts.
Mount Launcher Implementation
packages/sdk/typescript/src/mount-launcher.ts, packages/sdk/typescript/src/mount-launcher.test.ts
Default launcher spawning relayfile-mount child process with readiness polling via state.json or HTTP fallback, FUSE unavailability detection, log rotation, PID file management, timeout enforcement, and graceful shutdown; tests cover readiness timeout, log/PID writing, FUSE translation, and status reading.
Setup API & Mount Workflow
packages/sdk/typescript/src/setup.ts, packages/sdk/typescript/src/setup.test.ts
RelayfileSetup.mountWorkspace and ensureMountedWorkspace with input normalization, workspace resolution/join, mount-session creation, launcher integration, provider verification, cloud error mapping, workspace token injection, and MountedWorkspaceHandleImpl; tests validate flows, error mapping, provider verification, abort behavior, and handle semantics.
Client & onWrite Base URL
packages/sdk/typescript/src/client.ts, packages/sdk/typescript/src/onWrite.ts, packages/sdk/typescript/src/onWrite.test.ts
RelayFileClient.getBaseUrl() method and onWrite refactor to resolve base URL at registration via resolveOnWriteBaseUrl, removing environment fallback from the sync-connection path and centralizing resolution logic.
E2E Integration Test & Scripts
packages/sdk/typescript/scripts/post-auth-mount-session-e2e.mjs, packages/sdk/typescript/package.json
Comprehensive e2e script that mocks Cloud/Relayfile services, packages SDK artifacts, generates and runs consumer/harness scenarios (mount/ensure/readonly/failure/invalid-mode), validates HTTP requests/responses, environment mapping, launcher lifecycle, relayfile interaction, and writes evidence logs; package.json scripts added to run the runner.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I stitched a token, spawned a mount,
Logs spin, state.json counts each count,
The launcher hums, the probe sings true,
Provider checks and handles too,
Sandboxes attach — hooray, we’re through!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title 'feat(sdk): add post-auth mount workspace helper' is fully related to the main changes in the PR, which adds mountWorkspace and ensureMountedWorkspace SDK helpers for the post-auth sandbox mount contract.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, explaining the new SDK helpers, error types, provider verification, onWrite updates, and linking to the Cloud companion PR.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/post-auth-mount-session

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

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

🧹 Nitpick comments (2)
docs/guides/post-auth-mount-session.md (2)

18-23: 💤 Low value

Add 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 text or ``` 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.md around 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
opening withtext) 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.md around 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 opening tohttp) 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 opening withtext)
    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 opening tohttp) 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 -->

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

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +298 to +302
try {
await waitForMountReady(launcherInstance.ready, normalized.signal)
} catch (error) {
await safeStopLauncher(launcherInstance)
throw error

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.

🟡 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
  1. Signal is aborted during or immediately after launcher.start() resolves (setup.ts:291-296)
  2. waitForMountReady is called — checks signal.aborted → true → throws immediately (setup.ts:1445-1447)
  3. Catch block calls safeStopLauncher which completes the stop
  4. The launcher's waitForReady resumes, detects abort, throws CloudAbortErrorready rejects
  5. No handler is attached to readyunhandledRejection event → process exits
Suggested change
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
}
Open in Devin Review

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.
@khaliqgant khaliqgant merged commit 051d2f8 into main May 9, 2026
7 checks passed
@khaliqgant khaliqgant deleted the codex/post-auth-mount-session branch May 9, 2026 13:47
khaliqgant added a commit that referenced this pull request May 9, 2026
…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>
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