Commit Graph

44950 Commits

Author SHA1 Message Date
Sarah Fortune 67bcea131e feat(slack): add compact commentary progress (#126480)
* feat(slack): add compact progress style

* chore(slack): protect compact progress contract

* chore(slack): clarify compact progress comment

* chore(slack): document compact task card behavior

* chore(slack): document compact draft card behavior

* chore(slack): document compact progress config contract

* fix(slack): enforce compact commentary-only progress

* chore(slack): strengthen compact style guard

* fix(slack): type compact progress config

* fix(slack): scope compact plan suppression

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-08-20 15:02:33 -07:00
Peter Steinberger d00cbd1593 fix(web-fetch): honor Unicode BOM before HTTP charset (#126854) 2026-08-20 14:43:35 -07:00
Peter Steinberger a8a9f284fb fix(auth): create a fresh install with canonical shared-auth ownership (#126783)
* fix(auth): create a fresh install with canonical shared-auth ownership

A brand-new install was born in the retired shape. `parseSharedAuthStoreOwnership(undefined)` returns
`legacy-main`, which is the correct compat answer for an existing install whose profiles really do
live in the main agent database -- but a new install has no ownership row and no legacy data either,
so onboarding wrote its first credential into `agents/main/agent/openclaw-agent.sqlite` and the
operator's very first `openclaw doctor` told them to run a migration for state OpenClaw had created
seconds earlier. The main agent also stayed undeletable until they did.

Record `auth.sharedStore = {"location":"state-db"}` when the shared store is first written and the
legacy source provably holds nothing: no `auth_profile_store` row, no `auth_profile_state` row, and
no unfinished cleanup ledger entry. Any legacy row, or any inspection error, leaves ownership alone
so doctor keeps owning the relocation. The check is memoized per ownership generation with a WeakSet
keyed on the process-stable ownership object, so a legacy root is inspected once per process and
doctor's committed flip naturally invalidates it.

The legacy row inspection moves out of `state-migrations.shared-auth-store.ts` into the auth-profiles
owner so doctor and runtime share one contract instead of runtime importing migration code. Explicit
main-agent credential writes now follow the shared target, which is a no-op on legacy roots where
both routes already resolve to the same file.

No SQLite schema change; the ownership row is data. Existing installs take exactly the path they take
today.

* fix(auth): preserve JSON-era shared credentials

* docs(auth): explain why doctor names the main agent dir during a shared JSON import

* test(auth): assert shared-owner runtime reads

* test(doctor): read migrated catalog credentials through the shared owner

A fresh root records state-db shared ownership, so the model-catalog credential migration persists into the shared store rather than the agent file. The assertion read the agent file directly and saw an empty store while all three credentials were present and correct in state/openclaw.sqlite. Read through the owner for that state root instead of pinning storage layout; the credential contents are still asserted exactly.
2026-08-20 14:39:11 -07:00
Peter Steinberger 1d3d65e924 fix(workers): preserve results after scratch cleanup (#126849) 2026-08-20 14:29:54 -07:00
Peter Steinberger cd5002610d fix(web-fetch): extract readable content from XHTML pages (#126835) 2026-08-20 14:26:50 -07:00
Eden 4c52d3fe9d fix(cli): message send cannot address channels from npm-installed plugins (#126700)
* fix(cli): message send cannot address channels from npm-installed plugins

Target resolution, channel enumeration, and target-prefix inference only consulted the process-root channel registry, so message CLI actions running against a scoped registry handle could not see installed channel plugins even though selection and send execution could. Carry the selection-resolved plugin into target resolution, fall back to the registry handle in scope for resolver-owned lookups, and list runtime-visible channel plugins for channel selection and prefix inference.

* fix(cli): keep runtime-visible channel reads import-light

Importing channel-resolution from the target-prefix leaf pulled the plugin bootstrap/loader graph into every consumer and reordered module loading under distant vi.mock factories (subagent-registry.steer-restart failed in CI with a hoisting TDZ). Move the scoped-registry reads into a dedicated import-light module, share its registry matcher with channel-resolution, and drop the mock workarounds the heavier graph had required.

* chore(ui): re-baseline startup JS for the outbound scoped-registry reads

CI measured 348285 B gzip on the merge ref (baseline 347023 + 1056 tolerance). The first CI round measured 347784 B, so most of the growth is main-side drift since the 2026-08-19 baseline; the outbound changes account for roughly 60 B in a local A/B. Updated with the documented --update-baseline --startup-js-bytes flow using the CI value.

* Revert "chore(ui): re-baseline startup JS for the outbound scoped-registry reads"

This reverts commit f60bd4c45f.

* fix(cli): plan broadcast accounts from runtime-visible channel plugins

The unscoped message broadcast --account planner still enumerated only process-root plugins, so a registry-scoped installed channel could not join broadcast candidate planning. Use the runtime-visible read and cover the scoped and no-scope paths.

* fix(cli): honor scoped channel plugin precedence

* fix(outbound): preserve loaded plugin fallback order

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-20 13:56:01 -07:00
Peter Steinberger 21bab3a8ac fix(gateway): honor local port for usage and stability queries (#126832) 2026-08-20 13:51:36 -07:00
Peter Steinberger 6b3f7272dc fix(plugin-state): bound expired row cleanup (#126827) 2026-08-20 13:42:18 -07:00
Peter Steinberger ea77d4c708 fix(plugins): fence stale config-scoped loader failures (#126829) 2026-08-20 13:35:24 -07:00
Peter Steinberger 97c0455add fix(tui): route messages to the replacement agent session (#126820) 2026-08-20 13:04:25 -07:00
Josh Avant a4901b6291 fix(gateway): preserve managed Tailscale ingress compatibility (#126519)
* fix(gateway): accept tailnet peers on managed Funnel

* fix(gateway): release Tailscale claim on interrupt

* test(gateway): cover Funnel tailnet ingress

* docs: remove Tailscale changelog entry
2026-08-20 13:04:06 -07:00
Peter Steinberger cd04d3f82d fix(agents): stop suggesting unavailable tools in system prompts (#126792)
* fix(agents): keep system prompts truthful about available tools

* test(agents): preserve prepared message tool in delivery fixture

* test(agents): enable MCP for prepared CLI messaging fixtures
2026-08-20 12:46:55 -07:00
Peter Steinberger ba4ff7a1e9 fix(plugins): recover official catalog after failed loads (#126815) 2026-08-20 12:42:09 -07:00
Peter Steinberger b3a41e6516 fix(agents): preserve delivery causality in subagent retries (#126812) 2026-08-20 12:37:20 -07:00
Peter Steinberger 5e7f98ec1d fix(onboard): report health-check failures through the non-interactive failure owner (#126801)
A reachable gateway whose health check failed either escaped as a raw thrown
error or hit healthCommand's CLI-style runtime.exit(1), killing non-interactive
onboarding before logNonInteractiveOnboardingJson could emit the --json summary
and polluting JSON stdout with human diagnostic text. Route health failures
through logNonInteractiveOnboardingFailure (structured ok:false payload in
--json mode, framed text otherwise) via healthCommandNonExiting, and capture
healthCommand's human output off stdout in --json runs so the diagnostic lands
in the payload's detail field instead.
2026-08-20 12:25:30 -07:00
Peter Steinberger 4249df34b4 improve(code-mode): call tools as global functions (#126262)
* feat(code-mode): expose tools as global functions

* fix(code-mode): harden callable tool composition

* fix(code-mode): reserve private guest globals

* fix(cron): migrate legacy code-mode triggers

* fix(code-mode): align final tool result contracts
2026-08-20 12:18:23 -07:00
Peter Steinberger 73555c5fdd fix(doctor): stop asking a fresh install to repair a registry it never had (#126799)
The first `openclaw doctor` on a freshly onboarded install reported "Persisted
plugin registry is missing or stale. Repair with `openclaw doctor --fix`".
Nothing was wrong: the `installed_plugin_index` row had never existed, `plugins
list` reported 148 plugins without it, no retired `plugins.installs` records
were present, and starting the gateway once builds the row by itself -- measured
going 0 -> 1 across a single `gateway run`.

`preflightPluginRegistryInstallMigration` returned a single `action: "migrate"`
whether the index was absent or unreadable, and the health issue name
`registry-missing-or-stale` shows the conflation: missing and stale are
different states and only one is a problem.

Split that into `initialize | migrate`. A root with no persisted index, no
install records, and no retired config records is initialization the gateway
owns, so doctor stays quiet. Install records without a readable index stays a
migration, as does a config still carrying retired `plugins.installs` records,
or a caller that supplied no config to prove otherwise. `doctor --fix` still
builds the index in every case -- this changes the warning, not the repair.

Production +5 LOC.
2026-08-20 12:02:42 -07:00
Peter Steinberger 0d555266e4 refactor(nodes): retire legacy runner inventory parsing (#126773) 2026-08-20 11:53:00 -07:00
Peter Steinberger 28ec823c3b fix(wizard): health-check failures no longer kill the configure/onboard/doctor flow (#126758)
* fix(wizard): keep embedded health-check failures visible instead of exiting mid-flow

healthCommand's reachable-gateway auth diagnostic paths call runtime.exit(1),
which with defaultRuntime hard-kills the hosting configure wizard, onboarding
finalize, or doctor daemon flow mid-render — dropping the failure framing,
docs guidance, and outro. Add healthCommandNonExiting, which traps that
CLI-style exit into ExitError so the host flow owns the outcome, and use it
at every embedded call site.

Also fix the doctor e2e harness createConfigIO mock missing configPath, which
broke doctor.runs-legacy-state-migrations on current main.

* fix(onboard): reflect a failed health check in the finalize outro

A reachable gateway whose health check failed still ended onboarding with the
plain success outro because completion gating only read the earlier
reachability probe. Record the health outcome as its own fact and end with a
dedicated outro pointing at openclaw health. (ClawSweeper P1 on #126758.)
2026-08-20 11:42:18 -07:00
Dallin Romney 8fdc6d7c36 test(gateway): bind archive terminal PTYs by owner (#126767) 2026-08-20 11:32:31 -07:00
Dallin Romney 2f602fd382 fix(plugin-sdk): deduplicate API diff declarations (#126725) 2026-08-20 11:31:48 -07:00
Peter Steinberger 0e781ea8d2 fix(skills): reuse plugin metadata for execution workspaces (#126720)
* fix(skills): reuse metadata for execution workspaces

* test(skills): make upload lease heartbeat deterministic
2026-08-20 11:22:22 -07:00
Peter Steinberger 2006049629 fix(onboard): reject a malformed API key from the environment too (#126776)
`openclaw onboard --openai-api-key 'openclaw onboard --auth-choice ...'`
correctly refuses with "Paste the API key value, not an OpenClaw onboarding
command", exits 1, and writes no config. The identical value supplied through
`OPENAI_API_KEY` -- the form `docs/start/wizard-cli-automation.md` documents for
automation -- exited 0 with empty stderr and persisted the command string as the
credential.

`isMalformedApiKeyInput` was already imported into this file but guarded only
the `flagKey` branch; both `resolveEnvKey()` branches returned unchecked. The
operator finished onboarding believing they were configured, and nothing told
them otherwise until an agent turn failed or they happened to run
`openclaw doctor`, which classifies that exact value as `malformed_api_key` and
prints the hint they never saw.

Route every operator-supplied key -- flag, env, and secret-ref env -- through one
guard, and name the environment variable in the message so an operator with
several exported keys knows which one is wrong. The stored-profile branch stays
unguarded on purpose: a bad key already on disk is doctor's to diagnose, and
refusing there would strand someone re-onboarding to replace it.
2026-08-20 11:15:48 -07:00
Peter Steinberger 8efbf7ce00 fix(ui): session hovercards stop replaying unchanged PR status (#126726)
* perf(ui): avoid replaying unchanged session PR subscriptions

* test: ratchet assertion safety baseline

* test(ui): assert service worker update outcomes

* test(slack): avoid wall-clock rate-limit assertion
2026-08-20 11:11:40 -07:00
Peter Steinberger 2054c769b7 fix(sessions): query child entries directly (#126770) 2026-08-20 11:10:25 -07:00
Peter Steinberger 7f9a46ea82 fix(gateway): recover chat when session subscriptions stop responding (#126760)
* fix(gateway): prevent stalled session subscriptions from freezing chat

* fix(gateway): retire timed-out session subscription leases

* fix(gateway): preserve subscription recovery failure cause
2026-08-20 11:06:26 -07:00
Peter Steinberger b439c2f588 fix(sessions): warn when reset retains worktree (#126771) 2026-08-20 11:04:24 -07:00
Peter Steinberger a2051c9bb2 fix(onboard): support password-authenticated remote gateways (#126768)
* fix(onboard): support password-authenticated remote gateways

* fix(onboard): parse remote password without type assertions
2026-08-20 11:02:25 -07:00
Peter Steinberger 4963599688 fix(cron): stop retrying permanent command failures as provider overloads (#126763)
* fix(cron): avoid retrying incidental overload status codes

* fix(cron): preserve authoritative command failure classification
2026-08-20 10:53:16 -07:00
Peter Steinberger 9bdc37f4ba fix(doctor): give the operator a way out of an unparseable config (#126757)
`openclaw onboard` refuses a corrupt openclaw.json and tells the operator to run
`openclaw doctor --fix`. Doctor then answered with one sentence -- "Config could
not be parsed or recovered ... refusing to apply repairs" -- named no next step,
and exited 1. The operator was left looping between two commands that pointed at
each other.

The refuse path also wrote openclaw.json.clobbered.<timestamp> and called it
"Original preserved", but it had not clobbered anything: at that point the
snapshot is a reread of the live file, so the copy was byte-identical to the
untouched config. Three failed runs left three identical copies.

Drop the copy and say what to do instead: name the file, state that it cannot be
repaired automatically, and point at `openclaw config validate` for the exact
parse position, hand-editing, or moving the file aside and re-running
`openclaw onboard`. Commands go through formatCliCommand so profile and
container invocations stay pasteable.

`doctor-config-preflight.ts` was the only caller of the public
preserveConfigSnapshotAsClobbered wrapper, so the wrapper, its factory entry and
its barrel export go too; the genuine recovery paths keep using the core helper
and still preserve real originals. Production -16 LOC.
2026-08-20 10:36:38 -07:00
Dallin Romney 28568d0c12 test(acp): close owned state database exactly (#126746) 2026-08-20 10:34:25 -07:00
Peter Steinberger 5e03d3c495 test(web-fetch): colocate extractor coverage with runtime owner (#126755) 2026-08-20 10:34:01 -07:00
Dallin Romney 7620ac28ba test(gateway): invalidate per-case session config (#126744) 2026-08-20 10:33:58 -07:00
Dallin Romney 34e5caa15a test: repair current-main agent E2E fixtures (#123125) 2026-08-20 10:32:44 -07:00
Peter Steinberger 3c5cecee06 refactor: compact recovery ownership and model metadata (#126751)
* refactor: compact recovery ownership and model metadata

* chore: respect release-owned changelog gate

* test: skip unsupported Windows permission assertions
2026-08-20 10:30:21 -07:00
Jony 3f2d3f9958 fix(onboard): honor explicit provider auth choice (#117883)
* fix(onboard): honor explicit auth when keeping the current model

Co-authored-by: Jony <13896935+zyz619963502zyz@users.noreply.github.com>

* test(onboard): preserve existing config across wizard snapshots

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Jony <13896935+zyz619963502zyz@users.noreply.github.com>
2026-08-20 10:28:02 -07:00
Peter Steinberger cd1de94dc5 fix(plugins): preserve strict load failures when reusing registries (#126737) 2026-08-20 10:15:07 -07:00
Peter Steinberger 62ed43dfd0 fix(ui): keep ad hoc visualizations inline and readable (#126729)
* fix(ui): keep ad hoc visualizations readable

* perf(ui): preserve startup bundle budget
2026-08-20 10:05:21 -07:00
Peter Steinberger bbbd70542b feat(sessions): recover offline device placements (#126284)
* feat(sessions): recover offline device placements

* chore(protocol): refresh session placement models

* perf(ui): lazy-load session placement recovery

* test(ui): remove dropdown timing assertion

* test(ui): await committed cloud recovery route

* fix(sessions): complete explicit abandonment locally

* fix(sessions): fence lists by runner availability

* fix(ui): preserve canonical session freshness

* fix(sessions): preserve recovery contracts after rebase

* test(ui): await durable cloud recovery entry

* test(gateway): complete current runner fixtures

* fix(gateway): publish runner availability edges

* fix(ui): preserve shared session freshness

* fix(ui): preserve canonical sidebar session state

* fix(sessions): preserve abandoned partials and run-owned replies

* fix(sessions): resume durable abandonment retries

* test(gateway): compose provisioning replay with runner availability

* fix(sessions): publish recovered move transitions
2026-08-20 09:59:59 -07:00
Peter Lee 41d05f6bdf fix(models): Web UI provider probe fails for direct-credential providers (#125816)
* fix(models): keep direct-credential gateway probes on isolated runtime generations

* fix(models): carry isolated-runtime probe mode on the loader type

Drop the call-site cast for the widened runner params: the lazy loader now
declares the isolated-read-only capable runEmbeddedAgent shape, keeping the
assertion-safety ratchet at its grandfathered baseline for this file.
2026-08-20 09:57:53 -07:00
Peter Steinberger ddb49f61ca fix(gateway): keep liveness probes working when config reload fails (#126736) 2026-08-20 09:52:29 -07:00
Peter Steinberger d8c3d94fb1 fix(cli): fence destructive offline fallback (#126743) 2026-08-20 09:49:16 -07:00
Peter Steinberger 58cda469f5 refactor(acp): simplify ownership plumbing (#126741) 2026-08-20 09:48:04 -07:00
Dallin Romney 14cfd111ad fix(media): tolerate sparse persisted facts (#123054) 2026-08-20 09:42:21 -07:00
Peter Steinberger 46c71b564e fix(config): validate config writes against the config being written (#126734)
* fix(config): validate config writes against the config being written

writeConfigFileFromContext passed the pre-write snapshot's plugin
metadata into strict validation. During onboarding that snapshot belongs
to an intermediate config written by agent creation, which has no plugin
entries, so its scoped manifest registry is empty. Validating the final
candidate against it made every plugin entry added by the same write look
unknown, and non-interactive onboarding warned that the openai and codex
entries it had just written were stale or uninstalled.

Drop the stale snapshot so validation resolves the manifest registry from
the candidate it is actually validating. Strict semantic validation is
unchanged, and the registry load stays lazy.

* fix(ci): raise the Control UI startup JS baseline to unblock main

main is red on the Control UI startup-JS ratchet: unrelated PR #126725 measures 348289 B and this branch measures 348351 B against a 347023 B baseline + 1056 B tolerance. Twenty-six UI commits have landed since the last bump (#126474), none individually large. Baseline moves to the CI-measured 348351 B, well under the 358400 B maintainer-approved ceiling that still guards cumulative creep.
2026-08-20 09:32:57 -07:00
Vito Cappello 094873902b fix(agents): retire delivered requester finals (#123285)
* fix(agents): retire delivered requester finals

* fix(agents): bind requester final receipts before yielded settlement

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-20 09:02:08 -07:00
Peter Steinberger 8b448439b6 fix(plugins): retire executable plugin caches on metadata lifecycle clears (#126719)
Plugin install, replacement, and uninstall clear process memos through
registerPluginMetadataProcessMemoLifecycleClear, but four executable-
authority caches never registered, so retired plugin callbacks kept
executing after the registry moved on:

- createConfigScopedPromiseLoader (document/web-content extractor lists)
  now self-registers its clear at the factory, so no caller can leak
  resolved plugin callbacks past a lifecycle change.
- Provider policy surface maps (bundled + external, including cached
  negative entries) clear on lifecycle changes.
- Public surface loader now drops module exports, loader closures, and
  native require cache entries, not just resolved locations.
- SDK facade loader registers the same clear for facade exports and
  loader state; imported-plugin history is preserved as diagnostics.

The tracked-roots + native-require eviction pattern from provider
discovery is extracted into clearPluginModuleLoaderLifecycleCache and
reused by provider discovery, doctor contracts, the public surface
loader, and the facade loader, removing two near-copies.

Regression tests fail pre-fix: replaced or uninstalled plugin callbacks
must not run after clearPluginMetadataLifecycleCaches, proven down to
on-disk artifact replacement through the native require chain.
2026-08-20 08:58:05 -07:00
Peter Steinberger 8feeffb1a0 perf(tests): skip incidental Claude capture waits (#126717) 2026-08-20 08:52:30 -07:00
Peter Steinberger e50f73d331 fix(onboard): activate media-only provider authentication (#126711)
* fix(onboard): activate media-only provider authentication

* refactor(onboard): keep media model defaults plugin-private
2026-08-20 08:43:40 -07:00
SunnyShu 9bba88dbba fix(tasks): rank terminal tasks by completion and keep Recent terminal-only (#123219)
* fix(tasks): rank terminal tasks by completion and keep Recent terminal-only

- updateTaskStateByRunId backfills lastEventAt from endedAt for terminal
  finalizers (mirrors markTaskTerminalById), keeping activity monotonic
- both taskUpdatedAt projections rank terminal tasks by the maximum
  available activity timestamp, healing stale rows while preserving later
  delivery/terminal-outcome events recorded after completion
- Tasks page Recent fetch filters to terminal statuses so queued/running
  rows cannot starve the Recent section

Related to #100911

* refactor(tasks): normalize completion at registry owner

Absorb terminal timestamp ordering into the canonical registry lifecycle boundary, remove duplicated projection and writer policy, and prove Recent remains visible behind 200 active tasks in Chromium.

Co-authored-by: SunnyShu0925 <shu.zongyu@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-20 08:38:57 -07:00