Skip to content

Dashboard webchat turns can enter message-tool-only mode without a send target #81092

Description

@brandonlipman

Summary

Dashboard/webchat-originated sessions can enter sourceReplyDeliveryMode: message_tool_only even when there is no external source conversation target for the message tool to infer.

When that happens, the prompt tells the model that visible replies must use message(action="send") and that the target defaults to the current source conversation. In a dashboard-only turn, the dynamic message tool does not have a usable currentChannelId, so message.send fails with:

Action send requires a target.

The assistant can then still produce a normal final answer such as Sent., which is related to the broader fail-closed issue in #49876, but the target-default mismatch here is narrower.

Observed example

In a dashboard session, the user asked the agent to describe its tools. The agent generated a good message(action="send") payload but omitted target, following the prompt guidance. The tool result was Action send requires a target., then the assistant finaled with Sent..

Root cause

The source reply delivery policy can choose message_tool_only for direct sessions from config or harness defaults without excluding internal dashboard/webchat turns.

For external providers, this is fine because the runtime can pass a source target such as currentChannelId / OriginatingTo. For dashboard-only webchat turns, Provider, Surface, and OriginatingChannel are internal webchat, and there is no external OriginatingTo. The low-level target validator is correct to reject the send; the policy should not have selected message-tool-only delivery for that source in the first place.

Proposed fix

Keep automatic final delivery for internal dashboard/webchat-only turns, even if config or a harness default prefers message-tool delivery. Preserve message_tool_only when webchat is merely operating on behalf of an external origin or explicit deliver route.

Concretely:

  • Detect internal-only webchat sources in resolveSourceReplyDeliveryMode.
  • If the mode would be message_tool_only, coerce it to automatic when there is no external origin/deliver route.
  • Leave message.send target validation unchanged.
  • Add regression coverage for:
    • direct dashboard/webchat turns with global or harness message_tool defaults falling back to automatic
    • webchat routing an external source remaining message_tool_only
    • full dispatchReplyFromConfig dashboard/webchat turn queueing a normal final reply instead of suppressing it

Local validation

I tested this locally with a small patch following the above approach:

pnpm test src/auto-reply/reply/source-reply-delivery-mode.test.ts src/auto-reply/reply/dispatch-from-config.test.ts
# 2 Vitest shards passed: 19 policy tests, 107 auto-reply dispatch tests

pnpm exec oxfmt --check --threads=1 src/auto-reply/reply/source-reply-delivery-mode.ts src/auto-reply/reply/source-reply-delivery-mode.test.ts src/auto-reply/reply/dispatch-from-config.test.ts
# All matched files use the correct format.

pnpm tsgo:core:test
# passed

Repo-wide pnpm format:check still reports unrelated pre-existing formatting issues in other files; the three changed files pass oxfmt --check directly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions