Repository navigation
Description
Written-by: Chief
Owner decision 2026-10-30: the existing Project project-1fe64499 ("FlyBot 开发") is the one Project; every workflow line moves under it instead of living in a global list. Today a workflow definition has no Project at all — only a run can be bound (state.projectRuns), and nothing binds it — so "移到项目下" has no place to land.
Data model
- A workflow record in
workflows.jsongainsprojectId?: string. Absent ornull= unowned, and behaves exactly as today. workflow-saveacceptsprojectId: when it is a non-empty string it must name an existing Project (readProject), otherwise the save is refused with the reason and nothing is written. Saving withprojectId: nullclears it.- Authorization does not change:
workflow-savestays a signed-in write. Do not add a Project-owner check in this slice.
Binding a run (this is the part with no seam today)
The plugin cannot write host state, so add one seam beside the existing task seams (fb.tasks.onWrite, fb.tasks.onProjectProvider, plugins/workflow/index.js:158-176):
fb.projects.bindRun(runId, projectId)→ writesstate.projectRuns[runId] = projectId, validating the Project exists, using the samesetEntry(state.projectRuns, …)theproject-bindendpoint uses (core/src/host/store.js:537-551).- A host without the seam must not fail a run: the plugin logs once and keeps today's behaviour (unbound run).
Then in plugins/workflow/src/service.js:
assign({ workflowId, taskId, title, brief, projectId? })— whenprojectIdis given it wins; otherwise the workflow's ownprojectIdapplies; a run is bound at creation, so the Project board sees it from its first step.- A refused binding (unknown Project, or no seam) does not fail the assign.
Settings UI (plugins/workflow/client.js)
- The workflow list shows the Project name on each row (and "未归属" when it has none), with a filter that can narrow the list to one Project.
- The editor gains one "Project" picker fed by the host's
projectsendpoint; saving writesprojectId. - No new page, no new panel: this is the existing list and editor.
Tests
plugins/workflow/test/assign.test.js: an assign on a workflow withprojectIdbinds the run to it;projectIdin the assign call overrides; a workflow without one binds nothing; a binding refusal still starts the run.plugins/workflow/test/save-graph*.test.js(or the file that coversworkflow-save): an unknownprojectIdis refused and nothing is written;nullclears it; a save without the field keeps the stored value.core/test/: the newbindRunseam writesprojectRunsand validates the Project — one case each; a host without the seam answersundefinedrather than throwing.
Acceptance
npm run verify(TZ=UTC) green at the tip.- On a live host: assign a run of a workflow carrying
projectIdandGET project-boardfor that Project shows the row withrunId(the board already readsprojectOfMember('run', …),core/src/host/project-board.js:391-400). - A workflow with no
projectIdbehaves exactly as today. Fixes #<this>in the PR body, plusOwner:/Reviewer:lines and a## Checkpointblock.
Migration (mine, after this merges)
I set projectId on the eight FlyBot lines — wf-1cdf6ec5 (issue 快线), wf-7ed167b6 (PR 快线), wf-957f985b (变脏修复), wf-4dc654c8 (Triage), wf-2fc09116 (每日发布), wf-9fddf24a / wf-62948ce1 / wf-afbd83c4 (bot-social) — and leave wf-479fa7c4 (openDigitalMate verify) unowned, because it belongs to another repository's project.
Out of scope
- Listing the Project's lines on the Project board (its own slice, after this one).
- Any change to how a run is dispatched, retried or stepped.
Activity
Birfy commentedon Oct 30, 2026
Written-by: Triage
Scope confirmed from the issue body (Owner decision 2026-10-30:
project-1fe64499"FlyBot 开发" is the one Project). No open PR references #1856, no competing open issue, no hold.Data model + seams
core/src/host/hooks.js(thefbobject,tasks:block at 1329-1351): addfb.projectswithbindRun(runId, projectId)and the Project lookupworkflow-saveneeds to validate against (read(id)/has(id), built onreadProjectincore/src/host/store.js); a host without the seam logs once, answersundefined, and never throws out of a run.core/src/host/store.js537-553 already hasbindProject/setEntry(state.projectRuns, …)andrequireProject:bindRunreuses that path so the two cannot disagree.projectId?: string;workflow-savepath issaveGraph(plugins/workflow/src/service.js:1412) overmutate(973): non-empty must name an existing Project (refuse with the reason, write nothing),nullclears, omitted keeps.service.js assign()binds at run creation: an explicitprojectIdwins, else the workflow's own; a refused binding (unknown Project / no seam) still starts the run.plugins/workflow/client.js: Project name per row ("未归属" when unowned) + a one-Project filter; the editor gets one Project picker fed by the hostprojectsendpoint. No new page/panel.core/andpatches/commit together:node scripts/ds-bot-patches.mjs generatethencheck(hooks.js/store.js are mirrored intopatches/ds-bot/files/src/host/).Tests, red first
plugins/workflow/test/assign.test.js: workflowprojectIdbinds the run; assign-callprojectIdoverrides; noprojectIdbinds nothing; a binding refusal still starts the run.plugins/workflow/test/save-graph-running-node.test.jsor thesaveGraphtest file): unknownprojectIdrefused and nothing written;nullclears; omitted keeps the stored value.core/test/: thebindRunseam writesprojectRunsand refuses an unknown Project (one case each); a host without the seam answersundefinedrather than throwing.Acceptance probes
TZ=UTC npm run verifygreen at the tip;node scripts/ds-bot-patches.mjs checkgreen.projectId, thenGET project-boardfor that Project shows the row withrunId(projectOfMember('run', …),core/src/host/project-board.js:391-400).projectIdbehaves exactly as today.Fixes #1856,Owner:/Reviewer:lines,## Checkpointblock.Tier: T2 —
core/src/host/**andplugins/**(.github/agent/risk-rules.json), so a PASS at the tip plus greentests-ok.Out of scope: listing the Project's lines on the board; any change to dispatch/retry/step; the migration of the eight existing workflow ids (Chief does that after the merge).
Birfy commentedon Oct 30, 2026
Written-by: Dev
Claimed for implementation — branch
dev/1856-workflow-project. First step:fb.projects.bindRunincore/src/host/hooks.jsover the existingbindProjectpath.Apply the Project binding only after the graph validated (#1856)
Show a workflow's Project in the sidebar and in Settings (#1856)
Bind a workflow run to its Project (ThinkFlowLab#1856) (ThinkFlowLab#…