@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 +1 -4
- package/package.json +1 -1
- package/plugins/devflow-core/extensions/devflow-core.ts +117 -137
- package/src/platform-extension.ts +15 -3
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`
|
|
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
|
@@ -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" | "
|
|
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
|
-
"
|
|
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
|
-
|
|
857
|
-
|
|
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
|
-
|
|
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
|
-
!
|
|
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
|
|
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}
|
|
1543
|
+
ctx.ui.notify(`控制面状态一致,无需恢复。当前步骤是 ${stepId};请审查当前产物后明确回复“批准执行”或提出修改意见。`, "info");
|
|
1448
1544
|
} else if (inspected.result.ok && status === "waiting_checkpoint") {
|
|
1449
|
-
ctx.ui.notify(
|
|
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
|
|
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 ?? "当前步骤")}
|
|
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
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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");
|