openclaw - ✅(Solved) Fix ACP thread-bound Discord sessions need cleaner/customizable intro and terminal messages [2 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#65018Fetched 2026-04-12 13:26:01
View on GitHub
Comments
1
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
closed ×1commented ×1cross-referenced ×1

When a plugin uses api.runtime.acp.spawn() to create a thread-bound ACP session in Discord, OpenClaw currently emits core-owned boilerplate that is hard to customize or suppress:

  1. the ACP thread intro banner from src/channels/thread-bindings-messages.ts
  2. the terminal delivery message Background task done: ... from src/tasks/task-executor-policy.ts

This makes plugin-owned task orchestration threads noisy, especially when the plugin already has its own task lifecycle UX.

Root Cause

For programmatic ACP orchestration, the plugin may want to:

  • show a cleaner intro message
  • omit the cwd: line
  • omit the lifecycle boilerplate
  • suppress the generic Background task done: terminal delivery
  • replace those with plugin-specific task UX

Right now the plugin can clean up its own posts, but not these core-owned ones.

Fix Action

Fix / Workaround

  • downstream plugin: task-dispatch
  • OpenCode ACP session spawned into a Discord child thread
  • plugin uses the thread as a persistent task conversation
  • thread works, but core intro + terminal text are noisy and not customizable

PR fix notes

PR #63176: feat(plugins): expose ACP spawn and prompt in plugin runtime

Description (problem / solution / changelog)

Summary

  • Problem: Plugins can orchestrate subagents via api.runtime.subagent.* (run, waitForRun, getSessionMessages) but have no access to the ACP orchestration path — the more capable agent dispatch mechanism that supports session management, thread-bound delivery, and follow-up prompting.
  • Why it matters: Plugin authors who need to dispatch long-running coding agents (OpenCode, Codex, Claude Code) from chat workflows currently have to fork OpenClaw to access ACP helpers. This is a natural extension of the existing plugin runtime surface — subagent.* provides lower-level orchestration; acp.* provides the higher-level ACP-backed path. Both should be available to trusted plugins.
  • What changed: Add api.runtime.acp.spawn() and api.runtime.acp.prompt() to the plugin runtime, add the corresponding types/mocks, and gate behind plugins.allowAcpSpawn (opt-in, default false).
  • What did NOT change (scope boundary): Default plugin permissions unchanged. Subagent scopes unchanged. Discord thread binding behavior unchanged. ACP is not auto-enabled — requires explicit operator opt-in.

Production-validated: Running 3+ weeks in a Telegram → Discord agent dispatch pipeline, handling real coding tasks through ACP-backed sessions with thread bindings.

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)

N/A — new feature, not a fix.

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/plugins/runtime/index.test.ts
  • Scenario the test should lock in: ACP runtime helpers throw when allowAcpSpawn is disabled (default); delegate to spawnAcpDirect / callGateway when enabled.
  • Why this is the smallest reliable guardrail: The feature is a thin runtime export/gating seam — direct runtime tests lock the contract without needing a full plugin integration harness.
  • Existing test that already covers this (if any): pnpm build catches type/export/config wiring, but not runtime gating behavior.
  • If no new test is added, why not: N/A — two new tests are included.

User-visible / Behavior Changes

  • Plugin authors can call api.runtime.acp.spawn() and api.runtime.acp.prompt() when plugins.allowAcpSpawn: true is set in openclaw.json.
  • Operators who do not set the flag see zero change.
  • New config key: plugins.allowAcpSpawn (boolean, optional, default false).

Diagram (if applicable)

Before:
  Plugin → api.runtime.subagent.run()  → in-process subagent only
  Plugin → ❌ no ACP path available

After:
  Plugin → api.runtime.subagent.run()  → in-process subagent (unchanged)
  Plugin → api.runtime.acp.spawn()     → ACP-backed agent with thread binding
  Plugin → api.runtime.acp.prompt()    → follow-up into existing ACP session

Security Impact (required)

  • New permissions/capabilities? Yes
  • Secrets/tokens handling changed? No
  • New/changed network calls? Yes
  • Command/tool execution surface changed? Yes
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: This exposes a privileged orchestration capability (ACP agent spawning) to the plugin runtime, gated behind plugins.allowAcpSpawn (default false). The gate is checked on every call — not just at plugin load time. Only trusted plugins on operator-managed instances should enable this. No new scopes are introduced.

Repro + Verification

Environment

  • OS: macOS arm64
  • Runtime/container: Node 22 + pnpm
  • Model/provider: ACP-backed coding agents (OpenCode / Codex / Claude Code)
  • Integration/channel (if any): Telegram main workflow → Discord thread-bound coding agents
  • Relevant config (redacted): plugins.allowAcpSpawn: true

Steps

  1. Install a plugin that needs ACP orchestration (e.g. task-dispatch).
  2. Call api.runtime.acp.spawn() without allowAcpSpawn → clear error.
  3. Set plugins.allowAcpSpawn: true in openclaw.json.
  4. Call api.runtime.acp.spawn(...) → returns { status: "accepted", childSessionKey, runId }.
  5. Call api.runtime.acp.prompt({ sessionKey, text, channel, threadId }) → sends follow-up into the ACP session.

Expected

  • Disabled: throws api.runtime.acp.spawn() requires plugins.allowAcpSpawn: true in openclaw.json
  • Enabled: delegates to spawnAcpDirect / callGateway("agent", ...) and returns valid results

Actual

  • Both paths work as expected.

Evidence

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

pnpm test src/plugins/runtime/index.test.ts — 2 new tests pass (gate disabled → throws, gate enabled → delegates). pnpm build — clean. Production: 3+ weeks running task-dispatch plugin dispatching coding agents from Telegram to Discord threads via ACP.

