Skip to content

fix(agent): honor sessions_spawn ACP model overrides#70210

Merged
steipete merged 3 commits intoopenclaw:mainfrom
felix-miao:fix/acp-spawn-model
Apr 22, 2026
Merged

fix(agent): honor sessions_spawn ACP model overrides#70210
steipete merged 3 commits intoopenclaw:mainfrom
felix-miao:fix/acp-spawn-model

Conversation

@felix-miao
Copy link
Copy Markdown
Contributor

@felix-miao felix-miao commented Apr 22, 2026

Fixes #70200

Summary

  • Problem: sessions_spawn accepted a model parameter, but the ACP runtime branch dropped it before session initialization.
  • Why it matters: callers asking for ACP sessions with an explicit model silently got the agent default instead of the requested model.
  • What changed: ACP spawn now forwards model, initializes ACP sessions with runtimeOptions.model, and persists those options through the existing ACP manager path.
  • What did NOT change (scope boundary): this PR does not add ACP support for thinking; it only fixes model override handling.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: the ACP branch in sessions_spawn read model from tool input but never forwarded it into spawnAcpDirect, and the ACP initialize path had no way to accept initial runtime options.
  • Missing detection / guardrail: ACP spawn tests covered cwd, mode, and resumeSessionId, but not explicit model overrides.
  • Contributing context (if known): ACP already had persisted runtime-options support for per-session config, so this was a wiring gap rather than a missing backend capability.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/tools/sessions-spawn-tool.test.ts, src/agents/acp-spawn.test.ts, src/acp/control-plane/manager.test.ts
  • Scenario the test should lock in: ACP sessions_spawn with model carries the override through tool dispatch, ACP spawn initialization, and ACP manager runtime-option persistence.
  • Why this is the smallest reliable guardrail: the regression happened across the tool -> spawn -> manager seam, so each handoff now has focused coverage without requiring a live ACP backend.
  • Existing test that already covers this (if any): None.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

ACP sessions_spawn calls with runtime="acp" now honor explicit model overrides instead of silently falling back to the target agent default model.

Diagram (if applicable)

Before:
sessions_spawn(model=foo, runtime=acp) -> spawnAcpDirect() -> initializeSession() -> agent default model

After:
sessions_spawn(model=foo, runtime=acp) -> spawnAcpDirect(model=foo)
  -> initializeSession(runtimeOptions.model=foo) -> ACP session runs with requested model

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS darwin arm64
  • Runtime/container: local source checkout
  • Model/provider: ACP target agents, explicit model override path
  • Integration/channel (if any): ACP session spawn
  • Relevant config (redacted): default local dev config

Steps

  1. Call sessions_spawn with runtime: "acp" and an explicit model.
  2. Observe the ACP child session initialization path.
  3. Verify the requested model is persisted in ACP runtime options and used for the child session.

Expected

  • ACP child sessions honor the requested model override.

Actual

  • Before this change, ACP child sessions silently ignored model and used the target agent default.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: ran targeted tests for the sessions tool, ACP spawn path, and ACP manager path; committed through scripts/committer, which also ran pnpm check:changed --staged.
  • Edge cases checked: ACP model forwarding without thread binding; runtime-option persistence during ACP session initialization.
  • What you did not verify: live ACP backend behavior against a real external harness.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: ACP backends that reject model config options could still fail later when runtime controls apply.
    • Mitigation: this change only restores the documented override path and uses the existing ACP runtime-options/control plumbing that already handles backend capability checks.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR fixes ACP spawn model overrides by threading a model parameter through sessions-spawn-toolspawnAcpDirectinitializeAcpSpawnRuntimeAcpSessionManager.initializeSession, where it is now forwarded as runtimeOptions and applied via the existing validateRuntimeOptionPatch path. All three changed layers have corresponding unit tests added.

Confidence Score: 5/5

Safe to merge; the fix is minimal and well-tested with no P0/P1 issues.

The change is a focused, correctly-implemented bug fix with new unit tests at each layer. The only finding is a P2 style concern about a latent cwd erasure edge case that does not affect any current caller.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/acp/control-plane/manager.core.ts
Line: 314-317

Comment:
**`cwd: input.cwd` silently erases `runtimeOptions.cwd` when `input.cwd` is `undefined`**

`validateRuntimeOptionPatch` uses `Object.hasOwn` to detect keys, so spreading `cwd: input.cwd` after `...input.runtimeOptions` will always produce a `cwd` key in the patch object — even when `input.cwd` is `undefined`. That causes `validateRuntimeOptionPatch` to set `next.cwd = undefined`, erasing any `cwd` a caller put inside `runtimeOptions`.

No current caller passes `cwd` through `runtimeOptions` (they all use the top-level `input.cwd` parameter), so there is no breakage today. But the spread order is a latent trap as `runtimeOptions` support grows. Conditionally including `cwd` makes the intent explicit and protects future callers:

```typescript
const initialRuntimeOptions = validateRuntimeOptionPatch({
  ...input.runtimeOptions,
  ...(input.cwd !== undefined ? { cwd: input.cwd } : {}),
});
```

```suggestion
      const initialRuntimeOptions = validateRuntimeOptionPatch({
        ...input.runtimeOptions,
        ...(input.cwd !== undefined ? { cwd: input.cwd } : {}),
      });
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(agent): honor ACP spawn model overri..." | Re-trigger Greptile

Comment thread src/acp/control-plane/manager.core.ts
@felix-miao felix-miao changed the title fix(agent): honor ACP spawn model overrides fix(agent): honor sessions_spawn ACP model overrides Apr 22, 2026
@felix-miao
Copy link
Copy Markdown
Contributor Author

Confirmed root cause in src/agents/tools/sessions-spawn-tool.ts: the ACP path read model from tool input but never forwarded it into spawnAcpDirect, so ACP child sessions silently fell back to the target agent default model.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef3b7e2db4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/acp/control-plane/manager.core.ts
@steipete steipete force-pushed the fix/acp-spawn-model branch from 702a8be to 29a6fb6 Compare April 22, 2026 18:55
@steipete steipete merged commit 449cad5 into openclaw:main Apr 22, 2026
10 of 11 checks passed
@steipete
Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm test src/acp/control-plane/manager.test.ts src/agents/acp-spawn.test.ts src/agents/tools/sessions-spawn-tool.test.ts; pnpm check:changed
  • Source head: 29a6fb6
  • Merge commit: 449cad5

Thanks @felix-miao!

artisticlight added a commit to artisticlight/openclaw that referenced this pull request Apr 22, 2026
* fix: skip clean run-node runtime restaging

* fix: stabilize testbox test suite

* test: align plugin test contracts

* fix(agents): normalize malformed assistant replay content

* fix(agents): harden replay normalization guards

* fix(agents): distill replay content normalization

* fix: normalize assistant replay content (openclaw#69850) (thanks @fuller-stack-dev)

* Make harness failures fail honestly (openclaw#69981)

* Agents: fail honestly on harness errors

* Docs: clarify Codex harness fallback

* perf(ci): unblock node compat and trim runtime compat test

* docs: record testbox full-suite profile

* chore(agents): prefer local validation over testbox

* refactor: generalize route target parsing

* refactor: drop provider reconnect shim

* test: generalize legacy state migration coverage

* refactor: keep plugin login policy out of core

* fix(googlechat): harden google auth transport (openclaw#69812)

* fix(googlechat): localize google auth gaxios compat

* fix(googlechat): declare undici for staged runtime deps

* fix(googlechat): harden google auth transport

* fix(googlechat): narrow credential file reads

* fix(googlechat): preserve auth proxy transport

* fix(googlechat): allow symlinked auth files

* fix(googlechat): atomically load auth files

* fix(googlechat): eagerly buffer auth responses

* fix(googlechat): cap auth response buffering

* fix(googlechat): pin staged auth runtime deps

* fix(googlechat): buffer auth responses as array buffers

* Update CHANGELOG.md

* fix(googlechat): reject unstreamed auth responses

* fix(googlechat): use ambient fetch for auth transport

* fix(googlechat): keep guarded auth fetch on runtime path

* fix(googlechat): align staged zod range

* chore(lockfile): sync googlechat zod spec

* test: reuse plugin auto-enable fixture environment

* refactor: remove plugin tool display overrides from core

* fix(agents): guard replay convert hook

* test: generalize media fetch token fixtures

* feat(tencent): remove Token Plan provider and auth (openclaw#69996)

Co-authored-by: albertxyu <albertxyu@tencent.com>

* docs: fix stale community links in README and CONTRIBUTING (openclaw#69945)

Co-authored-by: Jonathan Amponsah <amponsahjonathan442@gmail.com>

* docs: generalize core channel examples

* fix(config): enforce resolved runtime channel config

* fix(channels): thread runtime config through sends

* fix(telegram): isolate sent-message cache stores

* fix(channels): preserve setup promotion fallbacks

* docs: remove bundled channel examples from core types

* refactor: generalize voice audio compatibility

* perf(test): avoid bundled channel fallback in model override tests

* docs: generalize core routing comments

* refactor: gate setup promotion by manifest feature

* docs(tui): document local config repair flow (openclaw#69995) (thanks @fuller-stack-dev)

* docs(tui): document local config repair flow

* docs(tui): clarify local TUI examples

* docs(config): gate local TUI repair flow

* docs(tui): fix local repair docs

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* docs: generalize plugin runtime comments

* perf(test): skip setup promotion metadata fallback

* perf(slack): narrow runtime-setter + lazy-load 4 modules + narrow 2 SDK surfaces (openclaw#69317)

Lazy load modules showing a ~50% gateway startup performance improvement

* feat(tokenjuice): bundle the native adapter (openclaw#69946)

* feat(plugins): register embedded extension factories

* feat(tokenjuice): bundle the native adapter

* fix(tokenjuice): gate the bundled embedded extension seam

* fix(tokenjuice): refresh runtime sidecar baseline

* fix(plugins): harden bundled embedded extensions

* fix(plugins): install source bundled runtime deps

* fix(tokenjuice): sync lockfile importer

* fix(plugins): validate reused runtime dep versions

* fix(plugins): restore tokenjuice CI contract

* fix(plugins): remove tokenjuice dts bridge

* fix(tokenjuice): repair openclaw type shim

* fix(plugins): harden bundled runtime deps

* fix(plugins): keep source checkout runtime deps local

* fix(plugins): isolate bundled runtime dep installs

* fix(cli): keep plugin startup registration non-activating

* fix(cli): keep loader overrides out of plugin cli options

* runtime-taskflow: sync blocked waiting edge into durable jobs

* docs(skill): tighten duplicate triage mirror rules

* docs(tokenjuice): add bundled plugin guide (openclaw#70038)

* docs(tokenjuice): add bundled plugin guide

* docs(tokenjuice): sort nav entry

* docs(changelog): mention tokenjuice embedded support (openclaw#70039)

* Fix Codex auth handoff for the app-server harness (openclaw#69990)

* Codex: fix auth bridge token shape

* Codex: preserve selected auth tokens

* Codex: prefer selected profile id token

* Codex: honor inherited Codex home

---------

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>

* fix(agents): keep mocked OpenAI Responses on HTTP (openclaw#69815)

* fix(agents): keep mocked OpenAI responses on HTTP

* docs(changelog): add entry for mocked responses fix

* fix(release-check): assert bundled plugin runtime deps after packed postinstall (openclaw#70035)

* fix(release-check): assert bundled plugin runtime deps after packed postinstall

Release-check already validates source dist/extensions runtime deps are staged, but runPackedBundledChannelEntrySmoke never re-validates after the packed postinstall runs against the installed tarball. That gap is how 2026.4.21 shipped without @whiskeysockets/baileys in dist/extensions/whatsapp/node_modules, because the source staging passed while the installed layout was left broken.

Re-use collectBuiltBundledPluginStagedRuntimeDependencyErrors against the installed packageRoot right after runPackedBundledPluginPostinstall and fail release-check if any declared runtime dependency is missing from the plugin-local node_modules.

* fix(release-check): check postinstalled dep sentinels at packageRoot/node_modules

Codex review on openclaw#70035 caught that collectInstalledBundledPluginRuntimeDepErrors was pointing at dist/extensions/<id>/node_modules, but packed postinstall installs and probes sentinels at packageRoot/node_modules (see dependencySentinelPath in scripts/postinstall-bundled-plugins.mjs). The previous implementation would have falsely failed release-check on healthy packed installs while still missing the original WhatsApp regression.

Reuse discoverBundledPluginRuntimeDeps from postinstall-bundled-plugins.mjs so the release guard uses the exact same dep discovery and sentinel paths the packed postinstall uses. Update the test fixtures accordingly so they model the real install layout.

* feat(openai): add codex device-code auth and fix login options in menu (openclaw#69557)

Merged via squash.

Prepared head SHA: 4918ed6
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev

* fix: make slack socket health event-driven

* fix: clean up slack socket waiters on start hooks

* fix: use transport activity for stale health

* docs: update changelog for channel health (openclaw#69833) (thanks @bek91)

* ci: serialize parity gate scenarios

* ci: stabilize parity gate runner

* ci: pin qa parity tool profile

* ci: build runtime before parity gate

* test: harden qa parity config cleanup

* fix: drop stale socket mode opt-in

* test: harden qa parity runtime staging

* ci: build private qa parity runtime

* test: harden qa private runtime staging

* fix: harden tokenjuice host typing

* fix: keep custom pi tools executable

* tooling: add corepack pnpm fallback for git hooks

* qa: harden parity gate execution (openclaw#70045)

* fix: keep claude cli sessions warm (openclaw#69679)

* feat(cli): keep claude cli sessions warm

* test(cli): cover claude live session reuse

* fix(cli): harden claude live session reuse

* fix(cli): redact mcp session key logs

* fix(cli): bound claude live session turns

* fix(cli): reuse claude live sessions on resume

* refactor(cli): canonicalize claude live argv

* fix(cli): preserve claude live resume state

* fix(cli): close dead claude live sessions

* fix(cli): serialize claude live session creates

* fix(cli): count pending claude live sessions

* fix(cli): tighten claude live resume abort

* fix(cli): reject closed claude live sessions

* fix(cli): refresh claude live fingerprints

* fix(cli): stabilize MCP resume hash

* fix: preserve claude live inline resume (openclaw#69679)

---------

Co-authored-by: Frank Yang <frank.ekn@gmail.com>

* fix(pair): render /pair qr as media (openclaw#70047)

* fix(pair): render pair qr as media

* fix(gateway): preserve media reply threading

* fix(gateway): harden webchat media replies

* fix(plugin-sdk): keep trustedLocalMedia internal

* docs(changelog): note pair qr media fix

* Update CHANGELOG with recent fixes and enhancements

Updated changelog to include recent fixes and enhancements.

* fix(codex): unchain app-server defaults (openclaw#70082)

* place permission under each branch of bot permissions for discord docs (openclaw#69218)

Merged via squash.

Prepared head SHA: dd6ae52
Co-authored-by: epicseven-cup <59263116+epicseven-cup@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark

* fix: lower the log level from info to debug (openclaw#70108)

* fix(cli): keep provider-owned sessions through implicit expiry

* fix(cli): upgrade legacy mcp session reuse

* fix(gateway): preserve cli session binding metadata

* fix: update cli session changelog (openclaw#70106)

* fix(config): accept truncateAfterCompaction (openclaw#68395)

Merged via squash.

Prepared head SHA: bf45148
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819

* chore(pi): remove local pr prompts

Remove repo-local /landpr and /reviewpr prompt templates so maintainers use the externally maintained workflow instead.
These flows remain available from the external maintainers repo via globally installed Pi skills and prompts.

* fix(plugins): avoid doctor crash on legacy interactive state (openclaw#70135)

* fix(plugins): hydrate legacy interactive state

* fix(plugins): avoid doctor crash on legacy interactive state (openclaw#70135) (thanks @ngutman)

* fix(cli): stabilize oauth session auth epochs

* test(cli): cover oauth auth epoch continuity

* fix: update cli session changelog (openclaw#70132)

* fix: require cli auth epoch version (openclaw#70132)

* fix(qqbot): add interaction intents (openclaw#70143)

* feat(qqbot): add intents interaction

* fix(qqbot): add interaction intents (openclaw#70143) (thanks @cxyhhhhh)

---------

Co-authored-by: sliverp <870080352@qq.com>

* fix(codex): apply GPT-5 prompt overlay (openclaw#70175)

* ci: consolidate test shard fanout

* chore: update dependencies

* fix(agent): align pi session tool options

* ci: downsize blacksmith runners

* ci: run aggregate checks off blacksmith

* fix(gateway): harden WS pairing locality

* fix: fail closed on plugin integrity drift

* ci: keep long matrix aggregates on blacksmith

* build: refresh a2ui bundle hash

* ci: keep cpu-sensitive lanes larger

* fix(doctor): skip token generation for trusted-proxy and none auth modes (openclaw#59055)

runGatewayAuthHealth() only excluded 'password' and 'token' (with existing
token) from its needsToken check. When gateway.auth.mode was set to
'trusted-proxy' or 'none', doctor --fix would incorrectly:

1. Flag the config as 'missing a token'
2. Prompt to generate a gateway token
3. Overwrite auth.mode to 'token' in openclaw.json

This silently broke trusted-proxy deployments (common in SaaS/reverse-proxy
setups) by replacing the delegated auth mode with token auth.

The fix aligns runGatewayAuthHealth() with the existing
hasExplicitGatewayInstallAuthMode() in auth-install-policy.ts, which
already correctly returns false for 'password', 'none', and 'trusted-proxy'.

Co-authored-by: wujiaming88 <wujiaming88@example.com>

* fix: preserve restart continuations after reboot (openclaw#63406) (thanks @VACInc)

* gateway: add restart continuation sentinel

* gateway: address restart continuation review

* gateway: handle restart continuation edge cases

* gateway: keep restart continuations on threaded delivery path

* fix(gateway): harden restart continuation routing

* test(gateway): cover restart continuation edge cases

* docs(agent): clarify restart continuation usage

* fix: preserve restart continuations after reboot (openclaw#63406) (thanks @VACInc)

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* ci: move lightweight automation off blacksmith

* docs(changelog): fix cron attribution

* docs(changelog): correct cron contributors

* docs(changelog): thank cron contributors

* ci: skip aggregate fan-in after cancellation

* ci: refresh ci concurrency group

* fix(gateway): write restart sentinels atomically

* docs(changelog): note restart sentinel atomic writes

* fix(gateway): allow silent metadata-upgrade pairing for loopback CLI clients (openclaw#70224)

Loopback CLI clients (cli_container_local, shared_secret_loopback_local)
with valid shared-secret auth previously got disconnected with 1008
pairing required whenever the paired device record's platform or
deviceFamily string differed from what the CLI claimed at connect time.

PR openclaw#69431 added the shared_secret_loopback_local locality but deferred
the metadata-upgrade reason from the auto-approval allowlist. That
deferral created an unrecoverable handshake loop in practice: every CLI
connect triggers a fresh metadata-upgrade request, the Control UI has
no approval surface for this reason, and non-interactive shells cannot
complete pairing. This broke every non-interactive openclaw agent use
case when paired device keys are replicated across hosts or installs
are migrated across platforms.

Extend shouldAllowSilentLocalPairing to auto-approve metadata-upgrade
for cli_container_local and shared_secret_loopback_local localities
only. Browser / Control-UI / remote paths retain existing approval-
required behavior. Gateway still logs every metadata refresh via the
existing security audit line for operator review.

Add 4 unit tests covering the decision table for metadata-upgrade
across all four localities.

Related: openclaw#69397, openclaw#69431

* refactor(gateway): unify startup task execution

* fix(pairing): clear stale requests on device removal (openclaw#70239)

* fix(pairing): clear stale requests on device removal

* docs(changelog): note pairing stale request cleanup

* perf(test): avoid bundled setup in auto-enable tests

* ci: rebalance agentic node tests

* fix(gateway): preserve restart continuation chat type

* ci: reduce blacksmith test pressure

* ci: offload short linux checks

* ci: keep build smoke on blacksmith

* ci: rebalance runtime config tests

* ci: consolidate short test workers

* ci: keep native lanes native scoped

* fix(dotenv): block connector endpoint workspace overrides (openclaw#70240)

* fix(dotenv): block connector endpoint workspace overrides

* docs(changelog): note dotenv endpoint blocklist

* fix(dotenv): block Matrix per-account scoped homeserver overrides

* feat: Add /models add hot-reload model registration (openclaw#70211)

* feat(models): add chat model registration with hot reload

* docs(changelog): add models entry for pr 70211

* fix(models): harden add flow follow-ups

* fix models add review follow-ups

* harden models add config writes

* tighten plugin boundary invariant

* move models add adapters behind sdk facades

* avoid ollama-specific core facade

* ci: reuse build artifacts for gateway topology

* telegram: align model picker callback auth (openclaw#70235)

* telegram: align model picker callback auth

* docs(changelog): note telegram model callback auth fix

* fix(telegram): use runtime config for model callback auth

* Revert "ci: reuse build artifacts for gateway topology"

This reverts commit be31776.

* ci: trim gateway watch build profile

* ci: keep workflow edits off windows lane

* ci: parallelize additional boundary guards

* fix: load staged dist-runtime plugins in docker

* test: stabilize audio directive tag test

* fix: add plugin load debug shape

* fix(agents): accept silent no-reply turns

* fix: default claude cli to stdio sessions

* ci: add fast docker install smoke

* docs: update claude cli stdio notes

* fix(config): preserve source config during recovery

* test: keep loader fixture inside plugin boundary

* fix(discord): thread runtime config through guild actions

* fix(discord): use resolveDiscordChannelParentIdSafe in voice command path

openclaw#69908 switched native slash commands, listeners, and the model picker to
the safe accessor for partial thread channels, but the voice /join command
still reads channel.parentId through the unsafe "parentId" in channel
pattern. Route it through the same helper so the voice command path does
not crash with "Cannot access rawData on partial Channel" when invoked
from inside a thread on @buape/carbon >=0.16.

* fix(discord): use resolveDiscordChannelNameSafe for voice channel override name

Applies the same safe-accessor pattern to the adjacent name field.
If @buape/carbon implements name as a getter that also reads _rawData
(like parentId), the previous `"name" in channel` pattern would throw
for the same reason. Aligns with the fix for parentId in the same call
site.

* fix(plugins): harden bundled runtime dep staging

* fix: harden Discord voice commands in threads

* ci: run install smoke for runtime dep staging

* fix(hooks): standardize outbound routing metadata

* test(slack): drop obsolete adapter hook test

* ci: downsize install smoke runner

* fix(discord): make thread parent inheritance opt-in

* fix: make Discord thread parent inheritance opt-in (openclaw#69986) (thanks @Blahdude)

* refactor: move channel doctor migrations to plugins

* refactor(memory): migrate lancedb recall to prompt-build hook

* refactor: build channel setup input generically

* tooling: finish corepack-only pnpm repo paths

* fix(agent-runner): accept injected normalizeMediaPaths in runAgentTurnWithFallback

* fix(agent-runner): share media-path normalizer with runAgentTurnWithFallback to prevent duplicate outbound media

* test(agent-runner): regression — createReplyMediaPathNormalizer.runtime not called when normalizer injected

* fix: share reply media context (openclaw#68111) (thanks @ayeshakhalid192007-dev)

* refactor: move doctor capabilities to channel manifests

* test: keep hook and slack tests on public boundaries

* refactor: declare channel add flags in manifests

* refactor: use memory slot defaults in core paths

* test: keep config fallback test on generic plugin channel

* fix(cli-session): only hash static extraSystemPrompt for session reuse

The extraSystemPrompt includes per-message dynamic content from
buildInboundMetaSystemPrompt() (timestamps, message IDs, sender metadata)
that changes on every inbound message. This causes the extraSystemPromptHash
to differ every turn, triggering a session reset with reason='system-prompt'
and discarding all CLI session context.

Fix: split extraSystemPrompt into dynamic (inbound meta) and static
(group context, group intro, group system prompt, exec override hints)
portions. Only hash the static portion for session reuse validation.

The full extraSystemPrompt (dynamic + static) is still sent to the CLI
as before — only the session stability hash uses the static subset.

Fixes openclaw#70100

* fix: address review feedback — handle empty static prompt and remove stray blank lines

- Always pass extraSystemPromptStatic as string (even when empty) so the
  fallback in prepare.ts never accidentally hashes dynamic content
- Use explicit undefined check (params.extraSystemPromptStatic !== undefined)
  instead of ?? nullish coalescing to avoid edge case where empty static
  string falls through to hashing the full dynamic prompt
- Remove extra blank line

* fix(cli-session): forward static prompt hash input

* fix: stabilize Claude CLI session prompt hashing

* fix(hooks): expose typed gateway startup context

* fix(plugins): preserve source activation config

* ci: use dist cache instead of artifact upload

* refactor(hooks): centralize bundled subagent hook wiring

* refactor(discord): centralize thread channel context

* refactor(discord): share channel action param parsing

* refactor(discord): share partial channel test fixtures

* ci: parallelize extension batch groups

* fix(discord): break monitor threading import cycle

* refactor(hooks): centralize matrix subagent hook wiring

* fix(hooks): prefer shared outbound conversation context

* ci: fold build smoke into artifact job

* test(memory): drop stale dreaming hook doubles

* fix: honor ACP spawn model overrides (openclaw#70210)

Honor explicit ACP sessions_spawn model overrides and preserve ACP runtime cwd options.\n\nThanks @felix-miao.

* fix(hooks): canonicalize thread ownership conversation ids

* test(acpx): exercise registered reply_dispatch hook

* fix: persist CLI session clearing atomically (openclaw#70298)

Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.

* ci: add scoped docker gateway e2e

* test(skill-workshop): exercise registered prompt hook

* fix: drop silent parent replies while subagents are pending (openclaw#69942)

Drop bare parent NO_REPLY payloads while spawned subagents are pending, preserving quiet parent turns until child completion delivers the real reply.\n\nThanks @neeravmakwana.

* ci: rotate stale concurrency group

* test(memory): exercise registered auto-capture hook

* ci: skip windows for test-only changes

* fix(auto-reply): preserve streaming reply directives (openclaw#70243)

Preserve streamed MEDIA/reply/audio directives across chunk boundaries and phase-aware final_answer delivery.\n\nThanks @zqchris.

* test(memory): exercise registered auto-recall hook

* Fix Slack HTTP route registry dispatch

* fix: route Slack HTTP webhook dispatch (openclaw#70275) (thanks @FroeMic)

* ci: narrow windows check scope

* fix(slack): pass cfg into resolveToken from downloadSlackFile call site

Commit 95331e5 ("fix(channels): thread runtime config through sends")
migrated resolveToken to a 3-arg signature (explicit, accountId, cfg) and
updated the getClient call site at actions.ts:83. The sibling call inside
downloadSlackFile at actions.ts:445 was not migrated and still dropped
opts.cfg, so the cfg-only resolution branch was unreachable from that path.

Current production callers (action-runtime.ts:386-389) always inject a
resolved readToken into opts.token before calling downloadSlackFile, so
this is defense-in-depth today -- the broken path is not hit in runtime.
Landing this closes the call-site migration gap and adds test coverage
for the cfg-only resolution contract on downloadSlackFile.

Note: pre-commit typecheck hook bypassed because upstream/main has 14
pre-existing TS errors in unrelated packages (discord, qa-lab, qqbot,
slack/monitor/provider.ts, tokenjuice, pi-embedded-runner) -- verified
reproducible on clean HEAD 4a16cf8 without this diff.

* fix: preserve Slack download cfg token fallback (openclaw#70160) (thanks @martingarramon)

* fix(telegram): mark polling transport dirty on 409 conflict (openclaw#69787)

When getUpdates returns 409 Conflict (e.g.
'terminated by other getUpdates request'), the polling runtime
previously retried on the same HTTP keep-alive TCP socket because
markDirty() was only called in the isRecoverable branch.

Telegram treats that connection as the 'old' session and keeps
terminating it — producing a sustained low-rate 409 retry loop
(observed a few per minute after eliminating duplicate pollers).

Broaden the dirty-mark condition to fire on isConflict as well as
isRecoverable so the next cycle forces a fresh TCP connection.

Update the existing 'reuses transport after getUpdates conflict' test
— which previously locked in the buggy behavior — to assert the new
correct behavior: one fresh transport is built, the stale one is
closed.

* test(telegram): update monitor test for openclaw#69787 transport rebuild on 409

Sibling test in monitor.test.ts asserted the pre-fix behavior (single
transport reused across cycles on 409). My openclaw#69787 change rebuilds the
transport on 409 so Telegram sees a fresh TCP socket — update the
assertion to match.

Two transports are now expected: the initial one plus the rebuild
after the conflict.

* test: tighten Telegram polling conflict coverage (openclaw#69873) (thanks @hclsys)

* test(plugins): guard legacy bundled hook regressions

* fix(telegram): lower webhook callback timeout to 5s

openclaw#16763 added `onTimeout: "return"` with `timeoutMilliseconds: 10_000`
(grammY default). In practice, Telegram's webhook servers abort the
read well before 10s when handler latency is LLM-bound: `getWebhookInfo`
reports `last_error_message: "Read timeout expired"` and pending updates
pile up, cascading into multi-minute reply lag.

Reproducible A/B on identical infra (same region, same bot token):
- Minimal Python echo bot: 5 back-to-back webhook RTTs 341-642ms, clean.
- OpenClaw current main: intermittent Read timeout expired, 1-5 min lag.

The handler still runs to completion; only the Telegram-facing ack is
sooner. grammY's deployment guide suggests 5s for long-running handlers.

No new config surface; minimal one-line change to the existing constant
and its test assertion. If a configurable timeout is wanted, that can be
a follow-up (see stale openclaw#7754).

* fix: lower Telegram webhook callback timeout (openclaw#70146) (thanks @friday-james)

* fix(amazon-bedrock): inject cache points for application inference profile ARNs (openclaw#69953)

* fix(amazon-bedrock): inject cache points for application inference profile ARNs

pi-ai's internal supportsPromptCaching checks model.id for specific Claude
model name patterns (e.g. "-4-", "claude-3-7-sonnet"), which fails for
application inference profile ARNs that don't contain the model name.
This causes prompt caching to silently break for Bedrock users with
application inference profiles.

Work around this by detecting when pi-ai would miss cache point injection
(via piAiWouldInjectCachePoints mirror) and patching the Converse API
payload via onPayload to add cachePoint blocks to the system prompt and
last user message — matching the same format pi-ai uses natively.

The fix is safe:
- Checks for existing cache points to avoid double-injection
- Respects cacheRetention: "none"
- Defaults to "short" retention (matching pi-ai default)
- Becomes a no-op once upstream pi-mono#2925 is fixed

Fixes openclaw#19279
Upstream: badlogic/pi-mono#2925

* fix(amazon-bedrock): tighten app-profile cache injection

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* test(plugins): pin bundled hook registration surfaces

* perf: keep gateway live probes off helper imports

* test(plugins): pin bundled hook names

* test: cover Telegram webhook timeout reply continuation

* fix(hooks): fail open without thread ownership routing

* fix(hooks): canonicalize slack thread ownership ids

* fix(plugins): repair bundled deps on activation

* fix(hooks): normalize thread ownership channel allowlists

* fix: clear phantom Claude CLI resumes (openclaw#70317)

Verify Claude CLI session transcripts before reuse and clear phantom bindings with transcript-missing instead of passing stale --resume ids.\n\nFixes openclaw#70177.

* fix(discord): restore DM reactions and guild activation

* fix(gateway): redact audio payloads from chat history

* fix(media): load inbound media store URIs

* fix(agents): dedupe emitted TTS media

* fix(image): resolve custom provider model IDs

* docs: note media delivery fixes

* fix(hooks): normalize thread ownership slack id casing

* fix(hooks): track thread ownership mentions case-insensitively

* ci: move node aggregate checks off blacksmith

* fix(hooks): tighten thread ownership mention matching

* test(skill-workshop): pin disabled hook wiring

* ci: rotate main concurrency queue

* fix(hooks): skip skill workshop capture when review is off

* fix(discord): harden partial thread channels

* test(memory): pin disabled lifecycle hook wiring

* ci: merge short auto-reply node shards

* fix(hooks): use live config for memory dreaming runtime

* feat(commands): gate /models add with modelsWrite (openclaw#70321)

* fix: restore Pi embedded tool allowlist

Restore the Pi embedded session tool allowlist for OpenAI/OpenAI Codex GPT-5 runs and compaction sessions after Pi 0.68.1 began treating session tools as a global allowlist.

Local validation: pnpm check:changed.
GitHub validation: check/check-additional/node shards green; parity gate red on unrelated config.patch stale/rate-limit QA harness scenario after plugins.allow restart.

* ci: rotate cancelled docs queue

* fix(hooks): refresh active memory config at runtime

* ci: balance extension tests across fewer workers

* fix: normalize opus 4.7 context window

Normalize Anthropic-owned Opus 4.7 context reporting to 1M while keeping inferred and bare discovery paths conservative.

- normalize Anthropic and claude-cli Opus 4.7 runtime/status context metadata to 1M
- keep inferred-provider and bare discovery ids on discovered conservative limits
- add regression coverage for provider, lookup, status, and discovery-cache paths
- keep the Telegram abort-signal wrapper typing narrow so changed-scope validation stays green

* fix(hooks): respect live skill workshop config

* fix(hooks): use live thread ownership config

* perf(plugins): cache normalized jiti aliases

* fix: honor explicit strict-agentic retry contract

Honor explicit strict-agentic execution contracts for incomplete-turn retry guards across providers, including local/compatible models that opt in without relying on OpenAI model inference.

Validation:
- pnpm test src/agents/pi-embedded-runner/run.incomplete-turn.test.ts
- pnpm check:changed
- GitHub CI + parity gate green

Thanks @ziomancer.

* test(media): harden media store URI validation

* ci: keep extension test fanout under two minutes

* fix(hooks): respect live lancedb memory config

* test(slack): cover send.ts customize-scope fallback retry path (openclaw#69009)

Adds 5 vitest cases for postSlackMessageBestEffort's silent retry
behavior when Slack rejects a chat:write.customize-identity post:

- Retry on err.data.needed matching chat:write.customize
- Retry on chat:write.customize in response_metadata.acceptedScopes
- Retry on chat:write.customize in response_metadata.scopes
- Rethrow on different missing_scope (e.g. channels:history)
- Rethrow when identity is empty (hasCustomIdentity returns false)

* fix(discord): normalize ACP thread binding targets

Normalize Discord ACP thread-binding channel targets at the REST/thread-create boundary while preserving current-conversation binding keys.\n\nThanks @Zetarcos.

* test(slack): provide send config in identity fallback tests

* fix(hooks): use live memory-core config during dreaming runs

* fix(memory-lancedb): retry failed runtime initialization

* runtime-taskflow: tighten waiting sync review fixes

* refactor(hooks): centralize live plugin config lookup

* fix(qa): deflake parity approval preflight

* fix(agents): centralize native websocket endpoint checks

* docs(changelog): note websocket endpoint classifier fix

* fix: clarify browser playwright-core install guidance

* fix(types): narrow live thread ownership config

* test(plugins): pin live config hook guards

* fix(agents): restore pi session tool activation

* docs(changelog): note pi session tool activation fix

* build: harden tsdown wrapper

---------

Co-authored-by: Shakker <shakkerdroid@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: pashpashpash <nik@vault77.ai>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: JuniperSling <80268751+JuniperSling@users.noreply.github.com>
Co-authored-by: albertxyu <albertxyu@tencent.com>
Co-authored-by: Jonathan <82057176+mgalore@users.noreply.github.com>
Co-authored-by: Jonathan Amponsah <amponsahjonathan442@gmail.com>
Co-authored-by: Alex Knight <aknight@atlassian.com>
Co-authored-by: Andy <andrew@artisticforge.studio>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Dewaldt Huysamen <dhuysamen@gmail.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: Bek <bek.akhmedov@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: Jacky <59263116+epicseven-cup@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: Sliverp <38134380+sliverp@users.noreply.github.com>
Co-authored-by: Ted Li <tl2493@columbia.edu>
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
Co-authored-by: cxy <49286167+cxyhhhhh@users.noreply.github.com>
Co-authored-by: sliverp <870080352@qq.com>
Co-authored-by: Garming <jiaming_wu@foxmail.com>
Co-authored-by: wujiaming88 <wujiaming88@example.com>
Co-authored-by: VACInc <hixvac@gmail.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Jason Perlow <jperlow@gmail.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Claw Kowalski <szponeczek@forserial.org>
Co-authored-by: Hana Chang <hana@hanamizuki.tw>
Co-authored-by: Oliver Camp <olivercamp@Olivers-MacBook-Pro-3.local>
Co-authored-by: ayeshakhalid192007-dev <ayeshakhalid192007@gmail.com>
Co-authored-by: Zijun Lin <zijunlin@Zijuns-Mac-mini.local>
Co-authored-by: Felix Miao <felix.us.miao@gmail.com>
Co-authored-by: HFConsultant <62076556+HFConsultant@users.noreply.github.com>
Co-authored-by: Neerav Makwana <neeravmakwana@gmail.com>
Co-authored-by: zqchris <chrisz83@gmail.com>
Co-authored-by: froemic <m.froehlich1994@gmail.com>
Co-authored-by: Martin Garramon <martin@yulicreative.ai>
Co-authored-by: HCL <chenglunhu@gmail.com>
Co-authored-by: friday-james <lamcollin37@gmail.com>
Co-authored-by: anirudhmarc <43162556+anirudhmarc@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Josh Lehman <josh@martian.engineering>
Co-authored-by: Devin Matthews <zionitesoldier@gmail.com>
Co-authored-by: Zetarcos <117005244+Zetarcos@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sessions_spawn: model parameter silently dropped on ACP runtime branch

2 participants