Repository navigation
Conversation
Bring simulator discovery, authenticated streaming, device controls and scoped agent tools into Pathway's existing environment and panel model. Add per-environment setup, SSH hosts and demand-rendered 3D views without new Convex queries or writes. Repair stream reconnect/decoder recovery, preserve host identity and gate Duo display handoffs on native acknowledgements. Use original procedural geometry and keep the pinned native Duo prototype opt-in with documented limitations. Validated with focused tests, server/web typechecks, lint and isolated simulator/RPC checks. Model: GPT-6. Harness: Codex desktop.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Replace constructor parameter properties with explicit fields and assignments so the new recovery tests pass the client runtime TypeScript configuration.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
Regenerate the bundled terminal source manifest for the updated workspace lockfile and contract graph. Terminal assets remain unchanged, and the native bundle verification passes. The formatter hook excludes this generated file, so its source-and-asset integrity check was run directly.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
Declare the standalone native build script boundary for Node APIs and use Effect Console for its result. The scripts package typecheck and targeted checks now pass.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
Replace the boxy foldable fallback with inset rounded glass, a beveled metal body, articulated hinge hardware and rear details. Map each display leaf to exactly half of the native framebuffer so the hinge gap no longer crops content. Validate folded touch mapping and image continuity with 21 scene/viewer tests, client-runtime typecheck and targeted lint. An isolated live simulator pass verified open, book, closed and orbit views.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e970b578b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <Button | ||
| disabled={props.state.hostStatus !== "ready" || pending !== null} | ||
| onClick={() => setStep(step + 1)} |
There was a problem hiding this comment.
Allow remote-only hosts to complete onboarding
When the environment machine has no local simulator toolchain but a configured SSH host becomes ready, this condition permanently disables Continue and Done. DeviceService.setHostStatus only mirrors status into the legacy hostStatus field for the local host, while remote readiness is recorded solely in hostStatuses, so a remote-only setup remains idle even after its SSH host is ready. Gate onboarding on any ready host status, or derive an aggregate status that includes remote hosts.
AGENTS.md reference: AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
| const command = [node, launcherPath] | ||
| .map((value) => "'" + value.replaceAll("'", "'\"'\"'") + "'") | ||
| .join(" "); | ||
| const script = `#!/bin/sh\nexec ${command} "$@"\n`; |
There was a problem hiding this comment.
Force the desktop Electron shim to run as Node
When a provider invokes this shim from a Linux desktop backend using the ACP cgroup wrapper, the command launches the Electron executable without ELECTRON_RUN_AS_NODE=1: resolveNodeExecutable deliberately returns process.execPath for Electron, while AcpSessionRuntime.ts lines 229-232 explicitly removes that environment variable before starting the provider. The returned agent-device command consequently launches Electron rather than executing the generated .mjs launcher, breaking agent device control in that desktop context; the shim must set Node mode itself when spawning the resolved runtime.
AGENTS.md reference: AGENTS.md:L19-L25
Useful? React with 👍 / 👎.
| export function platformSetupStatus(state: DeviceServiceState, platform: DevicePlatform) { | ||
| const availability = state.hosts | ||
| .flatMap((host) => host.platforms) | ||
| .find((candidate) => candidate.platform === platform); |
There was a problem hiding this comment.
Aggregate platform availability across device hosts
For an environment whose local machine lacks a platform but whose SSH host provides it, this selects the local host's first matching entry and reports the platform as unavailable. Hosts are published local-first, so a Linux environment connected to a Mac will still show the local “iOS needs macOS” failure even after the remote Mac is ready. Select an available matching entry when any host supports the platform, and only choose a failure reason when none do.
AGENTS.md reference: AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
| const commandLine = yield* runner | ||
| .run({ | ||
| command: "ps", | ||
| args: ["-o", "command=", "-p", String(previous.value.pid)], |
There was a problem hiding this comment.
Reap stale device hubs on Windows
After an abrupt server exit on Windows, the recorded hub can still be alive, but this unconditional ps invocation fails because standard Windows installations do not provide ps. The failure is converted to an empty command line, after which hub.json is deleted without terminating the verified PID; every subsequent crash/restart can therefore leave another device-hub process running. Use a platform-specific process inspection method before removing the state file.
Useful? React with 👍 / 👎.
| return { | ||
| httpBase, | ||
| wsBase, | ||
| query: { wsTicket: ticket.ticket }, | ||
| credentials: false, |
There was a problem hiding this comment.
Refresh media tickets before their five-minute expiry
For bearer and DPoP connections, this retains only the ticket string and discards the server-provided expiresAt, so the active atom can keep serving the same five-minute ticket indefinitely. After the panel has been open for five minutes, newly initiated requests such as enabling the accessibility overlay or opening the iOS event log receive 401 responses and retry with the same expired ticket; those paths do not call refreshDeviceHubAccess, so they remain broken until another media channel happens to fail or the workspace remounts. Preserve the expiry and proactively refresh the access object before issuing new proxy requests.
AGENTS.md reference: AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
| if (commandLine.includes(previous.value.entryPath)) { | ||
| yield* Effect.logWarning("Stopping a device hub left behind by a previous server", { | ||
| pid: previous.value.pid, | ||
| port: previous.value.port, | ||
| }); | ||
| yield* Effect.sync(() => { | ||
| try { | ||
| process.kill(previous.value.pid, "SIGTERM"); |
There was a problem hiding this comment.
Verify stale hub ownership without path matching
After a recorded PID has been recycled, this path-only command-line check can match another Pathway server running the same installed hub entry and send SIGTERM to that unrelated process. This is especially possible when development and userdata servers share a base directory and therefore the same versioned tool path. Verify the listener on the recorded port and the process working directory or another unique ownership token before killing rather than treating an entry-path substring as identity.
AGENTS.md reference: AGENTS.md:L63-L65
Useful? React with 👍 / 👎.
| const stopAgent: DeviceHost.DeviceHost["Service"]["stopAgent"] = startLock.withPermits(1)( | ||
| Effect.gen(function* () { | ||
| yield* stopAgentDeviceDaemon(agentToolRef); | ||
| yield* Ref.update(runningRef, (running) => | ||
| running ? { ...running, agentDevice: null } : running, |
There was a problem hiding this comment.
Stop a recovered daemon when agent access is revoked
After an unclean server exit, agent-device is designed to survive and be reused from daemon.json, but a fresh LocalDeviceHost starts with agentToolRef set to null. If the user disables agent access or all device support before another agent call runs ensureAgentReady, this call becomes a no-op and leaves the old authenticated daemon running, so previously issued config files and tokens can continue controlling devices despite the setting being off. Resolve the installed agent entry from durable state when stopping, rather than relying only on the current process having populated the in-memory ref.
AGENTS.md reference: AGENTS.md:L77-L77
Useful? React with 👍 / 👎.
| const closing = state.sessions.filter( | ||
| (session) => | ||
| session.threadId === input.threadId && | ||
| (input.hostId === undefined || session.hostId === input.hostId) && | ||
| (input.deviceId === undefined || session.deviceId === input.deviceId), | ||
| ); |
There was a problem hiding this comment.
Scope a targeted close to one device host
When two hosts expose the same device ID, omitting hostId while supplying deviceId makes this predicate match both sessions. An agent calling device_close({deviceId: "emulator-5554", shutdown: true}) can therefore close and power off identically named devices on every host, even though the new device model treats (hostId, deviceId) as the identity. Default a targeted close to the local host, require hostId, or reject an ambiguous match; reserve the all-host wildcard for the documented case where deviceId is omitted.
AGENTS.md reference: AGENTS.md:L78-L78
Useful? React with 👍 / 👎.
| Effect.flatMap((request) => | ||
| hubJson(request, HubActionResult, "attach stream", BOOT_TIMEOUT), | ||
| ), |
There was a problem hiding this comment.
Reject failed iOS stream attachment
The iOS grid-start endpoint uses the same {ok, error} result shape as boot, but this call validates only the HTTP status and schema and discards ok. If the hub returns HTTP 200 with ok: false, open continues, publishes a session, and shows a panel whose stream helper was never attached. Check the returned result and fail with a device operation error in both the newly booted and already-booted attachment paths.
Useful? React with 👍 / 👎.
| const executable = /^[a-zA-Z0-9_./:-]+$/.test(command) | ||
| ? command | ||
| : "'" + command.replaceAll("'", "'\"'\"'") + "'"; | ||
| const target = targetArgs | ||
| .map((arg) => | ||
| /^[a-zA-Z0-9_./:-]+$/.test(arg) ? arg : "'" + arg.replaceAll("'", "'\"'\"'") + "'", | ||
| ) |
There was a problem hiding this comment.
Generate Windows-compatible agent quick-start commands
On Windows, command is the absolute .cmd launcher path, but the POSIX quoting here wraps every backslash-containing path in single quotes. In PowerShell a quoted command path requires the call operator (&), and in cmd.exe single quotes are literal, so the generated “Typical loop” commands fail even though the structured launcher path is valid. Format the quick-start examples for the detected host shell/platform instead of always applying POSIX shell quoting.
AGENTS.md reference: AGENTS.md:L19-L25
Useful? React with 👍 / 👎.
Include device_list and device_screenshot in Claude’s read-only MCP allowlist so it matches the production toolkit annotations. Device control remains gated by current environment consent. The 77-test Claude adapter suite, server typecheck, targeted lint and formatting pass.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b0160622f2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!powerOff) { | ||
| useRightPanelStore.getState().closeSurface(props.threadRef, props.surface.id); | ||
| return; |
There was a problem hiding this comment.
Remove the server session when closing a device view
When the user closes a running device without powering it off, this only removes the local surface and leaves its DeviceSession registered on the server. Besides making device_list and implicit device_screenshot claim the dismissed device is still open, a later agent device_open for the same device cannot reopen the panel: useDeviceSessionSync still has that session identity in its seen set, so the republished session is not considered new. Call device.close with shutdown: false for every device-surface close path before removing the local surface.
AGENTS.md reference: AGENTS.md:L73-L78
Useful? React with 👍 / 👎.
| yield* publish((current) => ({ | ||
| ...current, | ||
| sessions: current.sessions.filter((session) => !closing.includes(session)), | ||
| })); | ||
| if (input.shutdown) { |
There was a problem hiding this comment.
Defer session removal until shutdown succeeds
When device_close is called with shutdown: true and the hub shutdown request subsequently fails, this publishes the session removal before returning the error. Connected clients immediately reconcile away the device surface, so the UI's failure handler has nowhere to display the error and the user loses the live panel even though the device remains running. Preserve the sessions until shutdown succeeds, while retaining the immediate removal for a close that does not request shutdown.
Useful? React with 👍 / 👎.
| hostLabel={ | ||
| state.hosts.find((host) => host.id === activeDevice.hostId)?.label ?? "Device host" | ||
| } | ||
| hostDiagnostics={state.hostStatusDetail} |
There was a problem hiding this comment.
Read diagnostics from the active device host
When the active device belongs to an SSH host, this passes the legacy local-host diagnostic field into its Tools panel. Remote discovery errors are stored under state.hostStatuses[activeDevice.hostId].detail, so the drawer either hides the relevant remote warning or displays an unrelated local-host warning. Select diagnostics using the active device's hostId.
AGENTS.md reference: AGENTS.md:L78-L78
Useful? React with 👍 / 👎.
| for (const permission of permissions) { | ||
| // Not every app declares every permission in a group; ignore those. | ||
| yield* shell(["pm", verb, input.appId, permission], "permission").pipe(Effect.ignore); | ||
| } |
There was a problem hiding this comment.
Report Android permission commands when all fail
For Android permissions, every pm grant or pm revoke failure is discarded. Although ignoring one unavailable permission is useful for version-dependent groups such as Photos, a nonexistent package, an undeclared single permission such as Camera, or a failure of every group member is therefore reported as a successful action. Track whether at least one command succeeds and return the operation error when all candidates fail.
Useful? React with 👍 / 👎.
| onKeyDown={(event) => { | ||
| if (event.target !== event.currentTarget) return; | ||
| if (event.metaKey && !["r", "R"].includes(event.key)) return; | ||
| event.preventDefault(); | ||
| clientRef.current?.sendKey(event.nativeEvent, "down"); |
There was a problem hiding this comment.
Forward the Command modifier for simulator shortcuts
On macOS, pressing Command-R first produces a Meta-key keydown, which this condition drops because its key is Meta, and then forwards only the R-key event. The browser refresh is prevented, but the iOS helper receives plain r rather than Command-R and may type into the foreground app; the subsequent Meta keyup can also be forwarded without a matching press. Forward or synthesize the modifier state for the explicitly allowed Command-R chord.
Useful? React with 👍 / 👎.
| const shimDir = yield* ensureAgentDeviceShim({ | ||
| entryPath: yield* devices.agentCli, | ||
| stateDir: config.stateDir, | ||
| }).pipe( |
There was a problem hiding this comment.
Prepare the local launcher before opening a remote device
When the target is on an SSH host, remote agentTarget setup can succeed but the subsequent local agent-device installation or shim creation can still fail, for example when local npm is unavailable. By the time this code reports that failure, devices.open has already booted the target and published its session, so device_open returns an error while leaving the user's panel open and possibly the device running. Prepare the local launcher before calling devices.open, or roll back the session and boot side effects on failure.
AGENTS.md reference: AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
| const devices = [...list.simulators, ...list.emulators].map(toSummary); | ||
| const host = yield* resolveHost(ready.hostId); | ||
| if ((yield* host.platformAvailability("android")).available) { | ||
| const avds = yield* ready.run("emulator", ["-list-avds"]); |
There was a problem hiding this comment.
Verify the remote emulator before listing AVDs
When an SSH host has a working adb but no emulator executable, the remote probe still marks Android available, so every refresh reaches this command and fails. Because the hub device list has already been fetched but is discarded when this enrichment step errors, the entire host is marked failed and even valid iOS simulators on that host disappear. Require the emulator command in the remote availability probe, or retain the hub results when AVD enumeration is unavailable.
AGENTS.md reference: AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
feat(devices): Devices panel and device server ported from #194
Pathway can now open an iOS Simulator or Android Emulator beside a conversation, with authenticated streaming, manual controls and separately enabled agent access. This adapts Julius's T3 Code work in pingdotgg/t3code#12787 and pingdotgg/t3code#12813 into Pathway's existing environment, authentication and panel model.
Behavior
Desktop and web share the panel. React Native retains the shared protocol and agent access but does not gain a native 3D panel in this change.
Evidence and validation
PlanLink walkthrough — setup, before/after model comparison and validation
Latest evidence, after the model revision: the 12.8-second recording shows open → book → closed → open folding, orbit and a close hinge view. It is a genuine live simulator recording.
pathway-device-workspace-revised.mp4
The PlanLink comparison labels the earlier boxy model as before evidence. Its recording link points only to the revised model. PR media uses GitHub attachments because FileStore was unavailable. PlanLink embeds the locally captured screenshots directly and links to the playable MP4.
node scripts/ios/build-terminal.mjs --checkpasses (terminal assets unchanged).Performance and audit
A focused source audit found no new Convex queries, mutations, schemas or scheduled work. Device state uses the existing environment RPC connection; helpers install on demand and device/agent support default off. No dedicated Convex performance skill was installed, so this used the source-audit fallback. Production Insights and unrelated Convex suites were not exercised for this environment-local change.
Full upstream commit audit · Device operations and native build pins · User guide
Codex task:
codex://threads/01a0c263-daa6-7fc1-b7cc-5960a27c2bd5(copyable URI; GitHub does not render custom-scheme links and no approved HTTPS bridge is configured).Model: GPT-6. Harness: Codex desktop. Evidence capture: GPT-5.6 Sol via Unified Computer Use.