Commit Graph
100 Commits
Author SHA1 Message Date
e8e6f45cc8 fix: disable chat input drag-resize when not bottom-anchored (OHE-3062) (#16479)
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com>
Co-authored-by: hieptl <hieptl.developer@gmail.com>
2026-08-10 14:18:04 +00:00
21d5e716b2 fix(metrics): fetch runtime conversation directly instead of removed cloud-proxy (#16392)
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com>
2026-08-07 16:43:19 +00:00
Tim O'Farrellandopenhands e42220d0e2 chore: Suppress verbose Laminar info logs (#15374)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-23 14:40:35 +00:00
Tim O'Farrellandopenhands ce87346b7e fix: upgraded instances with no superadmin (#15349)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-22 06:16:42 -06:00
Tim O'Farrellandopenhands eba332c134 fix(jira): allow conversations without repositories (#15328)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-21 02:00:48 -06:00
Tim O'Farrellandopenhands eca5075dc4 fix: Debounce last_used_at writes in ApiKeyStore.validate_api_key (#15331)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-21 01:28:53 -06:00
Tim O'Farrellandopenhands 7db8bd4d92 refactor: Expand better-error-logging patterns across the codebase (#15241)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-20 03:37:01 -06:00
Tim O'Farrellandopenhands 73b72a223d feat(enterprise): make BYOR key alias pattern configurable (#15232)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-10 13:09:36 +00:00
Tim O'Farrellandopenhands d1563c9526 fix: Enable LIFO database connection pooling (#15225)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-09 22:18:48 -06:00
Tim O'Farrellandopenhands b348a900e1 fix(enterprise): cascade-delete conversation_cost_events on conversation delete (#15220)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-10 04:03:07 +00:00
Tim O'Farrellandopenhands ecb9b4ad36 fix: prevent webhook-driven DB connection leaks (#15212)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-09 12:05:46 -06:00
Tim O'Farrellandopenhands 20a721e23b fix(app_server): derive agent server image from package version (#15168)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-07 15:26:02 -06:00
Tim O'Farrellandopenhands 2dc45f5e0d fix(frontend): read SDK 1.31.x flat mcp_config wire format (#15165)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-07 10:24:39 -06:00
Tim O'Farrellandopenhands d009e69bff fix(app_server): pass flat mcp_config shape to SDK Agent (#15159)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-07 09:18:47 -06:00
Tim O'Farrellandopenhands d626f5285c fix(sandbox-spec): fall back to defaults when runtime-api has no warm runtimes (#15141)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-06 14:47:12 +00:00
Tim O'Farrellandopenhands d4059af45e feat(saas-auth): accept api_key cookie as a fallback credential (#15101)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-02 19:08:34 +00:00
Tim O'Farrellandopenhands 387d2b86b6 feat(api-keys): make 'unbound' org scope an explicit, first-class option (#15096)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-02 10:52:47 -06:00
Tim O'Farrellandopenhands 72db3fdd80 fix: Timezones stored in the db do not have a timezone (#15092)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-01 15:50:19 -06:00
Tim O'Farrellandopenhands 66941d2ad2 feat(api-keys): add optional active window (not_before & expires_at) (#15085)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-07-01 12:03:41 -06:00
Tim O'Farrellandopenhands 8bb5623655 test: drop should-not-exist assertions on removed code (APP-2570) (#1554)
Removes three negative-existence test assertions that have decayed into
no-ops because the testids they target no longer exist in source. Each
was added in the same PR that deleted the related component (or, for
login-cta, in the OSS-strip PR) and was useful as a regression guard at
the time. Once the deletion is several weeks old, an assertion of the
form `expect(queryByTestId("deleted-testid")).not.toBeInTheDocument()`
is always trivially true and only adds maintenance noise.

Follow-up to PR #1545 review feedback (tofarr): 'A test that makes sure
that something which was deleted six months ago does not exist. We
should probably have an agent review our tests for cases like this and
remove them.' See APP-2570 for the audit inventory and follow-ups.

* __tests__/routes/device-verify.test.tsx — drop the entire
  'keeps the device verification view OSS-only without login CTA chrome'
  test. The testid 'login-cta' has no source counterpart; the assertion
  was always trivially true. Test was added in #17 (the OSS-strip PR).

* __tests__/routes/agent-settings.test.tsx — drop the orphan
  queryByTestId('acp-credentials-save-button') assertion (and the
  obsolete explanatory comment) from the 'a single Save persists ACP
  credentials' test. The testid has no source counterpart; the rest of
  the test (the single Save flow) is unaffected.

* __tests__/components/features/conversation-panel/conversation-card.test.tsx
  — drop the entire 'should not render the llm model in the conversation
  card' test. The testid 'conversation-card-llm-model' was removed in
  6e545c54 ('Move LLM model name from conversation lists/title to chat
  input'); the inverted test added at the same time is now a no-op.

All three test files still pass. typecheck clean. No new lint issues.
The PR's own new analytics-consent-modal assertion is intentionally kept
— it is load-bearing for the release that removed the modal and matches
the same pattern that PR #1251 used for acp-credentials-save-button
(now itself a candidate for a future cleanup pass; APP-2570 tracks).

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-30 12:34:35 -06:00
Tim O'Farrellandopenhands 94ddd67631 fix(device-verify): rename 'Workspace' dropdown to 'Organization' (#15057)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-30 10:56:45 -06:00
Tim O'Farrellandopenhands cfa2ba5796 fix(backends): hide edit/remove row actions when locked to cloud (#1555)
The Manage Backends modal previously rendered pencil and trash icons on
every backend row even when the deployment was started with
--lock-to-cloud. In locked mode the user should not be able to mutate
the locked backend's host/key/name or remove it outright.

backend-row.tsx now consults getLockedCloudHost() and skips rendering
both action buttons when a locked cloud host is configured. The row
identity (name + host) still renders so the user can see which backend
is locked.

Adds three focused tests to describe("BackendRow", ...):

  * renders edit and remove buttons when not locked to a cloud host
  * hides edit and remove buttons when locked via VITE_LOCK_TO_CLOUD
  * hides edit and remove buttons when locked via the
    window.__AGENT_CANVAS_LOCK_TO_CLOUD__ runtime global

Also extends afterEach with vi.unstubAllEnvs + window-global cleanup
(consistent with backend-form-modal.test.tsx) and isolates three
pre-existing edit-form tests from a local .env that sets
VITE_LOCK_TO_CLOUD, so they keep exercising edit-form behavior rather
than lock behavior.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-30 09:57:46 -06:00
Tim O'Farrellandopenhands 4b5847fefe feat(device-verify): align warning, button color, and add workspace dropdown (#15031)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-30 08:49:30 -06:00
Tim O'Farrellandopenhands 0571ff530f fix(enterprise): add alembic migration for execution_status column (#15030)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-29 21:02:13 +00:00
Tim O'Farrellandopenhands 72140ff39c feat: add default_sandbox_spec_id to user settings (#14985)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-25 11:44:33 -06:00
Tim O'Farrellandopenhands 3ed5b5c806 fix: handle None git_full_clone from DB in SaasSettingsStore (#14988)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-25 17:15:28 +00:00
Tim O'Farrellandopenhands c7a00b14ab fix: seed AUTOMATION_KV_SECRET for local dev stack (#1414)
* fix: seed AUTOMATION_KV_SECRET for local dev

The KV store (automation PR#69) requires AUTOMATION_KV_SECRET to be set
or every KV endpoint returns 503. In a local dev stack the secret is never
configured, so the store is silently unavailable.

Fall back to sessionApiKey when the env var is not set explicitly — the
same zero-config pattern already used for AUTOMATION_AGENT_SERVER_URL,
AUTOMATION_BASE_URL, and AUTOMATION_WORKSPACE_BASE.

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

* chore: bump openhands-automation to 1.0.0a10

Update to the latest released version of openhands-automation on PyPI.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-17 14:31:56 -06:00
Tim O'Farrellandopenhands b914ad5d40 Increase secrets table max-height to show up to 12 rows (#1406)
* Increase secrets table max-height to show up to 12 rows

Raises the scrollable container cap from min(40vh,22rem) to
min(70vh,39rem). At 48px per row (h-12) + 48px sticky header,
39rem (624px) fits exactly 12 data rows before the scrollbar
appears. The max-h constraint means the container still
shrinks naturally when there are fewer than 12 rows.

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

* Remove scroll cap and sticky header from secrets table

The secrets table had a max-h constraint (overflow-auto) that triggered
a scrollbar after 12 rows, plus a sticky thead to compensate. Both were
specific to this file. Since the server API returns all secrets in one
response and the columns are simple (name, description, actions), a
plain full-height table is clearer.

- Replace settingsListScrollContainerClassName with
  settingsListContainerClassName on the outer div
- Inline border-b on <thead> instead of the sticky-specific class
- Remove onScroll handler, tableContainerRef, handleScroll callback,
  and isFetchingNextPage spinner (all scroll-only code)

The shared scroll/sticky classes in settings-list-classes.ts are
untouched for use by other tables.

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

* Revert secrets-settings.tsx and settings-list-classes.ts to main

* Increase secrets table max-height to show up to 12 rows

Raises the scrollable container cap from min(40vh,22rem) to
min(70vh,39rem). At 48px per row (h-12) + 48px sticky header,
39rem (624px) fits exactly 12 data rows before the scrollbar
appears. The max-h constraint means the container still
shrinks naturally when there are fewer than 12 rows.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-17 08:53:24 -06:00
Tim O'Farrellandopenhands 56034afe10 feat: add DynamicRemoteSandboxSpecService backed by runtime-api warm configs (#14849)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-16 15:27:55 -06:00
Tim O'Farrellandopenhands be6f44684d fix: renumber duplicate migration 122 → 123 (drop is_paused) (#14840)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-16 08:06:51 -06:00
Tim O'Farrellandopenhands 4a6c74dd2b fix: use runtime /list API for sandbox concurrency count, not is_paused DB flag (#14834)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-16 12:24:44 +00:00
Tim O'Farrellandopenhands e1c9e6ab33 chore: remove redundant automationSdk version — derive from agentServer (#1333)
The automationSdk version was always intended to equal agentServer.
Having a separate field creates a maintenance foothole where the two
values can silently drift. Remove automationSdk from defaults.json and
have all consumers (check-sdk-version-sync, dev-with-automation,
agent-canvas CLI --version output) read versions.agentServer directly.
The sync check still catches any mismatch between the released
openhands-automation package and the expected SDK version.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-15 16:18:15 +00:00
Tim O'Farrellandopenhands d20aa65228 fix: show full workspace list when dropdown reopens after selection (#1325)
* fix: show full workspace list when dropdown reopens after selection

When a workspace was already selected, opening the dropdown again would
filter the list to just the one matching item (because inputValue was set
to the selected name). Add onIsOpenChange to useCombobox so the input
clears on open (showing all options) and restores on close. When open,
use the current selection as the input placeholder so the user still has
a visual reminder of what they've chosen.

This is the same pattern already used in src/ui/dropdown/dropdown.tsx.

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

* Lint fixes

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-12 07:29:15 -06:00
Tim O'Farrellandopenhands 771acb24a5 chore: bump @openhands/extensions from 0.4.1 to 0.4.2 (#1323)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-12 02:01:06 +00:00
Tim O'Farrellandopenhands 910b19ae76 chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1319)
* chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1

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

* Test fixes

* fix: inject proxy base_url for litellm_proxy/* when server omits it (agent-server ≥1.28)

Agent-server ≥1.28 may return base_url:null when fetching a litellm_proxy/*
profile config, even when the profile was saved with the All-Hands proxy URL.
This caused the Basic-tab re-save flow in LlmSettingsLocalView.handleSave to
call isOpenHandsProxyModel(model, null) → false, hitting the else-branch that
deletes base_url and stranding the profile (issue #1146).

Fix: add a secondary check — litellm_proxy/* with a missing base_url is treated
the same as litellm_proxy/* with the proxy URL already set, and
OPENHANDS_LLM_PROXY_BASE_URL is injected before the save request is sent.

Also updates the mock-LLM E2E test to accept both storage representations:
- litellm_proxy/* + proxyBaseUrl  (pre-1.28, guards issue #1146 regression)
- openhands/*     + null          (1.28+, server-managed routing)

And adds a unit test exercising the base_url:null path.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-11 19:16:03 -04:00
Tim O'Farrellandopenhands 8071edf72a Revert "chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1315)" (#1318)
This reverts commit 1917b5d39fbf09dc51213b4b484698fe394314c7.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-11 21:28:04 +00:00
Tim O'Farrellandopenhands 15a52fea75 chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1 (#1315)
* chore: bump agent-server → 1.28.1, automation → 1.0.0a9, extensions → 0.4.1

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

* chore: update doc examples to reference agent-server 1.28.1

Update version references in AGENTS.md, scripts/dev-safe.mjs, and
scripts/check-sdk-version-sync.mjs from 1.27.0 → 1.28.1 to stay
in sync with the agentServer pin in config/defaults.json.

Fixes: docs-version-sync.test.ts failures

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

* fix: inject proxy base_url for litellm_proxy/* when server omits it (agent-server ≥1.28)

Agent-server ≥1.28 may return base_url:null when fetching a litellm_proxy/*
profile config, even when the profile was saved with the All-Hands proxy URL.
This caused the Basic-tab re-save flow in LlmSettingsLocalView.handleSave to
call isOpenHandsProxyModel(model, '') → false, hitting the else-branch that
deletes base_url and stranding the profile (issue #1146).

Fix: add a secondary check for litellm_proxy/* models with a missing base_url
(null/undefined/empty), treating them the same as a stored proxy URL and
injecting OPENHANDS_LLM_PROXY_BASE_URL before the save request is sent.

Also adds a unit test exercising the base_url:null path.

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

* test(e2e): accept agent-server 1.28 model rewrite in proxy profile test

Agent-server 1.28 normalises litellm_proxy/* → openhands/* on storage
and manages the proxy URL internally (returning base_url:null). The old
assertions hard-coded the pre-1.28 storage format (litellm_proxy/* +
explicit proxy URL), causing the test to fail on every 1.28 run.

Extract assertProxyProfileConfig() helper that accepts both storage
representations:
- litellm_proxy/* + proxyBaseUrl   (pre-1.28, guards issue #1146 regression)
- openhands/*     + null           (1.28+, server-managed routing)

The issue #1146 guard is preserved: a litellm_proxy/* profile without a
proxy URL is still flagged as a stranded profile.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-11 21:00:05 +00:00
Tim O'Farrellandopenhands bee6cee418 fix: set bundled skill source to real filesystem path (#1310)
* fix: set bundled skill source to real filesystem path

Bundled public skills were sent to the Python agent-server with
`source: "public"`, which the SDK uses as the skill's `location` in
SkillKnowledge. Any skill whose SKILL.md references bundled resources
(scripts/, references/) is therefore unable to resolve those files —
the agent sees 'Skill location: public' instead of an actual path.

Fix: compute the absolute path to the skills directory inside the
`@openhands/extensions` node_modules package at Vite build/serve time
and inject it as `__EXTENSIONS_SKILLS_DIR__` via Vite's `define`.
`buildBundledSkills()` now sets `source` to:
  `${__EXTENSIONS_SKILLS_DIR__}/${name}/SKILL.md`

Library builds receive an empty string so consumers are not bound to
this machine's node_modules path; the adapter falls back to 'public'
when the value is falsy.

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

* fix: declare __EXTENSIONS_SKILLS_DIR__ global for TypeScript

Add missing 'declare const __EXTENSIONS_SKILLS_DIR__: string' to
src/react-app-env.d.ts so tsc can resolve the Vite-injected constant
used in buildBundledSkills().

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

* test: assert skill source is absolute path ending in /<name>/SKILL.md

Replace the loose 'typeof === string' / toBeTruthy checks with two specific
assertions that directly verify the intent of buildBundledSkills():
- source starts with '/' (absolute path the agent-server can resolve)
- source ends with '/<name>/SKILL.md' (points to the right file)

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-11 09:23:45 -06:00
Tim O'Farrellandopenhands 994912fbe7 bump automation to 1.0.0a7 (#1298)
* bump automation to 1.0.0a7 and add automationSdk==agentServer version test

- Update versions.automation: 1.0.0a6 → 1.0.0a7 (latest on PyPI)
- Update versions.automationSdk: 1.22.1 → 1.27.0
  openhands-automation==1.0.0a7 depends on openhands-sdk==1.27.0,
  which now matches versions.agentServer (1.27.0)
- Add 'versions.automationSdk matches versions.agentServer' test in
  __tests__/scripts/check-sdk-version-sync.test.ts so any future bump
  that forgets to keep both fields in sync fails CI immediately
- Add config/defaults.json to sdk-version-sync.yml path triggers so
  the PyPI metadata check also fires when the config file is changed

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

* remove automationSdk==agentServer static test

The sdk-version-sync workflow already covers the meaningful invariant
(automationSdk matches what the released openhands-automation on PyPI
actually depends on). The static test enforced automationSdk===agentServer
at all times, but the script explicitly allows automationSdk to lag
agentServer while a compatible automation release is pending — making
the test both unnecessary and incorrect.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-10 12:52:41 -06:00
Tim O'Farrellandopenhands e65a07dcad Bump extensions to latest version (#1294)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-10 08:18:55 -06:00
Tim O'Farrellandopenhands 2a3f06a75d perf: skip runtime API call in webhook auth via SandboxRecord (#14732)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-09 13:03:55 -06:00
Tim O'Farrellandopenhands 65ae6bdabe fix: remove org_id from ownership assertion in save_app_conversation_info (#14727)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-09 11:23:22 -06:00
Tim O'Farrellandopenhands 7aa694fda2 Remove _get_sandbox_by_session_api_key_legacy to stop spurious runtime API queries on stopped sandboxes (#14706)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-08 08:14:52 -06:00
Tim O'Farrellandopenhands cca79d096e chore: bump software-agent-sdk to 1.26.0 (#1186)
Update agentServer version pin in config/defaults.json from 1.25.0 to 1.26.0.
This drives all four packages (openhands-agent-server, openhands-sdk,
openhands-tools, openhands-workspace) which are released in lockstep.

Also update matching test expectations and example version strings in
dev-safe.mjs, check-sdk-version-sync.mjs, and AGENTS.md.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-05 11:47:43 -06:00
Tim O'Farrellandopenhands 90754e2571 feat: add daily-rotating file logger for dev scripts (closes #815) (#1181)
* feat: add daily-rotating file logger for dev scripts (issue #815)

Add winston + winston-daily-rotate-file to write all dev-server log
output to logs/agent-canvas.YYYY-MM-DD.log alongside the existing
console output (which is unchanged).

- scripts/logger.mjs  — shared module; exports fileLog(level, msg)
  and stripAnsi(str). DailyRotateFile transport stores files in
  logs/ relative to the project root, rotates at midnight, and
  auto-deletes files older than 7 days.
- scripts/dev-with-automation.mjs — logService / logStep /
  logSuccess / logError each call fileLog as a side-channel. The
  shutdown message, startup title, checkPrerequisites uvx-error, and
  printBanner summary are also captured.
- scripts/dev-safe.mjs — spawnProcess errors, main() startup lines,
  the unexpected-exit error, and the fatal-error handler all call
  fileLog.
- logs/ was already in .gitignore.

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

* fix: store log files in agent-canvas state dir, not project root

Use OH_CANVAS_SAFE_STATE_DIR (or ~/.openhands/agent-canvas as
the default) to match where all other agent-canvas runtime state
lives, e.g. ~/.openhands/agent-canvas/logs/agent-canvas.YYYY-MM-DD.log

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-05 10:34:30 -06: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
Tim O'Farrellandopenhands 80e01e099d fix: single-line agent-server logs in dev:automation mode (#1178)
Switch the agent-server to LOG_JSON=true so the Python SDK emits one
JSON object per log record instead of Rich-formatted output.  Rich was
wrapping long lines across multiple entries and prepending its own
timestamp on top of the HH:MM:SS [agent-server] prefix that logService
already provides.

Add parseAgentServerLogLine() which turns each JSON record into a clean
single-line string:

  INFO     127.0.0.1:51658 - "GET /api/..." 200  h11_impl.py:481

Level-based ANSI colouring is also applied (dim for DEBUG, yellow for
WARNING, red for ERROR/CRITICAL, blue for INFO) so errors still stand
out.  Non-JSON lines (e.g. startup text) fall back to the original
yellow stderr / service-colour stdout behaviour unchanged.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-05 07:43:33 -06:00
Tim O'Farrellandopenhands a89aac89f6 fix: prevent deletion of the active LLM profile (#1127)
* fix: prevent deletion of the active LLM profile

Hide the Delete option in the profile actions menu when the profile is
the currently active one, so users cannot accidentally remove it.

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

* fix: add tooltip to disabled Delete menu item for active profile

When the Delete action is disabled because the profile is active, show
a StyledTooltip ('Cannot delete the active profile') over the item so
users understand why it is not interactive.

- Add SETTINGS$PROFILE_CANNOT_DELETE_ACTIVE i18n key + all translations
- Wrap the disabled Delete MenuItem in StyledTooltip with a span so
  pointer events reach the trigger on a disabled button
- Add complementary test confirming Delete is enabled when isActive=false
- Extend i18n mock with the new key

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

* test: fix delete-modal tests to use inactive profile

The two delete-modal tests were clicking Delete on the active profile,
which is now correctly disabled. Switch both tests to the second
(inactive) profile so they exercise the enabled path.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-04 08:19:36 -06:00
Tim O'Farrellandopenhands 17db6dac05 fix: route LLM metadata fetches through cloud proxy for cloud backends (#1109)
* fix: route LLM metadata fetches through cloud proxy for cloud backends

When the active backend is 'cloud', ConfigService.searchProviders,
ConfigService.searchModels, and fetchVerifiedModelsByProvider all called
getAgentServerClientOptions(), which delegates to getEffectiveLocalBackend().
That returns null for cloud backends, so NoBackendAvailableError was thrown,
surfacing as the 'No backend is configured' toast on /settings/llm.

Follow the established cloud-vs-local branching pattern: for cloud backends,
route the three LLM metadata endpoints (/api/llm/providers,
/api/llm/models, /api/llm/models/verified) through callCloudProxy so
correct Bearer auth is used and CORS is handled; for local backends keep
the existing LLMMetadataClient path unchanged.

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

* fix: extract named fields from raw cloud proxy LLM responses

callCloudProxy returns the full JSON response body, while
LLMMetadataClient extracts named sub-fields:
  getProviders()      → response.data.providers  (raw: { providers: string[] })
  getModels()         → response.data.models      (raw: { models: string[] })
  getVerifiedModels() → response.data.models      (raw: { models: Record<string, string[]> })

The cloud proxy calls were treating the whole response object as the
array/record directly, causing '(models ?? []).filter is not a function'
and garbled provider/model dropdowns.

Use typed raw-response wrappers and chain .then(raw => raw?.field ?? null)
to mirror exactly what LLMMetadataClient does internally.

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

* fix: use cloud API search endpoints for providers and models

For cloud backends, /api/llm/providers and /api/llm/models are wrong —
those are local agent-server endpoints. The cloud API exposes:
  GET /api/v1/config/providers/search → ProviderPage
  GET /api/v1/config/models/search    → LLMModelPage

Both return the same shape the local ConfigService types already define,
so the cloud path calls callCloudProxy directly and returns the response
as-is, with no reconstruction logic needed.

The intermediate fetchVerifiedModelsByProvider step is skipped for cloud
entirely — the cloud search endpoints embed verified status natively.

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

* style: fix prettier formatting on buildCloudQueryString signature

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

* docs: address review bot suggestions on cloud LLM metadata fix

- Add JSDoc to ConfigService.searchModels and searchProviders noting that
  verifiedByProvider is ignored for cloud backends (cloud API embeds
  verified status directly on each returned item)
- Expand comment on fetchVerifiedModelsByProvider's cloud early-return to
  clarify it is safe to treat as a no-op for cloud callers

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-03 17:53:28 -06:00
Tim O'Farrellandopenhands 4fa822d1f7 chore: bump openhands-automation to 1.0.0a6 (#1097)
* chore: bump openhands-automation to 1.0.0a6

* Remove fragile automation version assertion test

The test hardcoded an exact version string that breaks on every
version bump. It provides no utility — the version is already
covered by integration/config tests; pinning it in a unit test
just means a manual edit is required on every release.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-03 20:03:03 +00:00
Tim O'Farrellandopenhands 3e51a68bd4 chore: auto-graduate npm dist-tag from latest to per-tier once first stable release ships (#1028)
* chore: always publish to npm with --tag latest until first stable release

All alpha/beta/rc versions now get the 'latest' dist-tag so plain
'npm install @openhands/agent-canvas' always resolves to the newest
published release. The per-tier dist-tags (alpha/beta/rc) can be
re-introduced once the first full stable version is ready to ship.

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

* chore: auto-graduate npm dist-tag when first stable release ships

At publish time, query npm for any published version without a pre-release
suffix. If none exists, all releases (alpha/beta/rc/stable) use --tag latest
so plain 'npm install' always resolves to the newest build. Once a stable
version has been published, pre-release versions revert to their own
dist-tags (alpha/beta/rc) automatically — no workflow change required.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-02 10:07:54 -06:00
Tim O'Farrellandopenhands 5a3011c001 fix: align dev-stack OH_PERSISTENCE_DIR and automation DB path with Docker (#960)
* fix: align dev-stack OH_PERSISTENCE_DIR and automation DB path with Docker

Two path mismatches between `npm run dev` and Docker prevented config and
automation data from being shared across the two modes:

1. **OH_PERSISTENCE_DIR wrong (supersedes PR #959)**
   Docker's entrypoint.sh sets OH_PERSISTENCE_DIR to $HOME/.openhands
   (OPENHANDS_DIR). PR #959 added the env var to buildAgentServerEnv() but
   used config.stateDir (~/.openhands/agent-canvas) — one level too deep.
   Settings and secrets written by Docker live at ~/.openhands/settings.toml
   etc; dev wrote to ~/.openhands/agent-canvas/settings.toml.
   Fix: use path.dirname(config.stateDir) = ~/.openhands, matching Docker.

2. **Automation DB in wrong directory**
   dev-with-automation.mjs put the SQLite DB at
   ~/.openhands/agent-canvas/automations.db. Docker puts it at
   ~/.openhands/automation/automations.db (from config/defaults.json
   paths.automationDb = "automation/automations.db" relative to OPENHANDS_DIR).
   Fix: use join(dirname(config.stateDir), SHARED_DEFAULTS.paths.automationDb)
   so both modes resolve to ~/.openhands/automation/automations.db.
   Also ensure the directory is created on startup (mirrors Docker's mkdir -p).

3. **Mock-LLM test cleanup**
   Update playwright.mock-llm.config.ts to clean both STATE_DIR and the new
   AUTOMATION_DB_DIR (.tmp/automation/) before each test run, since the DB now
   lives outside STATE_DIR.

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

* fix: use dev_conversations dir for npm to isolate from Docker conversations

npm dev mode now writes conversations to ~/.openhands/agent-canvas/dev_conversations
instead of conversations, keeping them separate from Docker's conversations dir.

OH_CONVERSATIONS_PATH (set via buildAgentServerEnv) is the single control point;
all mkdir and releaseStaleConversationLeases calls updated to match.

Also condense verbose multi-line comments on OH_PERSISTENCE_DIR and
AUTOMATION_DB_URL to single lines.

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

* fix: use join(config.stateDir, dev_conversations) in ensureDirectories

The outer config in dev-with-automation.mjs does not have conversationsPath
(that is a SafeDevConfig property). Use the same join(stateDir, ...) pattern
as the other dirs in the list.

Also removes the debug console.log and restores dev-safe.mjs and dev-static.mjs
to dev_conversations after the manual revert.

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

* test: update conversationsPath assertion to match dev_conversations

The implementation in buildConfigFromPorts was changed to use
'dev_conversations' as the subdirectory name, but the corresponding
test assertion was not updated.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-02 15:26:19 +00:00
Tim O'Farrellandopenhands e5907df5b4 ci: trigger CI on rel-* branch pushes for tag protection rule (#1004)
The Release Tag ruleset requires test-and-build (ubuntu) to pass
before v* tags can be pushed, but CI previously only ran on main and
pull_request events. This caused rel-* version bump commits to fail
the tag protection check unless a workaround PR was opened.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-02 14:57:44 +00:00
Tim O'Farrellandopenhands d0994a6fe1 fix: use X-Session-API-Key for local automation auth in prompts and RUNTIME_SERVICES (#999)
Fixes #980

The agent prompt in recommended-automations-launcher and the
RUNTIME_SERVICES block in agent-server-adapter both advertised
X-API-Key as the auth header for the local automation backend.
The automation service (openhands-automation) does not accept
X-API-Key — it accepts Authorization: Bearer and X-Session-API-Key.

X-Session-API-Key is the established local convention: the agent
server uses it, the frontend automation API client uses it (with an
explicit comment that both backends share the same header), and
auth.py describes it as matching that convention. Update both call
sites and the corresponding test assertion to use X-Session-API-Key.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-01 15:10:30 -06:00
Tim O'Farrellandopenhands 1d4fd1525c chore: switch to tag-based release workflow with per-tier npm dist-tags (#996)
- create-release.yml now triggers on v* tag push (not PR merge).
  The release branch is never merged to main; publishing is triggered
  by pushing the tag directly to the rel-X.Y.Z branch.
- npm-publish.yml resolves the dist-tag from the version string:
    alpha → alpha, beta → beta, rc → rc, stable → latest
  Removes the temporary 'always publish as latest' workaround.
- release.md skill and AGENTS.md updated to document the new model.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-01 12:44:05 -06:00
Tim O'Farrellandopenhands cd03d9cf9c chore: bump version to 1.0.0-alpha.10 (#990)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-01 23:09:58 +07:00
Tim O'Farrellandopenhands 9285c7a827 fix: set AUTOMATION_AGENT_SERVER_URL in Docker entrypoint to enable local auth (#977)
Without AUTOMATION_AGENT_SERVER_URL, ServiceSettings.is_local_mode returns
False and the automation server tries to validate the session API key against
the OpenHands cloud API (app.all-hands.dev/api/v1/users/me), which returns
401 for locally-generated keys.

Setting AUTOMATION_AGENT_SERVER_URL activates the local-mode fast path in
authenticate_request(), which validates the key directly against
AUTOMATION_LOCAL_API_KEY (already set to the session key) without any
network call. This matches what dev-with-automation.mjs and dev-static.mjs
already do correctly.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-01 15:42:20 +00:00
Tim O'Farrellandopenhands 38253e41ba fix: proxy to window.location.origin when local hostnames differ (127.0.0.1 vs localhost) (#975)
shouldUseProxyOrigin() only redirected to window.location.origin when the
browser was at a non-local hostname. When the configured backend uses
127.0.0.1 and the browser accesses via localhost (Docker default), both
are in localHosts so the proxy substitution was skipped — sending the
automation health check directly to http://127.0.0.1:8000 and triggering
a CORS error from http://localhost:8000.

Expand the condition to also proxy when the configured local hostname
differs from the browser hostname (e.g. 127.0.0.1 vs localhost).

Fixes #974

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-01 08:51:22 -06:00
Tim O'Farrellandopenhands 95b6d7a23e fix: persist OH_SECRET_KEY to secret-key.txt in dev mode, same as Docker (#957)
Dev mode (npm run dev) was always using the hardcoded static default key
'openhands-dev-secret-key-change-in-prod' from config/defaults.json.
Docker mode (docker/entrypoint.sh) generates a random key on first run and
persists it to ~/.openhands/agent-canvas/secret-key.txt.

When both modes share the same ~/.openhands directory, they used different
keys — causing decryption failures for any settings encrypted by the other
mode.

Fix: remove the static default in dev-safe.mjs and instead use
getOrCreatePersistedApiKey() with a new DEFAULT_SECRET_KEY_PATH constant
pointing to the same secret-key.txt file that Docker reads/writes.
Whichever mode runs first generates and persists the key; the other picks
it up automatically on next start.

- Add DEFAULT_SECRET_KEY_PATH export to dev-safe.mjs
- Replace 'env.OH_SECRET_KEY || DEFAULT_SECRET_KEY' with
  'env.OH_SECRET_KEY || getOrCreatePersistedApiKey(secretKeyPath, "secret")'
- Update startup log to show persisted file path (not 'default (for local development)')
- Remove now-unused 'defaults.secretKey' from config/defaults.json
- Update AGENTS.md to reflect the new shared-file behavior

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-30 06:36:47 -06:00
Tim O'Farrellandopenhands 07b53a756f chore: bump version to 1.0.0-alpha.9 (#948)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-29 16:18:54 -06:00
Tim O'Farrellandopenhands eb3ae22b23 fix: fast-fail dev scripts when ports are already in use (#939)
* fix: fast-fail dev scripts when ports are already in use

Add `assertPortsFree` to `scripts/dev-safe.mjs` that checks each
preferred port and throws a descriptive error if any are already bound,
rather than silently binding to an alternative port.

- `buildSafeDevConfigAsync` now calls `assertPortsFree` before
  returning config, so `npm run dev:minimal` exits immediately with a
  clear message when the agent-server port is occupied.
- `dev-with-automation.mjs`'s `buildConfig` does the same for all four
  service ports (ingress, agent-server, automation, vite), covering
  `npm run dev` and `npm run dev:static`.
- `buildConfig` gains env-var overrides for internal service ports
  (`OH_CANVAS_SAFE_BACKEND_PORT`, `OH_CANVAS_SAFE_AUTOMATION_PORT`,
  `OH_CANVAS_SAFE_VITE_PORT`) so tests and advanced users can redirect
  ports without touching production defaults.

Tests updated accordingly:
- Old "falls back when port is busy" tests replaced with "throws when
  port is busy" equivalents.
- New `assertPortsFree` describe block with three targeted cases.
- `envWithIsolatedKeyPath` in `dev-with-automation.test.ts` now seeds
  high free ports so the pre-flight check passes when a real dev stack
  is running during local test execution.

Closes #934

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

* refactor: address review bot suggestions on assertPortsFree

- Check ports in parallel with Promise.all instead of sequentially
  (each check is independent I/O, so parallel is faster and more idiomatic)
- Include vscode port in the buildSafeDevConfigAsync pre-flight check
  alongside the agent-server port, so a conflict on that internal port
  is also caught with a helpful message rather than a cryptic spawn error

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-29 17:25:52 +00:00
Tim O'Farrellandopenhands 99e7bb06cb Upgraded public extensions (#933)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-29 16:17:08 +00:00
Tim O'Farrellandopenhands fc450ef0e9 fix: make load_public_skills default to true (opt-out) (#901)
PR #229 flipped shouldLoadPublicSkills() from opt-out (default true) to
opt-in (default false) for dev-latency reasons. PR #362 patched the
conversation-start path by hardcoding load_public_skills: true, but
that fix was inadvertently lost in the #457 refactor which reintroduced
shouldLoadPublicSkills() inside the new buildAgentContext() helper.

This restores the original opt-out semantics:
- shouldLoadPublicSkills() now returns true unless VITE_LOAD_PUBLIC_SKILLS
  is explicitly set to "false"
- .env.sample updated to document the opt-out form
- Tests updated to match the new default
- AGENTS.md updated (it already said "defaults to true" but the code
  disagreed — now they agree)

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-28 12:52:58 -06:00
Tim O'Farrellandopenhands d1813c6cfe refactor: update @openhands/extensions from MCP to integrations (#875)
* refactor: update @openhands/extensions from MCP to integrations

Migrate from @openhands/extensions/mcps to @openhands/extensions/integrations
following the upstream rename in OpenHands/extensions.

Key changes:
- MCP_CATALOG -> INTEGRATION_CATALOG
- McpCatalogEntry -> IntegrationCatalogEntry
- MarketplaceTemplate -> IntegrationTransport
- MCP_LOGOS/MCP_FALLBACK_LOGO -> INTEGRATION_LOGOS/INTEGRATION_FALLBACK_LOGO
- entry.template -> entry.connectionOptions[].transport (via getDefaultTemplate helper)
- automation.requiredMcpIds -> automation.requiredIntegrationIds

The new integration catalog structure supports multiple connection options per
entry (e.g., OAuth + stdio fallback). A getDefaultTemplate() helper extracts
the transport config from the default connection option.

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

* Set correct version

* fix: resolve lint errors

- Replace Date.now() with useId() for pure render function compliance
- Use optional chaining in handleStdioSubmit
- Remove unused eslint-disable directive
- Fix prettier formatting issues

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

* fix: add getInstallableTemplate for stdio-preferred MCP installations

Integration entries like GitHub and Slack now default to OAuth transport,
but the UI doesn't support OAuth yet. Add getInstallableTemplate() that
prefers stdio (API key-based) connection options over OAuth defaults.

- Add getInstallableTemplate() that finds stdio options first
- Update install-server-modal.tsx to use getInstallableTemplate
- Update recommended-automations-*.tsx to use getInstallableTemplate
- Update findCatalogEntryForServer to check ALL connection options

This ensures the install modal shows the correct input fields (e.g.,
GITHUB_PERSONAL_ACCESS_TOKEN) and correctly detects installed servers.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-28 11:21:35 -06:00
Tim O'Farrellandopenhands 082cbc2ddb fix: truncate long conversation title on mobile to keep tab toggle visible (#867)
* fix(skills): save disabled_skills when settings has no prior disabled_skills field

The hydration effect in SkillsSettingsScreen only set hasHydratedInitialSettings=true
when settings?.disabled_skills was truthy. When no skills had ever been disabled,
the server omits the field entirely (undefined), so the condition was always false:

  if (settings?.disabled_skills) { ... }   // skipped when field is absent

Because settings?.disabled_skills is undefined both before *and* after settings
load, the dependency array [settings?.disabled_skills] never changed value on
load either, so the effect never ran a second time. hasHydratedInitialSettings
stayed false, and the save effect's early-return guard blocked every toggle.

Fix by gating on settingsLoading instead and defaulting the missing field with
?? []. Also add a localStorage stub for Node.js 25+ which ships a built-in
localStorage that is non-functional without --localstorage-file, breaking the
zustand persist middleware in the test environment.

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

* fix(tests): use mockResolvedValue(true) for saveSettings spy

saveSettings returns Promise<boolean>, not Promise<void>, so
mockResolvedValue(undefined) caused TS2345 type errors in CI.

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

* fix(skills): persist disabled_skills to localStorage for local backend

The local agent-server has no concept of disabled_skills and its
PATCH /api/settings strips the field before sending. As a result,
toggling a skill off appeared to work in the UI but was never
persisted -- the value was silently discarded on every save and
page refresh reverted the toggle.

Fix by mirroring the existing app-preferences pattern:

- saveSettings (local path): call writeStoredDisabledSkills before
  stripping the field from the PATCH payload.
- transformApiResponse: call readStoredDisabledSkills and overlay it
  onto the partial Settings returned from the API, so every getSettings
  call (cached or fresh) surfaces the stored value.

Export DISABLED_SKILLS_STORAGE_KEY so tests can reference the key
without duplicating the string literal.

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

* fix: truncate long conversation title on mobile to keep tab toggle visible

When a conversation title was long enough it would overflow and hide the
RightPanelToggle button (the tab toggle) in the top-right of the mobile
header. Fixed by:

- Adding `min-w-0` to the left-side flex container in
  ConversationNameWithStatus so it can shrink and the toggle stays in view
- Adding `shrink-0` to the status dot wrapper so it never collapses
- Adding `min-w-0` to ConversationName's root div (flex item) to allow
  it to shrink within the parent
- Removing `w-fit max-w-fit` from the title div (both prevented
  `truncate` from ever activating) so the title now properly truncates
  with an ellipsis when too long
- Adding `shrink-0` to the ellipsis-button wrapper so it is always
  accessible regardless of title length

Fixes #848

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-28 07:41:05 -06:00
Tim O'Farrellandopenhands 0206d0e9ee fix(skills): save disabled_skills when server omits the field (#837)
* fix(skills): save disabled_skills when settings has no prior disabled_skills field

The hydration effect in SkillsSettingsScreen only set hasHydratedInitialSettings=true
when settings?.disabled_skills was truthy. When no skills had ever been disabled,
the server omits the field entirely (undefined), so the condition was always false:

  if (settings?.disabled_skills) { ... }   // skipped when field is absent

Because settings?.disabled_skills is undefined both before *and* after settings
load, the dependency array [settings?.disabled_skills] never changed value on
load either, so the effect never ran a second time. hasHydratedInitialSettings
stayed false, and the save effect's early-return guard blocked every toggle.

Fix by gating on settingsLoading instead and defaulting the missing field with
?? []. Also add a localStorage stub for Node.js 25+ which ships a built-in
localStorage that is non-functional without --localstorage-file, breaking the
zustand persist middleware in the test environment.

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

* fix(tests): use mockResolvedValue(true) for saveSettings spy

saveSettings returns Promise<boolean>, not Promise<void>, so
mockResolvedValue(undefined) caused TS2345 type errors in CI.

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

* fix(skills): persist disabled_skills to localStorage for local backend

The local agent-server has no concept of disabled_skills and its
PATCH /api/settings strips the field before sending. As a result,
toggling a skill off appeared to work in the UI but was never
persisted -- the value was silently discarded on every save and
page refresh reverted the toggle.

Fix by mirroring the existing app-preferences pattern:

- saveSettings (local path): call writeStoredDisabledSkills before
  stripping the field from the PATCH payload.
- transformApiResponse: call readStoredDisabledSkills and overlay it
  onto the partial Settings returned from the API, so every getSettings
  call (cached or fresh) surfaces the stored value.

Export DISABLED_SKILLS_STORAGE_KEY so tests can reference the key
without duplicating the string literal.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-27 17:49:25 -06:00
Tim O'Farrellandopenhands 463deff24a feat: validate MCP server connectivity before saving (#785) (#816)
* feat: pre-flight MCP test before save (#785)

Validate MCP server connectivity via POST /api/mcp/test before saving
in both the marketplace install modal and the custom-server editor.

- Add McpService.testServer() using MCPClient from @openhands/typescript-client
- Add useTestMcpServer() useMutation hook
- InstallServerModal: test before addMcpServer; show inline error on failure,
  keep modal open (onClose not called); button shows Verifying… then Saving…
- CustomServerEditor: same pre-flight pattern before add/update; also exposes
  a standalone Test Connection button via MCPServerForm's new onTest prop
- MCPServerForm: add onTest/isTestPending/testMessage props; extract buildConfig
  helper; add handleTestClick using formRef; render Test Connection button and
  testMessage inline above action buttons
- Add i18n keys: MCP$TEST_BUTTON, MCP$VERIFYING, MCP$TEST_SUCCESS,
  MCP$TEST_ERROR_TIMEOUT, MCP$TES  MCP$TEST_ERROR_TIMEOUT, MCP$TES  MCP$TEST_ERROR_TIMEOUT, MCP$TES  MCP$TEST_ERROR_TIMEOUT, MCP$TES  MCP$TEST_ERROR_TIMEOUT, MCP$TES  + keeps modal open, success path, Verifying label)

Closes #785

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

* test: stub McpService.testServer in tests that save through the pre-flight

Three test suites click a submit button whose handler now runs the
pre-flight connectivity test before calling saveSettings.  None of
them mocked McpService.testServer, so the mutation errored out before
reaching the save step.

Add vi.spyOn(McpService, 'testServer').mockResolvedValue({ ok: true, tools: [] })
to the beforeEach of each affected suite so the test-then-save flow
resolves as expected and the existing assertions remain valid.

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

* fix: decode HTML entities in MCP test-server error messages

The Python backend passes error strings through html.escape(), so
apostrophes arrive as &#39; and slashes as &#x2F;.  Add a one-liner
decodeHtmlEntities() helper in McpService that uses a temporary
<textarea> to let the browser's own HTML parser decode the string
before it reaches any UI component.

Decoding happens once, at the API boundary, so every consumer
(InstallServerModal, CustomServerEditor, etc.) automatically gets
clean text without needing its own unescaping logic.

Add a focused McpService unit test (4 cases) that mocks MCPClient
via vi.hoisted + vi.mock to exercise the real decoding path:
- success responses pass through unchanged
- &#39; / &#x2F; entities decoded in the error field
- numeric (&lt;) and hex (&#x73;) entities decoded
- stdio config mapped to the correct MCPServerSpec shape

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

* fix: render MCP error strings as plain text with newlines

Two bugs in the error message display path:

1. HTML-entity encoding: react-i18next escapes interpolated values by
   default (\' -> &#39;, / -> &#x2F;).  Fix by using the no-escape
   prefix {{-error}} in the MCP$TEST_ERROR_UNKNOWN translation string
   so the raw error text is interpolated verbatim.

2. Newlines ignored: the \n in multi-line Python tracebacks was
   swallowed by the browser.  Fix by adding whitespace-pre-wrap to the
   <p> elements in install-server-modal (globalError) and
   mcp-server-form (testMessage) so \n renders as a visual line break.

Also revert the previous decodeHtmlEntities approach from the service
layer — the backend does not HTML-escape thlayer — the backend does nwas introduced by i18next, not the server.  Update McpService telayer — the backend does not HTML-escape thlayer — the backend dngs in the
translation layer instead.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-27 12:56:25 -06:00
Tim O'Farrellandopenhands 99108509f6 perf(useLocalGitInfo): consolidate git probe into single bash round-trip (#792)
* perf(useLocalGitInfo): consolidate git probe into single bash round-trip

Replace the former probeGitInfoAtDir + probeNestedRepoInDir pair (which
issued 2-3 serial bash WebSocket round-trips per poll cycle) with a single
GIT_INFO_COMMAND shell script that handles both the root and nested-repo
cases in one run() call.

The script outputs two newline-separated lines: <remote-url>\n<branch>.
The new probeGitInfo function runs the script and splits on the first
newline to recover the same LocalGitInfo struct as before.

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

* fix: prettier quote style in use-local-git-info.ts

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-27 07:16:13 -06:00
Tim O'Farrellandopenhands 9c1ae19878 fix: auto-clear backend health on successful conversation list fetch (#647)
* feat: execute git-info probe commands over bash-events WebSocket

Replace per-poll REST calls in useLocalGitInfo with a persistent
WebSocket connection to /sockets/bash-events.

Previously, useQuery's refetchInterval:10_000 caused
AgentServerRuntimeService.executeCommand to fire individual HTTP
requests on every tick (find + 2 git commands × up to 3 candidate
dirs) to probe the workspace for git metadata.

Changes:
- websocket-url.ts: export buildBashWebSocketUrl() using the same
  host/pathPrefix extraction as the conversation-events URL builder
- use-bash-command-runner.ts: new hook that maintains a persistent WS
  connection to /sockets/bash-events and exposes runCommand(command,
  cwd, timeout) → Promise. Commands queued during CONNECTING are
  flushed on open; all in-flight commands are rejected on
  close/error/unmount.
- use-local-git-info.ts: swap AgentServerRuntimeService.executeCommand
  for useBashCommandRunner; keep refetchInterval:10_000 so branch
  changes (e.g. git checkout) are still reflected without a full
  refresh, but each probe now reuses the open socket rather than
  opening new HTTP connections.

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

* fix: auto-clear backend health on successful conversation list fetch

When the backend health probe fails 5 times in a row, the disabled flag
is written to localStorage and background polling stops permanently.
This left the status dot red even after the backend recovered, because
no successful API call ever cleared the disabled state.

Fix by wiring a global QueryCache onSuccess handler that calls
recordBackendSuccess(backendId) whenever a query tagged with
meta.backendId succeeds. Tag usePaginatedConversations (which polls
every 10s) with the active backend's id so a recovered backend clears
its stale failure state within one polling cycle automatically.

Any future query that targets a specific backend can opt in by adding
meta: { backendId } to its options.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-26 13:26:59 -06:00
Tim O'Farrellandopenhands eb7c983169 feat: bump @openhands/extensions to include github-repo-monitor and slack-channel-monitor (#781)
Pins @openhands/extensions from 7b33f64 (May 19) to b8c1869 (May 23),
which adds two new automation catalog entries:
- github-repo-monitor: watches GitHub repos for @OpenHands mentions
- slack-channel-monitor: watches Slack channels for @openhands mentions

Updates the popularity-order unit test to reflect the new 7-entry catalog
ranking (github-repo-monitor at rank 98 sits between github-pr-reviewer
at 100 and slack-standup-digest at 94).

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-26 12:57:54 -06:00
Tim O'Farrellandopenhands c9616c1153 fix: pin openhands-sdk to same version as other SDK packages (#772)
Add openhands-sdk==${VERSION} to the --with list in both PyPI
resolution branches (specific version and default) of
buildAgentServerCommand so all four packages are pinned to the
same versions.agentServer:

  openhands-agent-server, openhands-sdk, openhands-tools, openhands-workspace

Without this pin, openhands-sdk floated to the latest release on
PyPI (a transitive dep with no version bound in the published
openhands-agent-server metadata), causing non-reproducible builds
and version skew between the banner and defaults.json.

The local-path (editable) and git-ref branches are unaffected —
they already source all packages from the same checkout/ref.

Update the two affected unit-test cases to expect the new pinned
openhands-sdk entry in the args array.

Fixes #767

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-26 15:38:53 +00:00
Tim O'Farrellandopenhands 2d494a4767 feat: execute git-info probe commands over bash-events WebSocket (#646)
Replace per-poll REST calls in useLocalGitInfo with a persistent
WebSocket connection to /sockets/bash-events.

Previously, useQuery's refetchInterval:10_000 caused
AgentServerRuntimeService.executeCommand to fire individual HTTP
requests on every tick (find + 2 git commands × up to 3 candidate
dirs) to probe the workspace for git metadata.

Changes:
- websocket-url.ts: export buildBashWebSocketUrl() using the same
  host/pathPrefix extraction as the conversation-events URL builder
- use-bash-command-runner.ts: new hook that maintains a persistent WS
  connection to /sockets/bash-events and exposes runCommand(command,
  cwd, timeout) → Promise. Commands queued during CONNECTING are
  flushed on open; all in-flight commands are rejected on
  close/error/unmount.
- use-local-git-info.ts: swap AgentServerRuntimeService.executeCommand
  for useBashCommandRunner; keep refetchInterval:10_000 so branch
  changes (e.g. git checkout) are still reflected without a full
  refresh, but each probe now reuses the open socket rather than
  opening new HTTP connections.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-25 08:50:55 -06:00
Tim O'Farrellandopenhands 87b1079388 fix: persist home-page prompt draft to sessionStorage (#668)
* Persist home page prompt draft to sessionStorage

Saves the chat input text on the 'Let's Start Building' home page to
sessionStorage (debounced) so it survives navigation and failed
conversation starts.

- Restored when navigating away and back to the home page
- Restored on page refresh within the same browser session
- Cleared only on successful conversation creation (navigate away)
- Discarded automatically when the browser tab is closed

useDraftPersistence: when conversationId is undefined (home page context),
save/restore/clear via sessionStorage[HOME_PROMPT_DRAFT_KEY] instead of
the existing no-op path. Exports HOME_PROMPT_DRAFT_KEY constant.

HomeChatLauncher: remove the sessionStorage key in onSuccess before
navigating to the new conversation, so a fresh home page starts clean.

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

* fix: flush home-page draft to sessionStorage on unmount

The previous implementation debounced sessionStorage writes (500ms),
which meant text typed and then quickly navigated away from — before
the debounce fired — was never persisted. The unmount cleanup cancelled
the pending timer, leaving sessionStorage with the stale value from the
last full save, which was then incorrectly restored on the next visit.

Fix: in the unmount cleanup, synchronously flush the current input text
to sessionStorage when conversationId is undefined (home-page context).
The debounce still handles the in-flight saves; the flush ensures nothing
is lost when the component tears down with a pending timer.

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

* fix: save home-page draft synchronously; use ref not DOM in flush

Two bugs caused the wrong prompt to be restored after in-app navigation:

1. React 18 clears ref.current during the synchronous commit phase, before
   async useEffect cleanup functions run. The flush-on-unmount was reading
   chatInputRef.current — which is null at that point — so it never saved
   the updated text, leaving sessionStorage with the stale value.

2. The 500 ms debounce in saveDraft left a window where text typed and
   then navigated away from quickly would also not reach sessionStorage.

Fix:
- saveDraft (home-page path): write synchronously on every onInput event;
  no debounce needed for a sessionStorage write of a few hundred chars.
  Track the written text in lastHomeTextRef.
- Restoration: seed lastHomeTextRef with the restored draft so an
  immediate navigate-away (without typing) still preserves it.
- Unmount flush: read from lastHomeTextRef (always valid) instead of
  chatInputRef (null by cleanup time).

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

* fix: ignore stale messageToSend for home-page draft restoration

useAutoResize has an effect that writes `value.text` (sourced from the
Zustand store's `messageToSend`) directly to the chat input element.
After navigating away from /conversations and back, the store still holds
the stale `messageToSend = { text: '' }` set during the previous visit.

This caused a race with the sessionStorage draft restoration:
1. useDraftPersistence (effect B) restored the saved draft into the element
2. useAutoResize's value-update effect (E) then ran with the stale
   empty-string value and set element.textContent = '', erasing the draft
3. React 18 StrictMode double-invokes effects, so the second pass of
   the drawer-state effect (D) read the now-empty element and queued
   another setMessageToSend(''), keeping the text permanently gone

A page refresh reset the store to messageToSend = null, which caused
useAutoResize to skip its content update (value === undefined), making
the draft visible – hence 'refresh works but navigation back doesn't'.

Fix: on the home page (no conversationId) return null for messageToSend
from useChatInputLogic, keeping value === undefined for useAutoResize
and ensuring the content-update effect is never triggered there.
Also guard the drawer-state effect so it only runs in conversation views.

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

* fix: don't restore home-page draft on unmount if key was intentionally cleared

The unmount flush in useDraftPersistence wrote lastHomeTextRef.current
back to sessionStorage whenever the component tore down on the home page.
This undid the explicit sessionStorage.removeItem call that
HomeChatLauncher.onSuccess makes immediately before navigating to the new
conversation, so the submitted prompt was still present when the user
navigated back home.

Fix: wrap the sessionStorage.setItem in the unmount cleanup with a
check that the key already exists. saveDraft writes synchronously on
every keystroke, so the key is always present when there is unsaved
text. If the key is absent it was intentionally removed (successful
conversation start), and the flush must not restore it.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-25 07:26:19 -06:00
Tim O'Farrellandopenhands dcdef5832e feat(automations): add Download Tarball option to automation kebab menu (#725)
* feat(automations): add Download Tarball option to automation kebab menu

Add a 'Download tarball' menu item to the automation dropdown in both
the list card (AutomationCard) and the detail page header (DetailHeader).
The item appears after 'Turn On/Off' and before 'Delete', and triggers a
browser download via GET /api/automation/v1/{id}/tarball.

- Add download.svg icon (Heroicons outline style)
- Add AUTOMATIONS$DOWNLOAD_TARBALL i18n key with translations for all
  15 supported locales
- Wire menu item to the tarball endpoint using a programmatic anchor click

Ported from OpenHands/automation feat/tarball-download-endpoint branch.
Depends on the new GET /{id}/tarball endpoint added in that PR.

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

* fix(automations): rename tarball label to 'Tarball' and fix auth on download

- Shorten menu item label from 'Download tarball' to 'Tarball' to prevent overflow
- Replace bare anchor-click download (which opened an unauthed browser request)
  with AutomationService.downloadTarball(), which fetches via the authenticated
  axios client (Bearer token for local, cloud proxy for cloud backends) and
  triggers the download from a blob URL

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-21 14:21:22 -06:00
Tim O'Farrellandopenhands 9db4683219 fix: remove hardcoded /workspace/project fallbacks from useLocalGitInfo (#671)
The candidateDirs array introduced in #455 appended the Docker-specific
paths "/workspace/project" and "workspace/project" as unconditional
fallbacks after the configured workingDir. For any conversation without
a git repo (or any non-Docker environment), the probe would iterate all
three candidates on every 10-second tick, firing 6+ bash commands each
time — two of which target a path that doesn't exist outside the
container.

Remove the hardcoded fallbacks. The probe now queries only workingDir
(from conversation.workspace.working_dir, or VITE_WORKING_DIR, or the
built-in default). The nested-repo find is retained for the common
local-clone flow where the agent clones a repo into a subdirectory of
the workspace.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-20 09:58:01 -06:00
Tim O'Farrellandopenhands a6b26e6b08 Added pycache to gitignore (#664)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-20 14:26:36 +00:00
Tim O'Farrellandopenhands 1ee1de48cd fix(dev:docker): use localhost for AUTOMATION_AGENT_SERVER_URL, separate sandbox URL (#609)
The previous attempt (#603) collapsed the host-side and sandbox-side
agent-server URLs onto a single value, `host.docker.internal:<hostPort>`,
on the assumption that the host's /etc/hosts would alias it back to
loopback. That holds on Docker Desktop macOS but NOT on OrbStack / colima
/ Linux hosts, where `host.docker.internal` only exists inside
containers. On those hosts the automation backend (which runs on the
host via uvx) fails to resolve the URL on its very first `_upload`
call:

  [Errno 8] nodename nor servname provided, or not known

so dispatch never reaches `_start_bash`, and every automation run ends
up with `bash_command_id: NULL`.

Fix:

* Restore `AUTOMATION_AGENT_SERVER_URL` to `http://localhost:<hostPort>`
  in every mode. localhost on the host always resolves to the published
  agent-server port; this is the URL the *backend* uses for HTTP calls.
* Add a new launcher option `sandboxAgentServerUrl` that is exported
  as `AUTOMATION_SANDBOX_AGENT_SERVER_URL`. The automation backend
  (OpenHands/automation#125) uses this to override the AGENT_SERVER_URL
  it exports into the in-sandbox bash chain. In dev:docker this is
  `http://127.0.0.1:8000` — the agent-server's in-container loopback,
  which avoids bouncing through the host port-forward.
* Plumb `sandboxAgentServerUrl` through dev-with-automation.mjs::main
  (mirrors the existing `automationApiHost` / `automationWorkspaceBase`
  pattern) and set it in dev-docker.mjs.
* Older automation backends that don't recognise
  AUTOMATION_SANDBOX_AGENT_SERVER_URL ignore it and fall back to
  AUTOMATION_AGENT_SERVER_URL, so this is forward-compatible.

Dockerless modes (`dev`, `dev:automation`) leave
`sandboxAgentServerUrl` undefined; the backend falls back to
AUTOMATION_AGENT_SERVER_URL, which is correct for them.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-18 19:31:42 -06:00
Tim O'Farrellandopenhands 2ac97d1bd8 fix(dev:docker): container-safe automation paths, hostnames, and AGENT_SERVER_URL alias (#603)
* fix(dev:docker): set AUTOMATION_WORKSPACE_BASE to a container-safe path

When agent-canvas is started via `npm run dev:docker`, the agent-server
runs in a container but the automation backend runs on the host via uvx.
The dispatcher resolves `AUTOMATION_WORKSPACE_BASE` on the host (where
it expands to the host's $HOME, e.g. /Users/<you>/.openhands/...) and
then embeds that absolute path into a `mkdir -p ...` shell command
that is executed *inside* the agent-server container. The container
has no /Users directory and the non-root user can't write to /, so
every preset automation fails with:

  mkdir: cannot create directory '/Users': Permission denied

Fix:
* Thread a new `automationWorkspaceBase` option through the launcher
  (mirroring the existing `viteWorkingDir` pattern).
* `dev-docker.mjs` now passes `CONTAINER_WORKSPACES_DIR` — the same
  in-container path already used as the agent-server's working-dir
  root, so it's guaranteed to exist and be writable.
* Resolution order for `AUTOMATION_WORKSPACE_BASE` is now:
  1) explicit user env var (wins over everything; previously the
     hard-coded value silently clobbered user-set values)
  2) launcher-provided default (container-safe in docker mode)
  3) host-side fallback under config.stateDir (unchanged for dockerless)

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

* fix(dev:docker): route AUTOMATION_BASE_URL through host.docker.internal

In `npm run dev:docker`, the automation backend's `AUTOMATION_BASE_URL`
was hard-coded to `http://localhost:${ingressPort}`. That URL is then:

  1. propagated into each automation sandbox as `AUTOMATION_API_URL`
     (dispatcher.py:213), and
  2. consumed by the auto-generated `setup.sh` inside the sandbox to
     hit `${AUTOMATION_API_URL}/sdk-version`.

The sandbox is a separate Docker container, so `localhost` resolves to
the sandbox itself, not the host ingress. Every preset automation run
therefore failed at the very first step with:

  [setup] ERROR: Failed to fetch SDK version from
  http://localhost:8000/api/automation/sdk-version

Reachability check from inside a container in the same network:

  http://localhost:8000/api/automation/sdk-version          -> 404
  http://host.docker.internal:8000/api/automation/sdk-version -> 200

Fix (mirrors the existing `automationWorkspaceBase` plumbing for the
same host-vs-container class of bug):

* New `automationApiHost` launcher option threaded through main() and
  stamped onto config.
* `dev-docker.mjs` passes `automationApiHost: "host.docker.internal"`.
* `startAutomationBackend` resolves `AUTOMATION_BASE_URL` with
  precedence: user env > launcher-provided host > `localhost`.

Dockerless modes (`dev`, `dev:automation`) are unaffected — they
don't pass `automationApiHost` and fall back to the existing
`http://localhost:${ingressPort}` value.

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

* fix(dev:docker): expose AGENT_SERVER_URL and SESSION_API_KEY aliases to the sandbox

The OpenHands SDK boilerplate emitted by automation prompt/plugin presets
reads AGENT_SERVER_URL and SESSION_API_KEY in main.py / setup.sh when
calling back into the agent-server from an automation run. The
agent-server itself sets OH_INTERNAL_SERVER_URL at startup and we set
OH_SESSION_API_KEYS_0 via the container/process env, but neither of the
unprefixed aliases the SDK actually reads were defined — so every preset
automation hit ECONNREFUSED / 401 the moment its setup.sh tried to hit
the agent-server.

Mirror the values under their canonical SDK names in both the dockerless
buildAgentServerEnv() (covers dev / dev:automation) and in dev-docker.mjs
containerEnv (covers dev:docker). Inside the dev:docker container the
agent-server always listens on port 8000, and 0.0.0.0 normalises to
127.0.0.1 (matching the agent-server's own OH_INTERNAL_SERVER_URL
construction), so the docker path uses http://127.0.0.1:8000 directly.

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

* fix(dev:docker): route AUTOMATION_AGENT_SERVER_URL via host.docker.internal

This env var plays a dual role: the automation backend uses it directly
to call the agent-server's upload/bash REST APIs (host-side), and the
same value is exported as AGENT_SERVER_URL into the in-sandbox bash
command that main.py uses to call back. In dev:docker the script runs
inside the agent-server container, so the URL has to be reachable from
both sides.

http://localhost:${agentServerPort} was fine for dockerless modes
(both backend and agent-server live on the host) but broke dev:docker:
from inside the container localhost:${hostPort} doesn't resolve, and
`main.py` failed at the first RemoteWorkspace call.

Reuse the same automationApiHost launcher option already plumbed for
AUTOMATION_BASE_URL (Bug 2): dev:docker passes host.docker.internal, so
the URL works as a loopback alias from the host and bounces back into
the container via the published port-forward from the sandbox side.
Dockerless modes keep the previous `localhost` default.

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

* revert: drop SESSION_API_KEY alias from agent-server env

The OpenHands SDK's sanitized_env() strips SESSION_API_KEY from every
bash subprocess as a (cosmetic) defense-in-depth measure, so setting
this alias on the agent-server's process env had no effect on what the
sandbox script actually sees. Worse, it created cross-purposes between
the dev stack (which exports it) and the SDK (which strips it).

Keep the AGENT_SERVER_URL alias — that one is genuinely needed and
isn't subject to filtering. SESSION_API_KEY is now obtained inside the
sandbox script via OH_SESSION_API_KEYS_0 (handled in an accompanying
PR against openhands/automation), which the SDK does not strip.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-18 16:32:38 -06:00
Tim O'Farrellandopenhands 048e390419 feat(automations): add logs modal to activity log items (#600)
* feat(automations): add logs modal to activity log items

Each AutomationRun now surfaces its bash command output via a small
terminal-icon button placed to the left of the run status badge in the
activity log. Clicking the icon opens a modal that fetches the
BashCommand event and all paginated BashOutput events for the run.

- Add bash_command_id to AutomationRun type (and mock data).
- New BashService.getCommandLogs(): cloud-aware reader for bash events
  that routes through callCloudProxy on cloud backends (runtime URL +
  session-api-key auth) and through BashClient directly on local
  backends. Pages through BashOutput events sorted by timestamp.
- New useBashCommandLogs hook: hydrates the run's conversation to
  resolve runtime URL + session API key, then drives the BashService
  query. Surfaces resolution states (loading, conversation missing,
  sandbox gone) so the modal can render meaningful empty states.
- New RunLogsModal: renders interleaved stdout/stderr from the command
  with stderr highlighted, exit code, and explicit messaging when the
  command is missing or the sandbox is no longer alive.
- Activity-log-item gets a logs button that stopPropagation +
  preventDefault on the wrapping conversation link.
- i18n: 8 new AUTOMATIONS$DETAIL$LOGS_* keys across all 15 languages.
- Tests: BashService cloud/local routing + ActivityLogItem button
  behaviour.

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

* feat(automations): split logs modal into Output/Error tabs

Address review feedback on the run logs modal:

* Rename the modal title from "Run logs" to "Logs".
* Make the modal actually fire the bash-events search request:
  - In local mode, the agent-server hosts events under a single root,
    so the search query no longer waits for the per-conversation URL
    to resolve before firing. The conversation lookup still runs (so
    session-api-key and per-conversation URL are honoured when
    present), but it no longer gates the fetch.
  - In cloud mode the behaviour is unchanged — runtime endpoints
    require the conversation_url for the cloud-proxy hostOverride.
* Replace the interleaved stdout/stderr pre-block with two tabs:
  Output (stdout, default) and Error (stderr). The body of each tab
  is the chronological concatenation (by timestamp + order) of the
  matching field across every BashOutput event for the command.
* Simplify BashService — drop the extra BashCommand fetch; only the
  BashOutput search (`kind__eq=BashOutput, command_id__eq=<id>`) is
  needed for the rendered view. Note that the agent-server API uses
  `command_id__eq` (not `bash_command_id__eq`) — see the python
  bash_router for the canonical filter name.

i18n: add LOGS_TAB_OUTPUT, LOGS_TAB_ERROR, LOGS_EMPTY across all 15
locales; retranslate LOGS_TITLE to "Logs".

Tests:
* New run-logs-modal.test.tsx covers tab defaults, stdout/stderr
  concatenation (with reverse-order inputs to verify the sort),
  loading state, and Escape-to-close.
* bash-service.test.ts rewritten for the new listOutputs API and a
  cloud-without-conversation-url error path.

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

* feat(automations): tolerate paused/missing/unreachable sandboxes in run logs modal

Cloud sandboxes can be in non-RUNNING states (paused, starting,
deleted, errored) and even RUNNING sandboxes can transiently fail at
the network layer. Previously the modal would either show a stuck
'Loading logs...' spinner or dump a raw axios error string. Now each
known-bad state is mapped to a stable `SandboxIssue` code with its
own localized empty-state message.

Behaviour:

* Pre-flight: when `sandbox_status` is MISSING, PAUSED, STARTING, or
  ERROR — or the conversation has no runtime URL at all — the bash
  query is **disabled** (no doomed request is fired). The modal
  renders the matching message instead of a spinner.
* Post-flight: when the request does fire and fails with a 404 or
  5xx response, or a network-level error (no response), the failure
  is classified as `unreachable` and the modal renders the
  'sandbox unreachable' message instead of the raw error. 401/403
  are *not* collapsed — those are auth bugs we want to surface.
* Local backends are unchanged: no sandbox lifecycle, so
  `sandboxIssue` is always null and errors flow through as-is.

API changes:

* `useBashCommandLogs` exposes a `sandboxIssue` discriminated union
  ("missing" | "paused" | "starting" | "errored" | "unreachable")
  instead of the old `hasNoRuntime` boolean. When an issue is set
  the hook clears `error` so the modal doesn't render both an empty
  state AND a raw axios string for the same failure.
* The modal switches over the issue codes via a centralized
  `SANDBOX_ISSUE_I18N` map.

i18n: replace the single `LOGS_SANDBOX_GONE` key with five specific
keys (one per sandbox issue) across all 15 locales.

Tests:

* New `__tests__/hooks/query/use-bash-command-logs.test.tsx`
  exhaustively covers each sandbox_status, the no-runtime-URL case,
  conversationMissing, the happy path, 404/5xx → unreachable
  classification, the network-error case, and the explicit
  401/403-passes-through case.
* `run-logs-modal.test.tsx` extended with a parameterized case
  covering all five issue codes; verifies the empty-state message is
  rendered and the tab body is suppressed.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-18 18:22:26 +00:00
Tim O'Farrellandopenhands 0cf58f8657 fix(dev-docker): allow exec on the home tmpfs so stdio MCP servers work (#535)
Docker's `--tmpfs` flag defaults its mount option set to
`rw,noexec,nosuid,nodev`. When dev:docker overlays the agent-server
container's `/home/openhands` with a tmpfs (so the mapped host user can
own it), it was inheriting that `noexec` flag unintentionally.

That breaks any stdio MCP server installed via npx -- e.g.
`npx -y @modelcontextprotocol/server-github` caches its binary under
`~/.npm/_npx/<hash>/node_modules/.bin/mcp-server-github`, npx then tries
to exec it, and the kernel returns EACCES regardless of the 0755 mode
bits on the file. The user sees:

    sh: 1: mcp-server-github: Permission denied
    Failed to connect to MCP server 'github', skipping
    ...
    MCPError: MCP Connection Failure

and the conversation that triggered it aborts during agent init.

Pass `exec` explicitly to override only the `noexec` default. We keep
`nosuid` and `nodev` (the home dir has no business hosting setuid
binaries or device nodes) so we lose no defense-in-depth beyond what's
necessary to make the supported MCP integration actually function.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-16 21:15:42 -06:00
Tim O'Farrellandopenhands b2b71855c6 feat(dev): surface dev-stack runtime services in agent system prompt (#503)
* feat(dev): surface dev-stack runtime services in agent system prompt

Add a structured 'runtime services' info object that the dev launchers
(`dev:safe`, `dev:automation`, `dev:docker`, and the published
`agent-canvas` binary) propagate to the frontend via
`VITE_RUNTIME_SERVICES_INFO`. The frontend renders it into a
`<RUNTIME_SERVICES>` markdown block and attaches it as
`AgentContext.system_message_suffix` on every `POST /api/conversations`.

This means agents start each conversation knowing exactly what services
exist in the current dev stack (ingress URL, automation backend URL +
`/api/automation` prefix, auth header, etc.), instead of having to probe
or — worse — assume `localhost:8000` is the automation server when it is
actually the Agent Server they are running inside of.

URLs are written from the agent's point of view: dockerless modes use
`localhost`, `dev:docker` uses `host.docker.internal`. When automation
isn't running in the current mode (e.g. `dev:safe`), the block says so
explicitly so agents know to skip `/api/automation` calls.

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

* fix(runtime-services): address review feedback on PR #503

- Validate required `agentServerPort` in `buildRuntimeServicesInfo`;
  previously a missing port baked `http://localhost:undefined` into
  the agent's system prompt.
- Skip the automation entry when the supplied `automation` object has
  no `port` (e.g. a bare `{}` from a misconfigured launcher).
- Rename the JSON service key from `vite` to `frontend` and add a
  `kind: "vite" | "static"` discriminator + mode-aware description,
  so static-build dev stacks (`dev:docker`, the published binary, ...)
  no longer surface a misleading "Vite dev server" line in the agent
  system prompt. The renderer still accepts the legacy `vite` key.
- Anchor the "don't guess" warning to the actual agent-server URL from
  runtime info instead of hardcoded `localhost:8000`, since the
  agent-server uses different ports across dev modes (18000 in
  dev:safe, 8000 in dev:docker, ...).
- Plumb `frontendKind` through `buildAutomationRuntimeServicesInfo`
  and stamp `config.frontendKind` in `dev-with-automation.mjs::main`
  so both Vite spawn and static-build paths describe the frontend
  correctly.
- Expand AGENTS.md with the JSON schema of `VITE_RUNTIME_SERVICES_INFO`
  and a concrete example of the rendered `<RUNTIME_SERVICES>` block.
- Tests: assert the new URL-in-warning behavior, the new `frontend` /
  legacy `vite` rendering, the `agentServerPort`-required guard, the
  `automation: {}` skip, and the legacy `vitePort` alias.

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-15 20:53:45 -06:00
Tim O'Farrellandopenhands 8db469487b docs(readme): mention OpenAPI docs alongside UI URL (#502)
Both the agent server's FastAPI docs and the automation backend's docs
are reachable through the local ingress proxy on port 8000, so point
users at them right next to the existing UI URL in both the Docker and
non-Docker quickstart sections.

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-15 19:16:55 -06:00
Tim O'Farrellandopenhands 4db59b8b94 Proxy agent-server FastAPI docs (/docs, /redoc, /openapi.json) through the ingress (#501)
* feat(ingress): route /docs to the agent server

Add /docs to the list of prefixes proxied to the agent-server in:
  - vite.config.ts (Vite dev server proxy)
  - scripts/dev-with-automation.mjs (ingress + static-server fallback)
  - scripts/dev-static.mjs (ingress + static-server fallback)

Update the explanatory comment in scripts/static-server.mjs to match.

This exposes the agent-server's FastAPI Swagger UI at `/docs` on the
ingress port, alongside the automation backend's existing
`/api/automation/docs`.

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

* feat(ingress): also route /redoc and /openapi.json to the agent server

Without /openapi.json, the Swagger UI page served at /docs (added in the
previous commit) renders but fails to load any spec. /redoc is the
FastAPI-served ReDoc alternative and benefits from the same fix.

Routes are added everywhere /docs already is:
  - vite.config.ts (Vite dev server proxy)
  - scripts/dev-with-automation.mjs (ingress + static-server fallback)
  - scripts/dev-static.mjs (ingress + static-server fallback)

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

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-15 18:46:13 -06:00
Tim O'Farrell b407ad7b64 APP-1547 Added sqlalchemy mapper to loquacious logger list (#14292) 2026-05-04 09:34:52 -06:00
Tim O'Farrellandopenhands 90cf5f8003 APP-1546 feat(frontend): expose sandbox grouping strategy UI to all users (#14291)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-04 09:25:12 -06:00
Tim O'Farrellandopenhands 949a15a560 feat: Proxy Tavily MCP connections through app server (#14278)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-04 06:59:42 -06:00
Tim O'Farrellandopenhands 48b1341776 Add unit tests for get_version() and fix pkg_resources UnboundLocalError (#14265)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 15:45:07 -06:00
Tim O'Farrellandopenhands d9c428b8fe Fix agent kind (#14263)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 13:58:58 -06:00
Tim O'Farrellandopenhands 8a880b2996 docs: fix stale and inaccurate markdown documentation (#14256)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 11:38:57 -06:00
Tim O'Farrellandopenhands 5232d96dab refactor: Move openhands.server content to openhands.app_server (#14254)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 11:02:07 -06:00
Tim O'Farrellandopenhands 2b59566836 refactor: move AppMode class to config_api/config_models.py (#14253)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 10:23:26 -06:00
Tim O'Farrellandopenhands 49de7c8504 Remove openhands.core package (#14252)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 09:49:42 -06:00
Tim O'Farrellandopenhands 1a5a06174b refactor: remove OpenHandsConfig from settings and secrets stores (#14251)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 08:55:17 -06:00
Tim O'Farrellandopenhands 1da9e2b477 Migrate all jwt_secret consumers from OpenHandsConfig to JwtService (#14242)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-01 07:42:30 -06:00
Tim O'Farrellandopenhands d035bf1fe1 Remove mcp_config.py: drop TOML-based MCP configuration (#14241)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-30 12:49:59 -06:00
Tim O'Farrellandopenhands 2ed36cfa7d Remove legacy LLMConfig and all related code (#14239)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-30 12:25:43 -06:00
Tim O'Farrellandopenhands e4a6c4ae47 feat: introduce LLMModelService with DI, replacing legacy config-based model discovery (#14237)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-30 11:35:59 -06:00
Tim O'Farrellandopenhands e94b504eb4 refactor: move openhands.core.logger to openhands.app_server.utils.logger (#14235)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-30 08:41:19 -06:00
Tim O'Farrellandopenhands b55b975458 Remove unused OpenHandsConfig from RepositoryStore and UserRepositoryMapStore (#14234)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-30 08:33:26 -06:00
Tim O'Farrellandopenhands 18460a3461 refactor: move openhands.utils to openhands.app_server.utils (#14223)
Co-authored-by: openhands <openhands@all-hands.dev>
2026-04-30 07:47:02 -06:00