Skip to content

feat(agents): per-runner recent working directories synced via sessions#4509

Open
msfstef wants to merge 4 commits into
mainfrom
msfstef/recent-working-dirs
Open

feat(agents): per-runner recent working directories synced via sessions#4509
msfstef wants to merge 4 commits into
mainfrom
msfstef/recent-working-dirs

Conversation

@msfstef
Copy link
Copy Markdown
Contributor

@msfstef msfstef commented Jun 4, 2026

Goal

The Horton spawn prompt needs a working directory. On desktop a native folder picker works, but when spawning onto a runner on another machine — or from the mobile app — there's no way to browse the runner's filesystem. Mobile previously hardcoded "Mobile uses the server default working directory for now", and desktop recents were a per-device localStorage list with no runner awareness.

This PR gives every client a per-runner recent working directories picker that syncs across the user's devices, and makes mobile able to choose a sandbox profile + working directory at all.

Design decisions

1. Recents are derived from synced sessions, not stored.
We originally planned to attach recents as metadata/state on the user's principal entity. Review showed everything needed is already synced to every device: each session row carries spawn_args.workingDirectory and its dispatch_policy runner target, scoped to the user. So recents are just a live query: filter sessions by runner → dedupe paths keeping newest updated_at → top 10 (recentWorkingDirsForRunner in agents-server-ui/src/lib/recentWorkingDirectories.ts). Zero new server state, no migration, no new auth surface, and existing sessions populate the list retroactively.

Accepted trade-offs: recents live only as long as the sessions that reference them, and there's no per-row "remove" (they're facts, not a curated list). The principal-entity-state design remains the follow-up if curation/durability is ever needed — the derived list could seed it.

2. Mobile sends the sandbox profile with the working directory (bug fix).
args.workingDirectory is only honoured through a sandbox-profile factory (packages/agents/src/bootstrap.ts resolveCwd); a spawn with no sandbox falls back to unrestrictedSandbox(process.cwd()) and silently ignores the arg (process-wake.ts). Desktop always sent a profile; mobile sent none — so without this fix the chosen directory would be a no-op. Mobile now syncs sandbox_profiles on its runners shape, shows a profile picker (advertised order, first = default), and sends sandbox: { profile, key: entityUrl } like desktop.

3. Remote profiles take no working directory.
For local profiles (local/unrestricted, Docker) the working-directory section applies; for remote: true profiles the workspace lives in the provider VM, so the section is hidden and no directory is sent — same gating as desktop (isSandboxProfileRemote).

4. Shared helpers extracted, not duplicated.
pickDefaultSandboxProfile / useSandboxProfileSelection / isSandboxProfileRemote moved from NewSessionView.tsx into agents-server-ui/src/lib/sandboxProfiles.ts, deep-imported by mobile per the existing convention.

5. Desktop picker becomes per-runner; localStorage hook retired.
A desktop user targeting a remote runner needs that runner's recents, which localStorage can't provide. The picker now takes derived per-runner recents and defaults to the selected runner's most recent path (explicit picks win; switching runner re-derives, since paths from one machine may not exist on another). No migration needed — existing sessions already encode real recents.

Changes

  • agents-server-ui: new lib/recentWorkingDirectories.ts + tests, new lib/sandboxProfiles.ts (pure extraction), WorkingDirectoryPicker takes recents prop (✕-remove dropped), NewSessionView wires per-runner recents + default, deleted hooks/useRecentWorkingDirectories.ts
  • agents-mobile: entities shape + dispatch_policy, runners shape + sandbox_profiles, spawnEntity gains sandboxProfile/workingDirectory (+ tests), NewSessionScreen gains Sandbox and Working-directory sections (recents cards, free-text absolute path, runner default)

Testing

  • New unit tests: derivation helper (5) and mobile spawn body/schemas (6); full suites pass (server-ui 71, mobile 25), typecheck + lint clean
  • Not yet verified end-to-end against a live desktop runner + mobile device (spawn from mobile into a picked directory and confirm the session pwds there) — flagged for review

🤖 Generated with Claude Code

