Skip to content

[release] v0.105.0#5300

Merged
mmabrouk merged 1594 commits into
mainfrom
release/v0.105.0
Jul 14, 2026
Merged

[release] v0.105.0#5300
mmabrouk merged 1594 commits into
mainfrom
release/v0.105.0

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

New version v0.105.0 in

  • web
    • web/oss
    • web/ee
  • services
  • api
  • sdks
    • sdks/python
  • clients
    • clients/python
    • clients/typescript
  • kubernetes
    • kubernetes/helm

mmabrouk and others added 30 commits July 12, 2026 04:01
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.
- 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
jp-agenta and others added 17 commits July 14, 2026 16:57
[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
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XS This PR changes 0-9 lines, ignoring generated files. labels Jul 14, 2026
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-07-14T21:59:14.016Z

mmabrouk added 2 commits July 14, 2026 22:36
…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"
mmabrouk added 2 commits July 14, 2026 22:55
…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
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jul 14, 2026
@mmabrouk mmabrouk merged commit e723777 into main Jul 14, 2026
62 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants