@guanlin4924/devflow-pi 0.1.11 → 0.1.13

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
@@ -29,9 +29,6 @@ devflow-pi
29
29
  /devflow explain-state
30
30
  /devflow recover
31
31
  /devflow start <feature>|<phase>
32
- /devflow continue
33
- /devflow advance
34
- /devflow checkpoint
35
32
  /devflow resume
36
33
  /devflow-usage
37
34
  /usage-stats
@@ -118,7 +115,7 @@ bash .skills/install.sh --apply --force-config
118
115
 
119
116
  首次使用时,在 OMP 的模型角色界面中分别为 `smol`、`task`、`slow` 选择具体模型;`modelRoleStorage: project` 会把选择保存在项目作用域。建议分别使用快速探索模型、代码实现模型和独立推理/审查模型。配置完成后以 `/devflow doctor` 的 `models:` 行为准。
120
117
 
121
- `/devflow start` 只创建 Feature 状态,不会自动启动整条流程。`/devflow continue` 只运行当前一个工作流步骤并停止;产物审查后由用户显式执行 `/devflow advance`,checkpoint 也必须由用户通过 `/devflow checkpoint` 确认。`/devflow explain-state` 发现控制面和制品 registry 漂移时会给出只读恢复计划;用户审查后可用 `/devflow recover` 确认仅修复可由文件系统证明的 registry 字段,它绝不推进步骤或自动通过 checkpoint。恢复已有流程使用 `/devflow resume`,它同样只恢复状态而不自动执行。普通中文输入“初始化项目”“查看状态”“需求:……”会进入相应的受控只读分诊,不会偷偷启动实现或状态迁移。
118
+ `/devflow start` 会先进入安全的需求梳理:可澄清并只读检查,不写入项目。用户随后直接补充需求,原文会被带入入口分诊并启动当前步骤。每个步骤结束后,系统展示产物和证据;用户明确回复“批准执行”才会推进并运行下一步,回复修改意见或“拒绝执行”则保持原步骤。普通需求描述绝不会自动确认 checkpoint。`/devflow explain-state` 发现控制面和制品 registry 漂移时会给出只读恢复计划;用户审查后可用 `/devflow recover` 确认仅修复可由文件系统证明的 registry 字段,它绝不推进步骤或自动通过 checkpoint。恢复已有流程使用 `/devflow resume`,它同样只恢复状态而不自动执行。
122
119
 
123
120
  ## Marketplace 能力包
124
121
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanlin4924/devflow-pi",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
6
6
  "license": "UNLICENSED",
@@ -41,6 +41,8 @@ const READ_ONLY_TOOLS = new Set([
41
41
  "usage_stats",
42
42
  ]);
43
43
 
44
+ const REQUIREMENT_DRAFT_TOOLS = new Set([...READ_ONLY_TOOLS, "ask"]);
45
+
44
46
  const PLANNING_AGENTS = new Set(["project-scout", "requirements-auditor", "contract-auditor"]);
45
47
 
