17 Commits
Author SHA1 Message Date
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
Rohit Malhotraandopenhands c86824bafd fix: update release repository metadata (#16186)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-29 22:47:31 -04:00
Rohit Malhotraandopenhands 22fe594133 feat: forward automation telemetry context (#1917)
* feat: forward telemetry context to automations

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

* feat: sync automation telemetry consent

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

* fix: default automation telemetry key in launchers

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

* fix: bake production telemetry defaults into npm package

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

* fix: dedupe automation consent sync

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

* chore: bump automation version to 1.3.0

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-24 05:47:26 +00:00
Graham Neubigandneubig 3598bb14e3 fix: preserve Canvas analytics identity (#1839)
* fix: unify Canvas PostHog identity

* fix: preserve funnel events across backend transitions

* fix: preserve telemetry consent through cloud login

* fix: isolate Canvas telemetry from host PostHog

* fix: preserve telemetry during client startup

* fix: centralize Canvas telemetry ownership

* fix: make telemetry lifecycle atomic

---------

Co-authored-by: neubig <neubig@users.noreply.github.com>
2026-07-19 20:16:20 +01: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 Neubigandopenhands c552545926 feat(mcp): add OAuth support to MCP install flow
Squash merge PR #1583.

This merge commit was created by an AI agent (OpenHands) on behalf of Graham Neubig.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-07 12:03:36 +02:00
74f06866ec Use libraries for local proxy and static serving (#1543)
* Use libraries for local proxy and static serving

* Fix CI for proxy library refactor

* Fix static server CI failures

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

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-30 06:07:20 -07:00
OpenHands Bot 82ea2b609a feat: save hosted MCP credentials as secrets (#1331) 2026-06-12 20:03:46 +00:00
aivong-openhands 5076bb3517 APP-2306: Add VITE_POSTHOG_CLIENT_KEY to Docker build (#1302)
* Add VITE_POSTHOG_CLIENT_KEY to Docker build

Bake the PostHog client key into the frontend bundle at build time by
accepting a VITE_POSTHOG_CLIENT_KEY build arg in the Dockerfile and
passing it from the Docker workflow via the POSTHOG_CLIENT_KEY repo
variable. The key is a public, client-side key (not a secret), following
the same pattern as VITE_APP_ENV.

Closes APP-2306

* Select staging/prod PostHog key by release tag

Mirror the VITE_APP_ENV logic for VITE_POSTHOG_CLIENT_KEY: tagged v*
releases bake the prod key, all other builds (PR/main/local) use staging.
Both keys are public client-side keys (not secrets) sourced from the
POSTHOG_CLIENT_KEY_PROD / POSTHOG_CLIENT_KEY_STAGING repo variables.

* Rename PostHog repo vars to POSTHOG_STAGING_KEY / POSTHOG_PROD_KEY
2026-06-10 22:36:24 +00:00
Rohit Malhotraandopenhands 8c2cc3997d fix: GitHub MCP server works in Docker without Docker-in-Docker (#1282)
* fix: GitHub MCP server works in Docker without Docker-in-Docker

The GitHub MCP catalog entry uses `docker run` as its transport command,
which fails inside the agent-canvas Docker container because Docker is not
available (no daemon, no CLI). This is the only MCP integration affected —
all others use `npx` or `uvx`.

Fix:
- Pre-install the `github-mcp-server` Go binary in the Docker image via a
  new multi-arch download stage (supports amd64/arm64)
- Export `getDeploymentMode()` from agent-server-adapter to expose the
  runtime services info mode ("docker", "dev:automation", etc.)
- Add `patchGitHubEntry()` in mcp-marketplace-utils.ts that rewrites the
  catalog entry from `docker run … ghcr.io/github/github-mcp-server` to
  `github-mcp-server stdio` when deployment mode is "docker"
- The patch follows the existing `patchLinearEntry` pattern: immutable
  spread, conditional on entry id, wired into `getMcpMarketplaceCatalog()`

Closes #1190

* docs: document GitHub MCP catalog patching in AGENTS.md

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

* test: add E2E test for GitHub MCP install flow via marketplace UI

Exercises the full MCP page UI flow:
- Navigate to /mcp, verify GitHub marketplace card is visible
- Open install modal, verify fields (command, PAT input)
- Validate empty PAT shows error
- Fill PAT, submit with mocked /api/mcp/test success, verify installed
- Delete installed server via toggle + confirmation modal

Intercepts POST /api/mcp/test to return mock success since the real
github-mcp-server binary is not available in the test environment.

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

* chore: track github-mcp-server version in config/defaults.json

Move the hardcoded GITHUB_MCP_SERVER_VERSION=1.2.0 from the Dockerfile
default into config/defaults.json (versions.githubMcpServer) alongside
the other external dependency pins.

- Dockerfile: ARG no longer has a default; CI and local builds must
  pass it explicitly
- docker.yml: reads the version from config and passes it as a build-arg
- docker-build.mjs: reads the version from config and passes it too

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

* fix: correct GitHub MCP binary download URL and remove flaky validation test

- Fix Dockerfile: release assets use github-mcp-server_Linux_{arch}.tar.gz
  (no version in the filename), not github-mcp-server_{version}_Linux_{arch}.tar.gz
- Remove step 3 (empty PAT validation test) which relied on CSS class
  selector that doesn't work reliably in Playwright with compiled Tailwind
- Renumber remaining steps (4→3, 5→4)

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

* docs: address review comments — document docker command assumption and arch fallback

- mcp-marketplace-utils.ts: explain why we match on command === 'docker'
  and what happens if upstream changes the catalog entry
- Dockerfile: document the *) arch fallback and when to update it

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

* test: assert Docker-specific command patching in GitHub MCP E2E test

The test now asserts the command field value based on the deployment mode:
- Docker E2E: expects 'github-mcp-server stdio' (native binary)
- npm E2E: expects 'docker' (original catalog transport)

Uses MOCK_LLM_DOCKER_IMAGE env var presence (set only by the Docker
Playwright config) to determine which assertion to make. This ensures
the patchGitHubEntry runtime rewrite is exercised in Docker E2E.

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

* test: add unit tests for patchGitHubEntry Docker command rewrite

Addresses review feedback to add unit test coverage for the runtime
catalog patching. Three new tests via getMcpMarketplaceCatalog:
- Non-Docker mode: GitHub entry keeps original 'docker run' command
- Docker mode: command rewritten to 'github-mcp-server stdio'
- Docker mode: other entries (Tavily) unaffected

Uses vi.mock to control getDeploymentMode return value.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-09 14:08:40 -04:00
c39354f2b3 feat: load public skills from @openhands/extensions npm package (#1199)
* build(deps): move @openhands/extensions to npm 0.2.0

* feat: load public skills from @openhands/extensions npm package

Public skills are now loaded from the @openhands/extensions npm package
via a standard JS module import instead of fetching them through the
agent-server (which cloned the extensions GitHub repo at runtime).

  import { SKILLS_CATALOG } from '@openhands/extensions/skills';

SkillsService maps each SkillCatalogEntry to a SkillInfo and merges the
bundled public catalog with user/project skills fetched from the
agent-server (load_public: false). If the agent-server is unreachable,
the bundled catalog is returned alone.

Changes:
- SkillsService: imports SKILLS_CATALOG from @openhands/extensions/skills,
  maps entries to SkillInfo, merges with user/project skills from
  agent-server (load_public: false).
- agent-server-adapter: hardcodes load_public_skills: false in
  buildAgentContext().
- agent-server-config: removes shouldLoadPublicSkills() and its
  VITE_LOAD_PUBLIC_SKILLS env var.
- dev-safe.mjs: removes getExtensionsRef() / DEFAULT_EXTENSIONS_REF
  and EXTENSIONS_REF injection in buildAgentServerEnv().
- Docker: removes CONFIG_EXTENSIONS_REF from config-gen stage and
  EXTENSIONS_REF from entrypoint.sh.
- .env.sample: removes VITE_LOAD_PUBLIC_SKILLS comment.
- Tests updated to match new architecture.

Depends on OpenHands/extensions#310 which adds the SKILLS_CATALOG export.

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

* test: remove activated_skills assertion from preset-automation E2E

With load_public_skills: false the agent-server no longer loads public
skills at runtime, so activated_skills is always empty. The conversation
itself works (slash command sent, agent replies) — only the server-side
skill activation metadata is gone.

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

* feat: pass bundled public skills via agent_context.skills for SDK-side activation

Instead of doing frontend-side trigger matching, pass the bundled
SKILLS_CATALOG entries directly in agent_context.skills at conversation
start. The SDK performs trigger matching, sets activated_skills on user
events, and injects skill content into the system prompt — the exact
same behavior as when load_public_skills was true, but without cloning
the extensions repo at runtime.

buildBundledSkills() converts each catalog entry into the SDK Skill JSON
shape with KeywordTrigger ({ type: 'keyword', keywords: [...] }) for
skills with triggers, or null for always-active skills.

Restores the activated_skills E2E assertion in the preset-automation
test since the SDK now handles activation.

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

* test: add E2E tests for project/user skill loading and deletion

Add mock-llm-skills.spec.ts with three tests:
1. Project skill in workspace/.agents/skills/ triggers on matching keyword
2. User skill in ~/.openhands/skills/ triggers on matching keyword
3. Deleting a user skill removes it from subsequent conversations

Tests create ephemeral SKILL.md files with unique trigger keywords,
send messages through the real agent-server stack, and verify
activated_skills in the conversation events API.

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

* fix: use explicit APIRequestContext type import for CI TS6 compatibility

Replace inline `import('@playwright/test').APIRequestContext` type
references with a proper top-level type import. Also align afterEach
fixture destructuring with other specs' pattern.

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

* fix: remove node: prefix from imports to fix CI TS resolution

TypeScript 6 on CI (Node 24) has a type resolution conflict when
`node:` prefixed imports (node:path, node:fs, node:os) coexist with
`@playwright/test` types in the same file. This caused
`APIRequestContext` to be incorrectly resolved as `Page`. Use
unprefixed imports (path, fs, os) which work identically in Node.js.

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

* fix: split fs helpers into separate file to fix CI TS6 type resolution

Move node built-in imports (path, fs, os) and filesystem helpers to
`utils/skill-test-helpers.ts`. The spec file now only imports from
`@playwright/test` and the two helper modules, avoiding the type
resolution conflict between node builtins and Playwright fixture types
that caused `APIRequestContext` to be incorrectly inferred as `Page`
on CI (TypeScript 6 / Node 24 / Ubuntu).

API assertion logic is now inline within each test step, using the
`request` fixture directly instead of standalone functions with
explicit `APIRequestContext` type annotations.

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

* fix: use namespace imports to avoid TS6 type inference issue

Switch from named imports to namespace imports (`import * as helpers`)
with subsequent destructuring. This changes how TypeScript resolves the
imported function signatures, avoiding a Node 24 / TS6 type inference
bug where `ensureMockLLMProfile` was incorrectly resolved as expecting
`Page` instead of `APIRequestContext`.

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

* fix: add typed wrapper for ensureMockLLMProfile to fix CI TS2345

Add a local `configureMockLLM` wrapper with an explicit
`APIRequestContext` type annotation. This works around a CI-specific
TypeScript 6 type inference issue where the imported
`ensureMockLLMProfile` signature is incorrectly resolved as expecting
`Page` instead of `APIRequestContext` when called from a Playwright
test body that also imports from `skill-test-helpers` (a module with
node built-in imports). The wrapper's explicit type annotation forces
correct type checking at the call site.

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

* fix: inline ensureMockLLMProfile logic to fix CI TS2345

Instead of importing ensureMockLLMProfile from mock-llm-helpers (which
triggers a CI-specific TS6 type inference bug when combined with
skill-test-helpers imports), inline the same logic as a local function
with explicit APIRequestContext typing. This avoids the cross-module
type resolution issue entirely.

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

* fix: resolve WORKSPACE_DIR relative to agent-server CWD, not STATE_DIR

The agent-server resolves the relative working_dir ("workspace/project")
from its own CWD (the project root), not from STATE_DIR/workspaces.
The test was writing skill files to the wrong directory so the SDK
never found them, causing activated_skills to be empty.

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

* fix: create standalone git repo for project skill E2E test

The agent-server creates a git worktree for each conversation, and only
committed files appear in worktrees. The previous approach wrote skill
files to the filesystem without committing them, so the worktree never
contained them and load_project_skills found nothing.

Now the test:
1. Creates a standalone git repo (.tmp/mock-llm-skill-repos/) with the
   skill file committed
2. Creates the conversation via API with that repo as working_dir
3. The agent-server worktree includes the committed skill
4. load_project_skills discovers it in the worktree

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

* fix: add secrets_encrypted flag to skill test conversation creation

The GET /api/settings with X-Expose-Secrets: encrypted returns cipher-
encrypted secret values. The POST /api/conversations needs
secrets_encrypted: true to tell the server to decrypt them, otherwise
the request fails with HTTP 422.

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

* refactor: use UI workspace selection for project skill E2E test

Instead of creating conversations via API (bypassing the frontend code),
the test now exercises the full UI flow:

1. Creates a standalone git repo with the skill committed
2. Registers the repo as a workspace via POST /api/workspaces
3. Opens the 'Open workspace' dialog in the UI
4. Selects the workspace from the dropdown
5. Types the message and submits via the chat input

This exercises the actual frontend code paths (workspace dropdown,
workspace selection form, createConversation with workingDirOverride)
that real users go through.

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

* fix: add padding response for skill-analysis in deletion test

The agent-server makes a skill-analysis LLM call even when no user/project
skills are loaded, because public skills from the npm package are still
present. The deletion test only had 1 trajectory response, causing the
agent to hang waiting for the 2nd response (the actual reply).

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

* fix: simplify deletion test to not depend on specific event type

The deletion test was failing because it waited for an event with
source='agent' and event_type='message' in the events API, but the
mock LLM text reply may produce a different event type. Since
waitForNonUserMessageText already confirms the agent replied in the
UI, we just need to verify no activated_skills contains the deleted
skill name.

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

* fix: mount skill test dirs into Docker container for e2e tests

The Docker E2E skills test was failing because the agent-server inside
the Docker container couldn't access skill repos and user skill files
created on the host filesystem.

Fix by:
- Adding volume mounts for skill repos (.tmp/mock-llm-skill-repos/ →
  /tmp/mock-llm-skill-repos/) and user skills (.tmp/mock-llm-user-skills/
  → /home/openhands/.openhands/skills/) to the Docker run command
- Setting env vars (MOCK_LLM_SKILL_REPOS_CONTAINER_DIR,
  MOCK_LLM_USER_SKILLS_HOST_DIR) so skill-test-helpers.ts can
  distinguish host-side vs agent-side paths
- Updating createProjectSkillRepo to return both hostDir and agentDir
  so the test registers the container-side path with the agent-server

In npm mode (no env vars set), all paths fall back to the existing
host-side values — no behavior change for the npm test path.

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

* docs: document Docker skill test volume mounts in AGENTS.md

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

* feat: mark newly added mock-LLM E2E tests with 🆕 badge in PR comments

The render-mock-llm-report.mjs script now accepts a --new-files flag
with a comma-separated list of spec file paths added in the PR. Tests
from those files get a 🆕 badge in the results table, and the summary
line shows the count (e.g. '🆕 2 new').

Both CI workflows (mock-llm-e2e.yml and mock-llm-docker-e2e.yml) add
a 'Detect newly added spec files' step that queries the GitHub API
for files with status=='added' matching the mock-LLM spec pattern,
avoiding shallow-clone issues with git diff.

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

* fix: match Playwright basename file paths against repo-relative --new-files

Playwright's JSON reporter emits file paths relative to testDir
(e.g. 'mock-llm-skills.spec.ts') while the GitHub API returns
repo-relative paths (e.g. 'tests/e2e/mock-llm/mock-llm-skills.spec.ts').
The isNewTest() matcher now compares basenames in addition to exact/suffix
matching, so 🆕 badges render correctly.

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

* fix: stabilize pagination loading-indicator test + improve new-test callout

1. Flaky test fix: the 'loads older events when scrolling up' test
   asserts that the loading-older-events indicator appears, but the
   instant mock response lets React batch isLoading true→false in one
   commit — the DOM element never materialises. Add a 300ms delay to
   older-events mock responses so the indicator renders reliably.

2. Better new-test visibility: replace the subtle inline 🆕 emoji with
   a prominent green blockquote callout above the results table that
   lists each new test with its status icon and spec file.

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

* fix: address PR review — type safety, docs, test assertions

1. Define BundledSkill interface for buildBundledSkills() return type
   instead of the opaque SettingsRecord[] (review thread #1).

2. Document PUBLIC_SKILLS as an immutable build-time snapshot that is
   baked into the bundle and requires a dependency bump to update
   (review thread #2).

3. Add migration note to buildAgentContext() explaining that the former
   VITE_LOAD_PUBLIC_SKILLS env var was removed because bundled skills
   have no clone latency. load_public_skills: false is still passed to
   tell the SDK to skip its own clone (review thread #3).

4. Add structural assertions for individual skill entries in the adapter
   test: name, content, source, is_agentskills_format, and trigger
   shape (review testing gap).

5. Update stale VITE_LOAD_PUBLIC_SKILLS comments in E2E test files.

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

---------

Co-authored-by: Joe Laverty <joe.laverty@openhands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-07 21:52:59 +00:00
Tim O'Farrellandopenhands dda46c10bd feat: default EXTENSIONS_REF from pinned @openhands/extensions SHA in package.json (#1060)
* feat: default EXTENSIONS_REF from pinned @openhands/extensions SHA in package.json

The agent-server polls the OpenHands extensions repo using EXTENSIONS_REF
(defaulting to 'main'), while the frontend bundles the MCP catalog and
automations data from a pinned commit SHA in package.json. This caused the
two to silently diverge — skills loaded at runtime were from latest main
while the UI showed catalog data from an older commit.

Fix: derive a DEFAULT_EXTENSIONS_REF from the '#<sha>' fragment in the
@openhands/extensions git URL in package.json and inject it as the agent-
server's EXTENSIONS_REF when the caller hasn't set it explicitly.

- scripts/dev-safe.mjs: getExtensionsRef() helper reads package.json at
  module init; buildAgentServerEnv() spreads EXTENSIONS_REF into the
  child-process env unless the caller already set it.

- docker/Dockerfile (config-gen stage): reads package.json alongside
  config/defaults.json and emits CONFIG_EXTENSIONS_REF=<sha> into
  defaults.env when the dependency uses a pinned git SHA.

- docker/entrypoint.sh: applies CONFIG_EXTENSIONS_REF as the default via
  EXTENSIONS_REF="${EXTENSIONS_REF:-${CONFIG_EXTENSIONS_REF:-}}".

User-supplied EXTENSIONS_REF always takes precedence in both paths.

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

* fix: address review bot comments on EXTENSIONS_REF sync

- entrypoint.sh: guard EXTENSIONS_REF export so an absent CONFIG_EXTENSIONS_REF
  never sets the variable to an empty string (which would defeat the
  agent-server's own 'main' default in os.environ.get('EXTENSIONS_REF','main'))
- scripts/dev-safe.mjs: getExtensionsRef() now checks devDependencies as
  fallback when @openhands/extensions is not in dependencies
- docker/Dockerfile config-gen stage: same devDependencies fallback
- AGENTS.md: replace internal constant name DEFAULT_EXTENSIONS_REF with the
  actual env var EXTENSIONS_REF; also note the empty-string guard

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

* fix: pre-seed extensions cache when EXTENSIONS_REF is a commit SHA

The SDK's git clone --depth 1 --branch <sha> fails because --branch only
accepts branch/tag names, not raw 40-char commit SHAs (GitHub returns
'fatal: Remote branch <sha> not found').

When EXTENSIONS_REF is a commit SHA, pre-seed the public-skills cache with
a plain full git clone + checkout before starting the agent-server:

- docker/entrypoint.sh: pre-seeds after EXTENSIONS_REF is exported; is a
  no-op when the cache already exists or EXTENSIONS_REF is a branch/tag name
- scripts/dev-safe.mjs: exports preseedExtensionsCache() and
  DEFAULT_EXTENSIONS_REF; calls pre-seed in main() before spawning the
  agent-server; guards with the same SHA regex as entrypoint.sh
- scripts/dev-with-automation.mjs: imports preseedExtensionsCache and
  DEFAULT_EXTENSIONS_REF from dev-safe.mjs; calls pre-seed in
  startAgentServer() before spawnService()

The SDK's update path (fetch + git checkout) can handle SHAs once the
repo exists, so this is sufficient without any SDK changes.

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

* refactor(docker): bake extensions clone into image, cp at runtime

Move the git clone for the pinned extensions SHA from the entrypoint
into a dedicated Dockerfile build stage (extensions-cache).

Why: the clone is deterministic (same SHA for a given image), so doing
it once at build time is cheaper than doing it on first container start.
It also avoids network access at runtime entirely for Docker users.

How:
- New extensions-cache stage (node:24-slim + git): reads package.json,
  clones OpenHands/extensions, checks out the pinned SHA into
  /tmp/public-skills. When the dependency is not SHA-pinned the stage
  exits early with an empty directory (graceful no-op).
- Final stage: COPY --from=extensions-cache stores the result at
  /opt/agent-canvas/extensions-cache/ — outside the
  /home/openhands/.openhands VOLUME so it is not hidden by bind-mounts.
  chown passes ownership to openhands.
- entrypoint.sh: replaces git clone + checkout with cp -r from the
  pre-baked path. The guard checks [ -d "${_ext_baked}/.git" ] so
  the block is a no-op when the stage produced an empty directory.

The npm-dev preseedExtensionsCache() path in dev-safThe npm-dev preseedExtensionsCache() path in d — that still clones over the
network on first run (build-time baking does not apply to npm dev).

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

* fix(extensions-preseed): handle existing shallow clone missing the pinned SHA

When `npm run dev` has been run previously without the PR, the agent-server
SDK creates a shallow clone of the extensions repo via:
  git clone --depth 1 --branch main

After PR #1060 is applied, `EXTENSIONS_REF` is set to the 40-char pinned
SHA. The SDK then tries `git checkout <sha>` against that shallow clone,
which fails because the specific commit is not in its shallow history.

`preseedExtensionsCache` had a blind early-return when `.git` already
existed, trusting the "SDK update path" to handle it. But the SDK's update
path also does a plain `git checkout <sha>` and cannot succeed on a shallow
clone that lacks the commit.

Fix: before returning early, verify the SHA is accessible with
  git cat-file -t <sha>
If it returns anything other than "commit", the cache is a shallow clone
that pre-dates the pinned commit. Unshallow the existing clone with
  git fetch --unshallow
so the full history is available, then checkout the SHA.

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

* refactor(extensions): copy from node_modules on npm, always overwrite cache

Implement the intended architecture for extensions cache seeding:

npm path (dev-safe.mjs / dev-with-automation.mjs):
- Replace preseedExtensionsCache (git clone approach) with
  copyExtensionsToSkillsCache, which copies the already-installed
  node_modules/@openhands/extensions directly into the skills cache.
  No network call required — npm already installed the package at
  the pinned SHA.
- Always overwrite the cache (rm -rf + cpSync) so stale files from a
  prior run that used a different version are never left behind.
- Set EXTENSIONS_REF unconditionally in buildAgentServerEnv so it
  always matches the pre-seeded cache content.

Docker path (docker/entrypoint.sh):
- Always copy /opt/agent-canvas/extensions-cache into the skills cache
  (rm -rf + cp -r), removing the prior guard that skipped the copy when
  .git already existed.
- Set EXTENSIONS_REF unconditionally from CONFIG_EXTENSIONS_REF.

Both paths accept that the SDK will warn 'Using cached version' for a
raw commit SHA — that warning is the expected fallback until SDK polling
is disabled in a follow-up PR.

Note: the npm package only publishes integrations/ and automations/.
Docker's baked full clone also includes marketplaces/, plugins/, skills/.

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

* fix(extensions): delete stale cache on npm startup instead of copying files

The copy-from-node_modules approach was still broken: the npm package has no
.git directory, so the SDK tried a fresh 'git clone --depth 1 --branch <sha>'
on top of the already-populated directory and got confused.

Simpler fix: just delete ~/.openhands/cache/skills/public-skills on startup.
The SDK starts with a clean slate and attempts its normal clone; for a raw SHA
ref it will warn 'Using cached version' which is the accepted fallback until
SDK polling is disabled in a follow-up PR.

- Replace copyExtensionsToSkillsCache (copy from node_modules) with
  clearExtensionsCache (delete the directory)
- Remove now-unused cpSync import
- Docker path unchanged (baked clone always copied by entrypoint.sh)

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

* fix(extensions): always delete cache; only set EXTENSIONS_REF if not in env

Three simple rules:
1. Always delete ~/.openhands/cache/skills/public-skills on startup so the
   SDK clones fresh — no conditional on DEFAULT_EXTENSIONS_REF.
2. Only inject EXTENSIONS_REF into the agent-server env when the caller has
   not already set it (restore the !process.env.EXTENSIONS_REF guard).
3. Let the agent-server SDK do its own cloning from there.

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

* feat(extensions): use OH_PUBLIC_SKILLS_PATH to bypass git polling

Switch from EXTENSIONS_REF (which still went through the SDK's broken
git clone --branch <sha> path) to OH_PUBLIC_SKILLS_PATH, introduced in
software-agent-sdk PR #3513. When set, the SDK skips all git operations
and loads public skills directly from the given directory.

npm path (dev-safe.mjs / dev-with-automation.mjs):
- Add cloneExtensionsForSkillsCache(sha, cacheDir): does a targeted
  git fetch --depth=1 origin <sha> into ~/.openhands/cache/skills/public-skills.
  Reuses the existing directory when it already contains the right commit.
- buildAgentServerEnv now sets OH_PUBLIC_SKILLS_PATH (not EXTENSIONS_REF)
  pointing at that directory when DEFAULT_EXTENSIONS_REF is known and
  OH_PUBLIC_SKILLS_PATH is not already in the environment.

Docker path (docker/entrypoint.sh):
- Removes EXTENSIONS_REF export entirely.
- After copying the baked clone to the skills cache, exports
  OH_PUBLIC_SKILLS_PATH pointing at that directory.

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

* fix(dev): include openhands-sdk in git-ref uvx args

When OH_AGENT_SERVER_GIT_REF is set, openhands-sdk was missing from the
--with args, so uv fell back to the released PyPI version. That version
lacks the public_skills_path parameter added to update_skills_repository
in SDK PR #3513, so the agent-server's skills_service called the old SDK
path and did a plain git clone of 'main' — overwriting the SHA-pinned
clone our cloneExtensionsForSkillsCache had just created.

Fix: add --with git+<repo>@<ref>#subdirectory=openhands-sdk alongside the
existing openhands-tools and openhands-workspace entries, matching what
the PyPI and local paths already do for openhands-sdk.

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

* fix(dev): add --reinstall to git-ref uvx command

When OH_AGENT_SERVER_GIT_REF points at a branch whose version string
matches the current PyPI release (e.g. both are 1.25.0), uv silently
reuses the cached PyPI wheels and the git ref is never used.

--reinstall forces uv to build a fresh environment from the specified
git sources regardless of what is already cached. The git clone itself
is still cached in ~/.cache/uv/git-v0/, so only the first run after a
ref change pays the full network cost.

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

* fix: rename OH_PUBLIC_SKILLS_PATH to PUBLIC_SKILLS_PATH

Aligns with the env var name used by the SDK PR.

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

* test: update dev-safe buildAgentServerCommand tests for --reinstall and openhands-sdk

The git-ref path now adds --reinstall (so uv doesn't silently reuse cached
PyPI wheels) and includes openhands-sdk as a --with package so inter-package
APIs stay in sync. Update the two affected toEqual assertions to match.

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

* refactor: use EXTENSIONS_REF instead of PUBLIC_SKILLS_PATH for extensions pinning

The PUBLIC_SKILLS_PATH / cloneExtensionsForSkillsCache approach was considered
but not chosen. EXTENSIONS_REF is the variable the agent-server SDK uses, and
it now skips network polling when the requested SHA is already in its cache.

- scripts/dev-safe.mjs: replace PUBLIC_SKILLS_PATH spread in buildAgentServerEnv
  with a simple EXTENSIONS_REF injection; remove cloneExtensionsForSkillsCache
  function, EXTENSIONS_REPO const, and the main() clone call; remove the
  spawnSync and rmSync imports that were only needed for cloning
- scripts/dev-with-automation.mjs: remove cloneExtensionsForSkillsCache import
  and the clone call from startAgentServer()
- docker/Dockerfile: remove the extensions-cache build stage entirely; keep the
  CONFIG_EXTENSIONS_REF line in config-gen (still needed for entrypoint.sh)
- docker/entrypoint.sh: replace the cp-based seeding block with a single
  export EXTENSIONS_REF="${EXTENSIONS_REF:-${CONFIG_EXTENSIONS_REF:-}}"
- AGENTS.md: replace inaccurate OH_PUBLIC_SKILLS_PATH bullet with an accurate
  description of the EXTENSIONS_REF flow

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-05 14:30:45 +00:00
7dbe8fd7de fix: populate <RUNTIME_SERVICES> in static builds so automations work (#1125)
* fix: populate <RUNTIME_SERVICES> in static builds so automations work

The agent's <RUNTIME_SERVICES> system-prompt block is built from
VITE_RUNTIME_SERVICES_INFO, which the dev launchers set at build time.
Static builds (the Docker image and the published binary) run
`npm run build` without it, so the block is dropped and the agent does
not know how to reach the local automation backend — it falls back to
the cloud API (app.all-hands.dev) and automation creation fails.

Inject the info at serve time, mirroring the existing --session-api-key
path:

- scripts/static-server.mjs: new --runtime-services-info flag injects
  the JSON as window.__AGENT_CANVAS_RUNTIME_SERVICES_INFO__
- src/api/agent-server-adapter.ts: parseRuntimeServicesInfo() falls back
  to that window global when the env var is empty
- docker/entrypoint.sh: builds the JSON from the sandbox-facing service
  URLs (AGENT_SERVER_URL, AUTOMATION_BASE_URL) and passes it to the
  static server(s)

Refs #1098

* refactor: extract runtime-services-info into a shared module

Replace the inline JSON building in docker/entrypoint.sh with a single
source of truth for the <RUNTIME_SERVICES> shape.

buildRuntimeServicesInfo now lives in scripts/runtime-services-info.mjs
(moved out of dev-safe.mjs, which re-exports it for back-compat) and
gains:
- optional full-URL overrides (agentServerUrl, automation.url) so the
  container can pass its runtime-resolved AGENT_SERVER_URL /
  AUTOMATION_BASE_URL and use 127.0.0.1 (avoiding IPv6 loopback) instead
  of build-time ports, and
- a CLI entrypoint so docker/entrypoint.sh emits the JSON by running the
  same builder the dev stack uses, rather than a hand-rolled node -e blob.

The Dockerfile ships the new dependency-free module into the image.

* fix: pass --runtime-services-info in static mode and verify in automation e2e

- startStaticFrontend() in dev-with-automation.mjs now builds the
  runtime-services info JSON via buildAutomationRuntimeServicesInfo()
  and passes it to static-server.mjs via --runtime-services-info.
  Without this, the npm binary / dev:static path served pre-built
  frontends that never populated the agent's <RUNTIME_SERVICES>
  system-prompt block (the Docker entrypoint already did this).

- The mock-LLM automation e2e test now verifies that the
  <RUNTIME_SERVICES> block is present in the system messages sent
  to the LLM, and that it includes the expected service entries
  (Agent Server, Automation backend, /api/automation).

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

* docs: update AGENTS.md with runtime-services-info plumbing changes

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

---------

Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com>
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-04 18:28:35 +00:00
Rohit Malhotraandopenhands f2d19c8923 Add GitHub bug report issue template (#813)
* Add GitHub bug report issue template

- Bug report form with install method dropdown (npm, Docker, source, other)
  and version dropdown listing all pre-release versions (alpha.2–alpha.6)
- Includes optional agent-server version, environment, logs/screenshots fields

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

* Remove agent server version field from bug report template

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

* Remove environment field from bug report template

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

* Add OS dropdown to bug report template

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

* Address review feedback on bug report template

- Convert version dropdown to free-text input to avoid maintenance burden
- Fix docker inspect description to include image reference
- Add Actual Behavior field between Steps to Reproduce and Expected Behavior
- Split Logs/Screenshots into separate fields so render:shell doesn't break images

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

* Add --version flag to CLI and version label to Docker image

- bin/agent-canvas.mjs: add -v/--version flag that reads version from package.json
- docker/Dockerfile: add AGENT_CANVAS_VERSION build arg and org.opencontainers.image.version label
- .github/workflows/docker.yml: extract version from package.json, pass as build arg
- bug_report.yml: update version field description with the actual commands users can run

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

* Update version description: use image tag for Docker (label not yet released)

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-27 12:17:35 -04:00
Rohit Malhotraandopenhands db976f2e66 fix: use PostHog prod creds only for tagged Docker releases (#666)
* fix: set VITE_APP_ENV=production in Docker build for PostHog prod creds

The Docker frontend build stage was not setting VITE_APP_ENV, so all
Docker images (including tagged releases) used the PostHog staging key.
This adds ENV VITE_APP_ENV=production to the frontend-build stage,
matching the build:lib npm path behavior.

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

* fix: use PostHog prod creds only for tagged Docker releases

Make VITE_APP_ENV a Dockerfile build arg (default empty = staging key).
The CI workflow passes VITE_APP_ENV=production only when building from
a tagged release (refs/tags/v*), so PR and main-branch images keep the
staging key while release images get the production key.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-20 14:55:56 +00:00
Rohit Malhotraandopenhands 979e64fe19 feat: add Docker CI to build all-in-one image with agent-server + automation + frontend (#634)
* feat: add Docker CI to build all-in-one image with agent-server + automation + frontend

Adds a GitHub Actions workflow (.github/workflows/docker.yml) that builds and
publishes ghcr.io/openhands/agent-canvas — a single Docker image combining:

  1. Agent Server (ghcr.io/openhands/agent-server base image from SDK repo)
  2. Automation server (pip-installed from openhands-automation)
  3. agent-canvas frontend (static build from this repo)

The automation server is pip-installed rather than copied from its Docker image
because both services share openhands-sdk, fastapi, uvicorn, pydantic, httpx
etc. — installing into the agent-server's Python 3.13 deduplicates all shared
packages. Only automation-specific deps (asyncpg, sqlalchemy, boto3, …) are
added on top.

An entrypoint script starts all three services and a static-server proxy that
unifies them behind a single port (default 8000):
  /api/automation/* → automation backend (:18001)
  /api/*            → agent-server (:18000)
  /*                → static frontend + SPA fallback

Workflow triggers:
  - Push to main: builds and pushes with branch + SHA tags
  - v* tags (releases): also pushes semver tags (1.2.3, 1.2, 1, latest)
  - PRs: builds, pushes SHA-tagged image, updates PR description with
    pull/run instructions (same pattern as the SDK repo)
  - workflow_dispatch: supports overriding base image and automation version

Files added:
  - docker/Dockerfile (multi-stage: frontend build + agent-server base)
  - docker/entrypoint.sh (process manager for all three services)
  - .dockerignore
  - .github/workflows/docker.yml

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

* fix: build multi-arch Docker images (amd64 + arm64)

Adds QEMU setup for cross-compilation and defaults the platform matrix
to linux/amd64,linux/arm64 so the image works on both Intel and Apple
Silicon machines.

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

* refactor: rewrite Docker workflow to match SDK repo structure

Replace the single-job QEMU approach with the same architecture-matrix
pattern used by the SDK repo's server.yml:

  1. build-and-push-image — matrix over {amd64, arm64} with native runners
     (ubuntu-24.04 for amd64, ubuntu-24.04-arm for arm64). Each job pushes
     arch-suffixed tags (e.g. sha-abc1234-amd64) and uploads build-info
     artifacts.

  2. merge-manifests — downloads both arch build-infos, strips the -amd64
     suffix from amd64 tags to derive manifest tags, and creates multi-arch
     manifests via `docker buildx imagetools create`.

  3. consolidate-build-info — aggregates all build-info and manifest-info
     artifacts into a single JSON summary (PR-only).

  4. update-pr-description — renders the summary into the PR body between
     AGENT_CANVAS_DOCKER_START/END markers.

Native runners avoid the 3-5× slowdown of QEMU emulation for arm64
builds.

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

* fix: sanitize branch names in Docker tags (/ is not allowed)

Branch names like 'feat/docker-ci' produce invalid Docker tags because
'/' is forbidden in tag names. Replace '/' with '-' so the tag becomes
'feat-docker-ci-amd64'.

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

* fix: default automation to SQLite and fix wait blocking proxy startup

Two bugs:

1. The automation server defaults to PostgreSQL on localhost, which
   doesn't exist in the all-in-one container. Default AUTOMATION_DB_URL
   to sqlite+aiosqlite:// so it works out of the box. Users can override
   with a real Postgres URL for production.

2. The bare 'wait' command waited for ALL background children — including
   the long-running agent-server and automation processes — so the
   static-server/proxy on port 8000 never started. Fix by waiting only
   for the wait_for_port subshell PIDs.

Verified locally: all three services start, endpoints respond correctly,
no more scheduler ConnectionRefusedError.

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

* feat: add VOLUME directives for persistence and project mounts

Declare /home/openhands/.openhands (settings, secrets, conversations,
automation SQLite DB) and /projects (user code) as Docker volumes so
data survives container restarts by default. Users should bind-mount
these for durable persistence:

  docker run -v ~/.openhands:/home/openhands/.openhands \
             -v ~/projects:/projects \
             -p 8000:8000 ghcr.io/openhands/agent-canvas

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

* fix: set OH_SECRET_KEY default and pre-create persistence dirs

Three issues fixed:

1. OH_SECRET_KEY was not set → agent-server refused to return encrypted
   secrets → conversation creation failed with 503. Set the same static
   default used by dev-safe.mjs / dev-docker.mjs.

2. Persistence dirs (conversations, bash_events, automation DB) were not
   pre-created → the openhands user got PermissionError when the VOLUME
   directive created them as root. Pre-create with correct ownership
   before the USER switch in the Dockerfile.

3. Set OH_PERSISTENCE_DIR, OH_CONVERSATIONS_PATH, OH_BASH_EVENTS_DIR
   defaults in the entrypoint (matching dev-docker.mjs) so data lands
   under the well-known ~/.openhands tree.

Verified locally: all three services start clean, no warnings about
OH_SECRET_KEY, SQLite migrations apply successfully.

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

* chore: merge main and remove stale dev-docker.mjs references

Main removed scripts/dev-docker.mjs (Docker is no longer a dependency of
the npm package flow). Update comments in docker.yml, entrypoint.sh, and
AGENTS.md that referenced the deleted file.

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

* feat: centralize config into config/defaults.json (single source of truth)

All version pins, port defaults, persistence paths, package names, and
the dev secret key now live in config/defaults.json. Consumers read from
it instead of hardcoding values:

- scripts/dev-safe.mjs: reads via JSON.parse(readFileSync(...))
- scripts/dev-with-automation.mjs: same
- scripts/check-sdk-version-sync.mjs: same (no longer regex-parses JS)
- docker/Dockerfile: config-gen build stage converts JSON to
  /opt/agent-canvas/defaults.env (shell-sourceable)
- docker/entrypoint.sh: sources defaults.env at startup; also adds
  session API key auto-generation so the image doesn't run wide-open
- .github/workflows/docker.yml: reads versions from JSON in a setup
  step (no more hardcoded env vars)

To bump a version, edit config/defaults.json only.

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

* fix: address PR review feedback (#634)

- Fix PID tracking bug: move PIDS+=($!) inside if/elif branches so the
  else (automation-not-found) path doesn't add a stale PID
- chmod 600 session API key file to prevent credential leak
- Warn when using insecure default OH_SECRET_KEY in Docker entrypoint
- Add try/catch + field validation for config/defaults.json loading in
  check-sdk-version-sync.mjs
- Fix semver tag parsing: strip pre-release/build metadata, only create
  abbreviated tags (major.minor, major, latest) for stable releases
- Sanitize branch names for Docker tags (tr invalid chars, strip leading
  dot/dash) to handle branches with #, @, spaces, etc.
- Add arch validation before manifest merge (assert both amd64.json and
  arm64.json exist)
- Remove $schema reference to non-existent defaults.schema.json

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

* fix: remove hardcoded version defaults from Dockerfile

Replace hardcoded ARG defaults (AGENT_SERVER_IMAGE, AUTOMATION_VERSION)
with empty ARGs. Values are always derived from config/defaults.json:
- CI: reads JSON in the workflow config step, passes --build-arg
- Local: new scripts/docker-build.mjs helper reads JSON and invokes
  docker build with the correct --build-arg values

Added npm run build:docker convenience script.

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

* fix: stabilize snapshot tests and auto-generate Docker secret key

Two fixes:

1. **Flaky snapshot tests**: The 'Local pagination fixture' mock conversation
   used a fixed absolute timestamp (PAGINATION_BASE_TIME = May 13, 2026) for
   its created_at/updated_at, while 'Errored Project' used a relative
   timestamp (now - 7d). As real time progressed past the crossover point,
   their sort order in the sidebar flipped, causing 30/73 snapshot diffs on
   every PR. Fix: use relative timestamps (now - 6d) for the pagination
   fixture's conversation listing fields. The internal event timestamps
   (used by pagination tests) still use PAGINATION_BASE_TIME — only the
   sidebar ordering is affected.

2. **Docker OH_SECRET_KEY**: The entrypoint used a static insecure default
   for OH_SECRET_KEY and warned about it. Now mirrors the session API key
   pattern: auto-generate a cryptographic random key on first run, persist
   it to ~/.openhands/agent-canvas/secret-key.txt, and reuse on restart.
   Users can still override via the OH_SECRET_KEY env var. Removed the
   now-unused CONFIG_SECRET_KEY from the Docker defaults.env generation.
   Also deduped STATE_DIR computation (was repeated for session key path).

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

* docs: update AGENTS.md with mock timestamp and Docker secret key notes

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

* fix: include canvas_ui tool in Docker image

The Docker image was missing the tools/ directory and OH_EXTRA_PYTHON_PATH,
so the agent-server couldn't import canvas_ui_tool.py when the frontend
sent canvas_ui in the conversation tools list. This caused:

  HTTP 500: ToolDefinition 'canvas_ui' is not registered

Fix: COPY tools/ into the image and set OH_EXTRA_PYTHON_PATH in the
entrypoint, matching what scripts/dev-safe.mjs already does for local dev.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-20 04:24:45 +00:00