Skip to content

fix(workflow): a message typed into the stage chat never reaches the node that runs next #1455

Closed as not planned

Description

@HugoXK

Written-by: FlyBot Product

What happens

A message typed into a Workflow stage chat is logged and acknowledged, never delivered.

On f0133279, say() (plugins/workflow/src/service.js:854-868) appends the owner's text to the workflow log, and when a node is running it also writes a receipt as if that node had answered (:864: Planner:收到。我继续处理「…」。). The text itself never leaves the log: stepBody() (:337-352) — the object handOff() (:357) hands to dispatch — carries botId, nodeId, name, role, prompt, model, tools, brief, taskId, inputs, outputs, and no field for the message.

After #1452 the receipt is invisible too: stageMessages() (plugins/workflow/client.js:133-141) filters out bot lines whose botId is a workflow node, so the owner's own line is the only trace on the stage. The message therefore looks sent, is silently dropped, and nothing reports it.

Reproduction (deterministic, the plugin's own service — no host, no network)

import { createWorkflowService } from './plugins/workflow/src/service.js'

const dispatched = []
const roster = [{ id: 'bot-planner', name: 'Planner', role: 'plan' }]
let seq = 0
const svc = createWorkflowService({
  persist: false,
  now: () => 1_700_000_000_000,
  bots: () => roster,
  spawn: async () => `eph-${++seq}`,
  release: async () => {},
  dispatch: async (step) => { dispatched.push(step); return { ok: true } },
})

const made = svc.define({
  name: 'Lane',
  nodes: [
    { id: 'plan', name: 'Planner', role: 'plan', botId: 'bot-planner', prompt: 'Write the plan.' },
    { id: 'code', name: 'Coder', role: 'code', prompt: 'Implement it.' },
  ],
  edges: [{ from: 'plan', to: 'code' }],
}).workflows.find(item => item.name === 'Lane')

await svc.runNamed({ workflow: 'Lane', brief: 'Ship the note', task: '#1373' })
const TEXT = 'review 只看 diff,不要改代码'
svc.say({ workflowId: made.id, text: TEXT })
await svc.advance({ workflowId: made.id, nodeId: 'plan', outcome: 'done' })

console.log(dispatched.some(step => JSON.stringify(step).includes(TEXT))) // false today

Today the run prints:

PROBE E chat after say: {"user":"review 只看 diff,不要改代码","bot":{"botId":"plan","text":"Planner:收到。我继续处理「Ship the note」。"}}
PROBE E dispatched step bodies: [{"nodeId":"plan","carriesText":false},{"nodeId":"code","carriesText":false}]
PROBE E any dispatch carries the owner message: false

And with the #1452 filter applied to the same state (stageMessages is exported at plugins/workflow/client.js:133), what the stage actually renders is the owner's line plus the workflow's story so far — the receipt never appears:

PROBE G logged: [{"kind":"system","text":"Workflow「Lane」已创建 · 成员 Planner、Coder"},{"kind":"time","text":"刚刚"},{"kind":"system","text":"来自 Tasks 看板:#1373 Ship the note → 分配给 Workflow「Lane」"},{"kind":"system","text":"任务 #1373 Ship the note 已进入 Workflow,Planner 认领"},{"kind":"user","text":"review 只看 diff,不要改代码"},{"kind":"bot","botId":"plan","text":"Planner:收到。我继续处理「Ship the note」。"}]
PROBE G rendered in stage: [{"kind":"system","text":"Workflow「Lane」已创建 · 成员 Planner、Coder"},{"kind":"time","text":"刚刚"},{"kind":"system","text":"来自 Tasks 看板:#1373 Ship the note → 分配给 Workflow「Lane」"},{"kind":"system","text":"任务 #1373 Ship the note 已进入 Workflow,Planner 认领"},{"kind":"user","text":"review 只看 diff,不要改代码"}]
PROBE G dispatched bodies carry the text: [false,false]
PROBE G any dispatch carries the owner message: false

Expected

The text the owner types reaches the Bot that runs the next node.

Minimal shape: stepBody() carries the pending owner message (for example message: <latest user line> or messages: [...], cleared when a dispatch consumes it), and handOff() (:357) / the next dispatch passes it. say() already computes the running node (:861), so a follow-up dispatch to that node is a second option.

Never write a receipt that claims an answer the node never gave. If the host cannot deliver mid-step, record the message as queued and say so ("will go out with the next step") instead of fabricating 收到。我继续处理….

Acceptance, deterministic:

  • the repro above prints true for any dispatch carries the owner message;
  • a second test asserts that a say() whose text was not dispatched leaves no kind: 'bot' receipt in the log (today :864 always writes one).

Scope

plugins/workflow/src/service.js: say() (:854-868), stepBody() (:337-352), handOff() (:357), plus whatever consumes the new field. S–M. No core/ change is needed for the minimal shape; only if delivery is routed through the host Bot channel instead of the step body.

Reachability

The running instance is checkout e37f1f8, which predates the Workflows surface. This is not yet visible to users; it arrives with the next milestone update.

Ownership

plugins/workflow/** belongs to the workflow module. FlyBot reports this and does not implement it or open a PR for it.

Not a duplicate (as of my read)

  • #1451 (open) — on the host Conversation path the composer posts the previous draft. That is about which text is sent; this is that no text reaches a node even when the right text is sent. Independent in both directions.
  • #1452 (merged, f0133279) hid ephemeral node lines and restyled the stage; it did not touch plugins/workflow/src/service.js at all, so the delivery path is unchanged.
  • #1272 (open) is the umbrella "each node names its Bot and its inputs and outputs" engine issue; this reports a concrete gap in what shipped, not the design request.
  • #1448 (open) Settings canvas draws every node as Idle; #1446 (open) duplicate workflow names; #1441 (open) deleting a workflow during a live run; #1424 (open) a reaped run keeps nodeStatus: running; #1445 (open) run Bots keep the team tools; #1439 (open) a node card should show tools. None touches say() or stepBody().
  • Search counts: workflow say dispatch → 17; stage chat deliver node → 1 (#1272); workflow chat message → 4. No issue reports a typed message not reaching a node.

Activity

  1. HugoXK commented on Oct 27, 2026

    @HugoXK
    ContributorAuthor

    Closing as not planned: the surface this defect lived in has been removed.

    • #1459 is closed (2026-10-27T14:16:29Z). Its body carries the owner's decision: Workflow does not need messaging. Tasks are handed to the graph; pause/resume/progress only.
    • #1461 merged 2026-10-27T14:39:29Z (merge commit 952d6358) and removed the stage composer. plugins/workflow/client.js:342 now reads // Stage: view/control only — progress and flowchart; no message composer (#1459).
    • Repo-wide grep at 8f1ac121: no caller of workflow/say. The string appears only at plugins/workflow/index.js:105 (the handler) and docs/subsystems/workflow.md:41 (one command in a list); no client code sends it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions