Two related improvements to the interactive `openclaw migrate <provider>`
flow, both surfaced by the onboarding post-install migration prompt that
landed in #81192.
1. `suppressPlanLog?: boolean` on `MigrateCommonOptions`
(`src/commands/migrate/types.ts`). When set, `migratePlanCommand`
skips the up-front `runtime.log(formatMigrationPlan(plan))` dump.
The interactive Codex selection picker and the "Apply this migration
now?" confirm still run. Wired from the wizard helper at
`src/wizard/setup.post-install-migration.ts` so that path no longer
shows the plan dump after the user has already confirmed at the
wizard prompt.
2. New "Accept recommended" sentinel row at the top of both Codex
selection pickers, with "Toggle all on" and "Toggle all off" moved
to the bottom. The cursor starts on "Accept recommended" so pressing
Enter at the default position submits the picker's `initialValues`
(the recommended set) — matching the visual state of the checkboxes.
Implemented in `skill-selection-prompt.ts`:
- Enter on the Accept sentinel sets `prompt.value` to
`opts.initialValues` and lets clack submit.
- Space on the Accept sentinel snaps `prompt.value` to
`opts.initialValues` so the visible checkboxes flip to the
recommended state. The user can then Enter to commit or continue
toggling individual rows. The Accept row itself is never persisted
in the submitted value list.
The existing Enter handler for "Toggle all on" / "Toggle all off"
stays unchanged.
3. Removed the "Skip for now" sentinel entirely. It was a single-
keystroke trap: with the picker cursor wrapping from Accept to Skip
via up-arrow (or via accidental down-arrows), Enter on Skip wiped
`prompt.value` to `[MIGRATION_SELECTION_SKIP]` and abandoned the
whole migration — including any items the user had already
confirmed in the previous picker. To exit without migrating, users
now navigate to "Toggle all off" (or use the `a` / `i` keyboard
shortcuts) to clear the selection; the apply phase then sees no
planned work and skips itself via the existing
`shouldSkipCodexApplyAfterInteractiveSelection` path.
Cleanup spans `migrate/selection.ts` (constants, `{ action: "skip" }`
variant, and the reconcile/resolve SKIP branches),
`migrate.ts` (the picker option rows and the
`if (selection.action === "skip")` handler blocks in both pickers),
and the corresponding tests.
4. Plugin selection hint relabelled from "Activate every recommended
plugin" to "Migrate every recommended plugin" so it matches the
skill hint and the prompt's own verb ("Migrate ... into this agent
now?").
Tests:
- `src/commands/migrate/skill-selection-prompt.test.ts` — Accept
sentinel cases (Enter and Space + Enter both submit initialValues);
Skip-related test removed; Skip row dropped from the picker fixture.
- `src/commands/migrate/selection.test.ts` — Skip-related sub-
assertions trimmed from the resolve/reconcile tests; the
"skip + toggle-off precedence" test renamed to "toggle-off precedence
over toggle-on" and Skip cases removed.
- `src/commands/migrate.test.ts` — four Skip-driven scenarios removed
(plugin-only skip, both-pickers skip, skip-skills-continue-to-plugins,
Codex subscription warning + skip).
- `src/wizard/setup.post-install-migration.test.ts` — call-args
assertion expects the new `suppressPlanLog` option.
Verification:
- `pnpm lint` clean
- `pnpm tsgo:core` + `pnpm tsgo:core:test` clean
- Touched test suites green (migrate 32/32, selection 17/17,
skill-selection-prompt 6/6, setup.post-install-migration 10/10).
`runtime-options.buildRuntimeConfigOptionPairs` translated
`AcpSessionRuntimeOptions.timeoutSeconds` into a
`session/set_config_option(configId: "timeout")` pair on every turn. Both the
control plane (`AcpSessionManager.applyManagerRuntimeControls`) and the ACPX
wrapper (`AcpxRuntime.setConfigOption`) sit between that pair and the backend:
- The control plane validates pairs against the backend's advertised
config-option keys and throws `ACP_BACKEND_UNSUPPORTED_CONTROL` for any
pair the backend did not advertise. claude-agent-acp does not advertise a
`timeout` alias.
- The wrapper then forwards remaining pairs to the delegate. The Codex ACP
command was already short-circuited there; every other command, including
claude-agent-acp, fell through.
Net effect on the reporter's scenario:
`sessions_spawn({ runtime:"acp", agentId:"claude", timeoutSeconds: 60 })`
failed at the control-plane validation with `ACP_BACKEND_UNSUPPORTED_CONTROL`
(and, had it reached the wire, claude-agent-acp would have answered
`-32603 Internal error / Unknown config option: timeout`, surfacing as
`ACP_TURN_FAILED: Internal error`).
Fix two layers:
1. Control plane (`src/acp/control-plane/runtime-options.ts`): add
`isTimeoutConfigOptionAdvertised(advertisedConfigOptionKeys)` and gate the
timeout pair on it. When advertised keys are unknown (`undefined` or
empty), keep emitting the pair — this preserves current behavior for
backends that have not produced a capability list yet. When advertised
keys are present but exclude every alias in
`RUNTIME_CONFIG_OPTION_ALIASES.timeoutSeconds`, skip the pair. The
per-turn timeout is still enforced in-process via
`AcpSessionManager.resolveTurnTimeoutMs` in `manager.core.ts`.
2. ACPX wrapper (`extensions/acpx/src/runtime.ts`): hoist the Codex
`timeout` / `timeout_seconds` suppression so it also applies to
claude-agent-acp commands. Add `isClaudeAcpCommand` mirroring
`isCodexAcpCommand` (package spec, binary, generated wrapper script).
This layer is defense in depth — relevant when callers reach the wrapper
without going through `applyManagerRuntimeControls`, or when advertised
keys are not yet known.
Coverage:
- `src/acp/control-plane/runtime-options.test.ts` (new) asserts:
- the timeout pair is omitted when advertised keys exclude every alias,
- the pair is kept when `timeout` or `timeout_seconds` is advertised,
- the pair is kept when advertised keys are unknown,
- model/thinking emission is unaffected.
- `extensions/acpx/src/runtime.test.ts` flips the previous
`forwards timeout config controls for non-Codex ACP agents` test, which
codified the buggy behavior, into a suppression assertion. Adds a
positive `still forwards non-timeout config controls for claude-agent-acp`
test and an `isClaudeAcpCommand` detector test.
Closes#81127