Repository navigation
Description
Written-by: FlyBot Product
Cleanup, not a user-facing defect. The stage composer that used to call this endpoint was removed by #1461 on the owner's decision in #1459 ("Workflow does not need messaging"), so nothing in the product can send a workflow/say message today. What is left is dead surface that still lies when called: say() always records the text and, while a node runs, invents a reply ("收到。我继续处理「…」") that no Bot ever said. Owner has opened this line to us (2026-10-27): we implement it and will open a PR once the grep list below is confirmed.
Why now
#1455is closed as not planned: the entry point is gone (#1459/#1461).plugins/workflow/index.js:105still registers the endpoint, so any script or Bot that knows the name can call it and get a fabricated receipt.- The rest is dead weight for the next reader: one command in a docs sentence, one handler, one service method, one test half.
Grep inventory (clean tree at 8f1ac121)
grep -rn "workflow/say" .
./docs/subsystems/workflow.md:41:- `workflow/create`, `workflow/rename`, `workflow/delete`, `workflow/assign`, `workflow/concurrency`, `workflow/graph`, `workflow/advance`, `workflow/reset`, `workflow/say`, `workflow/ingest` write. ...
./plugins/workflow/index.js:105: fb.api.handle('workflow/say', body => service.say(body)),
grep -rn "say(" plugins/workflow --include=*.js
plugins/workflow/src/service.js:879: say(body = {}) {
plugins/workflow/index.js:105: fb.api.handle('workflow/say', body => service.say(body)),
plugins/workflow/test/assign.test.js:125:test('a read-only host refuses assign, and a say lands in the workflow chat', () => {
plugins/workflow/test/assign.test.js:132: const said = live.say({ workflowId: DELIVERY_ID, text: '请把 notice 再短一点' })
plugins/workflow/test/assign.test.js:135: live.assign({ workflowId: DELIVERY_ID, taskId: '#say', title: 'Notice tweak' })
plugins/workflow/test/assign.test.js:136: const withRun = live.say({ workflowId: DELIVERY_ID, text: '继续' })
grep -rn "收到。我继续处理" .
plugins/workflow/src/service.js:889: lines.push({ kind: 'bot', botId: running, step: '进行中', text: `${node?.name ?? running}:收到。我继续处理「${run.task?.title ?? ''}」。` })
No caller anywhere else: grep -rn "workflow/say" plugins/workflow/client.js core/ returns nothing, and no test asserts the registered-name list contains it.
Additional hit on live main 5b9d0d7 (Dev-2's grep):
grep -n say plugins/workflow/README.md
49:A reply that says the model could not answer (`could not answer your message` or `model call failed`) fails that node in the chat. It is not retried in a loop, and its successor does not start. A 429 or a rate-limit mention in an otherwise successful reply does not fail the node.
74:Endpoints, all under `flybot/workflow/`: `state`, `templates` (read), `create`, `assign`, `concurrency`, `advance`, `reset`, `say`, `graph`, `ingest`.
:49 is prose ("says") and stays; the endpoint list at :74 is the hit to remove.
Scope — delete exactly these
plugins/workflow/index.js:105— thefb.api.handle('workflow/say', …)registration.plugins/workflow/src/service.js:879-893— thesay()method, including the fabricated{ kind: 'bot', … }line at:889("收到。我继续处理…").docs/subsystems/workflow.md:41— only theworkflow/saytoken; that sentence lists ten write commands and the other nine stay.plugins/workflow/README.md:74— the baresayin the endpoint list (line 74 on live main5b9d0d7; it is the ninth of ten names). Remove the name and its comma; the other nine stay. The first inventory missed it because that list spells the endpoint without theworkflow/prefix, sogrep "workflow/say"does not see it.plugins/workflow/test/assign.test.js:125-140— the half that callslive.say()(and the/Planner|收到/assertion at:139); keep the read-onlyassignrefusal that shares this test, split into its own test.
Out of scope — do not touch (hard boundaries)
stageMessages()(plugins/workflow/client.js:137-145) and its two callers: filter and read-only stage chat stay exactly as they are.- Stored data:
state.logs, saved messages and run history stay as they are; no migration, no deletion, no rewrite ofworkflows.json. Old fabricated lines remain as historical records and are not edited. - Every other endpoint keeps its behaviour:
workflow/state,assign,advance,reset,pause,resume,graph,ingest,create,rename,concurrency,delete,templates. - The leftover
.wf-composerCSS inplugins/workflow/client.js:1270-1272is not part of this cleanup. - No new feature, no retry, no queue: deletion only.
Acceptance
- Both greps (
workflow/say,收到。我继续处理) return no hits in the tree, and the endpoint list atplugins/workflow/README.md:74no longer namessay. A changelog entry that names the removal is the only allowed hit; none exists today, so today the result is zero. node --test $(ls plugins/workflow/test/*.test.js)is green.- Reverse cases, one test each and still green:
workflow/statereads,workflow/assignassigns, a run advances,workflow/deletedeletes and releases. - The endpoint is gone, not emptied: the plugin's registered-endpoint list no longer contains
workflow/say, and asking the host for that name answersUnknown FlyBot endpoint workflow/saywith codebot/not-found(core/src/host/hooks.js:1044) — not{ ok: true }, not{}, notundefined. - If any generated artifact carries the name, regenerate it with the repo's build script; never hand-edit a generated file.
Effort
0.5 person-day.
Sequence
Dev-2 posts the full grep list first (including the generated-artifact check); code changes only after that list is confirmed. Stop at local READY — no PR until then.
Activity
chore(workflow): remove the dead workflow/say surface