@geminilight/mindos 0.6.29 → 0.6.30

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.
Files changed (71) hide show
  1. package/README.md +10 -4
  2. package/app/app/api/acp/config/route.ts +82 -0
  3. package/app/app/api/acp/detect/route.ts +71 -48
  4. package/app/app/api/acp/install/route.ts +51 -0
  5. package/app/app/api/acp/session/route.ts +141 -11
  6. package/app/app/api/ask/route.ts +116 -13
  7. package/app/app/api/workflows/route.ts +156 -0
  8. package/app/app/page.tsx +7 -2
  9. package/app/components/ActivityBar.tsx +12 -4
  10. package/app/components/AskModal.tsx +4 -1
  11. package/app/components/FileTree.tsx +21 -10
  12. package/app/components/HomeContent.tsx +1 -0
  13. package/app/components/Panel.tsx +1 -0
  14. package/app/components/RightAskPanel.tsx +5 -1
  15. package/app/components/SidebarLayout.tsx +6 -0
  16. package/app/components/agents/AgentDetailContent.tsx +263 -47
  17. package/app/components/agents/AgentsContentPage.tsx +11 -0
  18. package/app/components/agents/AgentsPanelA2aTab.tsx +285 -46
  19. package/app/components/agents/AgentsPanelSessionsTab.tsx +166 -0
  20. package/app/components/agents/agents-content-model.ts +2 -2
  21. package/app/components/ask/AgentSelectorCapsule.tsx +218 -0
  22. package/app/components/ask/AskContent.tsx +197 -239
  23. package/app/components/ask/FileChip.tsx +82 -17
  24. package/app/components/ask/MentionPopover.tsx +21 -3
  25. package/app/components/ask/MessageList.tsx +30 -9
  26. package/app/components/ask/SlashCommandPopover.tsx +21 -3
  27. package/app/components/panels/AgentsPanel.tsx +1 -0
  28. package/app/components/panels/AgentsPanelHubNav.tsx +9 -2
  29. package/app/components/panels/WorkflowsPanel.tsx +206 -0
  30. package/app/components/renderers/workflow-yaml/StepEditor.tsx +157 -0
  31. package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +201 -0
  32. package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +226 -0
  33. package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +126 -0
  34. package/app/components/renderers/workflow-yaml/execution.ts +177 -0
  35. package/app/components/renderers/workflow-yaml/index.ts +6 -0
  36. package/app/components/renderers/workflow-yaml/manifest.ts +21 -0
  37. package/app/components/renderers/workflow-yaml/parser.ts +172 -0
  38. package/app/components/renderers/workflow-yaml/selectors.tsx +522 -0
  39. package/app/components/renderers/workflow-yaml/serializer.ts +56 -0
  40. package/app/components/renderers/workflow-yaml/types.ts +46 -0
  41. package/app/hooks/useAcpConfig.ts +96 -0
  42. package/app/hooks/useAcpDetection.ts +69 -14
  43. package/app/hooks/useAcpRegistry.ts +46 -11
  44. package/app/hooks/useAskModal.ts +12 -5
  45. package/app/hooks/useAskPanel.ts +8 -5
  46. package/app/hooks/useAskSession.ts +19 -2
  47. package/app/hooks/useImageUpload.ts +152 -0
  48. package/app/lib/acp/acp-tools.ts +3 -1
  49. package/app/lib/acp/agent-descriptors.ts +274 -0
  50. package/app/lib/acp/bridge.ts +6 -0
  51. package/app/lib/acp/index.ts +20 -4
  52. package/app/lib/acp/registry.ts +74 -7
  53. package/app/lib/acp/session.ts +481 -28
  54. package/app/lib/acp/subprocess.ts +307 -21
  55. package/app/lib/acp/types.ts +158 -20
  56. package/app/lib/agent/model.ts +18 -3
  57. package/app/lib/agent/to-agent-messages.ts +25 -2
  58. package/app/lib/i18n/modules/knowledge.ts +4 -0
  59. package/app/lib/i18n/modules/navigation.ts +2 -0
  60. package/app/lib/i18n/modules/panels.ts +146 -2
  61. package/app/lib/pi-integration/skills.ts +21 -6
  62. package/app/lib/renderers/index.ts +2 -2
  63. package/app/lib/settings.ts +10 -0
  64. package/app/lib/types.ts +12 -1
  65. package/app/next-env.d.ts +1 -1
  66. package/app/package.json +3 -1
  67. package/package.json +1 -1
  68. package/templates/en/.mindos/workflows/Sprint Release.flow.yaml +130 -0
  69. package/templates/zh/.mindos/workflows//345/221/250/350/277/255/344/273/243/346/243/200/346/237/245.flow.yaml +84 -0
  70. package/app/components/renderers/workflow/WorkflowRenderer.tsx +0 -409
  71. package/app/components/renderers/workflow/manifest.ts +0 -14