@msfstef msfstef added the claude label Jun 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

Electric Agents Desktop Builds

Build artifacts for commit 98e4b4f.

Platform Status Artifact
macOS Apple Silicon Passed DMG
macOS Intel Passed DMG
Windows x64 Passed Installer
Linux x64 Passed AppImage / deb

Workflow run

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

❌ Patch coverage is 32.78689% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.73%. Comparing base (5f96a15) to head (98e4b4f).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...-server-ui/src/components/views/NewSessionView.tsx 0.00% 20 Missing ⚠️
...ckages/agents-server-ui/src/lib/sandboxProfiles.ts 0.00% 20 Missing ⚠️
...erver-ui/src/components/WorkingDirectoryPicker.tsx 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #4509       +/-   ##
===========================================
- Coverage   69.21%   36.73%   -32.48%     
===========================================
  Files          77      232      +155     
  Lines        9238    19382    +10144     
  Branches     2878     6670     +3792     
===========================================
+ Hits         6394     7120      +726     
- Misses       2826    12229     +9403     
- Partials       18       33       +15     
Flag Coverage Δ
packages/agents 71.51% <ø> (ø)
packages/agents-mobile 65.70% <100.00%> (?)
packages/agents-server 72.73% <ø> (ø)
packages/agents-server-ui 5.93% <24.07%> (?)
packages/electric-ax 46.42% <ø> (ø)
typescript 36.73% <32.78%> (-32.48%) ⬇️
unit-tests 36.73% <32.78%> (-32.48%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

Electric Agents Mobile Build

Local mobile checks ran for commit 98e4b4f.

The EAS Android preview build was skipped because the mobile-eas-build label is not present.
Add the mobile-eas-build label to this PR to produce an installable preview build.

Workflow run

@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude Code Review

Summary

Incremental review (iteration 4). Since iteration 3, one new commit (98e4b4f80) landed, which directly resolves my previous suggestion #4. No other changes. Verified correct; no blocking issues.

What's Working Well

  • Suggestion Read from vaxine stream, send transactions to slot server #4 (screen readers announcing the abbreviated path) is fully resolved. The recent-dir cards now pass the full tildified path as label (NewSessionScreen.tsx:292) and let ellipsizeMode="head" clip purely at render width. Since OptionCard derives accessibilityLabel={label} (:406), VoiceOver/TalkBack now announce the complete path while the visual is still truncated head-first. This is a strictly better fix than what I suggested: rather than maintaining a separate full-path accessibilityLabel, dropping the fixed-width abbreviatePath pre-clip means the visible truncation now adapts to device width and font scale instead of a hardcoded 28-char budget — one source of truth, and the visible/announced strings can't drift.
  • Clean removal. The abbreviatePath import was dropped from the mobile file and there are no leftover references; detectHomeDir/tildifyPath remain the only pathDisplay imports needed. The shared abbreviatePath helper itself is untouched and still used by displayWorkingDirectory (sidebar grouping), so this is a mobile-display-only change with no cross-package impact.
  • The comment explains the reasoning well — it captures both that head-ellipsizing keeps the distinguishing tail and that the full label is what screen readers get, so the intent survives future edits.

Issues Found

Critical (Must Fix)

None.

Important (Should Fix)

None.

Suggestions (Nice to Have)

None new. All prior suggestions are now resolved or confirmed deliberate (see below).

Issue Conformance

No linked issue (per-convention nit, unchanged). PR body remains thorough and accurate. Changeset present (.changeset/recent-working-dirs-per-runner.md).

The one remaining self-flagged item is unchanged and is a verification step, not a code issue: the live E2E check (spawn from mobile into a picked directory against a real runner and confirm the session pwds there) before un-drafting.

Previous Review Status

Nothing outstanding on the code itself. The PR is in good shape pending the author's E2E verification.


Review iteration: 4 | 2026-06-04

msfstef added a commit that referenced this pull request Jun 4, 2026
…sandbox profiles

A working directory only takes effect through a sandbox-profile factory,
so when a runner advertises no profiles the chosen directory would be
silently ignored — the same no-op this PR fixes for mobile. Gate the
working-directory section (and the spawn arg) on an actually-selected
non-remote profile, on both desktop and mobile.

Addresses the review-bot suggestion on #4509.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@msfstef
Copy link
Copy Markdown
Contributor Author

msfstef commented Jun 4, 2026

Re: the two suggestions carried over in review iteration 2 — both are deliberate, documenting the reasoning here:

#2 — mobile defaults to "Runner default" while desktop defaults to the most-recent dir. Intentional, decided during design: mobile's cold-start flow is runner default + free-text path, while desktop preserves its pre-existing "resume where you last worked" behavior. Strict parity isn't the goal — mobile users typically spawn into a remote runner whose paths they may not know, so defaulting to the runner's configured directory is the safer default. If usage shows mobile users always tapping the newest recent, pre-selecting it is a one-line follow-up.

#3 — the desktop default-selection effect interplay isn't unit-tested. Acknowledged coverage boundary: agents-server-ui has no render-test harness (pure vitest, no jsdom/renderHook), so the userPickedDirRef reset/re-derive ordering relies on manual verification. The derivation logic itself (recentWorkingDirsForRunner) is fully covered. Adding a component-test harness to the package is out of scope for this PR.

Remaining before un-drafting: the live E2E check (spawn from mobile into a picked directory against a real runner and confirm the session pwds there).

🤖 Generated with Claude Code

msfstef and others added 3 commits June 4, 2026 18:36
Derive the spawn prompt's recent-working-directories list from the
already-synced entities collection (spawn_args.workingDirectory +
dispatch_policy runner target) instead of per-device localStorage, so
the same per-runner recents appear on desktop, web, and mobile.

- agents-server-ui: new recentWorkingDirsForRunner lib helper; the
  working-directory picker takes per-runner recents and defaults to the
  selected runner's most recent path; sandbox-profile helpers extracted
  to lib/sandboxProfiles.ts for reuse; localStorage recents hook removed
- agents-mobile: runners shape gains sandbox_profiles and entities shape
  gains dispatch_policy; the new-session screen gains sandbox-profile and
  working-directory sections (recents, free-text path, runner default,
  hidden for remote profiles); spawnEntity sends sandbox.profile with
  args.workingDirectory — required, since the runtime only honours the
  working directory through a sandbox profile factory

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sandbox profiles

A working directory only takes effect through a sandbox-profile factory,
so when a runner advertises no profiles the chosen directory would be
silently ignored — the same no-op this PR fixes for mobile. Gate the
working-directory section (and the spawn arg) on an actually-selected
non-remote profile, on both desktop and mobile.

Addresses the review-bot suggestion on #4509.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… expert review

Findings from security / version-skew / UX+a11y review passes:

- OptionCard now sets accessibilityRole/State/Label (selection was
  conveyed by color only, below the bar SessionRow et al. set)
- Recent-directory cards tildify + abbreviate paths and ellipsize from
  the head: absolute paths share long prefixes and differ at the tail,
  so tail truncation rendered distinct projects identically
- Working directory is only offered/sent for the default (horton)
  agent, mirroring the desktop composer — other agent types have their
  own creation schemas and may reject unknown args
- Free-text path input gains an accessibilityLabel

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@msfstef msfstef force-pushed the msfstef/recent-working-dirs branch from 2cc3a89 to 1054bc2 Compare June 4, 2026 15:38
@msfstef
Copy link
Copy Markdown
Contributor Author

msfstef commented Jun 4, 2026

Simulator Screenshot - iPhone 15 Pro - 2026-06-04 at 18 56 56 Simulator Screenshot - iPhone 15 Pro - 2026-06-04 at 18 56 51

@msfstef msfstef requested review from kevin-dp and samwillis June 4, 2026 16:05
@msfstef msfstef marked this pull request as ready for review June 4, 2026 16:05
…idth

Drop the fixed 28-char abbreviatePath pre-clip on mobile recent-dir
cards; tildify only and let ellipsizeMode="head" truncate at the
rendered width. Adapts to device width and font scale, and screen
readers now announce the full path (review suggestion #4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant