Repository navigation
Description
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 todayToday 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
trueforany dispatch carries the owner message; - a second test asserts that a
say()whose text was not dispatched leaves nokind: 'bot'receipt in the log (today:864always 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 hostConversationpath 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 touchplugins/workflow/src/service.jsat 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 asIdle;#1446(open) duplicate workflow names;#1441(open) deleting a workflow during a live run;#1424(open) a reaped run keepsnodeStatus: running;#1445(open) run Bots keep the team tools;#1439(open) a node card should show tools. None touchessay()orstepBody().- 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
HugoXK commentedon Oct 27, 2026
Closing as not planned: the surface this defect lived in has been removed.
#1459is 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.#1461merged 2026-10-27T14:39:29Z (merge commit952d6358) and removed the stage composer.plugins/workflow/client.js:342now reads// Stage: view/control only — progress and flowchart; no message composer (#1459).8f1ac121: no caller ofworkflow/say. The string appears only atplugins/workflow/index.js:105(the handler) anddocs/subsystems/workflow.md:41(one command in a list); no client code sends it.