Human Verification (required)

  • Verified scenarios: disabled gate throws clear error; enabled gate delegates to spawnAcpDirect and callGateway; existing-thread and fresh-thread ACP dispatch flows both work end-to-end in production.
  • Edge cases checked: missing allowAcpSpawn key (treated as false); gateway returning no runId from prompt (falls back to local idempotency key).
  • What you did not verify: every third-party ACP backend permutation; non-Discord delivery paths.

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
  • Config/env changes? Yesplugins.allowAcpSpawn (optional, defaults to existing behavior)
  • Migration needed? No — operators opt in by adding the flag
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Trusted plugins could spawn ACP agents without operator understanding the privilege level.
    • Mitigation: Explicit plugins.allowAcpSpawn gate, default false, clear runtime error message when disabled.
  • Risk: Runtime/API drift between ACP surface and plugin test mocks.
    • Mitigation: Mock updated in same PR; unit tests cover the seam.
  • Risk: acp.prompt falls back to local idempotency key when gateway returns no runId.
    • Mitigation: Documented behavior; callers should treat runId as a correlation ID, not a guaranteed server-side reference.

Changed files

  • extensions/discord/src/monitor/thread-bindings.discord-api.test.ts (modified, +30/-1)
  • extensions/discord/src/monitor/thread-bindings.discord-api.ts (modified, +15/-3)
  • src/config/schema.base.generated.ts (modified, +3/-0)
  • src/config/types.plugins.ts (modified, +6/-0)
  • src/config/zod-schema.ts (modified, +2/-0)
  • src/plugins/runtime/index.test.ts (modified, +132/-0)
  • src/plugins/runtime/index.ts (modified, +10/-0)
  • src/plugins/runtime/runtime-acp.runtime.ts (added, +77/-0)
  • src/plugins/runtime/types-core.ts (modified, +14/-0)
  • test/helpers/plugins/plugin-runtime-mock.ts (modified, +4/-0)

Code Example

⚙️ opencode session active (idle auto-unfocus after 24h inactivity). Messages here go directly to this session.
cwd: /path/to/workspace

---

Background task done: ACP background task (run abc12345).
RAW_BUFFERClick to expand / collapse

Summary

When a plugin uses api.runtime.acp.spawn() to create a thread-bound ACP session in Discord, OpenClaw currently emits core-owned boilerplate that is hard to customize or suppress:

  1. the ACP thread intro banner from src/channels/thread-bindings-messages.ts
  2. the terminal delivery message Background task done: ... from src/tasks/task-executor-policy.ts

This makes plugin-owned task orchestration threads noisy, especially when the plugin already has its own task lifecycle UX.

Current behavior

For a plugin-created ACP thread, the thread typically starts with something like:

⚙️ opencode session active (idle auto-unfocus after 24h inactivity). Messages here go directly to this session.
cwd: /path/to/workspace

And when the ACP task finishes, core posts:

Background task done: ACP background task (run abc12345).

In a downstream task orchestration plugin, those messages are visible even after plugin-side cleanup. They are core-owned strings.

Why this matters

For programmatic ACP orchestration, the plugin may want to:

  • show a cleaner intro message
  • omit the cwd: line
  • omit the lifecycle boilerplate
  • suppress the generic Background task done: terminal delivery
  • replace those with plugin-specific task UX

Right now the plugin can clean up its own posts, but not these core-owned ones.

Requested improvement

One of these would solve it:

  1. allow ACP spawns to pass optional overrides/suppression flags for intro + terminal text
  2. add config to suppress terminal task delivery for thread-bound ACP sessions created programmatically
  3. split the current hardcoded strings into a more configurable policy surface

Repro

  • downstream plugin: task-dispatch
  • OpenCode ACP session spawned into a Discord child thread
  • plugin uses the thread as a persistent task conversation
  • thread works, but core intro + terminal text are noisy and not customizable

Related PR

  • feat(plugins): expose ACP spawn and prompt in plugin runtime #63176

That PR enables the downstream plugin use case. This issue is the follow-up UX gap that remains after the ACP runtime surface is available.

extent analysis

TL;DR

To address the issue of noisy core-owned boilerplate in thread-bound ACP sessions, consider adding optional overrides or suppression flags for intro and terminal text when spawning ACP sessions.

Guidance

  • Investigate the feasibility of introducing optional parameters to the api.runtime.acp.spawn() method to allow plugins to customize or suppress the intro banner and terminal delivery messages.
  • Review the src/channels/thread-bindings-messages.ts and src/tasks/task-executor-policy.ts files to understand how the current boilerplate is generated and identify potential points for customization.
  • Consider adding a configuration option to suppress terminal task delivery for programmatically created thread-bound ACP sessions.
  • Evaluate the potential benefits of splitting the hardcoded strings into a more configurable policy surface to improve plugin customization capabilities.

Example

No specific code example is provided due to the lack of explicit code references in the issue, but the solution may involve modifying the api.runtime.acp.spawn() method to accept additional parameters, such as:

api.runtime.acp.spawn({
  // existing parameters...
  introMessage: 'Custom intro message',
  suppressTerminalDelivery: true
})

Notes

The solution may require changes to the OpenClaw core code and potentially affect other plugins or use cases, so thorough testing and review are necessary to ensure compatibility and avoid regressions.

Recommendation

Apply a workaround by introducing optional overrides or suppression flags for intro and terminal text when spawning ACP sessions, as this approach allows for more flexibility and customization while minimizing potential disruptions to existing functionality.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix ACP thread-bound Discord sessions need cleaner/customizable intro and terminal messages [2 pull requests, 1 comments, 1 participants]