@@ -0,0 +1,84 @@
1
+ title: 周迭代检查工作流
2
+ description: 每周进行代码审查、测试运行和文档更新的标准工作流
3
+
4
+ # 全局配置 - 此工作流可用的技能
5
+ skills:
6
+ - software-architecture
7
+ - code-review-quality
8
+
9
+ # 工作流步骤 - 按顺序执行
10
+ steps:
11
+ # 第 1 步:运行测试套件
12
+ - id: run_tests
13
+ name: 运行测试
14
+ description: 执行完整测试套件并报告覆盖率
15
+ agent: cursor
16
+ prompt: |
17
+ 执行此项目的完整测试套件。
18
+
19
+ 提供以下信息:
20
+ 1. 运行的总测试数、通过数、失败数
21
+ 2. 覆盖率报告(行/分支/函数)
22
+ 3. 任何严重失败及修复建议
23
+ 4. 耗时
24
+
25
+ 如果有失败,列出前 3 个最严重的失败及建议的修复方案。
26
+ timeout: 120
27
+
28
+ # 第 2 步:代码审查
29
+ - id: code_review
30
+ name: 代码审查
31
+ description: 使用标准进行代码审查
32
+ agent: claude-code
33
+ skill: code-review-quality
34
+ prompt: |
35
+ 使用代码审查标准对最近的代码变更进行审查。
36
+
37
+ 评估以下方面:
38
+ 1. 正确性 - 错误处理、边界情况
39
+ 2. 安全性 - 认证、输入验证、密钥管理
40
+ 3. 性能 - 查询优化、缓存策略、复杂度
41
+ 4. 可维护性 - 命名规范、测试覆盖、文档完整性
42
+ 5. 架构 - 是否遵循项目模式,有无反模式
43
+
44
+ 给出评分(如 8.5/10)和主要建议。
45
+ timeout: 120
46
+
47
+ # 第 3 步:更新文档
48
+ - id: update_docs
49
+ name: 更新文档
50
+ description: 同步 CHANGELOG 和 README
51
+ skill: document-release
52
+ prompt: |
53
+ 根据本次发布的变更更新项目文档:
54
+
55
+ 1. 更新 CHANGELOG.md,遵循语义化版本规范
56
+ 2. 如果功能或 API 变更,更新 README.md
57
+ 3. 如果端点变更,更新 API 文档
58
+ 4. 验证所有文档链接有效
59
+ 5. 检查代码示例是否匹配当前实现
60
+
61
+ 提供所有文档变更的摘要。
62
+ timeout: 60
63
+
64
+ # 第 4 步:发布前最终检查
65
+ - id: final_check
66
+ name: 发布前验证
67
+ description: 最终确认一切就绪
68
+ agent: mindos
69
+ prompt: |
70
+ 进行最终的发布前验证检查:
71
+
72
+ 清单:
73
+ ☐ 所有测试通过
74
+ ☐ 代码审查完成并问题已解决
75
+ ☐ 文档已更新并链接有效
76
+ ☐ 版本号正确升级(语义化版本)
77
+ ☐ Git 标签和提交干净
78
+ ☐ 无未提交的更改
79
+ ☐ CHANGELOG 条目存在
80
+ ☐ 依赖项已更新并锁定
81
+ ☐ 性能基准可接受
82
+
83
+ 确认发布就绪或列出任何阻碍因素。
84
+ timeout: 60
@@ -1,409 +0,0 @@
1
- 'use client';
2
-
3
- import { useMemo, useState, useRef, useCallback } from 'react';
4
- import { Play, SkipForward, RotateCcw, CheckCircle2, Circle, Loader2, AlertCircle, ChevronDown, Sparkles } from 'lucide-react';
5
- import type { RendererContext } from '@/lib/renderers/registry';
6
- import { useLocale } from '@/lib/LocaleContext';
7
-
8
- // ─── Types ────────────────────────────────────────────────────────────────────
9
-
10
- type StepStatus = 'pending' | 'running' | 'done' | 'skipped' | 'error';
11
-
12
- interface WorkflowStep {
13
- index: number;
14
- heading: string; // full heading text e.g. "Step 1: Gather requirements"
15
- body: string; // body text below heading
16
- status: StepStatus;
17
- output: string; // AI output for this step
18
- }
19
-
20
- interface WorkflowMeta {
21
- title: string;
22
- description: string;
23
- }
24
-
25
- // ─── Parser ───────────────────────────────────────────────────────────────────
26
-
27
- function parseWorkflow(content: string): { meta: WorkflowMeta; steps: WorkflowStep[] } {
28
- const lines = content.split('\n');
29
- let title = '';
30
- let description = '';
31
- const steps: WorkflowStep[] = [];
32
- let currentStep: { heading: string; bodyLines: string[] } | null = null;
33
- let inMeta = true;
34
- const metaLines: string[] = [];
35
-
36
- const flushStep = () => {
37
- if (!currentStep) return;
38
- steps.push({
39
- index: steps.length,
40
- heading: currentStep.heading,
41
- body: currentStep.bodyLines.join('\n').trim(),
42
- status: 'pending',
43
- output: '',
44
- });
45
- currentStep = null;
46
- };
47
-
48
- for (const line of lines) {
49
- if (/^# /.test(line)) {
50
- title = line.slice(2).trim();
51
- inMeta = true;
52
- continue;
53
- }
54
- // H2 = step
55
- if (/^## /.test(line)) {
56
- flushStep();
57
- inMeta = false;
58
- currentStep = { heading: line.slice(3).trim(), bodyLines: [] };
59
- continue;
60
- }
61
- if (currentStep) {
62
- currentStep.bodyLines.push(line);
63
- } else if (inMeta) {
64
- metaLines.push(line);
65
- }
66
- }
67
- flushStep();
68
-
69
- description = metaLines.filter(l => l.trim() && !/^#/.test(l)).join(' ').trim().slice(0, 200);
70
-
71
- return { meta: { title, description }, steps };
72
- }
73
-
74
- // ─── Inline markdown renderer ─────────────────────────────────────────────────
75
-
76
- function renderInline(text: string): string {
77
- return text
78
- .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
79
- .replace(/`(.+?)`/g, `<code class="font-display" style="font-size:.82em;padding:1px 5px;border-radius:4px;background:var(--muted)">$1</code>`)
80
- .replace(/\*(.+?)\*/g, '<em>$1</em>');
81
- }
82
-
83
- function renderBody(body: string): string {
84
- return body.split('\n').map(line => {
85
- if (!line.trim()) return '';
86
- if (/^- /.test(line)) return `<li style="margin:.2em 0;font-size:.82rem;color:var(--muted-foreground)">${renderInline(line.slice(2))}</li>`;
87
- return `<p style="margin:.3em 0;font-size:.82rem;line-height:1.6;color:var(--muted-foreground)">${renderInline(line)}</p>`;
88
- }).join('');
89
- }
90
-
91
- // ─── Status icon ─────────────────────────────────────────────────────────────
92
-
93
- function StatusIcon({ status }: { status: StepStatus }) {
94
- if (status === 'pending') return <Circle size={15} style={{ color: 'var(--border)' }} />;
95
- if (status === 'running') return <Loader2 size={15} style={{ color: 'var(--amber)', animation: 'spin 1s linear infinite' }} />;
96
- if (status === 'done') return <CheckCircle2 size={15} style={{ color: 'var(--success)' }} />;
97
- if (status === 'skipped') return <SkipForward size={15} style={{ color: 'var(--muted-foreground)', opacity: .5 }} />;
98
- return <AlertCircle size={15} style={{ color: 'var(--error)' }} />;
99
- }
100
-
101
- const STATUS_BORDER: Record<StepStatus, string> = {
102
- pending: 'var(--border)',
103
- running: 'rgba(200,135,58,0.5)',
104
- done: 'rgba(122,173,128,0.4)',
105
- skipped: 'var(--border)',
106
- error: 'rgba(200,80,80,0.4)',
107
- };
108
-
109
- // ─── AI execution ─────────────────────────────────────────────────────────────
110
-
111
- async function runStepWithAI(
112
- step: WorkflowStep,
113
- filePath: string,
114
- allStepsSummary: string,
115
- onChunk: (chunk: string) => void,
116
- signal: AbortSignal,
117
- ): Promise<void> {
118
- const prompt = `You are executing step ${step.index + 1} of a SOP/Workflow: "${step.heading}".
119
-
120
- Context of the full workflow:
121
- ${allStepsSummary}
122
-
123
- Current step instructions:
124
- ${step.body || '(No specific instructions — use common sense for this step.)'}
125
-
126
- Execute this step concisely. Provide:
127
- 1. What you did / what the output is
128
- 2. Any decisions made
129
- 3. What the next step should watch out for
130
-
131
- Be specific and actionable. Format in Markdown.`;
132
-
133
- const res = await fetch('/api/ask', {
134
- method: 'POST',
135
- headers: { 'Content-Type': 'application/json' },
136
- body: JSON.stringify({
137
- messages: [{ role: 'user', content: prompt }],
138
- currentFile: filePath,
139
- }),
140
- signal,
141
- });
142
-
143
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
144
- if (!res.body) throw new Error('No response body');
145
-
146
- const reader = res.body.getReader();
147
- const decoder = new TextDecoder();
148
- let acc = '';
149
-
150
- while (true) {
151
- const { done, value } = await reader.read();
152
- if (done) break;
153
- const raw = decoder.decode(value, { stream: true });
154
- for (const line of raw.split('\n')) {
155
- const m = line.match(/^0:"((?:[^"\\]|\\.)*)"$/);
156
- if (m) {
157
- acc += m[1].replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
158
- onChunk(acc);
159
- }
160
- }
161
- }
162
- }
163
-
164
- // ─── Step card ────────────────────────────────────────────────────────────────
165
-
166
- function StepCard({
167
- step, isActive, onRun, onSkip, canRun,
168
- }: {
169
- step: WorkflowStep;
170
- isActive: boolean;
171
- onRun: () => void;
172
- onSkip: () => void;
173
- canRun: boolean;
174
- }) {
175
- const { t } = useLocale();
176
- const [expanded, setExpanded] = useState(false);
177
- const hasBody = step.body.trim().length > 0;
178
- const hasOutput = step.output.length > 0;
179
-
180
- return (
181
- <div style={{
182
- border: `1px solid ${STATUS_BORDER[step.status]}`,
183
- borderRadius: 10,
184
- overflow: 'hidden',
185
- background: 'var(--card)',
186
- opacity: step.status === 'skipped' ? 0.6 : 1,
187
- transition: 'border-color .2s, opacity .2s',
188
- }}>
189
- {/* header */}
190
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 14px' }}>
191
- <StatusIcon status={step.status} />
192
- <span
193
- style={{ flex: 1, fontWeight: 600, fontSize: '.88rem', color: 'var(--foreground)', cursor: hasBody || hasOutput ? 'pointer' : 'default' }}
194
- onClick={() => (hasBody || hasOutput) && setExpanded(v => !v)}
195
- >
196
- {step.heading}
197
- </span>
198
-
199
- {/* action buttons */}
200
- <div style={{ display: 'flex', gap: 5, flexShrink: 0 }}>
201
- {step.status === 'pending' && (
202
- <>
203
- <button
204
- onClick={onRun}
205
- disabled={!canRun}
206
- title={!canRun ? t.hints.workflowStepRunning : undefined}
207
- style={{
208
- display: 'flex', alignItems: 'center', gap: 4,
209
- padding: '3px 10px', borderRadius: 6, fontSize: '0.72rem',
210
- cursor: canRun ? 'pointer' : 'not-allowed',
211
- border: 'none', background: canRun ? 'var(--amber)' : 'var(--muted)',
212
- color: canRun ? 'var(--amber-foreground)' : 'var(--muted-foreground)',
213
- opacity: canRun ? 1 : 0.5,
214
- }}
215
- >
216
- <Play size={10} /> Run
217
- </button>
218
- <button
219
- onClick={onSkip}
220
- style={{
221
- padding: '3px 8px', borderRadius: 6, fontSize: '0.72rem',
222
- cursor: 'pointer',
223
- border: '1px solid var(--border)', background: 'transparent',
224
- color: 'var(--muted-foreground)',
225
- }}
226
- >
227
- Skip
228
- </button>
229
- </>
230
- )}
231
- {step.status === 'running' && (
232
- <span className="font-display" style={{ fontSize: '0.7rem', color: 'var(--amber)' }}>executing…</span>
233
- )}
234
- {(step.status === 'done' || step.status === 'error') && (
235
- <button
236
- onClick={() => setExpanded(v => !v)}
237
- style={{ padding: '3px 8px', borderRadius: 6, fontSize: '0.72rem', cursor: 'pointer', border: '1px solid var(--border)', background: 'transparent', color: 'var(--muted-foreground)' }}
238
- >
239
- <ChevronDown size={11} style={{ display: 'inline', transform: expanded ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }} />
240
- </button>
241
- )}
242
- </div>
243
- </div>
244
-
245
- {/* body / output */}
246
- {(expanded || step.status === 'running') && (hasBody || hasOutput) && (
247
- <div style={{ borderTop: '1px solid var(--border)' }}>
248
- {hasBody && (
249
- <div style={{ padding: '10px 14px', borderBottom: hasOutput ? '1px solid var(--border)' : 'none' }}>
250
- <div dangerouslySetInnerHTML={{ __html: renderBody(step.body) }} />
251
- </div>
252
- )}
253
- {hasOutput && (
254
- <div style={{ padding: '10px 14px', background: 'var(--background)', position: 'relative' }}>
255
- <div style={{ display: 'flex', alignItems: 'center', gap: 5, marginBottom: 6 }}>
256
- <Sparkles size={11} style={{ color: 'var(--amber)' }} />
257
- <span className="font-display" style={{ fontSize: '0.68rem', color: 'var(--muted-foreground)', textTransform: 'uppercase', letterSpacing: '.06em' }}>AI Output</span>
258
- {step.status === 'running' && <span style={{ width: 5, height: 5, borderRadius: '50%', background: 'var(--amber)', animation: 'pulse 1.2s ease-in-out infinite', marginLeft: 4 }} />}
259
- </div>
260
- <div style={{ fontSize: '.82rem', lineHeight: 1.7, color: 'var(--foreground)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
261
- {step.output}
262
- </div>
263
- </div>
264
- )}
265
- </div>
266
- )}
267
- </div>
268
- );
269
- }
270
-
271
- // ─── Main renderer ────────────────────────────────────────────────────────────
272
-
273
- export function WorkflowRenderer({ filePath, content }: RendererContext) {
274
- const { t } = useLocale();
275
- const parsed = useMemo(() => parseWorkflow(content), [content]);
276
- const [steps, setSteps] = useState<WorkflowStep[]>(() => parsed.steps);
277
- const [running, setRunning] = useState(false);
278
- const abortRef = useRef<AbortController | null>(null);
279
-
280
- // Reset when content changes externally
281
- useMemo(() => { setSteps(parsed.steps.map(s => ({ ...s, status: 'pending' as StepStatus, output: '' }))); }, [parsed]);
282
-
283
- const allStepsSummary = useMemo(() =>
284
- parsed.steps.map((s, i) => `${i + 1}. ${s.heading}`).join('\n'),
285
- [parsed]);
286
-
287
- const runStep = useCallback(async (idx: number) => {
288
- if (running) return;
289
- abortRef.current?.abort();
290
- const ctrl = new AbortController();
291
- abortRef.current = ctrl;
292
- setRunning(true);
293
-
294
- setSteps(prev => prev.map((s, i) =>
295
- i === idx ? { ...s, status: 'running', output: '' } : s));
296
-
297
- try {
298
- await runStepWithAI(
299
- steps[idx], filePath, allStepsSummary,
300
- (chunk) => setSteps(prev => prev.map((s, i) =>
301
- i === idx ? { ...s, output: chunk } : s)),
302
- ctrl.signal,
303
- );
304
- setSteps(prev => prev.map((s, i) =>
305
- i === idx ? { ...s, status: 'done' } : s));
306
- } catch (err: unknown) {
307
- if (err instanceof Error && err.name === 'AbortError') return;
308
- setSteps(prev => prev.map((s, i) =>
309
- i === idx ? { ...s, status: 'error', output: (err instanceof Error ? err.message : String(err)) } : s));
310
- } finally {
311
- setRunning(false);
312
- }
313
- }, [running, steps, filePath, allStepsSummary]);
314
-
315
- const skipStep = useCallback((idx: number) => {
316
- setSteps(prev => prev.map((s, i) =>
317
- i === idx ? { ...s, status: 'skipped' } : s));
318
- }, []);
319
-
320
- const reset = useCallback(() => {
321
- abortRef.current?.abort();
322
- setRunning(false);
323
- setSteps(parsed.steps.map(s => ({ ...s, status: 'pending' as StepStatus, output: '' })));
324
- }, [parsed]);
325
-
326
- // Next runnable step = first pending step
327
- const nextPendingIdx = steps.findIndex(s => s.status === 'pending');
328
- const doneCount = steps.filter(s => s.status === 'done').length;
329
- const progress = steps.length > 0 ? Math.round((doneCount / steps.length) * 100) : 0;
330
-
331
- if (steps.length === 0) {
332
- return (
333
- <div className="font-display" style={{ padding: '3rem 1rem', textAlign: 'center', color: 'var(--muted-foreground)', fontSize: 12 }}>
334
- No steps found. Add <code style={{ background: 'var(--muted)', padding: '1px 5px', borderRadius: 4 }}>## Step N: …</code> headings to define workflow steps.
335
- </div>
336
- );
337
- }
338
-
339
- return (
340
- <div style={{ maxWidth: 720, margin: '0 auto', padding: '1.5rem 0' }}>
341
- {/* header */}
342
- <div style={{ marginBottom: '1.2rem' }}>
343
- {parsed.meta.description && (
344
- <p style={{ fontSize: '.82rem', color: 'var(--muted-foreground)', lineHeight: 1.6, marginBottom: 12 }}>
345
- {parsed.meta.description}
346
- </p>
347
- )}
348
-
349
- {/* progress + actions */}
350
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
351
- {/* progress bar */}
352
- <div style={{ flex: 1, minWidth: 120, height: 4, borderRadius: 999, background: 'var(--border)', overflow: 'hidden' }}>
353
- <div style={{ height: '100%', width: `${progress}%`, background: 'var(--amber)', borderRadius: 999, transition: 'width .3s' }} />
354
- </div>
355
- <span className="font-display" style={{ fontSize: '0.7rem', color: 'var(--muted-foreground)', flexShrink: 0 }}>
356
- {doneCount}/{steps.length} done
357
- </span>
358
-
359
- {/* run next */}
360
- {nextPendingIdx >= 0 && (
361
- <button
362
- onClick={() => runStep(nextPendingIdx)}
363
- disabled={running}
364
- title={running ? t.hints.workflowRunning : undefined}
365
- style={{
366
- display: 'flex', alignItems: 'center', gap: 5,
367
- padding: '4px 12px', borderRadius: 7, fontSize: '0.75rem',
368
- cursor: running ? 'not-allowed' : 'pointer',
369
- border: 'none', background: running ? 'var(--muted)' : 'var(--amber)',
370
- color: running ? 'var(--muted-foreground)' : 'var(--amber-foreground)',
371
- opacity: running ? 0.7 : 1,
372
- }}
373
- >
374
- {running ? <Loader2 size={11} style={{ animation: 'spin 1s linear infinite' }} /> : <Play size={11} />}
375
- Run next
376
- </button>
377
- )}
378
-
379
- {/* reset */}
380
- <button
381
- onClick={reset}
382
- style={{ padding: '4px 10px', borderRadius: 7, fontSize: '0.75rem', cursor: 'pointer', border: '1px solid var(--border)', background: 'transparent', color: 'var(--muted-foreground)', display: 'flex', alignItems: 'center', gap: 4 }}
383
- >
384
- <RotateCcw size={11} /> Reset
385
- </button>
386
- </div>
387
- </div>
388
-
389
- {/* step list */}
390
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
391
- {steps.map((step, i) => (
392
- <StepCard
393
- key={i}
394
- step={step}
395
- isActive={i === nextPendingIdx}
396
- canRun={!running}
397
- onRun={() => runStep(i)}
398
- onSkip={() => skipStep(i)}
399
- />
400
- ))}
401
- </div>
402
-
403
- <style>{`
404
- @keyframes spin { to { transform: rotate(360deg); } }
405
- @keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.3; } }
406
- `}</style>
407
- </div>
408
- );
409
- }
@@ -1,14 +0,0 @@
1
- import type { RendererDefinition } from '@/lib/renderers/registry';
2
-
3
- export const manifest: RendererDefinition = {
4
- id: 'workflow',
5
- name: 'Workflow Runner',
6
- description: 'Parses step-by-step workflow markdown into an interactive runner. Execute steps sequentially with AI assistance.',
7
- author: 'MindOS',
8
- icon: '⚡',
9
- tags: ['workflow', 'automation', 'steps', 'ai'],
10
- builtin: true,
11
- entryPath: 'Workflow.md',
12
- match: ({ filePath }) => /\b(Workflow|workflow|WORKFLOW)\b.*\.md$/i.test(filePath),
13
- load: () => import('./WorkflowRenderer').then(m => ({ default: m.WorkflowRenderer })),
14
- };