[release] v0.105.0#5300
Merged
Merged
Conversation
Update the tools page's file-relay description (writer in relay-client.ts, wire protocol in relay-protocol.ts, atomic temp+rename publication, delete-on-pickup / at-most-once, per-turn stale snapshot, event-driven pickup with the polling fallback, the three new AGENTA_AGENT_TOOLS_RELAY_*WATCH* env vars and the keep-the-window-below-30s guidance), fix the stale services/agent paths in its where-this-lives table for the relay rows, refresh the runner-to-MCP-server interface page's relay lifecycle (no more 'polls every 300ms'; atomic publication; crash redelivery gone on both backends), and add the new relay modules to the interface index row. Claude-Session: https://claude.ai/code/session_01Fr4A5zjs5rsufRaWgWiUNC
Five verified findings from the high-effort review, fixed: - The orphan snapshot raced the resume flow: the first successful list's READ time is unordered against respondPermission on Daytona, so a just-approved request could be swallowed as stale, and a transiently failing first list deferred the snapshot behind legitimate requests. The snapshot is now a dedicated pre-loop sweep (3 bounded list attempts; removes stale .req.json, .res.json, and relay tmp names concurrently; never touches other files) and startToolRelay exposes it as 'ready', which the engine awaits BEFORE respondPermission and the prompt - the causal ordering is now explicit, not a comment. Stale responses are swept too: a resumed approval reuses its original toolCallId, so a crashed attempt's response file could otherwise satisfy the new wait with stale bytes. - A nullish runProcess result (or missing exitCode) classified as watch success: an insta-resolving broken daemon path stormed forever with the demotion counter pinned at zero. Success now requires exitCode === 0 and no daemon timeout; everything else counts a failure. - Delete-on-pickup landed asynchronously after the loop re-armed the next watch window, so each pickup still bought an insta-complete/rearm burst, and a silently failed remove revived the full storm with counter-resetting exit-0 wakes. Pickup (read, then stat and remove concurrently) now completes before the next window arms; failed removes are retried on later list passes; execution still starts as soon as the read returns. - A window failure while a wait was already parked left the source windowless until the 30 s safety timer and miscounted the safety-poll discovery as a watch miss. countFailure now schedules the deferred re-arm when a waiter is parked; the arm gate is one tryArm() used by all three arm sites; the inFlight flag collapsed into the generation counter. - The safety-poll independence promised by the plan was implemented inside the watch source it guards. The loop now bounds every source.wait with its own racing timer, so a wedged wait can never stop the list pass. Plus cheap wins: waitForRelayResponse fast-paths a pre-existing response before arming a watcher and re-checks once at the deadline; the pickup stat is skipped when no log sink is wired; one relayEnvFlag parser for both watch flags; the extension build now FAILS if server-side relay symbols leak into the sandbox bundle (negative-tested); the orchestration test pins the engine's relay logger wiring. Claude-Session: https://claude.ai/code/session_01Fr4A5zjs5rsufRaWgWiUNC
…ndbox stdio MCP shim Slice 1 of in-sandbox-tool-mcp (docs/design/agent-workflows/projects/in-sandbox-tool-mcp/). Today a Claude run on Daytona with any custom tool is refused up front (REMOTE_TOOLS_UNSUPPORTED_MESSAGE): execution via the file relay is solved, but nothing inside the sandbox advertises the tools to an MCP-client harness. This revives PR #4873 onto today's paths, consuming the relay client PR #5243 extracted. - tools/tool-mcp-stdio.ts: a dependency-free stdio MCP shim (9.6 kB bundled) the harness's ACP adapter spawns inside the sandbox. NDJSON JSON-RPC; tools/list serves the run's public specs from a specs FILE; tools/call writes a relay request through the shared relay client (atomic publication, per-tool timeoutMs) and maps failures to isError results. Fail-loud exit 1 on missing env/specs. Env contract in tools/tool-mcp-env.ts: AGENTA_AGENT_TOOLS_RELAY_DIR + AGENTA_AGENT_TOOLS_PUBLIC_SPECS_FILE (a file, not an unbounded env value copied through four exec layers). - engines/sandbox_agent/tool-mcp-assets.ts: bundle + specs upload, always-write (closes bundle-version skew in reused sandboxes), THROWS on missing bundle/failed upload on the path that requires the shim. Bundle override SANDBOX_AGENT_RELAY_MCP_BUNDLE. - engines/sandbox_agent/mcp.ts: dedicated internal-entry constructor building the typeless ACP stdio shape named agenta-tools (claude_settings.py couples permission rules to it); McpServerStdio moved here out of mcp-bridge.ts; user-declared servers may not claim the reserved name; toAcpMcpServers still cannot emit stdio; user-stdio refusal unrelaxed. - run-plan.ts: the remote-tools gate narrows - executable tools on Daytona pass (new toolMcpDir ephemeral sibling of the relay dir), client tools refuse loud with their own message, non-Daytona remote providers stay fail-closed. - engines/sandbox_agent/relay-guard.ts: the relay execution guard now guards EVERY harness (review finding): allow/deny identical everywhere; ask consumes the Pi grant ledger on Pi and passes on MCP harnesses whose own dialog gates the call, with the forged-ask residual documented. Previously Claude relay records executed unguarded. - The slice-0 restart spike locked the A2 transport: the pinned Claude ACP adapter (claude-agent-acp 0.22.2 in snapshot agenta-sandbox-pi) respawns the stdio MCP subprocess on the session/load path after a VM stop/restart (spike-restart.md in the workspace). Tests: 25+ new (shim handler + real startToolRelay round trip incl. crash-after-write, concurrency, stdout purity; assets fail-loud; three-layer session MCP pin; gate matrix; non-Pi guard semantics). Suite 1054 passed / 4 known pre-existing failures; typecheck clean for the slice; both bundles build with the forbidden-server-symbols gate. Claude-Session: https://claude.ai/code/session_01Fr4A5zjs5rsufRaWgWiUNC
…internal MCP name The engine half of the shim slice, split from the previous commit after a cross-lane hunk-dependency snag (a formatting pass had touched the parallel Daytona-secret lane's committed regions; reverted, insertions re-anchored): - sandbox_agent.ts: upload the shim bundle + specs file on the Daytona non-Pi executable-tools path (fail-loud helper, deps-injectable) and pass the assets into buildSessionMcpServers; the relay execution guard is now built for EVERY harness via buildRelayExecutionGuard (allow/deny identical everywhere; ask consumes the Pi grant ledger on Pi and passes on MCP harnesses whose own dialog gates the call). - mcp.ts: materialization-time reserved-name defense (assertNoReservedUserMcpName at the top of buildSessionMcpServers). - run-plan.ts: declaration-time refusal of a user MCP server named agenta-tools, before any resource is created, plus its gate test. Claude-Session: https://claude.ai/code/session_01Fr4A5zjs5rsufRaWgWiUNC
The three-layer test used the credentials wire field owned by the parallel Daytona-secret work, so this lane's tip failed typecheck standalone. The layering pin is about WHICH servers are delivered; auth delivery has its own tests. Also swap the 'no Bearer' assertion for 'no user MCP url leaks into the internal entry'. Claude-Session: https://claude.ai/code/session_01Fr4A5zjs5rsufRaWgWiUNC
…CP shim The internal gateway-tool channel now has two transports (local loopback HTTP; the in-sandbox stdio shim on Daytona): update the runner-to-MCP-server interface page (shim env contract, SANDBOX_AGENT_RELAY_MCP_BUNDLE, reserved server name, narrowed remote gates, the every-harness relay execution guard and its stated ask residual), the interface index rows, the harness-adapters and mcp-models coupling notes, the permission-responder guard note, the tools/ground-truth/claude-code narrative pages, and the operator env-var inventory in running-the-agent. Claude-Session: https://claude.ai/code/session_01Fr4A5zjs5rsufRaWgWiUNC
…ision The revision-change effect in AgentConversation called stripProvenance.clear(), which empties the composer text as well as the template chip. A commit swaps the panel's entityId (old revision -> new revision), so the effect fired and wiped whatever the user was typing. Use the existing clearProvenance() primitive (drops the chip only) for the revision swap, leaving the in-progress draft intact. clear() is still used on the legitimate post-send/commit paths where the text should go.
…rs-draft [5246] fix(frontend): keep the agent chat draft when committing a revision
The chip morph was hand-rolled: an off-flow ghost measured by a ResizeObserver drove an explicit width onto the visible chip, with CSS transitions for the width/opacity/fade. It only animated the container — switching templates snapped the initials, name, and provider logos. Rebuild it on Motion (already a dep, previously unused): - the chip's `layout` prop morphs the auto width (a no-bounce spring via the visualDuration/bounce API, so the 1.5px seam border never overshoots), deleting the ghost, ResizeObserver, useLayoutEffect, and width state; - an inner AnimatePresence keyed on the template transitions the whole content group (badge + name + logos), so the content swaps instead of snapping; - AnimatePresence in the dock owns the fade/rise on select/clear; - MotionConfig reducedMotion="user" collapses the transform/layout animations to plain opacity for users who ask for reduced motion.
The playground shows the same sessions two ways — compact tags in the build-mode bar, list rows in the chat-mode rail — and neither entered or left with any motion (the rail hand-rolled a grid-collapse + rAF + setTimeout delete-defer; the bar just snapped). Add a coordinated Motion group across both. - shared assets/sessionMotion.ts holds one SESSION_SPRING plus the tag/row variants, so a session's two representations enter and leave with identical physics — the "connected" feel between the bar and the rail; - each item collapses its OWN size in step with its fade (rows their height, tags their width) so a neighbour closes the gap as one continuous motion rather than a pop-then-reflow two-step, and the inter-item gap is an animated margin that collapses to nothing with the item so nothing snaps on unmount; - deleting a rail row is now immediate — AnimatePresence plays the exit — replacing the leavingIds/deleteTimersRef/220ms-timer machinery; - MotionConfig reducedMotion="user" honours the OS setting on both.
…roved state management
- SessionRail: keep AnimatePresence always mounted (render the empty/no-match states as siblings) so the last row still plays its exit when the session list is emptied by a delete or filtered to nothing. - Trim the new animation comments to the repo's one-line rule and correct the width-morph attribution (TemplateChip owns it, not TemplateChipDock).
…tions-initial-batch feat(frontend): add Motion transitions for template chip + sessions
Resolve conflicts in SessionRail and SessionTagBar: keep the incoming Motion enter/exit animations from big-agents and re-apply the pencil-rename actions (rename handle, hover overlay, hidden actions while editing) on top. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n-and-agent [feat] Rename agents and chat sessions from the playground and tables
Addresses the mechanical review findings on #5243, #5244, and #5245 that carry no design decision and need no live verification. Landed as one commit on top of the stack because GitButler cannot retro-assign a new delta onto a lower applied lane; each fix is mapped to its PR below. Relay (#5243): - relay.ts: bound every relay-file removal with a timeout so a hung daemon deletion can no longer wedge pickup, stale-sweep, or shutdown; a timed-out removal is treated as failed and the loop continues. - relay-watch.ts: tie a safety-poll miss to the active watch generation so a later zero-exit of the same generation cannot erase it (the demotion counter now accumulates); validate the whole watch-window string instead of parseInt accepting partial garbage. Shim (#5244): - tool-mcp-stdio.ts: answer MCP 'ping' with an empty result; return -32700 on unparseable input and -32600 on an invalid request; document the at-least-once relay delivery contract. Protocol-version negotiation is left unchanged (that needs a live check). - tool-mcp-assets.ts: cache the shim bundle by path and write bundle + specs concurrently, removing avoidable serial Daytona round trips. - restored the direct client-only Daytona refusal test; pinned the Pi vs non-Pi ask guard wiring; updated the build:extension docs. Continuation (#5245): - tool-mcp-http.ts: authenticate before the method check so an unauthenticated GET/DELETE gets 401, not 405. - added auth tests: unauthorized GET/DELETE, wrong scheme, empty and whitespace tokens, and that neither the callback executor nor the client relay runs on an unauthorized call. Deferred (need a design decision or a live check, not in this commit): the non-Pi ask authorization model (#5244), the bearer-in-argv delivery (#5245), MCP protocol-version negotiation (#5244). Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
Harden the internal gateway-tool MCP endpoint that delivers Agenta tools to local Claude over loopback HTTP. Until now it relied only on 127.0.0.1 reachability, so any other local process could list or call tools through it. - Mint a per-server bearer token (randomBytes 32, base64url) in startInternalToolMcpServer; advertise it as the entry's Authorization header in buildToolMcpServers. - Authenticate before reading or parsing the request body; reject missing/wrong tokens with 401 and no dispatch. Timing-safe comparison, length-guarded. - Reject a JSON-RPC batch containing a client tool before executing any item. - The bearer lives ONLY in the HTTP transport path; the in-sandbox stdio shim (#5234) stays credential-free by construction (different file, different transport). It is a local loopback guard, never a provider/control-plane credential — the private callRef still never reaches the advertisement. Adds real-socket HTTP integration tests (auth, malformed/oversized body, batch client-tool rejection, close) and updates the two layering pins that asserted headers: [] to expect the loopback guard header. WP0: checked-in measurement protocol + unlock-gate scorecard for the deferred warm hold-open path (results marked PENDING LIVE RUN). No runtime behavior change from WP0. Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
Design-only. Moves the harness model list out of the hardcoded CLAUDE_MODEL_ALIASES into curated data maintained by a sync-model-catalog skill, and publishes a richer /inspect schema with clean labels, a description, real pricing, and curated ratings. Key finding (code-traced): the advertised set and the harness's ACCEPTED set drift both ways. The live harness config options at session init are the only authoritative gate. 'default[1m]' and 'haiku[1m]' are over-advertised and rejected; Fable is accepted but not advertised. So the schema carries an 'advertised' flag and validates the published set against the live accepted set. Schema separates sourced facts (id, provider, name, pricing per MTok, context window) from curated judgments (label, description, ratings 1-5). Real cost (pricing) and cost rating are distinct typed fields. Additive, backward-compatible migration into the already-empty FE metadata seam; the prompt-playground picker is untouched. Workspace: README, research, design, plan, open-questions, pr-body. Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
…l facts from current sources Owner review: simplify the logic (keep the interface). Removes the advertised flag and the advertised-vs-accepted dual state — the catalog is simply the models the harness accepts, verified once against the live set. Corrects the model facts from current public sources (Fable 5 is the top tier above Opus; Opus 4.8 pricing 5/25 per MTok). Enforces the 1-5 ratings range, drops the invalid inline comment from the generated JSON, tags the code fences. Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
Publishes a curated model catalog via /inspect alongside the existing models
map (additive, backward-compatible). Separates sourced facts (id, provider,
name, pricing, context window) from curated judgment (label, description,
ratings 1-5). No advertised flag: the catalog is the models the harness accepts.
- sdk: model_catalog.py (schema + loaders), data/{pi_models.generated,
pi_models.curated, claude_models.curated}.json, capabilities.py publishes
model_catalog defensively (a bad data file yields [], never crashes /inspect).
- web: connectionUtils prefers the catalog and fills the model-option metadata
seam with pricing + description + ratings; the picker tooltip shows the
description. Falls back to the models map when the catalog is absent.
- tests: 25 python (schema + capabilities), 36 web (catalog path + fallback).
The sync-model-catalog skill stays local (gitignored per the local-only-skills
convention). Live-harness reconcile (incl. a Claude 'fable' alias) is deferred
to that skill's probe.
Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
The skill that maintains the catalog data (regenerate Pi entries from the pi-ai catalog, reconcile against the live harness accepted set, refresh curated facts from current sources). Force-tracked despite the .agents/skills gitignore because it is part of this feature. Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
[fix] Land the v7 audit findings that need no product decision
The published OSS/EE gh compose shipped no store, so runner mount signing returned 503 and agent file writes were silently lost. Bundles SeaweedFS (matching the dev stack) and sets working store env defaults. Claude-Session: https://claude.ai/code/session_01XhENr63WL9npkKrJGnzDc1
Move services/runner/images/service back to services/runner/docker so the runner matches the api/web/services convention (<svc>/docker/Dockerfile.gh) that platform CI builds against; the images/service rename made platform build.sh silently skip the runner and OSS preview deploys failed with no oss/agenta-runner image in ECR. Compose, Railway, and docs references updated; the Railway build script keeps images/service as a fallback for branches still carrying the renamed layout. Sandbox images stay under images/sandbox/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(hosting): bundle SeaweedFS store in gh compose
fix(hosting): bind Postgres and the dashboard to loopback by default
…ompose variants PR #5313 added the bundled store and flipped the gh env default to http://seaweedfs:8333, but only added the service to docker-compose.gh.yml. The gh.local (build-from-source) and gh.ssl variants reuse the same gh env file, so they inherited the endpoint default with no such service on the network. Add the identical seaweedfs service, api depends_on, and seaweed-data volume to oss gh.local, oss gh.ssl, and ee gh.local, plus a multi-replica JWT note in the gh env examples. Claude-Session: https://claude.ai/code/session_01XhENr63WL9npkKrJGnzDc1
fix(hosting): bundle SeaweedFS store in gh.local and gh.ssl variants
…s and store
${POSTGRES_PORT:-127.0.0.1:5432} put the loopback inside the default, so
any port override (POSTGRES_PORT=5433) silently reverted to a public
bind. Use the traefik-dashboard shape (127.0.0.1:${VAR:-port}) so the
variable carries only the port and loopback survives overrides; same fix
for the new AGENTA_STORE_PORT, whose own comment promises loopback-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r path The local-run env table lumped AGENTA_RUNNER_TOKEN in with AGENTA_RUNNER_DAYTONA_API_KEY under one "when they are set" qualifier, but the runner token is mandatory (the runner refuses to start without it), so only the Daytona key is conditional. Reword accordingly. Also fix the licensing pointer in build_snapshot.py: the referenced services/runner/images/service/README.md no longer exists; the licensing section lives at services/runner/docker/README.md.
…osted deployments On a gh self-host deployment using Daytona cloud sandboxes, geesefs runs inside the sandbox and reaches the store over the internet. The bundled SeaweedFS is bound to loopback with traefik.enable=false, so the signed store endpoint is unreachable from Daytona and the runner skips the mount silently, dropping every file the agent writes. Add an opt-in traefik router on the seaweedfs service (off by default) that publishes the S3 endpoint on its own subdomain, gated by AGENTA_STORE_TRAEFIK_ENABLE and AGENTA_STORE_DOMAIN. The API's existing AGENTA_STORE_ENDPOINT_URL then carries the public endpoint into the signed mount credentials. Host-only routing keeps the request path intact, which SeaweedFS S3 SigV4 requires. Document the recipe on the Daytona self-host page and in the gh env examples. Claude-Session: https://claude.ai/code/session_01Hyn9365BLPXDmNZShrQkmH
docs(self-host): correct runner-token wording and a stale image-folder path
…d no output" Commit 42075a5 moved Pi session transcripts to <cwd>/agents/sessions/pi via PI_CODING_AGENT_SESSION_DIR, but findSwallowedPiError was still passed the Pi agent dir, which no longer holds the transcript. The helper found nothing, so every provider failure (bad key, quota, model 404) fell back to the generic "The agent produced no output." message. findSwallowedPiError now takes the run cwd and derives the transcript location from piSessionWorkspaceDir, the same shared helper configurePiSessionWorkspace uses to point Pi at it, so the reader and the writer can never disagree again. Claude-Session: https://claude.ai/code/session_01Hyn9365BLPXDmNZShrQkmH
…posure fix(hosting): expose the object store for Daytona sandboxes on self-hosted deployments
The published runner image runs as user node (uid 1000) but never created /pi-agent (the configured PI_CODING_AGENT_DIR), so installing Pi's permission-enforcing extension failed with EACCES. The install swallowed that error and returned void, so the runner continued with NO enforcement: a deny/ask policy silently ran every built-in tool. Three parts: - Image: create /pi-agent owned by node in Dockerfile.gh before USER node. - Routing: managed/no-key local Pi runs install the extension into a per-run temp dir the runtime user owns, so the install no longer depends on the configured agent dir being writable. - Fail closed: installPiExtensionLocal / uploadPiExtensionToSandbox now report success; session setup throws a named error when the policy could gate a Pi built-in and the extension did not install, surfaced as an error frame. Claude-Session: https://claude.ai/code/session_01Hyn9365BLPXDmNZShrQkmH
fix(runner): surface Pi provider errors instead of "The agent produced no output"
fix(runner): fail closed when the Pi permission extension cannot install
[integration] big-agents
Contributor
Author
Railway Preview Environment
Updated at 2026-07-14T21:59:14.016Z |
…ed no output" A swallowed-provider-error recovery (e.g. an out-of-credit key) both live-streams the real error AND fails the run's terminal result for the same failure. The zero-content-parts backstop never counted an error frame as content, so it piled a third, generic "The agent produced no output." frame on top of the real one(s) -- burying the actionable message. Track whether a real error already went out this turn and skip both the duplicate exception-derived frame and the generic backstop when it did.
fix(runner): emit a single real error frame instead of appending "produced no output"
…gure race The release preview deploy failed with "Service 'cron' not found" seconds after bootstrap created the service: configure.sh resolves service ids from a single 'railway status --json' snapshot, and a service created moments earlier by the setup job may not be visible in it yet. The CLI fallback then hit the same eventual-consistency window and hard-failed the deploy. Re-fetch the status snapshot and retry id resolution a few times (bounded, short sleeps) before falling back to the CLI, and let the CLI fallback retry the specific 'not found' failure a couple of times instead of exiting on the first hit. First-try hits cost zero extra API calls, so the happy path is unchanged. Claude-Session: https://claude.ai/code/session_01Hyn9365BLPXDmNZShrQkmH
ci(railway): retry service resolution to survive the bootstrap→configure race
mmabrouk
approved these changes
Jul 14, 2026
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.
New version v0.105.0 in