558 Commits
Author SHA1 Message Date
Hiep Le 89dc8bd446 feat: land the Canvas Extensions frontend (load pages, sidebar, customize) (#16895) 2026-08-27 17:53:40 +00:00
1e995e89ab docs: document OpenHands repository boundaries (#16834)
Co-authored-by: neubig <neubig@users.noreply.github.com>
Co-authored-by: openhands <openhands@all-hands.dev>
2026-08-23 15:08:06 +00:00
bad1687dec fix: render the VSCode button on self-hosted (local) backends (#16106)
Co-authored-by: Harish Chandramowli <harish.jhu@gmail.com>
Co-authored-by: Engel Nyst <engel.nyst@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 17:38:43 +02:00
MarMar Labs b25f9b3969 docs: add DefenseClaw and testing matrix to the docs index (#16408) 2026-08-18 12:50:42 +00:00
karan singhandVasco Schiavo 342175e479 docs: fix broken internal links (#16627)
Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
2026-08-18 09:41:58 +00:00
e0b115757b fix: route runtime services through server_info (#16090)
Co-authored-by: neubig <398875+neubig@users.noreply.github.com>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: neubig <neubig@users.noreply.github.com>
2026-08-02 22:39:43 -04:00
hi-neasonandopenhands 1708efc446 docs: fix runtime modes table in architecture doc (#16163)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-31 20:47:38 -04:00
7fbc01824a test: add Stryker mutation testing (#16184)
Co-authored-by: Ai Vong <ai.vong@openhands.dev>
Co-authored-by: Engel Nyst <engel.nyst@gmail.com>
2026-07-30 08:39:49 +02:00
Hiep Le e06c8ed86b chore: bump typescript-client to 1.33.0 (#1824) 2026-07-16 20:38:33 +07:00
519c856c37 feat: support serving Canvas under a subpath (#1796)
* feat: support serving canvas under subpath

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: redirect root app routes to canvas base path

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
2026-07-15 12:24:11 -04:00
2b7ceea667 refactor: define canvas UI as an SDK client tool (#1797)
* refactor: define canvas UI as an SDK client tool

Send a JSON-defined canvas_ui_client tool on new, profile-based, and resumed conversation requests while retaining the legacy Python registration for persisted conversations. Normalize the new SDK event kinds to the existing Canvas UI rendering.

Co-authored-by: smolpaws <engel@enyst.org>

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: omit canvas client tool from ACP launches

* refactor: rename canvas client tool

Use the semantic canvas_ui_control name and contain the SDK-generated action discriminator behind exported constants.

Co-authored-by: Engel Nyst <engel.nyst@gmail.com>

---------

Co-authored-by: Engel Nyst <engel.nyst@gmail.com>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Debug Agent <157206163+simonrosenberg@users.noreply.github.com>
2026-07-15 09:40:57 +00:00
Graham Neubigandneubig bbf9ce9667 [codex] Bump minimum compatible agent server (#1342)
* Bump minimum compatible agent server

* Apply version compatibility to backend health

* Explain backend health failures in manage modal

---------

Co-authored-by: neubig <398875+neubig@users.noreply.github.com>
2026-06-12 20:59:37 +00:00
Rohit Malhotraandopenhands c7c8862c11 Remove visual snapshot tests and bump Docker E2E timeout (#1332)
* Remove visual snapshot tests

Co-authored-by: openhands <openhands@all-hands.dev>

* Bump Docker E2E timeout

Co-authored-by: openhands <openhands@all-hands.dev>

* Align Docker E2E timeout caps

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-12 13:03:58 -04:00
Hiep Le fbcb8084d7 chore: drop dead constants, inert VITE_WORKER_URLS plumbing, and needless exports (#1238) 2026-06-10 14:33:17 +00:00
ec4616c1c7 feat(acp): containerized + cloud ACP — onboarding, secrets, and recycled-sandbox resume (#1013/#1014/#988) (#1102)
* feat(acp): containerized ACP — credential onboarding + inline secrets (#1013/#1014)

Wire the canvas halves of agent-canvas#1014 (Docker) and #1013 (credential
onboarding) so a user can run an ACP agent (Codex / Claude Code / Gemini)
against a containerized agent-server through Canvas, with credentials supplied
in the UI.

Credential onboarding UX (#1013):
- Extend the ACP secrets step beyond the API key to the per-provider reserved
  credentials a fresh container needs: Codex CODEX_AUTH_JSON, Claude
  CLAUDE_CODE_OAUTH_TOKEN, Gemini GOOGLE_APPLICATION_CREDENTIALS_JSON +
  GOOGLE_CLOUD_PROJECT/LOCATION + GOOGLE_GENAI_USE_VERTEXAI. File-content blobs
  render as multiline fields.
- Make the step capability-driven: required on a backend with no host login
  (cloud, or a logged-out local/Docker backend per the auth probe), optional
  when a login is detected or the probe can't classify (native dev).
- Fix the orphaned-secret bug: warn instead of toasting "Saved" when the active
  backend can't consume the credential (cloud can't yet read file secrets).

Send secrets + model (start request):
- buildStartConversationRequest emits reserved ACP credentials inline as
  StaticSecrets (overriding any same-named LookupSecret) and mirrors them onto
  agent_context.secrets, so the SDK's acp_file_secrets defaults materialise the
  *_JSON blobs before the CLI spawns. The orchestrator reads back the saved
  reserved values for the active provider (local backends only).
- Preselect a Vertex-safe acp_model for Gemini (gemini-2.5-flash) so a fresh
  container doesn't hit gemini-cli's preview default that 404s on Vertex.
- Never auto-promote *_BASE_URL to an inline secret (an inherited base URL
  breaks the Claude OAuth token's bearer auth).

Docker setup + docs:
- examples/acp-docker/ docker-compose (persistent volume + canvas_ui tool mount
  + credential notes); .env.sample + docs point VITE_BACKEND_BASE_URL at it.
- docs/ACP_AGENTS.md gains a "Running ACP agents in a Docker container" section.

Per-conversation isolation (acp_isolate_data_dir) left as a documented TODO —
the field isn't exposed on ACPAgentSettings in the released typescript-client.

Tests + e2e:
- Unit tests for the StaticSecret emission, reserved-credential sets, Vertex
  model default, getSecretValues read-back, and the required-credentials matrix.
- tests/e2e/live-acp/: a vite-node harness that builds each provider's request
  via buildStartConversationRequest and POSTs it to a real container. Validated
  with REAL API calls against agent-server c950fdb-python: Codex ✅, Claude ✅,
  Gemini ✅ (materialise ADC -> vertex-ai -> real reply). Gemini's default-config
  init is blocked by an SDK/gemini-cli set_session_mode("yolo") issue (documented
  caveat, not a credential problem).

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

* fix(acp): make containerized credentials survive the real conversation-start path

Validating end-to-end through the application's own orchestrator
(buildStartConversationRequestWithEncryptedSettings) against a live container —
rather than the request builder in isolation — surfaced two real bugs that would
have broken the feature in the product:

1. secrets_encrypted mangled the plaintext reserved StaticSecrets. The app always
   fetches settings in encrypted mode, so the start request carried
   secrets_encrypted=true. The agent-server then runs every secret value through
   cipher.decrypt() during validation — including our reserved ACP creds, which
   are read back as PLAINTEXT. Result: the credential was silently dropped
   (decrypt fails → None) on a cipher backend, or a hard 500 ("cipher not
   configured") on a fresh container with no OH_SECRET_KEY. Fix: don't set
   secrets_encrypted for ACP conversations — an ACP agent has no encrypted agent
   secret (no LLM api_key), and its provider creds ride as plaintext StaticSecrets.

2. A different provider's leftover file-content secret broke the active provider.
   A CODEX_AUTH_JSON saved while onboarding Codex leaks into a later Claude
   conversation via the global-secrets → LookupSecret path. The SDK materialises
   file secrets eagerly at spawn by resolving the secret source, and a LookupSecret
   resolution stalls → ReadTimeout → "Failed to start ACP server: timed out". Fix:
   reserved file-content blobs (the multiline *_JSON creds) never travel as
   LookupSecrets — the active provider's is sent inline as a StaticSecret, any
   other provider's is dropped (getAllReservedAcpFileSecretNames).

Re-validated through the app orchestrator against agent-server c950fdb-python
(onboarding createSecret → buildAcpAgentSettingsDiff PATCH → orchestrator
read-back → real reply): Codex ✅, Claude ✅ (leftover CODEX_AUTH_JSON correctly
dropped). Gemini's app path is correct (StaticSecrets emitted, vertex-ai auth
reached); this run hit the documented invalid_rapt stale-ADC caveat (host ADC
expired since the prior fresh-ADC pass) — an environment issue, not code.

Adds regression tests (secrets_encrypted suppressed for ACP / kept for non-ACP;
leftover file blob dropped not LookupSecret'd; getAllReservedAcpFileSecretNames)
and the app-path e2e harness (tests/e2e/live-acp/acp-docker-app-e2e.mts). Notes
OH_SECRET_KEY as optional (secret persistence) in the compose example.

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

* chore: address PR review feedback (#1102)

- retag acp_isolate_data_dir TODO #1014 (this PR) -> #1019 (the
  per-conversation isolation follow-up the knob serves)
- note the Gemini Vertex scalars (PROJECT/LOCATION/USE_VERTEXAI) are
  plain config / a routing flag, not secrets

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

* fix(acp): order subscription credential before API key in onboarding

Show each provider's reserved subscription/Vertex credential first
(Claude CLAUDE_CODE_OAUTH_TOKEN, Codex CODEX_AUTH_JSON, Gemini Vertex SA),
then the API key, then the base URL — the subscription token is the
primary auth path for ACP providers, with the API key as the fallback.
Display order only; getAcpProviderSecrets consumers are order-independent.

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

* fix(i18n): disable i18next value escaping so React handles it

i18next's default escapeValue double-escapes interpolated values on top
of React's own escaping, rendering paths like ~/.codex/auth.json as
~&#x2F;.codex&#x2F;auth.json. Set interpolation.escapeValue=false (the
standard react-i18next config); React still escapes at render time.

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

* refactor(acp): unify secret wire-delivery; keep "reserved" as onboarding-only

Drop the reserved-vs-custom split in how secrets reach the agent-server.
Previously, provider credentials ("reserved") rode inline as StaticSecrets
while user secrets rode as loopback LookupSecrets — a fork introduced only
to dodge a deadlock: the SDK resolved an ACP agent's secrets synchronously
on its event loop at CLI spawn, so a loopback LookupSecret self-deadlocked.

That deadlock is fixed at the source in software-agent-sdk#3510 (ACP
cold-start runs off the event loop), so the workaround is no longer needed.
Now every secret — env-var credential, file-content blob, or user secret —
ships uniformly as a LookupSecret, for ACP and non-ACP alike. The SDK
resolves and (for file blobs) materialises them off the loop, so the
loopback fetch is safe.

"Reserved" survives only as an onboarding/validation concept (which fields
to prompt for per provider, capability-driven required steps) — it no
longer affects the wire.

Removed: StaticSecret type, acpStaticSecrets option + the inline path, the
file-blob lookupSkip, SecretsService.getSecretValues, and the reserved-name
value read-back. Kept: secrets_encrypted suppression for ACP (an ACP
request carries no encrypted payload, and a fresh ACP container may have no
OH_SECRET_KEY cipher).

Note: getReservedAcpSecretNames / getAllReservedAcpFileSecretNames in
constants/acp-providers.ts are now unused by the wire; the former is still
useful for validation, the latter can be pruned.

Depends on software-agent-sdk#3510.

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

* refactor(acp): prune now-dead reserved-secret wire helpers

Follow-up to the wire-delivery unification: getReservedAcpSecretNames and
getAllReservedAcpFileSecretNames were only ever consumed by the inline
StaticSecret / file-blob-skip path, which is gone. They have no remaining
production callers, so remove them (and their tests). The reserved-credential
field definitions (ACP_RESERVED_CREDENTIALS, getAcpProviderSecrets) and the
``reserved`` / ``multiline`` flags stay — onboarding still reads them.

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

* fix(acp): re-point containerized ACP at SDK 1.25.0 (#3510) + fix e2e harnesses

The unified LookupSecret delivery (e076e9bb) depends on software-agent-sdk#3510
(ACP cold-start off the event loop), which first ships in v1.25.0. The example
compose/docs/e2e all still defaulted to agent-server:c950fdb-python, which
predates #3510 and deadlocks the first ACP turn ("Failed to start ACP server:
timed out"). Bump every default to 1.25.0-python and document it as the minimum.

Also realign the live-acp e2e harnesses, which still encoded the removed
StaticSecret API (the PR's headline evidence predated the unification):
- acp-docker-e2e.mts: store each credential via SecretsService.createSecret,
  send name-only customSecrets, assert every emitted secret is a LookupSecret.
- acp-docker-app-e2e.mts: flip the assertion StaticSecret -> LookupSecret; drop
  the stale getSecretValues reference.
- Both: fix a polling bug where "idle" (the transient pre-run state) was treated
  as terminal, so the loop bailed before the agent ran and read an empty reply.
  Terminal is now {finished, error, stuck, stopped}.

Correct the stale StaticSecret doc comments in constants/acp-providers.ts
(reserved is now an onboarding/validation marker, not a wire distinction).

Re-validated in-container against agent-server:1.25.0-python: Codex and Claude
pass end-to-end on both harnesses (LookupSecret resolves off-loop, no deadlock,
even with leftover cross-provider file-secrets present). Gemini's credential
path is proven (vertex-ai auth reached) but the turn is blocked by gemini-cli
0.45.x ignoring the requested acp_model and running gemini-3-flash — an SDK
model-selection concern tracked in software-agent-sdk#3532, not a Canvas bug;
the docs/e2e notes are corrected accordingly.

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

* fix(acp): improve credential hint text with fetch commands

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(acp): show provider credentials in Settings → Agent

Adds a Credentials section to /settings/agent when an ACP provider is
selected, so users can set or rotate tokens/keys after onboarding without
hunting through Settings → Secrets. Mirrors the onboarding fields exactly
(same hints, same already-saved placeholders, Optional tag on multiline
fields) with its own Save button that writes directly to the secret store.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(acp): drop the agent_context.secrets mirror — request.secrets is the sole channel

The mirror's justification ("ACPAgent's spawn-time env loop reads from
agent_context.secrets, not the registry") predates the pinned minimum
agent-server: 1.25.0 already injects the ACP spawn env from
secret_registry, seeded from request.secrets (sdk#3299/#3464), and
sdk#3528 removes the agent_context drain entirely. Keeping the mirror
preserved a second, dead credential channel — the exact coupling
agent-canvas#1039 is eliminating.

Canvas now sends every credential in top-level request.secrets only.
Tests inverted to pin the single-channel contract; adapter/type
comments updated to match.

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

* refactor(acp): non-flash Gemini default, shared credential form, review cleanups

- ACP_VERTEX_SAFE_MODEL → gemini-2.5-pro: gemini-cli 0.45.x re-resolves any
  *-flash id at generation time to its current default flash (sdk#3532), so a
  flash pin is never honored; docs + e2e defaults updated to match
- extract AcpSecretField + useSaveAcpSecrets and move AcpCredentialsSection
  to components/ — onboarding and Settings → Agent share one field renderer
  and one save flow (incl. the orphaned-file-credential warning on cloud)
- a required credentials step is only satisfied by an actual credential (a
  masked `secret` field) — a base URL or GCP scalar alone no longer unblocks
- warn inline when CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_BASE_URL are both
  set (typed or saved) — the pair silently breaks bearer auth
- drop the near-dead `reserved` field flag; collapse the leftover two-block
  secrets scaffolding in buildStartConversationRequest
- sync 14 stale locales on the OAuth/file-blob hints; fix issue refs
  (TODO #1019→#1014 — #1019 is closed; OpenHands#1016→agent-canvas#1016)
- tests: settings credentials-section coverage, non-flash pin, conflict
  matrix, tightened-gate cases

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

* refactor(acp): unify default-model surfaces + dedupe credential forms and e2e harness

Review-pass cleanups:

- Route ALL three default-model surfaces (onboarding diff builder,
  Settings -> Agent seeding, start-request null fallback, + chat-input
  display) through getAcpPreferredDefaultModel, so the Vertex-safe
  Gemini override can't diverge between surfaces. New regression tests
  pin the diff-builder and start-request fallbacks to it.
- Extract useAcpCredentialForm + AcpConflictWarnings: the onboarding
  step and the Settings credentials section now share the values state,
  existing-secret lookups, conflict pairs, and save flow.
- Extract tests/e2e/live-acp/harness.mts: provider plans, host
  credential collectors, and HTTP/poll helpers shared by both live
  scripts (a model default can no longer drift between them).
- Restore the TODO(#1019) retag (accidentally reverted to the
  self-referencing #1014 in the last cleanup commit); same fix in
  docs/ACP_AGENTS.md.
- Drop the tautological ACP_VERTEX_SAFE_MODEL literal assertion, fix a
  dead key-ternary in getAcpProviderSecrets, TODO(#1016) on the
  cloud file-credential capability check, and document that baked .env
  creds don't satisfy the onboarding login probe.

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

* chore: address PR review feedback (#1102)

- Restore package-lock.json to main — the npm-install churn (29 dropped
  "dev": true flags) was never meant to ship with this PR
- Note why global escapeValue:false is safe (React escapes at render;
  no translated string hits dangerouslySetInnerHTML)
- Note the non-macOS skip path in the e2e claudeOAuthToken collector

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

* fix(acp): tighten the credential gate + clarify base-URL docs (#1102 review)

- A file blob no longer satisfies the required credential step on a
  backend that can't materialise it (cloud, #1016) — the save flow
  already warned it was orphaned, so it can't be what opens the gate.
  consumesFileCredentials moves into useAcpCredentialForm so the gate
  and the save warning share one capability check.
- Next stays disabled while the login probe is still classifying a
  local backend, so a fast click can't slip past a gate about to come
  up "unauthenticated". A probe that completes as "unknown" stays
  permissive.
- Docs: a saved *_BASE_URL secret does ride along on every start
  request like any other saved secret; Canvas only never derives one
  from LLM settings. Reword the two claims that suggested otherwise.

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

* docs(e2e): record 2026-06-07 re-validation — all three providers pass

Fresh 1.25.0-python container + fresh volume at the branch tip: Codex and
Claude pass both scripts; Gemini's full turn now passes too (fresh ADC +
gemini-2.5-pro + session-mode override), upgrading the previous
"blocked on model selection" row.

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

* feat(acp): resume a recycled cloud ACP conversation via bootstrap prompt (#988)

A cloud ACP conversation whose sandbox was recycled (STOPPED/MISSING, e.g. the
runtime idle-stopped or hit its TTL) was a read-only dead end: the chat input
was replaced by the archived banner, and cloud createConversation never
re-provisions an existing conversation_id. The backend already supports
resuming such a conversation — re-issuing the start with the same
conversation_id rebuilds it and, for ACP, replays the durable event store as a
bootstrap prompt (OpenHands#14640) — but nothing in canvas triggered it.

Surface it:
- AppConversationStartRequest.conversation_id so the cloud start path can target
  an existing conversation.
- wakeRecycledCloudConversation(id, repoSelection): re-POST /api/v1/app-conversations
  with the conversation_id (and repo selection, so the rebuilt working dir
  matches the original cwd an ACP resume keys off).
- useWakeConversation mutation: wakes + invalidates the conversation queries so
  the active-conversation poll reconnects once the fresh sandbox is RUNNING.
- A Resume button in the archived banner for an ACP conversation whose sandbox
  is MISSING (ERROR stays read-only).

Validated e2e against a local SaaS-equivalent stack (OpenHands main app_server +
a main-built agent-server image, Docker sandboxes): create an ACP conversation,
docker rm -f the sandbox, wake → fresh sandbox + bootstrap-prompt resume, the
agent recalls prior context (codeword) and the <<RESUMED CONVERSATION>> marker
is present.

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

* feat(acp): consume file-content credentials on cloud too (#988)

Cloud now materialises reserved file-content credentials (Codex auth.json,
Gemini Vertex SA) from the per-user encrypted secret store via
agent_context.secrets at conversation start (the cloud backend pins an SDK that
materialises reserved file secrets), so a pasted blob is consumable on every
supported backend — not just local. Drop the local-only gate on
consumesFileCredentials: a Codex/Gemini file blob now satisfies the onboarding
credential gate on cloud and saving it toasts success instead of the
orphaned-credential warning.

Folds the remaining cloud-enablement piece in from the native-resume canvas
branch (the wake/bootstrap-resume path landed separately); native session/load
is a backend-only concern (SDK + OpenHands), so canvas needs nothing further.

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

---------

Co-authored-by: Debug Agent <debug@example.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 12:14:14 +02:00
Ash Clarke 78f96f49ed Add files via upload (#1187) 2026-06-05 18:21:01 +00:00
9e8b068850 fix(dev): keep tmux sockets under ~/.openhands so macOS won't reap them (#1107)
dev-safe.mjs put the tmux socket dir at os.tmpdir(). On macOS that is the
per-user $TMPDIR (/var/folders/.../T), which the OS periodically reaps
(com.apple.bsd.dirhelper deletes entries untouched for a few days). The reaper
deletes the live tmux socket while the tmux server keeps running, orphaning it,
so every later new-window fails with:

  LibTmuxException: new-window: error connecting to
  .../openhands-agent-canvas-tmux/tmux-501/openhands (No such file or directory)

#325 moved the socket dir to os.tmpdir() to dodge a Unix-domain-socket failure
on mounted volumes, but its intent (per the commit message) was literally /tmp
-- os.tmpdir() != /tmp on macOS, and the per-user temp dir is exactly what gets
reaped.

Default to <stateDir>/tmux (~/.openhands/agent-canvas/tmux) instead, matching
where the rest of dev state already lives: persistent, on local disk, and never
reaped mid-session. dev-safe.mjs only ever runs on the host (the container
launches openhands-agent-server directly, never this script), so the socket dir
is always under a normal local home there.

For the rare host whose $HOME is on a socket-incompatible mount (devcontainers,
NFS/CIFS homes) -- the case #325 cared about -- honor the standard TMUX_TMPDIR
env var (which we already pass through to the agent-server's tmux) so it can be
pointed at a local path like /tmp. No new project-specific env var needed.

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:07:13 +00:00
8bb2b8c518 Add frontend-only and backend-only agent-canvas modes (#1040)
* Add partial stack modes to agent-canvas

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: address PR review feedback (#1040)

- Replace padEnd(75) with ANSI-aware ansiPadEnd helper in printBanner;
  String.padEnd counts invisible escape bytes as visible chars, causing
  the box border to misalign in colour terminals
- Deduplicate storage directory creation in ensureDirectories; was being
  pushed once per launchAgentServer block and once per launchAutomation
  block (always both true together) — move to a shared unconditional slot
- Add explanatory comment to the checkNpm two-clause OR condition

Co-authored-by: openhands <openhands@all-hands.dev>

* test: add e2e tests for --frontend-only, --backend-only, and port conflicts

Add mock-llm-partial-stack.spec.ts with three test groups:

1. --frontend-only: verifies static frontend is served (200 on /),
   backend routes return 503 (/server_info, /api/settings,
   /api/automation/v1), and the browser shows the manage-backends modal.

2. --backend-only: verifies /server_info returns 200, /api/settings
   is reachable, automation endpoint works, and root/asset requests
   return 503 (no frontend configured).

3. Port conflict: verifies the process exits non-zero with a clear
   error when the ingress port is occupied, then starts successfully
   on a free port.

Unlike the other mock-llm specs, these tests spawn their own
bin/agent-canvas.mjs child processes with isolated state dirs and
high port numbers (18310+ range) to avoid collisions.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: adjust frontend-only test to expect SPA fallback instead of 503

In frontend-only mode the ingress has no backend routes — all requests
(including /server_info, /api/*) fall through to the static server's
default backend, which returns index.html via SPA fallback (200 with
HTML). The test now verifies that /server_info returns HTML (not JSON)
and that the browser detects the missing backend and shows the
manage-backends modal.

Co-authored-by: openhands <openhands@all-hands.dev>

* feat: add --reject-prefix to static server for clean 503 on missing backends

In --frontend-only mode the static server was SPA-fallbacking API paths
(/server_info, /api/*, /sockets, etc.) to index.html, returning 200
with HTML content instead of a clear failure. This made the frontend's
/server_info probe ambiguous.

Add a --reject-prefix flag to static-server.mjs: any matching request
returns 503 ('Service Unavailable') before the SPA fallback runs.

Wire it through dev-with-automation.mjs: getRejectPrefixes(config)
computes which API prefixes have no backend configured (e.g. all of
them in frontend-only mode) and buildRejectPrefixArgs() passes them
as --reject-prefix flags to the static server.

Restore the e2e test to assert 503 for /server_info, /api/settings,
and /api/automation/v1 in frontend-only mode.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: treat non-401 HTTP errors from /server_info as unavailable

loadAgentServerInfo was re-throwing all SDK HttpErrors (including 503)
as-is, but root.tsx only checks for AgentServerUnavailableError. A 503
from the static server in --frontend-only mode (or any non-401 error)
would fall through to the Outlet instead of showing the manage-backends
modal.

Narrow the re-throw to only preserve 401 (needed for the auth screen in
public mode). All other HTTP errors are now wrapped as
AgentServerUnavailableError so the app shows the correct recovery UI.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: poll automation readiness in backend-only test; retry on 5xx

The automation backend starts independently from the agent-server and
may not be ready when /server_info first returns 200. pollUrl now treats
5xx responses as 'not ready yet' and keeps retrying. The backend-only
test also polls /api/automation/v1 before asserting on it.

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com>
2026-06-03 06:27:25 +00:00
Rohit Malhotraandopenhands 53ed7757cb docs: rewrite self-hosting guide for --public mode (#1038)
* docs: rewrite self-hosting guide for --public mode

Replace the outdated npm-run-dev + nginx-basic-auth workflow with the
production CLI (npx @openhands/agent-canvas --public). Key changes:

- Step 3 now uses a single npx command instead of clone/install/dev
- Security model uses LOCAL_BACKEND_API_KEY + --public (built-in API key
  entry screen) instead of nginx HTTP basic auth as the primary defense
- Removed ~80 lines of htpasswd, POSIX ACL, and apache2-utils setup
- nginx section is now TLS-only (no auth_basic directives)
- Architecture diagram updated: static server replaces Vite dev server,
  LOCAL_BACKEND_API_KEY replaces SESSION_API_KEY
- Section 5 simplified: key comes from LOCAL_BACKEND_API_KEY, no
  session-api-key.txt or cross-origin basic-auth workaround

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: rename "Run the server" to "Run Agent Canvas"

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: add process-persistence guidance (tmux + systemd) to self-hosting guide

Add a brief section after the 'npx --public' command explaining how to
keep the service alive across SSH sessions, covering:
- Option A: tmux one-liner for quick setups
- Option B: minimal systemd unit with Restart=on-failure for long-term deployments

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: use export + openssl rand for key, remove dead anchor link

- Replace inline LOCAL_BACKEND_API_KEY=<secret> pattern with
  export + openssl rand -base64 32 so the key stays out of ps aux
  and shell history
- Add brief explanation of why export is preferred
- Update tmux example to use export before tmux new-session
- Remove dangling reference to non-existent
  #advanced-defense-in-depth section

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-02 21:53:29 +00:00
simonrosenbergandDebug Agent 779cba5f6e docs(acp): add a guide for using ACP agents (Claude Code, Codex, Gemini CLI) (#972)
* docs(acp): add ACP agents onboarding/config guide

A guide for onboarding and configuring external ACP agents (Claude Code, Codex,
Gemini CLI): where each stores its login, which credentials onboarding collects,
and how subscription/login vs API-key precedence works.

The credentials-collection code originally in this PR landed via #1002
(registry-derived fields including Gemini), so #972 is now docs-only — rebased
onto current main.

* docs(acp): state that a subscription login takes priority over API keys

Make explicit that a subscription / OAuth login is preferred over an env API
key for all three providers — while signed in, a key isn't used (the onboarding
fields do nothing). Corrects the earlier claim that Claude Code uses
ANTHROPIC_API_KEY instead of the login: with both present, `claude auth status`
reports it's using the subscription (claude.ai). The base URL remains the one
override that takes effect under a login.

* docs(acp): show the conversation-turns edge in the flow diagram

Canvas relays each turn through the Agent Server, not just the settings PATCH;
add that edge so the diagram doesn't read as config-only (addresses review).

---------

Co-authored-by: Debug Agent <debug@example.com>
2026-06-02 12:25:30 +00:00
14b1b1e8ad feat: two auth modes — local (auto-key) and public (paste-key) (#790)
* feat: two auth modes — local (auto-key) and public (paste-key)

Local mode (agent-canvas, no flags):
- Ingress binds to 127.0.0.1 only
- Auto-generates session API key
- Writes /backends.json to static dir so frontend auto-authenticates
- Zero setup for localhost use

Public mode (agent-canvas --public):
- Ingress binds to 0.0.0.0 (all interfaces)
- Requires LOCAL_BACKEND_API_KEY env var
- Does NOT write /backends.json
- Frontend shows API key entry screen on 401

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor: reuse BackendForm in public-mode API key entry screen

Replace the bespoke ApiKeyEntryScreen form with BackendForm configured
for the public-auth use case:

- Host field is auto-filled from window.location.origin and read-only
- Name field is hidden (auto-derived from the existing backend)
- Only the API key input is exposed to the user
- Uses the same SettingsInput / BrandButton components as the backend
  connection modals for visual consistency

BackendForm gains three optional props to support this:
- hideName: hides the name input and uses a fallback name
- hostReadOnly: disables the host input
- onSubmitPayload: receives the submitted payload for side-effects
  (the API key screen uses it to persist to agent-server-config and
  reload the page)

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: update AGENTS.md with ApiKeyEntryScreen BackendForm reuse details

Co-authored-by: openhands <openhands@all-hands.dev>

* feat: implement public mode auth flow (--public flag)

- Add --public flag to dev-with-automation.mjs and bin/agent-canvas.mjs
- In public mode: require LOCAL_BACKEND_API_KEY, use as session key,
  don't bake into frontend (no VITE_SESSION_API_KEY / --session-api-key)
- Add isAgentServerAuthError() to detect 401 from /server_info probe
- root.tsx shows ApiKeyEntryScreen when 401 detected (lazy loaded)
- useConfig skips retries on 401 for instant auth screen display
- ApiKeyEntryScreen now has default export for React.lazy compatibility

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: use VITE_AUTH_REQUIRED flag instead of 401 detection for public mode

The 401-based approach was unreliable — /server_info may not require
auth on all server versions. Instead:

- dev-with-automation.mjs sets VITE_AUTH_REQUIRED=true in public mode
- isAuthRequiredAndMissing() checks the flag + localStorage for a key
- root.tsx gates on the flag BEFORE the /server_info probe, so the
  auth screen appears instantly with zero network round-trips
- 401 fallback kept as safety net for edge cases

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: handle stale key via 401 detection in public mode

When the server restarts with a new LOCAL_BACKEND_API_KEY, the browser
still has the old key in localStorage. isAuthRequiredAndMissing() returns
false (key exists), so the /server_info probe fires and 401s.

isAgentServerAuthError() now checks VITE_AUTH_REQUIRED=true AND 401
status, so it only triggers in public mode (a 401 in local mode is a
misconfiguration, not a key-rotation event). useConfig skips retries
on 401 to show the auth screen immediately.

Two gates, one screen:
- No key at all → flag check, instant, no network
- Stale key → /server_info 401, one round-trip

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: validate stale keys against GET /api/settings (protected)

/server_info is unprotected — it returns 200 even with a wrong key.
In public mode, after the /server_info probe succeeds, we now hit
GET /api/settings to verify the stored key is still valid. A 401
from that endpoint triggers the auth screen via isAgentServerAuthError().

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: rewrite ApiKeyEntryScreen — validate before save, always empty key, match add-modal UI

Three fixes:

1. Stale key conflict: The form now always starts with an empty API key
   field instead of pre-filling from the backend registry. Stale
   credentials from a previous session never bleed into the input.

2. Wrong key indicator: On submit, the key is validated against
   GET /api/settings (protected endpoint) BEFORE persisting. Wrong
   keys show an inline red status dot + 'Invalid API key' error
   via BackendStatusDot. Only validated keys trigger the reload.

3. UI parity with add-backend modal: Replaced BackendForm wrapper
   with direct SettingsInput fields matching ManualConnectionColumn's
   layout — host (read-only + helper text), API key (password with
   placeholder), status indicator, and Connect button. No cloud
   OAuth column.

New i18n key: AUTH$INVALID_KEY (all 15 languages).

Co-authored-by: openhands <openhands@all-hands.dev>

* feat: match add-backend modal UI + add test coverage

ApiKeyEntryScreen now renders the exact same card chrome as
BackendFormModal add-mode: same title ('Add a Backend'), same
Name/Host/API Key fields, same Connect button styling. Host is
pre-filled and read-only; no cloud OAuth column.

New tests (12 total):
- api-key-entry-screen.test.tsx (7 tests):
  - UI field parity with add-backend modal
  - Stale key wipe (empty API key field despite stale localStorage)
  - Connect disabled until name + key filled
  - Valid key: validates → persists → reloads
  - Invalid key: error indicator, no persist, no reload
  - Retry flow: wrong key → error → correct key → success
  - Stale key isolation: only fresh key persisted
- agent-server-config.test.ts (5 new tests for isAuthRequiredAndMissing):
  - Flag unset → false
  - Flag set, no key → true
  - Flag set, localStorage key → false
  - Flag set, VITE_SESSION_API_KEY → false
  - Flag not 'true' → false

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: distinguish 401 from other errors in ApiKeyEntryScreen

The catch-all was showing 'Invalid API key' for EVERY failure —
including 500s, network errors, and timeouts — even when the key
was correct. Now:

- 401 → 'Invalid API key. Please check the key and try again.'
- Anything else → 'Connection failed: <actual error message>'

This reveals the real problem when a correct key fails for a
non-auth reason (e.g. server misconfiguration, missing OH_SECRET_KEY).

New i18n key: AUTH$CONNECTION_FAILED (all 15 languages).
New test: non-401 errors show 'Connection failed' + detail.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: agent-server receives wrong session key in public mode

startAgentServer() called buildSafeDevConfig() which generated its
own random session key, ignoring config.sessionApiKey (which holds
LOCAL_BACKEND_API_KEY in public mode). The agent-server was started
with a random key while users were told to paste the LOCAL_BACKEND_API_KEY
value — every key was rejected with 401.

Fix: override OH_SESSION_API_KEYS_0 in the agent-server env with
config.sessionApiKey so both the agent-server and the frontend
agree on which key is valid.

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor: address review comments on ApiKeyEntryScreen

1. Remove dead BackendFormProps (hideName, hostReadOnly, onSubmitPayload)
   — ApiKeyEntryScreen is standalone so no caller used these props.

2. Auto-generate backend name from window.location.hostname instead of
   requiring users to type one. Only the API key field is required now,
   reducing public-mode auth to a single-field flow.

3. Simplify redundant ternary: connectionStatus === 'success' ? true : false
   → connectionStatus === 'success'.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: address second round of review comments

1. Use shared isSdkHttpError() helper in ApiKeyEntryScreen instead of
   duplicating the SDK error shape check inline. Exported the helper
   from agent-server-compatibility.ts.

2. Add code comment acknowledging the edge case where a network hiccup
   between /server_info and getSettings() probes lets the app load with
   an unvalidated key. Acceptable since the window is narrow and a page
   refresh recovers.

3. Add --auth-required flag to static-server.mjs so pre-built static
   binaries (npx @openhands/agent-canvas --public) show the API key
   entry screen without needing VITE_AUTH_REQUIRED baked in at build
   time. The flag injects window.__AGENT_CANVAS_AUTH_REQUIRED__=true
   into index.html at runtime. Frontend isAuthRequired() checks both
   the build-time env var and the runtime window flag.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: use double cast (unknown) to satisfy strict TS on window flag access

window cannot be cast directly to Record<string, unknown> — TypeScript
requires going through unknown first for unrelated types.

  (window as unknown as Record<string, unknown>).__AGENT_CANVAS_AUTH_REQUIRED__

This fixes the CI typecheck failure introduced in aa76c01a.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: address remaining review comments on auth modes PR

- Use isAuthRequired() instead of raw import.meta.env.VITE_AUTH_REQUIRED
  in isAgentServerAuthError() so the runtime window flag injected by
  static-server.mjs in pre-built binaries is also honoured (bug fix).

- Preserve existing backend name during re-authentication flow in
  ApiKeyEntryScreen; only fall back to window.location.hostname for
  the initial entry so users don't lose custom labels on key rotation.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: restore MCP-to-integrations migration from main

A prior merge into this branch incorrectly kept the old
@openhands/extensions/mcps imports instead of the
@openhands/extensions/integrations paths introduced by d41bfe15
on main. Restore all affected files from origin/main so the
extensions package (which no longer exports ./mcps) resolves
correctly.

Files restored from main:
- src/utils/mcp-marketplace-utils.ts
- src/routes/mcp.tsx
- src/components/features/mcp-logo-badge.tsx
- src/components/features/mcp-page/* (6 files)
- src/components/features/automations/* (2 files)
- __tests__/ (4 test files)

Co-authored-by: openhands <openhands@all-hands.dev>

* style: fix prettier formatting and remove unused eslint-disable in api-key-entry-screen

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: address remaining PR review comments

- Extract isSdkHttpStatusError() helper in agent-server-compatibility.ts
  to DRY up the SDK error status check (review comment #3321202503).
  Both isAgentServerAuthError() and ApiKeyEntryScreen now use it.

- Use AUTH i18n keys in api-key-entry-screen.tsx:
  • Heading: AUTH$API_KEY_REQUIRED_TITLE ('API Key Required')
  • Description: AUTH$API_KEY_REQUIRED_DESCRIPTION added below heading
  • Button: AUTH$CONNECT ('Connect')
  (review comments #3325478721, #3325478729)

- Fix nested <main> landmark in root.tsx: remove the outer <main>
  wrapper since ApiKeyEntryScreen already provides its own semantic
  container (review comment #3325478704).

- Change ApiKeyEntryScreen root element from <main> to <div> so
  the Layout's own landmarks are not violated.

Co-authored-by: openhands <openhands@all-hands.dev>

* test: add coverage for window.__AGENT_CANVAS_AUTH_REQUIRED__ runtime flag

Add isAuthRequired() test block covering the window flag path used by
pre-built static binaries (static-server.mjs --auth-required). Also add
window-flag variants to isAuthRequiredAndMissing() tests.

Addresses review comment #3325587577.

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor!: deduplicate SESSION_API_KEY into LOCAL_BACKEND_API_KEY

BREAKING CHANGE: The user-facing env var for setting the API key is now
`LOCAL_BACKEND_API_KEY` everywhere. The old `SESSION_API_KEY`,
`OH_SESSION_API_KEYS_0`, and `VITE_SESSION_API_KEY` env vars are no
longer read by launchers as user-facing configuration.

Internal plumbing (`config.sessionApiKey`, `VITE_SESSION_API_KEY` build
injection, `OH_SESSION_API_KEYS_0` agent-server env) is unchanged — only
the user-facing surface is unified into a single env var.

Changes:
- scripts/dev-safe.mjs: read LOCAL_BACKEND_API_KEY instead of
  SESSION_API_KEY / OH_SESSION_API_KEYS_0 / VITE_SESSION_API_KEY
- scripts/dev-with-automation.mjs: unify key resolution through
  buildSafeDevConfig for both public and local modes
- bin/agent-canvas.mjs: update CLI help text and examples
- docker/entrypoint.sh: read LOCAL_BACKEND_API_KEY, migrate legacy
  session-api-key.txt → api-key.txt
- scripts/static-server.mjs: add mutual-exclusion guard for
  --session-api-key + --auth-required flags
- playwright configs: pass LOCAL_BACKEND_API_KEY instead of the old trio
- test helpers: prefer LOCAL_BACKEND_API_KEY fallback chain
- Update tests and documentation

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: address review comments — narrow settings probe rethrow and use shared client options

- loadAgentServerInfo: narrow getSettings() catch to rethrow only 401
  errors. Other HTTP errors (403, 5xx) and non-HTTP errors (network,
  timeout) are now swallowed with a console.warn, since the server is
  confirmed up (via /server_info) and the probe is best-effort. This
  prevents misconfigured servers from silently falling through to
  <Outlet /> without showing either the auth or unavailable screen.

- ApiKeyEntryScreen: replace hand-rolled SettingsClient options with
  getAgentServerClientOptions() so transport-level settings (e.g.
  VITE_INSECURE_SKIP_VERIFY) are honoured. Uses the sessionApiKey
  override to pass the freshly-entered key.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: rewrite git+ssh to git+https for @openhands/extensions in lockfile

npm normalizes GitHub URLs to git+ssh:// in the lockfile, but machines
without SSH keys for GitHub (or with stale npm caches) can end up
installing a wrong version of the package. This causes the Vite resolve
error:

  "./integrations" is not exported under the conditions [...]

The same pattern was already fixed for @openhands/typescript-client
(see #384). vercel-install.sh already does a blanket sed rewrite, but
the committed lockfile itself should use git+https:// so local npm ci
works without SSH keys.

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor: align public auth screen with Add Backend form layout

Replace the custom 'API Key Required' screen with the same form
layout used by the 'Add a Backend' left column in BackendFormModal:

- Heading changed from 'API Key Required' to 'Add a Backend'
- Added backend Name field (required, same as ManualConnectionColumn)
- Host field remains pre-filled and disabled (from window.location.origin)
- API Key field unchanged
- Submit button now uses BACKEND$CONNECT label (matching the modal)
- Removed the subtitle description paragraph for cleaner parity
- Name is persisted to the backend registry on submit

Tests updated: fillApiKey → fillRequiredFields (name + apiKey),
assertions cover the new name field and dual-field submit gating.

Co-authored-by: openhands <openhands@all-hands.dev>

* chore: remove unused AUTH$ i18n keys from this PR

The UI refactor (02faa0b0) switched ApiKeyEntryScreen to the
BACKEND$* keys. Drop the three AUTH$ entries that were introduced
and then superseded within this same PR:

- AUTH$API_KEY_REQUIRED_TITLE
- AUTH$API_KEY_REQUIRED_DESCRIPTION
- AUTH$CONNECT

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: sync stale session API key on boot when LOCAL_BACKEND_API_KEY changes

When a user restarts the stack with a different LOCAL_BACKEND_API_KEY,
the new VITE_SESSION_API_KEY is baked in correctly, but localStorage
may still hold the old key in two places:

  1. openhands-agent-server-config.sessionApiKey (written by onboarding
     or the Settings page)
  2. openhands-backends[].apiKey (seeded on first load, never re-synced)

The existing syncDefaultLocalBackendAuth() in storage.ts already tries
to fix #2 by comparing against makeDefaultLocalBackend(), but that
function reads through getConfiguredSessionApiKey() which hits #1
(stale localStorage) before falling back to VITE_SESSION_API_KEY.
So a stale #1 defeats the #2 sync.

Fix: add syncBakedSessionApiKey() which runs from readStoredBackends()
before any key resolution. When VITE_SESSION_API_KEY is set and the
stored key in openhands-agent-server-config differs, overwrite it.
This ensures getConfiguredSessionApiKey() and makeDefaultLocalBackend()
both return the correct key, and the downstream backend-registry sync
works as intended.

Also fix the static-server.mjs injection script to always overwrite a
stored key that differs from the runtime key (was guarded by
`if(!_c.sessionApiKey)` which skipped updates when any key existed).

Add mock-LLM E2E tests for:
- Key rotation recovery: seeds stale localStorage, verifies app loads
- Public-mode auth gate: tests auth screen visibility, wrong key
  rejection, and correct key acceptance

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: document key rotation resilience in AGENTS.md

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: add syncBakedSessionApiKey to vi.mock stubs and use click-then-fill in E2E

Three test files mock #/api/agent-server-config without exporting
syncBakedSessionApiKey, which storage.ts now calls at import time.
Add the missing vi.fn() stub to all three.

Also fix the public-mode auth E2E test: use the click() → fill()
pattern for React controlled inputs (matching the established
convention in mock-llm-conversation.spec.ts) so the SettingsInput
onChange fires reliably in Playwright.

Co-authored-by: openhands <openhands@all-hands.dev>

* test: add public-mode key rotation E2E test

Simulates a server key rotation: localStorage holds a stale key from
a previous session, the server now has a new key. Verifies the app
detects the 401 from the stale key probe, shows the auth screen, and
accepts the new key.

Flow: stale key in localStorage → probe /server_info → 401 →
isAgentServerAuthError → ApiKeyEntryScreen → user pastes new key →
reload → app loads normally.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix(e2e): suppress consent modal in public-mode auth tests

The analytics consent modal overlays the auth screen on first visit
(clean localStorage). Playwright's click() on the form inputs was
intercepted by the modal overlay, causing a 60s timeout loop (121
retries). Add a beforeEach that seeds 'analytics-consent' and
'openhands-telemetry-consent' in localStorage before navigation.

Also deduplicate the consent seeding from the key-rotation test's
addInitScript since the beforeEach now handles it.

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor: deduplicate readStoredConfig() call in syncBakedSessionApiKey

Capture the first readStoredConfig() result and reuse it in the
spread instead of hitting localStorage twice.

Co-authored-by: openhands <openhands@all-hands.dev>

* chore(docker): remove legacy session-api-key.txt migration

The backwards-compatibility shim that migrated the old
session-api-key.txt to api-key.txt is no longer needed — a breaking
change here is acceptable.

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor: dedup ApiKeyEntryScreen against BackendForm

ApiKeyEntryScreen now renders BackendForm with three new props instead
of reimplementing the name/host/API-key inputs from scratch:

- hostReadOnly: locks the host field (pre-filled from window.origin)
- requireApiKey: forces a non-empty API key for local backends
- onSubmitOverride: replaces the default sync persist with async
  server-side validation (GET /api/settings) before persisting

The auth-gate-specific chrome (full-screen wrapper, connection status
indicator, validating/error state) stays in ApiKeyEntryScreen via the
existing renderActions slot.

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: chuckbutkus <chuck@openhands.dev>
2026-06-01 12:02:37 -06:00
chuckbutkusandopenhands 2075506f6e Prepare docs for incubator review (#605)
* Prepare docs for incubator review

Co-authored-by: openhands <openhands@all-hands.dev>

* Review updates

* Update readme

* Update packages and readme

* docs: fix SELF_HOSTING.md link to new docs/ location

Co-authored-by: openhands <openhands@all-hands.dev>

* Update owner to github handle

* Update phase language

* Update sandbox status badge to alpha in README

Co-authored-by: openhands <openhands@all-hands.dev>

* Update incubator stage from Alpha to Beta in README

Co-authored-by: openhands <openhands@all-hands.dev>

* Remove project owner

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-29 17:06:26 +00:00
Robert Brennanandopenhands 5a64e0b3c3 docs: DefenseClaw security governance integration guide (#719)
* docs: add DefenseClaw security governance integration guide

Explains how to integrate DefenseClaw (cisco-ai-defense/defenseclaw) with
Agent Canvas and the OpenHands Agent Server without any code-level changes:

- Load the DefenseClaw CodeGuard skill for secure-by-default code generation
- Route LLM traffic through the DefenseClaw guardrail proxy (localhost:4000)
- Pre-install skill scanning via the defenseclaw CLI
- Post-generation workspace scanning with CodeGuard
- Audit/observability via TUI, OTLP, Splunk, and webhooks

Also documents six future code-level extension points (SecurityAnalyzer hook,
skill install pipeline gate, hooks integration, UI dashboard, audit bridge,
and registry alignment).

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: replace ASCII diagrams with Mermaid in DefenseClaw integration guide

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: rename integrating-defenseclaw.md to DefenseClaw.md

Co-authored-by: openhands <openhands@all-hands.dev>

* docs: simplify Mermaid diagram node labels

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-21 18:36:12 +00:00
Xingyao Wangandopenhands f3c6fd2122 Remove docs/ folder - migrated to separate repository (#11261)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-10-08 08:22:39 -04:00
Hiep Le c62a6616db feat(frontend): integrate with Reo.dev (#11251) 2025-10-06 21:37:25 +07:00
mamoodi 025ac7672f Update docs (#11224) 2025-10-03 14:29:24 -04:00
Jamie Chicagoandopenhands 167fb3f429 Replace dub.sh/openhands links with all-hands.dev/joinslack (#11209)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-10-01 20:00:00 +00:00
mamoodi 5452abe513 Release 0.58.0 (#11168) 2025-10-01 13:18:20 -04:00
Xingyao Wangandopenhands e19b3dd1f0 Add claude-sonnet-4-5 model support (#11179)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-30 03:27:19 +00:00
mamoodi e6d799c51a Remove discord links as it winds down and some small doc updates (#11145) 2025-09-27 08:00:03 -04:00
mamoodi b883fe37e6 fix MCP links (#11137) 2025-09-25 23:32:19 +00:00
mamoodi 182b7adcab Clean up MCP docs and move it to settings (#11133) 2025-09-25 18:28:29 -04:00
jpelletier1andopenhands 830a9e027f docs: Add Pro Subscription documentation page (#11115)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-25 13:07:30 -04:00
mamoodi 120a5d6ebd Add new settings folder for better organization (#11118) 2025-09-25 12:06:10 -04:00
mamoodi f8f74858da Update parts of the OpenHands Cloud docs (#11107) 2025-09-24 16:27:52 -04:00
Tejas GoyalandTejas Goyal 16004426a2 feat: Add configurable timeouts for MCP tool invocations (Good first issues: #10684) (#11029)
Co-authored-by: Tejas Goyal <tejas@Tejass-MacBook-Pro.local>
2025-09-24 16:43:54 +04:00
e09f93aa75 Release 0.57.0 (#10981)
Co-authored-by: Ray Myers <ray.myers@gmail.com>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com>
Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com>
2025-09-19 12:40:56 -04:00
Graham Neubigandopenhands 89e3d2a867 Improve OpenHands provider pricing documentation (#10974)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-20 00:22:44 +08:00
Engel Nystandopenhands 65f4164749 [Docs] Add environment variables reference table (#10926)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-15 18:31:44 +00:00
Xingyao Wangandopenhands 8ea1259943 Add GitHub workflow for MDX format checking and fix parsing error (#10924)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-10 23:04:54 +00:00
mamoodi a809d74b7d Release 0.56.0 (#10876) 2025-09-09 10:30:43 -04:00
Graham Neubigandopenhands 79f32a34a0 Fix SANDBOX_VOLUMES format in headless mode documentation (#10887)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-08 14:17:20 -04:00
Xingyao Wangandopenhands 989a4e662b feat: integrate with unified docs repository (#10830)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-06 16:10:21 +02:00
Ankit Kumar Yadav f442e07b33 docs: replaced slack invite links with dub.sh link (fixes #10768) (#10779) 2025-09-05 08:57:49 -04:00
mamoodi f46b112f17 Add more troubleshooting for linux (#10704) 2025-09-02 14:02:16 -04:00
mamoodi 44dc7f9e9b Release 0.55.0 (#10657) 2025-09-02 13:49:02 -04:00
mamoodi 83b9262379 Add troubleshooting guide for linux timeout issue (#10685) 2025-08-29 05:52:36 +02:00
Web3 Outlaw 77b5c6b161 Fix Typos in Comment and Docs (#10644) 2025-08-27 14:06:39 +00:00
mamoodi b0007076c0 Remove duplicated command in CLI (#10634) 2025-08-26 16:01:16 -04:00
Jamie Chicagoandopenhands 6448f5a681 docs: Add Ubuntu installation steps for Windows WSL setup (#10485)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-08-25 20:43:04 +00:00