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 只会在工作阶段开始时载入 hook)或发生崩溃的工作阶段。
无论 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 会按请求读取这两项。每次引擎调用都会标记发起它的对话:在该对话资料夹内放置一个以 Claude Code 传给每个命令的工作阶段 id 命名的 workflow 文件。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
