@guanlin4924/devflow-pi 0.1.50 → 0.1.52
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/package.json +1 -1
- package/plugins/devflow-core/extensions/devflow-core.ts +164 -71
- package/src/constants.ts +1 -1
- package/src/runtime.ts +12 -2
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { appendFile, mkdir, rename, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
7
|
+
import { Box, Text } from "@oh-my-pi/pi-tui";
|
|
7
8
|
import {
|
|
8
9
|
TASK_SUBAGENT_LIFECYCLE_CHANNEL,
|
|
9
10
|
type SubagentLifecyclePayload,
|
|
@@ -27,6 +28,96 @@ type ProjectInitializationRequest = {
|
|
|
27
28
|
mode: ProjectInitializationMode;
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
const workflowStepNames: Record<string, string> = {
|
|
32
|
+
"requirement-triage": "需求梳理",
|
|
33
|
+
"requirement-wayfinder": "需求路径规划",
|
|
34
|
+
"domain-modeling": "领域建模",
|
|
35
|
+
"requirement-feasibility-review": "需求可行性审查",
|
|
36
|
+
"requirement-baseline": "需求基线确认",
|
|
37
|
+
"wireframe-proposal": "原型方案",
|
|
38
|
+
"wireframe-validate": "原型校验",
|
|
39
|
+
"wireframe-render": "原型预览生成",
|
|
40
|
+
"wireframe-review": "原型审查",
|
|
41
|
+
"api-contract": "接口契约",
|
|
42
|
+
"architecture-feasibility": "架构可行性评估",
|
|
43
|
+
"ui-spec": "界面规格",
|
|
44
|
+
"feature-breakdown": "功能拆分",
|
|
45
|
+
"frontend-tech-spec": "前端技术方案",
|
|
46
|
+
"backend-tech-spec": "后端技术方案",
|
|
47
|
+
"api-call-guide": "接口调用说明",
|
|
48
|
+
"api-supplement": "接口补充设计",
|
|
49
|
+
"integration-verify": "集成验证",
|
|
50
|
+
"change-plan": "变更计划",
|
|
51
|
+
"acceptance-test-design": "验收测试设计",
|
|
52
|
+
"phase-structure": "阶段结构确认",
|
|
53
|
+
"execute": "实现执行",
|
|
54
|
+
"code-index-refresh": "代码索引刷新",
|
|
55
|
+
"code-review": "代码审查",
|
|
56
|
+
"generate-mainflow-cases": "主流程用例生成",
|
|
57
|
+
"run-acceptance-tests": "冻结验收测试执行",
|
|
58
|
+
"release-closeout": "发布收尾与里程碑归档",
|
|
59
|
+
"dual-track-design": "前后端并行设计",
|
|
60
|
+
"acceptance-failed-remediation": "验收失败修复",
|
|
61
|
+
"diagnose": "验收失败诊断",
|
|
62
|
+
"execute-fix": "修复执行",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const workflowStatusNames: Record<string, string> = {
|
|
66
|
+
completed: "已完成",
|
|
67
|
+
current: "进行中",
|
|
68
|
+
pending: "未开始",
|
|
69
|
+
waiting_review: "等待审查",
|
|
70
|
+
blocked: "已阻塞",
|
|
71
|
+
failed: "执行失败",
|
|
72
|
+
cancelled: "已取消",
|
|
73
|
+
unknown: "未知",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const workflowActionNames: Record<string, string> = {
|
|
77
|
+
run_next_step: "执行下一步骤",
|
|
78
|
+
run_parallel_window: "执行并行步骤",
|
|
79
|
+
route_failure_branch: "进入失败处理分支",
|
|
80
|
+
await_user_confirmation: "等待人工确认",
|
|
81
|
+
recover_artifact_registry: "恢复工件登记",
|
|
82
|
+
workflow_complete: "功能交付已完成",
|
|
83
|
+
workflow_cancelled: "功能交付已取消",
|
|
84
|
+
blocked: "处理阻塞项",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function workflowSteps(snapshot: Record<string, unknown>): Array<{ id: string; label: string; status: string }> {
|
|
88
|
+
if (!Array.isArray(snapshot.workflow_steps)) return [];
|
|
89
|
+
return snapshot.workflow_steps.flatMap(step => {
|
|
90
|
+
if (!step || typeof step !== "object") return [];
|
|
91
|
+
const record = step as Record<string, unknown>;
|
|
92
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
93
|
+
if (!id) return [];
|
|
94
|
+
return [{
|
|
95
|
+
id,
|
|
96
|
+
label: typeof record.label === "string" && record.label ? record.label : id,
|
|
97
|
+
status: typeof record.status === "string" ? record.status : "unknown",
|
|
98
|
+
}];
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function workflowStepName(id: string | undefined, fallback?: string): string {
|
|
103
|
+
if (id && workflowStepNames[id]) return workflowStepNames[id];
|
|
104
|
+
return fallback || id || "未开始";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function workflowStatusName(status: unknown): string {
|
|
108
|
+
return typeof status === "string" ? workflowStatusNames[status] ?? status : "未知";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function workflowActionName(action: unknown): string {
|
|
112
|
+
return typeof action === "string" ? workflowActionNames[action] ?? action : "正在确认";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function workflowPhaseName(phase: unknown): string {
|
|
116
|
+
if (typeof phase !== "string" || !phase) return "未开始";
|
|
117
|
+
if (phase.startsWith("feature-")) return "功能交付";
|
|
118
|
+
return phase;
|
|
119
|
+
}
|
|
120
|
+
|
|
30
121
|
type ActiveExecutionContract = {
|
|
31
122
|
contractId: string;
|
|
32
123
|
feature: string;
|
|
@@ -278,7 +369,7 @@ export function isRequirementDraftToolAllowed(toolName: string, input: unknown,
|
|
|
278
369
|
return normalized === "task" && taskAgents.length > 0 && taskAgents.every(agent => PLANNING_AGENTS.has(agent));
|
|
279
370
|
}
|
|
280
371
|
|
|
281
|
-
type InternalDevflowControlResource = "contract" | "state" | "mainflow";
|
|
372
|
+
type InternalDevflowControlResource = "contract" | "state" | "mainflow" | "requirement_graph";
|
|
282
373
|
|
|
283
374
|
/** Resolve an xdev target only when all supplied target aliases agree.
|
|
284
375
|
* A direct/eval caller can otherwise provide `path` for a real file plus an
|
|
@@ -303,6 +394,7 @@ export function internalDevflowControlResource(toolName: string, input: unknown)
|
|
|
303
394
|
if (target === "xd://devflow_contract") return "contract";
|
|
304
395
|
if (target === "xd://devflow_state") return "state";
|
|
305
396
|
if (target === "xd://devflow_mainflow") return "mainflow";
|
|
397
|
+
if (target === "xd://devflow_requirement_graph") return "requirement_graph";
|
|
306
398
|
return undefined;
|
|
307
399
|
}
|
|
308
400
|
|
|
@@ -897,20 +989,21 @@ function projectInitializationDirective(summary: string): string {
|
|
|
897
989
|
}
|
|
898
990
|
|
|
899
991
|
function explainWorkflowSnapshot(snapshot: Record<string, unknown> | null): string {
|
|
900
|
-
if (!snapshot) return "Devflow 当前没有可解释的工作流状态。可使用 /devflow start
|
|
992
|
+
if (!snapshot) return "Devflow 当前没有可解释的工作流状态。可使用 /devflow start <功能名> 创建新功能。";
|
|
901
993
|
const step = compactStep(snapshot.next_step) ?? compactStep(snapshot.current_step);
|
|
902
994
|
const lines = [
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
995
|
+
`功能:${String(snapshot.current_feature ?? "未开始")}`,
|
|
996
|
+
`阶段:${workflowPhaseName(snapshot.current_phase)}`,
|
|
997
|
+
`状态:${workflowStatusName(snapshot.status)}`,
|
|
998
|
+
`动作:${workflowActionName(snapshot.action)}`,
|
|
999
|
+
`版本:${String(snapshot.revision ?? "未知")}`,
|
|
907
1000
|
];
|
|
908
|
-
if (step?.id) lines.push(
|
|
909
|
-
if (snapshot.checkpoint) lines.push(
|
|
1001
|
+
if (step?.id) lines.push(`当前/下一节点:${workflowStepName(step.id, step.label)}`);
|
|
1002
|
+
if (snapshot.checkpoint) lines.push(`检查点:${String(snapshot.checkpoint)}`);
|
|
910
1003
|
if (snapshot.action === "workflow_complete") {
|
|
911
|
-
lines.push("
|
|
1004
|
+
lines.push("结论:上一功能已完成;可以使用 /devflow start <功能名> 创建下一功能。");
|
|
912
1005
|
} else {
|
|
913
|
-
lines.push("
|
|
1006
|
+
lines.push("结论:这是只读说明;未执行任何状态迁移或文件写入。");
|
|
914
1007
|
}
|
|
915
1008
|
return lines.join("\n");
|
|
916
1009
|
}
|
|
@@ -1129,33 +1222,13 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1129
1222
|
|
|
1130
1223
|
type ProjectControlUi = {
|
|
1131
1224
|
setStatus(key: string, text: string | undefined): void;
|
|
1132
|
-
|
|
1225
|
+
// OMP widgets accept either plain lines or a component factory. Keep this
|
|
1226
|
+
// interface structural because command contexts are supplied by OMP.
|
|
1227
|
+
setWidget?(key: string, content: unknown, options?: { placement?: "aboveEditor" | "belowEditor" }): void;
|
|
1133
1228
|
};
|
|
1134
1229
|
|
|
1135
1230
|
let projectControlRefreshRevision = 0;
|
|
1136
1231
|
|
|
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
1232
|
|
|
1160
1233
|
function projectActionLabel(snapshot: Record<string, unknown>): string {
|
|
1161
1234
|
switch (snapshot.action) {
|
|
@@ -1166,11 +1239,11 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1166
1239
|
case "await_user_confirmation":
|
|
1167
1240
|
return "审查产物后批准推进";
|
|
1168
1241
|
case "recover_artifact_registry":
|
|
1169
|
-
return "
|
|
1242
|
+
return "可执行安全恢复(仅工件登记)";
|
|
1170
1243
|
case "workflow_complete":
|
|
1171
|
-
return "
|
|
1244
|
+
return "当前功能已完成";
|
|
1172
1245
|
case "workflow_cancelled":
|
|
1173
|
-
return "
|
|
1246
|
+
return "当前功能已取消";
|
|
1174
1247
|
case "blocked":
|
|
1175
1248
|
return "先处理阻塞证据";
|
|
1176
1249
|
default:
|
|
@@ -1184,59 +1257,78 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1184
1257
|
if (state.phase === "blocked") return `已阻塞:${state.label}`;
|
|
1185
1258
|
if (snapshot.action === "await_user_confirmation") return "等待人工审查";
|
|
1186
1259
|
if (snapshot.action === "blocked") return "已阻塞";
|
|
1187
|
-
return "
|
|
1260
|
+
return "当前会话未运行智能体";
|
|
1188
1261
|
}
|
|
1189
1262
|
|
|
1190
1263
|
function projectControlPanel(snapshot: Record<string, unknown> | null, state: SessionControlState): string[] {
|
|
1191
1264
|
if (!snapshot) {
|
|
1192
|
-
return ["Devflow
|
|
1265
|
+
return ["Devflow 当前功能交付", "── 项目状态 ──", "尚未读取到有效控制面状态。"];
|
|
1193
1266
|
}
|
|
1194
1267
|
const steps = workflowSteps(snapshot);
|
|
1195
1268
|
const completedCount = steps.filter(step => step.status === "completed").length;
|
|
1196
|
-
const
|
|
1269
|
+
const selectedStep = compactStep(snapshot.current_step) ?? compactStep(snapshot.next_step);
|
|
1270
|
+
const currentStep = selectedStep
|
|
1271
|
+
? workflowStepName(selectedStep.id, selectedStep.label)
|
|
1272
|
+
: "未开始";
|
|
1273
|
+
const nextStep = steps.find(step => step.status !== "completed" && step.id !== selectedStep?.id)
|
|
1274
|
+
?? steps.find(step => step.status !== "completed");
|
|
1197
1275
|
const recovery = snapshot.recovery_plan && typeof snapshot.recovery_plan === "object"
|
|
1198
1276
|
? snapshot.recovery_plan as Record<string, unknown>
|
|
1199
1277
|
: undefined;
|
|
1200
1278
|
const recoveryLabel = recovery?.classification === "safe_repairable"
|
|
1201
|
-
? "
|
|
1279
|
+
? "可执行安全恢复(不会推进节点)"
|
|
1202
1280
|
: snapshot.action === "blocked"
|
|
1203
|
-
? "
|
|
1204
|
-
: "
|
|
1205
|
-
const chain = steps.length > 0
|
|
1206
|
-
? steps.map(step => `${workflowStepIcon(step.status)} ${step.label}`).join(" → ")
|
|
1207
|
-
: "尚无可展示的已知工作流步骤。";
|
|
1281
|
+
? "暂无自动重置方案,请先查看阻塞证据"
|
|
1282
|
+
: "当前无需恢复或重置";
|
|
1208
1283
|
return [
|
|
1209
|
-
`Devflow
|
|
1210
|
-
`阶段:${
|
|
1211
|
-
|
|
1212
|
-
`进度:已完成 ${completedCount} / 当前已知 ${steps.length}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1284
|
+
`Devflow 当前功能交付 · ${String(snapshot.current_feature ?? "未开始")}`,
|
|
1285
|
+
`阶段:${workflowPhaseName(snapshot.current_phase)} 版本:${String(snapshot.revision ?? "未知")}`,
|
|
1286
|
+
"── 交付链路 ──",
|
|
1287
|
+
`进度:已完成 ${completedCount} / 当前已知 ${steps.length} 步 工作流:${workflowStatusName(snapshot.status)}`,
|
|
1288
|
+
`当前:${currentStep}${nextStep ? ` 后续:${workflowStepName(nextStep.id, nextStep.label)}` : ""}`,
|
|
1289
|
+
"── 当前节点 ──",
|
|
1290
|
+
`节点状态:${workflowStatusName(selectedStep?.status)} 执行状态:${projectExecutionLabel(state, snapshot)}`,
|
|
1291
|
+
`节点动作:${workflowActionName(snapshot.action)}`,
|
|
1292
|
+
"── 下一步执行 ──",
|
|
1293
|
+
`可执行操作:${projectActionLabel(snapshot)}`,
|
|
1294
|
+
"── 恢复与重置 ──",
|
|
1295
|
+
`处理建议:${recoveryLabel}`,
|
|
1217
1296
|
];
|
|
1218
1297
|
}
|
|
1219
1298
|
|
|
1299
|
+
function projectControlWidget(lines: string[]) {
|
|
1300
|
+
return (_tui: unknown, theme: { fg(color: "accent" | "muted" | "dim", text: string): string; bold(text: string): string }) => {
|
|
1301
|
+
const panel = new Box(1, 0, undefined, {
|
|
1302
|
+
chars: { topLeft: "╭", topRight: "╮", bottomLeft: "╰", bottomRight: "╯", horizontal: "─", vertical: "│" },
|
|
1303
|
+
color: text => theme.fg("dim", text),
|
|
1304
|
+
});
|
|
1305
|
+
const content = lines.map((line, index) => {
|
|
1306
|
+
if (index === 0) return theme.bold(theme.fg("accent", line));
|
|
1307
|
+
if (line.startsWith("──")) return theme.fg("accent", line);
|
|
1308
|
+
return theme.fg("muted", line);
|
|
1309
|
+
}).join("\n");
|
|
1310
|
+
panel.addChild(new Text(content, 0, 0));
|
|
1311
|
+
return panel;
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1220
1315
|
function refreshProjectControlPanel(ctx: { cwd: string; ui: ProjectControlUi }): void {
|
|
1221
1316
|
const request = ++projectControlRefreshRevision;
|
|
1222
1317
|
void inspectWorkflow(ctx.cwd).then(({ result, snapshot }) => {
|
|
1223
1318
|
if (request !== projectControlRefreshRevision) return;
|
|
1224
1319
|
const content = result.ok
|
|
1225
1320
|
? projectControlPanel(snapshot, controlState)
|
|
1226
|
-
: ["Devflow
|
|
1227
|
-
ctx.ui.setWidget?.("devflow-project-control", content, { placement: "aboveEditor" });
|
|
1321
|
+
: ["Devflow 当前功能交付", "── 项目状态 ──", "状态读取失败;未执行任何恢复或状态迁移。"];
|
|
1322
|
+
ctx.ui.setWidget?.("devflow-project-control", projectControlWidget(content), { placement: "aboveEditor" });
|
|
1228
1323
|
}).catch(() => {
|
|
1229
1324
|
if (request !== projectControlRefreshRevision) return;
|
|
1230
|
-
ctx.ui.setWidget?.("devflow-project-control", ["Devflow
|
|
1325
|
+
ctx.ui.setWidget?.("devflow-project-control", projectControlWidget(["Devflow 当前功能交付", "── 项目状态 ──", "状态读取失败;未执行任何恢复或状态迁移。"]), { placement: "aboveEditor" });
|
|
1231
1326
|
});
|
|
1232
1327
|
}
|
|
1233
1328
|
|
|
1234
1329
|
function setControlState(ctx: {
|
|
1235
1330
|
cwd: string;
|
|
1236
|
-
ui:
|
|
1237
|
-
setStatus(key: string, text: string | undefined): void;
|
|
1238
|
-
setWidget?(key: string, content: string[] | undefined, options?: { placement?: "aboveEditor" | "belowEditor" }): void;
|
|
1239
|
-
};
|
|
1331
|
+
ui: ProjectControlUi;
|
|
1240
1332
|
}, state: SessionControlState) {
|
|
1241
1333
|
controlState = state;
|
|
1242
1334
|
ctx.ui.setStatus("devflow-control", statusText(state));
|
|
@@ -1690,8 +1782,9 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1690
1782
|
if (!controlPlaneSucceeded(synced)) {
|
|
1691
1783
|
throw new Error(synced.stderr || synced.stdout || "artifact registry sync after rollover prepare failed");
|
|
1692
1784
|
}
|
|
1693
|
-
|
|
1785
|
+
// The runtime can observe the request immediately; arm the switch handler first.
|
|
1694
1786
|
pendingRollover = { snapshot, preparedRevision: payload.revision, rolloverId };
|
|
1787
|
+
await enqueueControlledRolloverRequest(ctx.cwd, rolloverId, ctx.sessionManager.getSessionId());
|
|
1695
1788
|
}
|
|
1696
1789
|
|
|
1697
1790
|
function parallelBranches(snapshot: Record<string, unknown>): Array<{ workflow_step: string; track: string }> {
|
|
@@ -1914,27 +2007,27 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1914
2007
|
const feature = typeof snapshot?.current_feature === "string" ? snapshot.current_feature : undefined;
|
|
1915
2008
|
const status = typeof snapshot?.status === "string" ? snapshot.status : undefined;
|
|
1916
2009
|
if (!inspected.result.ok || revision === undefined || !feature || !status || ["completed", "cancelled", "failed"].includes(status)) {
|
|
1917
|
-
ctx.ui.notify("
|
|
2010
|
+
ctx.ui.notify("当前没有可取消的进行中功能。", "error");
|
|
1918
2011
|
return;
|
|
1919
2012
|
}
|
|
1920
|
-
const reason = await ctx.ui.input("
|
|
2013
|
+
const reason = await ctx.ui.input("取消当前功能", "请输入取消原因");
|
|
1921
2014
|
if (!reason?.trim()) return;
|
|
1922
2015
|
const confirmed = await ctx.ui.confirm(
|
|
1923
|
-
"
|
|
1924
|
-
|
|
2016
|
+
"确认取消当前功能",
|
|
2017
|
+
`功能:${feature}\n原因:${reason.trim()}\n\n将结束当前功能、释放项目占用并保留现有工件;未完成步骤不会被标记为完成。继续吗?`,
|
|
1925
2018
|
);
|
|
1926
2019
|
if (!confirmed) return;
|
|
1927
2020
|
const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
|
|
1928
2021
|
"--write", "--json", "--cancel", "--cancel-reason", reason.trim(), "--expected-revision", String(revision),
|
|
1929
2022
|
]);
|
|
1930
2023
|
if (!controlPlaneSucceeded(result)) {
|
|
1931
|
-
ctx.ui.notify(
|
|
2024
|
+
ctx.ui.notify(`取消当前功能失败:${result.stderr || result.stdout}`, "error");
|
|
1932
2025
|
return;
|
|
1933
2026
|
}
|
|
1934
2027
|
activeContract = undefined;
|
|
1935
2028
|
contractAgentCalls = 0;
|
|
1936
2029
|
setControlState(ctx, initialControlState());
|
|
1937
|
-
ctx.ui.notify(
|
|
2030
|
+
ctx.ui.notify(`功能“${feature}”已取消;现有工件已保留。`, "info");
|
|
1938
2031
|
}
|
|
1939
2032
|
|
|
1940
2033
|
async function openProjectControlPanel(ctx: ProjectControlCommandContext): Promise<void> {
|
|
@@ -1951,10 +2044,10 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1951
2044
|
if (["run_next_step", "run_parallel_window", "route_failure_branch"].includes(String(snapshot.action))) options.push("启动当前可执行步骤");
|
|
1952
2045
|
if (snapshot.action === "await_user_confirmation") options.push("审查并批准推进…");
|
|
1953
2046
|
if (hasSafeRecoveryPlan(snapshot)) options.push("执行安全恢复…");
|
|
1954
|
-
if (typeof snapshot.current_feature === "string" && !["completed", "cancelled", "failed"].includes(String(snapshot.status))) options.push("
|
|
2047
|
+
if (typeof snapshot.current_feature === "string" && !["completed", "cancelled", "failed"].includes(String(snapshot.status))) options.push("取消当前功能…");
|
|
1955
2048
|
options.push("关闭");
|
|
1956
2049
|
|
|
1957
|
-
const selected = await ctx.ui.select(
|
|
2050
|
+
const selected = await ctx.ui.select("项目控制操作", options);
|
|
1958
2051
|
if (!selected || selected === "关闭") return;
|
|
1959
2052
|
if (selected === "刷新状态") continue;
|
|
1960
2053
|
if (selected === "查看状态说明") {
|
|
@@ -1978,7 +2071,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
1978
2071
|
await executeSafeRecoveryFromPanel(ctx);
|
|
1979
2072
|
return;
|
|
1980
2073
|
}
|
|
1981
|
-
if (selected === "
|
|
2074
|
+
if (selected === "取消当前功能…") {
|
|
1982
2075
|
await cancelFeatureFromPanel(ctx);
|
|
1983
2076
|
return;
|
|
1984
2077
|
}
|
|
@@ -2759,7 +2852,7 @@ export default function devflowCore(pi: ExtensionAPI) {
|
|
|
2759
2852
|
propagation: z.enum(["one_hop", "full"]).optional(),
|
|
2760
2853
|
}).optional(),
|
|
2761
2854
|
}),
|
|
2762
|
-
approval: (args) => args.action === "propose" || args.action === "confirm" ? "write" : "read",
|
|
2855
|
+
approval: (args) => args.action === "propose" || args.action === "confirm" || (args.action === "context" && args.write === true) ? "write" : "read",
|
|
2763
2856
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
2764
2857
|
const graphWrite = params.action === "propose" || params.action === "confirm" || (params.action === "context" && params.write === true);
|
|
2765
2858
|
if (graphWrite) {
|
package/src/constants.ts
CHANGED
package/src/runtime.ts
CHANGED
|
@@ -209,7 +209,7 @@ export async function runInteractiveRuntime(project: ProjectContext, options: Ru
|
|
|
209
209
|
// authority remains STATE.rollover, which the extension verifies and claims
|
|
210
210
|
// during session_switch before it starts the next workflow turn.
|
|
211
211
|
const consumeControlledRollover = async (): Promise<void> => {
|
|
212
|
-
if (rolloverConsumptionInFlight) return;
|
|
212
|
+
if (rolloverConsumptionInFlight || interactiveSubmissionInFlight || runtime.session.isStreaming) return;
|
|
213
213
|
rolloverConsumptionInFlight = true;
|
|
214
214
|
try {
|
|
215
215
|
const requestPath = path.join(project.root, ".planning", "rollover-request.json");
|
|
@@ -238,6 +238,8 @@ export async function runInteractiveRuntime(project: ProjectContext, options: Ru
|
|
|
238
238
|
// already durable and STATE remains frozen until the target consumes it.
|
|
239
239
|
let switched = false;
|
|
240
240
|
for (let attempt = 0; attempt < 3 && !switched; attempt += 1) {
|
|
241
|
+
// newSession aborts a running turn; leave the durable request for the poller.
|
|
242
|
+
if (interactiveSubmissionInFlight || runtime.session.isStreaming) return;
|
|
241
243
|
switched = await runtime.session.newSession();
|
|
242
244
|
if (!switched && attempt < 2) await new Promise(resolve => setTimeout(resolve, 100));
|
|
243
245
|
}
|
|
@@ -259,8 +261,11 @@ export async function runInteractiveRuntime(project: ProjectContext, options: Ru
|
|
|
259
261
|
rolloverConsumptionInFlight = false;
|
|
260
262
|
mode.showWarning(`Devflow 会话轮换运行时异常,将自动重试:${String(error)}`);
|
|
261
263
|
scheduleControlledRolloverRetry();
|
|
264
|
+
} finally {
|
|
265
|
+
rolloverConsumptionInFlight = false;
|
|
262
266
|
}
|
|
263
267
|
};
|
|
268
|
+
let interactiveSubmissionInFlight = false;
|
|
264
269
|
let rolloverConsumptionInFlight = false;
|
|
265
270
|
let rolloverRetryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
266
271
|
let rolloverRetryAttempt = 0;
|
|
@@ -287,7 +292,12 @@ export async function runInteractiveRuntime(project: ProjectContext, options: Ru
|
|
|
287
292
|
await consumeControlledRollover();
|
|
288
293
|
while (true) {
|
|
289
294
|
const input = await mode.getUserInput();
|
|
290
|
-
|
|
295
|
+
interactiveSubmissionInFlight = true;
|
|
296
|
+
try {
|
|
297
|
+
await omp.submitInteractiveInput(mode, runtime.session, input);
|
|
298
|
+
} finally {
|
|
299
|
+
interactiveSubmissionInFlight = false;
|
|
300
|
+
}
|
|
291
301
|
await consumeControlledRollover();
|
|
292
302
|
// The submitted turn may create a durable rollover request after the
|
|
293
303
|
// immediate check above returns. Poll once while the UI is waiting for the
|