46
48
  const STATE_MUTATION_TOOLS = new Set([
@@ -96,6 +98,30 @@ export function isPlanningToolAllowed(toolName: string, taskAgents: string[] = [
96
98
  return taskAgents.length > 0 && taskAgents.every(agent => PLANNING_AGENTS.has(agent));
97
99
  }
98
100
 
101
+ /** Requirement drafting may inspect with a shell, but never mutate a project. */
102
+ export function isReadOnlyShellCommand(command: string): boolean {
103
+ return command.trim() !== "" && !shellMutationPaths(command, ".").mutating;
104
+ }
105
+
106
+ export function isRequirementDraftToolAllowed(toolName: string, input: unknown, taskAgents: string[] = []): boolean {
107
+ const normalized = toolName.toLowerCase();
108
+ if (["bash", "shell", "exec_command"].includes(normalized)) {
109
+ const record = input && typeof input === "object" ? input as Record<string, unknown> : {};
110
+ return isReadOnlyShellCommand(String(record.command ?? record.cmd ?? ""));
111
+ }
112
+ if (REQUIREMENT_DRAFT_TOOLS.has(normalized)) return true;
113
+ return normalized === "task" && taskAgents.length > 0 && taskAgents.every(agent => PLANNING_AGENTS.has(agent));
114
+ }
115
+
116
+ export type NaturalControlAction = "approve" | "reject";
117
+
118
+ export function classifyNaturalControlAction(input: string): NaturalControlAction | null {
119
+ const text = input.trim();
120
+ if (/^(?:批准|确认|同意|通过)(?:执行|继续|推进|当前(?:方案|步骤|计划))?[。!!]?$/.test(text)) return "approve";
121
+ if (/^(?:拒绝|不同意|暂不|需要修改|退回)(?:执行|继续|推进|当前(?:方案|步骤|计划))?[。!!]?$/.test(text)) return "reject";
122
+ return null;
123
+ }
124
+
99
125
  export function isStateMutationTool(toolName: string): boolean {
100
126
  return STATE_MUTATION_TOOLS.has(toolName.toLowerCase());
101
127
  }
@@ -488,7 +514,7 @@ function compactStep(value: unknown): Record<string, unknown> | null {
488
514
  };
489
515
  }
490
516
 
491
- function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "resume" | "continue"): string {
517
+ function workflowDirective(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string): string {
492
518
  const selectedStep = compactStep(snapshot.next_step) ?? compactStep(snapshot.current_step);
493
519
  const envelope = {
494
520
  source,
@@ -502,13 +528,14 @@ function workflowDirective(snapshot: Record<string, unknown>, source: "start" |
502
528
  failure_branch: snapshot.failure_branch,
503
529
  reason: snapshot.reason,
504
530
  recovery: snapshot.recovery,
531
+ requirement_draft: requirementDraft,
505
532
  };
506
533
  return [
507
534
  "<devflow-runtime-directive>",
508
535
  "Devflow 已通过启动预检。读取 skill://workflow-pipeline,并只执行当前项目的一个工作流步骤。",
509
536
  "先调用 devflow_state(action=\"inspect\") 重新确认 revision,再由 workflow-orchestrator 路由当前步骤。",
510
537
  "只按 inspector 返回的当前 step 加载所需 Skill;不得调用 devflow_transition、不得推进下一步骤、不得替用户通过人工 checkpoint。",
511
- "完成本步骤的真实产物后,报告证据、未决项和验证结果并停止,等待用户执行 /devflow advance。",
538
+ "完成本步骤的真实产物后,报告证据、未决项和验证结果并停止。请用户审查后明确回复“批准执行”才可推进;回复修改意见或“拒绝执行”则不得推进。",
512
539
  "当前步骤信封:",
513
540
  JSON.stringify(envelope, null, 2),
514
541
  "</devflow-runtime-directive>",
@@ -853,8 +880,11 @@ export default function devflowCore(pi: ExtensionAPI) {
853
880
  return { result, snapshot: parseJsonResult(result) };
854
881
  }
855
882
 
856
- async function triggerWorkflow(snapshot: Record<string, unknown>, source: "start" | "resume" | "continue") {
857
- await pi.sendUserMessage(workflowDirective(snapshot, source));
883
+ let activeWorkflowTurn = false;
884
+
885
+ async function triggerWorkflow(snapshot: Record<string, unknown>, source: "start" | "approval", requirementDraft?: string) {
886
+ activeWorkflowTurn = true;
887
+ await pi.sendUserMessage(workflowDirective(snapshot, source, requirementDraft));
858
888
  }
859
889
 
860
890
  pi.setLabel("Devflow Project Runtime");
@@ -879,8 +909,69 @@ export default function devflowCore(pi: ExtensionAPI) {
879
909
  systemPrompt: [...event.systemPrompt, DEVFLOW_CHINESE_LANGUAGE_POLICY],
880
910
  }));
881
911
 
912
+ pi.on("agent_end", async (event, ctx) => {
913
+ if (!activeWorkflowTurn || event.willContinue) return;
914
+ activeWorkflowTurn = false;
915
+ if (controlState.phase === "executing") {
916
+ setControlState(ctx, waitingUserState("等待用户审查步骤产物并明确决定"));
917
+ ctx.ui.notify("当前步骤已结束。请审查输出:明确回复“批准执行”推进,或回复修改意见/“拒绝执行”保持当前步骤。", "info");
918
+ }
919
+ });
920
+
882
921
  pi.on("input", async (event, ctx) => {
883
922
  if (event.source !== "interactive") return;
923
+ const controlAction = classifyNaturalControlAction(event.text);
924
+ if (controlState.intent === "requirement-draft" && controlState.phase === "planning") {
925
+ const requirementDraft = event.text.trim();
926
+ if (!requirementDraft) return { handled: true };
927
+ const inspected = await inspectWorkflow(ctx.cwd);
928
+ if (!inspected.result.ok || !inspected.snapshot) {
929
+ ctx.ui.notify(`需求分诊启动失败:${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
930
+ return { handled: true };
931
+ }
932
+ setControlState(ctx, executingState("执行需求入口分诊"));
933
+ await triggerWorkflow(inspected.snapshot, "start", requirementDraft);
934
+ return { handled: true };
935
+ }
936
+ if (controlState.phase === "awaiting-approval" || controlState.phase === "waiting-user") {
937
+ if (controlAction === "reject") {
938
+ ctx.ui.notify("已保留当前步骤和产物。请直接说明需要修改的内容;未执行任何推进。", "info");
939
+ return { handled: true };
940
+ }
941
+ if (controlAction === "approve") {
942
+ const inspected = await inspectWorkflow(ctx.cwd);
943
+ if (!inspected.result.ok || !inspected.snapshot) {
944
+ ctx.ui.notify(`审批处理失败:${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
945
+ return { handled: true };
946
+ }
947
+ const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
948
+ const checkpoint = inspected.snapshot.status === "waiting_checkpoint";
949
+ const step = compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step);
950
+ const stepId = typeof step?.id === "string" ? step.id : undefined;
951
+ if (revision === undefined || (!checkpoint && !stepId)) {
952
+ ctx.ui.notify("当前状态缺少可确认的 revision 或步骤,拒绝推进。", "error");
953
+ return { handled: true };
954
+ }
955
+ const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
956
+ "--write", "--json", "--expected-revision", String(revision),
957
+ ...(checkpoint ? ["--confirm-checkpoint"] : ["--complete-step", stepId!]),
958
+ ]);
959
+ if (!result.ok) {
960
+ setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进校验失败" });
961
+ ctx.ui.notify(`未推进:${result.stderr || result.stdout}`, "error");
962
+ return { handled: true };
963
+ }
964
+ const after = await inspectWorkflow(ctx.cwd);
965
+ if (!after.result.ok || !after.snapshot || after.snapshot.action === "workflow_complete") {
966
+ setControlState(ctx, initialControlState());
967
+ ctx.ui.notify(after.snapshot?.action === "workflow_complete" ? "工作流已完成。" : "已推进,但读取下一步骤失败。", "info");
968
+ return { handled: true };
969
+ }
970
+ setControlState(ctx, executingState("执行已批准的下一工作流步骤"));
971
+ await triggerWorkflow(after.snapshot, "approval");
972
+ return { handled: true };
973
+ }
974
+ }
884
975
  const intent = classifyNaturalLanguageIntent(event.text);
885
976
  if (intent === "explain-state") {
886
977
  const inspected = await inspectWorkflow(ctx.cwd);
@@ -913,7 +1004,7 @@ export default function devflowCore(pi: ExtensionAPI) {
913
1004
  return {
914
1005
  text: [
915
1006
  "<devflow-planner-directive>",
916
- "当前是需求梳理(只读)阶段。只允许澄清、收集证据、派发只读审查代理和输出计划草案。",
1007
+ "当前是需求梳理(只读)阶段。可澄清、收集证据、派发只读审查代理,且可使用不修改文件的 shell 检查。",
917
1008
  "不得开始工作流、不得写入产物、不得调用实现代理或状态迁移。",
918
1009
  "把用户目标、约束、未知项、反例和下一步最少问题分开列出;缺少证据时不得猜测。",
919
1010
  event.text,
@@ -944,7 +1035,10 @@ export default function devflowCore(pi: ExtensionAPI) {
944
1035
  if (shadows.length > 0) {
945
1036
  return { block: true, reason: `Devflow task agent shadow detected: ${shadows.join("; ")}` };
946
1037
  }
947
- if (controlState.phase === "planning" && !isPlanningToolAllowed(toolName, requested.names)) {
1038
+ const allowedInDraft = controlState.intent === "requirement-draft"
1039
+ ? isRequirementDraftToolAllowed(toolName, event.input, requested.names)
1040
+ : isPlanningToolAllowed(toolName, requested.names);
1041
+ if (controlState.phase === "planning" && !allowedInDraft) {
948
1042
  return { block: true, reason: planningToolBlockReason(toolName) };
949
1043
  }
950
1044
  if (
@@ -958,7 +1052,9 @@ export default function devflowCore(pi: ExtensionAPI) {
958
1052
  controlState.phase === "planning" &&
959
1053
  toolName !== "devflow_contract" &&
960
1054
  !(toolName === "devflow_evidence" && String((event.input as Record<string, unknown> | undefined)?.action) !== "create") &&
961
- !isPlanningToolAllowed(toolName)
1055
+ !(controlState.intent === "requirement-draft"
1056
+ ? isRequirementDraftToolAllowed(toolName, event.input)
1057
+ : isPlanningToolAllowed(toolName))
962
1058
  ) {
963
1059
  return { block: true, reason: planningToolBlockReason(toolName) };
964
1060
  }
@@ -966,7 +1062,7 @@ export default function devflowCore(pi: ExtensionAPI) {
966
1062
  return {
967
1063
  block: true,
968
1064
  reason:
969
- "Devflow state transitions are user-command-only. Use /devflow start, /devflow advance, or /devflow checkpoint; an LLM may not advance delivery state directly.",
1065
+ "Devflow 状态迁移只能由运行时在用户明确批准已展示的步骤或 checkpoint 后执行;LLM 不得自行推进。",
970
1066
  };
971
1067
  }
972
1068
  if (toolName.startsWith("devflow_")) return;
@@ -1444,9 +1540,9 @@ export default function devflowCore(pi: ExtensionAPI) {
1444
1540
  const step = compactStep(inspected.snapshot?.next_step) ?? compactStep(inspected.snapshot?.current_step);
1445
1541
  const stepId = typeof step?.id === "string" ? step.id : "当前步骤";
1446
1542
  if (inspected.result.ok && status === "in_progress") {
1447
- ctx.ui.notify(`控制面状态一致,无需恢复。当前步骤是 ${stepId};请执行 /devflow continue。`, "info");
1543
+ ctx.ui.notify(`控制面状态一致,无需恢复。当前步骤是 ${stepId};请审查当前产物后明确回复“批准执行”或提出修改意见。`, "info");
1448
1544
  } else if (inspected.result.ok && status === "waiting_checkpoint") {
1449
- ctx.ui.notify(`控制面状态一致,无需恢复。当前正在等待 checkpoint;请审查产物后执行 /devflow checkpoint。`, "info");
1545
+ ctx.ui.notify("控制面状态一致,无需恢复。当前正在等待 checkpoint;请审查产物后明确回复“批准执行”或提出修改意见。", "info");
1450
1546
  } else {
1451
1547
  ctx.ui.notify("当前状态没有可安全自动执行的恢复计划;请运行 /devflow explain-state,并按其中的阻塞证据处理。", "error");
1452
1548
  }
@@ -1471,7 +1567,7 @@ export default function devflowCore(pi: ExtensionAPI) {
1471
1567
  ctx.ui.notify(`控制面恢复失败:${result.stderr || result.stdout}`, "error");
1472
1568
  return;
1473
1569
  }
1474
- ctx.ui.notify("控制面 registry 已修复;未推进工作流。请审查当前步骤产物后使用 /devflow advance。", "info");
1570
+ ctx.ui.notify("控制面 registry 已修复;未推进工作流。请审查当前步骤产物后明确回复“批准执行”或提出修改意见。", "info");
1475
1571
  return;
1476
1572
  }
1477
1573
  if (trimmed === "project-init") {
@@ -1515,132 +1611,11 @@ export default function devflowCore(pi: ExtensionAPI) {
1515
1611
  }
1516
1612
  setControlState(ctx, waitingUserState("等待用户审查步骤产物"));
1517
1613
  ctx.ui.notify(
1518
- `Devflow 已恢复到 ${String((compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step))?.id ?? "当前步骤")};未自动执行。请先查看状态或使用 /devflow continue 执行一个步骤。`,
1614
+ `Devflow 已恢复到 ${String((compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step))?.id ?? "当前步骤")};请审查当前产物后明确回复“批准执行”,或直接提出修改意见。`,
1519
1615
  "info",
1520
1616
  );
1521
1617
  return;
1522
1618
  }
1523
- if (trimmed === "continue") {
1524
- const report = await runDoctor(ctx);
1525
- if (!report.ok) {
1526
- ctx.ui.notify(formatDoctorReport(report).slice(0, 10000), "error");
1527
- return;
1528
- }
1529
- const inspected = await inspectWorkflow(ctx.cwd);
1530
- if (!inspected.result.ok || !inspected.snapshot) {
1531
- ctx.ui.notify(`Devflow continue failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
1532
- return;
1533
- }
1534
- if (inspected.snapshot.action === "workflow_complete") {
1535
- ctx.ui.notify("Devflow workflow is already complete.", "info");
1536
- return;
1537
- }
1538
- if (inspected.snapshot.status === "waiting_checkpoint") {
1539
- setControlState(ctx, { intent: "workflow-execute", phase: "awaiting-approval", label: "等待用户确认 checkpoint" });
1540
- ctx.ui.notify("当前步骤正在等待 checkpoint;请审查产物后使用 /devflow checkpoint。", "warning");
1541
- return;
1542
- }
1543
- setControlState(ctx, executingState("执行当前工作流步骤(完成后停止)"));
1544
- ctx.ui.notify("仅启动当前步骤;模型被禁止迁移状态或继续下一步骤。", "info");
1545
- await triggerWorkflow(inspected.snapshot, "continue");
1546
- return;
1547
- }
1548
- if (trimmed === "advance") {
1549
- const inspected = await inspectWorkflow(ctx.cwd);
1550
- if (!inspected.result.ok || !inspected.snapshot) {
1551
- ctx.ui.notify(`Devflow advance failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
1552
- return;
1553
- }
1554
- if (inspected.snapshot.action === "workflow_complete" && inspected.snapshot.source_flow) {
1555
- const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
1556
- if (revision === undefined) {
1557
- ctx.ui.notify("补救分支缺少 revision,拒绝返回主流程。", "error");
1558
- return;
1559
- }
1560
- const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
1561
- "--write",
1562
- "--json",
1563
- "--expected-revision",
1564
- String(revision),
1565
- "--exit-failure-branch",
1566
- ]);
1567
- if (!result.ok) {
1568
- ctx.ui.notify(`补救分支返回失败:${result.stderr || result.stdout}`, "error");
1569
- return;
1570
- }
1571
- ctx.ui.notify("补救分支已返回主流程;仍需审查 reverify 结果,并在 CP-4 明确确认后才能继续。", "info");
1572
- return;
1573
- }
1574
- if (inspected.snapshot.status === "waiting_checkpoint") {
1575
- ctx.ui.notify("当前状态等待 checkpoint;请使用 /devflow checkpoint。", "warning");
1576
- return;
1577
- }
1578
- const step = compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step);
1579
- const stepId = typeof step?.id === "string" ? step.id : undefined;
1580
- const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
1581
- if (!stepId || revision === undefined) {
1582
- ctx.ui.notify("当前状态没有可推进的步骤或 revision;请先执行 /devflow explain-state。", "error");
1583
- return;
1584
- }
1585
- const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
1586
- "--write",
1587
- "--json",
1588
- "--expected-revision",
1589
- String(revision),
1590
- "--complete-step",
1591
- stepId,
1592
- ]);
1593
- if (!result.ok) {
1594
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "推进校验失败" });
1595
- ctx.ui.notify(`步骤未推进:${result.stderr || result.stdout}`, "error");
1596
- return;
1597
- }
1598
- const after = await inspectWorkflow(ctx.cwd);
1599
- setControlState(
1600
- ctx,
1601
- after.snapshot?.status === "waiting_checkpoint"
1602
- ? { intent: "workflow-execute", phase: "awaiting-approval", label: "等待用户确认 checkpoint" }
1603
- : waitingUserState("等待用户审查步骤产物"),
1604
- );
1605
- ctx.ui.notify(`已完成步骤 ${stepId} 的真实产物校验并推进状态。${after.snapshot?.status === "waiting_checkpoint" ? "下一步需要 /devflow checkpoint。" : "下一步请审查后使用 /devflow continue。"}`, "info");
1606
- return;
1607
- }
1608
- if (trimmed === "checkpoint") {
1609
- const inspected = await inspectWorkflow(ctx.cwd);
1610
- if (!inspected.result.ok || !inspected.snapshot) {
1611
- ctx.ui.notify(`Devflow checkpoint failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
1612
- return;
1613
- }
1614
- if (inspected.snapshot.status !== "waiting_checkpoint") {
1615
- ctx.ui.notify("当前并不等待 checkpoint;请使用 /devflow explain-state 查看原因。", "warning");
1616
- return;
1617
- }
1618
- const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
1619
- if (revision === undefined) {
1620
- ctx.ui.notify("当前状态缺少 revision,拒绝确认 checkpoint。", "error");
1621
- return;
1622
- }
1623
- const confirmed = await ctx.ui.confirm("确认 Devflow checkpoint", "你已审查当前步骤产物并同意推进到下一步骤吗?");
1624
- if (!confirmed) {
1625
- ctx.ui.notify("checkpoint 未确认;状态保持不变。", "info");
1626
- return;
1627
- }
1628
- const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
1629
- "--write",
1630
- "--json",
1631
- "--expected-revision",
1632
- String(revision),
1633
- "--confirm-checkpoint",
1634
- ]);
1635
- if (!result.ok) {
1636
- setControlState(ctx, { intent: "workflow-execute", phase: "blocked", label: "checkpoint 校验失败" });
1637
- ctx.ui.notify(`checkpoint 未推进:${result.stderr || result.stdout}`, "error");
1638
- return;
1639
- }
1640
- setControlState(ctx, waitingUserState("等待用户确认下一步骤"));
1641
- ctx.ui.notify("checkpoint 已由用户确认;下一步骤尚未执行,请使用 /devflow continue。", "info");
1642
- return;
1643
- }
1644
1619
  if (trimmed.startsWith("start ")) {
1645
1620
  const [feature, phase = "delivery"] = trimmed.slice(6).split("|", 2).map(value => value.trim());
1646
1621
  if (!feature) {
@@ -1706,14 +1681,19 @@ export default function devflowCore(pi: ExtensionAPI) {
1706
1681
  return;
1707
1682
  }
1708
1683
  setControlState(ctx, planningState("requirement-draft"));
1709
- ctx.ui.notify(
1710
- `Devflow 已创建 Feature ${feature},但没有自动执行。当前步骤:${String((compactStep(inspected.snapshot.next_step) ?? compactStep(inspected.snapshot.current_step))?.id ?? "requirement-triage")}。请补充需求后使用 /devflow continue。`,
1711
- "info",
1684
+ await pi.sendUserMessage(
1685
+ [
1686
+ "<devflow-requirement-draft-directive>",
1687
+ `Devflow 已创建 Feature ${feature}。当前为安全的需求梳理阶段:允许澄清和只读证据收集,不得写入项目或迁移状态。`,
1688
+ "请先基于功能名说明你需要的最少业务背景,并邀请用户直接补充目标、使用者、约束和验收结果。收到用户的下一段需求描述后,运行时会将其原文注入 requirement-triage 并启动该步骤。",
1689
+ "不要让用户输入命令;也不要把普通需求文本当成批准或 checkpoint 确认。",
1690
+ "</devflow-requirement-draft-directive>",
1691
+ ].join("\n"),
1712
1692
  );
1713
1693
  return;
1714
1694
  }
1715
1695
  ctx.ui.notify(
1716
- "Usage: /devflow doctor | status | explain-state | recover | project-init | start <feature>|<phase> | resume | continue | advance | checkpoint",
1696
+ "Usage: /devflow doctor | status | explain-state | recover | project-init | start <feature>|<phase> | resume",
1717
1697
  "warning",
1718
1698
  );
1719
1699
  },
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionUIContext } from "@oh-my-pi/pi-coding-agent";
2
2
  import { mkdir, writeFile } from "node:fs/promises";
3
+ import { spawn } from "node:child_process";
3
4
  import path from "node:path";
4
5
 
5
6
  import { devflowHome } from "./paths.ts";
@@ -43,6 +44,13 @@ function daysAgo(days: number): string {
43
44
  return date.toISOString().slice(0, 10);
44
45
  }
45
46
 
47
+ function openHtmlReport(file: string): void {
48
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
49
+ const args = process.platform === "win32" ? ["/c", "start", "", file] : [file];
50
+ const child = spawn(command, args, { detached: true, stdio: "ignore" });
51
+ child.unref();
52
+ }
53
+
46
54
  export function createPlatformExtension(options: PlatformExtensionOptions) {
47
55
  return function devflowPlatform(pi: ExtensionAPI) {
48
56
  const { z } = pi.zod;
@@ -86,7 +94,7 @@ export function createPlatformExtension(options: PlatformExtensionOptions) {
86
94
  ...usageTool,
87
95
  });
88
96
  }
89
- async function pickUsageInput(ctx: { ui: ExtensionUIContext }): Promise<UsageInput | undefined> {
97
+ async function pickUsageInput(ctx: { ui: ExtensionUIContext; modelRegistry: { getAvailable(): Array<{ provider: string; id: string }> } }): Promise<UsageInput | undefined> {
90
98
  const sourceChoice = await ctx.ui.select("使用统计:数据范围", [
91
99
  { label: "当前 Devflow 项目", description: "只读取当前项目会话(推荐)" },
92
100
  { label: "OMP 历史", description: "读取 ~/.omp/agent/sessions" },
@@ -97,7 +105,10 @@ export function createPlatformExtension(options: PlatformExtensionOptions) {
97
105
  const overview = await aggregateUsageReport(root, source);
98
106
  const sessionChoice = await ctx.ui.select("使用统计:会话", ["全部会话", ...Object.keys(overview.perSession)]);
99
107
  if (!sessionChoice) return undefined;
100
- const modelChoice = await ctx.ui.select("使用统计:模型", ["全部模型", ...Object.keys(overview.perModel)]);
108
+ // The selector is for filtering a report, so it must list every model
109
+ // configured in OMP — not merely the model names found in old sessions.
110
+ const registeredModels = [...new Set(ctx.modelRegistry.getAvailable().map(model => `${model.provider}/${model.id}`))].sort();
111
+ const modelChoice = await ctx.ui.select("使用统计:模型", ["全部模型", ...registeredModels]);
101
112
  if (!modelChoice) return undefined;
102
113
  const period = await ctx.ui.select("使用统计:时间范围", ["全部时间", "今天", "最近 7 天", "最近 30 天", "自定义日期"]);
103
114
  if (!period) return undefined;
@@ -129,7 +140,8 @@ export function createPlatformExtension(options: PlatformExtensionOptions) {
129
140
  const destination = path.join(reportDir, `usage-${new Date().toISOString().replaceAll(":", "-")}.html`);
130
141
  await mkdir(reportDir, { recursive: true });
131
142
  await writeFile(destination, renderUsageHtml(report), "utf8");
132
- ctx.ui.notify(`已生成 HTML 使用报告:${destination}`, "info");
143
+ openHtmlReport(destination);
144
+ ctx.ui.notify(`已生成并在默认浏览器打开 HTML 使用报告:${destination}`, "info");
133
145
  return;
134
146
  }
135
147
  ctx.ui.notify(input.format === "json" ? JSON.stringify({ projectId: options.projectId, ...report }, null, 2).slice(0, 12000) : renderUsageReport(report).slice(0, 12000), "info");