feat(remote): input-side file boundary — flows, screenshot-diff, profiler reports, workspace tools#319
Draft
filip131311 wants to merge 2 commits into
Draft
feat(remote): input-side file boundary — flows, screenshot-diff, profiler reports, workspace tools#319filip131311 wants to merge 2 commits into
filip131311 wants to merge 2 commits into
Conversation
…ote mode PR #278 made tool-produced files work across the remote boundary (output side). This closes the remaining gaps: Output side (existing artifact pattern): - native-profiler-analyze: reportFile is now an ArtifactHandle; the inline report references the result field instead of embedding the host path - screenshot-diff: diffPath/contextDiffPath are now ArtifactHandles; the summary references the result fields instead of embedding host paths Input side (new, symmetric mechanism): - Tools declare fileInputs ({target, path template, kind}) on their ToolDefinition, advertised via GET /tools - The client (tools-client; used by both CLI and MCP) wraps declared args as __argentFileInput {path, size, mtimeMs, content?}; file bytes are inlined only when routed to an external tool-server - The server resolves wrappers before zod validation: in place when the path matches on its own filesystem (co-located: zero copies), otherwise materialized from the uploaded content into a temp file; 'directory' kind gates with an actionable remote-mode error; 'probe' kind only reports host presence to the tool via ctx.fileInputs Tool migrations: - flows: recording keeps server-side session state; when the client is remote (project_root probe miss) mutating tools return a __argentClientFile directive and the client writes the YAML into the agent's project (constrained to .argent/flows/*.yaml); replay/read derive the flow file as a 'file' input so the YAML is read in place locally and uploaded when remote - screenshot-diff: baselinePath/currentPath are 'file' inputs; outputDir is now optional (temp default) and ignored when absent on the host - react-profiler-component-source, gather-workspace-data: 'directory' gate — unchanged locally, clear error remotely instead of silent empty results Both halves of the version-skew matrix degrade to today's behavior: an old client sends plain strings (no wrappers), and an old server doesn't advertise fileInputs (client never wraps).
Allow direct screenshot-diff tests to pass artifact context now that diff outputs are registered as artifacts. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
PR #278 fixed the output half of the remote file boundary: files a tool produces travel to the client as
ArtifactHandles. But seven tools still break when the tool-server runs on a different machine than the agent, in one (or both) of two ways:native-profiler-analyzeandscreenshot-diffstill returned raw host paths (and embedded them in report/summary prose), so the agent can't open them remotely.screenshot-diffbaselines, the three flow tools'project_root,react-profiler-component-source,gather-workspace-data) silently read/wrote the wrong machine's disk. Flows were fully broken in remote mode: the YAML landed on the server's disk under the agent's path.What
Output side (apply the existing pattern)
reportFileis now anArtifactHandle(mirrorsreact-profiler-analyze'sfileArtifact()); the inline report now says "use the Read tool on thereportFilepath in this result" instead of embedding the server path.diffPath/contextDiffPathare nowArtifactHandles; the MCP adapter renders the context-diff image from materialized bytes (legacy string-path results from older servers still render co-located). The summary references the result fields instead of embedding host paths.Input side (new, symmetric mechanism)
A declarative file-input boundary, mirroring the artifact gate in the opposite direction:
fileInputs: [{ target, path: "${param}…", kind }]on itsToolDefinition; the declaration ships to the client viaGET /tools.@argent/tools-client, shared by CLI and MCP) interpolates each template from the args and replaces the target arg with a wrapper:{ __argentFileInput, path, size, mtimeMs, content? }. File bytes are base64-inlined only when routed to an external tool-server (argent link/ARGENT_TOOLS_URL) — unlinked local calls never pay for encoding.kind: "file"content is materialized into a temp file (with size-integrity check, 32 MB cap);kind: "directory"(trees that can't ride in a call) → 422 with actionable remote-mode guidance;kind: "probe"→ never fails; the tool learns host-presence viactx.fileInputsand adapts.__argentClientFiledirective (path + content); the client writes it and rewrites the directive to the path. Writes are constrained to**/.argent/flows/<safe-name>.yamlso a tool-server can't direct writes anywhere else on the client machine.Tool migrations
flow-start-recordingprobeonproject_root: present → host persistence exactly as before; absent (remote) → recording kept in server memory, YAML returned as a client-write directiveflow-add-step/flow-add-echo/flow-finish-recordingsavedTo(path locally, directive remotely) so the agent-side file is always currentflow-execute/flow-read-prerequisitefileinput derived from${project_root}/.argent/flows/${name}.yamlinto an internalflow_fileparam — read in place locally, uploaded when remotescreenshot-diffbaselinePath/currentPathasfileinputs;outputDirnow optional (temp default) and probe-ignored when absent on the hostreact-profiler-component-source,gather-workspace-datadirectorygate — unchanged locally; clear error remotely instead of a silent empty AST index / all-nulls snapshotVersion skew
Both halves degrade to today's behavior: an old client sends plain strings (the resolver passes them through, tools take their legacy paths), and an old server doesn't advertise
fileInputs(the new client never wraps). Wrappers on undeclared params fail the tool's own schema validation, so nothing can be smuggled through free-form args.Tests
Unit (all green: registry 68, tool-server 992, tools-client 107, argent-mcp 66, argent-cli 117):
tool-server/test/file-inputs.test.ts— in-place gate, stat-mismatch → upload, truncated-upload rejection, directory gate, probe semantics, undeclared-target smuggling.argent-tools-client/test/file-inputs.test.ts— wrapping (stat-only vs inlined content), multi-param template derivation, explicit-override respect, directive writes incl. path-validation refusals (relative, traversal, outside.argent/flows, bad charset/extension).tool-server/test/flows/flow-remote-recording.test.ts— remote recording stays in memory + returns directives, nothing written on the host, replay/read from a boundary-resolvedflow_file.Local / co-located (unlink scenario) — isolated tool-server + CLI on an iPhone 17 Pro simulator: full flow record→finish→read-prerequisite→execute loop (YAML written via the same path as before, in-place gate hit, no byte copies); screenshot-diff with and without
outputDir; wire-level checks of the unlinked client shape (wrapper without content → in-place read) and the legacy plain-string shape; missing-file → 422 with a precise message.Remote (Lima Linux VM as the agent machine, tool-server on the host Mac,
--hostbind + bearer auth) — via both the CLI and the real MCP stdio server:savedTorewritten to the VM path, no agent-path directories created on the host; replay from the VM uploads the YAML and drives the host simulator.screenshotmaterializes into the VM temp cache;screenshot-diffround-trips both directions (VM baseline uploaded, live capture on host, diff artifacts downloaded back; MCP returnsimage,textcontent with the context diff inlined).gather-workspace-data/react-profiler-component-sourcewith VM-only paths → the new actionable 422.Notes
native-profiler-analyzewas not exercised live (xctrace export is unusable on this machine's iOS 26.4 simulators — known, unrelated); its change is the same one-linefileArtifactpatternreact-profiler-analyzealready ships, covered by unit tests.react-profiler-analyze's ownproject_root(AST resolve stage) is left as-is deliberately: it degrades gracefully (report still renders) and gating it would break currently-working remote analysis. Tracked as follow-up alongside nested invocations (run-sequence/flow-executesteps bypass the HTTP boundary by design).🤖 Generated with Claude Code