ethan-skill 1.1.0 → 1.2.0
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/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +254 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +152 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/dashboard.d.ts +1 -1
- package/dist/server/dashboard.d.ts.map +1 -1
- package/dist/server/dashboard.js +317 -16
- package/dist/server/dashboard.js.map +1 -1
- package/dist/workflow/state.d.ts +48 -0
- package/dist/workflow/state.d.ts.map +1 -0
- package/dist/workflow/state.js +162 -0
- package/dist/workflow/state.js.map +1 -0
- package/package.json +1 -1
- package/rules/claude-code/CLAUDE.md +2 -2
- package/rules/cline/.clinerules +2 -2
- package/rules/codebuddy/CODEBUDDY.md +2 -2
- package/rules/continue/.continuerules +2 -2
- package/rules/copilot/copilot-instructions.md +2 -2
- package/rules/cursor/.cursorrules +2 -2
- package/rules/cursor/smart-flow.mdc +2 -2
- package/rules/jetbrains/smart-flow.md +2 -2
- package/rules/lingma/smart-flow.md +2 -2
- package/rules/windsurf/.windsurf/rules/smart-flow.md +2 -2
- package/rules/zed/smart-flow.rules +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -44,6 +44,7 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
44
44
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
45
45
|
const index_1 = require("../skills/index");
|
|
46
46
|
const pipeline_1 = require("../skills/pipeline");
|
|
47
|
+
const state_1 = require("../workflow/state");
|
|
47
48
|
const fs = __importStar(require("fs"));
|
|
48
49
|
const path = __importStar(require("path"));
|
|
49
50
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
|
|
@@ -121,15 +122,164 @@ async function startMcpServer() {
|
|
|
121
122
|
required: ['pipeline', 'context'],
|
|
122
123
|
},
|
|
123
124
|
};
|
|
125
|
+
const workflowNextTool = {
|
|
126
|
+
name: 'ethan_workflow_next',
|
|
127
|
+
description: [
|
|
128
|
+
'完成工作流当前步骤并推进到下一步。',
|
|
129
|
+
'当你(AI 编辑器)已完成某步骤的任务时,调用此工具传入本步摘要,',
|
|
130
|
+
'工具会返回下一步的完整执行提示词(包含前序步骤上下文链),',
|
|
131
|
+
'你可以直接将返回的提示词作为下一步的指令执行。',
|
|
132
|
+
'若工作流已全部完成,返回完成状态。',
|
|
133
|
+
].join(' '),
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
summary: {
|
|
138
|
+
type: 'string',
|
|
139
|
+
description: '本步骤的执行摘要和关键产出,将作为下一步的上下文输入',
|
|
140
|
+
},
|
|
141
|
+
cwd: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: '项目目录路径(默认使用 MCP server 启动时的工作目录)',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
required: ['summary'],
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const workflowStatusTool = {
|
|
150
|
+
name: 'ethan_workflow_status',
|
|
151
|
+
description: '查看当前工作流的进度状态,返回各步骤完成情况和当前步骤的执行提示词。',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
cwd: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: '项目目录路径(默认使用 MCP server 启动时的工作目录)',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
required: [],
|
|
161
|
+
},
|
|
162
|
+
};
|
|
124
163
|
// 注册工具列表处理器
|
|
125
164
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
126
165
|
return {
|
|
127
|
-
tools: [...index_1.ALL_SKILLS.map(skillToMcpTool), pipelineTool],
|
|
166
|
+
tools: [...index_1.ALL_SKILLS.map(skillToMcpTool), pipelineTool, workflowNextTool, workflowStatusTool],
|
|
128
167
|
};
|
|
129
168
|
});
|
|
130
169
|
// 注册工具调用处理器
|
|
131
170
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
132
171
|
const { name, arguments: args } = request.params;
|
|
172
|
+
// ── ethan_workflow_status ──────────────────────────────────────────────
|
|
173
|
+
if (name === 'ethan_workflow_status') {
|
|
174
|
+
const cwd = args?.cwd || process.cwd();
|
|
175
|
+
const session = (0, state_1.loadSession)(cwd);
|
|
176
|
+
if (!session) {
|
|
177
|
+
return {
|
|
178
|
+
content: [{
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: '当前目录暂无工作流会话。\n\n运行 `ethan workflow start <pipeline-id> -c "任务描述"` 启动工作流。',
|
|
181
|
+
}],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const progress = (0, state_1.calcProgress)(session);
|
|
185
|
+
const currentIdx = (0, state_1.getCurrentStepIndex)(session);
|
|
186
|
+
const statusIcon = {
|
|
187
|
+
'done': '✅', 'in-progress': '▶️', 'pending': '⬜', 'skipped': '⏭️',
|
|
188
|
+
};
|
|
189
|
+
let text = `# 工作流进度\n\n`;
|
|
190
|
+
text += `- **Pipeline**: ${session.pipelineName}\n`;
|
|
191
|
+
text += `- **Session**: ${session.id}\n`;
|
|
192
|
+
text += `- **进度**: ${progress}% (${session.steps.filter((s) => s.status === 'done').length}/${session.steps.length})\n`;
|
|
193
|
+
text += `- **状态**: ${session.completed ? '🎉 已全部完成' : `第 ${currentIdx + 1}/${session.steps.length} 步进行中`}\n`;
|
|
194
|
+
text += `- **任务背景**: ${session.initialContext}\n\n`;
|
|
195
|
+
text += `## 步骤详情\n\n`;
|
|
196
|
+
for (let i = 0; i < session.steps.length; i++) {
|
|
197
|
+
const step = session.steps[i];
|
|
198
|
+
const icon = statusIcon[step.status] ?? '⬜';
|
|
199
|
+
const marker = i === currentIdx ? ' ← **当前**' : '';
|
|
200
|
+
text += `${icon} **${i + 1}. ${step.skillId}**${marker}\n`;
|
|
201
|
+
if (step.summary) {
|
|
202
|
+
text += ` > 摘要:${step.summary}\n`;
|
|
203
|
+
}
|
|
204
|
+
text += '\n';
|
|
205
|
+
}
|
|
206
|
+
if (!session.completed) {
|
|
207
|
+
const currentStep = session.steps[currentIdx];
|
|
208
|
+
const skill = index_1.ALL_SKILLS.find((s) => s.id === currentStep.skillId);
|
|
209
|
+
if (skill) {
|
|
210
|
+
text += `---\n\n## 当前步骤提示词\n\n`;
|
|
211
|
+
text += (0, state_1.buildStepPrompt)(session, currentStep, skill);
|
|
212
|
+
text += `\n\n---\n\n`;
|
|
213
|
+
text += `**完成本步后,调用 \`ethan_workflow_next\` 工具并传入本步摘要即可推进到下一步。**`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { content: [{ type: 'text', text }] };
|
|
217
|
+
}
|
|
218
|
+
// ── ethan_workflow_next ────────────────────────────────────────────────
|
|
219
|
+
if (name === 'ethan_workflow_next') {
|
|
220
|
+
const summary = (args?.summary || '').trim();
|
|
221
|
+
const cwd = args?.cwd || process.cwd();
|
|
222
|
+
if (!summary) {
|
|
223
|
+
return {
|
|
224
|
+
content: [{ type: 'text', text: '❌ 请提供本步骤的执行摘要(summary 参数不能为空)' }],
|
|
225
|
+
isError: true,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const session = (0, state_1.loadSession)(cwd);
|
|
229
|
+
if (!session) {
|
|
230
|
+
return {
|
|
231
|
+
content: [{ type: 'text', text: '❌ 未找到工作流会话。请先运行 `ethan workflow start` 启动工作流。' }],
|
|
232
|
+
isError: true,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (session.completed) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: 'text', text: `🎉 工作流"${session.pipelineName}"已全部完成!\n\n运行 \`ethan workflow reset\` 开始新工作流。` }],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const prevStep = (0, state_1.getCurrentStep)(session);
|
|
241
|
+
const prevIdx = (0, state_1.getCurrentStepIndex)(session);
|
|
242
|
+
const nextStep = (0, state_1.markStepDone)(session, summary, cwd);
|
|
243
|
+
const progress = (0, state_1.calcProgress)(session);
|
|
244
|
+
if (!nextStep) {
|
|
245
|
+
return {
|
|
246
|
+
content: [{
|
|
247
|
+
type: 'text',
|
|
248
|
+
text: [
|
|
249
|
+
`🎉 **工作流全部完成!**`,
|
|
250
|
+
``,
|
|
251
|
+
`- Pipeline: ${session.pipelineName}`,
|
|
252
|
+
`- 共完成 ${session.steps.length} 个步骤`,
|
|
253
|
+
`- 进度: 100%`,
|
|
254
|
+
``,
|
|
255
|
+
`运行 \`ethan workflow status\` 查看完整报告,或 \`ethan workflow reset\` 开始新工作流。`,
|
|
256
|
+
].join('\n'),
|
|
257
|
+
}],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const nextSkill = index_1.ALL_SKILLS.find((s) => s.id === nextStep.skillId);
|
|
261
|
+
if (!nextSkill) {
|
|
262
|
+
return {
|
|
263
|
+
content: [{ type: 'text', text: `❌ 未找到下一步的 Skill:${nextStep.skillId}` }],
|
|
264
|
+
isError: true,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
// 重新加载保存后的 session 以确保包含最新摘要
|
|
268
|
+
const updatedSession = (0, state_1.loadSession)(cwd);
|
|
269
|
+
const prompt = (0, state_1.buildStepPrompt)(updatedSession, nextStep, nextSkill);
|
|
270
|
+
const text = [
|
|
271
|
+
`✅ **步骤 ${prevIdx + 1}(${prevStep?.skillId})已完成** → 进度 ${progress}%`,
|
|
272
|
+
``,
|
|
273
|
+
`---`,
|
|
274
|
+
``,
|
|
275
|
+
prompt,
|
|
276
|
+
``,
|
|
277
|
+
`---`,
|
|
278
|
+
``,
|
|
279
|
+
`**完成本步后,再次调用 \`ethan_workflow_next\` 并传入摘要即可继续推进。**`,
|
|
280
|
+
].join('\n');
|
|
281
|
+
return { content: [{ type: 'text', text }] };
|
|
282
|
+
}
|
|
133
283
|
// Handle pipeline tool
|
|
134
284
|
if (name === 'ethan_pipeline') {
|
|
135
285
|
const pipelineId = args?.pipeline || '';
|
|
@@ -187,7 +337,7 @@ async function startMcpServer() {
|
|
|
187
337
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
188
338
|
await server.connect(transport);
|
|
189
339
|
// MCP server 运行时不输出到 stdout(会破坏协议)
|
|
190
|
-
process.stderr.write(`Ethan MCP Server v${pkg.version} running (${index_1.ALL_SKILLS.length} skill tools +
|
|
340
|
+
process.stderr.write(`Ethan MCP Server v${pkg.version} running (${index_1.ALL_SKILLS.length} skill tools + ethan_pipeline + ethan_workflow_next + ethan_workflow_status, ${pipeline_1.PIPELINES.length} pipelines)\n`);
|
|
191
341
|
}
|
|
192
342
|
// 直接运行时启动
|
|
193
343
|
if (require.main === module) {
|
package/dist/mcp/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EH,wCAyRC;AAtWD,wEAAmE;AACnE,wEAAiF;AACjF,iEAG4C;AAE5C,2CAA6C;AAE7C,iDAAgE;AAChE,6CAO2B;AAC3B,uCAAyB;AACzB,2CAA6B;AAE7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,OAAO,CAAC,CACrE,CAAC;AAEF;;GAEG;AACH,SAAS,cAAc,CAAC,KAAsB;IAC5C,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,MAAM;QAClB,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,WAAW,EAAE;QACnD,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,qBAAqB,KAAK,CAAC,IAAI,aAAa;iBAC1D;gBACD,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;oBACvB,WAAW,EAAE,iCAAiC;oBAC9C,OAAO,EAAE,MAAM;iBAChB;aACF;YACD,QAAQ,EAAE,CAAC,SAAS,CAAC;SACtB;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,KAAsB,EAAE,OAAe,EAAE,MAAwB;IACrF,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,MAAM,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,WAAW,cAAc,OAAO,qBAAqB,KAAK,CAAC,YAAY,GAAG,CAAC;IACjH,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK;SAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;SACrD,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhB,IAAI,MAAM,GAAG,cAAc,KAAK,CAAC,IAAI,MAAM,CAAC;IAC5C,MAAM,IAAI,aAAa,OAAO,MAAM,CAAC;IACrC,MAAM,IAAI,SAAS,CAAC;IACpB,MAAM,IAAI,cAAc,YAAY,MAAM,CAAC;IAC3C,MAAM,IAAI,SAAS,CAAC;IACpB,MAAM,IAAI,cAAc,KAAK,CAAC,YAAY,MAAM,CAAC;IAEjD,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,cAAc,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAC5E,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAEM,KAAK,UAAU,cAAc;IAClC,MAAM,MAAM,GAAG,IAAI,iBAAM,CACvB;QACE,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,GAAG,CAAC,OAAO;KACrB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;SACV;KACF,CACF,CAAC;IAEF,MAAM,YAAY,GAAS;QACzB,IAAI,EAAE,gBAAgB;QACtB,WAAW,EAAE,sCAAsC;QACnD,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,oBAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAChC,WAAW,EAAE,wBAAwB,oBAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;iBAC5F;gBACD,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,yCAAyC;iBACvD;aACF;YACD,QAAQ,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;SAClC;KACF,CAAC;IAEF,MAAM,gBAAgB,GAAS;QAC7B,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EAAE;YACX,mBAAmB;YACnB,mCAAmC;YACnC,+BAA+B;YAC/B,yBAAyB;YACzB,mBAAmB;SACpB,CAAC,IAAI,CAAC,GAAG,CAAC;QACX,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,4BAA4B;iBAC1C;gBACD,GAAG,EAAE;oBACH,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,kCAAkC;iBAChD;aACF;YACD,QAAQ,EAAE,CAAC,SAAS,CAAC;SACtB;KACF,CAAC;IAEF,MAAM,kBAAkB,GAAS;QAC/B,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EAAE,oCAAoC;QACjD,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,GAAG,EAAE;oBACH,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,kCAAkC;iBAChD;aACF;YACD,QAAQ,EAAE,EAAE;SACb;KACF,CAAC;IAEF,YAAY;IACZ,MAAM,CAAC,iBAAiB,CAAC,iCAAsB,EAAE,KAAK,IAAI,EAAE;QAC1D,OAAO;YACL,KAAK,EAAE,CAAC,GAAG,kBAAU,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,CAAC;SAC/F,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,YAAY;IACZ,MAAM,CAAC,iBAAiB,CAAC,gCAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QAEjD,0EAA0E;QAC1E,IAAI,IAAI,KAAK,uBAAuB,EAAE,CAAC;YACrC,MAAM,GAAG,GAAI,IAAI,EAAE,GAAc,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YACnD,MAAM,OAAO,GAAG,IAAA,mBAAW,EAAC,GAAG,CAAC,CAAC;YAEjC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,0EAA0E;yBACjF,CAAC;iBACH,CAAC;YACJ,CAAC;YAED,MAAM,QAAQ,GAAG,IAAA,oBAAY,EAAC,OAAO,CAAC,CAAC;YACvC,MAAM,UAAU,GAAG,IAAA,2BAAmB,EAAC,OAAO,CAAC,CAAC;YAChD,MAAM,UAAU,GAA2B;gBACzC,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI;aAClE,CAAC;YAEF,IAAI,IAAI,GAAG,aAAa,CAAC;YACzB,IAAI,IAAI,mBAAmB,OAAO,CAAC,YAAY,IAAI,CAAC;YACpD,IAAI,IAAI,kBAAkB,OAAO,CAAC,EAAE,IAAI,CAAC;YACzC,IAAI,IAAI,aAAa,QAAQ,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YACxH,IAAI,IAAI,aAAa,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,OAAO,IAAI,CAAC;YAC7G,IAAI,IAAI,eAAe,OAAO,CAAC,cAAc,MAAM,CAAC;YAEpD,IAAI,IAAI,aAAa,CAAC;YACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC9B,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;gBAC5C,MAAM,MAAM,GAAG,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnD,IAAI,IAAI,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,OAAO,KAAK,MAAM,IAAI,CAAC;gBAC3D,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACjB,IAAI,IAAI,WAAW,IAAI,CAAC,OAAO,IAAI,CAAC;gBACtC,CAAC;gBACD,IAAI,IAAI,IAAI,CAAC;YACf,CAAC;YAED,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;gBACvB,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC9C,MAAM,KAAK,GAAG,kBAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,WAAW,CAAC,OAAO,CAAC,CAAC;gBACnE,IAAI,KAAK,EAAE,CAAC;oBACV,IAAI,IAAI,uBAAuB,CAAC;oBAChC,IAAI,IAAI,IAAA,uBAAe,EAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;oBACrD,IAAI,IAAI,aAAa,CAAC;oBACtB,IAAI,IAAI,yDAAyD,CAAC;gBACpE,CAAC;YACH,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC/C,CAAC;QAED,0EAA0E;QAC1E,IAAI,IAAI,KAAK,qBAAqB,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,CAAE,IAAI,EAAE,OAAkB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACzD,MAAM,GAAG,GAAI,IAAI,EAAE,GAAc,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAEnD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,+BAA+B,EAAE,CAAC;oBAClE,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAG,IAAA,mBAAW,EAAC,GAAG,CAAC,CAAC;YACjC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,+CAA+C,EAAE,CAAC;oBAClF,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YACD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,OAAO,CAAC,YAAY,gDAAgD,EAAE,CAAC;iBAClH,CAAC;YACJ,CAAC;YAED,MAAM,QAAQ,GAAG,IAAA,sBAAc,EAAC,OAAO,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,IAAA,2BAAmB,EAAC,OAAO,CAAC,CAAC;YAC7C,MAAM,QAAQ,GAAG,IAAA,oBAAY,EAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;YACrD,MAAM,QAAQ,GAAG,IAAA,oBAAY,EAAC,OAAO,CAAC,CAAC;YAEvC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE;gCACJ,iBAAiB;gCACjB,EAAE;gCACF,eAAe,OAAO,CAAC,YAAY,EAAE;gCACrC,SAAS,OAAO,CAAC,KAAK,CAAC,MAAM,MAAM;gCACnC,YAAY;gCACZ,EAAE;gCACF,wEAAwE;6BACzE,CAAC,IAAI,CAAC,IAAI,CAAC;yBACb,CAAC;iBACH,CAAC;YACJ,CAAC;YAED,MAAM,SAAS,GAAG,kBAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC;YACpE,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;oBACxE,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,6BAA6B;YAC7B,MAAM,cAAc,GAAG,IAAA,mBAAW,EAAC,GAAG,CAAE,CAAC;YACzC,MAAM,MAAM,GAAG,IAAA,uBAAe,EAAC,cAAc,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;YAEpE,MAAM,IAAI,GAAG;gBACX,UAAU,OAAO,GAAG,CAAC,IAAI,QAAQ,EAAE,OAAO,eAAe,QAAQ,GAAG;gBACpE,EAAE;gBACF,KAAK;gBACL,EAAE;gBACF,MAAM;gBACN,EAAE;gBACF,KAAK;gBACL,EAAE;gBACF,qDAAqD;aACtD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC/C,CAAC;QAED,uBAAuB;QACvB,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAI,IAAI,EAAE,QAAmB,IAAI,EAAE,CAAC;YACpD,MAAM,OAAO,GAAI,IAAI,EAAE,OAAkB,IAAI,EAAE,CAAC;YAChD,MAAM,QAAQ,GAAG,IAAA,0BAAe,EAAC,UAAU,CAAC,CAAC;YAE7C,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,qBAAqB,UAAU,gBAAgB,oBAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;yBAC7F;qBACF;oBACD,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;YACtC,IAAI,MAAM,GAAG,iBAAiB,QAAQ,CAAC,IAAI,MAAM,CAAC;YAClD,MAAM,IAAI,UAAU,QAAQ,CAAC,WAAW,MAAM,CAAC;YAC/C,MAAM,IAAI,gBAAgB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YACtE,MAAM,IAAI,aAAa,OAAO,MAAM,CAAC;YACrC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC;YAElC,MAAM,IAAI,MAAM;iBACb,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;iBACpD,IAAI,CAAC,aAAa,CAAC,CAAC;YAEvB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;aAC1C,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,kBAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,iBAAiB,IAAI,sBAAsB,kBAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB;qBAC9G;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAI,IAAI,EAAE,OAAkB,IAAI,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,CAAE,IAAI,EAAE,MAAiB,IAAI,MAAM,CAAqB,CAAC;QAExE,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAEpD,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,MAAM;iBACb;aACF;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,qBAAqB;IACrB,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,mCAAmC;IACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qBAAqB,GAAG,CAAC,OAAO,aAAa,kBAAU,CAAC,MAAM,gFAAgF,oBAAS,CAAC,MAAM,eAAe,CAC9K,CAAC;AACJ,CAAC;AAED,UAAU;AACV,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function startDashboardServer(port: number): void;
|
|
1
|
+
export declare function startDashboardServer(port: number, cwd?: string): void;
|
|
2
2
|
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/server/dashboard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/server/dashboard.ts"],"names":[],"mappings":"AAsaA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,IAAI,CA+HpF"}
|
package/dist/server/dashboard.js
CHANGED
|
@@ -40,6 +40,7 @@ const path = __importStar(require("path"));
|
|
|
40
40
|
const os = __importStar(require("os"));
|
|
41
41
|
const index_1 = require("../skills/index");
|
|
42
42
|
const pipeline_1 = require("../skills/pipeline");
|
|
43
|
+
const state_1 = require("../workflow/state");
|
|
43
44
|
const HTML = `<!DOCTYPE html>
|
|
44
45
|
<html lang="zh">
|
|
45
46
|
<head>
|
|
@@ -65,6 +66,7 @@ const HTML = `<!DOCTYPE html>
|
|
|
65
66
|
.stat-card.blue .value { color: var(--blue); }
|
|
66
67
|
.stat-card.green .value { color: var(--green); }
|
|
67
68
|
.stat-card.purple .value { color: var(--purple); }
|
|
69
|
+
.stat-card.yellow .value { color: var(--yellow); }
|
|
68
70
|
section { margin-bottom: 40px; }
|
|
69
71
|
section h2 { font-size: 15px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
|
|
70
72
|
.skills-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
|
|
@@ -100,6 +102,44 @@ const HTML = `<!DOCTYPE html>
|
|
|
100
102
|
.flow-arrow { color: var(--muted); font-size: 14px; }
|
|
101
103
|
.empty { color: var(--muted); font-size: 13px; font-style: italic; }
|
|
102
104
|
#status { position: fixed; bottom: 16px; right: 16px; background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 14px; font-size: 12px; color: var(--muted); }
|
|
105
|
+
|
|
106
|
+
/* ── Workflow Panel ── */
|
|
107
|
+
.wf-card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 20px; }
|
|
108
|
+
.wf-empty { color: var(--muted); font-size: 13px; }
|
|
109
|
+
.wf-empty code { font-family: monospace; background: #1a1a1a; border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; font-size: 12px; }
|
|
110
|
+
.wf-meta { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 16px; }
|
|
111
|
+
.wf-meta-item { font-size: 12px; color: var(--muted); }
|
|
112
|
+
.wf-meta-item strong { color: var(--text); }
|
|
113
|
+
.wf-progress-bar-wrap { background: #1a1a1a; border-radius: 6px; height: 10px; overflow: hidden; margin-bottom: 16px; }
|
|
114
|
+
.wf-progress-bar { height: 100%; border-radius: 6px; background: var(--blue); transition: width 0.5s ease; }
|
|
115
|
+
.wf-steps { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
|
|
116
|
+
.wf-step { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: 8px; background: #0d0d0d; border: 1px solid var(--border); }
|
|
117
|
+
.wf-step.current { border-color: var(--blue); background: rgba(59,130,246,0.06); }
|
|
118
|
+
.wf-step.done { opacity: 0.6; }
|
|
119
|
+
.wf-step-icon { font-size: 16px; flex-shrink: 0; line-height: 1.4; }
|
|
120
|
+
.wf-step-body { flex: 1; min-width: 0; }
|
|
121
|
+
.wf-step-id { font-size: 12px; font-family: monospace; color: #aaa; }
|
|
122
|
+
.wf-step-summary { font-size: 11px; color: var(--muted); margin-top: 4px; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
123
|
+
.wf-step-current-label { font-size: 11px; color: var(--blue); font-weight: 600; margin-top: 2px; }
|
|
124
|
+
.wf-actions { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
125
|
+
.btn { border: none; border-radius: 7px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }
|
|
126
|
+
.btn:hover { opacity: 0.85; }
|
|
127
|
+
.btn-primary { background: var(--blue); color: #fff; }
|
|
128
|
+
.btn-secondary { background: #222; color: var(--text); border: 1px solid var(--border); }
|
|
129
|
+
.btn-success { background: var(--green); color: #fff; }
|
|
130
|
+
|
|
131
|
+
/* ── Modal ── */
|
|
132
|
+
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.75); z-index: 100; align-items: center; justify-content: center; }
|
|
133
|
+
.modal-overlay.open { display: flex; }
|
|
134
|
+
.modal { background: #161616; border: 1px solid var(--border); border-radius: 12px; padding: 24px; width: 90%; max-width: 680px; max-height: 90vh; overflow-y: auto; position: relative; }
|
|
135
|
+
.modal h3 { font-size: 15px; font-weight: 700; margin-bottom: 16px; }
|
|
136
|
+
.modal pre { font-family: 'SFMono-Regular', Consolas, monospace; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; color: #ccc; background: #0d0d0d; border: 1px solid var(--border); border-radius: 8px; padding: 16px; max-height: 55vh; overflow-y: auto; margin-bottom: 16px; }
|
|
137
|
+
.modal textarea { width: 100%; background: #0d0d0d; border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 13px; padding: 12px; resize: vertical; min-height: 100px; outline: none; font-family: inherit; }
|
|
138
|
+
.modal textarea:focus { border-color: var(--blue); }
|
|
139
|
+
.modal-footer { display: flex; gap: 10px; justify-content: flex-end; margin-top: 16px; }
|
|
140
|
+
.modal-close { position: absolute; top: 16px; right: 16px; background: none; border: none; color: var(--muted); font-size: 20px; cursor: pointer; line-height: 1; }
|
|
141
|
+
.modal-close:hover { color: var(--text); }
|
|
142
|
+
.wf-completed-banner { background: rgba(34,197,94,0.1); border: 1px solid rgba(34,197,94,0.3); border-radius: 8px; padding: 12px 16px; font-size: 13px; color: var(--green); margin-bottom: 16px; }
|
|
103
143
|
</style>
|
|
104
144
|
</head>
|
|
105
145
|
<body>
|
|
@@ -113,8 +153,14 @@ const HTML = `<!DOCTYPE html>
|
|
|
113
153
|
<div class="stat-card blue"><div class="value" id="stat-skills">-</div><div class="label">Skills</div></div>
|
|
114
154
|
<div class="stat-card green"><div class="value" id="stat-pipelines">-</div><div class="label">Pipelines</div></div>
|
|
115
155
|
<div class="stat-card purple"><div class="value" id="stat-execs">-</div><div class="label">Total Executions</div></div>
|
|
156
|
+
<div class="stat-card yellow"><div class="value" id="stat-workflow">-</div><div class="label">Workflow</div></div>
|
|
116
157
|
</div>
|
|
117
158
|
|
|
159
|
+
<section>
|
|
160
|
+
<h2>🔄 Current Workflow</h2>
|
|
161
|
+
<div id="workflow-panel"><div class="wf-card"><div class="empty">Loading...</div></div></div>
|
|
162
|
+
</section>
|
|
163
|
+
|
|
118
164
|
<section>
|
|
119
165
|
<h2>Skills</h2>
|
|
120
166
|
<div class="skills-grid" id="skills-grid"><div class="empty">Loading...</div></div>
|
|
@@ -132,6 +178,33 @@ const HTML = `<!DOCTYPE html>
|
|
|
132
178
|
</main>
|
|
133
179
|
<div id="status">Connecting...</div>
|
|
134
180
|
|
|
181
|
+
<!-- Prompt Modal -->
|
|
182
|
+
<div class="modal-overlay" id="modal-prompt">
|
|
183
|
+
<div class="modal">
|
|
184
|
+
<button class="modal-close" onclick="closeModal('modal-prompt')">✕</button>
|
|
185
|
+
<h3>当前步骤提示词</h3>
|
|
186
|
+
<pre id="prompt-content"></pre>
|
|
187
|
+
<div class="modal-footer">
|
|
188
|
+
<button class="btn btn-secondary" onclick="copyPrompt()">复制</button>
|
|
189
|
+
<button class="btn btn-primary" onclick="closeModal('modal-prompt')">关闭</button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<!-- Done Modal -->
|
|
195
|
+
<div class="modal-overlay" id="modal-done">
|
|
196
|
+
<div class="modal">
|
|
197
|
+
<button class="modal-close" onclick="closeModal('modal-done')">✕</button>
|
|
198
|
+
<h3>完成本步 — 填写执行摘要</h3>
|
|
199
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:12px;">摘要将作为下一步的上下文输入,请简要描述本步产出结果。</p>
|
|
200
|
+
<textarea id="done-summary" placeholder="例如:需求文档已完成,用户登录支持手机号+密码,JWT 有效期 7 天..."></textarea>
|
|
201
|
+
<div class="modal-footer">
|
|
202
|
+
<button class="btn btn-secondary" onclick="closeModal('modal-done')">取消</button>
|
|
203
|
+
<button class="btn btn-success" id="btn-confirm-done" onclick="submitDone()">确认完成 →</button>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
135
208
|
<script>
|
|
136
209
|
(async function() {
|
|
137
210
|
const status = document.getElementById('status');
|
|
@@ -142,11 +215,151 @@ const HTML = `<!DOCTYPE html>
|
|
|
142
215
|
return r.json();
|
|
143
216
|
}
|
|
144
217
|
|
|
218
|
+
function esc(str) {
|
|
219
|
+
return String(str || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
220
|
+
}
|
|
221
|
+
|
|
145
222
|
function badge(category) {
|
|
146
223
|
if (!category) return '';
|
|
147
224
|
return '<span class="badge ' + category + '">' + category + '</span>';
|
|
148
225
|
}
|
|
149
226
|
|
|
227
|
+
// ── Workflow Panel ────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
async function renderWorkflow() {
|
|
230
|
+
const panel = document.getElementById('workflow-panel');
|
|
231
|
+
const statEl = document.getElementById('stat-workflow');
|
|
232
|
+
try {
|
|
233
|
+
const data = await fetchJSON('/api/workflow');
|
|
234
|
+
const session = data.session;
|
|
235
|
+
if (!session) {
|
|
236
|
+
panel.innerHTML = '<div class="wf-card"><div class="wf-empty">暂无进行中的工作流。<br><br>在项目目录运行:<code>ethan workflow start dev-workflow -c "任务描述"</code></div></div>';
|
|
237
|
+
statEl.textContent = '-';
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const done = session.steps.filter(s => s.status === 'done').length;
|
|
242
|
+
const total = session.steps.length;
|
|
243
|
+
const pct = Math.round((done / total) * 100);
|
|
244
|
+
const currentIdx = session.steps.findIndex(s => s.status === 'in-progress');
|
|
245
|
+
|
|
246
|
+
statEl.textContent = session.completed ? '已完成' : (pct + '%');
|
|
247
|
+
|
|
248
|
+
const statusIcons = { 'done': '✅', 'in-progress': '▶️', 'pending': '⬜', 'skipped': '⏭️' };
|
|
249
|
+
|
|
250
|
+
const stepsHtml = session.steps.map((step, i) => {
|
|
251
|
+
const isCurrent = i === currentIdx;
|
|
252
|
+
const cls = step.status === 'done' ? 'done' : (isCurrent ? 'current' : '');
|
|
253
|
+
const icon = statusIcons[step.status] || '⬜';
|
|
254
|
+
const summaryHtml = step.summary
|
|
255
|
+
? '<div class="wf-step-summary">' + esc(step.summary) + '</div>'
|
|
256
|
+
: '';
|
|
257
|
+
const currentLabel = isCurrent ? '<div class="wf-step-current-label">▶ 当前步骤</div>' : '';
|
|
258
|
+
return '<div class="wf-step ' + cls + '">' +
|
|
259
|
+
'<div class="wf-step-icon">' + icon + '</div>' +
|
|
260
|
+
'<div class="wf-step-body">' +
|
|
261
|
+
'<div class="wf-step-id">' + (i+1) + '. ' + esc(step.skillId) + '</div>' +
|
|
262
|
+
currentLabel + summaryHtml +
|
|
263
|
+
'</div></div>';
|
|
264
|
+
}).join('');
|
|
265
|
+
|
|
266
|
+
const completedBanner = session.completed
|
|
267
|
+
? '<div class="wf-completed-banner">🎉 工作流已全部完成!运行 <code style="font-family:monospace;font-size:11px">ethan workflow reset</code> 开始新工作流。</div>'
|
|
268
|
+
: '';
|
|
269
|
+
|
|
270
|
+
const actionsHtml = session.completed ? '' :
|
|
271
|
+
'<div class="wf-actions">' +
|
|
272
|
+
'<button class="btn btn-secondary" onclick="showPrompt()">查看当前提示词</button>' +
|
|
273
|
+
'<button class="btn btn-primary" onclick="openDoneModal()">完成本步 →</button>' +
|
|
274
|
+
'</div>';
|
|
275
|
+
|
|
276
|
+
panel.innerHTML = '<div class="wf-card">' +
|
|
277
|
+
completedBanner +
|
|
278
|
+
'<div class="wf-meta">' +
|
|
279
|
+
'<div class="wf-meta-item"><strong>' + esc(session.pipelineName) + '</strong></div>' +
|
|
280
|
+
'<div class="wf-meta-item">ID: ' + esc(session.id) + '</div>' +
|
|
281
|
+
'<div class="wf-meta-item">' + done + ' / ' + total + ' 步完成</div>' +
|
|
282
|
+
'<div class="wf-meta-item">更新: ' + esc(session.updatedAt.slice(0,19).replace('T',' ')) + '</div>' +
|
|
283
|
+
'</div>' +
|
|
284
|
+
'<div class="wf-progress-bar-wrap"><div class="wf-progress-bar" style="width:' + pct + '%"></div></div>' +
|
|
285
|
+
'<div class="wf-steps">' + stepsHtml + '</div>' +
|
|
286
|
+
actionsHtml +
|
|
287
|
+
'</div>';
|
|
288
|
+
} catch (err) {
|
|
289
|
+
panel.innerHTML = '<div class="wf-card"><div class="wf-empty">加载失败: ' + esc(String(err)) + '</div></div>';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Modal helpers ──────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
window.closeModal = function(id) {
|
|
296
|
+
document.getElementById(id).classList.remove('open');
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
document.querySelectorAll('.modal-overlay').forEach(el => {
|
|
300
|
+
el.addEventListener('click', e => { if (e.target === el) el.classList.remove('open'); });
|
|
301
|
+
});
|
|
302
|
+
document.addEventListener('keydown', e => {
|
|
303
|
+
if (e.key === 'Escape') document.querySelectorAll('.modal-overlay.open').forEach(el => el.classList.remove('open'));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
window.showPrompt = async function() {
|
|
307
|
+
const pre = document.getElementById('prompt-content');
|
|
308
|
+
pre.textContent = '加载中...';
|
|
309
|
+
document.getElementById('modal-prompt').classList.add('open');
|
|
310
|
+
try {
|
|
311
|
+
const data = await fetchJSON('/api/workflow/prompt');
|
|
312
|
+
pre.textContent = data.prompt || '(无提示词)';
|
|
313
|
+
} catch (err) {
|
|
314
|
+
pre.textContent = '加载失败: ' + err.message;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
window.copyPrompt = function() {
|
|
319
|
+
const text = document.getElementById('prompt-content').textContent;
|
|
320
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
321
|
+
const btn = document.querySelector('#modal-prompt .btn-secondary');
|
|
322
|
+
const orig = btn.textContent;
|
|
323
|
+
btn.textContent = '✅ 已复制';
|
|
324
|
+
setTimeout(() => { btn.textContent = orig; }, 1500);
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
window.openDoneModal = function() {
|
|
329
|
+
document.getElementById('done-summary').value = '';
|
|
330
|
+
document.getElementById('modal-done').classList.add('open');
|
|
331
|
+
document.getElementById('done-summary').focus();
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
window.submitDone = async function() {
|
|
335
|
+
const summary = document.getElementById('done-summary').value.trim();
|
|
336
|
+
if (!summary) {
|
|
337
|
+
document.getElementById('done-summary').style.borderColor = 'var(--red)';
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
document.getElementById('done-summary').style.borderColor = '';
|
|
341
|
+
const btn = document.getElementById('btn-confirm-done');
|
|
342
|
+
btn.textContent = '提交中...';
|
|
343
|
+
btn.disabled = true;
|
|
344
|
+
try {
|
|
345
|
+
const r = await fetch('/api/workflow/done', {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
headers: { 'Content-Type': 'application/json' },
|
|
348
|
+
body: JSON.stringify({ summary }),
|
|
349
|
+
});
|
|
350
|
+
if (!r.ok) throw new Error(await r.text());
|
|
351
|
+
closeModal('modal-done');
|
|
352
|
+
await renderWorkflow();
|
|
353
|
+
} catch (err) {
|
|
354
|
+
alert('提交失败: ' + err.message);
|
|
355
|
+
} finally {
|
|
356
|
+
btn.textContent = '确认完成 →';
|
|
357
|
+
btn.disabled = false;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// ── Skills ─────────────────────────────────────────────────────────────────
|
|
362
|
+
|
|
150
363
|
function renderSkills(skills) {
|
|
151
364
|
const grid = document.getElementById('skills-grid');
|
|
152
365
|
if (!skills.length) { grid.innerHTML = '<div class="empty">No skills found.</div>'; return; }
|
|
@@ -194,9 +407,7 @@ const HTML = `<!DOCTYPE html>
|
|
|
194
407
|
}).join('');
|
|
195
408
|
}
|
|
196
409
|
|
|
197
|
-
|
|
198
|
-
return String(str || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
199
|
-
}
|
|
410
|
+
// ── Initial load ───────────────────────────────────────────────────────────
|
|
200
411
|
|
|
201
412
|
try {
|
|
202
413
|
const [skills, stats, pipelines] = await Promise.all([
|
|
@@ -221,35 +432,46 @@ const HTML = `<!DOCTYPE html>
|
|
|
221
432
|
status.style.color = '#ef4444';
|
|
222
433
|
status.textContent = 'Error: ' + err.message;
|
|
223
434
|
}
|
|
435
|
+
|
|
436
|
+
// Workflow panel — initial + auto-refresh every 5s
|
|
437
|
+
await renderWorkflow();
|
|
438
|
+
setInterval(renderWorkflow, 5000);
|
|
224
439
|
})();
|
|
225
440
|
</script>
|
|
226
441
|
</body>
|
|
227
442
|
</html>`;
|
|
228
|
-
function
|
|
229
|
-
|
|
443
|
+
function parseBody(req) {
|
|
444
|
+
return new Promise((resolve, reject) => {
|
|
445
|
+
let body = '';
|
|
446
|
+
req.on('data', (chunk) => { body += chunk.toString(); });
|
|
447
|
+
req.on('end', () => resolve(body));
|
|
448
|
+
req.on('error', reject);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function startDashboardServer(port, cwd = process.cwd()) {
|
|
452
|
+
const server = http.createServer(async (req, res) => {
|
|
230
453
|
const url = req.url ?? '/';
|
|
231
454
|
const method = req.method ?? 'GET';
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
res.end('Method Not Allowed');
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (url === '/') {
|
|
455
|
+
// ── GET / ──────────────────────────────────────────��─────────────────────
|
|
456
|
+
if (method === 'GET' && url === '/') {
|
|
238
457
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
239
458
|
res.end(HTML);
|
|
240
459
|
return;
|
|
241
460
|
}
|
|
242
|
-
|
|
461
|
+
// ── GET /api/skills ──────────────────────────────────────────────────────
|
|
462
|
+
if (method === 'GET' && url === '/api/skills') {
|
|
243
463
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
244
464
|
res.end(JSON.stringify(index_1.ALL_SKILLS));
|
|
245
465
|
return;
|
|
246
466
|
}
|
|
247
|
-
|
|
467
|
+
// ── GET /api/stats ───────────────────────────────────────────────────────
|
|
468
|
+
if (method === 'GET' && url === '/api/stats') {
|
|
248
469
|
const statsPath = path.join(os.homedir(), '.ethan-stats.json');
|
|
249
470
|
try {
|
|
250
471
|
const raw = fs.readFileSync(statsPath, 'utf-8');
|
|
472
|
+
const data = JSON.parse(raw);
|
|
251
473
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
252
|
-
res.end(
|
|
474
|
+
res.end(JSON.stringify({ skillUsage: data }));
|
|
253
475
|
}
|
|
254
476
|
catch {
|
|
255
477
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -257,16 +479,95 @@ function startDashboardServer(port) {
|
|
|
257
479
|
}
|
|
258
480
|
return;
|
|
259
481
|
}
|
|
260
|
-
|
|
482
|
+
// ── GET /api/pipelines ───────────────────────────────────────────────────
|
|
483
|
+
if (method === 'GET' && url === '/api/pipelines') {
|
|
261
484
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
262
485
|
res.end(JSON.stringify(pipeline_1.PIPELINES));
|
|
263
486
|
return;
|
|
264
487
|
}
|
|
488
|
+
// ── GET /api/workflow ────────────────────────────────────────────────────
|
|
489
|
+
if (method === 'GET' && url === '/api/workflow') {
|
|
490
|
+
const session = (0, state_1.loadSession)(cwd);
|
|
491
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
492
|
+
res.end(JSON.stringify({ session: session ?? null }));
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
// ── GET /api/workflow/prompt ─────────────────────────────────────────────
|
|
496
|
+
if (method === 'GET' && url === '/api/workflow/prompt') {
|
|
497
|
+
const session = (0, state_1.loadSession)(cwd);
|
|
498
|
+
if (!session) {
|
|
499
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
500
|
+
res.end(JSON.stringify({ error: 'No active workflow session' }));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const currentStep = (0, state_1.getCurrentStep)(session);
|
|
504
|
+
if (!currentStep) {
|
|
505
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
506
|
+
res.end(JSON.stringify({ prompt: '工作流已全部完成。' }));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const skill = index_1.ALL_SKILLS.find((s) => s.id === currentStep.skillId);
|
|
510
|
+
if (!skill) {
|
|
511
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
512
|
+
res.end(JSON.stringify({ error: `Skill not found: ${currentStep.skillId}` }));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const prompt = (0, state_1.buildStepPrompt)(session, currentStep, skill);
|
|
516
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
517
|
+
res.end(JSON.stringify({ prompt }));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
// ── POST /api/workflow/done ──────────────────────────────────────────────
|
|
521
|
+
if (method === 'POST' && url === '/api/workflow/done') {
|
|
522
|
+
let body;
|
|
523
|
+
try {
|
|
524
|
+
body = await parseBody(req);
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
528
|
+
res.end(JSON.stringify({ error: 'Failed to read request body' }));
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
let summary;
|
|
532
|
+
try {
|
|
533
|
+
const parsed = JSON.parse(body);
|
|
534
|
+
summary = (parsed.summary ?? '').trim();
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
538
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (!summary) {
|
|
542
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
543
|
+
res.end(JSON.stringify({ error: 'summary is required' }));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const session = (0, state_1.loadSession)(cwd);
|
|
547
|
+
if (!session) {
|
|
548
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
549
|
+
res.end(JSON.stringify({ error: 'No active workflow session' }));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (session.completed) {
|
|
553
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
554
|
+
res.end(JSON.stringify({ error: 'Workflow already completed' }));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
(0, state_1.markStepDone)(session, summary, cwd);
|
|
558
|
+
const updated = (0, state_1.loadSession)(cwd);
|
|
559
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
560
|
+
res.end(JSON.stringify({ session: updated }));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
// ── 404 ──────────────────────────────────────────────────────────────────
|
|
265
564
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
266
565
|
res.end('Not Found');
|
|
267
566
|
});
|
|
268
567
|
server.listen(port, () => {
|
|
269
|
-
console.log(`\n🌐 Ethan Dashboard running at http://localhost:${port}
|
|
568
|
+
console.log(`\n🌐 Ethan Dashboard running at http://localhost:${port}`);
|
|
569
|
+
console.log(` Workflow panel auto-refreshes every 5s`);
|
|
570
|
+
console.log(` Press Ctrl+C to stop.\n`);
|
|
270
571
|
});
|
|
271
572
|
}
|
|
272
573
|
//# sourceMappingURL=dashboard.js.map
|