toolflow 3.1.4
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/.github/workflows/ci.yml +31 -0
- package/README.md +106 -0
- package/README_zh.md +109 -0
- package/docs/reports/ADVANCED_EVOLUTION_REPORT.md +44 -0
- package/docs/reports/AUDIT_AND_OPTIMIZATION_REPORT.md +645 -0
- package/docs/reports/COLD_START_REVIEW_EVOLUTION.md +51 -0
- package/docs/reports/DEEP_ECOSYSTEM_EVOLUTION.md +43 -0
- package/docs/reports/MEMORY.md +18 -0
- package/docs/reports/MICHAEL_DISPATCH_RESULT.md +36 -0
- package/docs/reports/OPENSOURCE_INTEGRATION_REPORT.md +43 -0
- package/docs/reports/PHASE_1_OPTIMIZATION_REPORT.md +87 -0
- package/docs/reports/PHASE_2_OPTIMIZATION_REPORT.md +50 -0
- package/docs/reports/PHASE_3_OPTIMIZATION_REPORT.md +24 -0
- package/docs/reports/PHASE_4_OPTIMIZATION_REPORT.md +28 -0
- package/docs/reports/REPORT_TO_MICHAEL.md +101 -0
- package/docs/reports/SIGNOFF_AND_RELEASE_REPORT.md +85 -0
- package/docs/reports/STAFF_ASSIGNMENTS.md +26 -0
- package/docs/reports/TASK_ASSIGNMENTS.md +59 -0
- package/docs/reports/V1_6_0_EVOLUTION_REPORT.md +48 -0
- package/docs/reports/V1_9_0_HOTFIX_REPORT.md +30 -0
- package/docs/reports/V2_0_0_RELEASE_REPORT.md +18 -0
- package/docs/reports/V2_2_0_ZERO_SPECIALIZATION_REPORT.md +24 -0
- package/docs/reports/V2_3_0_EVOLUTION_REPORT.md +12 -0
- package/ecosystem_taxonomy.json +798 -0
- package/package.json +46 -0
- package/src/blast_radius.ts +302 -0
- package/src/deep_ecosystem.ts +523 -0
- package/src/degradation_matrix.ts +180 -0
- package/src/dehydrator.ts +532 -0
- package/src/ecosystem_taxonomy.json +803 -0
- package/src/engine.ts +1510 -0
- package/src/i18n.ts +89 -0
- package/src/index.ts +983 -0
- package/src/json_extractor.ts +57 -0
- package/src/memory.ts +151 -0
- package/src/prompts_manager.ts +262 -0
- package/src/review_isolation.ts +188 -0
- package/src/state.ts +810 -0
- package/src/taxonomy.ts +580 -0
- package/src/types.ts +341 -0
- package/src/ui.ts +1036 -0
- package/src/worker_orchestrator.ts +60 -0
- package/tests/challenger_stress_harness.ts +265 -0
- package/tests/monorepo_multilang_stress.ts +404 -0
- package/tests/sandbox_e2e.ts +167 -0
- package/tests/test_json_extractor.ts +44 -0
- package/tests/test_modules_1_to_4.ts +106 -0
- package/tests/test_suite.ts +1689 -0
- package/tsconfig.json +17 -0
package/src/ui.ts
ADDED
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
import { isZh } from "./i18n.js";
|
|
2
|
+
import { truncateToWidth, visibleWidth, sliceByColumn, matchesKey, parseKey } from "@earendil-works/pi-tui";
|
|
3
|
+
import { PromptsManager, PromptItemInfo } from "./prompts_manager.js";
|
|
4
|
+
import { EcosystemTaxonomy, Blueprint, DecisionSlot, BlueprintStage, ArchitectNavigatorResult, TaskRequirementChoice, TaskDiagnosis } from "./types.js";
|
|
5
|
+
import { diagnoseTaskExecutionMode } from "./engine.js";
|
|
6
|
+
|
|
7
|
+
// 边框规范与内宽计算
|
|
8
|
+
const BOX_BORDER_LEFT = "│ ";
|
|
9
|
+
const BOX_BORDER_RIGHT = " │";
|
|
10
|
+
const BOX_BORDER_OVERHEAD = BOX_BORDER_LEFT.length + BOX_BORDER_RIGHT.length;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 专为 CJK 设计的单行输入框可视窗口裁剪器
|
|
14
|
+
* 无论汉字怎么输入、退格,确保返回的物理宽度恒等于 windowWidth
|
|
15
|
+
*/
|
|
16
|
+
export function renderCJKSafeInputBox(
|
|
17
|
+
prefix: string,
|
|
18
|
+
text: string,
|
|
19
|
+
windowWidth: number,
|
|
20
|
+
theme: any,
|
|
21
|
+
showCursor: boolean = true
|
|
22
|
+
): string {
|
|
23
|
+
const prefixWidth = visibleWidth(prefix);
|
|
24
|
+
const cursorWidth = showCursor ? 1 : 0;
|
|
25
|
+
const availableWidth = Math.max(10, windowWidth - prefixWidth - cursorWidth);
|
|
26
|
+
|
|
27
|
+
const totalTextWidth = visibleWidth(text);
|
|
28
|
+
|
|
29
|
+
let visibleSlice = text;
|
|
30
|
+
if (totalTextWidth > availableWidth) {
|
|
31
|
+
const startCol = totalTextWidth - availableWidth;
|
|
32
|
+
visibleSlice = sliceByColumn(text, startCol, availableWidth, true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const currentSliceWidth = visibleWidth(visibleSlice);
|
|
36
|
+
const padCount = Math.max(0, availableWidth - currentSliceWidth);
|
|
37
|
+
const cursorChar = showCursor ? theme.fg("accent", "█") : "";
|
|
38
|
+
|
|
39
|
+
return `${prefix}${visibleSlice}${cursorChar}${" ".repeat(padCount)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function padToVisibleWidth(content: string, targetWidth: number): string {
|
|
43
|
+
const truncated = truncateToWidth(content, targetWidth, "", true);
|
|
44
|
+
const visW = visibleWidth(truncated);
|
|
45
|
+
const padCount = Math.max(0, targetWidth - visW);
|
|
46
|
+
return truncated + " ".repeat(padCount);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function renderValueReceipt(metrics: {
|
|
50
|
+
task: string;
|
|
51
|
+
blueprintId: string;
|
|
52
|
+
stageCount: number;
|
|
53
|
+
verifiedFiles: string[];
|
|
54
|
+
totalDurationSec?: number;
|
|
55
|
+
durationSec?: number;
|
|
56
|
+
tokenSavingsRatio?: string;
|
|
57
|
+
tokensSavedPct?: number;
|
|
58
|
+
}): string[] {
|
|
59
|
+
const w = 62;
|
|
60
|
+
const line = "-".repeat(w);
|
|
61
|
+
const rows: string[] = [];
|
|
62
|
+
const dur = metrics.totalDurationSec ?? metrics.durationSec ?? 0;
|
|
63
|
+
const saved = metrics.tokenSavingsRatio ?? `${metrics.tokensSavedPct ?? 65}%`;
|
|
64
|
+
|
|
65
|
+
rows.push(`+${line}+`);
|
|
66
|
+
rows.push(`| ${padToVisibleWidth(" VALUE DELIVERY RECEIPT", w - 2)} |`);
|
|
67
|
+
rows.push(`+${line}+`);
|
|
68
|
+
rows.push(`| ${padToVisibleWidth(`任务目标 : ${metrics.task}`, w - 2)} |`);
|
|
69
|
+
rows.push(`| ${padToVisibleWidth(`蓝图编号 : ${metrics.blueprintId}`, w - 2)} |`);
|
|
70
|
+
rows.push(`| ${padToVisibleWidth(`阶段总数 : ${metrics.stageCount} 个阶段已全部闭环`, w - 2)} |`);
|
|
71
|
+
rows.push(`| ${padToVisibleWidth(`交付耗时 : ${dur}s`, w - 2)} |`);
|
|
72
|
+
rows.push(`| ${padToVisibleWidth(`Token 效率 : ~${saved} 冗余已被裁剪`, w - 2)} |`);
|
|
73
|
+
rows.push(`+${line}+`);
|
|
74
|
+
rows.push(`| ${padToVisibleWidth("已验收物理产物清单 (SHA-256 校验通过):", w - 2)} |`);
|
|
75
|
+
for (const f of metrics.verifiedFiles.slice(0, 4)) {
|
|
76
|
+
rows.push(`| ${padToVisibleWidth(` [x] ${f}`, w - 2)} |`);
|
|
77
|
+
}
|
|
78
|
+
if (metrics.verifiedFiles.length > 4) {
|
|
79
|
+
const remaining = metrics.verifiedFiles.length - 4;
|
|
80
|
+
rows.push(`| ${padToVisibleWidth(` ... 另有 ${remaining} 个物理产物已通过 SHA 校验`, w - 2)} |`);
|
|
81
|
+
}
|
|
82
|
+
rows.push(`+${line}+`);
|
|
83
|
+
rows.push(`| ${padToVisibleWidth("快捷操作: [/toolflow export] 导出完整蓝图文档", w - 2)} |`);
|
|
84
|
+
rows.push(`+${line}+`);
|
|
85
|
+
|
|
86
|
+
return rows;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 动态执行流水线看板 (执行过程多波次与中高阶工具直观呈现)
|
|
91
|
+
*/
|
|
92
|
+
export function renderExecutionPipelineCard(params: {
|
|
93
|
+
blueprintId: string;
|
|
94
|
+
task: string;
|
|
95
|
+
currentStageIndex: number;
|
|
96
|
+
stages: BlueprintStage[];
|
|
97
|
+
activeWorkers?: Array<{ name: string; tool: string; status: string }>;
|
|
98
|
+
verifiedArtifactCount: number;
|
|
99
|
+
}): string {
|
|
100
|
+
const completedCount = params.stages.filter((_, idx) => idx < params.currentStageIndex).length;
|
|
101
|
+
const totalCount = params.stages.length;
|
|
102
|
+
|
|
103
|
+
const lines: string[] = [
|
|
104
|
+
`● **执行进度** (${completedCount}/${totalCount}) \`${params.blueprintId}\``,
|
|
105
|
+
`> 🎯 目标: **${params.task}**`,
|
|
106
|
+
""
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
params.stages.forEach((stage, idx) => {
|
|
110
|
+
const isLast = idx === params.stages.length - 1;
|
|
111
|
+
const branch = isLast ? "└─" : "├─";
|
|
112
|
+
|
|
113
|
+
let symbol = "○";
|
|
114
|
+
let statusDesc = "等待就绪";
|
|
115
|
+
|
|
116
|
+
if (idx < params.currentStageIndex) {
|
|
117
|
+
symbol = "✓";
|
|
118
|
+
statusDesc = "已验收完成";
|
|
119
|
+
} else if (idx === params.currentStageIndex) {
|
|
120
|
+
symbol = "◐";
|
|
121
|
+
statusDesc = "正在执行推进中...";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const highLevelTools: string[] = [];
|
|
125
|
+
if (stage.allowedTools?.includes("workflow")) highLevelTools.push("@workflow");
|
|
126
|
+
if (stage.allowedTools?.includes("goal") || stage.allowedTools?.includes("goal_complete")) highLevelTools.push("@goal");
|
|
127
|
+
if (stage.allowedTools?.includes("subagent")) highLevelTools.push("@subagent");
|
|
128
|
+
const toolBadge = highLevelTools.length > 0 ? ` [${highLevelTools.join(", ")}]` : "";
|
|
129
|
+
|
|
130
|
+
lines.push(`${branch} ${symbol} **阶段 ${idx + 1}**: ${stage.title}${toolBadge} \`(${statusDesc})\``);
|
|
131
|
+
lines.push(` └ 产物交付: \`${stage.expectedArtifact}\` | 责任专员: \`${stage.roleProfile}\``);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (params.activeWorkers && params.activeWorkers.length > 0) {
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push(`**🚀 活跃高阶编排节点:**`);
|
|
137
|
+
params.activeWorkers.forEach(w => {
|
|
138
|
+
lines.push(`- \`${w.name}\` -> 调度工具: **${w.tool}** (${w.status})`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
lines.push("");
|
|
143
|
+
lines.push(`- **物理交付物核验进度**: ${params.verifiedArtifactCount} / ${params.stages.length} 已物理落地并校验`);
|
|
144
|
+
lines.push(`*提示: 输入 \`/toolflow rollback\` 可秒级撤销当前阶段变更。*`);
|
|
145
|
+
|
|
146
|
+
return lines.join("\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 渲染 Unicode 阶段拓扑 DAG 字符流程图
|
|
151
|
+
*/
|
|
152
|
+
export function renderUnicodeDAG(
|
|
153
|
+
stages: BlueprintStage[],
|
|
154
|
+
options?: {
|
|
155
|
+
currentStageIndex?: number;
|
|
156
|
+
width?: number;
|
|
157
|
+
theme?: any;
|
|
158
|
+
}
|
|
159
|
+
): string[] {
|
|
160
|
+
if (!stages || stages.length === 0) return [];
|
|
161
|
+
const theme = options?.theme || {
|
|
162
|
+
bold: (s: string) => `\x1b[1m${s}\x1b[22m`,
|
|
163
|
+
fg: (_color: string, s: string) => s,
|
|
164
|
+
};
|
|
165
|
+
const activeIdx = options?.currentStageIndex ?? -1;
|
|
166
|
+
const lines: string[] = [];
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < stages.length; i++) {
|
|
169
|
+
const stage = stages[i];
|
|
170
|
+
const isCurrent = i === activeIdx;
|
|
171
|
+
const isDone = activeIdx >= 0 && i < activeIdx;
|
|
172
|
+
|
|
173
|
+
const statusNode = isDone
|
|
174
|
+
? theme.fg("success", "[x]")
|
|
175
|
+
: isCurrent
|
|
176
|
+
? theme.fg("accent", theme.bold("[>]"))
|
|
177
|
+
: theme.fg("dim", "[ ]");
|
|
178
|
+
|
|
179
|
+
const stageNum = `[阶段 ${i + 1}]`;
|
|
180
|
+
const headerTitle = `${stageNum} ${stage.title}`;
|
|
181
|
+
const headerLine = isCurrent
|
|
182
|
+
? `${statusNode} ${theme.bold(theme.fg("accent", headerTitle))}`
|
|
183
|
+
: `${statusNode} ${theme.bold(headerTitle)}`;
|
|
184
|
+
|
|
185
|
+
lines.push(headerLine);
|
|
186
|
+
|
|
187
|
+
const roleLine = ` ├─ [角色] ${theme.fg("dim", stage.roleProfile)}`;
|
|
188
|
+
const artifactLine = ` ├─ [产物] ${theme.fg("accent", stage.expectedArtifact)}`;
|
|
189
|
+
|
|
190
|
+
let depLine: string | null = null;
|
|
191
|
+
if (stage.dependsOn && stage.dependsOn.length > 0) {
|
|
192
|
+
depLine = ` ├─ [前置] ${theme.fg("warning", stage.dependsOn.join(", "))}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const boundExts = stage.boundCapabilities?.extensions || [];
|
|
196
|
+
const boundSkills = stage.boundCapabilities?.skills || [];
|
|
197
|
+
const boundPrompts = stage.boundCapabilities?.prompts || [];
|
|
198
|
+
|
|
199
|
+
const boundAll = [...boundExts, ...boundSkills, ...boundPrompts];
|
|
200
|
+
const boundLine = boundAll.length > 0
|
|
201
|
+
? ` └─ [生态] ${theme.fg("success", boundAll.map(b => `@${b}`).join(" "))}`
|
|
202
|
+
: ` └─ [工具] ${theme.fg("dim", (stage.allowedTools || []).join(", ") || "原生基础工具")}`;
|
|
203
|
+
|
|
204
|
+
lines.push(roleLine);
|
|
205
|
+
lines.push(artifactLine);
|
|
206
|
+
if (depLine) lines.push(depLine);
|
|
207
|
+
lines.push(boundLine);
|
|
208
|
+
|
|
209
|
+
if (i < stages.length - 1) {
|
|
210
|
+
lines.push(" │");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return lines;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 渲染生态全景概览 Markdown
|
|
219
|
+
*/
|
|
220
|
+
export function renderCompactEcosystemOverview(tax: EcosystemTaxonomy): string {
|
|
221
|
+
const getItems = (items: any[]) =>
|
|
222
|
+
items.length > 0 ? items.map((i) => `\`${i.name}\``).join(" ") : "无";
|
|
223
|
+
|
|
224
|
+
const l1 = getItems(tax.extensions.filter((e) => e.layer === "L1_UTILITY"));
|
|
225
|
+
const l2 = getItems(tax.extensions.filter((e) => e.layer === "L2_PERCEPTION"));
|
|
226
|
+
const l3 = getItems(tax.extensions.filter((e) => e.layer === "L3_ORCHESTRATION"));
|
|
227
|
+
const l4 = getItems(tax.extensions.filter((e) => e.layer === "L4_REVIEW_GUARD"));
|
|
228
|
+
|
|
229
|
+
const skills = getItems(tax.skills);
|
|
230
|
+
const prompts = getItems(tax.prompts);
|
|
231
|
+
|
|
232
|
+
return [
|
|
233
|
+
`### 🧩 已安装生态能力清单 (0-Token 内存感知)`,
|
|
234
|
+
`- **通用基础工具**: ${l1}`,
|
|
235
|
+
`- **信息与搜索**: ${l2}`,
|
|
236
|
+
`- **多工协同与编排**: ${l3}`,
|
|
237
|
+
`- **质量验证与走查**: ${l4}`,
|
|
238
|
+
`- **技能库 (Skills)**: ${skills}`,
|
|
239
|
+
`- **提示词模版**: ${prompts}`,
|
|
240
|
+
`> 提示: 输入 \`/toolflow <任务目标>\` 即可一键自适应生成最优执行蓝图。`
|
|
241
|
+
].join("\n");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 渲染蓝图交付摘要 Markdown
|
|
246
|
+
*/
|
|
247
|
+
export function renderBlueprintSummary(bp: Blueprint): string {
|
|
248
|
+
const lines: string[] = [];
|
|
249
|
+
lines.push(`## [执行蓝图] ${bp.task}`);
|
|
250
|
+
lines.push(`**蓝图编号**: \`${bp.blueprintId}\``);
|
|
251
|
+
lines.push(`\n### ⌬ DAG 执行步骤与物理产物清单`);
|
|
252
|
+
lines.push("");
|
|
253
|
+
|
|
254
|
+
const dagLines = renderUnicodeDAG(bp.stages, {
|
|
255
|
+
theme: {
|
|
256
|
+
bold: (s: string) => s,
|
|
257
|
+
fg: (_color: string, s: string) => s
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
lines.push("```text");
|
|
261
|
+
lines.push(...dagLines);
|
|
262
|
+
lines.push("```");
|
|
263
|
+
|
|
264
|
+
lines.push("\n### ⌬ 阶段详细规范");
|
|
265
|
+
lines.push("| 阶段 | 负责角色 | 动态装配进阶工具 | 期望交付物 | 允许基础工具 |");
|
|
266
|
+
lines.push("| :--- | :--- | :--- | :--- | :--- |");
|
|
267
|
+
|
|
268
|
+
for (let i = 0; i < bp.stages.length; i++) {
|
|
269
|
+
const stage = bp.stages[i];
|
|
270
|
+
const bound = [
|
|
271
|
+
...(stage.boundCapabilities?.extensions || []),
|
|
272
|
+
...(stage.boundCapabilities?.skills || []),
|
|
273
|
+
...(stage.boundCapabilities?.prompts || [])
|
|
274
|
+
];
|
|
275
|
+
const boundAll = bound.length > 0 ? bound.map((b) => `\`@${b}\``).join(" ") : "`@原生基础工具`";
|
|
276
|
+
|
|
277
|
+
lines.push(
|
|
278
|
+
`| **${i + 1}. ${stage.title}** | \`${stage.roleProfile}\` | ${boundAll} | \`${stage.expectedArtifact}\` | \`${stage.allowedTools.join(",")}\` |`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
lines.push(`\n### ⚡ 架构选型与 Token 节约依据`);
|
|
283
|
+
lines.push(bp.tokenEfficiencySummary);
|
|
284
|
+
|
|
285
|
+
return lines.join("\n");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 沉浸式生态全景与架构决策浮层 ( ctx.ui.custom )
|
|
290
|
+
* 严格复用 @quintinshaw/pi-dynamic-workflows 的 wrapAndBg 算法与边框协议
|
|
291
|
+
*/
|
|
292
|
+
export function openArchitectNavigator(
|
|
293
|
+
ui: any,
|
|
294
|
+
taxonomy: EcosystemTaxonomy,
|
|
295
|
+
initialTask: string = "",
|
|
296
|
+
slots?: DecisionSlot[],
|
|
297
|
+
diagnosis?: TaskDiagnosis,
|
|
298
|
+
ctx?: any
|
|
299
|
+
): Promise<ArchitectNavigatorResult> {
|
|
300
|
+
return ui.custom(
|
|
301
|
+
(tui: any, theme: any, _keybindings: any, done: (result: ArchitectNavigatorResult) => void) => {
|
|
302
|
+
let promptsList: PromptItemInfo[] = PromptsManager.scanAllPrompts();
|
|
303
|
+
let promptTab: "recent" | "user" | "system" = "recent";
|
|
304
|
+
let selectedPromptIdx = 0;
|
|
305
|
+
let newPromptName = "";
|
|
306
|
+
let newPromptDesc = "";
|
|
307
|
+
let newPromptContent = "";
|
|
308
|
+
|
|
309
|
+
let state: "overview" | "input" | "deciding" | "refining" | "custom_option_input" | "outline_confirm" | "add_prompt_name" | "add_prompt_desc" | "add_prompt_content" =
|
|
310
|
+
slots && slots.length > 0 ? "deciding" : initialTask ? "input" : "overview";
|
|
311
|
+
let inputTask = initialTask;
|
|
312
|
+
let customRequirementsText = "";
|
|
313
|
+
let newOptionTitle = "";
|
|
314
|
+
let currentSlotIndex = 0;
|
|
315
|
+
let acceptedSparks = new Set<string>(
|
|
316
|
+
(diagnosis?.architectSparks || []).filter((s: any) => s.isAcceptedByDefault).map((s: any) => s.id)
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const safeSlots = Array.isArray(slots) ? slots : [];
|
|
320
|
+
|
|
321
|
+
function getInitialOptionIndex(slot?: DecisionSlot): number {
|
|
322
|
+
if (!slot || !slot.options) return 0;
|
|
323
|
+
const recIdx = slot.options.findIndex(o => o.isRecommended);
|
|
324
|
+
return recIdx >= 0 ? recIdx : 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
let selectedOptionIndex = getInitialOptionIndex(safeSlots[0]);
|
|
328
|
+
const userDecisions: Record<string, string> = {};
|
|
329
|
+
let selectedPlan: "A" | "B" = "B";
|
|
330
|
+
|
|
331
|
+
const ecoToggles: Record<string, boolean> = {};
|
|
332
|
+
|
|
333
|
+
function getFilteredPrompts(): PromptItemInfo[] {
|
|
334
|
+
try {
|
|
335
|
+
if (promptTab === "recent") {
|
|
336
|
+
if (typeof (PromptsManager as any).getRecentPrompts === "function") {
|
|
337
|
+
return PromptsManager.getRecentPrompts(promptsList, 5);
|
|
338
|
+
}
|
|
339
|
+
return [...promptsList].sort((a, b) => b.updatedAt - a.updatedAt).slice(0, 5);
|
|
340
|
+
} else if (promptTab === "user") {
|
|
341
|
+
return promptsList.filter(p => p.category === "user");
|
|
342
|
+
} else {
|
|
343
|
+
return promptsList.filter(p => p.category === "system");
|
|
344
|
+
}
|
|
345
|
+
} catch (_) {
|
|
346
|
+
return promptsList.slice(0, 5);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function getOptionEcoItems(opt: any): any[] {
|
|
351
|
+
if (!opt?.recommendedEcosystem) return [];
|
|
352
|
+
const rec = opt.recommendedEcosystem;
|
|
353
|
+
const rawList = [
|
|
354
|
+
...(rec.extensions || []),
|
|
355
|
+
...(rec.skills || []),
|
|
356
|
+
...(rec.prompts || [])
|
|
357
|
+
];
|
|
358
|
+
// 过滤掉无意义的默认泛称或空值
|
|
359
|
+
return rawList.filter((item: any) => {
|
|
360
|
+
if (!item) return false;
|
|
361
|
+
const name = typeof item === "string" ? item : (item?.name || item?.id);
|
|
362
|
+
if (!name) return false;
|
|
363
|
+
const trimmed = String(name).trim();
|
|
364
|
+
return trimmed !== "" && trimmed !== "生态插件" && trimmed !== "ecosystem-plugin";
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isEcoItemEnabled(slotId: string, optId: string, itemName: string, defaultVal = true): boolean {
|
|
369
|
+
const k = `${slotId}:${optId}:${itemName}`;
|
|
370
|
+
if (ecoToggles[k] !== undefined) return ecoToggles[k];
|
|
371
|
+
return defaultVal;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function parseKey(data: string): string {
|
|
375
|
+
if (data === "\r" || data === "\n") return "enter";
|
|
376
|
+
if (data === "\x1b") return "escape";
|
|
377
|
+
if (data === "\x1b[A" || data === "\x1bOA") return "up";
|
|
378
|
+
if (data === "\x1b[B" || data === "\x1bOB") return "down";
|
|
379
|
+
if (data === "\x1b[C" || data === "\x1bOC") return "right";
|
|
380
|
+
if (data === "\x1b[D" || data === "\x1bOD") return "left";
|
|
381
|
+
if (data === "\x7f" || data === "\x08") return "backspace";
|
|
382
|
+
if (data === " ") return "space";
|
|
383
|
+
return data;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function rerender() {
|
|
387
|
+
tui.requestRender();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const component = {
|
|
391
|
+
render: (width: number) => {
|
|
392
|
+
const borderColor = (s: string) => theme.fg("borderMuted", s);
|
|
393
|
+
const bgColor = (s: string) => theme.bg("customMessageBg", s);
|
|
394
|
+
const titleColor = (s: string) => theme.fg("accent", theme.bold(s));
|
|
395
|
+
|
|
396
|
+
const innerWidth = Math.max(10, width - BOX_BORDER_OVERHEAD);
|
|
397
|
+
const lines: string[] = [];
|
|
398
|
+
|
|
399
|
+
if (state === "overview") {
|
|
400
|
+
lines.push(titleColor("⌬ ToolFlow 任务编排"));
|
|
401
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
402
|
+
|
|
403
|
+
// 提示词分类与 MRU 切换 Tab
|
|
404
|
+
const currentFiltered = getFilteredPrompts();
|
|
405
|
+
const tabRecent = promptTab === "recent" ? theme.bold(theme.fg("accent", "● 最近使用 (Top 5)")) : theme.fg("dim", "○ 最近使用");
|
|
406
|
+
const tabUser = promptTab === "user" ? theme.bold(theme.fg("accent", "● 用户自定")) : theme.fg("dim", "○ 用户自定");
|
|
407
|
+
const tabSystem = promptTab === "system" ? theme.bold(theme.fg("accent", "● 系统内置")) : theme.fg("dim", "○ 系统内置");
|
|
408
|
+
lines.push(` ${theme.bold("提示词分类:")} [Tab 切换] ${tabRecent} ${tabUser} ${tabSystem}`);
|
|
409
|
+
lines.push("");
|
|
410
|
+
|
|
411
|
+
if (!currentFiltered || currentFiltered.length === 0) {
|
|
412
|
+
const emptyTip = promptTab === "recent" ? " (暂无最近使用记录,按 [Tab] 查看全部或按 [+] 新建)" : " (此分类下暂无提示词模板,按 [+] 可直接新建)";
|
|
413
|
+
lines.push(theme.fg("dim", emptyTip));
|
|
414
|
+
} else {
|
|
415
|
+
// 动态滑动窗口 (Sliding Window),确保光标永远在视口内可见
|
|
416
|
+
const windowSize = 5;
|
|
417
|
+
let startIdx = 0;
|
|
418
|
+
if (currentFiltered.length > windowSize) {
|
|
419
|
+
if (selectedPromptIdx < windowSize) {
|
|
420
|
+
startIdx = 0;
|
|
421
|
+
} else if (selectedPromptIdx >= currentFiltered.length - 1) {
|
|
422
|
+
startIdx = currentFiltered.length - windowSize;
|
|
423
|
+
} else {
|
|
424
|
+
startIdx = selectedPromptIdx - Math.floor(windowSize / 2);
|
|
425
|
+
if (startIdx + windowSize > currentFiltered.length) {
|
|
426
|
+
startIdx = currentFiltered.length - windowSize;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const visiblePrompts = currentFiltered.slice(startIdx, startIdx + windowSize);
|
|
431
|
+
|
|
432
|
+
if (startIdx > 0) {
|
|
433
|
+
lines.push(theme.fg("dim", ` ▲ 上方还有 ${startIdx} 个模板 (按 ↑ 查看)`));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
visiblePrompts.forEach((p, vIdx) => {
|
|
437
|
+
const actualIdx = startIdx + vIdx;
|
|
438
|
+
const isSel = actualIdx === selectedPromptIdx;
|
|
439
|
+
const cursor = isSel ? theme.fg("accent", "▶ ") : " ";
|
|
440
|
+
const cmdPadded = p.command.length < 22 ? p.command + " ".repeat(22 - p.command.length) : p.command;
|
|
441
|
+
const cmdStr = isSel ? theme.bold(theme.fg("accent", cmdPadded)) : theme.fg("dim", cmdPadded);
|
|
442
|
+
const tag = p.category === "user"
|
|
443
|
+
? (p.scope === "project" ? theme.fg("warning", "[项目自定]") : theme.fg("success", "[用户全局]"))
|
|
444
|
+
: theme.fg("dim", "[系统内置]");
|
|
445
|
+
const maxDescLen = Math.max(10, innerWidth - 38);
|
|
446
|
+
const descStr = p.description ? (p.description.length > maxDescLen ? p.description.slice(0, maxDescLen - 3) + "..." : p.description) : "";
|
|
447
|
+
lines.push(cursor + cmdStr + " " + tag + " " + theme.fg("dim", descStr));
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
if (startIdx + windowSize < currentFiltered.length) {
|
|
451
|
+
lines.push(theme.fg("dim", ` ▼ 下方还有 ${currentFiltered.length - (startIdx + windowSize)} 个模板 (按 ↓ 查看)`));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// 🌟 核心优化:实时高亮选中的提示词内容预览 (Preview Pane)
|
|
455
|
+
const selectedItem = currentFiltered[selectedPromptIdx];
|
|
456
|
+
if (selectedItem) {
|
|
457
|
+
lines.push("");
|
|
458
|
+
lines.push(theme.fg("dim", " ┌─ 模板即时预览 (按 [p] 填入 / [d] 删除) " + "─".repeat(Math.max(2, innerWidth - 43))));
|
|
459
|
+
const previewLines = PromptsManager.getPromptPreviewLines(selectedItem, 3);
|
|
460
|
+
if (previewLines.length === 0) {
|
|
461
|
+
lines.push(theme.fg("dim", " │ (空白模板或无正文内容)"));
|
|
462
|
+
} else {
|
|
463
|
+
previewLines.forEach((pl: string) => {
|
|
464
|
+
const cleanPl = pl.length > (innerWidth - 6) ? pl.slice(0, innerWidth - 9) + "..." : pl;
|
|
465
|
+
lines.push(theme.fg("muted", ` │ ${cleanPl}`));
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
lines.push(theme.fg("dim", " └" + "─".repeat(Math.max(2, innerWidth - 5))));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
lines.push("");
|
|
473
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
474
|
+
lines.push(
|
|
475
|
+
" " + theme.fg("accent", "[Enter]/[i]") + " 输入任务开工 " +
|
|
476
|
+
theme.fg("accent", "[p]") + " 填入选中模板 " +
|
|
477
|
+
theme.fg("accent", "[+]") + " 新建模板 " +
|
|
478
|
+
theme.fg("warning", "[d]") + " 删除模板 " +
|
|
479
|
+
theme.fg("dim", "• [Esc] 退出")
|
|
480
|
+
);
|
|
481
|
+
} else if (state === "add_prompt_content") {
|
|
482
|
+
lines.push(titleColor("新建提示词模板 - 粘贴正文"));
|
|
483
|
+
lines.push(theme.fg("dim", "直接输入或鼠标右键粘贴内容,可按 [Ctrl+L] 让 AI 自动生成命令与说明:"));
|
|
484
|
+
lines.push("");
|
|
485
|
+
const displayC = newPromptContent.length > (innerWidth * 2) ? newPromptContent.slice(0, innerWidth * 2) + "..." : newPromptContent;
|
|
486
|
+
lines.push(theme.fg("dim", "> ") + displayC + theme.fg("accent", "█"));
|
|
487
|
+
lines.push("");
|
|
488
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
489
|
+
lines.push(" " + theme.fg("accent", "[Enter]") + " 手动填写 " + theme.fg("accent", "[Ctrl+L]") + " AI自动生成 " + theme.fg("dim", "[Esc] 返回"));
|
|
490
|
+
} else if (state === "add_prompt_name") {
|
|
491
|
+
lines.push(titleColor("新建提示词模板 - 命令名称"));
|
|
492
|
+
lines.push(theme.fg("dim", "设置斜杠命令名(如 wechat-test, code-review 等):"));
|
|
493
|
+
lines.push("");
|
|
494
|
+
lines.push(theme.fg("dim", "命令: /") + newPromptName + theme.fg("accent", "█"));
|
|
495
|
+
lines.push(theme.fg("dim", "说明: ") + newPromptDesc);
|
|
496
|
+
lines.push("");
|
|
497
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
498
|
+
lines.push(" " + theme.fg("accent", "[Enter]") + " 下一步 " + theme.fg("dim", "[Esc] 返回"));
|
|
499
|
+
} else if (state === "add_prompt_desc") {
|
|
500
|
+
lines.push(titleColor("新建提示词模板 - 功能说明"));
|
|
501
|
+
lines.push(theme.fg("dim", "简述它的作用(20字以内):"));
|
|
502
|
+
lines.push("");
|
|
503
|
+
lines.push(theme.fg("dim", "命令: /") + newPromptName);
|
|
504
|
+
lines.push(theme.fg("dim", "说明: ") + newPromptDesc + theme.fg("accent", "█"));
|
|
505
|
+
lines.push("");
|
|
506
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
507
|
+
lines.push(" " + theme.fg("accent", "[Enter]") + " 保存落盘 " + theme.fg("dim", "[Esc] 返回")); } else if (state === "input") {
|
|
508
|
+
lines.push(titleColor("输入任务目标"));
|
|
509
|
+
lines.push(theme.fg("dim", "用一句话描述您想达成的目标(系统将自动规划最优执行路径与推荐选项):"));
|
|
510
|
+
lines.push("");
|
|
511
|
+
|
|
512
|
+
// 使用 CJK 安全滑动视口渲染输入行,彻底防止中文超长溢出与断行
|
|
513
|
+
const inputBox = renderCJKSafeInputBox("> ", inputTask, innerWidth, theme, true);
|
|
514
|
+
lines.push(inputBox);
|
|
515
|
+
lines.push("");
|
|
516
|
+
|
|
517
|
+
const taskRoute = diagnoseTaskExecutionMode(inputTask);
|
|
518
|
+
const isFast = taskRoute.mode === "FAST_TRACK";
|
|
519
|
+
const routeBadge = isFast
|
|
520
|
+
? theme.fg("success", "[⚡ Fast-Track 极速直达]") + theme.fg("dim", " (0 冗余阶段,即刻开工)")
|
|
521
|
+
: theme.fg("accent", "[⌬ Blueprint 完整蓝图]") + theme.fg("dim", " (激活 3 阶段流水线)");
|
|
522
|
+
|
|
523
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
524
|
+
lines.push(`调度研判: ${routeBadge}`);
|
|
525
|
+
lines.push(theme.fg("dim", "[Enter] 确认开工 [Esc] 返回全景 [Backspace] 删除"));
|
|
526
|
+
} else if (state === "deciding") {
|
|
527
|
+
const currentSlot = safeSlots[currentSlotIndex];
|
|
528
|
+
if (!currentSlot) return [];
|
|
529
|
+
|
|
530
|
+
lines.push(
|
|
531
|
+
` ${theme.bold("当前目标:")} ${theme.fg("accent", theme.bold(inputTask || "任务目标"))}`
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
const tabA = selectedPlan === "A"
|
|
535
|
+
? theme.fg("accent", theme.bold("[选项 A: 敏捷直出]"))
|
|
536
|
+
: theme.fg("dim", " 选项 A: 敏捷直出 ");
|
|
537
|
+
const tabB = selectedPlan === "B"
|
|
538
|
+
? theme.fg("accent", theme.bold("[选项 B: 工业工程]"))
|
|
539
|
+
: theme.fg("dim", " 选项 B: 工业工程 ");
|
|
540
|
+
|
|
541
|
+
lines.push(` 架构模式: [1]${tabA} [2]${tabB} ${theme.fg("dim", "• [1/2] 切换")}`);
|
|
542
|
+
|
|
543
|
+
const slotQuestion = currentSlot.question || "请选择配置偏好";
|
|
544
|
+
lines.push(
|
|
545
|
+
` 决策维度 [${currentSlotIndex + 1}/${safeSlots.length}]: ${theme.bold(
|
|
546
|
+
theme.fg("accent", currentSlot.title || "维度")
|
|
547
|
+
)} ${theme.fg("dim", `· ${slotQuestion}`)}`
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// 若当前为 Slot 0 (生态套件一键推荐),给予显著的高亮标识与价值说明
|
|
551
|
+
if (currentSlot.slotId === "slot_ecosystem_expansion") {
|
|
552
|
+
lines.push(
|
|
553
|
+
` ${theme.fg("warning", "🌟 [生态拓展] 发现经过社区验证的现成扩展套件,可大幅缩短编码周期 (按回车选用):")}`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
558
|
+
|
|
559
|
+
const options = Array.isArray(currentSlot.options) ? currentSlot.options : [];
|
|
560
|
+
options.forEach((opt: any, idx: number) => {
|
|
561
|
+
if (!opt) return;
|
|
562
|
+
const isSel = idx === selectedOptionIndex;
|
|
563
|
+
const optId = opt.id || opt.title || `opt_${idx}`;
|
|
564
|
+
const titleText = opt.title || opt.label || "";
|
|
565
|
+
const descText = opt.description || "";
|
|
566
|
+
const isRec = opt.isRecommended;
|
|
567
|
+
|
|
568
|
+
const prefix = isSel ? theme.fg("accent", theme.bold(" > ")) : " ";
|
|
569
|
+
const recPrefix = isRec ? theme.fg("accent", "[推荐] ") : "";
|
|
570
|
+
const optLine = isSel
|
|
571
|
+
? `${prefix}${theme.bold(theme.fg("accent", `${recPrefix}${titleText}`))}`
|
|
572
|
+
: `${prefix}${theme.fg("muted", `${recPrefix}${titleText}`)}`;
|
|
573
|
+
lines.push(optLine);
|
|
574
|
+
|
|
575
|
+
if (descText) {
|
|
576
|
+
lines.push(` ${theme.fg("dim", descText)}`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (isSel) {
|
|
580
|
+
const ecoItems = getOptionEcoItems(opt);
|
|
581
|
+
if (ecoItems.length > 0) {
|
|
582
|
+
const itemsToggles = ecoItems
|
|
583
|
+
.map((item: any) => {
|
|
584
|
+
const enabled = isEcoItemEnabled(currentSlot.slotId, optId, item, true);
|
|
585
|
+
const itemName = typeof item === "string" ? item : (item?.name || item?.id || "");
|
|
586
|
+
if (!itemName || itemName === "生态插件") return null;
|
|
587
|
+
const checkbox = enabled
|
|
588
|
+
? theme.fg("accent", `[@${itemName}]`)
|
|
589
|
+
: theme.fg("dim", `[- 未启用]`);
|
|
590
|
+
return checkbox;
|
|
591
|
+
})
|
|
592
|
+
.filter(Boolean)
|
|
593
|
+
.join(" ");
|
|
594
|
+
|
|
595
|
+
if (itemsToggles.trim()) {
|
|
596
|
+
lines.push(` ${theme.fg("dim", "装配高阶能力:")} ${itemsToggles}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
lines.push("");
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// 渲染 AI 架构师灵感推荐 (Surprises & Sparks)
|
|
604
|
+
const sparks = diagnosis?.architectSparks || [];
|
|
605
|
+
if (sparks.length > 0) {
|
|
606
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
607
|
+
lines.push(` ${theme.bold(theme.fg("accent", "灵感推荐 (按 [s] 采纳/切换):"))}`);
|
|
608
|
+
sparks.forEach((spark: any) => {
|
|
609
|
+
const isAccepted = acceptedSparks.has(spark.id);
|
|
610
|
+
const tag = isAccepted
|
|
611
|
+
? theme.fg("success", "[✓ 已采纳]")
|
|
612
|
+
: theme.fg("dim", "[○ 待采纳]");
|
|
613
|
+
lines.push(` ${tag} ${theme.bold(spark.title)}: ${theme.fg("dim", spark.description)}`);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
lines.push("─".repeat(innerWidth));
|
|
618
|
+
const backPrompt = currentSlotIndex > 0 ? " [Esc] 返回上一步" : " [Esc] 退出";
|
|
619
|
+
lines.push(
|
|
620
|
+
theme.fg(
|
|
621
|
+
"dim",
|
|
622
|
+
` [1-3] 快捷选取 [s] 采纳灵感 [a] 15秒极速开工 [e] 补充要求 [Enter] 确认下一步${backPrompt}`
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
} else if (state === "outline_confirm") {
|
|
626
|
+
lines.push(titleColor("[蓝图大纲确认] 确认后按回车即刻开工"));
|
|
627
|
+
lines.push(theme.fg("dim", "─".repeat(innerWidth)));
|
|
628
|
+
lines.push(` ${theme.bold("任务目标:")} ${theme.fg("accent", inputTask || "任务目标")}`);
|
|
629
|
+
lines.push(` ${theme.bold("执行模式:")} ${selectedPlan === "A" ? "敏捷直出型 (3 阶段)" : "工业工程型 (5 阶段 · 含走查共创)"}`);
|
|
630
|
+
if (customRequirementsText.trim()) {
|
|
631
|
+
lines.push(` ${theme.bold("个性要求:")} ${theme.fg("warning", customRequirementsText.trim())}`);
|
|
632
|
+
}
|
|
633
|
+
lines.push("");
|
|
634
|
+
lines.push(` ${theme.bold("计划执行阶段:")}`);
|
|
635
|
+
if (selectedPlan === "A") {
|
|
636
|
+
lines.push(" 1. 方案设计与契约 (docs/design.md)");
|
|
637
|
+
lines.push(" 2. ⭐ 敏捷编码与实机走查 (核心源码 + 浏览器/终端展示)");
|
|
638
|
+
lines.push(" 3. 运行验证与交付归档 (构建测试与交付结果)");
|
|
639
|
+
} else {
|
|
640
|
+
lines.push(" 1. 方案设计与契约 (docs/design.md)");
|
|
641
|
+
lines.push(" 2. 核心功能编写与模块构建 (核心源码编写)");
|
|
642
|
+
lines.push(" 3. ⭐ 效果展示与实机走查 (效果预览与反馈征询)");
|
|
643
|
+
lines.push(" 4. 自动化测试与质量校验 (单元测试与功能验证)");
|
|
644
|
+
lines.push(" 5. 交付结算与最终归档 (交付结果清单)");
|
|
645
|
+
}
|
|
646
|
+
lines.push("");
|
|
647
|
+
lines.push("─".repeat(innerWidth));
|
|
648
|
+
lines.push(theme.fg("accent", " [Enter] 立即开工") + theme.fg("dim", " [Esc] 返回微调"));
|
|
649
|
+
} else if (state === "refining") {
|
|
650
|
+
lines.push(titleColor("补充个性化需求 (最高优先级)"));
|
|
651
|
+
lines.push(theme.fg("dim", "在此输入您对执行蓝图的特殊指令或约束(如性能指标、特定依赖库):"));
|
|
652
|
+
lines.push("");
|
|
653
|
+
lines.push(`> ${customRequirementsText}${theme.fg("accent", "█")}`);
|
|
654
|
+
lines.push("");
|
|
655
|
+
lines.push("─".repeat(innerWidth));
|
|
656
|
+
lines.push(theme.fg("dim", "[Enter] 确认并继续 [Esc] 取消 [Backspace] 删除"));
|
|
657
|
+
} else if (state === "custom_option_input") {
|
|
658
|
+
lines.push(titleColor("添加自定义决策选项"));
|
|
659
|
+
lines.push(
|
|
660
|
+
theme.fg(
|
|
661
|
+
"dim",
|
|
662
|
+
`为当前槽位【${safeSlots[currentSlotIndex]?.title || ""}】输入您的自定义方案名称:`
|
|
663
|
+
)
|
|
664
|
+
);
|
|
665
|
+
lines.push("");
|
|
666
|
+
lines.push(`> ${newOptionTitle}${theme.fg("accent", "█")}`);
|
|
667
|
+
lines.push("");
|
|
668
|
+
lines.push("─".repeat(innerWidth));
|
|
669
|
+
lines.push(theme.fg("dim", "[Enter] 添加并选中 [Esc] 取消 [Backspace] 删除"));
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const raw = lines;
|
|
673
|
+
const titleText = " ⌬ ToolFlow ";
|
|
674
|
+
const title = titleColor(titleText);
|
|
675
|
+
const titleVisW = visibleWidth(titleText);
|
|
676
|
+
|
|
677
|
+
// 严格按终端实际宽度计算顶边框和底边框,严禁超宽 1 字符导致崩溃
|
|
678
|
+
const topFillLen = Math.max(0, width - 2 - 1 - titleVisW); // "╭─" (2) + "╮" (1) + title
|
|
679
|
+
const topBorder = truncateToWidth(
|
|
680
|
+
borderColor("╭─") + title + borderColor("─".repeat(topFillLen)) + borderColor("╮"),
|
|
681
|
+
width,
|
|
682
|
+
"",
|
|
683
|
+
true
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
const botFillLen = Math.max(0, width - 2); // "╰" (1) + "╯" (1)
|
|
687
|
+
const botBorder = truncateToWidth(
|
|
688
|
+
borderColor(`╰${"─".repeat(botFillLen)}╯`),
|
|
689
|
+
width,
|
|
690
|
+
"",
|
|
691
|
+
true
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
const wrapAndBg = (line: string) => {
|
|
695
|
+
const padded = truncateToWidth(line, innerWidth, "", true);
|
|
696
|
+
const fullLine = truncateToWidth(
|
|
697
|
+
borderColor(BOX_BORDER_LEFT) + padded + borderColor(BOX_BORDER_RIGHT),
|
|
698
|
+
width,
|
|
699
|
+
"",
|
|
700
|
+
true
|
|
701
|
+
);
|
|
702
|
+
const trailingPad = width - visibleWidth(fullLine);
|
|
703
|
+
return bgColor(fullLine + (trailingPad > 0 ? " ".repeat(trailingPad) : ""));
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
return [bgColor(topBorder), ...raw.map(wrapAndBg), bgColor(botBorder)];
|
|
707
|
+
},
|
|
708
|
+
handleInput: (data: string) => {
|
|
709
|
+
const parsed = parseKey(data);
|
|
710
|
+
const isUp = matchesKey(data, "up") || parsed === "up" || data === "\x1b[A" || data === "\x1bOA";
|
|
711
|
+
const isDown = matchesKey(data, "down") || parsed === "down" || data === "\x1b[B" || data === "\x1bOB";
|
|
712
|
+
const isEnter = matchesKey(data, "enter") || parsed === "enter" || data === "\r" || data === "\n";
|
|
713
|
+
const isEscape = matchesKey(data, "escape") || parsed === "escape" || data === "\x1b" || data === "\u001b";
|
|
714
|
+
const isTab = matchesKey(data, "tab") || parsed === "tab" || data === "\t";
|
|
715
|
+
const key = data.toLowerCase();
|
|
716
|
+
|
|
717
|
+
if (state === "overview") {
|
|
718
|
+
const currentFiltered = getFilteredPrompts();
|
|
719
|
+
if (isTab) {
|
|
720
|
+
if (promptTab === "recent") promptTab = "user";
|
|
721
|
+
else if (promptTab === "user") promptTab = "system";
|
|
722
|
+
else promptTab = "recent";
|
|
723
|
+
selectedPromptIdx = 0;
|
|
724
|
+
rerender();
|
|
725
|
+
} else if (isUp) {
|
|
726
|
+
if (selectedPromptIdx > 0) {
|
|
727
|
+
selectedPromptIdx--;
|
|
728
|
+
rerender();
|
|
729
|
+
}
|
|
730
|
+
} else if (isDown) {
|
|
731
|
+
if (selectedPromptIdx < currentFiltered.length - 1) {
|
|
732
|
+
selectedPromptIdx++;
|
|
733
|
+
rerender();
|
|
734
|
+
}
|
|
735
|
+
} else if (key === "d" || key === "delete") {
|
|
736
|
+
const sel = currentFiltered[selectedPromptIdx];
|
|
737
|
+
if (sel && sel.category === "user") {
|
|
738
|
+
const deleted = PromptsManager.deletePrompt(sel);
|
|
739
|
+
if (deleted) {
|
|
740
|
+
promptsList = PromptsManager.scanAllPrompts();
|
|
741
|
+
const updatedFiltered = getFilteredPrompts();
|
|
742
|
+
if (selectedPromptIdx >= updatedFiltered.length) {
|
|
743
|
+
selectedPromptIdx = Math.max(0, updatedFiltered.length - 1);
|
|
744
|
+
}
|
|
745
|
+
rerender();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} else if (key === "p") {
|
|
749
|
+
const sel = currentFiltered[selectedPromptIdx];
|
|
750
|
+
if (sel) {
|
|
751
|
+
if (typeof (PromptsManager as any).recordPromptUsage === "function") {
|
|
752
|
+
PromptsManager.recordPromptUsage(sel.command);
|
|
753
|
+
}
|
|
754
|
+
done({ kind: "prompt_invoke", command: sel.command, filePath: sel.filePath });
|
|
755
|
+
}
|
|
756
|
+
} else if (isEnter || key === "i" || key === " ") {
|
|
757
|
+
state = "input";
|
|
758
|
+
rerender();
|
|
759
|
+
} else if (key === "+" || key === "a") {
|
|
760
|
+
newPromptContent = "";
|
|
761
|
+
newPromptName = "";
|
|
762
|
+
newPromptDesc = "";
|
|
763
|
+
state = "add_prompt_content";
|
|
764
|
+
rerender();
|
|
765
|
+
} else if (isEscape) {
|
|
766
|
+
done(null);
|
|
767
|
+
}
|
|
768
|
+
} else if (state === "add_prompt_content") {
|
|
769
|
+
if (isEscape) {
|
|
770
|
+
state = "overview";
|
|
771
|
+
rerender();
|
|
772
|
+
} else if (data === "" || data === "" || (data.charCodeAt(0) === 12)) {
|
|
773
|
+
// 按 Ctrl+L:调用用户大模型自动总结提取标签与说明
|
|
774
|
+
PromptsManager.autoSummarizeTagWithLLM(newPromptContent, ctx || (ui as any)?.ctx).then(res => {
|
|
775
|
+
newPromptName = res.name;
|
|
776
|
+
newPromptDesc = res.description;
|
|
777
|
+
state = "add_prompt_desc";
|
|
778
|
+
rerender();
|
|
779
|
+
});
|
|
780
|
+
} else if (isEnter) {
|
|
781
|
+
if (newPromptContent.trim()) {
|
|
782
|
+
state = "add_prompt_name";
|
|
783
|
+
rerender();
|
|
784
|
+
}
|
|
785
|
+
} else if (key === "backspace" || data === "" || data === "") {
|
|
786
|
+
newPromptContent = Array.from(newPromptContent).slice(0, -1).join("");
|
|
787
|
+
rerender();
|
|
788
|
+
} else if (data) {
|
|
789
|
+
const cleaned = data.replace(/\[[0-9;]*[a-zA-Z]/g, "");
|
|
790
|
+
|
|
791
|
+
if (cleaned.length > 0) {
|
|
792
|
+
newPromptContent += cleaned;
|
|
793
|
+
rerender();
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
} else if (state === "add_prompt_name") {
|
|
797
|
+
if (isEscape) {
|
|
798
|
+
state = "add_prompt_content";
|
|
799
|
+
rerender();
|
|
800
|
+
} else if (isEnter) {
|
|
801
|
+
if (newPromptName.trim()) {
|
|
802
|
+
state = "add_prompt_desc";
|
|
803
|
+
rerender();
|
|
804
|
+
}
|
|
805
|
+
} else if (key === "backspace" || data === "" || data === "") {
|
|
806
|
+
newPromptName = Array.from(newPromptName).slice(0, -1).join("");
|
|
807
|
+
rerender();
|
|
808
|
+
} else if (data) {
|
|
809
|
+
const cleaned = data.replace(/\[[0-9;]*[a-zA-Z]/g, "");
|
|
810
|
+
|
|
811
|
+
if (cleaned.length > 0) {
|
|
812
|
+
newPromptName += cleaned;
|
|
813
|
+
rerender();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
} else if (state === "add_prompt_desc") {
|
|
817
|
+
if (isEscape) {
|
|
818
|
+
state = "add_prompt_name";
|
|
819
|
+
rerender();
|
|
820
|
+
} else if (isEnter) {
|
|
821
|
+
if (newPromptName.trim() && newPromptContent.trim()) {
|
|
822
|
+
PromptsManager.createPrompt(newPromptName, newPromptDesc || newPromptName, newPromptContent, "global");
|
|
823
|
+
promptsList = PromptsManager.scanAllPrompts();
|
|
824
|
+
selectedPromptIdx = 0;
|
|
825
|
+
state = "overview";
|
|
826
|
+
rerender();
|
|
827
|
+
}
|
|
828
|
+
} else if (key === "backspace" || data === "" || data === "") {
|
|
829
|
+
newPromptDesc = Array.from(newPromptDesc).slice(0, -1).join("");
|
|
830
|
+
rerender();
|
|
831
|
+
} else if (data) {
|
|
832
|
+
const cleaned = data.replace(/\[[0-9;]*[a-zA-Z]/g, "");
|
|
833
|
+
|
|
834
|
+
if (cleaned.length > 0) {
|
|
835
|
+
newPromptDesc += cleaned;
|
|
836
|
+
rerender();
|
|
837
|
+
}
|
|
838
|
+
} } else if (state === "input") {
|
|
839
|
+
if (isEscape) {
|
|
840
|
+
state = "overview";
|
|
841
|
+
rerender();
|
|
842
|
+
} else if (isEnter) {
|
|
843
|
+
if (inputTask.trim()) {
|
|
844
|
+
done({
|
|
845
|
+
kind: "task_input",
|
|
846
|
+
task: inputTask.trim()
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
} else if (key === "backspace" || data === "\x7f" || data === "\x08") {
|
|
850
|
+
inputTask = Array.from(inputTask).slice(0, -1).join("");
|
|
851
|
+
rerender();
|
|
852
|
+
} else if (data) {
|
|
853
|
+
// 过滤掉纯 ANSI 控制字符与换行退格,完整支持鼠标右键粘贴或快捷键粘贴大段文本
|
|
854
|
+
const cleaned = data.replace(/\[[0-9;]*[a-zA-Z]/g, "");
|
|
855
|
+
|
|
856
|
+
if (cleaned.length > 0) {
|
|
857
|
+
inputTask += cleaned;
|
|
858
|
+
rerender();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
} else if (state === "deciding") {
|
|
862
|
+
const currentSlot = safeSlots[currentSlotIndex];
|
|
863
|
+
if (!currentSlot) return;
|
|
864
|
+
|
|
865
|
+
const options = Array.isArray(currentSlot.options) ? currentSlot.options : [];
|
|
866
|
+
|
|
867
|
+
if (isEscape) {
|
|
868
|
+
// 支持按 Esc 回退到上一个决策维度
|
|
869
|
+
if (currentSlotIndex > 0) {
|
|
870
|
+
currentSlotIndex--;
|
|
871
|
+
selectedOptionIndex = getInitialOptionIndex(safeSlots[currentSlotIndex]);
|
|
872
|
+
rerender();
|
|
873
|
+
} else {
|
|
874
|
+
// 第一步按 Esc,退出交互
|
|
875
|
+
done(null);
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
} else if (data === "q" || data === "Q") {
|
|
879
|
+
done(null);
|
|
880
|
+
} else if (key === "tab" || data === "\t" || data === "p" || data === "P") {
|
|
881
|
+
// 自由切换 Plan A (敏捷) / Plan B (工业)
|
|
882
|
+
selectedPlan = selectedPlan === "A" ? "B" : "A";
|
|
883
|
+
userDecisions.__plan = selectedPlan;
|
|
884
|
+
rerender();
|
|
885
|
+
} else if (data === "s" || data === "S") {
|
|
886
|
+
// 切换采纳架构师灵感建议 (支持多灵感循环切换状态)
|
|
887
|
+
const sparks = diagnosis?.architectSparks || [];
|
|
888
|
+
if (sparks.length > 0) {
|
|
889
|
+
// 找到第一个未采纳的灵感进行采纳;如果全部已采纳,则清空重新循环
|
|
890
|
+
const unaccepted = sparks.find((sp: any) => !acceptedSparks.has(sp.id));
|
|
891
|
+
if (unaccepted) {
|
|
892
|
+
acceptedSparks.add(unaccepted.id);
|
|
893
|
+
} else {
|
|
894
|
+
acceptedSparks.clear();
|
|
895
|
+
}
|
|
896
|
+
rerender();
|
|
897
|
+
}
|
|
898
|
+
} else if (data === "e" || data === "E") {
|
|
899
|
+
state = "refining";
|
|
900
|
+
rerender();
|
|
901
|
+
} else if (data === "+" || data === "=") {
|
|
902
|
+
state = "custom_option_input";
|
|
903
|
+
newOptionTitle = "";
|
|
904
|
+
rerender();
|
|
905
|
+
} else if (data === "a" || data === "A") {
|
|
906
|
+
// [a] 一键全选推荐项并跳转至开工大纲确认
|
|
907
|
+
safeSlots.forEach((s) => {
|
|
908
|
+
const recOpt = s.options.find((o: any) => o.isRecommended) || s.options[0];
|
|
909
|
+
if (recOpt) {
|
|
910
|
+
userDecisions[s.slotId] = recOpt.id || recOpt.label || "";
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
userDecisions.__plan = "A";
|
|
914
|
+
selectedPlan = "A";
|
|
915
|
+
state = "outline_confirm";
|
|
916
|
+
rerender();
|
|
917
|
+
} else if (key === "1" || data === "1") {
|
|
918
|
+
if (options.length >= 1) {
|
|
919
|
+
selectedOptionIndex = 0;
|
|
920
|
+
}
|
|
921
|
+
rerender();
|
|
922
|
+
} else if (key === "2" || data === "2") {
|
|
923
|
+
if (options.length >= 2) {
|
|
924
|
+
selectedOptionIndex = 1;
|
|
925
|
+
}
|
|
926
|
+
rerender();
|
|
927
|
+
} else if (key === "3" || data === "3") {
|
|
928
|
+
if (options.length >= 3) {
|
|
929
|
+
selectedOptionIndex = 2;
|
|
930
|
+
}
|
|
931
|
+
rerender();
|
|
932
|
+
} else if (isUp) {
|
|
933
|
+
if (selectedOptionIndex > 0) {
|
|
934
|
+
selectedOptionIndex--;
|
|
935
|
+
rerender();
|
|
936
|
+
}
|
|
937
|
+
} else if (isDown) {
|
|
938
|
+
if (selectedOptionIndex < options.length - 1) {
|
|
939
|
+
selectedOptionIndex++;
|
|
940
|
+
rerender();
|
|
941
|
+
}
|
|
942
|
+
} else if (isEnter) {
|
|
943
|
+
const chosen = options[selectedOptionIndex];
|
|
944
|
+
if (chosen) {
|
|
945
|
+
userDecisions[currentSlot.slotId] = chosen.id || chosen.label || "";
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (currentSlotIndex < safeSlots.length - 1) {
|
|
949
|
+
currentSlotIndex++;
|
|
950
|
+
selectedOptionIndex = getInitialOptionIndex(safeSlots[currentSlotIndex]);
|
|
951
|
+
rerender();
|
|
952
|
+
} else {
|
|
953
|
+
userDecisions.__plan = selectedPlan;
|
|
954
|
+
state = "outline_confirm";
|
|
955
|
+
rerender();
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
} else if (state === "outline_confirm") {
|
|
959
|
+
if (isEscape) {
|
|
960
|
+
state = "deciding";
|
|
961
|
+
rerender();
|
|
962
|
+
} else if (isEnter) {
|
|
963
|
+
const allCustomReqs: string[] = [];
|
|
964
|
+
if (customRequirementsText.trim()) {
|
|
965
|
+
allCustomReqs.push(customRequirementsText.trim());
|
|
966
|
+
}
|
|
967
|
+
const sparks = diagnosis?.architectSparks || [];
|
|
968
|
+
sparks.forEach((sp: any) => {
|
|
969
|
+
if (acceptedSparks.has(sp.id)) {
|
|
970
|
+
allCustomReqs.push(`[架构师灵感采纳] ${sp.title}: ${sp.impact}`);
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
done({
|
|
975
|
+
kind: "decisions",
|
|
976
|
+
decisions: userDecisions,
|
|
977
|
+
task: inputTask,
|
|
978
|
+
selectedPlan,
|
|
979
|
+
customRequirements: allCustomReqs.length > 0 ? allCustomReqs : undefined,
|
|
980
|
+
customEcosystem: {
|
|
981
|
+
enabledExtensions: [],
|
|
982
|
+
enabledSkills: [],
|
|
983
|
+
enabledPrompts: []
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
} else if (state === "refining") {
|
|
988
|
+
if (isEscape || isEnter) {
|
|
989
|
+
state = "deciding";
|
|
990
|
+
rerender();
|
|
991
|
+
} else if (key === "backspace" || data === "\x7f" || data === "\x08") {
|
|
992
|
+
customRequirementsText = Array.from(customRequirementsText).slice(0, -1).join("");
|
|
993
|
+
rerender();
|
|
994
|
+
} else if (!data.startsWith("\x1b") && !/[\r\n\x7f\x08]/.test(data)) {
|
|
995
|
+
customRequirementsText += data;
|
|
996
|
+
rerender();
|
|
997
|
+
}
|
|
998
|
+
} else if (state === "custom_option_input") {
|
|
999
|
+
if (isEscape) {
|
|
1000
|
+
state = "deciding";
|
|
1001
|
+
rerender();
|
|
1002
|
+
} else if (isEnter) {
|
|
1003
|
+
if (newOptionTitle.trim()) {
|
|
1004
|
+
const currentSlot = safeSlots[currentSlotIndex];
|
|
1005
|
+
if (currentSlot) {
|
|
1006
|
+
const newOpt: TaskRequirementChoice = {
|
|
1007
|
+
id: `custom_${Date.now()}`,
|
|
1008
|
+
label: `[自定义] ${newOptionTitle.trim()}`,
|
|
1009
|
+
description: "用户手动补充的个性化要求 (最高优先级执行)",
|
|
1010
|
+
isRecommended: false,
|
|
1011
|
+
recommendedEcosystem: {
|
|
1012
|
+
extensions: [],
|
|
1013
|
+
reason: "用户自定义个性化方案"
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
currentSlot.options.push(newOpt);
|
|
1017
|
+
selectedOptionIndex = currentSlot.options.length - 1;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
state = "deciding";
|
|
1021
|
+
rerender();
|
|
1022
|
+
} else if (key === "backspace" || data === "\x7f" || data === "\x08") {
|
|
1023
|
+
newOptionTitle = Array.from(newOptionTitle).slice(0, -1).join("");
|
|
1024
|
+
rerender();
|
|
1025
|
+
} else if (!data.startsWith("\x1b") && !/[\r\n\x7f\x08]/.test(data)) {
|
|
1026
|
+
newOptionTitle += data;
|
|
1027
|
+
rerender();
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
return component;
|
|
1034
|
+
}
|
|
1035
|
+
);
|
|
1036
|
+
}
|