@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanlin4924/devflow-pi",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "type": "module",
5
5
  "description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
6
6
  "license": "UNLICENSED",
@@ -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 <feature> 创建新 Feature。";
992
+ if (!snapshot) return "Devflow 当前没有可解释的工作流状态。可使用 /devflow start <功能名> 创建新功能。";
901
993
  const step = compactStep(snapshot.next_step) ?? compactStep(snapshot.current_step);
902
994
  const lines = [
903
- `Feature:${String(snapshot.current_feature ?? "未开始")}`,
904
- `状态:${String(snapshot.status ?? "unknown")}`,
905
- `动作:${String(snapshot.action ?? "unknown")}`,
906
- `Revision:${String(snapshot.revision ?? "unknown")}`,
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(`当前/下一步骤:${String(step.id)}${step.label ? `(${String(step.label)})` : ""}`);
909
- if (snapshot.checkpoint) lines.push(`Checkpoint:${String(snapshot.checkpoint)}`);
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("结论:上一 Feature 已完成;可以使用 /devflow start <新功能名> 创建下一 Feature。");
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
- setWidget?(key: string, content: string[] | undefined, options?: { placement?: "aboveEditor" | "belowEditor" }): void;
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 "可执行安全恢复(仅 registry)";
1242
+ return "可执行安全恢复(仅工件登记)";
1170
1243
  case "workflow_complete":
1171
- return "当前 Feature 已完成";
1244
+ return "当前功能已完成";
1172
1245
  case "workflow_cancelled":
1173
- return "当前 Feature 已取消";
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 "当前会话未运行 Agent";
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 当前 Feature 交付链路", "尚未读取到有效控制面状态。"];
1265
+ return ["Devflow 当前功能交付", "── 项目状态 ──", "尚未读取到有效控制面状态。"];
1193
1266
  }
1194
1267
  const steps = workflowSteps(snapshot);
1195
1268
  const completedCount = steps.filter(step => step.status === "completed").length;
1196
- const current = compactStep(snapshot.current_step) ?? compactStep(snapshot.next_step);
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 当前 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}`,
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 当前 Feature 交付链路", "状态读取失败;未执行任何恢复或状态迁移。"];
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 当前 Feature 交付链路", "状态读取失败;未执行任何恢复或状态迁移。"], { placement: "aboveEditor" });
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
- await enqueueControlledRolloverRequest(ctx.cwd, rolloverId, ctx.sessionManager.getSessionId());
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("当前没有可取消的进行中 Feature。", "error");
2010
+ ctx.ui.notify("当前没有可取消的进行中功能。", "error");
1918
2011
  return;
1919
2012
  }
1920
- const reason = await ctx.ui.input("取消当前 Feature", "请输入取消原因");
2013
+ const reason = await ctx.ui.input("取消当前功能", "请输入取消原因");
1921
2014
  if (!reason?.trim()) return;
1922
2015
  const confirmed = await ctx.ui.confirm(
1923
- "确认取消当前 Feature",
1924
- `Feature:${feature}\n原因:${reason.trim()}\n\n将结束当前 Feature、释放项目占用并保留现有工件;未完成步骤不会被标记为完成。继续吗?`,
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(`取消当前 Feature 失败:${result.stderr || result.stdout}`, "error");
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(`Feature“${feature}”已取消;现有工件已保留。`, "info");
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("取消当前 Feature…");
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(projectControlPanel(snapshot, controlState).join("\n"), options);
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 === "取消当前 Feature…") {
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
@@ -1,5 +1,5 @@
1
1
  export const PRODUCT_NAME = "Devflow Pi";
2
- export const PRODUCT_VERSION = "0.1.50";
2
+ export const PRODUCT_VERSION = "0.1.52";
3
3
  export const PROJECT_SCHEMA_VERSION = 1;
4
4
  export const OMP_RUNTIME_VERSION = "17.2.7";
5
5
 
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
- await omp.submitInteractiveInput(mode, runtime.session, input);
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