* feat(onboarding): recommend plugins and skills from installed apps
Scan installed macOS apps during classic onboarding (TCC-free), gather
candidates from official catalogs + ClawHub search, let the configured
model pick genuine matches, and offer an opt-in multiselect install step.
Adds a device.apps node-host command (default-off sharing, Android-parity
envelope) so remote gateways can request a paired Mac's inventory, and a
wizard.appRecommendations kill switch. Custom setup-inference completions
no longer inherit the 32-token verification-probe output cap.
* feat(onboarding): recommend apps in guided flow
* fix(onboarding): harden app recommendations against ClawHub self-promotion
Third-party ClawHub skills are never pre-selected regardless of model tier
(publisher-controlled listing text reaches the matcher prompt and could
promote itself); their labels now say they install third-party code.
Installed-app scans follow symlinked .app bundles. Matcher output stays
bounded by the resolved model's own maxTokens budget (documented invariant).
* fix(onboarding): key official catalog candidates by resolved plugin id
Real catalog entries are package manifests without a top-level id; keying the
candidate map and channel/provider classification by entry.id collapsed the
whole official catalog into one undefined-keyed entry, so no official plugin
or channel was ever recommended. Regression test runs against the bundled
catalogs.
* fix(onboarding): satisfy lint, types, deadcode, and migration gates
Split the guided-onboarding test into a self-contained custodian suite to stay
under max-lines. Narrow app-recommendation exports (drop dead node-payload
normalizer, unexport internal types/helpers, route candidate tests through the
public API), replace map-spread with a helper, unexport device.apps result
types, add installedAppsSharing to node-host migration expectations, cast the
wizard multiselect mock, and regenerate the docs map.
* test(onboarding): register new live test in the shard classifier
* fix(sqlite): bound fallback mount classification
* test(sqlite): cover mount probe failure policy
* test(sqlite): use tracked mount probe fixtures
* perf(sqlite): keep mount result import type-only
* style(sqlite): format mount probe result
* perf(sqlite): defer mount process module loading
* perf(sqlite): avoid eager mount parser dependency
* refactor(sqlite): isolate lock error detection
* revert: keep sqlite lock errors colocated
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(onboard): guided onboarding becomes the custodian flow
Question zero asks for discovery consent up front (full access recommended,
guarded asks once or configures manually; persisted as wizard.accessMode and
honored on reruns). Discovery quietly collects auto-ladder failures into one
summary with details behind 'See other options', quips about detected coding
agents, and announces the working route as a default with an easy undo.
After inference and the memory offer (skipped when discovery was declined),
fresh installs auto-apply the standard setup plan and hatch the agent
directly in the terminal; configured installs (pre-existing model or gateway
config) stay verification-only and never reinstall or restart the Gateway
service. Setup-apply failures fall back to the conversational chat.
Remote-gateway onboarding keeps its legacy chat handoff explicitly.
listManualSetupInferenceOptions lists manual providers without scanning.
* docs(onboarding): add custodian redesign implementation plan (living doc)
* docs: regenerate docs map for onboarding-redesign page
* fix(hooks): bound hook workspace manifest and HOOK.md reads
* fix(hooks): enforce maxBytes while reading hook metadata fd
ClawSweeper review noted that openRootFileSync validates stat size
before returning the fd, but the subsequent fs.readFileSync of the fd
was unbounded. A file that grows after validation could still OOM
workspace discovery. Replace the full fd read with a chunked bounded
reader that throws once maxBytes is exceeded, and add regression
coverage for the overflow case.
* test(hooks): cover bounded hook reads through public workspace API
* refactor(hooks): use canonical bounded fd reader for hook metadata
* fix(hooks): warn and skip oversized hook metadata during discovery
ClawSweeper review required the maintainer-selected oversized-metadata
diagnostic contract: warn and skip. readRootFileUtf8 now catches the
RangeError from the canonical readFileDescriptorBoundedSync helper and
emits one warning per oversized package.json/HOOK.md identifying the
path and the byte limit, while discovery continues for other hooks.
The redundant open-time maxBytes stat check is removed so the shared
bounded reader is the single owner of the byte cap; overflow always
surfaces as RangeError, keeping the warning accurate for both static
oversized files and post-open growth.
Add focused tests: warning content for both oversized surfaces,
continued discovery alongside an oversized hook, exact-limit acceptance,
and the plain-hook fallback when a package manifest is oversized.
* chore: retrigger CI after rebase
* refactor(hooks): tighten bounded metadata coverage
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(infra): gateway pid probe no longer hangs when ps sampling stalls
readGatewayProcessArgsSync probes ps to verify gateway argv before
signaling or reporting listener PIDs. The darwin branch already passed
timeout: 1000, but spawnSync defaults killSignal to SIGTERM, which an
unresponsive or hung ps child can ignore and keep the gateway process
blocked past the timeout.
Force killSignal: SIGKILL (matching the doctor probe fix in #109243) so
a stalled ps probe is reaped at the deadline instead of hanging gateway
signal verification and listener PID discovery.
Extract GATEWAY_PS_PROBE_TIMEOUT_MS for the named timeout and assert the
full spawnSync options (encoding / killSignal / timeout) in the darwin
test.
* refactor: centralize bounded ps probes
Co-authored-by: 琚耀辉0668001366 <ju.yaohui@xydigit.com>
* refactor: leave doctor ps hardening to main
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
@openclaw/codex is force-updated with core, so string plan steps can only
reach the normalizer from a plugin pinned against an update. Record that
as a TODO instead of a standing version-skew contract.
* fix: guard terminateStaleProcessesSync against non-ESRCH kill errors
The two catch blocks silently swallowed all errors, not just ESRCH.
Non-ESRCH errors (e.g. EPERM) indicate the process is still alive but
cannot be signaled — continuing as if it were killed risks leaving a
stale process on the port.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: continue stale PID cleanup after signal errors
Co-authored-by: 丁宇婷0668001435 <ding.yuting@xydigit.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(cli): guard secrets plan JSON.parse against malformed input
readPlanFile reads a user-specified file and parses it with JSON.parse
without a try-catch. A malformed or corrupted plan file produces a raw
SyntaxError that is not user-actionable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: tighten malformed secrets plan proof
Co-authored-by: 丁宇婷0668001435 <ding.yuting@xydigit.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(doctor): diagnose direct web fetch proxy routing
* test(doctor): cover proxy diagnostic through public API
* test(doctor): validate emitted proxy diagnostic type
Root cause of the flaky 120s timeout in "logs a swallowed finalize error
without blocking the background run" (CI run 29559328261, shard
agentic-gateway-methods): the preceding ACP plugin-subagent test registers a
subagent run whose completion keeps running in the background after the test
ends. That leaked chain (completeSubagentRunAttempt ->
emitSubagentEndedHookForRun -> ensureSubagentRegistryPluginRuntimeLoaded ->
ensureRuntimePluginsLoaded -> loadOpenClawPlugins) starts by calling
clearActivatedPluginRuntimeState(), which clears the process-wide detached
task lifecycle runtime registration. When it lands between the next test's
setDetachedTaskLifecycleRuntime(spy) install and that test's background
finalize, finalizeTaskRunByRunId re-reads the registration at call time,
falls back to the default executor, and finalizes the run successfully - so
the finalize spy is never called while the run itself completes with an ok
frame.
The prior event-driven rework (#109653, 883995f08d) could not fix this: it
only waits longer for a spy call that never comes. The older 2s
waitForAssertion flake ("expected 1 call, got 0") was the same mechanism.
Fix at the harness boundary: gateway agent handler tests now pin the
subagent-registry deps with a no-op ensureRuntimePluginsLoaded (matching
subagent-registry's own suites), applied at harness setup, in the shared
afterEach, and through the one wholesale deps override, so no leaked
ended-run hook can reload the plugin runtime and clear a later test's
runtime seam mid-test.
Proof:
- Deterministic repro (temporary env-gated interleaving patch in
subagent-registry.ts, not committed): hold the ACP plugin-child run's
ended-hook load until the next finalize-only runtime registration appears,
then reload synchronously. Pre-fix this reproduces the exact CI failure
(target times out at 120000ms; registration observed ABSENT right after
the reload). Post-fix, with the identical forced interleaving, all 238
tests pass and the registration stays present.
- Stress post-fix: 10x agent.test.ts and 5x src/gateway/server-methods with
OPENCLAW_VITEST_MAX_WORKERS=6 - 0 failures (3765 tests per shard run).
- The same wipe-then-re-register window exists in prod plugin activation
(loadOpenClawPlugins cache-miss path); flagged separately for an
owner-reviewed atomic-swap follow-up rather than bundled here.