@guanlin4924/devflow-pi 0.1.48 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ bash .skills/install.sh --apply --force-config
|
|
|
117
117
|
|
|
118
118
|
### 窗口操作指引与干净会话
|
|
119
119
|
|
|
120
|
-
Devflow
|
|
120
|
+
Devflow 会在输入框上方显示“当前 Feature 交付链路”:它基于只读状态快照展示已知步骤、当前节点、完成数、工作流/执行状态、当前允许的操作,以及安全恢复方案。使用 `/devflow control` 打开交互式控制台:它只会在状态检查器允许时提供启动当前步骤、审查并批准推进、安全恢复或取消当前 Feature;每个有写入影响的操作都会重新读取状态并沿用已有确认流程。面板不提供重试或重置当前节点,避免破坏 revision、checkpoint 和证据链。
|
|
121
121
|
|
|
122
122
|
正式节点经用户批准后,Devflow 会把已校验的状态和产物引用固化为内容校验的 handoff,再自动创建干净 Session 执行下一节点。交接期间工作流冻结;只有新 Session 成功认领并消费同一份 handoff 后才解除冻结。Devflow 不使用 OMP 的模型摘要 handoff,因此 `.planning/STATE.md` 仍是唯一流程事实源。
|
|
123
123
|
|
package/package.json
CHANGED
|
@@ -974,7 +974,10 @@ export function isShellControlMutation(toolName: string, input: unknown): boolea
|
|
|
974
974
|
command,
|
|
975
975
|
);
|
|
976
976
|
const namesControlPath = [...CONTROL_PATHS, ...CONTROL_PREFIXES].some(item => command.includes(item)) || /\.planning\/requirements\//.test(command);
|
|
977
|
-
|
|
977
|
+
// `2>` and `2>>` only redirect stderr. They frequently accompany read-only
|
|
978
|
+
// probes of control-plane files and must not be treated as a file mutation.
|
|
979
|
+
// Keep bare `>`/`>>` and explicit stdout `1>`/`1>>` guarded.
|
|
980
|
+
const looksLikeWrite = /(?:^|[;&|]\s*)(?:rm|mv|cp|install|truncate)\b|(?:^|\s)(?:tee|sed\s+-i|perl\s+-i)\b|(?:^|[\s;&|])(?:1)?>{1,2}(?![>=&])\s*\S/.test(
|
|
978
981
|
command,
|
|
979
982
|
);
|
|
980
983
|
return invokesWriter || (namesControlPath && looksLikeWrite);
|
|
@@ -1124,27 +1127,112 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1124
1127
|
})}\n`, "utf8");
|
|
1125
1128
|
}
|
|
1126
1129
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
if (
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1130
|
+
type ProjectControlUi = {
|
|
1131
|
+
setStatus(key: string, text: string | undefined): void;
|
|
1132
|
+
setWidget?(key: string, content: string[] | undefined, options?: { placement?: "aboveEditor" | "belowEditor" }): void;
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
let projectControlRefreshRevision = 0;
|
|
1136
|
+
|
|
1137
|
+
function workflowSteps(snapshot: Record<string, unknown>): Array<{ id: string; label: string; status: string }> {
|
|
1138
|
+
if (!Array.isArray(snapshot.workflow_steps)) return [];
|
|
1139
|
+
return snapshot.workflow_steps.flatMap(step => {
|
|
1140
|
+
if (!step || typeof step !== "object") return [];
|
|
1141
|
+
const record = step as Record<string, unknown>;
|
|
1142
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
1143
|
+
if (!id) return [];
|
|
1144
|
+
return [{
|
|
1145
|
+
id,
|
|
1146
|
+
label: typeof record.label === "string" && record.label ? record.label : id,
|
|
1147
|
+
status: typeof record.status === "string" ? record.status : "unknown",
|
|
1148
|
+
}];
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function workflowStepIcon(status: string): string {
|
|
1153
|
+
if (status === "completed") return "✓";
|
|
1154
|
+
if (status === "current") return "●";
|
|
1155
|
+
if (status === "waiting_review") return "◆";
|
|
1156
|
+
if (status === "blocked") return "!";
|
|
1157
|
+
return "○";
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function projectActionLabel(snapshot: Record<string, unknown>): string {
|
|
1161
|
+
switch (snapshot.action) {
|
|
1162
|
+
case "run_next_step":
|
|
1163
|
+
case "run_parallel_window":
|
|
1164
|
+
case "route_failure_branch":
|
|
1165
|
+
return "可启动当前步骤";
|
|
1166
|
+
case "await_user_confirmation":
|
|
1167
|
+
return "审查产物后批准推进";
|
|
1168
|
+
case "recover_artifact_registry":
|
|
1169
|
+
return "可执行安全恢复(仅 registry)";
|
|
1170
|
+
case "workflow_complete":
|
|
1171
|
+
return "当前 Feature 已完成";
|
|
1172
|
+
case "workflow_cancelled":
|
|
1173
|
+
return "当前 Feature 已取消";
|
|
1174
|
+
case "blocked":
|
|
1175
|
+
return "先处理阻塞证据";
|
|
1176
|
+
default:
|
|
1177
|
+
return "正在重新确定可执行操作";
|
|
1140
1178
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function projectExecutionLabel(state: SessionControlState, snapshot: Record<string, unknown>): string {
|
|
1182
|
+
if (state.phase === "executing") return `执行中:${state.label}`;
|
|
1183
|
+
if (state.phase === "waiting-user" || state.phase === "awaiting-approval") return "等待人工审查";
|
|
1184
|
+
if (state.phase === "blocked") return `已阻塞:${state.label}`;
|
|
1185
|
+
if (snapshot.action === "await_user_confirmation") return "等待人工审查";
|
|
1186
|
+
if (snapshot.action === "blocked") return "已阻塞";
|
|
1187
|
+
return "当前会话未运行 Agent";
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function projectControlPanel(snapshot: Record<string, unknown> | null, state: SessionControlState): string[] {
|
|
1191
|
+
if (!snapshot) {
|
|
1192
|
+
return ["Devflow 当前 Feature 交付链路", "尚未读取到有效控制面状态。"];
|
|
1143
1193
|
}
|
|
1144
|
-
|
|
1194
|
+
const steps = workflowSteps(snapshot);
|
|
1195
|
+
const completedCount = steps.filter(step => step.status === "completed").length;
|
|
1196
|
+
const current = compactStep(snapshot.current_step) ?? compactStep(snapshot.next_step);
|
|
1197
|
+
const recovery = snapshot.recovery_plan && typeof snapshot.recovery_plan === "object"
|
|
1198
|
+
? snapshot.recovery_plan as Record<string, unknown>
|
|
1199
|
+
: undefined;
|
|
1200
|
+
const recoveryLabel = recovery?.classification === "safe_repairable"
|
|
1201
|
+
? "已提供安全恢复方案(不推进步骤)"
|
|
1202
|
+
: snapshot.action === "blocked"
|
|
1203
|
+
? "没有可自动执行的重置方案;请先查看阻塞证据。"
|
|
1204
|
+
: "当前无需恢复或重置。";
|
|
1205
|
+
const chain = steps.length > 0
|
|
1206
|
+
? steps.map(step => `${workflowStepIcon(step.status)} ${step.label}`).join(" → ")
|
|
1207
|
+
: "尚无可展示的已知工作流步骤。";
|
|
1208
|
+
return [
|
|
1209
|
+
`Devflow 当前 Feature 交付链路 · ${String(snapshot.current_feature ?? "未开始")}`,
|
|
1210
|
+
`阶段:${String(snapshot.current_phase ?? "unknown")} Revision:${String(snapshot.revision ?? "unknown")}`,
|
|
1211
|
+
`链路:${chain}`,
|
|
1212
|
+
`进度:已完成 ${completedCount} / 当前已知 ${steps.length} 步`,
|
|
1213
|
+
`当前节点:${current?.label ? String(current.label) : String(current?.id ?? "未开始")}`,
|
|
1214
|
+
`工作流状态:${String(snapshot.status ?? "unknown")} 执行状态:${projectExecutionLabel(state, snapshot)}`,
|
|
1215
|
+
`当前可执行操作:${projectActionLabel(snapshot)}`,
|
|
1216
|
+
`恢复与重置:${recoveryLabel}`,
|
|
1217
|
+
];
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function refreshProjectControlPanel(ctx: { cwd: string; ui: ProjectControlUi }): void {
|
|
1221
|
+
const request = ++projectControlRefreshRevision;
|
|
1222
|
+
void inspectWorkflow(ctx.cwd).then(({ result, snapshot }) => {
|
|
1223
|
+
if (request !== projectControlRefreshRevision) return;
|
|
1224
|
+
const content = result.ok
|
|
1225
|
+
? projectControlPanel(snapshot, controlState)
|
|
1226
|
+
: ["Devflow 当前 Feature 交付链路", "状态读取失败;未执行任何恢复或状态迁移。"];
|
|
1227
|
+
ctx.ui.setWidget?.("devflow-project-control", content, { placement: "aboveEditor" });
|
|
1228
|
+
}).catch(() => {
|
|
1229
|
+
if (request !== projectControlRefreshRevision) return;
|
|
1230
|
+
ctx.ui.setWidget?.("devflow-project-control", ["Devflow 当前 Feature 交付链路", "状态读取失败;未执行任何恢复或状态迁移。"], { placement: "aboveEditor" });
|
|
1231
|
+
});
|
|
1145
1232
|
}
|
|
1146
1233
|
|
|
1147
1234
|
function setControlState(ctx: {
|
|
1235
|
+
cwd: string;
|
|
1148
1236
|
ui: {
|
|
1149
1237
|
setStatus(key: string, text: string | undefined): void;
|
|
1150
1238
|
setWidget?(key: string, content: string[] | undefined, options?: { placement?: "aboveEditor" | "belowEditor" }): void;
|
|
@@ -1152,7 +1240,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1152
1240
|
}, state: SessionControlState) {
|
|
1153
1241
|
controlState = state;
|
|
1154
1242
|
ctx.ui.setStatus("devflow-control", statusText(state));
|
|
1155
|
-
ctx
|
|
1243
|
+
refreshProjectControlPanel(ctx);
|
|
1156
1244
|
}
|
|
1157
1245
|
|
|
1158
1246
|
function normalizeContractSegment(value: string, label: string): string {
|
|
@@ -1776,6 +1864,127 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1776
1864
|
return true;
|
|
1777
1865
|
}
|
|
1778
1866
|
|
|
1867
|
+
type ProjectControlCommandContext = {
|
|
1868
|
+
cwd: string;
|
|
1869
|
+
sessionManager: { getSessionId(): string };
|
|
1870
|
+
ui: ProjectControlUi & {
|
|
1871
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
1872
|
+
confirm(title: string, body: string): Promise<boolean>;
|
|
1873
|
+
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
1874
|
+
notify(message: string, level: "info" | "error"): void;
|
|
1875
|
+
};
|
|
1876
|
+
};
|
|
1877
|
+
|
|
1878
|
+
function hasSafeRecoveryPlan(snapshot: Record<string, unknown>): boolean {
|
|
1879
|
+
const plan = snapshot.recovery_plan;
|
|
1880
|
+
return Boolean(plan && typeof plan === "object" && (plan as Record<string, unknown>).classification === "safe_repairable");
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
async function executeSafeRecoveryFromPanel(ctx: ProjectControlCommandContext): Promise<void> {
|
|
1884
|
+
// Do not use inspectWorkflowWithRegistryRecovery here. A view action must
|
|
1885
|
+
// never silently repair the control plane before the user sees and accepts
|
|
1886
|
+
// the currently inspected recovery plan.
|
|
1887
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1888
|
+
const plan = inspected.snapshot?.recovery_plan as Record<string, unknown> | undefined;
|
|
1889
|
+
const revision = plan?.expected_revision;
|
|
1890
|
+
if (!inspected.result.ok || !inspected.snapshot || !hasSafeRecoveryPlan(inspected.snapshot) || typeof revision !== "number") {
|
|
1891
|
+
ctx.ui.notify("当前没有可执行的安全恢复方案。", "error");
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
const confirmed = await ctx.ui.confirm(
|
|
1895
|
+
"确认执行安全恢复",
|
|
1896
|
+
"将只同步已存在文件对应的 artifact registry;不会推进步骤、确认 checkpoint、重试或重置当前节点。继续吗?",
|
|
1897
|
+
);
|
|
1898
|
+
if (!confirmed) return;
|
|
1899
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/repair_control_plane.rb", [
|
|
1900
|
+
"--write", "--confirm", "--expected-revision", String(revision), "--json",
|
|
1901
|
+
]);
|
|
1902
|
+
if (!controlPlaneSucceeded(result)) {
|
|
1903
|
+
ctx.ui.notify(`安全恢复失败:${result.stderr || result.stdout}`, "error");
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
setControlState(ctx, initialControlState());
|
|
1907
|
+
ctx.ui.notify("控制面 registry 已安全恢复;工作流未推进。", "info");
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
async function cancelFeatureFromPanel(ctx: ProjectControlCommandContext): Promise<void> {
|
|
1911
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1912
|
+
const snapshot = inspected.snapshot;
|
|
1913
|
+
const revision = typeof snapshot?.revision === "number" ? snapshot.revision : undefined;
|
|
1914
|
+
const feature = typeof snapshot?.current_feature === "string" ? snapshot.current_feature : undefined;
|
|
1915
|
+
const status = typeof snapshot?.status === "string" ? snapshot.status : undefined;
|
|
1916
|
+
if (!inspected.result.ok || revision === undefined || !feature || !status || ["completed", "cancelled", "failed"].includes(status)) {
|
|
1917
|
+
ctx.ui.notify("当前没有可取消的进行中 Feature。", "error");
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
const reason = await ctx.ui.input("取消当前 Feature", "请输入取消原因");
|
|
1921
|
+
if (!reason?.trim()) return;
|
|
1922
|
+
const confirmed = await ctx.ui.confirm(
|
|
1923
|
+
"确认取消当前 Feature",
|
|
1924
|
+
`Feature:${feature}\n原因:${reason.trim()}\n\n将结束当前 Feature、释放项目占用并保留现有工件;未完成步骤不会被标记为完成。继续吗?`,
|
|
1925
|
+
);
|
|
1926
|
+
if (!confirmed) return;
|
|
1927
|
+
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1928
|
+
"--write", "--json", "--cancel", "--cancel-reason", reason.trim(), "--expected-revision", String(revision),
|
|
1929
|
+
]);
|
|
1930
|
+
if (!controlPlaneSucceeded(result)) {
|
|
1931
|
+
ctx.ui.notify(`取消当前 Feature 失败:${result.stderr || result.stdout}`, "error");
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
activeContract = undefined;
|
|
1935
|
+
contractAgentCalls = 0;
|
|
1936
|
+
setControlState(ctx, initialControlState());
|
|
1937
|
+
ctx.ui.notify(`Feature“${feature}”已取消;现有工件已保留。`, "info");
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
async function openProjectControlPanel(ctx: ProjectControlCommandContext): Promise<void> {
|
|
1941
|
+
for (;;) {
|
|
1942
|
+
// The control panel is a read-only projection. Each action below reads a
|
|
1943
|
+
// fresh snapshot again before it can mutate anything.
|
|
1944
|
+
const inspected = await inspectWorkflow(ctx.cwd);
|
|
1945
|
+
if (!inspected.result.ok || !inspected.snapshot) {
|
|
1946
|
+
ctx.ui.notify(`项目控制台读取失败:${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
const snapshot = inspected.snapshot;
|
|
1950
|
+
const options = ["刷新状态", "查看状态说明"];
|
|
1951
|
+
if (["run_next_step", "run_parallel_window", "route_failure_branch"].includes(String(snapshot.action))) options.push("启动当前可执行步骤");
|
|
1952
|
+
if (snapshot.action === "await_user_confirmation") options.push("审查并批准推进…");
|
|
1953
|
+
if (hasSafeRecoveryPlan(snapshot)) options.push("执行安全恢复…");
|
|
1954
|
+
if (typeof snapshot.current_feature === "string" && !["completed", "cancelled", "failed"].includes(String(snapshot.status))) options.push("取消当前 Feature…");
|
|
1955
|
+
options.push("关闭");
|
|
1956
|
+
|
|
1957
|
+
const selected = await ctx.ui.select(projectControlPanel(snapshot, controlState).join("\n"), options);
|
|
1958
|
+
if (!selected || selected === "关闭") return;
|
|
1959
|
+
if (selected === "刷新状态") continue;
|
|
1960
|
+
if (selected === "查看状态说明") {
|
|
1961
|
+
ctx.ui.notify(explainWorkflowSnapshot(snapshot), "info");
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
if (selected === "启动当前可执行步骤") {
|
|
1965
|
+
const fresh = await inspectWorkflow(ctx.cwd);
|
|
1966
|
+
if (!fresh.result.ok || !fresh.snapshot) {
|
|
1967
|
+
ctx.ui.notify(`启动前状态读取失败:${fresh.result.stderr || fresh.result.stdout}`, "error");
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
await resumeInspectedWorkflow(ctx, fresh.snapshot);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
if (selected === "审查并批准推进…") {
|
|
1974
|
+
await confirmAndAdvanceWorkflowStep(ctx);
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
if (selected === "执行安全恢复…") {
|
|
1978
|
+
await executeSafeRecoveryFromPanel(ctx);
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
if (selected === "取消当前 Feature…") {
|
|
1982
|
+
await cancelFeatureFromPanel(ctx);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1779
1988
|
async function reconcileParallelLifecycle(event: Partial<SubagentLifecyclePayload>, context: { branch_id: string; attempt: number; branch_revision: number } | undefined): Promise<void> {
|
|
1780
1989
|
if (!context || !projectRoot || !event.status || event.status === "started") return;
|
|
1781
1990
|
const workflowInspection = await inspectWorkflow(projectRoot);
|
|
@@ -3409,6 +3618,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3409
3618
|
ctx.ui.notify((result.stdout || result.stderr).slice(0, 6000), result.ok ? "info" : "error");
|
|
3410
3619
|
return;
|
|
3411
3620
|
}
|
|
3621
|
+
if (trimmed === "control") {
|
|
3622
|
+
await openProjectControlPanel(ctx);
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3412
3625
|
if (trimmed === "explain-state") {
|
|
3413
3626
|
const inspected = await inspectWorkflowWithRegistryRecovery(ctx.cwd);
|
|
3414
3627
|
ctx.ui.notify(
|
|
@@ -3660,7 +3873,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
3660
3873
|
return;
|
|
3661
3874
|
}
|
|
3662
3875
|
ctx.ui.notify(
|
|
3663
|
-
"Usage: /devflow doctor | status | explain-state | recover | abandon [reason] | project-init | start <feature>|<phase> | resume",
|
|
3876
|
+
"Usage: /devflow doctor | status | control | explain-state | recover | abandon [reason] | project-init | start <feature>|<phase> | resume",
|
|
3664
3877
|
"warning",
|
|
3665
3878
|
);
|
|
3666
3879
|
},
|
|
@@ -126,6 +126,39 @@ def step_payload(step, state, root, artifact_by_path)
|
|
|
126
126
|
}
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
# This is a read model for the TUI, not a second workflow state. In
|
|
130
|
+
# particular, the current step may be waiting for human review or blocked,
|
|
131
|
+
# while it remains in pending_steps in STATE.md. The inspector action is the
|
|
132
|
+
# authority for that distinction.
|
|
133
|
+
def workflow_steps_payload(steps, state, result)
|
|
134
|
+
completed_ids = Array(state["completed_steps"])
|
|
135
|
+
pending_ids = Array(state["pending_steps"])
|
|
136
|
+
current_id = state["current_step"]
|
|
137
|
+
action = result[:action] || result["action"]
|
|
138
|
+
|
|
139
|
+
Array(steps).map do |step|
|
|
140
|
+
id = step["id"]
|
|
141
|
+
status = if completed_ids.include?(id)
|
|
142
|
+
"completed"
|
|
143
|
+
elsif id == current_id && action == "await_user_confirmation"
|
|
144
|
+
"waiting_review"
|
|
145
|
+
elsif id == current_id && action == "blocked"
|
|
146
|
+
"blocked"
|
|
147
|
+
elsif id == current_id
|
|
148
|
+
"current"
|
|
149
|
+
elsif pending_ids.include?(id)
|
|
150
|
+
"pending"
|
|
151
|
+
else
|
|
152
|
+
"unknown"
|
|
153
|
+
end
|
|
154
|
+
{
|
|
155
|
+
"id" => id,
|
|
156
|
+
"label" => step["label"] || id,
|
|
157
|
+
"status" => status
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
129
162
|
def dependencies_satisfied?(step, completed_ids)
|
|
130
163
|
Array(step["depends_on"]).all? { |dependency| completed_ids.include?(dependency) }
|
|
131
164
|
end
|
|
@@ -617,6 +650,9 @@ if defined?(state) && state.is_a?(Hash) && result.is_a?(Hash)
|
|
|
617
650
|
source_flow: state["source_flow"],
|
|
618
651
|
source_workflow_config: state["source_workflow_config"]
|
|
619
652
|
)
|
|
653
|
+
# Include the declared, currently known workflow chain so a presentation
|
|
654
|
+
# layer never has to infer it from filenames or raw control-plane files.
|
|
655
|
+
result["workflow_steps"] = workflow_steps_payload(steps, state, result) if defined?(steps) && steps.is_a?(Array)
|
|
620
656
|
end
|
|
621
657
|
|
|
622
658
|
if options[:json]
|
package/src/constants.ts
CHANGED