leeovery/portal/tree/main/.claude/skills/workflow-gates
workflow-gates
一個 Claude Code 外掛,把工作流程引擎的 gate 畫成提示列上方的互動式列,處理選取、排入佇列、持久化與恢復,而不是讓模型重新產生文字選單。
關於這個 mod
workflow-gates
一個 Claude Code mod,把工作流程引擎的 gate 畫在提示列上方,而不是丟給模型重新產生。
引擎會把每個 gate 以資料放在它組出的選單旁邊。這個 mod 在工作階段開始時宣告自己,讓引擎收集那些資料;再依照承載資料的 Bash 結果啟用 gate,從模型讀到的內容中移除選單,並在轉錄滾動時把列固定在原處。只要連接的畫面不是終端機,它就保留文字選單,讓每個畫面都看得到;但在終端機畫出選單後才接上的畫面,不會取得那份選單。任何 gate 的文字都不會點名這個 mod,只有 workflow-start 的設定步驟會這樣做;這會讓工作階段停住,直到 mod 在 Claude Code 的終端機應用程式中執行。
點擊一列會把答案放進提示框;再次點擊會把它當成下一則訊息送出,工作流程會把它讀成答案。點擊讓提示列取得鍵盤焦點後,方向鍵可在各列之間移動,Enter 或該列自己的按鍵可選取,按下已選列上的 Enter 就會送出。送出的答案會以外掛名稱送入,按模型可理解的格式包裝,並在轉錄中標示為該外掛的內容;mod 會把送出的內容留在對話自己的資料夾(見下文)中,檔名是 sent.json。第二個外掛 workflow-gates-rows(../workflow-gates-rows/)會讀取這份紀錄,把該列畫成問題與答案,因為沒有外掛能重畫自己提交的提示所在的列。答案也能用鍵盤輸入:在提示列按下按鍵再按 Enter,或選取後按 Esc 再按 Enter。只能輸入的列可以回答——Ask、Comment 或一個範圍——會以暗色顯示;點擊其中一列會提示你在提示框輸入。列下方的頁尾會說明怎麼回答、提示框裡有什麼,或應該在哪裡輸入。
提示列永遠不會比 Claude Code 提供的列更高,所以不會滾動。放得下的 gate 會完整顯示:規則、說明與問題、各列、頁尾。放不下的 gate 會固定規則、問題與頁尾,把各列逐頁顯示;每頁高度相同,下面有一行 ↑ previous ↓ next page 1 of 3。點擊任一方向會翻頁,並把游標放到該頁第一列。方向鍵會讓游標跨頁移動,移到下一頁;某列自己的按鍵無論它在哪一頁,都能選取那一列。
不是由使用者開始的回合——背景代理的報告、通知或排程——會讓提示列維持原樣,各列繼續存在。使用者開始新的回合、回覆正在執行的回合,或某個回合在上面畫出不同的 gate 時,提示列才會移除。在這種回合按 Esc 會維持提示列原樣,除非該回合已經畫出不同的 gate:此時模型正等在那個 gate 的停止點,提示列就會清空。Claude 工作時再次點擊會暫存答案而不送出:該列顯示 · queued,頁尾會說等 Claude 完成後送出。點擊排隊中的列會回到選取,點擊另一列則改選另一列。回合結束時,如果提示列上仍是同一個 gate,暫存答案就會送出;如果回合畫出了不同的 gate,答案會丟棄而不送出,新 gate 的頁尾會說明;如果 Esc 停止回合且同一個 gate 還在,答案會以選取項回到提示框。選取項的 gate 消失時,答案也會離開提示框,除非使用者已在那裡編輯過。Claude 工作時輸入的內容會加入 Claude Code 自己的佇列。
在一個由答案開始或加入的回合按 Esc,會讓它的 gate 回來,丟掉該回合畫出的任何內容,只要那個回合還沒有執行工具;一旦執行過工具,提示列就維持清空,因為 mod 分不出讀取和寫入。/clear 會把 gate 從提示列移除。
每個回合結束時,以及對話結束時,mod 會把提示列顯示的內容——gate 或空白——放在對話自己的資料夾:~/.config/workflows/conversations/{session-id}/gate.json(若有設定 WORKFLOWS_CONFIG_DIR,就放在該處、工作流程的系統設定旁邊)。它會依工作階段 id 找到檔案,即使工作階段的工作目錄已經移動;並記下轉錄結束的位置,不計入 Claude Code 在中斷回合周圍寫入的列。用 claude --resume 或 /resume 恢復的對話,或因重啟、重新載入 mod 檔案而再次接續的對話,只要轉錄仍在那裡結束,就會恢復 gate;其中沒有已選或暫存的內容——暫存答案等的是不會回來的回合;mod 未載入時已繼續往前的對話則什麼也不會恢復。工作階段開始後才載入 mod 的工作階段不會有宣告,因此不會在這裡保存或讀回任何內容;不執行工作流程的對話沒有資料夾,也不保存任何東西;在程序中既沒有指定主目錄、也沒有指定 WORKFLOWS_CONFIG_DIR 的對話同樣如此。資料夾只容納一個 gate,Claude Code 刪除對話轉錄後資料夾就會消失,因此保留的 gate 會和對話一樣持續到可以恢復為止。唯一例外是 session-end hook 從未看見結束的對話:第一次安裝 hook 的工作階段(Claude Code 只在工作階段開始時載入它),或發生當機的工作階段,都會保留資料夾。
無論 mod 開啟或缺席,引擎都會送出選單;在 mod 關閉或不存在的地方,模型讀到的就是引擎寫出的文字選單。
這個 mod 是工作流程的一部分,第一次 /workflow-start 會在能執行的地方把它開啟:從 2.1.282 起的 Claude Code 終端機應用程式,且本目錄已安裝。Claude Code 會從使用者自己的設定、受管理設定或 shell 讀取 CLAUDE_CODE_ENABLE_FUNCTION_HOOKS,永遠不會從專案設定讀取,因此在終端機應用程式中,每次 /workflow-start 都會把使用者 Claude Code 設定 env 的值寫成 "1"——若有設定 CLAUDE_CONFIG_DIR,就是其中的 settings.json,否則是 ~/.claude/settings.json。Claude Code 只在啟動時讀取設定,所以寫入它的啟動最後會要求重啟,下一個工作階段才會載入 mod。在終端機應用程式中,工作流程只有在 mod 執行時才會運作:若啟動發現 flag 存在但 mod 沒在執行、無法讀寫該檔案,或執行在早於 2.1.282 的 Claude Code 上,就會停止並說明原因。其他地方——網頁、IDE 擴充功能、另一個入口點、沒有本目錄的專案——都不會寫入任何東西,工作流程會繼續使用文字選單。
功能 hook 可能在 mod 無法執行的地方開啟:使用者設定中的 flag 會送到所有讀取它的 Claude Code 應用程式與版本,任何人的設定或 shell 都能設定它。因此在工作階段開始時,mod 會套用啟動規則:CLAUDE_CODE_ENTRYPOINT 不是 cli、設定了 CLAUDE_CODE_REMOTE、版本早於 2.1.282,或不是發行版版本(包括開發建置)時,它不會宣告自己,也不會繪製、保存或設定任何東西——選單維持文字,Claude Code 的執行方式和沒有 mod 時一樣。
它在 Claude Code 中設定的內容
從 2.1.282 起,Claude Code 終端機應用程式中的每個工作階段都會開啟 Claude Code 的 SendUserMessage 工具(CLAUDE_CODE_PEWTER_OWL_TOOL=true)。Claude Code 會在工作階段開始後立刻建立工具清單,因此只有那個時刻的開關有效。mod 在每個這類工作階段把工具藏在 ToolSearch 後面;答案永遠不變,所以不會消耗提示快取。一般工作階段的工具清單仍由 Claude Code 自己決定。
在其中執行工作流程的對話裡,mod 會設定 CLAUDE_CODE_THINKING_DISPLAY_UPDATES=false,阻止把 Claude 思考的一行摘要印得像輸出;也會設定 CLAUDE_CODE_SILENT_TURN_REMINDER=false,阻止提醒 Claude 說明它正在做什麼;專案設定不能設定第二項。Claude Code 會逐次請求讀取兩者。每次引擎呼叫都會標記發出它的對話:在對話資料夾中放一個 workflow 檔案,檔名使用 Claude Code 傳給每個命令的工作階段 id;mod 會在對話每次 Bash 呼叫後及啟動時,用自己的工作階段 id 讀取這個標記,所以只提到引擎的命令不會標記任何東西。被設定取代的內容——使用者自己的值或沒有值——會保存在程序環境的 WORKFLOWS_HARNESS_REPLACED;重新載入 mod 檔案時會保留它,而 /clear 或恢復工作階段會原樣放回。被標記的對話會在 mod 下一次跟隨它時恢復工作流程值,不論是 claude --resume、重啟或同一程序中的 /resume 帶它回來。相同專案中的普通對話會保留 Claude Code 預設值與使用者自己的設定;mod 不會碰它們。
開發此 mod
npm run mod:types # 將 API 宣告抓到 types/(已被 git 忽略)
npm run typecheck:mod # 對這些宣告執行 tsc
npm run test:mod # claude plugin test
這些宣告來自 Claude Code 儲存庫,可以重新產生,因此不會提交。第一次 typecheck 前先抓取它們。
功能 hook 仍屬搶先體驗:只有開啟後 Claude Code 才會載入這個 mod——環境中的 CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1、任何不是專案設定的設定檔中開啟,或帳號層級開啟;測試指令會自行設定 flag。
安裝
請先查看作者 README,確認 marketplace 與外掛名稱;指令可能隨儲存庫結構而變動。
claude plugin marketplace add leeovery/portal claude plugin install workflow-gates
原文 / README
workflow-gates
A Claude Code mod that draws the workflow engine's gates in the band above the prompt instead of leaving the model to reproduce them.
The engine states each gate as data beside the menu it composed. This mod announces itself at the session's start so the engine collects that data, arms the gate off the Bash result that carried it, cuts the menu out of what the model reads, and draws the rows where they stay put while the transcript scrolls; while any screen but the terminal is attached, it leaves the menu as text so every screen shows it, though a screen that attaches after a menu was drawn on the terminal does not get that menu. No gate's prose names the mod; only workflow-start's setup step does, which stops the session until the mod is running in Claude Code's terminal app.
A click on a row puts its answer in the prompt box; a second click on it sends
it as the next message, which the workflows read as the answer. Once a click
has given the band the keyboard, the arrows move between rows, Enter or a row's
own key picks, and Enter on the picked row sends. A sent answer enters under
the plugin's name, framed for the model and labelled in the transcript as the
plugin's; the mod leaves what it sent in the conversation's own folder (see
below) as sent.json. A second plugin, workflow-gates-rows
(../workflow-gates-rows/), reads that record to draw the row as the question
and the answer, since no plugin can redraw the row of a prompt it submitted.
Typing answers too: a key and Enter at the prompt, or Esc then Enter after a
pick. Rows only typing can answer — Ask, Comment, a range — draw dim, and a
click on one says to type it in the prompt. The footer under the rows says
which: how to answer, what is in the prompt, or where to type.
The band is never taller than the rows Claude Code gives it, so it never
scrolls. A gate that fits shows whole: a rule, the statement and the question,
the rows, the footer. One that does not keeps its rule, question and footer in
place and shows its rows a page at a time, every page the same height, over a
line reading ↑ previous ↓ next page 1 of 3; a click on either turns the
page and puts the cursor on its first row. The arrows carry the cursor across
pages, the page following it, and a row's own key picks that row whichever
page it is on.
A turn the person did not start — a background agent's report, a
notification, a schedule — leaves the band as it is, its rows live. The band
comes off when the person starts a turn or replies into a running one, or when
a turn draws a different gate over it. Esc on such a turn leaves the band as it
is, unless the turn had already rendered a different gate: the model now waits
at that gate's stop, so the band empties. A second click while Claude works
holds the answer instead of sending it: its row reads · queued, and the
footer says it sends when Claude finishes. A click on the queued row takes it
back to a pick, and a click on another row picks that one instead. As the turn
ends, the held answer sends if the same gate is still on the band; if the turn
rendered a different gate, the answer is dropped unsent, and the new gate's
footer says so; if Esc stopped the turn with the same gate still up, the
answer goes back into the prompt box as a pick. When a pick's gate goes, its
answer leaves the prompt box too, unless the person has edited it there.
Typing while Claude works joins Claude Code's own queue.
Esc on a turn an answer started or joined puts its gate back, dropping
whatever that turn drew, as long as no tool has run in it; once one has, the
band stays empty, since the mod cannot tell a read from a write. A /clear
takes the gate off the band.
At the end of every turn, and as the conversation ends, the mod keeps what the
band shows — the gate, or nothing — in the conversation's own folder,
~/.config/workflows/conversations/{session-id}/gate.json (under
WORKFLOWS_CONFIG_DIR where that is set, beside the workflows' system
config), found by the session id wherever the session's working directory has
moved, and stamped with where the transcript ends, not counting the lines
Claude Code writes around an interrupted turn. A conversation resumed with
claude --resume or /resume, or picked up again by a restart or a reload of
the mod's files, gets its gate back as long as its transcript still ends
there, with nothing picked or held — a held answer waits on a turn that does
not come back; one that moved on while the mod was not loaded gets nothing. A
session the mod was loaded into after it started carries no announcement, so
there it keeps and reads back nothing, and a conversation that does not run
the workflows has no folder and keeps nothing — nor does one in a process
that names neither a home directory nor WORKFLOWS_CONFIG_DIR. The folder
holds one gate, and goes once Claude Code has deleted the conversation's
transcript, so a kept gate lives as long as its conversation can be resumed,
with one exception: a conversation whose end the session-end hook never saw
keeps its folder — the session that first installed the hook, which Claude
Code picks up only as a session starts, or one that crashed.
The engine emits the menu regardless, so where the mod is off or absent the model reads the text menu the engine wrote.
The mod is part of the workflows, and the first /workflow-start switches it
on wherever it can run: Claude Code's terminal app, from 2.1.282, with this
directory installed in the project. Claude Code takes
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS from the person's own settings, managed
settings or the shell, never from a project's settings, so there every
/workflow-start makes it "1" in the env of the person's Claude Code
settings — settings.json in CLAUDE_CONFIG_DIR where that is set, else
~/.claude/settings.json. Claude Code reads its settings only when it starts,
so a start that writes it ends by asking for a restart, and the next session
loads the mod. In the terminal app the workflows run only with the mod: a
start that finds the flag there with the mod not running, that cannot read or
write that file, or that runs on a Claude Code older than 2.1.282 stops and
says why. Anywhere else — the web, an IDE extension, another entrypoint, a
project without this directory — nothing is written, and the workflows carry
on with the text menus.
Function hooks can be on where the mod cannot run — the flag in the person's
settings reaches every Claude Code app and version that reads them, and
anyone's own settings or shell can set it — so at the session's start the mod
applies the boot's rules: where CLAUDE_CODE_ENTRYPOINT is other than cli,
CLAUDE_CODE_REMOTE is set, or the version the session reports is older than
2.1.282 or not a release's (a development build among them), it announces
nothing, so it draws, keeps and sets nothing — the menus stay text, and Claude
Code runs as it would without it.
What it sets in Claude Code
Every session in Claude Code's terminal app, from 2.1.282, starts with Claude
Code's SendUserMessage tool switched on (CLAUDE_CODE_PEWTER_OWL_TOOL=true):
Claude Code builds its tool list just after the session starts, so that is
the only moment the switch counts. The mod keeps the tool behind ToolSearch
in every such session, one answer that never changes and so never spends the
prompt cache; a plain session's tool list is Claude Code's own.
In a conversation that runs the workflows there, the mod sets
CLAUDE_CODE_THINKING_DISPLAY_UPDATES=false, which stops one-line summaries of
Claude's thinking printing as if they were output, and
CLAUDE_CODE_SILENT_TURN_REMINDER=false, which stops the nudge to say what
Claude is doing; project settings cannot set the second. Claude Code reads
both per request. Every engine call marks the conversation that made it — a
workflow file in its folder, named by the session id Claude Code hands every
command — and the mod reads that mark by its own session id after each of the
conversation's Bash calls and when it starts, so a command that only mentions
the engine marks nothing. What the settings replace, the person's own value or
none, is kept in the process's environment (WORKFLOWS_HARNESS_REPLACED),
which a reload of the mod's files keeps, and a /clear or a resume puts it
back exactly. A marked conversation gets the workflow values back when the mod
next follows it, whether claude --resume, a restart or /resume in the same
process brings it back. A plain conversation in the same project keeps Claude
Code's defaults and the person's own settings: the mod never touches either
there.
Working on it
npm run mod:types # fetch the API declarations into types/ (gitignored)
npm run typecheck:mod # tsc against those declarations
npm run test:mod # claude plugin test
The declarations come from the Claude Code repository and are regenerable, so they are not committed. Fetch them before the first typecheck.
Function hooks are early access: Claude Code loads this mod only where they
are on — CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 in the environment or in any
settings file but a project's, or switched on for the account — and the test
script sets the flag for itself.
其他同名作品
- workflow-gatesleeovery · ★ 2
